Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
11 KiB
468 lines
11 KiB
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 |
|
}
|
|
|