githaven/vendor/github.com/nwaples/rardecode/archive15.go

469 lines
11 KiB
Go
Raw Normal View History

package rardecode
import (
"bufio"
"bytes"
"crypto/sha1"
"errors"
"hash"
"hash/crc32"
"io"
"io/ioutil"
"strconv"
"strings"
"time"
"unicode/utf16"
)
const (
// block types
blockArc = 0x73
blockFile = 0x74
blockService = 0x7a
blockEnd = 0x7b
// block flags
blockHasData = 0x8000
// archive block flags
arcVolume = 0x0001
arcSolid = 0x0008
arcNewNaming = 0x0010
arcEncrypted = 0x0080
// file block flags
fileSplitBefore = 0x0001
fileSplitAfter = 0x0002
fileEncrypted = 0x0004
fileSolid = 0x0010
fileWindowMask = 0x00e0
fileLargeData = 0x0100
fileUnicode = 0x0200
fileSalt = 0x0400
fileVersion = 0x0800
fileExtTime = 0x1000
// end block flags
endArcNotLast = 0x0001
saltSize = 8 // size of salt for calculating AES keys
cacheSize30 = 4 // number of AES keys to cache
hashRounds = 0x40000
)
var (
errMultipleDecoders = errors.New("rardecode: multiple decoders in a single archive not supported")
)
type blockHeader15 struct {
htype byte // block header type
flags uint16
data readBuf // header data
dataSize int64 // size of extra block data
}
// fileHash32 implements fileChecksum for 32-bit hashes
type fileHash32 struct {
hash.Hash32 // hash to write file contents to
sum uint32 // 32bit checksum for file
}
func (h *fileHash32) valid() bool {
return h.sum == h.Sum32()
}
// archive15 implements fileBlockReader for RAR 1.5 file format archives
type archive15 struct {
byteReader // reader for current block data
v *bufio.Reader // reader for current archive volume
dec decoder // current decoder
decVer byte // current decoder version
multi bool // archive is multi-volume
old bool // archive uses old naming scheme
solid bool // archive is a solid archive
encrypted bool
pass []uint16 // password in UTF-16
checksum fileHash32 // file checksum
buf readBuf // temporary buffer
keyCache [cacheSize30]struct { // cache of previously calculated decryption keys
salt []byte
key []byte
iv []byte
}
}
// Calculates the key and iv for AES decryption given a password and salt.
func calcAes30Params(pass []uint16, salt []byte) (key, iv []byte) {
p := make([]byte, 0, len(pass)*2+len(salt))
for _, v := range pass {
p = append(p, byte(v), byte(v>>8))
}
p = append(p, salt...)
hash := sha1.New()
iv = make([]byte, 16)
s := make([]byte, 0, hash.Size())
for i := 0; i < hashRounds; i++ {
hash.Write(p)
hash.Write([]byte{byte(i), byte(i >> 8), byte(i >> 16)})
if i%(hashRounds/16) == 0 {
s = hash.Sum(s[:0])
iv[i/(hashRounds/16)] = s[4*4+3]
}
}
key = hash.Sum(s[:0])
key = key[:16]
for k := key; len(k) >= 4; k = k[4:] {
k[0], k[1], k[2], k[3] = k[3], k[2], k[1], k[0]
}
return key, iv
}
// parseDosTime converts a 32bit DOS time value to time.Time
func parseDosTime(t uint32) time.Time {
n := int(t)
sec := n & 0x1f << 1
min := n >> 5 & 0x3f
hr := n >> 11 & 0x1f
day := n >> 16 & 0x1f
mon := time.Month(n >> 21 & 0x0f)
yr := n>>25&0x7f + 1980
return time.Date(yr, mon, day, hr, min, sec, 0, time.Local)
}
// decodeName decodes a non-unicode filename from a file header.
func decodeName(buf []byte) string {
i := bytes.IndexByte(buf, 0)
if i < 0 {
return string(buf) // filename is UTF-8
}
name := buf[:i]
encName := readBuf(buf[i+1:])
if len(encName) < 2 {
return "" // invalid encoding
}
highByte := uint16(encName.byte()) << 8
flags := encName.byte()
flagBits := 8
var wchars []uint16 // decoded characters are UTF-16
for len(wchars) < len(name) && len(encName) > 0 {
if flagBits == 0 {
flags = encName.byte()
flagBits = 8
if len(encName) == 0 {
break
}
}
switch flags >> 6 {
case 0:
wchars = append(wchars, uint16(encName.byte()))
case 1:
wchars = append(wchars, uint16(encName.byte())|highByte)
case 2:
if len(encName) < 2 {
break
}
wchars = append(wchars, encName.uint16())
case 3:
n := encName.byte()
b := name[len(wchars):]
if l := int(n&0x7f) + 2; l < len(b) {
b = b[:l]
}
if n&0x80 > 0 {
if len(encName) < 1 {
break
}
ec := encName.byte()
for _, c := range b {
wchars = append(wchars, uint16(c+ec)|highByte)
}
} else {
for _, c := range b {
wchars = append(wchars, uint16(c))
}
}
}
flags <<= 2
flagBits -= 2
}
return string(utf16.Decode(wchars))
}
// readExtTimes reads and parses the optional extra time field from the file header.
func readExtTimes(f *fileBlockHeader, b *readBuf) {
if len(*b) < 2 {
return // invalid, not enough data
}
flags := b.uint16()
ts := []*time.Time{&f.ModificationTime, &f.CreationTime, &f.AccessTime}
for i, t := range ts {
n := flags >> uint((3-i)*4)
if n&0x8 == 0 {
continue
}
if i != 0 { // ModificationTime already read so skip
if len(*b) < 4 {
return // invalid, not enough data
}
*t = parseDosTime(b.uint32())
}
if n&0x4 > 0 {
*t = t.Add(time.Second)
}
n &= 0x3
if n == 0 {
continue
}
if len(*b) < int(n) {
return // invalid, not enough data
}
// add extra time data in 100's of nanoseconds
d := time.Duration(0)
for j := 3 - n; j < n; j++ {
d |= time.Duration(b.byte()) << (j * 8)
}
d *= 100
*t = t.Add(d)
}
}
func (a *archive15) getKeys(salt []byte) (key, iv []byte) {
// check cache of keys
for _, v := range a.keyCache {
if bytes.Equal(v.salt[:], salt) {
return v.key, v.iv
}
}
key, iv = calcAes30Params(a.pass, salt)
// save a copy in the cache
copy(a.keyCache[1:], a.keyCache[:])
a.keyCache[0].salt = append([]byte(nil), salt...) // copy so byte slice can be reused
a.keyCache[0].key = key
a.keyCache[0].iv = iv
return key, iv
}
func (a *archive15) parseFileHeader(h *blockHeader15) (*fileBlockHeader, error) {
f := new(fileBlockHeader)
f.first = h.flags&fileSplitBefore == 0
f.last = h.flags&fileSplitAfter == 0
f.solid = h.flags&fileSolid > 0
f.IsDir = h.flags&fileWindowMask == fileWindowMask
if !f.IsDir {
f.winSize = uint(h.flags&fileWindowMask)>>5 + 16
}
b := h.data
if len(b) < 21 {
return nil, errCorruptFileHeader
}
f.PackedSize = h.dataSize
f.UnPackedSize = int64(b.uint32())
f.HostOS = b.byte() + 1
if f.HostOS > HostOSBeOS {
f.HostOS = HostOSUnknown
}
a.checksum.sum = b.uint32()
f.ModificationTime = parseDosTime(b.uint32())
unpackver := b.byte() // decoder version
method := b.byte() - 0x30 // decryption method
namesize := int(b.uint16())
f.Attributes = int64(b.uint32())
if h.flags&fileLargeData > 0 {
if len(b) < 8 {
return nil, errCorruptFileHeader
}
_ = b.uint32() // already read large PackedSize in readBlockHeader
f.UnPackedSize |= int64(b.uint32()) << 32
f.UnKnownSize = f.UnPackedSize == -1
} else if int32(f.UnPackedSize) == -1 {
f.UnKnownSize = true
f.UnPackedSize = -1
}
if len(b) < namesize {
return nil, errCorruptFileHeader
}
name := b.bytes(namesize)
if h.flags&fileUnicode == 0 {
f.Name = string(name)
} else {
f.Name = decodeName(name)
}
// Rar 4.x uses '\' as file separator
f.Name = strings.Replace(f.Name, "\\", "/", -1)
if h.flags&fileVersion > 0 {
// file version is stored as ';n' appended to file name
i := strings.LastIndex(f.Name, ";")
if i > 0 {
j, err := strconv.Atoi(f.Name[i+1:])
if err == nil && j >= 0 {
f.Version = j
f.Name = f.Name[:i]
}
}
}
var salt []byte
if h.flags&fileSalt > 0 {
if len(b) < saltSize {
return nil, errCorruptFileHeader
}
salt = b.bytes(saltSize)
}
if h.flags&fileExtTime > 0 {
readExtTimes(f, &b)
}
if !f.first {
return f, nil
}
// fields only needed for first block in a file
if h.flags&fileEncrypted > 0 && len(salt) == saltSize {
f.key, f.iv = a.getKeys(salt)
}
a.checksum.Reset()
f.cksum = &a.checksum
if method == 0 {
return f, nil
}
if a.dec == nil {
switch unpackver {
case 15, 20, 26:
return nil, errUnsupportedDecoder
case 29:
a.dec = new(decoder29)
default:
return nil, errUnknownDecoder
}
a.decVer = unpackver
} else if a.decVer != unpackver {
return nil, errMultipleDecoders
}
f.decoder = a.dec
return f, nil
}
// readBlockHeader returns the next block header in the archive.
// It will return io.EOF if there were no bytes read.
func (a *archive15) readBlockHeader() (*blockHeader15, error) {
var err error
b := a.buf[:7]
r := io.Reader(a.v)
if a.encrypted {
salt := a.buf[:saltSize]
_, err = io.ReadFull(r, salt)
if err != nil {
return nil, err
}
key, iv := a.getKeys(salt)
r = newAesDecryptReader(r, key, iv)
err = readFull(r, b)
} else {
_, err = io.ReadFull(r, b)
}
if err != nil {
return nil, err
}
crc := b.uint16()
hash := crc32.NewIEEE()
hash.Write(b)
h := new(blockHeader15)
h.htype = b.byte()
h.flags = b.uint16()
size := b.uint16()
if size < 7 {
return nil, errCorruptHeader
}
size -= 7
if int(size) > cap(a.buf) {
a.buf = readBuf(make([]byte, size))
}
h.data = a.buf[:size]
if err := readFull(r, h.data); err != nil {
return nil, err
}
hash.Write(h.data)
if crc != uint16(hash.Sum32()) {
return nil, errBadHeaderCrc
}
if h.flags&blockHasData > 0 {
if len(h.data) < 4 {
return nil, errCorruptHeader
}
h.dataSize = int64(h.data.uint32())
}
if (h.htype == blockService || h.htype == blockFile) && h.flags&fileLargeData > 0 {
if len(h.data) < 25 {
return nil, errCorruptHeader
}
b := h.data[21:25]
h.dataSize |= int64(b.uint32()) << 32
}
return h, nil
}
// next advances to the next file block in the archive
func (a *archive15) next() (*fileBlockHeader, error) {
for {
// could return an io.EOF here as 1.5 archives may not have an end block.
h, err := a.readBlockHeader()
if err != nil {
return nil, err
}
a.byteReader = limitByteReader(a.v, h.dataSize) // reader for block data
switch h.htype {
case blockFile:
return a.parseFileHeader(h)
case blockArc:
a.encrypted = h.flags&arcEncrypted > 0
a.multi = h.flags&arcVolume > 0
a.old = h.flags&arcNewNaming == 0
a.solid = h.flags&arcSolid > 0
case blockEnd:
if h.flags&endArcNotLast == 0 || !a.multi {
return nil, errArchiveEnd
}
return nil, errArchiveContinues
default:
_, err = io.Copy(ioutil.Discard, a.byteReader)
}
if err != nil {
return nil, err
}
}
}
func (a *archive15) version() int { return fileFmt15 }
func (a *archive15) reset() {
a.encrypted = false // reset encryption when opening new volume file
}
func (a *archive15) isSolid() bool {
return a.solid
}
// newArchive15 creates a new fileBlockReader for a Version 1.5 archive
func newArchive15(r *bufio.Reader, password string) fileBlockReader {
a := new(archive15)
a.v = r
a.pass = utf16.Encode([]rune(password)) // convert to UTF-16
a.checksum.Hash32 = crc32.NewIEEE()
a.buf = readBuf(make([]byte, 100))
return a
}