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