feat: vendor bodgit/sevenzip package to remove go4.org dependency
Vendored the sevenzip package to eliminate dependency chain: - sevenzip -> go4.org -> 25+ Google/Cloud/telemetry packages Changes: - Added internal/sevenzip/ with full package source - Inlined go4.org/readerutil into multireaderat.go - Updated all internal imports to use bookhoard/internal/sevenzip - Preserved .cb7 comic archive support This reduces bloat by ~4.9 MB and removes unused telemetry dependencies while maintaining all functionality.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2020, Matt Dainty
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,69 @@
|
||||
package aes7z
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type cacheKey struct {
|
||||
password string
|
||||
cycles int
|
||||
salt string // []byte isn't comparable
|
||||
}
|
||||
|
||||
const cacheSize = 10
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var once = sync.OnceValues(func() (*lru.Cache[cacheKey, []byte], error) {
|
||||
return lru.New[cacheKey, []byte](cacheSize)
|
||||
})
|
||||
|
||||
func calculateKey(password string, cycles int, salt []byte) ([]byte, error) {
|
||||
cache, err := once()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aes7z: error creating cache: %w", err)
|
||||
}
|
||||
|
||||
ck := cacheKey{
|
||||
password: password,
|
||||
cycles: cycles,
|
||||
salt: hex.EncodeToString(salt),
|
||||
}
|
||||
|
||||
if key, ok := cache.Get(ck); ok {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(salt)
|
||||
|
||||
// Convert password to UTF-16LE
|
||||
utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||
t := transform.NewWriter(b, utf16le.NewEncoder())
|
||||
_, _ = t.Write([]byte(password))
|
||||
|
||||
key := make([]byte, sha256.Size)
|
||||
if cycles == 0x3f {
|
||||
copy(key, b.Bytes())
|
||||
} else {
|
||||
h := sha256.New()
|
||||
for i := uint64(0); i < 1<<cycles; i++ {
|
||||
// These will never error
|
||||
_, _ = h.Write(b.Bytes())
|
||||
_ = binary.Write(h, binary.LittleEndian, i)
|
||||
}
|
||||
|
||||
copy(key, h.Sum(nil))
|
||||
}
|
||||
|
||||
_ = cache.Add(ck, key)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Package aes7z implements the 7-zip AES decryption.
|
||||
package aes7z
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("aes7z: already closed")
|
||||
errNeedOneReader = errors.New("aes7z: need exactly one reader")
|
||||
errInsufficientProperties = errors.New("aes7z: not enough properties")
|
||||
errNoPasswordSet = errors.New("aes7z: no password set")
|
||||
errUnsupportedMethod = errors.New("aes7z: unsupported compression method")
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
rc io.ReadCloser
|
||||
salt, iv []byte
|
||||
cycles int
|
||||
cbc cipher.BlockMode
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.rc == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.rc.Close(); err != nil {
|
||||
return fmt.Errorf("aes7z: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.rc = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Password(p string) error {
|
||||
key, err := calculateKey(p, rc.cycles, rc.salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aes7z: error creating cipher: %w", err)
|
||||
}
|
||||
|
||||
rc.cbc = cipher.NewCBCDecrypter(block, rc.iv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.rc == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
if rc.cbc == nil {
|
||||
return 0, errNoPasswordSet
|
||||
}
|
||||
|
||||
var block [aes.BlockSize]byte
|
||||
|
||||
for rc.buf.Len() < len(p) {
|
||||
if _, err := io.ReadFull(rc.rc, block[:]); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("aes7z: error reading block: %w", err)
|
||||
}
|
||||
|
||||
rc.cbc.CryptBlocks(block[:], block[:])
|
||||
|
||||
_, _ = rc.buf.Write(block[:])
|
||||
}
|
||||
|
||||
n, err := rc.buf.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("aes7z: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new AES-256-CBC & SHA-256 io.ReadCloser. The Password
|
||||
// method must be called before attempting to call Read so that the block
|
||||
// cipher is correctly initialised.
|
||||
func NewReader(p []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
// Need at least two bytes initially
|
||||
if len(p) < 2 {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
if p[0]&0xc0 == 0 {
|
||||
return nil, errUnsupportedMethod
|
||||
}
|
||||
|
||||
rc := new(readCloser)
|
||||
|
||||
salt := p[0]>>7&1 + p[1]>>4
|
||||
iv := p[0]>>6&1 + p[1]&0x0f
|
||||
|
||||
if len(p) != int(2+salt+iv) {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
rc.salt = p[2 : 2+salt]
|
||||
rc.iv = make([]byte, aes.BlockSize)
|
||||
copy(rc.iv, p[2+salt:])
|
||||
|
||||
rc.cycles = int(p[0] & 0x3f)
|
||||
rc.rc = readers[0]
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Package bcj2 implements the BCJ2 filter for x86 binaries.
|
||||
package bcj2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
main util.ReadCloser
|
||||
call io.ReadCloser
|
||||
jump io.ReadCloser
|
||||
|
||||
rd util.ReadCloser
|
||||
nrange uint
|
||||
code uint
|
||||
|
||||
sd [256 + 2]uint
|
||||
|
||||
previous byte
|
||||
written uint32
|
||||
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
const (
|
||||
numMoveBits = 5
|
||||
numbitModelTotalBits = 11
|
||||
bitModelTotal uint = 1 << numbitModelTotalBits
|
||||
numTopBits = 24
|
||||
topValue uint = 1 << numTopBits
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("bcj2: already closed")
|
||||
errNeedFourReaders = errors.New("bcj2: need exactly four readers")
|
||||
)
|
||||
|
||||
func isJcc(b0, b1 byte) bool {
|
||||
return b0 == 0x0f && (b1&0xf0) == 0x80
|
||||
}
|
||||
|
||||
func isJ(b0, b1 byte) bool {
|
||||
return (b1&0xfe) == 0xe8 || isJcc(b0, b1)
|
||||
}
|
||||
|
||||
func index(b0, b1 byte) int {
|
||||
switch b1 {
|
||||
case 0xe8:
|
||||
return int(b0)
|
||||
case 0xe9:
|
||||
return 256
|
||||
default:
|
||||
return 257
|
||||
}
|
||||
}
|
||||
|
||||
// NewReader returns a new BCJ2 io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 4 {
|
||||
return nil, errNeedFourReaders
|
||||
}
|
||||
|
||||
rc := &readCloser{
|
||||
main: util.ByteReadCloser(readers[0]),
|
||||
call: readers[1],
|
||||
jump: readers[2],
|
||||
rd: util.ByteReadCloser(readers[3]),
|
||||
nrange: 0xffffffff,
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
rc.buf.Grow(1 << 16)
|
||||
|
||||
b := make([]byte, 5)
|
||||
if _, err := io.ReadFull(rc.rd, b); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bcj2: error reading initial state: %w", err)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, x := range b {
|
||||
rc.code = (rc.code << 8) | uint(x)
|
||||
}
|
||||
|
||||
for i := range rc.sd {
|
||||
rc.sd[i] = bitModelTotal >> 1
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.main == nil || rc.call == nil || rc.jump == nil || rc.rd == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := errors.Join(rc.main.Close(), rc.call.Close(), rc.jump.Close(), rc.rd.Close()); err != nil {
|
||||
return fmt.Errorf("bcj2: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.main, rc.call, rc.jump, rc.rd = nil, nil, nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.main == nil || rc.call == nil || rc.jump == nil || rc.rd == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.read(); err != nil && !errors.Is(err, io.EOF) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err := rc.buf.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bcj2: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (rc *readCloser) update() error {
|
||||
if rc.nrange < topValue {
|
||||
b, err := rc.rd.ReadByte()
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return fmt.Errorf("bcj2: error reading byte: %w", err)
|
||||
}
|
||||
|
||||
rc.code = (rc.code << 8) | uint(b)
|
||||
rc.nrange <<= 8
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) decode(i int) (bool, error) {
|
||||
newBound := (rc.nrange >> numbitModelTotalBits) * rc.sd[i]
|
||||
|
||||
if rc.code < newBound {
|
||||
rc.nrange = newBound
|
||||
rc.sd[i] += (bitModelTotal - rc.sd[i]) >> numMoveBits
|
||||
|
||||
if err := rc.update(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
rc.nrange -= newBound
|
||||
rc.code -= newBound
|
||||
rc.sd[i] -= rc.sd[i] >> numMoveBits
|
||||
|
||||
if err := rc.update(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func (rc *readCloser) read() error {
|
||||
var (
|
||||
b byte
|
||||
err error
|
||||
)
|
||||
|
||||
for {
|
||||
if b, err = rc.main.ReadByte(); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bcj2: error reading byte: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
rc.written++
|
||||
_ = rc.buf.WriteByte(b)
|
||||
|
||||
if isJ(rc.previous, b) {
|
||||
break
|
||||
}
|
||||
|
||||
rc.previous = b
|
||||
|
||||
if rc.buf.Len() == rc.buf.Cap() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
bit, err := rc.decode(index(rc.previous, b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:nestif
|
||||
if bit {
|
||||
var r io.Reader
|
||||
if b == 0xe8 {
|
||||
r = rc.call
|
||||
} else {
|
||||
r = rc.jump
|
||||
}
|
||||
|
||||
var dest uint32
|
||||
if err = binary.Read(r, binary.BigEndian, &dest); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bcj2: error reading uint32: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
dest -= rc.written + 4
|
||||
_ = binary.Write(rc.buf, binary.LittleEndian, dest)
|
||||
|
||||
rc.previous = byte(dest >> 24)
|
||||
rc.written += 4
|
||||
} else {
|
||||
rc.previous = b
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package bra
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const armAlignment = 4
|
||||
|
||||
type arm struct {
|
||||
ip uint32
|
||||
}
|
||||
|
||||
func (c *arm) Size() int { return armAlignment }
|
||||
|
||||
func (c *arm) Convert(b []byte, encoding bool) int {
|
||||
if len(b) < c.Size() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if c.ip == 0 {
|
||||
c.ip += armAlignment
|
||||
}
|
||||
|
||||
var i int
|
||||
|
||||
for i = 0; i < len(b) & ^(armAlignment-1); i += armAlignment {
|
||||
v := binary.LittleEndian.Uint32(b[i:])
|
||||
|
||||
c.ip += uint32(armAlignment)
|
||||
|
||||
if b[i+3] == 0xeb {
|
||||
v <<= 2
|
||||
|
||||
if encoding {
|
||||
v += c.ip
|
||||
} else {
|
||||
v -= c.ip
|
||||
}
|
||||
|
||||
v >>= 2
|
||||
v &= 0x00ffffff
|
||||
v |= 0xeb000000
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b[i:], v)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// NewARMReader returns a new ARM io.ReadCloser.
|
||||
func NewARMReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
return newReader(readers, new(arm))
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package bra
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const bcjLookAhead = 4
|
||||
|
||||
type bcj struct {
|
||||
ip, state uint32
|
||||
}
|
||||
|
||||
func (c *bcj) Size() int { return bcjLookAhead + 1 }
|
||||
|
||||
func test86MSByte(b byte) bool {
|
||||
return (b+1)&0xfe == 0
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen,gocognit
|
||||
func (c *bcj) Convert(b []byte, encoding bool) int {
|
||||
if len(b) < c.Size() {
|
||||
return 0
|
||||
}
|
||||
|
||||
var (
|
||||
pos uint32
|
||||
mask = c.state & 7
|
||||
)
|
||||
|
||||
for {
|
||||
p := pos
|
||||
for ; int(p) < len(b)-bcjLookAhead; p++ {
|
||||
if b[p]&0xfe == 0xe8 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
d := p - pos
|
||||
pos = p
|
||||
|
||||
if int(p) >= len(b)-bcjLookAhead {
|
||||
if d > 2 {
|
||||
c.state = 0
|
||||
} else {
|
||||
c.state = mask >> d
|
||||
}
|
||||
|
||||
c.ip += pos
|
||||
|
||||
return int(pos)
|
||||
}
|
||||
|
||||
if d > 2 {
|
||||
mask = 0
|
||||
} else {
|
||||
mask >>= d
|
||||
if mask != 0 && (mask > 4 || mask == 3 || test86MSByte(b[p+(mask>>1)+1])) {
|
||||
mask = (mask >> 1) | 4
|
||||
pos++
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:nestif
|
||||
if test86MSByte(b[p+4]) {
|
||||
v := binary.LittleEndian.Uint32(b[p+1:])
|
||||
cur := c.ip + uint32(c.Size()) + pos //nolint:gosec
|
||||
pos += uint32(c.Size()) //nolint:gosec
|
||||
|
||||
if encoding {
|
||||
v += cur
|
||||
} else {
|
||||
v -= cur
|
||||
}
|
||||
|
||||
if mask != 0 {
|
||||
sh := mask & 6 << 2
|
||||
if test86MSByte(byte(v >> sh)) {
|
||||
v ^= (uint32(0x100) << sh) - 1
|
||||
if encoding {
|
||||
v += cur
|
||||
} else {
|
||||
v -= cur
|
||||
}
|
||||
}
|
||||
|
||||
mask = 0
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b[p+1:], v)
|
||||
b[p+4] = 0 - b[p+4]&1
|
||||
} else {
|
||||
mask = (mask >> 1) | 4
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewBCJReader returns a new BCJ io.ReadCloser.
|
||||
func NewBCJReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
return newReader(readers, new(bcj))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Package bra implements the branch rewriting filter for binaries.
|
||||
package bra
|
||||
|
||||
type converter interface {
|
||||
Size() int
|
||||
Convert(b []byte, encoding bool) int
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package bra
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const ppcAlignment = 4
|
||||
|
||||
type ppc struct {
|
||||
ip uint32
|
||||
}
|
||||
|
||||
func (c *ppc) Size() int { return ppcAlignment }
|
||||
|
||||
func (c *ppc) Convert(b []byte, encoding bool) int {
|
||||
if len(b) < c.Size() {
|
||||
return 0
|
||||
}
|
||||
|
||||
var i int
|
||||
|
||||
for i = 0; i < len(b) & ^(ppcAlignment-1); i += ppcAlignment {
|
||||
v := binary.BigEndian.Uint32(b[i:])
|
||||
|
||||
if b[i+0]&0xfc == 0x48 && b[i+3]&3 == 1 {
|
||||
if encoding {
|
||||
v += c.ip
|
||||
} else {
|
||||
v -= c.ip
|
||||
}
|
||||
|
||||
v &= 0x03ffffff
|
||||
v |= 0x48000000
|
||||
}
|
||||
|
||||
c.ip += uint32(ppcAlignment)
|
||||
|
||||
binary.BigEndian.PutUint32(b[i:], v)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// NewPPCReader returns a new PPC io.ReadCloser.
|
||||
func NewPPCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
return newReader(readers, new(ppc))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package bra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
rc io.ReadCloser
|
||||
buf bytes.Buffer
|
||||
n int
|
||||
conv converter
|
||||
}
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("bra: already closed")
|
||||
errNeedOneReader = errors.New("bra: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.rc == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.rc.Close(); err != nil {
|
||||
return fmt.Errorf("bra: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.rc = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.rc == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
if _, err := io.CopyN(&rc.buf, rc.rc, int64(max(len(p), rc.conv.Size())-rc.buf.Len())); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return 0, fmt.Errorf("bra: error buffering: %w", err)
|
||||
}
|
||||
|
||||
if rc.buf.Len() < rc.conv.Size() {
|
||||
rc.n = rc.buf.Len()
|
||||
}
|
||||
}
|
||||
|
||||
rc.n += rc.conv.Convert(rc.buf.Bytes()[rc.n:], false)
|
||||
|
||||
n, err := rc.buf.Read(p[:min(rc.n, len(p))])
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bra: error reading: %w", err)
|
||||
}
|
||||
|
||||
rc.n -= n
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func newReader(readers []io.ReadCloser, conv converter) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
rc: readers[0],
|
||||
conv: conv,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package bra
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
const sparcAlignment = 4
|
||||
|
||||
type sparc struct {
|
||||
ip uint32
|
||||
}
|
||||
|
||||
func (c *sparc) Size() int { return sparcAlignment }
|
||||
|
||||
func (c *sparc) Convert(b []byte, encoding bool) int {
|
||||
if len(b) < c.Size() {
|
||||
return 0
|
||||
}
|
||||
|
||||
var i int
|
||||
|
||||
for i = 0; i < len(b) & ^(sparcAlignment-1); i += sparcAlignment {
|
||||
v := binary.BigEndian.Uint32(b[i:])
|
||||
|
||||
if (b[i+0] == 0x40 && b[i+1]&0xc0 == 0) || (b[i+0] == 0x7f && b[i+1] >= 0xc0) {
|
||||
v <<= 2
|
||||
|
||||
if encoding {
|
||||
v += c.ip
|
||||
} else {
|
||||
v -= c.ip
|
||||
}
|
||||
|
||||
v &= 0x01ffffff
|
||||
v -= uint32(1) << 24
|
||||
v ^= 0xff000000
|
||||
v >>= 2
|
||||
v |= 0x40000000
|
||||
}
|
||||
|
||||
c.ip += uint32(sparcAlignment)
|
||||
|
||||
binary.BigEndian.PutUint32(b[i:], v)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// NewSPARCReader returns a new SPARC io.ReadCloser.
|
||||
func NewSPARCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
return newReader(readers, new(sparc))
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Package brotli implements the Brotli decompressor.
|
||||
package brotli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/bodgit/plumbing"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r *brotli.Reader
|
||||
}
|
||||
|
||||
const (
|
||||
frameMagic uint32 = 0x184d2a50
|
||||
frameSize uint32 = 8
|
||||
brotliMagic uint16 = 0x5242 // 'B', 'R'
|
||||
)
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
brotliReaderPool sync.Pool
|
||||
|
||||
errAlreadyClosed = errors.New("brotli: already closed")
|
||||
errNeedOneReader = errors.New("brotli: need exactly one reader")
|
||||
)
|
||||
|
||||
// This isn't part of the Brotli format but is prepended by the 7-zip implementation.
|
||||
type headerFrame struct {
|
||||
FrameMagic uint32
|
||||
FrameSize uint32
|
||||
CompressedSize uint32
|
||||
BrotliMagic uint16
|
||||
UncompressedSize uint16 // * 64 KB
|
||||
}
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.r == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("brotli: error closing: %w", err)
|
||||
}
|
||||
|
||||
brotliReaderPool.Put(rc.r)
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("brotli: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new Brotli io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
hr, b := new(headerFrame), new(bytes.Buffer)
|
||||
b.Grow(binary.Size(hr))
|
||||
|
||||
// The 7-Zip Brotli compressor adds a 16 byte frame to the beginning of
|
||||
// the data which will confuse a pure Brotli implementation. Read it
|
||||
// but keep a copy so we can add it back if it doesn't look right
|
||||
if err := binary.Read(io.TeeReader(readers[0], b), binary.LittleEndian, hr); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("brotli: error reading frame: %w", err)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reader io.ReadCloser
|
||||
|
||||
// If the header looks right, continue reading from that point
|
||||
// onwards, otherwise prepend it again and hope for the best
|
||||
if hr.FrameMagic == frameMagic && hr.FrameSize == frameSize && hr.BrotliMagic == brotliMagic {
|
||||
reader = readers[0]
|
||||
} else {
|
||||
reader = plumbing.MultiReadCloser(io.NopCloser(b), readers[0])
|
||||
}
|
||||
|
||||
r, ok := brotliReaderPool.Get().(*brotli.Reader)
|
||||
if ok {
|
||||
_ = r.Reset(reader)
|
||||
} else {
|
||||
r = brotli.NewReader(reader)
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: r,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Package bzip2 implements the Bzip2 decompressor.
|
||||
package bzip2
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("bzip2: already closed")
|
||||
errNeedOneReader = errors.New("bzip2: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.r == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("bzip2: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("bzip2: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new bzip2 io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: bzip2.NewReader(readers[0]),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Package deflate implements the Deflate decompressor.
|
||||
package deflate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
"github.com/klauspost/compress/flate"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
fr io.ReadCloser
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
flateReaderPool sync.Pool
|
||||
|
||||
errAlreadyClosed = errors.New("deflate: already closed")
|
||||
errNeedOneReader = errors.New("deflate: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.fr == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := errors.Join(rc.fr.Close(), rc.c.Close()); err != nil {
|
||||
return fmt.Errorf("deflate: error closing: %w", err)
|
||||
}
|
||||
|
||||
flateReaderPool.Put(rc.fr)
|
||||
rc.c, rc.fr = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.c == nil || rc.fr == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.fr.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("deflate: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new DEFLATE io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
fr, ok := flateReaderPool.Get().(io.ReadCloser)
|
||||
if ok {
|
||||
frf, ok := fr.(flate.Resetter)
|
||||
if ok {
|
||||
if err := frf.Reset(util.ByteReadCloser(readers[0]), nil); err != nil {
|
||||
return nil, fmt.Errorf("deflate: error resetting: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fr = flate.NewReader(util.ByteReadCloser(readers[0]))
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
fr: fr,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Package delta implements the Delta filter.
|
||||
package delta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
rc io.ReadCloser
|
||||
state [stateSize]byte
|
||||
delta int
|
||||
}
|
||||
|
||||
const (
|
||||
stateSize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("delta: already closed")
|
||||
errNeedOneReader = errors.New("delta: need exactly one reader")
|
||||
errInsufficientProperties = errors.New("delta: not enough properties")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.rc == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.rc.Close(); err != nil {
|
||||
return fmt.Errorf("delta: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.rc = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.rc == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.rc.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return n, fmt.Errorf("delta: error reading: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
buffer [stateSize]byte
|
||||
j int
|
||||
)
|
||||
|
||||
copy(buffer[:], rc.state[:rc.delta])
|
||||
|
||||
for i := 0; i < n; {
|
||||
for j = 0; j < rc.delta && i < n; i++ {
|
||||
p[i] = buffer[j] + p[i]
|
||||
buffer[j] = p[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
if j == rc.delta {
|
||||
j = 0
|
||||
}
|
||||
|
||||
copy(rc.state[:], buffer[j:rc.delta])
|
||||
copy(rc.state[rc.delta-j:], buffer[:j])
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// NewReader returns a new Delta io.ReadCloser.
|
||||
func NewReader(p []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
if len(p) != 1 {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
rc: readers[0],
|
||||
delta: int(p[0] + 1),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Package lz4 implements the LZ4 decompressor.
|
||||
package lz4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
lz4 "github.com/pierrec/lz4/v4"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r *lz4.Reader
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
lz4ReaderPool sync.Pool
|
||||
|
||||
errAlreadyClosed = errors.New("lz4: already closed")
|
||||
errNeedOneReader = errors.New("lz4: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.r == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("lz4: error closing: %w", err)
|
||||
}
|
||||
|
||||
lz4ReaderPool.Put(rc.r)
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("lz4: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new LZ4 io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
r, ok := lz4ReaderPool.Get().(*lz4.Reader)
|
||||
if ok {
|
||||
r.Reset(readers[0])
|
||||
} else {
|
||||
r = lz4.NewReader(readers[0])
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: r,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Package lzma implements the LZMA decompressor.
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ulikunitz/xz/lzma"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("lzma: already closed")
|
||||
errNeedOneReader = errors.New("lzma: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.r == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("lzma: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("lzma: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new LZMA io.ReadCloser.
|
||||
func NewReader(p []byte, s uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
h := bytes.NewBuffer(p)
|
||||
_ = binary.Write(h, binary.LittleEndian, s)
|
||||
|
||||
lr, err := lzma.NewReader(multiReader(h, readers[0]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lzma: error creating reader: %w", err)
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: lr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func multiReader(b *bytes.Buffer, rc io.ReadCloser) io.Reader {
|
||||
mr := io.MultiReader(b, rc)
|
||||
|
||||
if br, ok := rc.(io.ByteReader); ok {
|
||||
return &multiByteReader{
|
||||
b: b,
|
||||
br: br,
|
||||
mr: mr,
|
||||
}
|
||||
}
|
||||
|
||||
return mr
|
||||
}
|
||||
|
||||
type multiByteReader struct {
|
||||
b *bytes.Buffer
|
||||
br io.ByteReader
|
||||
mr io.Reader
|
||||
}
|
||||
|
||||
func (m *multiByteReader) ReadByte() (b byte, err error) {
|
||||
if m.b.Len() > 0 {
|
||||
b, err = m.b.ReadByte()
|
||||
} else {
|
||||
b, err = m.br.ReadByte()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("lzma: error multi byte reading: %w", err)
|
||||
}
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (m *multiByteReader) Read(p []byte) (int, error) {
|
||||
n, err := m.mr.Read(p)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("lzma: error multi reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Package lzma2 implements the LZMA2 decompressor.
|
||||
package lzma2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ulikunitz/xz/lzma"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
var (
|
||||
errAlreadyClosed = errors.New("lzma2: already closed")
|
||||
errNeedOneReader = errors.New("lzma2: need exactly one reader")
|
||||
errInsufficientProperties = errors.New("lzma2: not enough properties")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil || rc.r == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("lzma2: error closing: %w", err)
|
||||
}
|
||||
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("lzma2: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new LZMA2 io.ReadCloser.
|
||||
func NewReader(p []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
if len(p) != 1 {
|
||||
return nil, errInsufficientProperties
|
||||
}
|
||||
|
||||
config := lzma.Reader2Config{
|
||||
DictCap: (2 | (int(p[0]) & 1)) << (p[0]/2 + 11), // This gem came from Lzma2Dec.c
|
||||
}
|
||||
|
||||
if err := config.Verify(); err != nil {
|
||||
return nil, fmt.Errorf("lzma2: error verifying config: %w", err)
|
||||
}
|
||||
|
||||
lr, err := config.NewReader2(readers[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lzma2: error creating reader: %w", err)
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: lr,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Package pool implements the reader pooling.
|
||||
package pool
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
)
|
||||
|
||||
// Pooler is the interface implemented by a pool.
|
||||
type Pooler interface {
|
||||
Get(offset int64) (util.SizeReadSeekCloser, bool)
|
||||
Put(offset int64, rc util.SizeReadSeekCloser) (bool, error)
|
||||
}
|
||||
|
||||
// Constructor is the function prototype used to instantiate a pool.
|
||||
type Constructor func() (Pooler, error)
|
||||
|
||||
type noopPool struct{}
|
||||
|
||||
// NewNoopPool returns a Pooler that doesn't actually pool anything.
|
||||
func NewNoopPool() (Pooler, error) {
|
||||
return new(noopPool), nil
|
||||
}
|
||||
|
||||
func (noopPool) Get(_ int64) (util.SizeReadSeekCloser, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (noopPool) Put(_ int64, rc util.SizeReadSeekCloser) (bool, error) {
|
||||
return false, rc.Close() //nolint:wrapcheck
|
||||
}
|
||||
|
||||
type pool struct {
|
||||
mutex sync.Mutex
|
||||
size int
|
||||
evictList *list.List
|
||||
items map[int64]*list.Element
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
key int64
|
||||
value util.SizeReadSeekCloser
|
||||
}
|
||||
|
||||
// NewPool returns a Pooler that uses a LRU strategy to maintain a fixed pool
|
||||
// of util.SizeReadSeekCloser's keyed by their stream offset.
|
||||
func NewPool() (Pooler, error) {
|
||||
return &pool{
|
||||
size: runtime.NumCPU(),
|
||||
evictList: list.New(),
|
||||
items: make(map[int64]*list.Element),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *pool) Get(offset int64) (util.SizeReadSeekCloser, bool) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if ent, ok := p.items[offset]; ok {
|
||||
_ = p.removeElement(ent, false)
|
||||
|
||||
return ent.Value.(*entry).value, true //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
// Sort keys in descending order
|
||||
keys := p.keys()
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i] > keys[j] })
|
||||
|
||||
for _, k := range keys {
|
||||
// First key less than offset is the closest
|
||||
if k < offset {
|
||||
ent := p.items[k]
|
||||
_ = p.removeElement(ent, false)
|
||||
|
||||
return ent.Value.(*entry).value, true //nolint:forcetypeassert
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *pool) Put(offset int64, rc util.SizeReadSeekCloser) (bool, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if _, ok := p.items[offset]; ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ent := &entry{offset, rc}
|
||||
entry := p.evictList.PushFront(ent)
|
||||
p.items[offset] = entry
|
||||
|
||||
var err error
|
||||
|
||||
evict := p.evictList.Len() > p.size
|
||||
if evict {
|
||||
err = p.removeOldest()
|
||||
}
|
||||
|
||||
return evict, err
|
||||
}
|
||||
|
||||
func (p *pool) keys() []int64 {
|
||||
keys := make([]int64, len(p.items))
|
||||
i := 0
|
||||
|
||||
for ent := p.evictList.Back(); ent != nil; ent = ent.Prev() {
|
||||
keys[i] = ent.Value.(*entry).key //nolint:forcetypeassert
|
||||
i++
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (p *pool) removeOldest() error {
|
||||
if ent := p.evictList.Back(); ent != nil {
|
||||
return p.removeElement(ent, true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pool) removeElement(e *list.Element, cb bool) error {
|
||||
p.evictList.Remove(e)
|
||||
kv := e.Value.(*entry) //nolint:forcetypeassert
|
||||
delete(p.items, kv.key)
|
||||
|
||||
if cb {
|
||||
return kv.value.Close() //nolint:wrapcheck
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package util
|
||||
|
||||
import "bytes"
|
||||
|
||||
// CRC32Equal compares CRC32 checksums.
|
||||
func CRC32Equal(b []byte, c uint32) bool {
|
||||
return bytes.Equal(b, []byte{byte(0xff & (c >> 24)), byte(0xff & (c >> 16)), byte(0xff & (c >> 8)), byte(0xff & c)})
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Package util implements various utility types and interfaces.
|
||||
package util
|
||||
|
||||
import "io"
|
||||
|
||||
// SizeReadSeekCloser is an io.Reader, io.Seeker, and io.Closer with a Size
|
||||
// method.
|
||||
type SizeReadSeekCloser interface {
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Closer
|
||||
Size() int64
|
||||
}
|
||||
|
||||
// Reader is both an io.Reader and io.ByteReader.
|
||||
type Reader interface {
|
||||
io.Reader
|
||||
io.ByteReader
|
||||
}
|
||||
|
||||
// ReadCloser is a Reader that is also an io.Closer.
|
||||
type ReadCloser interface {
|
||||
Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
Reader
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NopCloser returns a ReadCloser with a no-op Close method wrapping the
|
||||
// provided Reader r.
|
||||
func NopCloser(r Reader) ReadCloser {
|
||||
return &nopCloser{r}
|
||||
}
|
||||
|
||||
type byteReadCloser struct {
|
||||
io.ReadCloser
|
||||
}
|
||||
|
||||
func (rc *byteReadCloser) ReadByte() (byte, error) {
|
||||
var b [1]byte
|
||||
|
||||
n, err := rc.Read(b[:])
|
||||
if err != nil {
|
||||
return 0, err //nolint:wrapcheck
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return 0, io.ErrNoProgress
|
||||
}
|
||||
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
// ByteReadCloser returns a ReadCloser either by returning the io.ReadCloser
|
||||
// r if it implements the interface, or wrapping it with a ReadByte method.
|
||||
func ByteReadCloser(r io.ReadCloser) ReadCloser {
|
||||
if rc, ok := r.(ReadCloser); ok {
|
||||
return rc
|
||||
}
|
||||
|
||||
return &byteReadCloser{r}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Package zstd implements the Zstandard decompressor.
|
||||
package zstd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
c io.Closer
|
||||
r *zstd.Decoder
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
zstdReaderPool sync.Pool
|
||||
|
||||
errAlreadyClosed = errors.New("zstd: already closed")
|
||||
errNeedOneReader = errors.New("zstd: need exactly one reader")
|
||||
)
|
||||
|
||||
func (rc *readCloser) Close() error {
|
||||
if rc.c == nil {
|
||||
return errAlreadyClosed
|
||||
}
|
||||
|
||||
if err := rc.c.Close(); err != nil {
|
||||
return fmt.Errorf("zstd: error closing: %w", err)
|
||||
}
|
||||
|
||||
zstdReaderPool.Put(rc.r)
|
||||
rc.c, rc.r = nil, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *readCloser) Read(p []byte) (int, error) {
|
||||
if rc.r == nil {
|
||||
return 0, errAlreadyClosed
|
||||
}
|
||||
|
||||
n, err := rc.r.Read(p)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
err = fmt.Errorf("zstd: error reading: %w", err)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReader returns a new Zstandard io.ReadCloser.
|
||||
func NewReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
r, ok := zstdReaderPool.Get().(*zstd.Decoder)
|
||||
if ok {
|
||||
if err = r.Reset(readers[0]); err != nil {
|
||||
return nil, fmt.Errorf("zstd: error resetting: %w", err)
|
||||
}
|
||||
} else {
|
||||
if r, err = zstd.NewReader(readers[0]); err != nil {
|
||||
return nil, fmt.Errorf("zstd: error creating reader: %w", err)
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(r, (*zstd.Decoder).Close)
|
||||
}
|
||||
|
||||
return &readCloser{
|
||||
c: readers[0],
|
||||
r: r,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Package sevenzip provides read access to 7-Zip archives.
|
||||
//
|
||||
// Portions Copyright 2016 The go4 Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
package sevenzip
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SizeReaderAt is a ReaderAt with a Size method.
|
||||
//
|
||||
// An io.SectionReader implements SizeReaderAt.
|
||||
type SizeReaderAt interface {
|
||||
Size() int64
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
// NewMultiReaderAt is like io.MultiReader but produces a ReaderAt
|
||||
// (and Size), instead of just a reader.
|
||||
func NewMultiReaderAt(parts ...SizeReaderAt) SizeReaderAt {
|
||||
m := &multiRA{
|
||||
parts: make([]offsetAndSource, 0, len(parts)),
|
||||
}
|
||||
var off int64
|
||||
for _, p := range parts {
|
||||
m.parts = append(m.parts, offsetAndSource{off, p})
|
||||
off += p.Size()
|
||||
}
|
||||
m.size = off
|
||||
return m
|
||||
}
|
||||
|
||||
type offsetAndSource struct {
|
||||
off int64
|
||||
SizeReaderAt
|
||||
}
|
||||
|
||||
type multiRA struct {
|
||||
parts []offsetAndSource
|
||||
size int64
|
||||
}
|
||||
|
||||
func (m *multiRA) Size() int64 { return m.size }
|
||||
|
||||
func (m *multiRA) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
wantN := len(p)
|
||||
|
||||
// Skip past the requested offset.
|
||||
skipParts := sort.Search(len(m.parts), func(i int) bool {
|
||||
// This function returns whether parts[i] will
|
||||
// contribute any bytes to our output.
|
||||
part := m.parts[i]
|
||||
return part.off+part.Size() > off
|
||||
})
|
||||
parts := m.parts[skipParts:]
|
||||
|
||||
// How far to skip in the first part.
|
||||
needSkip := off
|
||||
if len(parts) > 0 {
|
||||
needSkip -= parts[0].off
|
||||
}
|
||||
|
||||
for len(parts) > 0 && len(p) > 0 {
|
||||
readP := p
|
||||
partSize := parts[0].Size()
|
||||
if int64(len(readP)) > partSize-needSkip {
|
||||
readP = readP[:partSize-needSkip]
|
||||
}
|
||||
pn, err0 := parts[0].ReadAt(readP, needSkip)
|
||||
if err0 != nil {
|
||||
return n, err0
|
||||
}
|
||||
n += pn
|
||||
p = p[pn:]
|
||||
if int64(pn)+needSkip == partSize {
|
||||
parts = parts[1:]
|
||||
}
|
||||
needSkip = 0
|
||||
}
|
||||
|
||||
if n != wantN {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,855 @@
|
||||
// Package sevenzip provides read access to 7-zip archives.
|
||||
package sevenzip
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/pool"
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
"github.com/bodgit/plumbing"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
errFormat = errors.New("sevenzip: not a valid 7-zip file")
|
||||
errChecksum = errors.New("sevenzip: checksum error")
|
||||
errTooMuch = errors.New("sevenzip: too much data")
|
||||
errNegativeSize = errors.New("sevenzip: size cannot be negative")
|
||||
errOneHeaderStream = errors.New("sevenzip: expected only one folder in header stream")
|
||||
)
|
||||
|
||||
// sizeSectionReader wraps io.SectionReader to add Size() method
|
||||
type sizeSectionReader struct {
|
||||
*io.SectionReader
|
||||
size int64
|
||||
}
|
||||
|
||||
func (s *sizeSectionReader) Size() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// ReadError is used to wrap read I/O errors.
|
||||
type ReadError struct {
|
||||
// Encrypted is a hint that there is encryption involved.
|
||||
Encrypted bool
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ReadError) Error() string {
|
||||
return fmt.Sprintf("sevenzip: read error: %v", e.Err)
|
||||
}
|
||||
|
||||
func (e ReadError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// A Reader serves content from a 7-Zip archive.
|
||||
type Reader struct {
|
||||
r io.ReaderAt
|
||||
start int64
|
||||
end int64
|
||||
si *streamsInfo
|
||||
p string
|
||||
File []*File
|
||||
pool []pool.Pooler
|
||||
|
||||
fileListOnce sync.Once
|
||||
fileList []fileListEntry
|
||||
}
|
||||
|
||||
// A ReadCloser is a [Reader] that must be closed when no longer needed.
|
||||
type ReadCloser struct {
|
||||
f []afero.File
|
||||
Reader
|
||||
}
|
||||
|
||||
// A File is a single file in a 7-Zip archive. The file information is in the
|
||||
// embedded [FileHeader]. The file content can be accessed by calling
|
||||
// [File.Open].
|
||||
type File struct {
|
||||
FileHeader
|
||||
zip *Reader
|
||||
folder int
|
||||
offset int64
|
||||
}
|
||||
|
||||
type fileReader struct {
|
||||
rc util.SizeReadSeekCloser
|
||||
f *File
|
||||
n int64
|
||||
}
|
||||
|
||||
func (fr *fileReader) Stat() (iofs.FileInfo, error) {
|
||||
return headerFileInfo{&fr.f.FileHeader}, nil
|
||||
}
|
||||
|
||||
func (fr *fileReader) Read(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if fr.n <= 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if int64(len(p)) > fr.n {
|
||||
p = p[0:fr.n]
|
||||
}
|
||||
|
||||
n, err := fr.rc.Read(p)
|
||||
fr.n -= int64(n)
|
||||
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
e := &ReadError{
|
||||
Err: err,
|
||||
}
|
||||
|
||||
if frc, ok := fr.rc.(*folderReadCloser); ok {
|
||||
e.Encrypted = frc.hasEncryption
|
||||
}
|
||||
|
||||
return n, e
|
||||
}
|
||||
|
||||
return n, err //nolint:wrapcheck
|
||||
}
|
||||
|
||||
func (fr *fileReader) Close() error {
|
||||
if fr.rc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
offset, err := fr.rc.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sevenzip: error seeking current position: %w", err)
|
||||
}
|
||||
|
||||
if offset == fr.rc.Size() { // EOF reached
|
||||
if err := fr.rc.Close(); err != nil {
|
||||
return fmt.Errorf("sevenzip: error closing: %w", err)
|
||||
}
|
||||
} else {
|
||||
f := fr.f
|
||||
if _, err := f.zip.pool[f.folder].Put(offset, fr.rc); err != nil {
|
||||
return fmt.Errorf("sevenzip: error adding to pool: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fr.rc = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open returns an [io.ReadCloser] that provides access to the [File]'s
|
||||
// contents. Multiple files may be read concurrently.
|
||||
func (f *File) Open() (io.ReadCloser, error) {
|
||||
if f.FileHeader.isEmptyStream || f.FileHeader.isEmptyFile {
|
||||
// Return empty reader for directory or empty file
|
||||
return io.NopCloser(bytes.NewReader(nil)), nil
|
||||
}
|
||||
|
||||
rc, _ := f.zip.pool[f.folder].Get(f.offset)
|
||||
if rc == nil {
|
||||
var (
|
||||
encrypted bool
|
||||
err error
|
||||
)
|
||||
|
||||
rc, _, encrypted, err = f.zip.folderReader(f.zip.si, f.folder)
|
||||
if err != nil {
|
||||
return nil, &ReadError{
|
||||
Encrypted: encrypted,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := rc.Seek(f.offset, io.SeekStart); err != nil {
|
||||
e := &ReadError{
|
||||
Err: err,
|
||||
}
|
||||
|
||||
if fr, ok := rc.(*folderReadCloser); ok {
|
||||
e.Encrypted = fr.hasEncryption
|
||||
}
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &fileReader{
|
||||
rc: rc,
|
||||
f: f,
|
||||
n: int64(f.UncompressedSize), //nolint:gosec
|
||||
}, nil
|
||||
}
|
||||
|
||||
func openReader(fs afero.Fs, name string) (io.ReaderAt, int64, []afero.File, error) {
|
||||
f, err := fs.Open(filepath.Clean(name))
|
||||
if err != nil {
|
||||
return nil, 0, nil, fmt.Errorf("sevenzip: error opening: %w", err)
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
err = errors.Join(err, f.Close())
|
||||
|
||||
return nil, 0, nil, fmt.Errorf("sevenzip: error retrieving file info: %w", err)
|
||||
}
|
||||
|
||||
var reader io.ReaderAt = f
|
||||
|
||||
size := info.Size()
|
||||
files := []afero.File{f}
|
||||
|
||||
if ext := filepath.Ext(name); ext == ".001" {
|
||||
sr := []SizeReaderAt{&sizeSectionReader{io.NewSectionReader(f, 0, size), size}}
|
||||
|
||||
for i := 2; true; i++ {
|
||||
f, err := fs.Open(fmt.Sprintf("%s.%03d", strings.TrimSuffix(name, ext), i))
|
||||
if err != nil {
|
||||
if errors.Is(err, iofs.ErrNotExist) {
|
||||
break
|
||||
}
|
||||
|
||||
errs := make([]error, 0, len(files)+1)
|
||||
errs = append(errs, err)
|
||||
|
||||
for _, file := range files {
|
||||
errs = append(errs, file.Close())
|
||||
}
|
||||
|
||||
return nil, 0, nil, fmt.Errorf("sevenzip: error opening: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
files = append(files, f)
|
||||
|
||||
info, err = f.Stat()
|
||||
if err != nil {
|
||||
errs := make([]error, 0, len(files)+1)
|
||||
errs = append(errs, err)
|
||||
|
||||
for _, file := range files {
|
||||
errs = append(errs, file.Close())
|
||||
}
|
||||
|
||||
return nil, 0, nil, fmt.Errorf("sevenzip: error retrieving file info: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
sr = append(sr, io.NewSectionReader(f, 0, info.Size()))
|
||||
}
|
||||
|
||||
mr := NewMultiReaderAt(sr...)
|
||||
reader, size = mr, mr.Size()
|
||||
}
|
||||
|
||||
return reader, size, files, nil
|
||||
}
|
||||
|
||||
// OpenReaderWithPassword will open the 7-zip file specified by name using
|
||||
// password as the basis of the decryption key and return a [*ReadCloser]. If
|
||||
// name has a ".001" suffix it is assumed there are multiple volumes and each
|
||||
// sequential volume will be opened.
|
||||
func OpenReaderWithPassword(name, password string) (*ReadCloser, error) {
|
||||
reader, size, files, err := openReader(afero.NewOsFs(), name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := new(ReadCloser)
|
||||
r.p = password
|
||||
|
||||
if err := r.init(reader, size); err != nil {
|
||||
errs := make([]error, 0, len(files)+1)
|
||||
errs = append(errs, err)
|
||||
|
||||
for _, file := range files {
|
||||
errs = append(errs, file.Close())
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("sevenzip: error initialising: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
r.f = files
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// OpenReader will open the 7-zip file specified by name and return a
|
||||
// [*ReadCloser]. If name has a ".001" suffix it is assumed there are multiple
|
||||
// volumes and each sequential volume will be opened.
|
||||
func OpenReader(name string) (*ReadCloser, error) {
|
||||
return OpenReaderWithPassword(name, "")
|
||||
}
|
||||
|
||||
// NewReaderWithPassword returns a new [*Reader] reading from r using password
|
||||
// as the basis of the decryption key, which is assumed to have the given size
|
||||
// in bytes.
|
||||
func NewReaderWithPassword(r io.ReaderAt, size int64, password string) (*Reader, error) {
|
||||
if size < 0 {
|
||||
return nil, errNegativeSize
|
||||
}
|
||||
|
||||
zr := new(Reader)
|
||||
zr.p = password
|
||||
|
||||
if err := zr.init(r, size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zr, nil
|
||||
}
|
||||
|
||||
// NewReader returns a new [*Reader] reading from r, which is assumed to have
|
||||
// the given size in bytes.
|
||||
func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
|
||||
return NewReaderWithPassword(r, size, "")
|
||||
}
|
||||
|
||||
func (z *Reader) folderReader(si *streamsInfo, f int) (*folderReadCloser, uint32, bool, error) {
|
||||
// Create a SectionReader covering all of the streams data
|
||||
return si.FolderReader(io.NewSectionReader(z.r, z.start, z.end-z.start), f, z.p)
|
||||
}
|
||||
|
||||
const (
|
||||
chunkSize = 4096
|
||||
searchLimit = 1 << 20 // 1 MiB
|
||||
)
|
||||
|
||||
func findSignature(r io.ReaderAt, search []byte) ([]int64, error) {
|
||||
chunk := make([]byte, chunkSize+len(search))
|
||||
offsets := make([]int64, 0, 2)
|
||||
|
||||
for offset := int64(0); offset < searchLimit; offset += chunkSize {
|
||||
n, err := r.ReadAt(chunk, offset)
|
||||
|
||||
for i := 0; ; {
|
||||
idx := bytes.Index(chunk[i:n], search)
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
offsets = append(offsets, offset+int64(i+idx))
|
||||
if offsets[0] == 0 {
|
||||
// If signature is at the beginning, return immediately, it's a regular archive
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
i += idx + 1
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("sevenzip: error reading chunk: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen,gocognit,gocyclo,maintidx
|
||||
func (z *Reader) init(r io.ReaderAt, size int64) (err error) {
|
||||
h := crc32.NewIEEE()
|
||||
tra := plumbing.TeeReaderAt(r, h)
|
||||
|
||||
var (
|
||||
signature = []byte{'7', 'z', 0xbc, 0xaf, 0x27, 0x1c}
|
||||
offsets []int64
|
||||
)
|
||||
|
||||
offsets, err = findSignature(r, signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(offsets) == 0 {
|
||||
return errFormat
|
||||
}
|
||||
|
||||
var (
|
||||
sr *io.SectionReader
|
||||
off int64
|
||||
start startHeader
|
||||
)
|
||||
|
||||
for _, off = range offsets {
|
||||
sr = io.NewSectionReader(tra, off, size-off) // Will only read first 32 bytes
|
||||
|
||||
var sh signatureHeader
|
||||
if err = binary.Read(sr, binary.LittleEndian, &sh); err != nil {
|
||||
return fmt.Errorf("sevenzip: error reading signature header: %w", err)
|
||||
}
|
||||
|
||||
z.r = r
|
||||
|
||||
h.Reset()
|
||||
|
||||
if err = binary.Read(sr, binary.LittleEndian, &start); err != nil {
|
||||
return fmt.Errorf("sevenzip: error reading start header: %w", err)
|
||||
}
|
||||
|
||||
// CRC of the start header should match
|
||||
if util.CRC32Equal(h.Sum(nil), sh.CRC) {
|
||||
break
|
||||
}
|
||||
|
||||
err = errChecksum
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Work out where we are in the file (32, avoiding magic numbers)
|
||||
if z.start, err = sr.Seek(0, io.SeekCurrent); err != nil {
|
||||
return fmt.Errorf("sevenzip: error seeking current position: %w", err)
|
||||
}
|
||||
|
||||
// Seek over the streams
|
||||
if z.end, err = sr.Seek(int64(start.Offset), io.SeekCurrent); err != nil { //nolint:gosec
|
||||
return fmt.Errorf("sevenzip: error seeking over streams: %w", err)
|
||||
}
|
||||
|
||||
z.start += off
|
||||
z.end += off
|
||||
|
||||
h.Reset()
|
||||
|
||||
// Bound bufio.Reader otherwise it can read trailing garbage which screws up the CRC check
|
||||
br := bufio.NewReader(io.NewSectionReader(tra, z.end, int64(start.Size))) //nolint:gosec
|
||||
|
||||
var (
|
||||
id byte
|
||||
header *header
|
||||
streamsInfo *streamsInfo
|
||||
)
|
||||
|
||||
if id, err = br.ReadByte(); err != nil {
|
||||
return fmt.Errorf("sevenzip: error reading header id: %w", err)
|
||||
}
|
||||
|
||||
switch id {
|
||||
case idHeader:
|
||||
if header, err = readHeader(br); err != nil {
|
||||
return err
|
||||
}
|
||||
case idEncodedHeader:
|
||||
if streamsInfo, err = readStreamsInfo(br); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errUnexpectedID
|
||||
}
|
||||
|
||||
// If there's more data to read, we've not parsed this correctly. This
|
||||
// won't break with trailing data as the bufio.Reader was bounded
|
||||
if n, _ := io.CopyN(io.Discard, br, 1); n != 0 {
|
||||
return errTooMuch
|
||||
}
|
||||
|
||||
// CRC should match the one from the start header
|
||||
if !util.CRC32Equal(h.Sum(nil), start.CRC) {
|
||||
return errChecksum
|
||||
}
|
||||
|
||||
// If the header was encoded we should have sufficient information now
|
||||
// to decode it
|
||||
if streamsInfo != nil {
|
||||
if streamsInfo.Folders() != 1 {
|
||||
return errOneHeaderStream
|
||||
}
|
||||
|
||||
var (
|
||||
fr *folderReadCloser
|
||||
crc uint32
|
||||
encrypted bool
|
||||
)
|
||||
|
||||
fr, crc, encrypted, err = z.folderReader(streamsInfo, 0)
|
||||
if err != nil {
|
||||
return &ReadError{
|
||||
Encrypted: encrypted,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errors.Join(err, fr.Close())
|
||||
}()
|
||||
|
||||
if header, err = readEncodedHeader(util.ByteReadCloser(fr)); err != nil {
|
||||
return &ReadError{
|
||||
Encrypted: fr.hasEncryption,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if crc != 0 && !util.CRC32Equal(fr.Checksum(), crc) {
|
||||
return errChecksum
|
||||
}
|
||||
}
|
||||
|
||||
z.si = header.streamsInfo
|
||||
|
||||
// spew.Dump(header)
|
||||
filesPerStream := make(map[int]int, z.si.Folders())
|
||||
|
||||
if header.filesInfo != nil {
|
||||
folder, offset := 0, int64(0)
|
||||
z.File = make([]*File, 0, len(header.filesInfo.file))
|
||||
j := 0
|
||||
|
||||
for _, fh := range header.filesInfo.file {
|
||||
f := new(File)
|
||||
f.zip = z
|
||||
f.FileHeader = fh
|
||||
|
||||
if f.FileHeader.FileInfo().IsDir() && !strings.HasSuffix(f.FileHeader.Name, "/") {
|
||||
f.FileHeader.Name += "/"
|
||||
}
|
||||
|
||||
if !fh.isEmptyStream && !fh.isEmptyFile {
|
||||
f.folder, _, _ = header.streamsInfo.FileFolderAndSize(j)
|
||||
|
||||
// Make an exported copy of the folder index
|
||||
f.Stream = f.folder
|
||||
|
||||
filesPerStream[f.folder]++
|
||||
|
||||
if f.folder != folder {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
f.offset = offset
|
||||
offset += int64(f.UncompressedSize) //nolint:gosec
|
||||
folder = f.folder
|
||||
j++
|
||||
}
|
||||
|
||||
z.File = append(z.File, f)
|
||||
}
|
||||
}
|
||||
|
||||
// spew.Dump(filesPerStream)
|
||||
|
||||
z.pool = make([]pool.Pooler, z.si.Folders())
|
||||
for i := range z.pool {
|
||||
var newPool pool.Constructor = pool.NewNoopPool
|
||||
|
||||
if filesPerStream[i] > 1 {
|
||||
newPool = pool.NewPool
|
||||
}
|
||||
|
||||
if z.pool[i], err = newPool(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Volumes returns the list of volumes that have been opened as part of the
|
||||
// current archive.
|
||||
func (rc *ReadCloser) Volumes() []string {
|
||||
volumes := make([]string, len(rc.f))
|
||||
for idx, f := range rc.f {
|
||||
volumes[idx] = f.Name()
|
||||
}
|
||||
|
||||
return volumes
|
||||
}
|
||||
|
||||
// Close closes the 7-zip file or volumes, rendering them unusable for I/O.
|
||||
func (rc *ReadCloser) Close() error {
|
||||
errs := make([]error, 0, len(rc.f))
|
||||
|
||||
for _, f := range rc.f {
|
||||
errs = append(errs, f.Close())
|
||||
}
|
||||
|
||||
err := errors.Join(errs...)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("sevenzip: error closing: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type fileListEntry struct {
|
||||
name string
|
||||
file *File
|
||||
isDir bool
|
||||
isDup bool
|
||||
}
|
||||
|
||||
type fileInfoDirEntry interface {
|
||||
iofs.FileInfo
|
||||
iofs.DirEntry
|
||||
}
|
||||
|
||||
func (e *fileListEntry) stat() (fileInfoDirEntry, error) {
|
||||
if e.isDup {
|
||||
return nil, errors.New(e.name + ": duplicate entries in 7-zip file") //nolint:err113
|
||||
}
|
||||
|
||||
if !e.isDir {
|
||||
return headerFileInfo{&e.file.FileHeader}, nil
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (e *fileListEntry) Name() string {
|
||||
_, elem := split(e.name)
|
||||
|
||||
return elem
|
||||
}
|
||||
|
||||
func (e *fileListEntry) Size() int64 { return 0 }
|
||||
func (e *fileListEntry) Mode() iofs.FileMode { return iofs.ModeDir | 0o555 }
|
||||
func (e *fileListEntry) Type() iofs.FileMode { return iofs.ModeDir }
|
||||
func (e *fileListEntry) IsDir() bool { return true }
|
||||
func (e *fileListEntry) Sys() interface{} { return nil }
|
||||
|
||||
func (e *fileListEntry) ModTime() time.Time {
|
||||
if e.file == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
return e.file.FileHeader.Modified.UTC()
|
||||
}
|
||||
|
||||
func (e *fileListEntry) Info() (iofs.FileInfo, error) { return e, nil }
|
||||
|
||||
func toValidName(name string) string {
|
||||
name = strings.ReplaceAll(name, `\`, `/`)
|
||||
|
||||
p := strings.TrimPrefix(path.Clean(name), "/")
|
||||
|
||||
for strings.HasPrefix(p, "../") {
|
||||
p = p[len("../"):]
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func (z *Reader) initFileList() {
|
||||
z.fileListOnce.Do(func() {
|
||||
files := make(map[string]int)
|
||||
knownDirs := make(map[string]int)
|
||||
|
||||
dirs := make(map[string]struct{})
|
||||
|
||||
for _, file := range z.File {
|
||||
isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/'
|
||||
|
||||
name := toValidName(file.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if idx, ok := files[name]; ok {
|
||||
z.fileList[idx].isDup = true
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if idx, ok := knownDirs[name]; ok {
|
||||
z.fileList[idx].isDup = true
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
|
||||
dirs[dir] = struct{}{}
|
||||
}
|
||||
|
||||
idx := len(z.fileList)
|
||||
entry := fileListEntry{
|
||||
name: name,
|
||||
file: file,
|
||||
isDir: isDir,
|
||||
}
|
||||
z.fileList = append(z.fileList, entry)
|
||||
|
||||
if isDir {
|
||||
knownDirs[name] = idx
|
||||
} else {
|
||||
files[name] = idx
|
||||
}
|
||||
}
|
||||
|
||||
for dir := range dirs {
|
||||
if _, ok := knownDirs[dir]; !ok {
|
||||
if idx, ok := files[dir]; ok {
|
||||
z.fileList[idx].isDup = true
|
||||
} else {
|
||||
entry := fileListEntry{
|
||||
name: dir,
|
||||
file: nil,
|
||||
isDir: true,
|
||||
}
|
||||
z.fileList = append(z.fileList, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(z.fileList, func(i, j int) bool { return fileEntryLess(z.fileList[i].name, z.fileList[j].name) })
|
||||
})
|
||||
}
|
||||
|
||||
func fileEntryLess(x, y string) bool {
|
||||
xdir, xelem := split(x)
|
||||
ydir, yelem := split(y)
|
||||
|
||||
return xdir < ydir || xdir == ydir && xelem < yelem
|
||||
}
|
||||
|
||||
// Open opens the named file in the 7-zip archive, using the semantics of
|
||||
// [fs.FS.Open]: paths are always slash separated, with no leading / or ../
|
||||
// elements.
|
||||
func (z *Reader) Open(name string) (iofs.File, error) {
|
||||
z.initFileList()
|
||||
|
||||
if !iofs.ValidPath(name) {
|
||||
return nil, &iofs.PathError{Op: "open", Path: name, Err: iofs.ErrInvalid}
|
||||
}
|
||||
|
||||
e := z.openLookup(name)
|
||||
if e == nil {
|
||||
return nil, &iofs.PathError{Op: "open", Path: name, Err: iofs.ErrNotExist}
|
||||
}
|
||||
|
||||
if e.isDir {
|
||||
return &openDir{e, z.openReadDir(name), 0}, nil
|
||||
}
|
||||
|
||||
rc, err := e.file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rc.(iofs.File), nil //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
func split(name string) (dir, elem string) {
|
||||
if len(name) > 0 && name[len(name)-1] == '/' {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
i := len(name) - 1
|
||||
for i >= 0 && name[i] != '/' {
|
||||
i--
|
||||
}
|
||||
|
||||
if i < 0 {
|
||||
return ".", name
|
||||
}
|
||||
|
||||
return name[:i], name[i+1:]
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var dotFile = &fileListEntry{name: "./", isDir: true}
|
||||
|
||||
func (z *Reader) openLookup(name string) *fileListEntry {
|
||||
if name == "." {
|
||||
return dotFile
|
||||
}
|
||||
|
||||
dir, elem := split(name)
|
||||
|
||||
files := z.fileList
|
||||
i := sort.Search(len(files), func(i int) bool {
|
||||
idir, ielem := split(files[i].name)
|
||||
|
||||
return idir > dir || idir == dir && ielem >= elem
|
||||
})
|
||||
|
||||
if i < len(files) {
|
||||
fname := files[i].name
|
||||
if fname == name || len(fname) == len(name)+1 && fname[len(name)] == '/' && fname[:len(name)] == name {
|
||||
return &files[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Reader) openReadDir(dir string) []fileListEntry {
|
||||
files := z.fileList
|
||||
|
||||
i := sort.Search(len(files), func(i int) bool {
|
||||
idir, _ := split(files[i].name)
|
||||
|
||||
return idir >= dir
|
||||
})
|
||||
|
||||
j := sort.Search(len(files), func(j int) bool {
|
||||
jdir, _ := split(files[j].name)
|
||||
|
||||
return jdir > dir
|
||||
})
|
||||
|
||||
return files[i:j]
|
||||
}
|
||||
|
||||
type openDir struct {
|
||||
e *fileListEntry
|
||||
files []fileListEntry
|
||||
offset int
|
||||
}
|
||||
|
||||
func (d *openDir) Close() error { return nil }
|
||||
func (d *openDir) Stat() (iofs.FileInfo, error) { return d.e.stat() }
|
||||
|
||||
var errIsDirectory = errors.New("is a directory")
|
||||
|
||||
func (d *openDir) Read([]byte) (int, error) {
|
||||
return 0, &iofs.PathError{Op: "read", Path: d.e.name, Err: errIsDirectory}
|
||||
}
|
||||
|
||||
func (d *openDir) ReadDir(count int) ([]iofs.DirEntry, error) {
|
||||
n := len(d.files) - d.offset
|
||||
if count > 0 && n > count {
|
||||
n = count
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
if count <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
list := make([]iofs.DirEntry, n)
|
||||
for i := range list {
|
||||
s, err := d.files[d.offset+i].stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list[i] = s
|
||||
}
|
||||
|
||||
d.offset += n
|
||||
|
||||
return list, nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package sevenzip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/aes7z"
|
||||
"bookhoard/internal/sevenzip/internal/bcj2"
|
||||
"bookhoard/internal/sevenzip/internal/bra"
|
||||
"bookhoard/internal/sevenzip/internal/brotli"
|
||||
"bookhoard/internal/sevenzip/internal/bzip2"
|
||||
"bookhoard/internal/sevenzip/internal/deflate"
|
||||
"bookhoard/internal/sevenzip/internal/delta"
|
||||
"bookhoard/internal/sevenzip/internal/lz4"
|
||||
"bookhoard/internal/sevenzip/internal/lzma"
|
||||
"bookhoard/internal/sevenzip/internal/lzma2"
|
||||
"bookhoard/internal/sevenzip/internal/zstd"
|
||||
)
|
||||
|
||||
// Decompressor describes the function signature that decompression/decryption
|
||||
// methods must implement to return a new instance of themselves. They are
|
||||
// passed any property bytes, the size of the stream and a slice of at least
|
||||
// one io.ReadCloser's providing the stream(s) of bytes.
|
||||
type Decompressor func([]byte, uint64, []io.ReadCloser) (io.ReadCloser, error)
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals
|
||||
decompressors sync.Map
|
||||
|
||||
errNeedOneReader = errors.New("copy: need exactly one reader")
|
||||
)
|
||||
|
||||
func newCopyReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
|
||||
if len(readers) != 1 {
|
||||
return nil, errNeedOneReader
|
||||
}
|
||||
// just return the passed io.ReadCloser)
|
||||
return readers[0], nil
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
// Copy
|
||||
RegisterDecompressor([]byte{0x00}, Decompressor(newCopyReader))
|
||||
// Delta
|
||||
RegisterDecompressor([]byte{0x03}, Decompressor(delta.NewReader))
|
||||
// LZMA
|
||||
RegisterDecompressor([]byte{0x03, 0x01, 0x01}, Decompressor(lzma.NewReader))
|
||||
// BCJ
|
||||
RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x03}, Decompressor(bra.NewBCJReader))
|
||||
// BCJ2
|
||||
RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x1b}, Decompressor(bcj2.NewReader))
|
||||
// PPC
|
||||
RegisterDecompressor([]byte{0x03, 0x03, 0x02, 0x05}, Decompressor(bra.NewPPCReader))
|
||||
// ARM
|
||||
RegisterDecompressor([]byte{0x03, 0x03, 0x05, 0x01}, Decompressor(bra.NewARMReader))
|
||||
// SPARC
|
||||
RegisterDecompressor([]byte{0x03, 0x03, 0x08, 0x05}, Decompressor(bra.NewSPARCReader))
|
||||
// Deflate
|
||||
RegisterDecompressor([]byte{0x04, 0x01, 0x08}, Decompressor(deflate.NewReader))
|
||||
// Bzip2
|
||||
RegisterDecompressor([]byte{0x04, 0x02, 0x02}, Decompressor(bzip2.NewReader))
|
||||
// Zstandard
|
||||
RegisterDecompressor([]byte{0x04, 0xf7, 0x11, 0x01}, Decompressor(zstd.NewReader))
|
||||
// Brotli
|
||||
RegisterDecompressor([]byte{0x04, 0xf7, 0x11, 0x02}, Decompressor(brotli.NewReader))
|
||||
// LZ4
|
||||
RegisterDecompressor([]byte{0x04, 0xf7, 0x11, 0x04}, Decompressor(lz4.NewReader))
|
||||
// AES-CBC-256 & SHA-256
|
||||
RegisterDecompressor([]byte{0x06, 0xf1, 0x07, 0x01}, Decompressor(aes7z.NewReader))
|
||||
// LZMA2
|
||||
RegisterDecompressor([]byte{0x21}, Decompressor(lzma2.NewReader))
|
||||
}
|
||||
|
||||
// RegisterDecompressor allows custom decompressors for a specified method ID.
|
||||
func RegisterDecompressor(method []byte, dcomp Decompressor) {
|
||||
decompressors.Store(string(method), dcomp)
|
||||
}
|
||||
|
||||
func decompressor(method []byte) Decompressor {
|
||||
di, ok := decompressors.Load(string(method))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if d, ok := di.(Decompressor); ok {
|
||||
return d
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
package sevenzip
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
"github.com/bodgit/plumbing"
|
||||
)
|
||||
|
||||
var (
|
||||
errAlgorithm = errors.New("sevenzip: unsupported compression algorithm")
|
||||
errInvalidWhence = errors.New("invalid whence")
|
||||
errNegativeSeek = errors.New("negative seek")
|
||||
errSeekBackwards = errors.New("cannot seek backwards")
|
||||
errSeekEOF = errors.New("cannot seek beyond EOF")
|
||||
errMultipleOutputStreams = errors.New("more than one output stream")
|
||||
errNoBoundStream = errors.New("cannot find bound stream")
|
||||
errNoUnboundStream = errors.New("expecting one unbound output stream")
|
||||
)
|
||||
|
||||
// CryptoReadCloser adds a Password method to decompressors.
|
||||
type CryptoReadCloser interface {
|
||||
Password(password string) error
|
||||
}
|
||||
|
||||
type signatureHeader struct {
|
||||
Signature [6]byte
|
||||
Major byte
|
||||
Minor byte
|
||||
CRC uint32
|
||||
}
|
||||
|
||||
type startHeader struct {
|
||||
Offset uint64
|
||||
Size uint64
|
||||
CRC uint32
|
||||
}
|
||||
|
||||
type packInfo struct {
|
||||
position uint64
|
||||
streams uint64
|
||||
size []uint64
|
||||
digest []uint32
|
||||
}
|
||||
|
||||
type coder struct {
|
||||
id []byte
|
||||
in, out uint64
|
||||
properties []byte
|
||||
}
|
||||
|
||||
type bindPair struct {
|
||||
in, out uint64
|
||||
}
|
||||
|
||||
type folder struct {
|
||||
in, out uint64
|
||||
packedStreams uint64
|
||||
coder []*coder
|
||||
bindPair []*bindPair
|
||||
size []uint64
|
||||
packed []uint64
|
||||
}
|
||||
|
||||
func (f *folder) findInBindPair(i uint64) *bindPair {
|
||||
for _, v := range f.bindPair {
|
||||
if v.in == i {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *folder) findOutBindPair(i uint64) *bindPair {
|
||||
for _, v := range f.bindPair {
|
||||
if v.out == i {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *folder) coderReader(readers []io.ReadCloser, coder uint64, password string) (io.ReadCloser, bool, error) {
|
||||
dcomp := decompressor(f.coder[coder].id)
|
||||
if dcomp == nil {
|
||||
return nil, false, errAlgorithm
|
||||
}
|
||||
|
||||
cr, err := dcomp(f.coder[coder].properties, f.size[coder], readers)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
crc, ok := cr.(CryptoReadCloser)
|
||||
if ok {
|
||||
if err = crc.Password(password); err != nil {
|
||||
return nil, true, fmt.Errorf("sevenzip: error setting password: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return plumbing.LimitReadCloser(cr, int64(f.size[coder])), ok, nil //nolint:gosec
|
||||
}
|
||||
|
||||
type folderReadCloser struct {
|
||||
io.ReadCloser
|
||||
h hash.Hash
|
||||
wc *plumbing.WriteCounter
|
||||
size int64
|
||||
hasEncryption bool
|
||||
}
|
||||
|
||||
func (rc *folderReadCloser) Checksum() []byte {
|
||||
return rc.h.Sum(nil)
|
||||
}
|
||||
|
||||
func (rc *folderReadCloser) Seek(offset int64, whence int) (int64, error) {
|
||||
var newo int64
|
||||
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
newo = offset
|
||||
case io.SeekCurrent:
|
||||
newo = int64(rc.wc.Count()) + offset //nolint:gosec
|
||||
case io.SeekEnd:
|
||||
newo = rc.Size() + offset
|
||||
default:
|
||||
return 0, errInvalidWhence
|
||||
}
|
||||
|
||||
if newo < 0 {
|
||||
return 0, errNegativeSeek
|
||||
}
|
||||
|
||||
if uint64(newo) < rc.wc.Count() {
|
||||
return 0, errSeekBackwards
|
||||
}
|
||||
|
||||
if newo > rc.Size() {
|
||||
return 0, errSeekEOF
|
||||
}
|
||||
|
||||
if _, err := io.CopyN(io.Discard, rc, newo-int64(rc.wc.Count())); err != nil { //nolint:gosec
|
||||
return 0, fmt.Errorf("sevenzip: error seeking: %w", err)
|
||||
}
|
||||
|
||||
return newo, nil
|
||||
}
|
||||
|
||||
func (rc *folderReadCloser) Size() int64 {
|
||||
return rc.size
|
||||
}
|
||||
|
||||
func newFolderReadCloser(rc io.ReadCloser, size int64, hasEncryption bool) *folderReadCloser {
|
||||
nrc := new(folderReadCloser)
|
||||
nrc.h = crc32.NewIEEE()
|
||||
nrc.wc = new(plumbing.WriteCounter)
|
||||
nrc.ReadCloser = plumbing.TeeReadCloser(rc, io.MultiWriter(nrc.h, nrc.wc))
|
||||
nrc.size = size
|
||||
nrc.hasEncryption = hasEncryption
|
||||
|
||||
return nrc
|
||||
}
|
||||
|
||||
func (f *folder) unpackSize() uint64 {
|
||||
if len(f.size) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i := len(f.size) - 1; i >= 0; i-- {
|
||||
if f.findOutBindPair(uint64(i)) == nil {
|
||||
return f.size[i]
|
||||
}
|
||||
}
|
||||
|
||||
return f.size[len(f.size)-1]
|
||||
}
|
||||
|
||||
type unpackInfo struct {
|
||||
folder []*folder
|
||||
digest []uint32
|
||||
}
|
||||
|
||||
type subStreamsInfo struct {
|
||||
streams []uint64
|
||||
size []uint64
|
||||
digest []uint32
|
||||
}
|
||||
|
||||
type streamsInfo struct {
|
||||
packInfo *packInfo
|
||||
unpackInfo *unpackInfo
|
||||
subStreamsInfo *subStreamsInfo
|
||||
}
|
||||
|
||||
func (si *streamsInfo) Folders() int {
|
||||
if si != nil && si.unpackInfo != nil {
|
||||
return len(si.unpackInfo.folder)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (si *streamsInfo) FileFolderAndSize(file int) (int, uint64, uint32) {
|
||||
var (
|
||||
folder int
|
||||
streams uint64 = 1
|
||||
crc uint32
|
||||
)
|
||||
|
||||
if si.subStreamsInfo != nil {
|
||||
total := uint64(0)
|
||||
|
||||
for folder, streams = range si.subStreamsInfo.streams {
|
||||
total += streams
|
||||
if uint64(file) < total { //nolint:gosec
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(si.subStreamsInfo.digest) > 0 {
|
||||
crc = si.subStreamsInfo.digest[file]
|
||||
}
|
||||
}
|
||||
|
||||
if streams == 1 {
|
||||
if len(si.unpackInfo.digest) > 0 {
|
||||
crc = si.unpackInfo.digest[folder]
|
||||
}
|
||||
|
||||
return folder, si.unpackInfo.folder[folder].size[len(si.unpackInfo.folder[folder].coder)-1], crc
|
||||
}
|
||||
|
||||
return folder, si.subStreamsInfo.size[file], crc
|
||||
}
|
||||
|
||||
func (si *streamsInfo) folderOffset(folder int) int64 {
|
||||
offset := uint64(0)
|
||||
|
||||
for i, k := 0, uint64(0); i < folder; i++ {
|
||||
for j := k; j < k+si.unpackInfo.folder[i].packedStreams; j++ {
|
||||
offset += si.packInfo.size[j]
|
||||
}
|
||||
|
||||
k += si.unpackInfo.folder[i].packedStreams
|
||||
}
|
||||
|
||||
return int64(si.packInfo.position + offset) //nolint:gosec
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen,lll
|
||||
func (si *streamsInfo) FolderReader(r io.ReaderAt, folder int, password string) (*folderReadCloser, uint32, bool, error) {
|
||||
f := si.unpackInfo.folder[folder]
|
||||
in := make([]io.ReadCloser, f.in)
|
||||
out := make([]io.ReadCloser, f.out)
|
||||
|
||||
packedOffset := 0
|
||||
for i := 0; i < folder; i++ {
|
||||
packedOffset += len(si.unpackInfo.folder[i].packed)
|
||||
}
|
||||
|
||||
offset := int64(0)
|
||||
|
||||
for i, input := range f.packed {
|
||||
size := int64(si.packInfo.size[packedOffset+i]) //nolint:gosec
|
||||
in[input] = util.NopCloser(bufio.NewReader(io.NewSectionReader(r, si.folderOffset(folder)+offset, size)))
|
||||
offset += size
|
||||
}
|
||||
|
||||
var (
|
||||
hasEncryption bool
|
||||
input, output uint64
|
||||
)
|
||||
|
||||
for i, c := range f.coder {
|
||||
if c.out != 1 {
|
||||
return nil, 0, hasEncryption, errMultipleOutputStreams
|
||||
}
|
||||
|
||||
for j := input; j < input+c.in; j++ {
|
||||
if in[j] != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
bp := f.findInBindPair(j)
|
||||
if bp == nil || out[bp.out] == nil {
|
||||
return nil, 0, hasEncryption, errNoBoundStream
|
||||
}
|
||||
|
||||
in[j] = out[bp.out]
|
||||
}
|
||||
|
||||
var (
|
||||
isEncrypted bool
|
||||
err error
|
||||
)
|
||||
|
||||
out[output], isEncrypted, err = f.coderReader(in[input:input+c.in], uint64(i), password) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, 0, hasEncryption, err
|
||||
}
|
||||
|
||||
if isEncrypted {
|
||||
hasEncryption = true
|
||||
}
|
||||
|
||||
input += c.in
|
||||
output += c.out
|
||||
}
|
||||
|
||||
unbound := make([]uint64, 0, f.out)
|
||||
|
||||
for i := uint64(0); i < f.out; i++ {
|
||||
if bp := f.findOutBindPair(i); bp == nil {
|
||||
unbound = append(unbound, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unbound) != 1 || out[unbound[0]] == nil {
|
||||
return nil, 0, hasEncryption, errNoUnboundStream
|
||||
}
|
||||
|
||||
fr := newFolderReadCloser(out[unbound[0]], int64(f.unpackSize()), hasEncryption) //nolint:gosec
|
||||
|
||||
if si.unpackInfo.digest != nil {
|
||||
return fr, si.unpackInfo.digest[folder], hasEncryption, nil
|
||||
}
|
||||
|
||||
return fr, 0, hasEncryption, nil
|
||||
}
|
||||
|
||||
type filesInfo struct {
|
||||
file []FileHeader
|
||||
}
|
||||
|
||||
type header struct {
|
||||
streamsInfo *streamsInfo
|
||||
filesInfo *filesInfo
|
||||
}
|
||||
|
||||
// FileHeader describes a file within a 7-zip file.
|
||||
type FileHeader struct {
|
||||
Name string
|
||||
Created time.Time
|
||||
Accessed time.Time
|
||||
Modified time.Time
|
||||
Attributes uint32
|
||||
CRC32 uint32
|
||||
UncompressedSize uint64
|
||||
|
||||
// Stream is an opaque identifier representing the compressed stream
|
||||
// that contains the file. Any File with the same value can be assumed
|
||||
// to be stored within the same stream.
|
||||
Stream int
|
||||
|
||||
isEmptyStream bool
|
||||
isEmptyFile bool
|
||||
}
|
||||
|
||||
// FileInfo returns an [fs.FileInfo] for the FileHeader.
|
||||
func (h *FileHeader) FileInfo() iofs.FileInfo {
|
||||
return headerFileInfo{h}
|
||||
}
|
||||
|
||||
type headerFileInfo struct {
|
||||
fh *FileHeader
|
||||
}
|
||||
|
||||
func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }
|
||||
func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) } //nolint:gosec
|
||||
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.Modified.UTC() }
|
||||
func (fi headerFileInfo) Mode() iofs.FileMode { return fi.fh.Mode() }
|
||||
func (fi headerFileInfo) Type() iofs.FileMode { return fi.fh.Mode().Type() }
|
||||
func (fi headerFileInfo) Sys() interface{} { return fi.fh }
|
||||
|
||||
func (fi headerFileInfo) Info() (iofs.FileInfo, error) { return fi, nil }
|
||||
|
||||
const (
|
||||
// Unix constants. The specification doesn't mention them,
|
||||
// but these seem to be the values agreed on by tools.
|
||||
sIFMT = 0xf000
|
||||
sIFSOCK = 0xc000
|
||||
sIFLNK = 0xa000
|
||||
sIFREG = 0x8000
|
||||
sIFBLK = 0x6000
|
||||
sIFDIR = 0x4000
|
||||
sIFCHR = 0x2000
|
||||
sIFIFO = 0x1000
|
||||
sISUID = 0x800
|
||||
sISGID = 0x400
|
||||
sISVTX = 0x200
|
||||
|
||||
msdosDir = 0x10
|
||||
msdosReadOnly = 0x01
|
||||
)
|
||||
|
||||
// Mode returns the permission and mode bits for the FileHeader.
|
||||
func (h *FileHeader) Mode() (mode iofs.FileMode) {
|
||||
// Prefer the POSIX attributes if they're present
|
||||
if h.Attributes&0xf0000000 != 0 {
|
||||
mode = unixModeToFileMode(h.Attributes >> 16)
|
||||
} else {
|
||||
mode = msdosModeToFileMode(h.Attributes)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func msdosModeToFileMode(m uint32) (mode iofs.FileMode) {
|
||||
if m&msdosDir != 0 {
|
||||
mode = iofs.ModeDir | 0o777
|
||||
} else {
|
||||
mode = 0o666
|
||||
}
|
||||
|
||||
if m&msdosReadOnly != 0 {
|
||||
mode &^= 0o222
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func unixModeToFileMode(m uint32) iofs.FileMode {
|
||||
mode := iofs.FileMode(m & 0o777)
|
||||
|
||||
switch m & sIFMT {
|
||||
case sIFBLK:
|
||||
mode |= iofs.ModeDevice
|
||||
case sIFCHR:
|
||||
mode |= iofs.ModeDevice | iofs.ModeCharDevice
|
||||
case sIFDIR:
|
||||
mode |= iofs.ModeDir
|
||||
case sIFIFO:
|
||||
mode |= iofs.ModeNamedPipe
|
||||
case sIFLNK:
|
||||
mode |= iofs.ModeSymlink
|
||||
case sIFREG:
|
||||
// nothing to do
|
||||
case sIFSOCK:
|
||||
mode |= iofs.ModeSocket
|
||||
}
|
||||
|
||||
if m&sISGID != 0 {
|
||||
mode |= iofs.ModeSetgid
|
||||
}
|
||||
|
||||
if m&sISUID != 0 {
|
||||
mode |= iofs.ModeSetuid
|
||||
}
|
||||
|
||||
if m&sISVTX != 0 {
|
||||
mode |= iofs.ModeSticky
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
@@ -0,0 +1,876 @@
|
||||
package sevenzip
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
"time"
|
||||
|
||||
"bookhoard/internal/sevenzip/internal/util"
|
||||
"github.com/bodgit/windows"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const (
|
||||
idEnd = iota
|
||||
idHeader
|
||||
idArchiveProperties
|
||||
idAdditionalStreamsInfo
|
||||
idMainStreamsInfo
|
||||
idFilesInfo
|
||||
idPackInfo
|
||||
idUnpackInfo
|
||||
idSubStreamsInfo
|
||||
idSize
|
||||
idCRC
|
||||
idFolder
|
||||
idCodersUnpackSize
|
||||
idNumUnpackStream
|
||||
idEmptyStream
|
||||
idEmptyFile
|
||||
idAnti //nolint:deadcode,varcheck
|
||||
idName
|
||||
idCTime
|
||||
idATime
|
||||
idMTime
|
||||
idWinAttributes
|
||||
idComment //nolint:deadcode,varcheck
|
||||
idEncodedHeader
|
||||
idStartPos
|
||||
idDummy
|
||||
)
|
||||
|
||||
var (
|
||||
errIncompleteRead = errors.New("sevenzip: incomplete read")
|
||||
errUnexpectedID = errors.New("sevenzip: unexpected id")
|
||||
errMissingUnpackInfo = errors.New("sevenzip: missing unpack info")
|
||||
errWrongNumberOfFilenames = errors.New("sevenzip: wrong number of filenames")
|
||||
)
|
||||
|
||||
func readUint64(r io.ByteReader) (uint64, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("readUint64: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
l := bits.LeadingZeros8(^b)
|
||||
|
||||
var v uint64
|
||||
if l < 7 {
|
||||
v |= uint64(b&((1<<(8-l))-1)) << (8 * l)
|
||||
}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("readUint64: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
v |= uint64(b) << (8 * i)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func readBool(r io.ByteReader, count uint64) ([]bool, error) {
|
||||
defined := make([]bool, count)
|
||||
|
||||
var b, mask byte
|
||||
for i := range defined {
|
||||
if mask == 0 {
|
||||
var err error
|
||||
|
||||
b, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readBool: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
mask = 0x80
|
||||
}
|
||||
|
||||
defined[i] = (b & mask) != 0
|
||||
mask >>= 1
|
||||
}
|
||||
|
||||
return defined, nil
|
||||
}
|
||||
|
||||
func readOptionalBool(r io.ByteReader, count uint64) ([]bool, error) {
|
||||
all, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readOptionalBool: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if all == 0 {
|
||||
return readBool(r, count)
|
||||
}
|
||||
|
||||
defined := make([]bool, count)
|
||||
for i := range defined {
|
||||
defined[i] = true
|
||||
}
|
||||
|
||||
return defined, nil
|
||||
}
|
||||
|
||||
func readSizes(r io.ByteReader, count uint64) ([]uint64, error) {
|
||||
sizes := make([]uint64, count)
|
||||
|
||||
for i := uint64(0); i < count; i++ {
|
||||
size, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sizes[i] = size
|
||||
}
|
||||
|
||||
return sizes, nil
|
||||
}
|
||||
|
||||
func readCRC(r util.Reader, count uint64) ([]uint32, error) {
|
||||
defined, err := readOptionalBool(r, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crcs := make([]uint32, count)
|
||||
|
||||
for i := range defined {
|
||||
if defined[i] {
|
||||
if err := binary.Read(r, binary.LittleEndian, &crcs[i]); err != nil {
|
||||
return nil, fmt.Errorf("readCRC: Read error: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crcs, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func readPackInfo(r util.Reader) (*packInfo, error) {
|
||||
p := new(packInfo)
|
||||
|
||||
var err error
|
||||
|
||||
p.position, err = readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.streams, err = readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readPackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if id == idSize {
|
||||
if p.size, err = readSizes(r, p.streams); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readPackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id == idCRC {
|
||||
if p.digest, err = readCRC(r, p.streams); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readPackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id != idEnd {
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func readCoder(r util.Reader) (*coder, error) {
|
||||
c := new(coder)
|
||||
|
||||
v, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readCoder: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
c.id = make([]byte, v&0xf)
|
||||
if n, err := r.Read(c.id); err != nil || n != int(v&0xf) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readCoder: Read error: %w", err)
|
||||
}
|
||||
|
||||
return nil, errIncompleteRead
|
||||
}
|
||||
|
||||
if v&0x10 != 0 {
|
||||
c.in, err = readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.out, err = readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
c.in, c.out = 1, 1
|
||||
}
|
||||
|
||||
if v&0x20 != 0 {
|
||||
size, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.properties = make([]byte, size)
|
||||
if n, err := r.Read(c.properties); err != nil || uint64(n) != size { //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readCoder: Read error: %w", err)
|
||||
}
|
||||
|
||||
return nil, errIncompleteRead
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func readFolder(r util.Reader) (*folder, error) {
|
||||
f := new(folder)
|
||||
|
||||
coders, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.coder = make([]*coder, coders)
|
||||
|
||||
for i := uint64(0); i < coders; i++ {
|
||||
if f.coder[i], err = readCoder(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.in += f.coder[i].in
|
||||
f.out += f.coder[i].out
|
||||
}
|
||||
|
||||
bindPairs := f.out - 1
|
||||
|
||||
f.bindPair = make([]*bindPair, bindPairs)
|
||||
|
||||
for i := uint64(0); i < bindPairs; i++ {
|
||||
in, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.bindPair[i] = &bindPair{
|
||||
in: in,
|
||||
out: out,
|
||||
}
|
||||
}
|
||||
|
||||
f.packedStreams = f.in - bindPairs
|
||||
|
||||
if f.packedStreams == 1 {
|
||||
f.packed = []uint64{}
|
||||
for i := uint64(0); i < f.in; i++ {
|
||||
if f.findInBindPair(i) == nil {
|
||||
f.packed = append(f.packed, i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.packed = make([]uint64, f.packedStreams)
|
||||
for i := uint64(0); i < f.packedStreams; i++ {
|
||||
if f.packed[i], err = readUint64(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func readUnpackInfo(r util.Reader) (*unpackInfo, error) {
|
||||
u := new(unpackInfo)
|
||||
|
||||
if id, err := r.ReadByte(); err != nil || id != idFolder {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readUnpackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
folders, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
external, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readUnpackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if external > 0 {
|
||||
/*
|
||||
_, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
// TODO Apparently we seek to this read offset and read the
|
||||
// folder information from there. Not clear if the offset is
|
||||
// absolute for the whole file, or relative to some known
|
||||
// position in the file. Cowardly waiting for an example
|
||||
return nil, errors.New("sevenzip: TODO readUnpackInfo external") //nolint:goerr113
|
||||
}
|
||||
|
||||
u.folder = make([]*folder, folders)
|
||||
|
||||
for i := uint64(0); i < folders; i++ {
|
||||
if u.folder[i], err = readFolder(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if id, err := r.ReadByte(); err != nil || id != idCodersUnpackSize {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readUnpackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
for _, f := range u.folder {
|
||||
total := uint64(0)
|
||||
for _, c := range f.coder {
|
||||
total += c.out
|
||||
}
|
||||
|
||||
f.size = make([]uint64, total)
|
||||
for i := range f.size {
|
||||
if f.size[i], err = readUint64(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readUnpackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if id == idCRC {
|
||||
if u.digest, err = readCRC(r, folders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readUnpackInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id != idEnd {
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func readSubStreamsInfo(r util.Reader, folder []*folder) (*subStreamsInfo, error) {
|
||||
s := new(subStreamsInfo)
|
||||
|
||||
id, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readSubStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
s.streams = make([]uint64, len(folder))
|
||||
if id == idNumUnpackStream {
|
||||
for i := range s.streams {
|
||||
if s.streams[i], err = readUint64(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readSubStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
} else {
|
||||
for i := range s.streams {
|
||||
s.streams[i] = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Count the files in each stream
|
||||
files := uint64(0)
|
||||
for _, v := range s.streams {
|
||||
files += v
|
||||
}
|
||||
|
||||
if id == idSize {
|
||||
s.size = make([]uint64, files)
|
||||
k := 0
|
||||
|
||||
for i := range s.streams {
|
||||
total := uint64(0)
|
||||
|
||||
for j := uint64(1); j < s.streams[i]; j++ {
|
||||
if s.size[k], err = readUint64(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total += s.size[k]
|
||||
k++
|
||||
}
|
||||
|
||||
s.size[k] = folder[i].unpackSize() - total
|
||||
k++
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readSubStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id == idCRC {
|
||||
if s.digest, err = readCRC(r, files); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readSubStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id != idEnd {
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func readStreamsInfo(r util.Reader) (*streamsInfo, error) {
|
||||
s := new(streamsInfo)
|
||||
|
||||
id, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if id == idPackInfo {
|
||||
if s.packInfo, err = readPackInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id == idUnpackInfo {
|
||||
if s.unpackInfo, err = readUnpackInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id == idSubStreamsInfo {
|
||||
if s.unpackInfo == nil {
|
||||
return nil, errMissingUnpackInfo
|
||||
}
|
||||
|
||||
if s.subStreamsInfo, err = readSubStreamsInfo(r, s.unpackInfo.folder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readStreamsInfo: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id != idEnd {
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func readTimes(r util.Reader, count uint64) ([]time.Time, error) {
|
||||
defined, err := readOptionalBool(r, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
external, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readTimes: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if external > 0 {
|
||||
/*
|
||||
_, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
// TODO Apparently we seek to this read offset and read the
|
||||
// folder information from there. Not clear if the offset is
|
||||
// absolute for the whole file, or relative to some known
|
||||
// position in the file. Cowardly waiting for an example
|
||||
return nil, errors.New("sevenzip: TODO readTimes external") //nolint:goerr113
|
||||
}
|
||||
|
||||
times := make([]time.Time, count)
|
||||
|
||||
for i := range defined {
|
||||
if defined[i] {
|
||||
var ft windows.Filetime
|
||||
if err := binary.Read(r, binary.LittleEndian, &ft); err != nil {
|
||||
return nil, fmt.Errorf("readTimes: Read error: %w", err)
|
||||
}
|
||||
|
||||
times[i] = time.Unix(0, ft.Nanoseconds()).UTC()
|
||||
}
|
||||
}
|
||||
|
||||
return times, nil
|
||||
}
|
||||
|
||||
func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
if i := bytes.IndexRune(data, rune(0)); i >= 0 {
|
||||
return i + 1, data[0:i], nil
|
||||
}
|
||||
|
||||
if atEOF {
|
||||
return len(data), data, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readNames(r util.Reader, count, length uint64) ([]string, error) {
|
||||
external, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readNames: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if external > 0 {
|
||||
/*
|
||||
_, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
// TODO Apparently we seek to this read offset and read the
|
||||
// folder information from there. Not clear if the offset is
|
||||
// absolute for the whole file, or relative to some known
|
||||
// position in the file. Cowardly waiting for an example
|
||||
return nil, errors.New("sevenzip: TODO readNames external") //nolint:goerr113
|
||||
}
|
||||
|
||||
utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||
scanner := bufio.NewScanner(transform.NewReader(io.LimitReader(r, int64(length-1)), utf16le.NewDecoder())) //nolint:gosec,lll
|
||||
scanner.Split(splitNull)
|
||||
|
||||
names, i := make([]string, 0, count), uint64(0)
|
||||
for scanner.Scan() {
|
||||
names = append(names, scanner.Text())
|
||||
i++
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("readNames: Scan error: %w", err)
|
||||
}
|
||||
|
||||
if i != count {
|
||||
return nil, errWrongNumberOfFilenames
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func readAttributes(r util.Reader, count uint64) ([]uint32, error) {
|
||||
defined, err := readOptionalBool(r, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
external, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readAttributes: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if external > 0 {
|
||||
/*
|
||||
_, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
// TODO Apparently we seek to this read offset and read the
|
||||
// folder information from there. Not clear if the offset is
|
||||
// absolute for the whole file, or relative to some known
|
||||
// position in the file. Cowardly waiting for an example
|
||||
return nil, errors.New("sevenzip: TODO readAttributes external") //nolint:goerr113
|
||||
}
|
||||
|
||||
attributes := make([]uint32, count)
|
||||
|
||||
for i := range defined {
|
||||
if defined[i] {
|
||||
if err := binary.Read(r, binary.LittleEndian, &attributes[i]); err != nil {
|
||||
return nil, fmt.Errorf("readAttributes: Read error: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen,gocognit,gocyclo
|
||||
func readFilesInfo(r util.Reader) (*filesInfo, error) {
|
||||
f := new(filesInfo)
|
||||
|
||||
files, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.file = make([]FileHeader, files)
|
||||
|
||||
var emptyStreams uint64
|
||||
|
||||
for {
|
||||
property, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readFilesInfo: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if property == idEnd {
|
||||
break
|
||||
}
|
||||
|
||||
length, err := readUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch property {
|
||||
case idEmptyStream:
|
||||
empty, err := readBool(r, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range f.file {
|
||||
f.file[i].isEmptyStream = empty[i]
|
||||
|
||||
if empty[i] {
|
||||
emptyStreams++
|
||||
}
|
||||
}
|
||||
case idEmptyFile:
|
||||
empty, err := readBool(r, emptyStreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j := 0
|
||||
|
||||
for i := range f.file {
|
||||
if f.file[i].isEmptyStream {
|
||||
f.file[i].isEmptyFile = empty[j]
|
||||
j++
|
||||
}
|
||||
}
|
||||
case idCTime:
|
||||
times, err := readTimes(r, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, t := range times {
|
||||
f.file[i].Created = t
|
||||
}
|
||||
case idATime:
|
||||
times, err := readTimes(r, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, t := range times {
|
||||
f.file[i].Accessed = t
|
||||
}
|
||||
case idMTime:
|
||||
times, err := readTimes(r, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, t := range times {
|
||||
f.file[i].Modified = t
|
||||
}
|
||||
case idName:
|
||||
names, err := readNames(r, files, length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
f.file[i].Name = n
|
||||
}
|
||||
case idWinAttributes:
|
||||
attributes, err := readAttributes(r, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, a := range attributes {
|
||||
f.file[i].Attributes = a
|
||||
}
|
||||
case idStartPos:
|
||||
return nil, errors.New("sevenzip: TODO idStartPos") //nolint:goerr113
|
||||
case idDummy:
|
||||
if _, err := io.CopyN(io.Discard, r, int64(length)); err != nil { //nolint:gosec
|
||||
return nil, fmt.Errorf("readFilesInfo: CopyN error: %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func readHeader(r util.Reader) (*header, error) {
|
||||
h := new(header)
|
||||
|
||||
id, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readHeader: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
if id == idArchiveProperties {
|
||||
/*
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readHeader: ReadByte error: %w", err)
|
||||
}
|
||||
*/
|
||||
return nil, errors.New("sevenzip: TODO idArchiveProperties") //nolint:goerr113
|
||||
}
|
||||
|
||||
if id == idAdditionalStreamsInfo {
|
||||
/*
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readHeader: ReadByte error: %w", err)
|
||||
}
|
||||
*/
|
||||
return nil, errors.New("sevenzip: TODO idAdditionalStreamsInfo") //nolint:goerr113
|
||||
}
|
||||
|
||||
if id == idMainStreamsInfo {
|
||||
if h.streamsInfo, err = readStreamsInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readHeader: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id == idFilesInfo {
|
||||
if h.filesInfo, err = readFilesInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readHeader: ReadByte error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if id != idEnd {
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
if h.streamsInfo == nil || h.filesInfo == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
j := 0
|
||||
|
||||
for i := range h.filesInfo.file {
|
||||
if h.filesInfo.file[i].isEmptyStream {
|
||||
continue
|
||||
}
|
||||
|
||||
_, h.filesInfo.file[i].UncompressedSize, h.filesInfo.file[i].CRC32 = h.streamsInfo.FileFolderAndSize(j)
|
||||
j++
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func readEncodedHeader(r util.Reader) (*header, error) {
|
||||
if id, err := r.ReadByte(); err != nil || id != idHeader {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readEncodedHeader: ReadByte error: %w", err)
|
||||
}
|
||||
|
||||
return nil, errUnexpectedID
|
||||
}
|
||||
|
||||
header, err := readHeader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
Reference in New Issue
Block a user