310 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| package rardecode
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	maxSfxSize = 0x100000 // maximum number of bytes to read when searching for RAR signature
 | |
| 	sigPrefix  = "Rar!\x1A\x07"
 | |
| 
 | |
| 	fileFmt15 = iota + 1 // Version 1.5 archive file format
 | |
| 	fileFmt50            // Version 5.0 archive file format
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	errNoSig              = errors.New("rardecode: RAR signature not found")
 | |
| 	errVerMismatch        = errors.New("rardecode: volume version mistmatch")
 | |
| 	errCorruptHeader      = errors.New("rardecode: corrupt block header")
 | |
| 	errCorruptFileHeader  = errors.New("rardecode: corrupt file header")
 | |
| 	errBadHeaderCrc       = errors.New("rardecode: bad header crc")
 | |
| 	errUnknownArc         = errors.New("rardecode: unknown archive version")
 | |
| 	errUnknownDecoder     = errors.New("rardecode: unknown decoder version")
 | |
| 	errUnsupportedDecoder = errors.New("rardecode: unsupported decoder version")
 | |
| 	errArchiveContinues   = errors.New("rardecode: archive continues in next volume")
 | |
| 	errArchiveEnd         = errors.New("rardecode: archive end reached")
 | |
| 	errDecoderOutOfData   = errors.New("rardecode: decoder expected more data than is in packed file")
 | |
| 
 | |
| 	reDigits = regexp.MustCompile(`\d+`)
 | |
| )
 | |
| 
 | |
| type readBuf []byte
 | |
| 
 | |
