Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
522 lines
11 KiB
Go
522 lines
11 KiB
Go
package rardecode
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var (
|
|
ErrVerMismatch = errors.New("rardecode: volume version mistmatch")
|
|
ErrArchiveNameEmpty = errors.New("rardecode: archive name empty")
|
|
ErrFileNameRequired = errors.New("rardecode: filename required for multi volume archive")
|
|
ErrInvalidHeaderOff = errors.New("rardecode: invalid filed header offset")
|
|
|
|
defaultFS = osFS{}
|
|
)
|
|
|
|
const (
|
|
DefaultMaxDictionarySize = 4 << 30 // default max dictionary size of 4GB
|
|
)
|
|
|
|
type osFS struct{}
|
|
|
|
func (fs osFS) Open(name string) (fs.File, error) {
|
|
return os.Open(name)
|
|
}
|
|
|
|
type options struct {
|
|
bsize int // size to be use for bufio.Reader
|
|
maxDictSize int64 // max dictionary size
|
|
fs fs.FS // filesystem to use to open files
|
|
pass *string // password for encrypted volumes
|
|
skipCheck bool
|
|
openCheck bool
|
|
}
|
|
|
|
// An Option is used for optional archive extraction settings.
|
|
type Option func(*options)
|
|
|
|
// BufferSize sets the size of the bufio.Reader used in reading the archive.
|
|
func BufferSize(size int) Option {
|
|
return func(o *options) { o.bsize = size }
|
|
}
|
|
|
|
// MaxDictionarySize sets the maximum size in bytes of the dictionary used in decoding a file.
|
|
// Any attempt to decode a file with a larger size will return an error.
|
|
// The default size if not set is DefaultMaxDictionarySize.
|
|
// Any size above 64GB will be ignored. Any size below 256kB will prevent any file from being decoded.
|
|
func MaxDictionarySize(size int64) Option {
|
|
return func(o *options) { o.maxDictSize = size }
|
|
}
|
|
|
|
// FileSystem sets the fs.FS to be used for opening archive volumes.
|
|
func FileSystem(fs fs.FS) Option {
|
|
return func(o *options) { o.fs = fs }
|
|
}
|
|
|
|
// Password sets the password to use for decrypting archives.
|
|
func Password(pass string) Option {
|
|
return func(o *options) { o.pass = &pass }
|
|
}
|
|
|
|
// SkipCheck sets archive files checksum not to be checked.
|
|
func SkipCheck(o *options) { o.skipCheck = true }
|
|
|
|
// OpenFSCheck flags the archive files to be checked on Open or List.
|
|
func OpenFSCheck(o *options) { o.openCheck = true }
|
|
|
|
func getOptions(opts []Option) *options {
|
|
opt := &options{
|
|
fs: defaultFS,
|
|
maxDictSize: DefaultMaxDictionarySize,
|
|
}
|
|
for _, f := range opts {
|
|
f(opt)
|
|
}
|
|
// truncate password
|
|
if opt.pass != nil {
|
|
runes := []rune(*opt.pass)
|
|
if len(runes) > maxPassword {
|
|
pw := string(runes[:maxPassword])
|
|
opt.pass = &pw
|
|
}
|
|
}
|
|
return opt
|
|
}
|
|
|
|
type volume interface {
|
|
byteReader
|
|
writeToAtMost(w io.Writer, n int64) (int64, error)
|
|
nextBlock() (*fileBlockHeader, error)
|
|
openBlock(volnum int, offset, size int64) error
|
|
canSeek() bool
|
|
}
|
|
|
|
type readerVolume struct {
|
|
br *bufVolumeReader // buffered reader for current volume file
|
|
n int64 // bytes left in current block
|
|
num int // current volume number
|
|
ver int // archive file format version
|
|
arc archiveBlockReader
|
|
opt *options
|
|
}
|
|
|
|
func (v *readerVolume) init(r io.Reader, volnum int) error {
|
|
var err error
|
|
if v.br == nil {
|
|
v.br, err = newBufVolumeReader(r, v.opt.bsize)
|
|
} else {
|
|
err = v.br.Reset(r)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v.arc == nil {
|
|
switch v.br.ver {
|
|
case archiveVersion15:
|
|
v.arc = newArchive15(v.opt.pass)
|
|
case archiveVersion50:
|
|
v.arc = newArchive50(v.opt.pass)
|
|
default:
|
|
return ErrUnknownVersion
|
|
}
|
|
v.ver = v.br.ver
|
|
} else if v.ver != v.br.ver {
|
|
return ErrVerMismatch
|
|
}
|
|
n, err := v.arc.init(v.br)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.num = volnum
|
|
if n >= 0 && n != volnum {
|
|
return ErrBadVolumeNumber
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *readerVolume) nextBlock() (*fileBlockHeader, error) {
|
|
if v.n > 0 {
|
|
err := v.br.Discard(v.n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v.n = 0
|
|
}
|
|
f, err := v.arc.nextBlock(v.br)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f.volnum = v.num
|
|
f.dataOff = v.br.off
|
|
v.n = f.PackedSize
|
|
return f, nil
|
|
}
|
|
|
|
func (v *readerVolume) Read(p []byte) (int, error) {
|
|
if v.n == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
if v.n < int64(len(p)) {
|
|
p = p[:v.n]
|
|
}
|
|
n, err := v.br.Read(p)
|
|
v.n -= int64(n)
|
|
if err == io.EOF && v.n > 0 {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (v *readerVolume) ReadByte() (byte, error) {
|
|
if v.n == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
b, err := v.br.ReadByte()
|
|
if err == nil {
|
|
v.n--
|
|
} else if err == io.EOF && v.n > 0 {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
func (v *readerVolume) writeToAtMost(w io.Writer, n int64) (int64, error) {
|
|
if n == 0 {
|
|
return 0, nil
|
|
}
|
|
if n > 0 {
|
|
n = min(n, v.n)
|
|
} else {
|
|
n = v.n
|
|
}
|
|
l, err := v.br.writeToN(w, n)
|
|
v.n -= l
|
|
return l, err
|
|
}
|
|
|
|
func (v *readerVolume) canSeek() bool {
|
|
return v.br.canSeek()
|
|
}
|
|
|
|
func (v *readerVolume) openBlock(volnum int, offset, size int64) error {
|
|
if v.num != volnum {
|
|
return ErrBadVolumeNumber
|
|
}
|
|
err := v.br.seek(offset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.n = size
|
|
return nil
|
|
}
|
|
|
|
func newVolume(r io.Reader, opt *options, volnum int) (*readerVolume, error) {
|
|
v := &readerVolume{opt: opt}
|
|
err := v.init(r, volnum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
type fileVolume struct {
|
|
*readerVolume
|
|
f fs.File
|
|
vm *volumeManager
|
|
}
|
|
|
|
func (v *fileVolume) Close() error { return v.f.Close() }
|
|
|
|
func (v *fileVolume) open(volnum int) error {
|
|
err := v.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f, err := v.vm.openVolumeFile(volnum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = v.readerVolume.init(f, volnum)
|
|
if err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
v.f = f
|
|
return nil
|
|
}
|
|
|
|
func (v *fileVolume) openBlock(volnum int, offset, size int64) error {
|
|
if v.num != volnum {
|
|
err := v.open(volnum)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return v.readerVolume.openBlock(volnum, offset, size)
|
|
}
|
|
|
|
func (v *fileVolume) openNext() error { return v.open(v.num + 1) }
|
|
|
|
func (v *fileVolume) nextBlock() (*fileBlockHeader, error) {
|
|
for {
|
|
h, err := v.readerVolume.nextBlock()
|
|
if err == nil {
|
|
return h, nil
|
|
}
|
|
if err == ErrMultiVolume {
|
|
err = v.openNext()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err == errVolumeOrArchiveEnd {
|
|
err = v.openNext()
|
|
if err != nil {
|
|
// new volume doesnt exist, assume end of archive
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil, io.EOF
|
|
}
|
|
return nil, err
|
|
}
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func nextNewVolName(file string) string {
|
|
var inDigit bool
|
|
var m []int
|
|
for i, c := range file {
|
|
if c >= '0' && c <= '9' {
|
|
if !inDigit {
|
|
m = append(m, i)
|
|
inDigit = true
|
|
}
|
|
} else if inDigit {
|
|
m = append(m, i)
|
|
inDigit = false
|
|
}
|
|
}
|
|
if inDigit {
|
|
m = append(m, len(file))
|
|
}
|
|
if l := len(m); l >= 4 {
|
|
// 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-4 : l]
|
|
if strings.Contains(file[m[1]:m[2]], ".") || !strings.Contains(file[:m[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[2:]
|
|
}
|
|
}
|
|
// extract and increment volume number
|
|
lo, hi := m[0], m[1]
|
|
n, err := strconv.Atoi(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)
|
|
return file[:lo] + vol + file[hi:]
|
|
}
|
|
|
|
func nextOldVolName(file string) string {
|
|
// old style volume naming
|
|
i := strings.LastIndex(file, ".")
|
|
// get file extension
|
|
b := []byte(file[i+1:])
|
|
|
|
// If 2nd and 3rd character of file extension is not a digit replace
|
|
// with "00" and ignore any trailing characters.
|
|
if len(b) < 3 || b[1] < '0' || b[1] > '9' || b[2] < '0' || b[2] > '9' {
|
|
return file[:i+2] + "00"
|
|
}
|
|
|
|
// 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'
|
|
}
|
|
}
|
|
return file[:i+1] + string(b)
|
|
}
|
|
|
|
func hasDigits(s string) bool {
|
|
for _, c := range s {
|
|
if c >= '0' && c <= '9' {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func fixFileExtension(file string) string {
|
|
// check file extensions
|
|
i := strings.LastIndex(file, ".")
|
|
if i < 0 {
|
|
// no file extension, add one
|
|
return file + ".rar"
|
|
}
|
|
ext := strings.ToLower(file[i+1:])
|
|
// replace with .rar for empty extensions & self extracting archives
|
|
if ext == "" || ext == "exe" || ext == "sfx" {
|
|
file = file[:i+1] + "rar"
|
|
}
|
|
return file
|
|
}
|
|
|
|
type volumeManager struct {
|
|
dir string // current volume directory path
|
|
opt *options
|
|
|
|
mu sync.Mutex
|
|
files []string // file names for each volume
|
|
old bool // uses old naming scheme
|
|
}
|
|
|
|
func (vm *volumeManager) Files() []string {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
return vm.files
|
|
}
|
|
|
|
func (vm *volumeManager) tryNewName(file string) (fs.File, error) {
|
|
// try using new naming scheme
|
|
name := nextNewVolName(file)
|
|
f, err := vm.opt.fs.Open(vm.dir + name)
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
vm.files = append(vm.files, name)
|
|
return f, err
|
|
}
|
|
// file didn't exist, try old naming scheme
|
|
name = nextOldVolName(file)
|
|
f, oldErr := vm.opt.fs.Open(vm.dir + name)
|
|
if !errors.Is(oldErr, fs.ErrNotExist) {
|
|
vm.old = true
|
|
vm.files = append(vm.files, name)
|
|
return f, oldErr
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// next opens the next volume file in the archive.
|
|
func (vm *volumeManager) openVolumeFile(volnum int) (fs.File, error) {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
|
|
var file string
|
|
// check for cached volume name
|
|
if volnum < len(vm.files) {
|
|
return vm.opt.fs.Open(vm.dir + vm.files[volnum])
|
|
}
|
|
file = vm.files[len(vm.files)-1]
|
|
if len(vm.files) == 1 {
|
|
file = fixFileExtension(file)
|
|
if !vm.old && hasDigits(file) {
|
|
return vm.tryNewName(file)
|
|
}
|
|
vm.old = true
|
|
}
|
|
for len(vm.files) <= volnum {
|
|
if vm.old {
|
|
file = nextOldVolName(file)
|
|
} else {
|
|
file = nextNewVolName(file)
|
|
}
|
|
vm.files = append(vm.files, file)
|
|
}
|
|
return vm.opt.fs.Open(vm.dir + file)
|
|
}
|
|
|
|
func (vm *volumeManager) newVolume(volnum int) (*fileVolume, error) {
|
|
f, err := vm.openVolumeFile(volnum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v, err := newVolume(f, vm.opt, volnum)
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, err
|
|
}
|
|
mv := &fileVolume{
|
|
readerVolume: v,
|
|
f: f,
|
|
vm: vm,
|
|
}
|
|
return mv, nil
|
|
}
|
|
|
|
func (vm *volumeManager) openBlockOffset(h *fileBlockHeader, offset int64) (*fileVolume, error) {
|
|
v, err := vm.newVolume(h.volnum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if h.dataOff < v.br.off {
|
|
v.Close()
|
|
return nil, ErrInvalidHeaderOff
|
|
}
|
|
err = v.br.Discard(h.dataOff - v.br.off + offset)
|
|
v.n = h.PackedSize - offset
|
|
if err != nil {
|
|
v.Close()
|
|
return nil, err
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func (vm *volumeManager) openArchiveFile(blocks *fileBlockList) (fs.File, error) {
|
|
h := blocks.firstBlock()
|
|
if h.Solid {
|
|
return nil, ErrSolidOpen
|
|
}
|
|
v, err := vm.openBlockOffset(h, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pr := newPackedFileReader(v, vm.opt)
|
|
f, err := pr.newArchiveFile(blocks)
|
|
if err != nil {
|
|
v.Close()
|
|
return nil, err
|
|
}
|
|
if sr, ok := f.(archiveFileSeeker); ok {
|
|
return &fileSeekCloser{archiveFileSeeker: sr, Closer: v}, nil
|
|
}
|
|
return &fileCloser{archiveFile: f, Closer: v}, nil
|
|
}
|
|
|
|
func openVolume(filename string, opts *options) (*fileVolume, error) {
|
|
dir, file := filepath.Split(filename)
|
|
vm := &volumeManager{
|
|
dir: dir,
|
|
files: []string{file},
|
|
opt: opts,
|
|
}
|
|
v, err := vm.newVolume(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vm.old = v.arc.useOldNaming()
|
|
return v, nil
|
|
}
|