diff --git a/go.mod b/go.mod index d5d64ae..f7f2384 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/kr/text v0.2.0 // indirect github.com/smartystreets/assertions v1.2.0 // indirect + github.com/ulikunitz/xz v0.5.8 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect diff --git a/go.sum b/go.sum index 6a23aa1..26b6a80 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= diff --git a/internal/compression/zlib.go b/internal/compression/gzip.go similarity index 74% rename from internal/compression/zlib.go rename to internal/compression/gzip.go index a1842c9..19ddafd 100644 --- a/internal/compression/zlib.go +++ b/internal/compression/gzip.go @@ -6,11 +6,11 @@ import ( "io" ) -//Zlib is a decompressor for gzip type compression -type Zlib struct{} +//Gzip is a decompressor for gzip type compression. Uses zlib for compression and decompression +type Gzip struct{} //Decompress reads the entirety of the given reader and returns it uncompressed as a byte slice. -func (z *Zlib) Decompress(r io.Reader) ([]byte, error) { +func (g *Gzip) Decompress(r io.Reader) ([]byte, error) { rdr, err := zlib.NewReader(r) if err != nil { return nil, err @@ -24,7 +24,7 @@ func (z *Zlib) Decompress(r io.Reader) ([]byte, error) { } //Compress compresses the given data (as a byte array) and returns the compressed data. -func (z *Zlib) Compress(data []byte) ([]byte, error) { +func (g *Gzip) Compress(data []byte) ([]byte, error) { var buf bytes.Buffer wrt := zlib.NewWriter(&buf) defer wrt.Close() diff --git a/internal/compression/lzma.go b/internal/compression/lzma.go new file mode 100644 index 0000000..b9e7c9c --- /dev/null +++ b/internal/compression/lzma.go @@ -0,0 +1,25 @@ +package compression + +import ( + "bytes" + "io" + + "github.com/ulikunitz/xz/lzma" +) + +//Lzma is a lzma decompressor +type Lzma struct{} + +//Decompress decompresses all the data in the given reader and returns the uncompressed bytes. +func (l *Lzma) Decompress(rdr io.Reader) ([]byte, error) { + r, err := lzma.NewReader(rdr) + if err != nil { + return nil, err + } + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/internal/compression/xz.go b/internal/compression/xz.go new file mode 100644 index 0000000..c860ef3 --- /dev/null +++ b/internal/compression/xz.go @@ -0,0 +1,55 @@ +package compression + +import ( + "bytes" + "encoding/binary" + "io" + + "github.com/ulikunitz/xz" +) + +type xzInit struct { + DictionarySize int32 + Filters int32 +} + +//Xz is a Xz decompressor. +type Xz struct { + DictionarySize int32 + HasFilters bool +} + +//NewXzCompressorWithOptions creates a new Xz compressor/decompressor that reads the compressor options from the given reader. +func NewXzCompressorWithOptions(rdr io.Reader) (*Xz, error) { + var x Xz + var init xzInit + err := binary.Read(rdr, binary.LittleEndian, &init) + if err != nil { + return nil, err + } + x.DictionarySize = init.DictionarySize + //TODO: When I can do filters, parse the filters + if init.Filters != 0 { + x.HasFilters = true + } + return &x, nil +} + +//Decompress decompresses all the data from the rdr and returns the uncompressed bytes. +func (x *Xz) Decompress(rdr io.Reader) ([]byte, error) { + r, err := xz.NewReader(rdr) + if err != nil { + return nil, err + } + r.DictCap = int(x.DictionarySize) + err = r.Verify() + if err != nil { + return nil, err + } + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/reader.go b/reader.go index c1e6ea1..97dd69d 100644 --- a/reader.go +++ b/reader.go @@ -50,23 +50,26 @@ func NewSquashfsReader(r io.ReaderAt) (*Reader, error) { rdr.flags = rdr.super.GetFlags() if rdr.flags.CompressorOptions { switch rdr.super.CompressionType { - //Xzcompression isn't working right now. - // case xzCompression: - // xz, err := compression.NewXzWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 1000)) //1000 is technically too much, but it's just an easy way to do it. - // if err != nil { - // return nil, err - // } - // rdr.decompressor = xz + case xzCompression: + xz, err := compression.NewXzCompressorWithOptions(io.NewSectionReader(rdr.r, int64(binary.Size(rdr.super)), 1000)) //1000 is technically too much, but it's just an easy way to do it. + if err != nil { + return nil, err + } + if xz.HasFilters { + return nil, errors.New("XZ compression options has filters. These are not yet supported") + } + rdr.decompressor = xz default: return nil, errCompressorOptions } } else { switch rdr.super.CompressionType { case gzipCompression: - rdr.decompressor = &compression.Zlib{} - //Xzcompression isn't working right now. - // case xzCompression: - // rdr.decompressor = &compression.Xz{} + rdr.decompressor = &compression.Gzip{} + case lzmaCompression: + rdr.decompressor = &compression.Lzma{} + case xzCompression: + rdr.decompressor = &compression.Xz{} default: //TODO: all compression types. return nil, errIncompatibleCompression