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>
468 lines
9.7 KiB
Go
468 lines
9.7 KiB
Go
package sevenzip
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"hash/crc32"
|
|
"io"
|
|
iofs "io/fs"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/bodgit/plumbing"
|
|
"github.com/bodgit/sevenzip/internal/util"
|
|
)
|
|
|
|
var (
|
|
errAlgorithm = errors.New("sevenzip: unsupported compression algorithm")
|
|
errInvalidWhence = errors.New("invalid whence")
|
|
errNegativeSeek = errors.New("negative seek")
|
|
errSeekBackwards = errors.New("cannot seek backwards")
|
|
errSeekEOF = errors.New("cannot seek beyond EOF")
|
|
errMultipleOutputStreams = errors.New("more than one output stream")
|
|
errNoBoundStream = errors.New("cannot find bound stream")
|
|
errNoUnboundStream = errors.New("expecting one unbound output stream")
|
|
)
|
|
|
|
// CryptoReadCloser adds a Password method to decompressors.
|
|
type CryptoReadCloser interface {
|
|
Password(password string) error
|
|
}
|
|
|
|
type signatureHeader struct {
|
|
Signature [6]byte
|
|
Major byte
|
|
Minor byte
|
|
CRC uint32
|
|
}
|
|
|
|
type startHeader struct {
|
|
Offset uint64
|
|
Size uint64
|
|
CRC uint32
|
|
}
|
|
|
|
type packInfo struct {
|
|
position uint64
|
|
streams uint64
|
|
size []uint64
|
|
digest []uint32
|
|
}
|
|
|
|
type coder struct {
|
|
id []byte
|
|
in, out uint64
|
|
properties []byte
|
|
}
|
|
|
|
type bindPair struct {
|
|
in, out uint64
|
|
}
|
|
|
|
type folder struct {
|
|
in, out uint64
|
|
packedStreams uint64
|
|
coder []*coder
|
|
bindPair []*bindPair
|
|
size []uint64
|
|
packed []uint64
|
|
}
|
|
|
|
func (f *folder) findInBindPair(i uint64) *bindPair {
|
|
for _, v := range f.bindPair {
|
|
if v.in == i {
|
|
return v
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *folder) findOutBindPair(i uint64) *bindPair {
|
|
for _, v := range f.bindPair {
|
|
if v.out == i {
|
|
return v
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *folder) coderReader(readers []io.ReadCloser, coder uint64, password string) (io.ReadCloser, bool, error) {
|
|
dcomp := decompressor(f.coder[coder].id)
|
|
if dcomp == nil {
|
|
return nil, false, errAlgorithm
|
|
}
|
|
|
|
cr, err := dcomp(f.coder[coder].properties, f.size[coder], readers)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
crc, ok := cr.(CryptoReadCloser)
|
|
if ok {
|
|
if err = crc.Password(password); err != nil {
|
|
return nil, true, fmt.Errorf("sevenzip: error setting password: %w", err)
|
|
}
|
|
}
|
|
|
|
return plumbing.LimitReadCloser(cr, int64(f.size[coder])), ok, nil //nolint:gosec
|
|
}
|
|
|
|
type folderReadCloser struct {
|
|
io.ReadCloser
|
|
h hash.Hash
|
|
wc *plumbing.WriteCounter
|
|
size int64
|
|
hasEncryption bool
|
|
}
|
|
|
|
func (rc *folderReadCloser) Checksum() []byte {
|
|
return rc.h.Sum(nil)
|
|
}
|
|
|
|
func (rc *folderReadCloser) Seek(offset int64, whence int) (int64, error) {
|
|
var newo int64
|
|
|
|
switch whence {
|
|
case io.SeekStart:
|
|
newo = offset
|
|
case io.SeekCurrent:
|
|
newo = int64(rc.wc.Count()) + offset //nolint:gosec
|
|
case io.SeekEnd:
|
|
newo = rc.Size() + offset
|
|
default:
|
|
return 0, errInvalidWhence
|
|
}
|
|
|
|
if newo < 0 {
|
|
return 0, errNegativeSeek
|
|
}
|
|
|
|
if uint64(newo) < rc.wc.Count() {
|
|
return 0, errSeekBackwards
|
|
}
|
|
|
|
if newo > rc.Size() {
|
|
return 0, errSeekEOF
|
|
}
|
|
|
|
if _, err := io.CopyN(io.Discard, rc, newo-int64(rc.wc.Count())); err != nil { //nolint:gosec
|
|
return 0, fmt.Errorf("sevenzip: error seeking: %w", err)
|
|
}
|
|
|
|
return newo, nil
|
|
}
|
|
|
|
func (rc *folderReadCloser) Size() int64 {
|
|
return rc.size
|
|
}
|
|
|
|
func newFolderReadCloser(rc io.ReadCloser, size int64, hasEncryption bool) *folderReadCloser {
|
|
nrc := new(folderReadCloser)
|
|
nrc.h = crc32.NewIEEE()
|
|
nrc.wc = new(plumbing.WriteCounter)
|
|
nrc.ReadCloser = plumbing.TeeReadCloser(rc, io.MultiWriter(nrc.h, nrc.wc))
|
|
nrc.size = size
|
|
nrc.hasEncryption = hasEncryption
|
|
|
|
return nrc
|
|
}
|
|
|
|
func (f *folder) unpackSize() uint64 {
|
|
if len(f.size) == 0 {
|
|
return 0
|
|
}
|
|
|
|
for i := len(f.size) - 1; i >= 0; i-- {
|
|
if f.findOutBindPair(uint64(i)) == nil {
|
|
return f.size[i]
|
|
}
|
|
}
|
|
|
|
return f.size[len(f.size)-1]
|
|
}
|
|
|
|
type unpackInfo struct {
|
|
folder []*folder
|
|
digest []uint32
|
|
}
|
|
|
|
type subStreamsInfo struct {
|
|
streams []uint64
|
|
size []uint64
|
|
digest []uint32
|
|
}
|
|
|
|
type streamsInfo struct {
|
|
packInfo *packInfo
|
|
unpackInfo *unpackInfo
|
|
subStreamsInfo *subStreamsInfo
|
|
}
|
|
|
|
func (si *streamsInfo) Folders() int {
|
|
if si != nil && si.unpackInfo != nil {
|
|
return len(si.unpackInfo.folder)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (si *streamsInfo) FileFolderAndSize(file int) (int, uint64, uint32) {
|
|
var (
|
|
folder int
|
|
streams uint64 = 1
|
|
crc uint32
|
|
)
|
|
|
|
if si.subStreamsInfo != nil {
|
|
total := uint64(0)
|
|
|
|
for folder, streams = range si.subStreamsInfo.streams {
|
|
total += streams
|
|
if uint64(file) < total { //nolint:gosec
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(si.subStreamsInfo.digest) > 0 {
|
|
crc = si.subStreamsInfo.digest[file]
|
|
}
|
|
}
|
|
|
|
if streams == 1 {
|
|
if len(si.unpackInfo.digest) > 0 {
|
|
crc = si.unpackInfo.digest[folder]
|
|
}
|
|
|
|
return folder, si.unpackInfo.folder[folder].size[len(si.unpackInfo.folder[folder].coder)-1], crc
|
|
}
|
|
|
|
return folder, si.subStreamsInfo.size[file], crc
|
|
}
|
|
|
|
func (si *streamsInfo) folderOffset(folder int) int64 {
|
|
offset := uint64(0)
|
|
|
|
for i, k := 0, uint64(0); i < folder; i++ {
|
|
for j := k; j < k+si.unpackInfo.folder[i].packedStreams; j++ {
|
|
offset += si.packInfo.size[j]
|
|
}
|
|
|
|
k += si.unpackInfo.folder[i].packedStreams
|
|
}
|
|
|
|
return int64(si.packInfo.position + offset) //nolint:gosec
|
|
}
|
|
|
|
//nolint:cyclop,funlen,lll
|
|
func (si *streamsInfo) FolderReader(r io.ReaderAt, folder int, password string) (*folderReadCloser, uint32, bool, error) {
|
|
f := si.unpackInfo.folder[folder]
|
|
in := make([]io.ReadCloser, f.in)
|
|
out := make([]io.ReadCloser, f.out)
|
|
|
|
packedOffset := 0
|
|
for i := 0; i < folder; i++ {
|
|
packedOffset += len(si.unpackInfo.folder[i].packed)
|
|
}
|
|
|
|
offset := int64(0)
|
|
|
|
for i, input := range f.packed {
|
|
size := int64(si.packInfo.size[packedOffset+i]) //nolint:gosec
|
|
in[input] = util.NopCloser(bufio.NewReader(io.NewSectionReader(r, si.folderOffset(folder)+offset, size)))
|
|
offset += size
|
|
}
|
|
|
|
var (
|
|
hasEncryption bool
|
|
input, output uint64
|
|
)
|
|
|
|
for i, c := range f.coder {
|
|
if c.out != 1 {
|
|
return nil, 0, hasEncryption, errMultipleOutputStreams
|
|
}
|
|
|
|
for j := input; j < input+c.in; j++ {
|
|
if in[j] != nil {
|
|
continue
|
|
}
|
|
|
|
bp := f.findInBindPair(j)
|
|
if bp == nil || out[bp.out] == nil {
|
|
return nil, 0, hasEncryption, errNoBoundStream
|
|
}
|
|
|
|
in[j] = out[bp.out]
|
|
}
|
|
|
|
var (
|
|
isEncrypted bool
|
|
err error
|
|
)
|
|
|
|
out[output], isEncrypted, err = f.coderReader(in[input:input+c.in], uint64(i), password) //nolint:gosec
|
|
if err != nil {
|
|
return nil, 0, hasEncryption, err
|
|
}
|
|
|
|
if isEncrypted {
|
|
hasEncryption = true
|
|
}
|
|
|
|
input += c.in
|
|
output += c.out
|
|
}
|
|
|
|
unbound := make([]uint64, 0, f.out)
|
|
|
|
for i := uint64(0); i < f.out; i++ {
|
|
if bp := f.findOutBindPair(i); bp == nil {
|
|
unbound = append(unbound, i)
|
|
}
|
|
}
|
|
|
|
if len(unbound) != 1 || out[unbound[0]] == nil {
|
|
return nil, 0, hasEncryption, errNoUnboundStream
|
|
}
|
|
|
|
fr := newFolderReadCloser(out[unbound[0]], int64(f.unpackSize()), hasEncryption) //nolint:gosec
|
|
|
|
if si.unpackInfo.digest != nil {
|
|
return fr, si.unpackInfo.digest[folder], hasEncryption, nil
|
|
}
|
|
|
|
return fr, 0, hasEncryption, nil
|
|
}
|
|
|
|
type filesInfo struct {
|
|
file []FileHeader
|
|
}
|
|
|
|
type header struct {
|
|
streamsInfo *streamsInfo
|
|
filesInfo *filesInfo
|
|
}
|
|
|
|
// FileHeader describes a file within a 7-zip file.
|
|
type FileHeader struct {
|
|
Name string
|
|
Created time.Time
|
|
Accessed time.Time
|
|
Modified time.Time
|
|
Attributes uint32
|
|
CRC32 uint32
|
|
UncompressedSize uint64
|
|
|
|
// Stream is an opaque identifier representing the compressed stream
|
|
// that contains the file. Any File with the same value can be assumed
|
|
// to be stored within the same stream.
|
|
Stream int
|
|
|
|
isEmptyStream bool
|
|
isEmptyFile bool
|
|
}
|
|
|
|
// FileInfo returns an [fs.FileInfo] for the FileHeader.
|
|
func (h *FileHeader) FileInfo() iofs.FileInfo {
|
|
return headerFileInfo{h}
|
|
}
|
|
|
|
type headerFileInfo struct {
|
|
fh *FileHeader
|
|
}
|
|
|
|
func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }
|
|
func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) } //nolint:gosec
|
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
|
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.Modified.UTC() }
|
|
func (fi headerFileInfo) Mode() iofs.FileMode { return fi.fh.Mode() }
|
|
func (fi headerFileInfo) Type() iofs.FileMode { return fi.fh.Mode().Type() }
|
|
func (fi headerFileInfo) Sys() interface{} { return fi.fh }
|
|
|
|
func (fi headerFileInfo) Info() (iofs.FileInfo, error) { return fi, nil }
|
|
|
|
const (
|
|
// Unix constants. The specification doesn't mention them,
|
|
// but these seem to be the values agreed on by tools.
|
|
sIFMT = 0xf000
|
|
sIFSOCK = 0xc000
|
|
sIFLNK = 0xa000
|
|
sIFREG = 0x8000
|
|
sIFBLK = 0x6000
|
|
sIFDIR = 0x4000
|
|
sIFCHR = 0x2000
|
|
sIFIFO = 0x1000
|
|
sISUID = 0x800
|
|
sISGID = 0x400
|
|
sISVTX = 0x200
|
|
|
|
msdosDir = 0x10
|
|
msdosReadOnly = 0x01
|
|
)
|
|
|
|
// Mode returns the permission and mode bits for the FileHeader.
|
|
func (h *FileHeader) Mode() (mode iofs.FileMode) {
|
|
// Prefer the POSIX attributes if they're present
|
|
if h.Attributes&0xf0000000 != 0 {
|
|
mode = unixModeToFileMode(h.Attributes >> 16)
|
|
} else {
|
|
mode = msdosModeToFileMode(h.Attributes)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func msdosModeToFileMode(m uint32) (mode iofs.FileMode) {
|
|
if m&msdosDir != 0 {
|
|
mode = iofs.ModeDir | 0o777
|
|
} else {
|
|
mode = 0o666
|
|
}
|
|
|
|
if m&msdosReadOnly != 0 {
|
|
mode &^= 0o222
|
|
}
|
|
|
|
return mode
|
|
}
|
|
|
|
//nolint:cyclop
|
|
func unixModeToFileMode(m uint32) iofs.FileMode {
|
|
mode := iofs.FileMode(m & 0o777)
|
|
|
|
switch m & sIFMT {
|
|
case sIFBLK:
|
|
mode |= iofs.ModeDevice
|
|
case sIFCHR:
|
|
mode |= iofs.ModeDevice | iofs.ModeCharDevice
|
|
case sIFDIR:
|
|
mode |= iofs.ModeDir
|
|
case sIFIFO:
|
|
mode |= iofs.ModeNamedPipe
|
|
case sIFLNK:
|
|
mode |= iofs.ModeSymlink
|
|
case sIFREG:
|
|
// nothing to do
|
|
case sIFSOCK:
|
|
mode |= iofs.ModeSocket
|
|
}
|
|
|
|
if m&sISGID != 0 {
|
|
mode |= iofs.ModeSetgid
|
|
}
|
|
|
|
if m&sISUID != 0 {
|
|
mode |= iofs.ModeSetuid
|
|
}
|
|
|
|
if m&sISVTX != 0 {
|
|
mode |= iofs.ModeSticky
|
|
}
|
|
|
|
return mode
|
|
}
|