• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bodgit / sevenzip / 12000514469

24 Nov 2024 11:28PM UTC coverage: 74.182% (+0.8%) from 73.339%
12000514469

push

github

web-flow
test: Improve test coverage (#289)

* test: Test `NewReader()`

* test: Test `fileReadCloser.Seek()`

* refactor: Change `io/fs` import

* refactor: Use afero for filesystem interactions

* test: Test `openReader()`

41 of 93 new or added lines in 3 files covered. (44.09%)

22 existing lines in 1 file now uncovered.

1724 of 2324 relevant lines covered (74.18%)

1.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.63
/struct.go
1
package sevenzip
2

3
import (
4
        "bufio"
5
        "errors"
6
        "fmt"
7
        "hash"
8
        "hash/crc32"
9
        "io"
10
        iofs "io/fs"
11
        "path"
12
        "time"
13

14
        "github.com/bodgit/plumbing"
15
        "github.com/bodgit/sevenzip/internal/util"
16
)
17

18
var (
19
        errAlgorithm             = errors.New("sevenzip: unsupported compression algorithm")
20
        errInvalidWhence         = errors.New("invalid whence")
21
        errNegativeSeek          = errors.New("negative seek")
22
        errSeekBackwards         = errors.New("cannot seek backwards")
23
        errSeekEOF               = errors.New("cannot seek beyond EOF")
24
        errMultipleOutputStreams = errors.New("more than one output stream")
25
        errNoBoundStream         = errors.New("cannot find bound stream")
26
        errNoUnboundStream       = errors.New("expecting one unbound output stream")
27
)
28

29
// CryptoReadCloser adds a Password method to decompressors.
30
type CryptoReadCloser interface {
31
        Password(password string) error
32
}
33

34
type signatureHeader struct {
35
        Signature [6]byte
36
        Major     byte
37
        Minor     byte
38
        CRC       uint32
39
}
40

41
type startHeader struct {
42
        Offset uint64
43
        Size   uint64
44
        CRC    uint32
45
}
46

47
type packInfo struct {
48
        position uint64
49
        streams  uint64
50
        size     []uint64
51
        digest   []uint32
52
}
53

54
type coder struct {
55
        id         []byte
56
        in, out    uint64
57
        properties []byte
58
}
59

60
type bindPair struct {
61
        in, out uint64
62
}
63

64
type folder struct {
65
        in, out       uint64
66
        packedStreams uint64
67
        coder         []*coder
68
        bindPair      []*bindPair
69
        size          []uint64
70
        packed        []uint64
71
}
72

73
func (f *folder) findInBindPair(i uint64) *bindPair {
2✔
74
        for _, v := range f.bindPair {
4✔
75
                if v.in == i {
4✔
76
                        return v
2✔
77
                }
2✔
78
        }
79

80
        return nil
2✔
81
}
82

83
func (f *folder) findOutBindPair(i uint64) *bindPair {
2✔
84
        for _, v := range f.bindPair {
4✔
85
                if v.out == i {
4✔
86
                        return v
2✔
87
                }
2✔
88
        }
89

90
        return nil
2✔
91
}
92

93
func (f *folder) coderReader(readers []io.ReadCloser, coder uint64, password string) (io.ReadCloser, bool, error) {
2✔
94
        dcomp := decompressor(f.coder[coder].id)
2✔
95
        if dcomp == nil {
2✔
96
                return nil, false, errAlgorithm
×
97
        }
×
98

99
        cr, err := dcomp(f.coder[coder].properties, f.size[coder], readers)
2✔
100
        if err != nil {
2✔
101
                return nil, false, err
×
102
        }
×
103

104
        crc, ok := cr.(CryptoReadCloser)
2✔
105
        if ok {
4✔
106
                if err = crc.Password(password); err != nil {
2✔
107
                        return nil, true, fmt.Errorf("sevenzip: error setting password: %w", err)
×
108
                }
×
109
        }
110

111
        return plumbing.LimitReadCloser(cr, int64(f.size[coder])), ok, nil //nolint:gosec
2✔
112
}
113

114
type folderReadCloser struct {
115
        io.ReadCloser
116
        h             hash.Hash
117
        wc            *plumbing.WriteCounter
118
        size          int64
119
        hasEncryption bool
120
}
121

122
func (rc *folderReadCloser) Checksum() []byte {
2✔
123
        return rc.h.Sum(nil)
2✔
124
}
2✔
125

126
func (rc *folderReadCloser) Seek(offset int64, whence int) (int64, error) {
2✔
127
        var newo int64
2✔
128

2✔
129
        switch whence {
2✔
130
        case io.SeekStart:
2✔
131
                newo = offset
2✔
132
        case io.SeekCurrent:
2✔
133
                newo = int64(rc.wc.Count()) + offset //nolint:gosec
2✔
134
        case io.SeekEnd:
2✔
135
                newo = rc.Size() + offset
2✔
136
        default:
2✔
137
                return 0, errInvalidWhence
2✔
138
        }
139

140
        if newo < 0 {
4✔
141
                return 0, errNegativeSeek
2✔
142
        }
2✔
143

144
        if uint64(newo) < rc.wc.Count() {
4✔
145
                return 0, errSeekBackwards
2✔
146
        }
2✔
147

148
        if newo > rc.Size() {
4✔
149
                return 0, errSeekEOF
2✔
150
        }
2✔
151

152
        if _, err := io.CopyN(io.Discard, rc, newo-int64(rc.wc.Count())); err != nil { //nolint:gosec
2✔
153
                return 0, fmt.Errorf("sevenzip: error seeking: %w", err)
×
154
        }
×
155

156
        return newo, nil
2✔
157
}
158

159
func (rc *folderReadCloser) Size() int64 {
2✔
160
        return rc.size
2✔
161
}
2✔
162

163
func newFolderReadCloser(rc io.ReadCloser, size int64, hasEncryption bool) *folderReadCloser {
2✔
164
        nrc := new(folderReadCloser)
2✔
165
        nrc.h = crc32.NewIEEE()
2✔
166
        nrc.wc = new(plumbing.WriteCounter)
2✔
167
        nrc.ReadCloser = plumbing.TeeReadCloser(rc, io.MultiWriter(nrc.h, nrc.wc))
2✔
168
        nrc.size = size
2✔
169
        nrc.hasEncryption = hasEncryption
2✔
170

2✔
171
        return nrc
2✔
172
}
2✔
173

174
func (f *folder) unpackSize() uint64 {
2✔
175
        if len(f.size) == 0 {
2✔
176
                return 0
×
177
        }
×
178

179
        for i := len(f.size) - 1; i >= 0; i-- {
4✔
180
                if f.findOutBindPair(uint64(i)) == nil {
4✔
181
                        return f.size[i]
2✔
182
                }
2✔
183
        }
184

185
        return f.size[len(f.size)-1]
×
186
}
187

188
type unpackInfo struct {
189
        folder []*folder
190
        digest []uint32
191
}
192

193
type subStreamsInfo struct {
194
        streams []uint64
195
        size    []uint64
196
        digest  []uint32
197
}
198

199
type streamsInfo struct {
200
        packInfo       *packInfo
201
        unpackInfo     *unpackInfo
202
        subStreamsInfo *subStreamsInfo
203
}
204

205
func (si *streamsInfo) Folders() int {
2✔
206
        if si != nil && si.unpackInfo != nil {
4✔
207
                return len(si.unpackInfo.folder)
2✔
208
        }
2✔
209

210
        return 0
2✔
211
}
212

213
func (si *streamsInfo) FileFolderAndSize(file int) (int, uint64) {
2✔
214
        total := uint64(0)
2✔
215

2✔
216
        var (
2✔
217
                folder  int
2✔
218
                streams uint64 = 1
2✔
219
        )
2✔
220

2✔
221
        if si.subStreamsInfo != nil {
4✔
222
                for folder, streams = range si.subStreamsInfo.streams {
4✔
223
                        total += streams
2✔
224
                        if uint64(file) < total { //nolint:gosec
4✔
225
                                break
2✔
226
                        }
227
                }
228
        }
229

230
        if streams == 1 {
4✔
231
                return folder, si.unpackInfo.folder[folder].size[len(si.unpackInfo.folder[folder].coder)-1]
2✔
232
        }
2✔
233

234
        return folder, si.subStreamsInfo.size[file]
2✔
235
}
236

237
func (si *streamsInfo) folderOffset(folder int) int64 {
2✔
238
        offset := uint64(0)
2✔
239

2✔
240
        for i, k := 0, uint64(0); i < folder; i++ {
4✔
241
                for j := k; j < k+si.unpackInfo.folder[i].packedStreams; j++ {
4✔
242
                        offset += si.packInfo.size[j]
2✔
243
                }
2✔
244

245
                k += si.unpackInfo.folder[i].packedStreams
2✔
246
        }
247

248
        return int64(si.packInfo.position + offset) //nolint:gosec
2✔
249
}
250

251
//nolint:cyclop,funlen,lll
252
func (si *streamsInfo) FolderReader(r io.ReaderAt, folder int, password string) (*folderReadCloser, uint32, bool, error) {
2✔
253
        f := si.unpackInfo.folder[folder]
2✔
254
        in := make([]io.ReadCloser, f.in)
2✔
255
        out := make([]io.ReadCloser, f.out)
2✔
256

2✔
257
        packedOffset := 0
2✔
258
        for i := 0; i < folder; i++ {
4✔
259
                packedOffset += len(si.unpackInfo.folder[i].packed)
2✔
260
        }
2✔
261

262
        offset := int64(0)
2✔
263

2✔
264
        for i, input := range f.packed {
4✔
265
                size := int64(si.packInfo.size[packedOffset+i]) //nolint:gosec
2✔
266
                in[input] = util.NopCloser(bufio.NewReader(io.NewSectionReader(r, si.folderOffset(folder)+offset, size)))
2✔
267
                offset += size
2✔
268
        }
2✔
269

270
        var (
2✔
271
                hasEncryption bool
2✔
272
                input, output uint64
2✔
273
        )
2✔
274

2✔
275
        for i, c := range f.coder {
4✔
276
                if c.out != 1 {
2✔
277
                        return nil, 0, hasEncryption, errMultipleOutputStreams
×
278
                }
×
279

280
                for j := input; j < input+c.in; j++ {
4✔
281
                        if in[j] != nil {
4✔
282
                                continue
2✔
283
                        }
284

285
                        bp := f.findInBindPair(j)
2✔
286
                        if bp == nil || out[bp.out] == nil {
2✔
287
                                return nil, 0, hasEncryption, errNoBoundStream
×
288
                        }
×
289

290
                        in[j] = out[bp.out]
2✔
291
                }
292

293
                var (
2✔
294
                        isEncrypted bool
2✔
295
                        err         error
2✔
296
                )
2✔
297

2✔
298
                out[output], isEncrypted, err = f.coderReader(in[input:input+c.in], uint64(i), password) //nolint:gosec
2✔
299
                if err != nil {
2✔
300
                        return nil, 0, hasEncryption, err
×
301
                }
×
302

303
                if isEncrypted {
4✔
304
                        hasEncryption = true
2✔
305
                }
2✔
306

307
                input += c.in
2✔
308
                output += c.out
2✔
309
        }
310

311
        unbound := make([]uint64, 0, f.out)
2✔
312

2✔
313
        for i := uint64(0); i < f.out; i++ {
4✔
314
                if bp := f.findOutBindPair(i); bp == nil {
4✔
315
                        unbound = append(unbound, i)
2✔
316
                }
2✔
317
        }
318

319
        if len(unbound) != 1 || out[unbound[0]] == nil {
2✔
320
                return nil, 0, hasEncryption, errNoUnboundStream
×
321
        }
×
322

323
        fr := newFolderReadCloser(out[unbound[0]], int64(f.unpackSize()), hasEncryption) //nolint:gosec
2✔
324

2✔
325
        if si.unpackInfo.digest != nil {
4✔
326
                return fr, si.unpackInfo.digest[folder], hasEncryption, nil
2✔
327
        }
2✔
328

329
        return fr, 0, hasEncryption, nil
2✔
330
}
331

332
type filesInfo struct {
333
        file []FileHeader
334
}
335

336
type header struct {
337
        streamsInfo *streamsInfo
338
        filesInfo   *filesInfo
339
}
340

341
// FileHeader describes a file within a 7-zip file.
342
type FileHeader struct {
343
        Name             string
344
        Created          time.Time
345
        Accessed         time.Time
346
        Modified         time.Time
347
        Attributes       uint32
348
        CRC32            uint32
349
        UncompressedSize uint64
350

351
        // Stream is an opaque identifier representing the compressed stream
352
        // that contains the file. Any File with the same value can be assumed
353
        // to be stored within the same stream.
354
        Stream int
355

356
        isEmptyStream bool
357
        isEmptyFile   bool
358
}
359

360
// FileInfo returns an [fs.FileInfo] for the FileHeader.
361
func (h *FileHeader) FileInfo() iofs.FileInfo {
2✔
362
        return headerFileInfo{h}
2✔
363
}
2✔
364

365
type headerFileInfo struct {
366
        fh *FileHeader
367
}
368

369
func (fi headerFileInfo) Name() string        { return path.Base(fi.fh.Name) }
2✔
370
func (fi headerFileInfo) Size() int64         { return int64(fi.fh.UncompressedSize) } //nolint:gosec
2✔
371
func (fi headerFileInfo) IsDir() bool         { return fi.Mode().IsDir() }
2✔
372
func (fi headerFileInfo) ModTime() time.Time  { return fi.fh.Modified.UTC() }
2✔
373
func (fi headerFileInfo) Mode() iofs.FileMode { return fi.fh.Mode() }
2✔
374
func (fi headerFileInfo) Type() iofs.FileMode { return fi.fh.Mode().Type() }
2✔
NEW
375
func (fi headerFileInfo) Sys() interface{}    { return fi.fh }
×
376

377
func (fi headerFileInfo) Info() (iofs.FileInfo, error) { return fi, nil }
2✔
378

379
const (
380
        // Unix constants. The specification doesn't mention them,
381
        // but these seem to be the values agreed on by tools.
382
        sIFMT   = 0xf000
383
        sIFSOCK = 0xc000
384
        sIFLNK  = 0xa000
385
        sIFREG  = 0x8000
386
        sIFBLK  = 0x6000
387
        sIFDIR  = 0x4000
388
        sIFCHR  = 0x2000
389
        sIFIFO  = 0x1000
390
        sISUID  = 0x800
391
        sISGID  = 0x400
392
        sISVTX  = 0x200
393

394
        msdosDir      = 0x10
395
        msdosReadOnly = 0x01
396
)
397

398
// Mode returns the permission and mode bits for the FileHeader.
399
func (h *FileHeader) Mode() (mode iofs.FileMode) {
2✔
400
        // Prefer the POSIX attributes if they're present
2✔
401
        if h.Attributes&0xf0000000 != 0 {
4✔
402
                mode = unixModeToFileMode(h.Attributes >> 16)
2✔
403
        } else {
4✔
404
                mode = msdosModeToFileMode(h.Attributes)
2✔
405
        }
2✔
406

407
        return
2✔
408
}
409

410
func msdosModeToFileMode(m uint32) (mode iofs.FileMode) {
2✔
411
        if m&msdosDir != 0 {
2✔
NEW
412
                mode = iofs.ModeDir | 0o777
×
413
        } else {
2✔
414
                mode = 0o666
2✔
415
        }
2✔
416

417
        if m&msdosReadOnly != 0 {
4✔
418
                mode &^= 0o222
2✔
419
        }
2✔
420

421
        return mode
2✔
422
}
423

424
//nolint:cyclop
425
func unixModeToFileMode(m uint32) iofs.FileMode {
2✔
426
        mode := iofs.FileMode(m & 0o777)
2✔
427

2✔
428
        switch m & sIFMT {
2✔
429
        case sIFBLK:
×
NEW
430
                mode |= iofs.ModeDevice
×
431
        case sIFCHR:
×
NEW
432
                mode |= iofs.ModeDevice | iofs.ModeCharDevice
×
433
        case sIFDIR:
2✔
434
                mode |= iofs.ModeDir
2✔
435
        case sIFIFO:
×
NEW
436
                mode |= iofs.ModeNamedPipe
×
437
        case sIFLNK:
×
NEW
438
                mode |= iofs.ModeSymlink
×
439
        case sIFREG:
2✔
440
                // nothing to do
441
        case sIFSOCK:
×
NEW
442
                mode |= iofs.ModeSocket
×
443
        }
444

445
        if m&sISGID != 0 {
2✔
NEW
446
                mode |= iofs.ModeSetgid
×
447
        }
×
448

449
        if m&sISUID != 0 {
2✔
NEW
450
                mode |= iofs.ModeSetuid
×
451
        }
×
452

453
        if m&sISVTX != 0 {
2✔
NEW
454
                mode |= iofs.ModeSticky
×
455
        }
×
456

457
        return mode
2✔
458
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc