- update
continuous-integration/drone/push Build is passing Details

master
李光春 10 months ago
parent b56472cb82
commit d31a69a439

1
.gitignore vendored

@ -5,5 +5,4 @@
.vscode
*.log
gomod.sh
/vendor/
download_test.go

@ -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,