mattermost-community-enterp.../vendor/github.com/nwaples/rardecode/v2/volume.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
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>
2025-12-17 23:59:07 +09:00

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
}