github.com/bodgit/sevenzip@v1.5.1/internal/brotli/reader.go (about) 1 package brotli 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "io" 8 "sync" 9 10 "github.com/andybalholm/brotli" 11 "github.com/bodgit/plumbing" 12 ) 13 14 //nolint:gochecknoglobals 15 var brotliReaderPool sync.Pool 16 17 type readCloser struct { 18 c io.Closer 19 r *brotli.Reader 20 } 21 22 const ( 23 frameMagic uint32 = 0x184d2a50 24 frameSize uint32 = 8 25 brotliMagic uint16 = 0x5242 // 'B', 'R' 26 ) 27 28 // This isn't part of the Brotli format but is prepended by the 7-zip implementation. 29 type headerFrame struct { 30 FrameMagic uint32 31 FrameSize uint32 32 CompressedSize uint32 33 BrotliMagic uint16 34 UncompressedSize uint16 // * 64 KB 35 } 36 37 func (rc *readCloser) Close() (err error) { 38 if rc.c != nil { 39 brotliReaderPool.Put(rc.r) 40 err = rc.c.Close() 41 rc.c, rc.r = nil, nil 42 } 43 44 return 45 } 46 47 func (rc *readCloser) Read(p []byte) (int, error) { 48 if rc.r == nil { 49 return 0, errors.New("brotli: Read after Close") 50 } 51 52 return rc.r.Read(p) 53 } 54 55 // NewReader returns a new Brotli io.ReadCloser. 56 func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) { 57 if len(readers) != 1 { 58 return nil, errors.New("brotli: need exactly one reader") 59 } 60 61 hr, b := new(headerFrame), new(bytes.Buffer) 62 b.Grow(binary.Size(hr)) 63 64 // The 7-Zip Brotli compressor adds a 16 byte frame to the beginning of 65 // the data which will confuse a pure Brotli implementation. Read it 66 // but keep a copy so we can add it back if it doesn't look right 67 if err := binary.Read(io.TeeReader(readers[0], b), binary.LittleEndian, hr); err != nil { 68 return nil, err 69 } 70 71 var reader io.ReadCloser 72 73 // If the header looks right, continue reading from that point 74 // onwards, otherwise prepend it again and hope for the best 75 if hr.FrameMagic == frameMagic && hr.FrameSize == frameSize && hr.BrotliMagic == brotliMagic { 76 reader = readers[0] 77 } else { 78 reader = plumbing.MultiReadCloser(io.NopCloser(b), readers[0]) 79 } 80 81 r, ok := brotliReaderPool.Get().(*brotli.Reader) 82 if ok { 83 _ = r.Reset(reader) 84 } else { 85 r = brotli.NewReader(reader) 86 } 87 88 return &readCloser{ 89 c: readers[0], 90 r: r, 91 }, nil 92 }