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,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
|
||||
}
|
||||
Reference in New Issue
Block a user