github.com/bodgit/sevenzip@v1.5.1/struct.go (about)

     1  package sevenzip
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"hash"
     7  	"hash/crc32"
     8  	"io"
     9  	"io/fs"
    10  	"path"
    11  	"time"
    12  
    13  	"github.com/bodgit/plumbing"
    14  	"github.com/bodgit/sevenzip/internal/util"
    15  )
    16  
    17  var errAlgorithm = errors.New("sevenzip: unsupported compression algorithm")
    18  
    19  // CryptoReadCloser adds a Password method to decompressors.
    20  type CryptoReadCloser interface {
    21  	Password(string) error
    22  }
    23  
    24  type signatureHeader struct {
    25  	Signature [6]byte
    26  	Major     byte
    27  	Minor     byte
    28  	CRC       uint32
    29  }
    30  
    31  type startHeader struct {
    32  	Offset uint64
    33  	Size   uint64
    34  	CRC    uint32
    35  }
    36  
    37  type packInfo struct {
    38  	position uint64
    39  	streams  uint64
    40  	size     []uint64
    41  	digest   []uint32
    42  }
    43  
    44  type coder struct {
    45  	id         []byte
    46  	in, out    uint64
    47  	properties []byte
    48  }
    49  
    50  type bindPair struct {
    51  	in, out uint64
    52  }
    53  
    54  type folder struct {
    55  	in, out       uint64
    56  	packedStreams uint64
    57  	coder         []*coder
    58  	bindPair      []*bindPair
    59  	size          []uint64
    60  	packed        []uint64
    61  }
    62  
    63  func (f *folder) findInBindPair(i uint64) *bindPair {
    64  	for _, v := range f.bindPair {
    65  		if v.in == i {
    66  			return v
    67  		}
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func (f *folder) findOutBindPair(i uint64) *bindPair {
    74  	for _, v := range f.bindPair {
    75  		if v.out == i {
    76  			return v
    77  		}
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  func (f *folder) coderReader(readers []io.ReadCloser, coder uint64, password string) (io.ReadCloser, error) {
    84  	dcomp := decompressor(f.coder[coder].id)
    85  	if dcomp == nil {
    86  		return nil, errAlgorithm
    87  	}
    88  
    89  	cr, err := dcomp(f.coder[coder].properties, f.size[coder], readers)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	if crc, ok := cr.(CryptoReadCloser); ok {
    95  		if err = crc.Password(password); err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  
   100  	return plumbing.LimitReadCloser(cr, int64(f.size[coder])), nil
   101  }
   102  
   103  type folderReadCloser struct {
   104  	io.ReadCloser
   105  	h    hash.Hash
   106  	wc   *plumbing.WriteCounter
   107  	size int64
   108  }
   109  
   110  func (rc *folderReadCloser) Checksum() []byte {
   111  	return rc.h.Sum(nil)
   112  }
   113  
   114  func (rc *folderReadCloser) Seek(offset int64, whence int) (int64, error) {
   115  	var newo int64
   116  
   117  	switch whence {
   118  	case io.SeekStart:
   119  		newo = offset
   120  	case io.SeekCurrent:
   121  		newo = int64(rc.wc.Count()) + offset
   122  	case io.SeekEnd:
   123  		newo = rc.Size() + offset
   124  	default:
   125  		return 0, errors.New("invalid whence")
   126  	}
   127  
   128  	if newo < 0 {
   129  		return 0, errors.New("negative seek")
   130  	}
   131  
   132  	if newo < int64(rc.wc.Count()) {
   133  		return 0, errors.New("cannot seek backwards")
   134  	}
   135  
   136  	if newo > rc.Size() {
   137  		return 0, errors.New("cannot seek beyond EOF")
   138  	}
   139  
   140  	if _, err := io.CopyN(io.Discard, rc, newo-int64(rc.wc.Count())); err != nil {
   141  		return 0, err
   142  	}
   143  
   144  	return newo, nil
   145  }
   146  
   147  func (rc *folderReadCloser) Size() int64 {
   148  	return rc.size
   149  }
   150  
   151  func newFolderReadCloser(rc io.ReadCloser, size int64) *folderReadCloser {
   152  	nrc := new(folderReadCloser)
   153  	nrc.h = crc32.NewIEEE()
   154  	nrc.wc = new(plumbing.WriteCounter)
   155  	nrc.ReadCloser = plumbing.TeeReadCloser(rc, io.MultiWriter(nrc.h, nrc.wc))
   156  	nrc.size = size
   157  
   158  	return nrc
   159  }
   160  
   161  func (f *folder) unpackSize() uint64 {
   162  	if len(f.size) == 0 {
   163  		return 0
   164  	}
   165  
   166  	for i := len(f.size) - 1; i >= 0; i-- {
   167  		if f.findOutBindPair(uint64(i)) == nil {
   168  			return f.size[i]
   169  		}
   170  	}
   171  
   172  	return f.size[len(f.size)-1]
   173  }
   174  
   175  type unpackInfo struct {
   176  	folder []*folder
   177  	digest []uint32
   178  }
   179  
   180  type subStreamsInfo struct {
   181  	streams []uint64
   182  	size    []uint64
   183  	digest  []uint32
   184  }
   185  
   186  type streamsInfo struct {
   187  	packInfo       *packInfo
   188  	unpackInfo     *unpackInfo
   189  	subStreamsInfo *subStreamsInfo
   190  }
   191  
   192  func (si *streamsInfo) Folders() int {
   193  	if si != nil && si.unpackInfo != nil {
   194  		return len(si.unpackInfo.folder)
   195  	}
   196  
   197  	return 0
   198  }
   199  
   200  func (si *streamsInfo) FileFolderAndSize(file int) (int, uint64) {
   201  	total := uint64(0)
   202  
   203  	var (
   204  		folder  int
   205  		streams uint64 = 1
   206  	)
   207  
   208  	if si.subStreamsInfo != nil {
   209  		for folder, streams = range si.subStreamsInfo.streams {
   210  			total += streams
   211  			if uint64(file) < total {
   212  				break
   213  			}
   214  		}
   215  	}
   216  
   217  	if streams == 1 {
   218  		return folder, si.unpackInfo.folder[folder].size[len(si.unpackInfo.folder[folder].coder)-1]
   219  	}
   220  
   221  	return folder, si.subStreamsInfo.size[file]
   222  }
   223  
   224  func (si *streamsInfo) folderOffset(folder int) int64 {
   225  	offset := uint64(0)
   226  
   227  	for i, k := 0, uint64(0); i < folder; i++ {
   228  		for j := k; j < k+si.unpackInfo.folder[i].packedStreams; j++ {
   229  			offset += si.packInfo.size[j]
   230  		}
   231  
   232  		k += si.unpackInfo.folder[i].packedStreams
   233  	}
   234  
   235  	return int64(si.packInfo.position + offset)
   236  }
   237  
   238  //nolint:cyclop,funlen
   239  func (si *streamsInfo) FolderReader(r io.ReaderAt, folder int, password string) (*folderReadCloser, uint32, error) {
   240  	f := si.unpackInfo.folder[folder]
   241  	in := make([]io.ReadCloser, f.in)
   242  	out := make([]io.ReadCloser, f.out)
   243  
   244  	packedOffset := 0
   245  	for i := 0; i < folder; i++ {
   246  		packedOffset += len(si.unpackInfo.folder[i].packed)
   247  	}
   248  
   249  	offset := int64(0)
   250  
   251  	for i, input := range f.packed {
   252  		size := int64(si.packInfo.size[packedOffset+i])
   253  		in[input] = util.NopCloser(bufio.NewReader(io.NewSectionReader(r, si.folderOffset(folder)+offset, size)))
   254  		offset += size
   255  	}
   256  
   257  	input, output := uint64(0), uint64(0)
   258  
   259  	for i, c := range f.coder {
   260  		if c.out != 1 {
   261  			return nil, 0, errors.New("more than one output stream")
   262  		}
   263  
   264  		for j := input; j < input+c.in; j++ {
   265  			if in[j] != nil {
   266  				continue
   267  			}
   268  
   269  			bp := f.findInBindPair(j)
   270  			if bp == nil || out[bp.out] == nil {
   271  				return nil, 0, errors.New("cannot find bound stream")
   272  			}
   273  
   274  			in[j] = out[bp.out]
   275  		}
   276  
   277  		var err error
   278  
   279  		out[output], err = f.coderReader(in[input:input+c.in], uint64(i), password)
   280  		if err != nil {
   281  			return nil, 0, err
   282  		}
   283  
   284  		input += c.in
   285  		output += c.out
   286  	}
   287  
   288  	unbound := make([]uint64, 0, f.out)
   289  
   290  	for i := uint64(0); i < f.out; i++ {
   291  		if bp := f.findOutBindPair(i); bp == nil {
   292  			unbound = append(unbound, i)
   293  		}
   294  	}
   295  
   296  	if len(unbound) != 1 || out[unbound[0]] == nil {
   297  		return nil, 0, errors.New("expecting one unbound output stream")
   298  	}
   299  
   300  	fr := newFolderReadCloser(out[unbound[0]], int64(f.unpackSize()))
   301  
   302  	if si.unpackInfo.digest != nil {
   303  		return fr, si.unpackInfo.digest[folder], nil
   304  	}
   305  
   306  	return fr, 0, nil
   307  }
   308  
   309  type filesInfo struct {
   310  	file []FileHeader
   311  }
   312  
   313  type header struct {
   314  	streamsInfo *streamsInfo
   315  	filesInfo   *filesInfo
   316  }
   317  
   318  // FileHeader describes a file within a 7-zip file.
   319  type FileHeader struct {
   320  	Name             string
   321  	Created          time.Time
   322  	Accessed         time.Time
   323  	Modified         time.Time
   324  	Attributes       uint32
   325  	CRC32            uint32
   326  	UncompressedSize uint64
   327  
   328  	// Stream is an opaque identifier representing the compressed stream
   329  	// that contains the file. Any File with the same value can be assumed
   330  	// to be stored within the same stream.
   331  	Stream int
   332  
   333  	isEmptyStream bool
   334  	isEmptyFile   bool
   335  }
   336  
   337  // FileInfo returns an fs.FileInfo for the FileHeader.
   338  func (h *FileHeader) FileInfo() fs.FileInfo {
   339  	return headerFileInfo{h}
   340  }
   341  
   342  type headerFileInfo struct {
   343  	fh *FileHeader
   344  }
   345  
   346  func (fi headerFileInfo) Name() string       { return path.Base(fi.fh.Name) }
   347  func (fi headerFileInfo) Size() int64        { return int64(fi.fh.UncompressedSize) }
   348  func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
   349  func (fi headerFileInfo) ModTime() time.Time { return fi.fh.Modified.UTC() }
   350  func (fi headerFileInfo) Mode() fs.FileMode  { return fi.fh.Mode() }
   351  func (fi headerFileInfo) Type() fs.FileMode  { return fi.fh.Mode().Type() }
   352  func (fi headerFileInfo) Sys() interface{}   { return fi.fh }
   353  
   354  func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil }
   355  
   356  const (
   357  	// Unix constants. The specification doesn't mention them,
   358  	// but these seem to be the values agreed on by tools.
   359  	sIFMT   = 0xf000
   360  	sIFSOCK = 0xc000
   361  	sIFLNK  = 0xa000
   362  	sIFREG  = 0x8000
   363  	sIFBLK  = 0x6000
   364  	sIFDIR  = 0x4000
   365  	sIFCHR  = 0x2000
   366  	sIFIFO  = 0x1000
   367  	sISUID  = 0x800
   368  	sISGID  = 0x400
   369  	sISVTX  = 0x200
   370  
   371  	msdosDir      = 0x10
   372  	msdosReadOnly = 0x01
   373  )
   374  
   375  // Mode returns the permission and mode bits for the FileHeader.
   376  func (h *FileHeader) Mode() (mode fs.FileMode) {
   377  	// Prefer the POSIX attributes if they're present
   378  	if h.Attributes&0xf0000000 != 0 {
   379  		mode = unixModeToFileMode(h.Attributes >> 16)
   380  	} else {
   381  		mode = msdosModeToFileMode(h.Attributes)
   382  	}
   383  
   384  	return
   385  }
   386  
   387  func msdosModeToFileMode(m uint32) (mode fs.FileMode) {
   388  	if m&msdosDir != 0 {
   389  		mode = fs.ModeDir | 0o777
   390  	} else {
   391  		mode = 0o666
   392  	}
   393  
   394  	if m&msdosReadOnly != 0 {
   395  		mode &^= 0o222
   396  	}
   397  
   398  	return mode
   399  }
   400  
   401  //nolint:cyclop
   402  func unixModeToFileMode(m uint32) fs.FileMode {
   403  	mode := fs.FileMode(m & 0o777)
   404  
   405  	switch m & sIFMT {
   406  	case sIFBLK:
   407  		mode |= fs.ModeDevice
   408  	case sIFCHR:
   409  		mode |= fs.ModeDevice | fs.ModeCharDevice
   410  	case sIFDIR:
   411  		mode |= fs.ModeDir
   412  	case sIFIFO:
   413  		mode |= fs.ModeNamedPipe
   414  	case sIFLNK:
   415  		mode |= fs.ModeSymlink
   416  	case sIFREG:
   417  		// nothing to do
   418  	case sIFSOCK:
   419  		mode |= fs.ModeSocket
   420  	}
   421  
   422  	if m&sISGID != 0 {
   423  		mode |= fs.ModeSetgid
   424  	}
   425  
   426  	if m&sISUID != 0 {
   427  		mode |= fs.ModeSetuid
   428  	}
   429  
   430  	if m&sISVTX != 0 {
   431  		mode |= fs.ModeSticky
   432  	}
   433  
   434  	return mode
   435  }