mattermost-community-enterp.../vendor/github.com/nwaples/rardecode/v2/reader.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

768 lines
17 KiB
Go

package rardecode
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"errors"
"hash"
"io"
"io/fs"
"math"
"sync"
"time"
)
// FileHeader HostOS types
const (
HostOSUnknown = 0
HostOSMSDOS = 1
HostOSOS2 = 2
HostOSWindows = 3
HostOSUnix = 4
HostOSMacOS = 5
HostOSBeOS = 6
)
const (
maxPassword = int(128)
)
var (
ErrShortFile = errors.New("rardecode: decoded file too short")
ErrInvalidFileBlock = errors.New("rardecode: invalid file block")
ErrUnexpectedArcEnd = errors.New("rardecode: unexpected end of archive")
ErrBadFileChecksum = errors.New("rardecode: bad file checksum")
ErrSolidOpen = errors.New("rardecode: solid files don't support Open")
ErrUnknownVersion = errors.New("rardecode: unknown archive version")
)
// FileHeader represents a single file in a RAR archive.
type FileHeader struct {
Name string // file name using '/' as the directory separator
IsDir bool // is a directory
Solid bool // is a solid file
Encrypted bool // file contents are encrypted
HeaderEncrypted bool // file header is encrypted
HostOS byte // Host OS the archive was created on
Attributes int64 // Host OS specific file attributes
PackedSize int64 // packed file size (or first block if the file spans volumes)
UnPackedSize int64 // unpacked file size
UnKnownSize bool // unpacked file size is not known
ModificationTime time.Time // modification time (non-zero if set)
CreationTime time.Time // creation time (non-zero if set)
AccessTime time.Time // access time (non-zero if set)
Version int // file version
}
// Mode returns an fs.FileMode for the file, calculated from the Attributes field.
func (f *FileHeader) Mode() fs.FileMode {
var m fs.FileMode
if f.IsDir {
m = fs.ModeDir
}
if f.HostOS == HostOSWindows {
if f.IsDir {
m |= 0777
} else if f.Attributes&1 > 0 {
m |= 0444 // readonly
} else {
m |= 0666
}
return m
}
// assume unix perms for all remaining os types
m |= fs.FileMode(f.Attributes) & fs.ModePerm
// only check other bits on unix host created archives
if f.HostOS != HostOSUnix {
return m
}
if f.Attributes&0x200 != 0 {
m |= fs.ModeSticky
}
if f.Attributes&0x400 != 0 {
m |= fs.ModeSetgid
}
if f.Attributes&0x800 != 0 {
m |= fs.ModeSetuid
}
// Check for additional file types.
if f.Attributes&0xF000 == 0xA000 {
m |= fs.ModeSymlink
}
return m
}
type byteReader interface {
io.Reader
io.ByteReader
}
type archiveFile interface {
byteReader
io.WriterTo
writeToN(w io.Writer, n int64) (int64, error)
currFile() *fileBlockHeader
nextFile() (*fileBlockList, error)
newArchiveFile(blocks *fileBlockList) (archiveFile, error)
Stat() (fs.FileInfo, error)
}
type archiveFileSeeker interface {
archiveFile
io.Seeker
}
type fileCloser struct {
archiveFile
io.Closer
}
type fileSeekCloser struct {
archiveFileSeeker
io.Closer
}
type errorFile struct {
archiveFile
err error
}
func (ef *errorFile) ReadByte() (byte, error) { return 0, ef.err }
func (ef *errorFile) Read(p []byte) (int, error) { return 0, ef.err }
func (ef *errorFile) WriteTo(w io.Writer) (int64, error) { return 0, ef.err }
func (ef *errorFile) writeToN(w io.Writer, n int64) (int64, error) { return 0, ef.err }
type fileBlockList struct {
mu sync.RWMutex
blocks []*fileBlockHeader
}
func (fl *fileBlockList) firstBlock() *fileBlockHeader {
fl.mu.RLock()
defer fl.mu.RUnlock()
return fl.blocks[0]
}
func (fl *fileBlockList) lastBlock() *fileBlockHeader {
fl.mu.RLock()
defer fl.mu.RUnlock()
return fl.blocks[len(fl.blocks)-1]
}
func (fl *fileBlockList) findBlock(offset int64) *fileBlockHeader {
fl.mu.RLock()
defer fl.mu.RUnlock()
for _, h := range fl.blocks {
if offset < h.PackedSize || (offset == h.PackedSize && h.last) {
return h
}
offset -= h.PackedSize
}
return nil
}
func (fl *fileBlockList) addBlock(h *fileBlockHeader) {
fl.mu.Lock()
defer fl.mu.Unlock()
if len(fl.blocks) == h.blocknum {
fl.blocks = append(fl.blocks, h)
}
}
func (fl *fileBlockList) isDir() bool {
fl.mu.RLock()
defer fl.mu.RUnlock()
return fl.blocks[0].IsDir
}
func (fl *fileBlockList) hasFileHash() bool {
fl.mu.RLock()
defer fl.mu.RUnlock()
return fl.blocks[0].hash != nil
}
func (fl *fileBlockList) removeFileHash() {
fl.mu.Lock()
defer fl.mu.Unlock()
h := *fl.blocks[0]
h.hash = nil
fl.blocks[0] = &h
}
func newFileBlockList(blocks ...*fileBlockHeader) *fileBlockList {
return &fileBlockList{blocks: blocks}
}
// packedFileReader provides sequential access to packed files in a RAR archive.
type packedFileReader struct {
v volume
h *fileBlockHeader // current file header
dr *decodeReader
offset int64
blocks *fileBlockList
opt *options
}
func (f *packedFileReader) init(blocks *fileBlockList) error {
h := blocks.firstBlock()
f.h = h
f.blocks = blocks
f.offset = 0
return nil
}
func (f *packedFileReader) Stat() (fs.FileInfo, error) {
if f.h == nil {
return nil, fs.ErrInvalid
}
return fileInfo{h: f.h}, nil
}
// nextBlock reads the next file block in the current file at the current
// archive file position, or returns an error if there is a problem.
// It is invalid to call this when already at the last block in the current file.
func (f *packedFileReader) nextBlock() error {
if f.h == nil {
return io.EOF
}
if f.h.last {
return io.EOF
}
h, err := f.v.nextBlock()
if err != nil {
if err == io.EOF {
// archive ended, but file hasn't
return ErrUnexpectedArcEnd
} else if err == errVolumeOrArchiveEnd {
return ErrMultiVolume
}
return err
}
if h.first || h.Name != f.h.Name {
return ErrInvalidFileBlock
}
h.packedOff = f.h.packedOff + f.h.PackedSize
h.blocknum = f.h.blocknum + 1
f.h = h
f.offset = h.dataOff
f.blocks.addBlock(h)
return nil
}
// next advances to the next packed file in the RAR archive.
func (f *packedFileReader) nextFile() (*fileBlockList, error) {
// skip to last block in current file
var err error
for err == nil {
err = f.nextBlock()
}
if err != io.EOF {
return nil, err
}
h, err := f.v.nextBlock() // get next file block
if err != nil {
if err == errVolumeOrArchiveEnd {
err = io.EOF
}
return nil, err
}
if !h.first {
return nil, ErrInvalidFileBlock
}
blocks := newFileBlockList(h)
err = f.init(blocks)
if err != nil {
return nil, err
}
return blocks, nil
}
func (f *packedFileReader) currFile() *fileBlockHeader { return f.h }
// Read reads the packed data for the current file into p.
func (f *packedFileReader) Read(p []byte) (int, error) {
for {
n, err := f.v.Read(p)
if err == io.EOF {
err = f.nextBlock()
}
if n > 0 || err != nil {
f.offset += int64(n)
return n, err
}
}
}
func (f *packedFileReader) ReadByte() (byte, error) {
for {
b, err := f.v.ReadByte()
if err == nil {
f.offset++
return b, nil
}
if err == io.EOF {
err = f.nextBlock()
if err == nil {
continue
}
}
return b, err
}
}
func (f *packedFileReader) writeToN(w io.Writer, n int64) (int64, error) {
if n == 0 {
return 0, nil
}
var err error
var tot int64
todo := n
for todo != 0 && err == nil {
var l int64
l, err = f.v.writeToAtMost(w, todo)
if todo > 0 {
todo -= l
}
tot += int64(l)
if err == nil && todo != 0 {
err = f.nextBlock()
}
}
if todo <= 0 && err == io.EOF {
err = nil
}
return tot, err
}
func (f *packedFileReader) WriteTo(w io.Writer) (int64, error) {
return f.writeToN(w, -1)
}
func (pr *packedFileReader) newArchiveFileFrom(r archiveFile, blocks *fileBlockList) (archiveFile, error) {
h := blocks.firstBlock()
err := pr.init(blocks)
if err != nil {
return nil, err
}
if h.Encrypted {
if h.key == nil {
r = &errorFile{archiveFile: r, err: ErrArchivedFileEncrypted}
} else {
r, err = newAesDecryptFileReader(r, h.key, h.iv) // decrypt
if err != nil {
return nil, err
}
}
}
// check for compression
if h.decVer > 0 {
if pr.dr == nil {
pr.dr = new(decodeReader)
}
// doesn't make sense for the dictionary to be larger than the file
if !h.UnKnownSize && h.winSize > h.UnPackedSize {
h.winSize = h.UnPackedSize
}
if h.winSize > maxDictSize || h.winSize > pr.opt.maxDictSize {
return nil, ErrDictionaryTooLarge
}
if h.winSize > math.MaxInt {
return nil, ErrPlatformIntSize
}
err := pr.dr.init(r, h.decVer, int(h.winSize), !h.Solid, h.arcSolid, h.UnPackedSize)
if err != nil {
return nil, err
}
r = pr.dr
}
if h.UnPackedSize >= 0 && !h.UnKnownSize {
// Limit reading to UnPackedSize as there may be padding
r = newLimitedReader(r, h.UnPackedSize)
}
if h.hash != nil && !pr.opt.skipCheck {
r = newChecksumReader(r, h.hash(), blocks.removeFileHash)
}
return r, nil
}
func (pr *packedFileReader) newArchiveFile(blocks *fileBlockList) (archiveFile, error) {
return pr.newArchiveFileFrom(pr, blocks)
}
type packedFileReadSeeker struct {
packedFileReader
}
func (f *packedFileReadSeeker) openBlock(h *fileBlockHeader, offset int64) (int64, error) {
err := f.v.openBlock(h.volnum, h.dataOff+offset, h.PackedSize-offset)
if err != nil {
return 0, err
}
f.h = h
f.offset = h.packedOff + offset
return f.offset, nil
}
func (f *packedFileReadSeeker) openNextBlock(h *fileBlockHeader) error {
_, err := f.openBlock(h, h.PackedSize)
if err != nil {
return err
}
return f.nextBlock()
}
func (f *packedFileReadSeeker) packedSize() (int64, error) {
h := f.blocks.lastBlock()
if h.last {
return h.packedOff + h.PackedSize, nil
}
err := f.openNextBlock(h)
for err == nil {
err = f.nextBlock()
}
if err != io.EOF {
return 0, err
}
if !f.h.last {
return 0, ErrInvalidFileBlock
}
return f.h.packedOff + f.h.PackedSize, nil
}
func (f *packedFileReadSeeker) Seek(offset int64, whence int) (int64, error) {
// calculate absolute offset
switch whence {
case io.SeekStart:
case io.SeekCurrent:
offset += f.offset
case io.SeekEnd:
size, err := f.packedSize()
if err != nil {
return 0, err
}
offset += size
default:
return 0, fs.ErrInvalid
}
if offset < 0 {
return 0, fs.ErrInvalid
}
// find block in existing list
h := f.blocks.findBlock(offset)
if h != nil {
offset -= h.packedOff
return f.openBlock(h, offset)
}
h = f.blocks.lastBlock()
if h.last {
return 0, fs.ErrInvalid
}
err := f.openNextBlock(h)
for err == nil {
off := offset - h.packedOff
if off < f.h.PackedSize || (off == f.h.PackedSize && f.h.last) {
return f.openBlock(f.h, off)
} else if h.last {
return 0, fs.ErrInvalid
}
err = f.nextBlock()
}
if err == io.EOF {
return 0, fs.ErrInvalid
}
return 0, err
}
func (pr *packedFileReadSeeker) newArchiveFile(blocks *fileBlockList) (archiveFile, error) {
return pr.newArchiveFileFrom(pr, blocks)
}
func newPackedFileReader(v volume, opts *options) archiveFile {
pr := packedFileReader{v: v, opt: opts}
if v.canSeek() {
return &packedFileReadSeeker{pr}
}
return &pr
}
type limitedReader struct {
archiveFile
size int64
offset int64
}
func (l *limitedReader) Read(p []byte) (int, error) {
diff := l.size - l.offset
if diff <= 0 {
return 0, io.EOF
}
if int64(len(p)) > diff {
p = p[0:diff]
}
n, err := l.archiveFile.Read(p)
l.offset += int64(n)
if err == io.EOF {
if l.offset < l.size {
return n, ErrShortFile
}
return n, nil
}
return n, err
}
func (l *limitedReader) ReadByte() (byte, error) {
if l.offset >= l.size {
return 0, io.EOF
}
b, err := l.archiveFile.ReadByte()
if err != nil {
if err == io.EOF {
return 0, ErrShortFile
}
return 0, err
}
l.offset++
return b, nil
}
func (l *limitedReader) writeToN(w io.Writer, n int64) (int64, error) {
diff := l.size - l.offset
m, err := l.archiveFile.writeToN(w, min(diff, n))
l.offset += m
if m < n && err == nil {
err = io.EOF
}
return m, err
}
func (l *limitedReader) WriteTo(w io.Writer) (int64, error) {
diff := l.size - l.offset
n, err := l.archiveFile.writeToN(w, diff)
l.offset += n
return n, err
}
type limitedReadSeeker struct {
limitedReader
sr io.Seeker
}
func (l *limitedReadSeeker) Seek(offset int64, whence int) (int64, error) {
// calculate absolute offset
switch whence {
case io.SeekStart:
case io.SeekCurrent:
offset += l.offset
case io.SeekEnd:
offset += l.size
default:
return 0, fs.ErrInvalid
}
if offset < 0 || offset > l.size {
return 0, fs.ErrInvalid
}
return l.sr.Seek(offset, io.SeekStart)
}
func newLimitedReader(f archiveFile, size int64) archiveFile {
lr := limitedReader{archiveFile: f, size: size}
if sr, ok := f.(io.Seeker); ok {
return &limitedReadSeeker{limitedReader: lr, sr: sr}
}
return &lr
}
type checksumReader struct {
archiveFile
hash hash.Hash
success func()
eofErr error
}
func (cr *checksumReader) eofError() error {
if cr.eofErr != nil {
return cr.eofErr
}
// calculate file checksum
h := cr.currFile()
sum := cr.hash.Sum(nil)
if len(h.hashKey) > 0 {
mac := hmac.New(sha256.New, h.hashKey)
_, _ = mac.Write(sum) // ignore error, should always succeed
sum = mac.Sum(sum[:0])
if len(h.sum) == 4 {
// CRC32
for i, v := range sum[4:] {
sum[i&3] ^= v
}
sum = sum[:4]
}
}
if !bytes.Equal(sum, h.sum) {
cr.eofErr = ErrBadFileChecksum
} else {
cr.eofErr = io.EOF
if cr.success != nil {
cr.success()
}
}
return cr.eofErr
}
func (cr *checksumReader) Read(p []byte) (int, error) {
n, err := cr.archiveFile.Read(p)
if n > 0 {
if n, err = cr.hash.Write(p[:n]); err != nil {
return n, err
}
}
if err != io.EOF {
return n, err
}
return n, cr.eofError()
}
func (cr *checksumReader) ReadByte() (byte, error) {
b, err := cr.archiveFile.ReadByte()
if err != nil {
if err != io.EOF {
return 0, err
}
return 0, cr.eofError()
}
_, err = cr.hash.Write([]byte{b})
if err != nil {
return 0, err
}
return b, err
}
func (cr *checksumReader) writeToN(w io.Writer, n int64) (int64, error) {
panic("checksumReader.writeToN should never be called")
}
func (cr *checksumReader) WriteTo(w io.Writer) (int64, error) {
mw := io.MultiWriter(w, cr.hash)
n, err := cr.archiveFile.WriteTo(mw)
if err == nil || err == io.EOF {
err = cr.eofError()
}
if err != nil && err != io.EOF {
return n, err
}
return n, nil
}
func newChecksumReader(f archiveFile, h hash.Hash, success func()) *checksumReader {
return &checksumReader{archiveFile: f, hash: h, success: success}
}
// Reader provides sequential access to files in a RAR archive.
type Reader struct {
f archiveFile
}
func (r *Reader) Read(p []byte) (int, error) { return r.f.Read(p) }
func (r *Reader) ReadByte() (byte, error) { return r.f.ReadByte() }
func (r *Reader) WriteTo(w io.Writer) (int64, error) { return r.f.WriteTo(w) }
// Next advances to the next file in the archive.
func (r *Reader) Next() (*FileHeader, error) {
blocks, err := r.f.nextFile()
if err != nil {
return nil, err
}
r.f, err = r.f.newArchiveFile(blocks)
if err != nil {
return nil, err
}
h := blocks.firstBlock()
return &h.FileHeader, nil
}
func newReader(v volume, opts *options) Reader {
pr := newPackedFileReader(v, opts)
return Reader{f: pr}
}
// NewReader creates a Reader reading from r.
// NewReader only supports single volume archives.
// Multi-volume archives must use OpenReader.
func NewReader(r io.Reader, opts ...Option) (*Reader, error) {
options := getOptions(opts)
v, err := newVolume(r, options, 0)
if err != nil {
return nil, err
}
rdr := newReader(v, options)
return &rdr, nil
}
// ReadCloser is a Reader that allows closing of the rar archive.
type ReadCloser struct {
Reader
cl io.Closer
vm *volumeManager
}
// Close closes the rar file.
func (rc *ReadCloser) Close() error { return rc.cl.Close() }
// Volumes returns the volume filenames that have been used in decoding the archive
// up to this point. This will include the current open volume if the archive is still
// being processed.
func (rc *ReadCloser) Volumes() []string {
return rc.vm.Files()
}
// OpenReader opens a RAR archive specified by the name and returns a ReadCloser.
func OpenReader(name string, opts ...Option) (*ReadCloser, error) {
options := getOptions(opts)
v, err := openVolume(name, options)
if err != nil {
return nil, err
}
rc := &ReadCloser{vm: v.vm, cl: v}
rc.Reader = newReader(v, options)
return rc, nil
}
// File represents a file in a RAR archive
type File struct {
FileHeader
blocks *fileBlockList
vm *volumeManager
}
// Open returns an io.ReadCloser that provides access to the File's contents.
// Open is not supported on Solid File's as their contents depend on the decoding
// of the preceding files in the archive. Use OpenReader and Next to access Solid file
// contents instead.
func (f *File) Open() (io.ReadCloser, error) {
return f.vm.openArchiveFile(f.blocks)
}
// List returns a list of File's in the RAR archive specified by name.
func List(name string, opts ...Option) ([]*File, error) {
vm, fileBlocks, err := listFileBlocks(name, opts)
if err != nil {
return nil, err
}
var fl []*File
for _, blocks := range fileBlocks {
h := blocks.firstBlock()
f := &File{
FileHeader: h.FileHeader,
blocks: blocks,
vm: vm,
}
fl = append(fl, f)
}
return fl, nil
}