feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type bzip2Decompressor struct{ *BaseDecompressor }
|
||||
|
||||
func NewBzip2Decompressor() *bzip2Decompressor {
|
||||
return &bzip2Decompressor{NewBaseDecompressor("bzip2", []string{"bzip2"}, []string{".bz2", ".tbz", ".tbz2"})}
|
||||
}
|
||||
|
||||
// Reader returns a bzip2 decompression reader wrapped in a NopCloser.
|
||||
// compress/bzip2.NewReader returns a plain io.Reader (no Close), so we
|
||||
// wrap it to satisfy the io.ReadCloser contract of the Decompressor
|
||||
// interface — the bzip2 reader has no internal state that needs
|
||||
// releasing.
|
||||
func (b *bzip2Decompressor) Reader(r io.Reader) (io.ReadCloser, error) {
|
||||
return io.NopCloser(bzip2.NewReader(r)), nil
|
||||
}
|
||||
|
||||
type bzip2Compressor struct{ name string }
|
||||
|
||||
func NewBzip2Compressor() *bzip2Compressor { return &bzip2Compressor{name: "bzip2"} }
|
||||
func (b *bzip2Compressor) Name() string { return b.name }
|
||||
func (b *bzip2Compressor) Writer(w io.Writer) (io.WriteCloser, error) {
|
||||
return nil, fmt.Errorf("bzip2 compression not supported in stdlib")
|
||||
}
|
||||
|
||||
func init() { Register(NewBzip2Decompressor()) }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type flateDecompressor struct{ *BaseDecompressor }
|
||||
|
||||
func NewFlateDecompressor() *flateDecompressor {
|
||||
return &flateDecompressor{NewBaseDecompressor("flate", []string{}, []string{".deflate", ".fl"})}
|
||||
}
|
||||
|
||||
// ✅ Oprava: flate.NewReader returns io.ReadCloser, ale my returnsme io.Reader
|
||||
func (f *flateDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
|
||||
return flate.NewReader(r), nil
|
||||
}
|
||||
|
||||
type flateCompressor struct {
|
||||
name string
|
||||
level int
|
||||
}
|
||||
|
||||
func NewFlateCompressor() *flateCompressor {
|
||||
return &flateCompressor{name: "flate", level: flate.DefaultCompression}
|
||||
}
|
||||
|
||||
func NewFlateCompressorWithLevel(level int) (*flateCompressor, error) {
|
||||
if level < flate.HuffmanOnly || level > flate.BestCompression {
|
||||
return nil, fmt.Errorf("invalid compression level: %d (must be %d-%d)",
|
||||
level, flate.HuffmanOnly, flate.BestCompression)
|
||||
}
|
||||
return &flateCompressor{name: "flate", level: level}, nil
|
||||
}
|
||||
|
||||
func (f *flateCompressor) Name() string { return f.name }
|
||||
func (f *flateCompressor) Writer(w io.Writer) (io.WriteCloser, error) {
|
||||
return flate.NewWriter(w, f.level)
|
||||
}
|
||||
|
||||
func init() { Register(NewFlateDecompressor()) }
|
||||
@@ -0,0 +1,30 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
)
|
||||
|
||||
type gzipDecompressor struct{ *BaseDecompressor }
|
||||
|
||||
func NewGzipDecompressor() *gzipDecompressor {
|
||||
return &gzipDecompressor{NewBaseDecompressor("gzip", []string{"gzip"}, []string{".gz"})}
|
||||
}
|
||||
|
||||
// ✅ Oprava: returns (io.Reader, error)
|
||||
func (g *gzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
|
||||
return gzip.NewReader(r)
|
||||
}
|
||||
|
||||
type gzipCompressor struct{ name string }
|
||||
|
||||
func NewGzipCompressor() *gzipCompressor { return &gzipCompressor{name: "gzip"} }
|
||||
func (g *gzipCompressor) Name() string { return g.name }
|
||||
func (g *gzipCompressor) Writer(w io.Writer) (io.WriteCloser, error) {
|
||||
return gzip.NewWriter(w), nil
|
||||
}
|
||||
|
||||
func init() { Register(NewGzipDecompressor()) }
|
||||
@@ -0,0 +1,150 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Decompressor defines the interface for decompression
|
||||
type Decompressor interface {
|
||||
Name() string
|
||||
CanHandle(contentEncoding string) bool
|
||||
CanHandleFile(filename string) bool
|
||||
// Reader returns an io.ReadCloser (not just an io.Reader) so
|
||||
// callers can release the decompressor's internal state — decoder
|
||||
// tables, dictionaries, etc. — when finished. Forgetting to call
|
||||
// Close on a long-lived mirror scan would let decoder state
|
||||
// accumulate without bound.
|
||||
Reader(r io.Reader) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Compressor defines the interface for compression
|
||||
type Compressor interface {
|
||||
Name() string
|
||||
Writer(w io.Writer) (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
// BaseDecompressor is the base implementation
|
||||
type BaseDecompressor struct {
|
||||
name string
|
||||
contentEncodings []string
|
||||
fileExtensions []string
|
||||
}
|
||||
|
||||
func NewBaseDecompressor(name string, contentEncodings, fileExtensions []string) *BaseDecompressor {
|
||||
return &BaseDecompressor{
|
||||
name: name,
|
||||
contentEncodings: contentEncodings,
|
||||
fileExtensions: fileExtensions,
|
||||
}
|
||||
}
|
||||
|
||||
func (bd *BaseDecompressor) Name() string { return bd.name }
|
||||
func (bd *BaseDecompressor) Extensions() []string { return bd.fileExtensions }
|
||||
func (bd *BaseDecompressor) CanHandle(contentEncoding string) bool {
|
||||
for _, e := range bd.contentEncodings {
|
||||
if strings.EqualFold(e, contentEncoding) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (bd *BaseDecompressor) CanHandleFile(filename string) bool {
|
||||
filename = strings.ToLower(filename)
|
||||
for _, e := range bd.fileExtensions {
|
||||
if strings.HasSuffix(filename, e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Registry for decompressors
|
||||
type registry struct {
|
||||
mu sync.RWMutex
|
||||
items map[string]Decompressor
|
||||
}
|
||||
|
||||
var globalRegistry = ®istry{items: make(map[string]Decompressor)}
|
||||
|
||||
func Register(d Decompressor) {
|
||||
globalRegistry.mu.Lock()
|
||||
defer globalRegistry.mu.Unlock()
|
||||
globalRegistry.items[strings.ToLower(d.Name())] = d
|
||||
}
|
||||
|
||||
func Get(name string) (Decompressor, bool) {
|
||||
globalRegistry.mu.RLock()
|
||||
defer globalRegistry.mu.RUnlock()
|
||||
d, ok := globalRegistry.items[strings.ToLower(name)]
|
||||
return d, ok
|
||||
}
|
||||
|
||||
func GetByFilename(filename string) (Decompressor, bool) {
|
||||
format := DetectCompressionByFilename(filename)
|
||||
if format == "" || format == "identity" {
|
||||
return nil, false
|
||||
}
|
||||
return Get(format)
|
||||
}
|
||||
|
||||
// GetCompressionFromHeader detects compression from the Content-Encoding header
|
||||
func GetCompressionFromHeader(contentEncoding string) string {
|
||||
if contentEncoding == "" {
|
||||
return "identity"
|
||||
}
|
||||
encodings := ParseContentEncoding(contentEncoding)
|
||||
return GetEffectiveEncoding(encodings)
|
||||
}
|
||||
|
||||
func ParseContentEncoding(header string) []string {
|
||||
if header == "" {
|
||||
return []string{"identity"}
|
||||
}
|
||||
parts := strings.Split(header, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
result = append(result, strings.ToLower(p))
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return []string{"identity"}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetEffectiveEncoding(encodings []string) string {
|
||||
for _, e := range encodings {
|
||||
if e != "identity" {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return "identity"
|
||||
}
|
||||
|
||||
func DetectCompressionByFilename(filename string) string {
|
||||
filename = strings.ToLower(filename)
|
||||
switch {
|
||||
case strings.HasSuffix(filename, ".gz"):
|
||||
return "gzip"
|
||||
case strings.HasSuffix(filename, ".zlib"), strings.HasSuffix(filename, ".zz"):
|
||||
return "zlib"
|
||||
case strings.HasSuffix(filename, ".deflate"), strings.HasSuffix(filename, ".fl"):
|
||||
return "flate"
|
||||
case strings.HasSuffix(filename, ".bz2"), strings.HasSuffix(filename, ".tbz"), strings.HasSuffix(filename, ".tbz2"):
|
||||
return "bzip2"
|
||||
case strings.HasSuffix(filename, ".z"), strings.HasSuffix(filename, ".lzw"):
|
||||
return "lzw"
|
||||
}
|
||||
return "identity"
|
||||
}
|
||||
|
||||
func IsCompressedFile(filename string) bool {
|
||||
return DetectCompressionByFilename(filename) != "identity"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"compress/lzw"
|
||||
"io"
|
||||
)
|
||||
|
||||
const lzwLitWidth = 8
|
||||
|
||||
type lzwDecompressor struct {
|
||||
*BaseDecompressor
|
||||
order lzw.Order
|
||||
}
|
||||
|
||||
func NewLzwDecompressor() *lzwDecompressor { return NewLzwDecompressorWithOrder(lzw.LSB) }
|
||||
|
||||
func NewLzwDecompressorWithOrder(order lzw.Order) *lzwDecompressor {
|
||||
name := "lzw"
|
||||
if order == lzw.MSB {
|
||||
name = "lzw-msb"
|
||||
}
|
||||
return &lzwDecompressor{NewBaseDecompressor(name, []string{}, []string{".z", ".lzw"}), order}
|
||||
}
|
||||
|
||||
func (l *lzwDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
|
||||
return lzw.NewReader(r, l.order, lzwLitWidth), nil
|
||||
}
|
||||
|
||||
type lzwCompressor struct {
|
||||
name string
|
||||
order lzw.Order
|
||||
}
|
||||
|
||||
func NewLzwCompressor() *lzwCompressor { return NewLzwCompressorWithOrder(lzw.LSB) }
|
||||
func NewLzwCompressorWithOrder(order lzw.Order) *lzwCompressor {
|
||||
name := "lzw"
|
||||
if order == lzw.MSB {
|
||||
name = "lzw-msb"
|
||||
}
|
||||
return &lzwCompressor{name: name, order: order}
|
||||
}
|
||||
|
||||
func (l *lzwCompressor) Name() string { return l.name }
|
||||
|
||||
func (l *lzwCompressor) Writer(w io.Writer) (io.WriteCloser, error) {
|
||||
return lzw.NewWriter(w, l.order, lzwLitWidth), nil
|
||||
}
|
||||
|
||||
func init() { Register(NewLzwDecompressor()) }
|
||||
@@ -0,0 +1,30 @@
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package compression
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"io"
|
||||
)
|
||||
|
||||
type zlibDecompressor struct{ *BaseDecompressor }
|
||||
|
||||
func NewZlibDecompressor() *zlibDecompressor {
|
||||
return &zlibDecompressor{NewBaseDecompressor("zlib", []string{"deflate"}, []string{".zlib", ".zz"})}
|
||||
}
|
||||
|
||||
// ✅ Oprava: returns (io.Reader, error)
|
||||
func (z *zlibDecompressor) Reader(r io.Reader) (io.ReadCloser, error) {
|
||||
return zlib.NewReader(r)
|
||||
}
|
||||
|
||||
type zlibCompressor struct{ name string }
|
||||
|
||||
func NewZlibCompressor() *zlibCompressor { return &zlibCompressor{name: "zlib"} }
|
||||
func (z *zlibCompressor) Name() string { return z.name }
|
||||
func (z *zlibCompressor) Writer(w io.Writer) (io.WriteCloser, error) {
|
||||
return zlib.NewWriter(w), nil
|
||||
}
|
||||
|
||||
func init() { Register(NewZlibDecompressor()) }
|
||||
Reference in New Issue
Block a user