rclone/lib/encoder/filename/decode.go
Klaus Post cb7534dcdf
lib: Add file name compression
Allows to compress short arbitrary strings and returns a string using base64 url encoding.

Generator for tables included and a few samples has been added. Add more to init.go

Tested with fuzzing for crash resistance and symmetry, see fuzz.go
2020-08-13 16:14:11 +01:00

85 lines
2.0 KiB
Go

package filename
import (
"bytes"
"encoding/base64"
"encoding/binary"
"errors"
"sync"
"github.com/klauspost/compress/huff0"
)
// ErrCorrupted is returned if a provided encoded filename cannot be decoded.
var ErrCorrupted = errors.New("file name corrupt")
// ErrUnsupported is returned if a provided encoding may come from a future version or the file name is corrupt.
var ErrUnsupported = errors.New("file name possibly generated by future version of rclone")
// Custom decoder for tableCustom types. Stateful, so must have lock.
var customDec huff0.Scratch
var customDecMu sync.Mutex
// Decode an encoded string.
func Decode(s string) (string, error) {
if len(s) < 1 {
return "", ErrCorrupted
}
table := decodeMap[s[0]]
if table == 0 {
return "", ErrCorrupted
}
table--
s = s[1:]
data := make([]byte, base64.URLEncoding.DecodedLen(len(s)))
n, err := base64.URLEncoding.Decode(data, ([]byte)(s))
if err != nil || n < 0 {
return "", ErrCorrupted
}
data = data[:n]
switch table {
case tableUncompressed:
return string(data), nil
case tableReserved:
return "", ErrUnsupported
case tableRLE:
if len(data) < 2 {
return "", ErrCorrupted
}
n, used := binary.Uvarint(data[:len(data)-1])
if used <= 0 || n > maxLength {
return "", ErrCorrupted
}
return string(bytes.Repeat(data[len(data)-1:], int(n))), nil
case tableCustom:
customDecMu.Lock()
defer customDecMu.Unlock()
_, data, err := huff0.ReadTable(data, &customDec)
if err != nil {
return "", ErrCorrupted
}
customDec.MaxDecodedSize = maxLength
decoded, err := customDec.Decompress1X(data)
if err != nil {
return "", ErrCorrupted
}
return string(decoded), nil
default:
if table >= byte(len(decTables)) {
return "", ErrCorrupted
}
dec := decTables[table]
if dec == nil {
return "", ErrUnsupported
}
var dst [maxLength]byte
name, err := dec.Decompress1X(dst[:0], data)
if err != nil {
return "", ErrCorrupted
}
return string(name), nil
}
}