| func (b *readBuf) byte() byte {
 | |
| 	v := (*b)[0]
 | |
| 	*b = (*b)[1:]
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| func (b *readBuf) uint16() uint16 {
 | |
| 	v := uint16((*b)[0]) | uint16((*b)[1])<<8
 | |
| 	*b = (*b)[2:]
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| func (b *readBuf) uint32() uint32 {
 | |
| 	v := uint32((*b)[0]) | uint32((*b)[1])<<8 | uint32((*b)[2])<<16 | uint32((*b)[3])<<24
 | |
| 	*b = (*b)[4:]
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| func (b *readBuf) bytes(n int) []byte {
 | |
| 	v := (*b)[:n]
 | |
| 	*b = (*b)[n:]
 | |
| 	return v
 | |
| }
 | |
| 
 | |
| func (b *readBuf) uvarint() uint64 {
 | |
| 	var x uint64
 | |
| 	var s uint
 | |
| 	for i, n := range *b {
 | |
| 		if n < 0x80 {
 | |
| 			*b = (*b)[i+1:]
 | |
| 			return x | uint64(n)<<s
 | |
| 		}
 | |
| 		x |= uint64(n&0x7f) << s
 | |
| 		s += 7
 | |
| 
 | |
| 	}
 | |
| 	// if we run out of bytes, just return 0
 | |
| 	*b = (*b)[len(*b):]
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // readFull wraps io.ReadFull to return io.ErrUnexpectedEOF instead
 | |
| // of io.EOF when 0 bytes are read.
 | |
| func readFull(r io.Reader, buf []byte) error {
 | |
| 	_, err := io.ReadFull(r, buf)
 | |
| 	if err == io.EOF {
 | |
| 		return io.ErrUnexpectedEOF
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // findSig searches for the RAR signature and version at the beginning of a file.
 | |
| // It searches no more than maxSfxSize bytes.
 | |
| func findSig(br *bufio.Reader) (int, error) {
 | |
| 	for n := 0; n <= maxSfxSize; {
 | |
| 		b, err := br.ReadSlice(sigPrefix[0])
 | |
| 		n += len(b)
 | |
| 		if err == bufio.ErrBufferFull {
 | |
| 			continue
 | |
| 		} else if err != nil {
 | |
| 			if err == io.EOF {
 | |
| 				err = errNoSig
 | |
| 			}
 | |
| 			return 0, err
 | |
| 		}
 | |
| 
 | |
| 		b, err = br.Peek(len(sigPrefix[1:]) + 2)
 | |
| 		if err != nil {
 | |
| 			if err == io.EOF {
 | |
| 				err = errNoSig
 | |
| 			}
 | |
| 			return 0, err
 | |
| 		}
 | |
| 		if !bytes.HasPrefix(b, []byte(sigPrefix[1:])) {
 | |
| 			continue
 | |
| 		}
 | |
| 		b = b[len(sigPrefix)-1:]
 | |
| 
 | |
| 		var ver int
 | |
| 		switch {
 | |
| 		case b[0] == 0:
 | |
| 			ver = fileFmt15
 | |
| 		case b[0] == 1 && b[1] == 0:
 | |
| 			ver = fileFmt50
 | |
| 		default:
 | |
| 			continue
 | |
| 		}
 | |
| 		_, _ = br.ReadSlice('\x00')
 | |
| 
 | |
| 		return ver, nil
 | |
| 	}
 | |
| 	return 0, errNoSig
 | |
| }
 | |
| 
 | |
| // volume extends a fileBlockReader to be used across multiple
 | |
| // files in a multi-volume archive
 | |
| type volume struct {
 | |
| 	fileBlockReader
 | |
| 	f     *os.File      // current file handle
 | |
| 	br    *bufio.Reader // buffered reader for current volume file
 | |
| 	dir   string        // volume directory
 | |
| 	file  string        // current volume file (not including directory)
 | |
| 	files []string      // full path names for current volume files processed
 | |
| 	num   int           // volume number
 | |
| 	old   bool          // uses old naming scheme
 | |
| }
 | |
| 
 | |
| // nextVolName updates name to the next filename in the archive.
 | |
| func (v *volume) nextVolName() {
 | |
| 	if v.num == 0 {
 | |
| 		// check file extensions
 | |
| 		i := strings.LastIndex(v.file, ".")
 | |
| 		if i < 0 {
 | |
| 			// no file extension, add one
 | |
| 			i = len(v.file)
 | |
| 			v.file += ".rar"
 | |
| 		} else {
 | |
| 			ext := strings.ToLower(v.file[i+1:])
 | |
| 			// replace with .rar for empty extensions & self extracting archives
 | |
| 			if ext == "" || ext == "exe" || ext == "sfx" {
 | |
| 				v.file = v.file[:i+1] + "rar"
 | |
| 			}
 | |
| 		}
 | |
| 		if a, ok := v.fileBlockReader.(*archive15); ok {
 | |
| 			v.old = a.old
 | |
| 		}
 | |
| 		// new naming scheme must have volume number in filename
 | |
| 		if !v.old && reDigits.FindStringIndex(v.file) == nil {
 | |
| 			v.old = true
 | |
| 		}
 | |
| 		// For old style naming if 2nd and 3rd character of file extension is not a digit replace
 | |
| 		// with "00" and ignore any trailing characters.
 | |
| 		if v.old && (len(v.file) < i+4 || v.file[i+2] < '0' || v.file[i+2] > '9' || v.file[i+3] < '0' || v.file[i+3] > '9') {
 | |
| 			v.file = v.file[:i+2] + "00"
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	// new style volume naming
 | |
| 	if !v.old {
 | |
| 		// find all numbers in volume name
 | |
| 		m := reDigits.FindAllStringIndex(v.file, -1)
 | |
| 		if l := len(m); l > 1 {
 | |
| 			// More than 1 match so assume name.part###of###.rar style.
 | |
| 			// Take the last 2 matches where the first is the volume number.
 | |
| 			m = m[l-2 : l]
 | |
| 			if strings.Contains(v.file[m[0][1]:m[1][0]], ".") || !strings.Contains(v.file[:m[0][0]], ".") {
 | |
| 				// Didn't match above style as volume had '.' between the two numbers or didnt have a '.'
 | |
| 				// before the first match. Use the second number as volume number.
 | |
| 				m = m[1:]
 | |
| 			}
 | |
| 		}
 | |
| 		// extract and increment volume number
 | |
| 		lo, hi := m[0][0], m[0][1]
 | |
| 		n, err := strconv.Atoi(v.file[lo:hi])
 | |
| 		if err != nil {
 | |
| 			n = 0
 | |
| 		} else {
 | |
| 			n++
 | |
| 		}
 | |
| 		// volume number must use at least the same number of characters as previous volume
 | |
| 		vol := fmt.Sprintf("%0"+fmt.Sprint(hi-lo)+"d", n)
 | |
| 		v.file = v.file[:lo] + vol + v.file[hi:]
 | |
| 		return
 | |
| 	}
 | |
| 	// old style volume naming
 | |
| 	i := strings.LastIndex(v.file, ".")
 | |
| 	// get file extension
 | |
| 	b := []byte(v.file[i+1:])
 | |
| 	// start incrementing volume number digits from rightmost
 | |
| 	for j := 2; j >= 0; j-- {
 | |
| 		if b[j] != '9' {
 | |
| 			b[j]++
 | |
| 			break
 | |
| 		}
 | |
| 		// digit overflow
 | |
| 		if j == 0 {
 | |
| 			// last character before '.'
 | |
| 			b[j] = 'A'
 | |
| 		} else {
 | |
| 			// set to '0' and loop to next character
 | |
| 			b[j] = '0'
 | |
| 		}
 | |
| 	}
 | |
| 	v.file = v.file[:i+1] + string(b)
 | |
| }
 | |
| 
 | |
| func (v *volume) next() (*fileBlockHeader, error) {
 | |
| 	for {
 | |
| 		var atEOF bool
 | |
| 
 | |
| 		h, err := v.fileBlockReader.next()
 | |
| 		switch err {
 | |
| 		case errArchiveContinues:
 | |
| 		case io.EOF:
 | |
| 			// Read all of volume without finding an end block. The only way
 | |
| 			// to tell if the archive continues is to try to open the next volume.
 | |
| 			atEOF = true
 | |
| 		default:
 | |
| 			return h, err
 | |
| 		}
 | |
| 
 | |
| 		v.f.Close()
 | |
| 		v.nextVolName()
 | |
| 		v.f, err = os.Open(v.dir + v.file) // Open next volume file
 | |
| 		if err != nil {
 | |
| 			if atEOF && os.IsNotExist(err) {
 | |
| 				// volume not found so assume that the archive has ended
 | |
| 				return nil, io.EOF
 | |
| 			}
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		v.num++
 | |
| 		v.br.Reset(v.f)
 | |
| 		ver, err := findSig(v.br)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if v.version() != ver {
 | |
| 			return nil, errVerMismatch
 | |
| 		}
 | |
| 		v.files = append(v.files, v.dir+v.file)
 | |
| 		v.reset() // reset encryption
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (v *volume) Close() error {
 | |
| 	// may be nil if os.Open fails in next()
 | |
| 	if v.f == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return v.f.Close()
 | |
| }
 | |
| 
 | |
| func openVolume(name, password string) (*volume, error) {
 | |
| 	var err error
 | |
| 	v := new(volume)
 | |
| 	v.dir, v.file = filepath.Split(name)
 | |
| 	v.f, err = os.Open(name)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	v.br = bufio.NewReader(v.f)
 | |
| 	v.fileBlockReader, err = newFileBlockReader(v.br, password)
 | |
| 	if err != nil {
 | |
| 		v.f.Close()
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	v.files = append(v.files, name)
 | |
| 	return v, nil
 | |
| }
 | |
| 
 | |
| func newFileBlockReader(br *bufio.Reader, pass string) (fileBlockReader, error) {
 | |
| 	runes := []rune(pass)
 | |
| 	if len(runes) > maxPassword {
 | |
| 		pass = string(runes[:maxPassword])
 | |
| 	}
 | |
| 	ver, err := findSig(br)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	switch ver {
 | |
| 	case fileFmt15:
 | |
| 		return newArchive15(br, pass), nil
 | |
| 	case fileFmt50:
 | |
| 		return newArchive50(br, pass), nil
 | |
| 	}
 | |
| 	return nil, errUnknownArc
 | |
| }
 |