-
Notifications
You must be signed in to change notification settings - Fork 44
/
archive.go
139 lines (120 loc) · 2.19 KB
/
archive.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"archive/tar"
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
"strings"
)
type FileType byte
const (
TypeNormal FileType = iota
TypeDir
TypeLink
TypeSymlink
TypeOther
)
func tarft(typ byte) FileType {
switch typ {
case tar.TypeReg:
return TypeNormal
case tar.TypeDir:
return TypeDir
case tar.TypeLink:
return TypeLink
case tar.TypeSymlink:
return TypeSymlink
}
return TypeOther
}
type File struct {
Name string
LinkName string
Mode fs.FileMode
Type FileType
}
func (f File) Dir() bool {
return f.Type == TypeDir
}
type Archive interface {
Next() (File, error)
ReadAll() ([]byte, error)
}
type TarArchive struct {
r *tar.Reader
}
func NewTarArchive(data []byte, decompress DecompFn) (Archive, error) {
r := bytes.NewReader(data)
dr, err := decompress(r)
if err != nil {
return nil, err
}
return &TarArchive{
r: tar.NewReader(dr),
}, nil
}
func (t *TarArchive) Next() (File, error) {
for {
hdr, err := t.r.Next()
if err != nil {
return File{}, err
}
ft := tarft(hdr.Typeflag)
if ft != TypeOther {
return File{
Name: hdr.Name,
LinkName: hdr.Linkname,
Mode: fs.FileMode(hdr.Mode),
Type: ft,
}, err
}
}
}
func (t *TarArchive) ReadAll() ([]byte, error) {
return io.ReadAll(t.r)
}
type ZipArchive struct {
r *zip.Reader
idx int
}
// decompressor does nothing for a zip archive because it already has built-in
// compression.
func NewZipArchive(data []byte, d DecompFn) (Archive, error) {
r := bytes.NewReader(data)
zr, err := zip.NewReader(r, int64(len(data)))
return &ZipArchive{
r: zr,
idx: -1,
}, err
}
func (z *ZipArchive) Next() (File, error) {
z.idx++
if z.idx < 0 || z.idx >= len(z.r.File) {
return File{}, io.EOF
}
f := z.r.File[z.idx]
typ := TypeNormal
if strings.HasSuffix(f.Name, "/") {
typ = TypeDir
}
return File{
Name: f.Name,
Mode: f.Mode(),
Type: typ,
}, nil
}
func (z *ZipArchive) ReadAll() ([]byte, error) {
if z.idx < 0 || z.idx >= len(z.r.File) {
return nil, io.EOF
}
f := z.r.File[z.idx]
rc, err := f.Open()
if err != nil {
return nil, fmt.Errorf("zip extract: %w", err)
}
defer rc.Close()
data, err := io.ReadAll(rc)
return data, err
}