106 lines
2.3 KiB
Go
106 lines
2.3 KiB
Go
//go:build linux || freebsd
|
|||
|
|
// +build linux freebsd
|
||
|
|
|
||
|
|
package dataurl
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/base64"
|
||
|
|
"io"
|
||
|
|
"net/url"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"codeberg.org/petrbalvin/goget/internal/core"
|
||
|
|
"codeberg.org/petrbalvin/goget/internal/protocol"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Protocol handles data: URLs (RFC 2397).
|
||
|
|
type Protocol struct {
|
||
|
|
*protocol.BaseProtocol
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewProtocol creates a new data: URL protocol handler.
|
||
|
|
func NewProtocol() *Protocol {
|
||
|
|
return &Protocol{
|
||
|
|
BaseProtocol: protocol.NewBaseProtocol(protocol.ProtocolInfo{
|
||
|
|
Name: "DATA",
|
||
|
|
Scheme: "data",
|
||
|
|
DefaultPort: 0,
|
||
|
|
Features: []string{},
|
||
|
|
}),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Protocol) Download(ctx context.Context, req *core.DownloadRequest) (*core.DownloadResult, error) {
|
||
|
|
startTime := time.Now()
|
||
|
|
|
||
|
|
// data:[<mediatype>][;base64],<data>
|
||
|
|
data, err := Decode(req.URL.String())
|
||
|
|
if err != nil {
|
||
|
|
return nil, core.NewProtocolError("failed to decode data URL", err, core.SafeURL(req.URL))
|
||
|
|
}
|
||
|
|
|
||
|
|
var writer io.Writer
|
||
|
|
outputPath := req.Output
|
||
|
|
|
||
|
|
if req.Writer != nil {
|
||
|
|
writer = req.Writer
|
||
|
|
} else if outputPath == "" || outputPath == "-" {
|
||
|
|
writer = os.Stdout
|
||
|
|
outputPath = "-"
|
||
|
|
} else {
|
||
|
|
f, err := os.Create(outputPath)
|
||
|
|
if err != nil {
|
||
|
|
return nil, core.NewFileError("failed to create output file", err)
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
writer = f
|
||
|
|
}
|
||
|
|
|
||
|
|
n, err := writer.Write(data)
|
||
|
|
if err != nil {
|
||
|
|
return nil, core.NewFileError("failed to write data URL content", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
duration := time.Since(startTime)
|
||
|
|
|
||
|
|
return &core.DownloadResult{
|
||
|
|
BytesDownloaded: int64(n),
|
||
|
|
TotalSize: int64(len(data)),
|
||
|
|
Duration: duration,
|
||
|
|
Protocol: "DATA",
|
||
|
|
IPVersion: 0,
|
||
|
|
Speed: float64(n) / duration.Seconds(),
|
||
|
|
OutputPath: outputPath,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Decode parses and decodes a data: URL.
|
||
|
|
func Decode(rawURL string) ([]byte, error) {
|
||
|
|
if !strings.HasPrefix(rawURL, "data:") {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
content := strings.TrimPrefix(rawURL, "data:")
|
||
|
|
isBase64 := false
|
||
|
|
|
||
|
|
if idx := strings.Index(content, ";base64,"); idx >= 0 {
|
||
|
|
isBase64 = true
|
||
|
|
content = content[idx+8:]
|
||
|
|
} else if idx := strings.Index(content, ","); idx >= 0 {
|
||
|
|
content = content[idx+1:]
|
||
|
|
}
|
||
|
|
|
||
|
|
decoded, err := url.PathUnescape(content)
|
||
|
|
if err != nil {
|
||
|
|
decoded = content
|
||
|
|
}
|
||
|
|
|
||
|
|
if isBase64 {
|
||
|
|
return base64.StdEncoding.DecodeString(decoded)
|
||
|
|
}
|
||
|
|
return []byte(decoded), nil
|
||
|
|
}
|