feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user