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>
768 lines
17 KiB
Go
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
|
|
}
|