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 }