- update
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
b56472cb82
commit
d31a69a439
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Arran Walker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,68 @@
|
||||
# go7z
|
||||
|
||||
A native Go 7z archive reader.
|
||||
|
||||
Features:
|
||||
- Development in early stages.
|
||||
- Very little tests.
|
||||
- Medium probability of crashes.
|
||||
- Medium probability of using all memory.
|
||||
- Decompresses:
|
||||
- [LZMA](https://github.com/ulikunitz/xz)
|
||||
- [LZMA2](https://github.com/ulikunitz/xz)
|
||||
- Delta
|
||||
- BCJ2
|
||||
- bzip2
|
||||
- deflate
|
||||
|
||||
## Usage
|
||||
Extracting an archive:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/saracen/go7z"
|
||||
)
|
||||
|
||||
func main() {
|
||||
sz, err := go7z.OpenReader("hello.7z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer sz.Close()
|
||||
|
||||
for {
|
||||
hdr, err := sz.Next()
|
||||
if err == io.EOF {
|
||||
break // End of archive
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// If empty stream (no contents) and isn't specifically an empty file...
|
||||
// then it's a directory.
|
||||
if hdr.IsEmptyStream && !hdr.IsEmptyFile {
|
||||
if err := os.MkdirAll(hdr.Name, os.ModePerm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Create file
|
||||
f, err := os.Create(hdr.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(f, sz); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,132 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
var km keyManager
|
||||
|
||||
func init() {
|
||||
km.cache = make(map[string][]byte)
|
||||
km.hasher = sha256.New()
|
||||
}
|
||||
|
||||
// AESDecrypter is an AES-256 decryptor.
|
||||
type AESDecrypter struct {
|
||||
r io.Reader
|
||||
rbuf bytes.Buffer
|
||||
cbc cipher.BlockMode
|
||||
buf [aes.BlockSize]byte
|
||||
}
|
||||
|
||||
type keyManager struct {
|
||||
hasher hash.Hash
|
||||
cache map[string][]byte
|
||||
}
|
||||
|
||||
func (km *keyManager) Key(power int, salt []byte, password string) []byte {
|
||||
var cacheKey strings.Builder
|
||||
cacheKey.WriteString(password)
|
||||
cacheKey.Write(salt)
|
||||
cacheKey.WriteByte(byte(power))
|
||||
|
||||
key, ok := km.cache[cacheKey.String()]
|
||||
if ok {
|
||||
return key
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
for _, p := range utf16.Encode([]rune(password)) {
|
||||
binary.Write(b, binary.LittleEndian, p)
|
||||
}
|
||||
|
||||
if power == 0x3f {
|
||||
key = km.stretch(salt, b.Bytes())
|
||||
} else {
|
||||
key = km.sha256Stretch(power, salt, b.Bytes())
|
||||
}
|
||||
|
||||
km.cache[cacheKey.String()] = key
|
||||
return key
|
||||
}
|
||||
|
||||
func (km *keyManager) stretch(salt, password []byte) []byte {
|
||||
var key [aes.BlockSize]byte
|
||||
|
||||
var pos int
|
||||
for pos = 0; pos < len(salt); pos++ {
|
||||
key[pos] = salt[pos]
|
||||
}
|
||||
for i := 0; i < len(password) && pos < len(key); i++ {
|
||||
key[pos] = password[i]
|
||||
pos++
|
||||
}
|
||||
for ; pos < len(key); pos++ {
|
||||
key[pos] = 0
|
||||
}
|
||||
return key[:]
|
||||
}
|
||||
|
||||
func (km *keyManager) sha256Stretch(power int, salt, password []byte) []byte {
|
||||
var temp [8]byte
|
||||
for round := 0; round < 1<<power; round++ {
|
||||
km.hasher.Write(salt)
|
||||
km.hasher.Write(password)
|
||||
km.hasher.Write(temp[:])
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
temp[i]++
|
||||
if temp[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer km.hasher.Reset()
|
||||
return km.hasher.Sum(nil)
|
||||
}
|
||||
|
||||
// NewAESDecrypter returns a new AES-256 decryptor.
|
||||
func NewAESDecrypter(r io.Reader, power int, salt, iv []byte, password string) (*AESDecrypter, error) {
|
||||
key := km.Key(power, salt, password)
|
||||
|
||||
cb, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var aesiv [aes.BlockSize]byte
|
||||
copy(aesiv[:], iv)
|
||||
|
||||
return &AESDecrypter{
|
||||
r: r,
|
||||
cbc: cipher.NewCBCDecrypter(cb, aesiv[:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *AESDecrypter) Read(p []byte) (int, error) {
|
||||
for d.rbuf.Len() < len(p) {
|
||||
_, err := d.r.Read(d.buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
d.cbc.CryptBlocks(d.buf[:], d.buf[:])
|
||||
|
||||
_, err = d.rbuf.Write(d.buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err := d.rbuf.Read(p)
|
||||
return n, err
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type rangeDecoder struct {
|
||||
r io.Reader
|
||||
nrange uint
|
||||
code uint
|
||||
}
|
||||
|
||||
func newRangeDecoder(r io.Reader) (*rangeDecoder, error) {
|
||||
rd := &rangeDecoder{
|
||||
r: r,
|
||||
nrange: 0xffffffff,
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
b, err := rd.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rd.code = (rd.code << 8) | uint(b)
|
||||
}
|
||||
return rd, nil
|
||||
}
|
||||
|
||||
func (rd *rangeDecoder) ReadByte() (byte, error) {
|
||||
var b [1]byte
|
||||
_, err := rd.r.Read(b[:])
|
||||
return b[0], err
|
||||
}
|
||||
|
||||
const (
|
||||
numMoveBits = 5
|
||||
numbitModelTotalBits = 11
|
||||
bitModelTotal = uint(1) << numbitModelTotalBits
|
||||
|
||||
numTopBits = 24
|
||||
topValue = uint(1 << numTopBits)
|
||||
)
|
||||
|
||||
type statusDecoder struct {
|
||||
prob uint
|
||||
}
|
||||
|
||||
func newStatusDecoder() *statusDecoder {
|
||||
return &statusDecoder{prob: bitModelTotal / 2}
|
||||
}
|
||||
|
||||
func (sd *statusDecoder) Decode(decoder *rangeDecoder) (uint, error) {
|
||||
var err error
|
||||
var b byte
|
||||
|
||||
newBound := (decoder.nrange >> numbitModelTotalBits) * sd.prob
|
||||
if decoder.code < newBound {
|
||||
decoder.nrange = newBound
|
||||
sd.prob += (bitModelTotal - sd.prob) >> numMoveBits
|
||||
if decoder.nrange < topValue {
|
||||
if b, err = decoder.ReadByte(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
decoder.code = (decoder.code << 8) | uint(b)
|
||||
decoder.nrange <<= 8
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
decoder.nrange -= newBound
|
||||
decoder.code -= newBound
|
||||
sd.prob -= sd.prob >> numMoveBits
|
||||
if decoder.nrange < topValue {
|
||||
if b, err = decoder.ReadByte(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
decoder.code = (decoder.code << 8) | uint(b)
|
||||
decoder.nrange <<= 8
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// BCJ2Decoder is a BCJ2 decoder.
|
||||
type BCJ2Decoder struct {
|
||||
main *bufio.Reader
|
||||
call io.Reader
|
||||
jump io.Reader
|
||||
|
||||
rangeDecoder *rangeDecoder
|
||||
statusDecoder []*statusDecoder
|
||||
|
||||
written int64
|
||||
finished bool
|
||||
|
||||
prevByte byte
|
||||
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// NewBCJ2Decoder returns a new BCJ2 decoder.
|
||||
func NewBCJ2Decoder(main, call, jump, rangedecoder io.Reader, limit int64) (*BCJ2Decoder, error) {
|
||||
rd, err := newRangeDecoder(rangedecoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decoder := &BCJ2Decoder{
|
||||
main: bufio.NewReader(main),
|
||||
call: call,
|
||||
jump: jump,
|
||||
rangeDecoder: rd,
|
||||
statusDecoder: make([]*statusDecoder, 256+2),
|
||||
buf: new(bytes.Buffer),
|
||||
}
|
||||
decoder.buf.Grow(1 << 16)
|
||||
|
||||
for i := range decoder.statusDecoder {
|
||||
decoder.statusDecoder[i] = newStatusDecoder()
|
||||
}
|
||||
|
||||
return decoder, nil
|
||||
}
|
||||
|
||||
func (d *BCJ2Decoder) isJcc(b0, b1 byte) bool {
|
||||
return b0 == 0x0f && (b1&0xf0) == 0x80
|
||||
}
|
||||
|
||||
func (d *BCJ2Decoder) isJ(b0, b1 byte) bool {
|
||||
return (b1&0xfe) == 0xe8 || d.isJcc(b0, b1)
|
||||
}
|
||||
|
||||
func (d *BCJ2Decoder) index(b0, b1 byte) int {
|
||||
switch b1 {
|
||||
case 0xe8:
|
||||
return int(b0)
|
||||
case 0xe9:
|
||||
return 256
|
||||
}
|
||||
return 257
|
||||
}
|
||||
|
||||
func (d *BCJ2Decoder) Read(p []byte) (int, error) {
|
||||
err := d.read()
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return d.buf.Read(p)
|
||||
}
|
||||
|
||||
func (d *BCJ2Decoder) read() error {
|
||||
b := byte(0)
|
||||
|
||||
var err error
|
||||
for i := 0; i < d.buf.Cap(); i++ {
|
||||
b, err = d.main.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.written++
|
||||
if err = d.buf.WriteByte(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.isJ(d.prevByte, b) {
|
||||
break
|
||||
}
|
||||
d.prevByte = b
|
||||
}
|
||||
|
||||
if d.buf.Len() == d.buf.Cap() {
|
||||
return nil
|
||||
}
|
||||
|
||||
bit, err := d.statusDecoder[d.index(d.prevByte, b)].Decode(d.rangeDecoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bit == 1 {
|
||||
var r io.Reader
|
||||
if b == 0xe8 {
|
||||
r = d.call
|
||||
} else {
|
||||
r = d.jump
|
||||
}
|
||||
|
||||
var dest uint32
|
||||
if err = binary.Read(r, binary.BigEndian, &dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest -= uint32(d.written + 4)
|
||||
if err = binary.Write(d.buf, binary.LittleEndian, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.prevByte = byte(dest >> 24)
|
||||
d.written += 4
|
||||
} else {
|
||||
d.prevByte = b
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package filters
|
||||
|
||||
import "io"
|
||||
|
||||
const deltaStateSize = 256
|
||||
|
||||
// DeltaDecoder is a Delta decoder.
|
||||
type DeltaDecoder struct {
|
||||
state [deltaStateSize]byte
|
||||
r io.Reader
|
||||
delta uint
|
||||
}
|
||||
|
||||
// NewDeltaDecoder returns a new Delta decoder.
|
||||
func NewDeltaDecoder(r io.Reader, delta uint, limit int64) (*DeltaDecoder, error) {
|
||||
return &DeltaDecoder{r: r, delta: delta}, nil
|
||||
}
|
||||
|
||||
func (d *DeltaDecoder) Read(p []byte) (int, error) {
|
||||
n, err := d.r.Read(p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
var buf [deltaStateSize]byte
|
||||
copy(buf[:], d.state[:d.delta])
|
||||
|
||||
var i, j uint
|
||||
for i = 0; i < uint(n); {
|
||||
for j = 0; j < d.delta && i < uint(n); i++ {
|
||||
p[i] = buf[j] + p[i]
|
||||
buf[j] = p[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
if j == d.delta {
|
||||
j = 0
|
||||
}
|
||||
|
||||
copy(d.state[:], buf[j:d.delta])
|
||||
copy(d.state[d.delta-j:], buf[:j])
|
||||
|
||||
return n, err
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// +build gofuzz
|
||||
|
||||
package go7z
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
sz := new(Reader)
|
||||
if err := sz.init(bytes.NewReader(data), int64(len(data)), true); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
for {
|
||||
_, err := sz.Next()
|
||||
if err == io.EOF {
|
||||
return 0
|
||||
}
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if _, err = io.Copy(ioutil.Discard, sz); err != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadDigests reads an array of uint32 CRCs.
|
||||
func ReadDigests(r io.Reader, length int) ([]uint32, error) {
|
||||
defined, _, err := ReadOptionalBoolVector(r, length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crcs := make([]uint32, length)
|
||||
for i := range defined {
|
||||
if defined[i] {
|
||||
if err := binary.Read(r, binary.LittleEndian, &crcs[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crcs, nil
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// ErrInvalidFileCount is returned when the file count read from the stream
|
||||
// exceeds the caller supplied maxFileCount.
|
||||
var ErrInvalidFileCount = errors.New("invalid file count")
|
||||
|
||||
// FileInfo is a structure containing the information of an archived file.
|
||||
type FileInfo struct {
|
||||
Name string
|
||||
Attrib uint32
|
||||
|
||||
IsEmptyStream bool
|
||||
IsEmptyFile bool
|
||||
|
||||
// Flag indicating a file should be removed upon extraction.
|
||||
IsAntiFile bool
|
||||
|
||||
CreatedAt time.Time
|
||||
AccessedAt time.Time
|
||||
ModifiedAt time.Time
|
||||
}
|
||||
|
||||
// ReadFilesInfo reads the files info structure.
|
||||
func ReadFilesInfo(r io.Reader, maxFileCount int) ([]*FileInfo, error) {
|
||||
numFiles, err := ReadNumberInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if numFiles > maxFileCount {
|
||||
return nil, ErrInvalidFileCount
|
||||
}
|
||||
|
||||
fileInfo := make([]*FileInfo, numFiles)
|
||||
for i := range fileInfo {
|
||||
fileInfo[i] = &FileInfo{}
|
||||
}
|
||||
|
||||
var numEmptyStreams int
|
||||
for {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if id == k7zEnd {
|
||||
return fileInfo, nil
|
||||
}
|
||||
|
||||
size, err := ReadNumber(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch id {
|
||||
case k7zEmptyStream:
|
||||
var emptyStreams []bool
|
||||
emptyStreams, numEmptyStreams, err = ReadBoolVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, fi := range fileInfo {
|
||||
fi.IsEmptyStream = emptyStreams[i]
|
||||
}
|
||||
|
||||
case k7zEmptyFile, k7zAnti:
|
||||
files, _, err := ReadBoolVector(r, numEmptyStreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx := 0
|
||||
for _, fi := range fileInfo {
|
||||
if fi.IsEmptyStream {
|
||||
switch id {
|
||||
case k7zEmptyFile:
|
||||
fi.IsEmptyFile = files[idx]
|
||||
case k7zAnti:
|
||||
fi.IsAntiFile = files[idx]
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
case k7zStartPos:
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
|
||||
case k7zCTime, k7zATime, k7zMTime:
|
||||
times, err := ReadDateTimeVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, fi := range fileInfo {
|
||||
switch id {
|
||||
case k7zCTime:
|
||||
fi.CreatedAt = times[i]
|
||||
case k7zATime:
|
||||
fi.AccessedAt = times[i]
|
||||
case k7zMTime:
|
||||
fi.ModifiedAt = times[i]
|
||||
}
|
||||
}
|
||||
|
||||
case k7zName:
|
||||
external, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch external {
|
||||
case 0:
|
||||
for _, fi := range fileInfo {
|
||||
var rune uint16
|
||||
var name []uint16
|
||||
for {
|
||||
if err = binary.Read(r, binary.LittleEndian, &rune); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rune == 0 {
|
||||
break
|
||||
}
|
||||
name = append(name, rune)
|
||||
}
|
||||
fi.Name = string(utf16.Decode(name))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrAdditionalStreamsNotImplemented
|
||||
}
|
||||
|
||||
case k7zWinAttributes:
|
||||
attributes, err := ReadAttributeVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, fi := range fileInfo {
|
||||
fi.Attrib = attributes[i]
|
||||
}
|
||||
|
||||
case k7zDummy:
|
||||
for i := uint64(0); i < size; i++ {
|
||||
if _, err = ReadByte(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxInOutStreams is the maximum allowed stream inputs/outputs into/out
|
||||
// of a coder.
|
||||
MaxInOutStreams = 4
|
||||
|
||||
// MaxPropertyDataSize is the size in bytes supported for coder property data.
|
||||
MaxPropertyDataSize = 128
|
||||
|
||||
// MaxCodersInFolder is the maximum number of coders allowed to be
|
||||
// specified in a folder.
|
||||
MaxCodersInFolder = 4
|
||||
|
||||
// MaxPackedStreamsInFolder is the maximum number of packed streams allowed
|
||||
// to be in a folder.
|
||||
MaxPackedStreamsInFolder = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidStreamCount is the error returned when the input/output stream
|
||||
// count for a coder is <= 0 || > MaxInOutStreams.
|
||||
ErrInvalidStreamCount = errors.New("invalid in/out stream count")
|
||||
|
||||
// ErrInvalidPropertyDataSize is the error returned when the property data
|
||||
// size is <= 0 || > MaxInOutStreams.
|
||||
ErrInvalidPropertyDataSize = errors.New("invalid property data size")
|
||||
|
||||
// ErrInvalidCoderInFolderCount is the error returned when the number of
|
||||
// coders in a folder is <= 0 || > MaxCodersInFolder.
|
||||
ErrInvalidCoderInFolderCount = errors.New("invalid coder in folder count")
|
||||
|
||||
// ErrInvalidPackedStreamsCount is the error returned when the number of
|
||||
// packed streams exceeds MaxPackedStreamsInFolder
|
||||
ErrInvalidPackedStreamsCount = errors.New("invalid packed streams count")
|
||||
)
|
||||
|
||||
// Folder is a structure containing information on how a solid block was
|
||||
// constructed.
|
||||
type Folder struct {
|
||||
CoderInfo []*CoderInfo
|
||||
BindPairsInfo []*BindPairsInfo
|
||||
PackedIndices []int
|
||||
UnpackSizes []uint64
|
||||
UnpackCRC uint32
|
||||
}
|
||||
|
||||
// NumInStreamsTotal is the sum of inputs required by all codecs.
|
||||
func (f *Folder) NumInStreamsTotal() int {
|
||||
var count int
|
||||
for i := range f.CoderInfo {
|
||||
count += f.CoderInfo[i].NumInStreams
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// NumOutStreamsTotal is the sum of outputs required by all codecs.
|
||||
func (f *Folder) NumOutStreamsTotal() int {
|
||||
var count int
|
||||
for i := range f.CoderInfo {
|
||||
count += f.CoderInfo[i].NumOutStreams
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// FindBindPairForInStream returns the index of a bindpair by an in index.
|
||||
func (f *Folder) FindBindPairForInStream(inStreamIndex int) int {
|
||||
for i := range f.BindPairsInfo {
|
||||
if f.BindPairsInfo[i].InIndex == inStreamIndex {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// FindBindPairForOutStream returns the index of a bindpair by an out index.
|
||||
func (f *Folder) FindBindPairForOutStream(outStreamIndex int) int {
|
||||
for i := range f.BindPairsInfo {
|
||||
if f.BindPairsInfo[i].OutIndex == outStreamIndex {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// UnpackSize returns the final unpacked size of the folder.
|
||||
func (f *Folder) UnpackSize() uint64 {
|
||||
for i := range f.UnpackSizes {
|
||||
if f.FindBindPairForOutStream(i) < 0 {
|
||||
return f.UnpackSizes[i]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReadFolder reads a folder structure.
|
||||
func ReadFolder(r io.Reader) (*Folder, error) {
|
||||
var err error
|
||||
|
||||
folder := &Folder{}
|
||||
|
||||
numCoders, err := ReadNumberInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if numCoders == 0 || numCoders > MaxCodersInFolder {
|
||||
return nil, ErrInvalidCoderInFolderCount
|
||||
}
|
||||
|
||||
folder.CoderInfo = make([]*CoderInfo, numCoders)
|
||||
for i := range folder.CoderInfo {
|
||||
if folder.CoderInfo[i], err = ReadCoderInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
folder.BindPairsInfo = make([]*BindPairsInfo, numCoders-1)
|
||||
for i := range folder.BindPairsInfo {
|
||||
if folder.BindPairsInfo[i], err = ReadBindPairsInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
numInStreamsTotal := folder.NumInStreamsTotal()
|
||||
numPackedStreams := numInStreamsTotal - len(folder.BindPairsInfo)
|
||||
if numPackedStreams > 1 {
|
||||
if numPackedStreams > MaxPackedStreamsInFolder {
|
||||
return nil, ErrInvalidPackedStreamsCount
|
||||
}
|
||||
|
||||
folder.PackedIndices = make([]int, numPackedStreams)
|
||||
for i := range folder.PackedIndices {
|
||||
if folder.PackedIndices[i], err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if numPackedStreams == 1 {
|
||||
for i := 0; i < numInStreamsTotal; i++ {
|
||||
if folder.FindBindPairForInStream(i) < 0 {
|
||||
folder.PackedIndices = []int{i}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return folder, nil
|
||||
}
|
||||
|
||||
// CoderInfo is a structure holding information about a codec.
|
||||
type CoderInfo struct {
|
||||
CodecID uint32
|
||||
Properties []byte
|
||||
NumInStreams int
|
||||
NumOutStreams int
|
||||
}
|
||||
|
||||
// ReadCoderInfo reads a coder info structure.
|
||||
func ReadCoderInfo(r io.Reader) (*CoderInfo, error) {
|
||||
attributes, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coderInfo := &CoderInfo{}
|
||||
|
||||
codecIDSize := attributes & 0x0f
|
||||
isComplexCoder := attributes&0x10 > 0
|
||||
hasAttributes := attributes&0x20 > 0
|
||||
|
||||
if codecIDSize > 0 {
|
||||
b := make([]byte, codecIDSize)
|
||||
if _, err = r.Read(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := codecIDSize; i > 0; i-- {
|
||||
coderInfo.CodecID |= uint32(b[i-1]) << ((codecIDSize - i) * 8)
|
||||
}
|
||||
}
|
||||
|
||||
coderInfo.NumInStreams = 1
|
||||
coderInfo.NumOutStreams = 1
|
||||
if isComplexCoder {
|
||||
if coderInfo.NumInStreams, err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if coderInfo.NumInStreams == 0 || coderInfo.NumInStreams > MaxInOutStreams {
|
||||
return nil, ErrInvalidStreamCount
|
||||
}
|
||||
|
||||
if coderInfo.NumOutStreams, err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if coderInfo.NumOutStreams == 0 || coderInfo.NumOutStreams > MaxInOutStreams {
|
||||
return nil, ErrInvalidStreamCount
|
||||
}
|
||||
}
|
||||
|
||||
if hasAttributes {
|
||||
size, err := ReadNumberInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size <= 0 || size > MaxPropertyDataSize {
|
||||
return nil, ErrInvalidPropertyDataSize
|
||||
}
|
||||
|
||||
coderInfo.Properties = make([]byte, size)
|
||||
if _, err = r.Read(coderInfo.Properties); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return coderInfo, nil
|
||||
}
|
||||
|
||||
// BindPairsInfo is a structure that binds the in and out indexes of a codec.
|
||||
type BindPairsInfo struct {
|
||||
InIndex int
|
||||
OutIndex int
|
||||
}
|
||||
|
||||
// ReadBindPairsInfo reads a bindpairs info structure.
|
||||
func ReadBindPairsInfo(r io.Reader) (*BindPairsInfo, error) {
|
||||
bindPairsInfo := &BindPairsInfo{}
|
||||
|
||||
var err error
|
||||
if bindPairsInfo.InIndex, err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bindPairsInfo.OutIndex, err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bindPairsInfo, nil
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// SignatureHeader size is the size of the signature header.
|
||||
SignatureHeaderSize = 32
|
||||
|
||||
// MaxHeaderSize is the maximum header size.
|
||||
MaxHeaderSize = int64(1 << 62) // 4 exbibyte
|
||||
)
|
||||
|
||||
var (
|
||||
// MagicBytes is the magic bytes used in the 7z signature.
|
||||
MagicBytes = [6]byte{0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C}
|
||||
|
||||
// ErrInvalidSignatureHeader is returned when signature header is invalid.
|
||||
ErrInvalidSignatureHeader = errors.New("invalid signature header")
|
||||
)
|
||||
|
||||
// SignatureHeader is the structure found at the top of 7z files.
|
||||
type SignatureHeader struct {
|
||||
Signature [6]byte
|
||||
|
||||
ArchiveVersion struct {
|
||||
Major byte
|
||||
Minor byte
|
||||
}
|
||||
|
||||
StartHeaderCRC uint32
|
||||
|
||||
StartHeader struct {
|
||||
NextHeaderOffset int64
|
||||
NextHeaderSize int64
|
||||
NextHeaderCRC uint32
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSignatureHeader reads the signature header.
|
||||
func ReadSignatureHeader(r io.Reader) (*SignatureHeader, error) {
|
||||
var raw [SignatureHeaderSize]byte
|
||||
_, err := r.Read(raw[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var header SignatureHeader
|
||||
copy(header.Signature[:], raw[:6])
|
||||
if bytes.Compare(header.Signature[:], MagicBytes[:]) != 0 {
|
||||
return nil, ErrInvalidSignatureHeader
|
||||
}
|
||||
|
||||
header.ArchiveVersion.Major = raw[6]
|
||||
header.ArchiveVersion.Minor = raw[7]
|
||||
header.StartHeaderCRC = binary.LittleEndian.Uint32(raw[8:])
|
||||
header.StartHeader.NextHeaderOffset = int64(binary.LittleEndian.Uint64(raw[12:]))
|
||||
header.StartHeader.NextHeaderSize = int64(binary.LittleEndian.Uint64(raw[20:]))
|
||||
header.StartHeader.NextHeaderCRC = binary.LittleEndian.Uint32(raw[28:])
|
||||
|
||||
if header.StartHeader.NextHeaderSize < 0 || header.StartHeader.NextHeaderSize > MaxHeaderSize {
|
||||
return &header, ErrInvalidSignatureHeader
|
||||
}
|
||||
if crc32.ChecksumIEEE(raw[12:]) != header.StartHeaderCRC {
|
||||
err = ErrChecksumMismatch
|
||||
}
|
||||
return &header, err
|
||||
}
|
||||
|
||||
// Header is structure containing file and stream information.
|
||||
type Header struct {
|
||||
MainStreamsInfo *StreamsInfo
|
||||
FilesInfo []*FileInfo
|
||||
}
|
||||
|
||||
// ReadPackedStreamsForHeaders reads either a header or encoded header structure.
|
||||
func ReadPackedStreamsForHeaders(r *io.LimitedReader) (header *Header, encodedHeader *StreamsInfo, err error) {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch id {
|
||||
case k7zHeader:
|
||||
if header, err = ReadHeader(r); err != nil && err != io.EOF {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
case k7zEncodedHeader:
|
||||
if encodedHeader, err = ReadStreamsInfo(r); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
case k7zEnd:
|
||||
if header == nil && encodedHeader == nil {
|
||||
return nil, nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
return nil, nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
|
||||
return header, encodedHeader, nil
|
||||
}
|
||||
|
||||
// ReadHeader reads a header structure.
|
||||
func ReadHeader(r *io.LimitedReader) (*Header, error) {
|
||||
header := &Header{}
|
||||
|
||||
for {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch id {
|
||||
case k7zArchiveProperties:
|
||||
return nil, ErrArchivePropertiesNotImplemented
|
||||
|
||||
case k7zAdditionalStreamsInfo:
|
||||
return nil, ErrAdditionalStreamsNotImplemented
|
||||
|
||||
case k7zMainStreamsInfo:
|
||||
if header.MainStreamsInfo, err = ReadStreamsInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case k7zFilesInfo:
|
||||
// Limit the maximum amount of FileInfos that get allocated to size
|
||||
// of the remaining header / 3
|
||||
if header.FilesInfo, err = ReadFilesInfo(r, int(r.N)/3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case k7zEnd:
|
||||
if header.MainStreamsInfo == nil {
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
|
||||
return header, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package headers
|
||||
|
||||
import "io"
|
||||
|
||||
// PackInfo contains the pack stream sizes of the folders.
|
||||
type PackInfo struct {
|
||||
PackPos uint64
|
||||
PackSizes []uint64
|
||||
}
|
||||
|
||||
// ReadPackInfo reads a pack info structure.
|
||||
func ReadPackInfo(r io.Reader) (*PackInfo, error) {
|
||||
packInfo := &PackInfo{}
|
||||
|
||||
var err error
|
||||
if packInfo.PackPos, err = ReadNumber(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numPackStreams, err := ReadNumberInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch id {
|
||||
case k7zSize:
|
||||
packInfo.PackSizes = make([]uint64, numPackStreams+1)
|
||||
for i := 0; i < numPackStreams; i++ {
|
||||
packInfo.PackSizes[i], err = ReadNumber(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case k7zCRC:
|
||||
return nil, ErrPackInfoCRCsNotImplemented
|
||||
|
||||
case k7zEnd:
|
||||
return packInfo, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
k7zEnd = iota
|
||||
k7zHeader
|
||||
k7zArchiveProperties
|
||||
k7zAdditionalStreamsInfo
|
||||
k7zMainStreamsInfo
|
||||
k7zFilesInfo
|
||||
k7zPackInfo
|
||||
k7zUnpackInfo
|
||||
k7zSubStreamsInfo
|
||||
k7zSize
|
||||
k7zCRC
|
||||
k7zFolder
|
||||
k7zCodersUnpackSize
|
||||
k7zNumUnpackStream
|
||||
k7zEmptyStream
|
||||
k7zEmptyFile
|
||||
k7zAnti
|
||||
k7zName
|
||||
k7zCTime
|
||||
k7zATime
|
||||
k7zMTime
|
||||
k7zWinAttributes
|
||||
k7zComment
|
||||
k7zEncodedHeader
|
||||
k7zStartPos
|
||||
k7zDummy
|
||||
)
|
||||
|
||||
const MaxNumber = 0x7FFFFFFF
|
||||
|
||||
var (
|
||||
// ErrUnexpectedPropertyID is returned when we read a property id that was
|
||||
// either unexpected, or we don't support.
|
||||
ErrUnexpectedPropertyID = errors.New("unexpected property id")
|
||||
|
||||
// ErrAdditionalStreamsNotImplemented is returned for archives using
|
||||
// additional streams. These were apparently used in older versions of 7zip.
|
||||
ErrAdditionalStreamsNotImplemented = errors.New("additional streams are not implemented")
|
||||
|
||||
// ErrArchivePropertiesNotImplemented is returned if archive properties
|
||||
// structure is found. So far, this hasn't been used in any verison of 7zip.
|
||||
ErrArchivePropertiesNotImplemented = errors.New("archive properties are not implemented")
|
||||
|
||||
// ErrChecksumMismatch is returned when a CRC check fails.
|
||||
ErrChecksumMismatch = errors.New("checksum mismatch")
|
||||
|
||||
// ErrPackInfoCRCsNotImplemented is returned if a CRC property id is
|
||||
// encountered whilst reading packinfo.
|
||||
ErrPackInfoCRCsNotImplemented = errors.New("packinfo crcs are not implemented")
|
||||
|
||||
// ErrInvalidNumber is returned when a number read exceeds 0x7FFFFFFF
|
||||
ErrInvalidNumber = errors.New("invalid number")
|
||||
)
|
||||
|
||||
// ReadByte reads a single byte.
|
||||
func ReadByte(r io.Reader) (byte, error) {
|
||||
var val [1]byte
|
||||
_, err := r.Read(val[:])
|
||||
return val[0], err
|
||||
}
|
||||
|
||||
// ReadByteExpect reads a byte to be expected, errors if unexpected.
|
||||
func ReadByteExpect(r io.Reader, val byte) error {
|
||||
value, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != val {
|
||||
return ErrUnexpectedPropertyID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadNumber reads a 7z encoded uint64.
|
||||
func ReadNumber(r io.Reader) (uint64, error) {
|
||||
first, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var value uint64
|
||||
mask := byte(0x80)
|
||||
for i := uint64(0); i < 8; i++ {
|
||||
if first&mask == 0 {
|
||||
hp := uint64(first) & (uint64(mask) - 1)
|
||||
value += hp << (i * 8)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
val, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
value |= uint64(val) << (8 * i)
|
||||
mask >>= 1
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadNumberInt is the same as ReadNumber, but cast to int.
|
||||
func ReadNumberInt(r io.Reader) (int, error) {
|
||||
u64, err := ReadNumber(r)
|
||||
if u64 > MaxNumber {
|
||||
return 0, ErrInvalidNumber
|
||||
}
|
||||
|
||||
return int(u64), err
|
||||
}
|
||||
|
||||
// ReadUint32 reads a uint32.
|
||||
func ReadUint32(r io.Reader) (uint32, error) {
|
||||
var v uint32
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
}
|
||||
|
||||
// ReadUint64 reads a uint64.
|
||||
func ReadUint64(r io.Reader) (uint64, error) {
|
||||
var v uint64
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
}
|
||||
|
||||
// ReadBoolVector reads a vector of boolean values.
|
||||
func ReadBoolVector(r io.Reader, length int) ([]bool, int, error) {
|
||||
var b byte
|
||||
var mask byte
|
||||
var err error
|
||||
v := make([]bool, length)
|
||||
|
||||
count := 0
|
||||
for i := range v {
|
||||
if mask == 0 {
|
||||
b, err = ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
mask = 0x80
|
||||
}
|
||||
v[i] = (b & mask) != 0
|
||||
mask >>= 1
|
||||
if v[i] {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return v, count, nil
|
||||
}
|
||||
|
||||
// ReadOptionalBoolVector reads a vector of boolean values if they're available,
|
||||
// otherwise it returns an array of booleans all being true.
|
||||
func ReadOptionalBoolVector(r io.Reader, length int) ([]bool, int, error) {
|
||||
allDefined, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if allDefined == 0 {
|
||||
return ReadBoolVector(r, length)
|
||||
}
|
||||
|
||||
defined := make([]bool, length)
|
||||
for i := range defined {
|
||||
defined[i] = true
|
||||
}
|
||||
|
||||
return defined, length, nil
|
||||
}
|
||||
|
||||
// ReadNumberVector returns a vector of 7z encoded int64s.
|
||||
func ReadNumberVector(r io.Reader, numFiles int) ([]*int64, error) {
|
||||
defined, _, err := ReadOptionalBoolVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
external, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if external != 0 {
|
||||
return nil, ErrAdditionalStreamsNotImplemented
|
||||
}
|
||||
|
||||
numbers := make([]*int64, numFiles)
|
||||
for i := 0; i < numFiles; i++ {
|
||||
if defined[i] {
|
||||
num, err := ReadUint64(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val := int64(num)
|
||||
numbers[i] = &val
|
||||
} else {
|
||||
numbers[i] = nil
|
||||
}
|
||||
}
|
||||
|
||||
return numbers, err
|
||||
}
|
||||
|
||||
// ReadDateTimeVector reads a vector of datetime values.
|
||||
func ReadDateTimeVector(r io.Reader, numFiles int) ([]time.Time, error) {
|
||||
timestamps, err := ReadNumberVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
times := make([]time.Time, len(timestamps))
|
||||
for i := range times {
|
||||
if timestamps[i] != nil {
|
||||
nsec := *timestamps[i]
|
||||
nsec -= 116444736000000000
|
||||
nsec *= 100
|
||||
|
||||
times[i] = time.Unix(0, nsec)
|
||||
}
|
||||
}
|
||||
|
||||
return times, nil
|
||||
}
|
||||
|
||||
// ReadAttributeVector reads a vector of uint32s.
|
||||
func ReadAttributeVector(r io.Reader, numFiles int) ([]uint32, error) {
|
||||
defined, _, err := ReadOptionalBoolVector(r, numFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
external, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if external != 0 {
|
||||
return nil, ErrAdditionalStreamsNotImplemented
|
||||
}
|
||||
|
||||
attributes := make([]uint32, numFiles)
|
||||
for i := range attributes {
|
||||
if defined[i] {
|
||||
val, err := ReadUint32(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attributes[i] = val
|
||||
}
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// StreamsInfo is a top-level structure of the 7z format.
|
||||
type StreamsInfo struct {
|
||||
PackInfo *PackInfo
|
||||
UnpackInfo *UnpackInfo
|
||||
SubStreamsInfo *SubStreamsInfo
|
||||
}
|
||||
|
||||
// ReadStreamsInfo reads the streams info structure.
|
||||
func ReadStreamsInfo(r io.Reader) (*StreamsInfo, error) {
|
||||
streamsInfo := &StreamsInfo{}
|
||||
|
||||
for {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch id {
|
||||
case k7zPackInfo:
|
||||
if streamsInfo.PackInfo, err = ReadPackInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case k7zUnpackInfo:
|
||||
if streamsInfo.UnpackInfo, err = ReadUnpackInfo(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case k7zSubStreamsInfo:
|
||||
if streamsInfo.UnpackInfo == nil {
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
|
||||
if streamsInfo.SubStreamsInfo, err = ReadSubStreamsInfo(r, streamsInfo.UnpackInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case k7zEnd:
|
||||
if streamsInfo.PackInfo == nil || streamsInfo.UnpackInfo == nil {
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
|
||||
return streamsInfo, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnexpectedPropertyID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubStreamsInfo is a structure found within the StreamsInfo structure.
|
||||
type SubStreamsInfo struct {
|
||||
NumUnpackStreamsInFolders []int
|
||||
UnpackSizes []uint64
|
||||
Digests []uint32
|
||||
}
|
||||
|
||||
// ReadSubStreamsInfo reads the substreams info structure.
|
||||
func ReadSubStreamsInfo(r io.Reader, unpackInfo *UnpackInfo) (*SubStreamsInfo, error) {
|
||||
id, err := ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subStreamInfo := &SubStreamsInfo{}
|
||||
subStreamInfo.NumUnpackStreamsInFolders = make([]int, len(unpackInfo.Folders))
|
||||
for i := range subStreamInfo.NumUnpackStreamsInFolders {
|
||||
subStreamInfo.NumUnpackStreamsInFolders[i] = 1
|
||||
}
|
||||
|
||||
if id == k7zNumUnpackStream {
|
||||
for i := range subStreamInfo.NumUnpackStreamsInFolders {
|
||||
if subStreamInfo.NumUnpackStreamsInFolders[i], err = ReadNumberInt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
id, err = ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for i := range unpackInfo.Folders {
|
||||
if subStreamInfo.NumUnpackStreamsInFolders[i] == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var sum uint64
|
||||
if id == k7zSize {
|
||||
for j := 1; j < subStreamInfo.NumUnpackStreamsInFolders[i]; j++ {
|
||||
size, err := ReadNumber(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sum += size
|
||||
subStreamInfo.UnpackSizes = append(subStreamInfo.UnpackSizes, size)
|
||||
}
|
||||
}
|
||||
|
||||
subStreamInfo.UnpackSizes = append(subStreamInfo.UnpackSizes, unpackInfo.Folders[i].UnpackSize()-uint64(sum))
|
||||
}
|
||||
|
||||
if id == k7zSize {
|
||||
id, err = ReadByte(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
numDigests := 0
|
||||
for i := range unpackInfo.Folders {
|
||||
numSubStreams := subStreamInfo.NumUnpackStreamsInFolders[i]
|
||||
if numSubStreams > 1 || unpackInfo.Folders[i].UnpackCRC == 0 {
|
||||
numDigests += int(numSubStreams)
|
||||
}
|
||||
}
|
||||
|
||||
if id == k7zCRC {
|
||||
subStreamInfo.Digests, err = ReadDigests(r, numDigests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, |