-
Notifications
You must be signed in to change notification settings - Fork 44
/
unarr.go
286 lines (231 loc) · 5.57 KB
/
unarr.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Package unarr is a decompression library for RAR, TAR, ZIP and 7z archives.
package unarr
import (
"errors"
"io"
"os"
"path/filepath"
"strings"
"time"
"unsafe"
"github.com/gen2brain/go-unarr/unarrc"
)
var (
ErrOpenFile = errors.New("unarr: open file failed")
ErrOpenMemory = errors.New("unarr: open memory failed")
ErrOpenArchive = errors.New("unarr: no valid RAR, ZIP, 7Z or TAR archive")
ErrEntry = errors.New("unarr: failed to parse entry")
ErrEntryAt = errors.New("unarr: failed to parse entry at")
ErrEntryFor = errors.New("unarr: failed to parse entry for")
ErrSeek = errors.New("unarr: seek failed")
ErrRead = errors.New("unarr: read failure")
)
// Archive represents unarr archive
type Archive struct {
// C stream struct
stream *unarrc.Stream
// C archive struct
archive *unarrc.Archive
}
// NewArchive returns new unarr Archive
func NewArchive(path string) (a *Archive, err error) {
a = new(Archive)
a.stream = unarrc.OpenFile(path)
if a.stream == nil {
err = ErrOpenFile
return
}
err = a.open()
return
}
// NewArchiveFromMemory returns new unarr Archive from byte slice
func NewArchiveFromMemory(b []byte) (a *Archive, err error) {
a = new(Archive)
a.stream = unarrc.OpenMemory(unsafe.Pointer(&b[0]), uint(len(b)))
if a.stream == nil {
err = ErrOpenMemory
return
}
err = a.open()
return
}
// NewArchiveFromReader returns new unarr Archive from io.Reader
func NewArchiveFromReader(r io.Reader) (a *Archive, err error) {
b, e := io.ReadAll(r)
if e != nil {
err = e
return
}
a, err = NewArchiveFromMemory(b)
return
}
// open opens archive
func (a *Archive) open() (err error) {
a.archive = unarrc.OpenRarArchive(a.stream)
if a.archive == nil {
a.archive = unarrc.OpenZipArchive(a.stream, false)
}
if a.archive == nil {
a.archive = unarrc.Open7zArchive(a.stream)
}
if a.archive == nil {
a.archive = unarrc.OpenTarArchive(a.stream)
}
if a.archive == nil {
unarrc.Close(a.stream)
err = ErrOpenArchive
}
return
}
// Entry reads the next archive entry.
//
// io.EOF is returned when there is no more to be read from the archive.
func (a *Archive) Entry() error {
r := unarrc.ParseEntry(a.archive)
if !r {
e := unarrc.AtEof(a.archive)
if e {
return io.EOF
}
return ErrEntry
}
return nil
}
// EntryAt reads the archive entry at the given offset
func (a *Archive) EntryAt(off int64) error {
r := unarrc.ParseEntryAt(a.archive, off)
if !r {
return ErrEntryAt
}
return nil
}
// EntryFor reads the (first) archive entry associated with the given name
func (a *Archive) EntryFor(name string) error {
r := unarrc.ParseEntryFor(a.archive, name)
if !r {
return ErrEntryFor
}
return nil
}
// Read tries to read 'b' bytes into buffer, advancing the read offset pointer.
//
// Returns the actual number of bytes read.
func (a *Archive) Read(b []byte) (n int, err error) {
r := unarrc.EntryUncompress(a.archive, unsafe.Pointer(&b[0]), uint(len(b)))
n = len(b)
if !r || n == 0 {
err = io.EOF
}
return
}
// Seek moves the read offset pointer interpreted according to whence.
//
// Returns the new offset.
func (a *Archive) Seek(offset int64, whence int) (int64, error) {
r := unarrc.Seek(a.stream, offset, whence)
if !r {
return 0, ErrSeek
}
return int64(unarrc.Tell(a.stream)), nil
}
// Close closes the underlying unarr archive
func (a *Archive) Close() (err error) {
unarrc.CloseArchive(a.archive)
unarrc.Close(a.stream)
return
}
// Size returns the total size of uncompressed data of the current entry
func (a *Archive) Size() int {
return int(unarrc.EntryGetSize(a.archive))
}
// Offset returns the stream offset of the current entry, for use with EntryAt
func (a *Archive) Offset() int64 {
return int64(unarrc.EntryGetOffset(a.archive))
}
// Name returns the name of the current entry as UTF-8 string
func (a *Archive) Name() string {
return toValidName(unarrc.EntryGetName(a.archive))
}
// RawName returns the name of the current entry as raw string
func (a *Archive) RawName() string {
return unarrc.EntryGetRawName(a.archive)
}
// ModTime returns the stored modification time of the current entry
func (a *Archive) ModTime() time.Time {
filetime := int64(unarrc.EntryGetFiletime(a.archive))
return time.Unix((filetime/10000000)-11644473600, 0)
}
// ReadAll reads current entry and returns data
func (a *Archive) ReadAll() ([]byte, error) {
var err error
var n int
size := a.Size()
b := make([]byte, size)
for size > 0 {
n, err = a.Read(b)
if err != nil {
if err != io.EOF {
return nil, err
}
}
size -= n
}
if size > 0 {
return nil, ErrRead
}
return b, nil
}
// Extract extracts archive to destination path
func (a *Archive) Extract(path string) (contents []string, err error) {
for {
e := a.Entry()
if e != nil {
if e == io.EOF {
break
}
err = e
return
}
name := a.Name()
contents = append(contents, name)
data, e := a.ReadAll()
if e != nil {
err = e
return
}
dirname := filepath.Join(path, filepath.Dir(name))
_ = os.MkdirAll(dirname, 0755)
e = os.WriteFile(filepath.Join(dirname, filepath.Base(name)), data, 0644)
if e != nil {
err = e
return
}
}
return
}
// List lists the contents of archive
func (a *Archive) List() (contents []string, err error) {
for {
e := a.Entry()
if e != nil {
if e == io.EOF {
break
}
err = e
return
}
name := a.Name()
contents = append(contents, name)
}
return
}
func toValidName(name string) string {
p := filepath.Clean(name)
if strings.HasPrefix(p, "/") {
p = p[len("/"):]
}
for strings.HasPrefix(p, "../") {
p = p[len("../"):]
}
return p
}