mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-24 12:07:59 +00:00
This commit implements a massive refactor of the repository, and moves the build system over to use Mage (magefile.org) which should allow seamless building across multiple platforms.
307 lines
7.3 KiB
Go
307 lines
7.3 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
|
|
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.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
|
|
}
|
|
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
|
|
}
|