blob: 19a4255a305ac8798f20259177113d74ccb5ae38 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001var util = require('util')
2var bl = require('bl')
3var xtend = require('xtend')
4var headers = require('./headers')
5
6var Writable = require('readable-stream').Writable
7var PassThrough = require('readable-stream').PassThrough
8
9var noop = function () {}
10
11var overflow = function (size) {
12 size &= 511
13 return size && 512 - size
14}
15
16var emptyStream = function (self, offset) {
17 var s = new Source(self, offset)
18 s.end()
19 return s
20}
21
22var mixinPax = function (header, pax) {
23 if (pax.path) header.name = pax.path
24 if (pax.linkpath) header.linkname = pax.linkpath
25 if (pax.size) header.size = parseInt(pax.size, 10)
26 header.pax = pax
27 return header
28}
29
30var Source = function (self, offset) {
31 this._parent = self
32 this.offset = offset
33 PassThrough.call(this)
34}
35
36util.inherits(Source, PassThrough)
37
38Source.prototype.destroy = function (err) {
39 this._parent.destroy(err)
40}
41
42var Extract = function (opts) {
43 if (!(this instanceof Extract)) return new Extract(opts)
44 Writable.call(this, opts)
45
46 opts = opts || {}
47
48 this._offset = 0
49 this._buffer = bl()
50 this._missing = 0
51 this._partial = false
52 this._onparse = noop
53 this._header = null
54 this._stream = null
55 this._overflow = null
56 this._cb = null
57 this._locked = false
58 this._destroyed = false
59 this._pax = null
60 this._paxGlobal = null
61 this._gnuLongPath = null
62 this._gnuLongLinkPath = null
63
64 var self = this
65 var b = self._buffer
66
67 var oncontinue = function () {
68 self._continue()
69 }
70
71 var onunlock = function (err) {
72 self._locked = false
73 if (err) return self.destroy(err)
74 if (!self._stream) oncontinue()
75 }
76
77 var onstreamend = function () {
78 self._stream = null
79 var drain = overflow(self._header.size)
80 if (drain) self._parse(drain, ondrain)
81 else self._parse(512, onheader)
82 if (!self._locked) oncontinue()
83 }
84
85 var ondrain = function () {
86 self._buffer.consume(overflow(self._header.size))
87 self._parse(512, onheader)
88 oncontinue()
89 }
90
91 var onpaxglobalheader = function () {
92 var size = self._header.size
93 self._paxGlobal = headers.decodePax(b.slice(0, size))
94 b.consume(size)
95 onstreamend()
96 }
97
98 var onpaxheader = function () {
99 var size = self._header.size
100 self._pax = headers.decodePax(b.slice(0, size))
101 if (self._paxGlobal) self._pax = xtend(self._paxGlobal, self._pax)
102 b.consume(size)
103 onstreamend()
104 }
105
106 var ongnulongpath = function () {
107 var size = self._header.size
108 this._gnuLongPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding)
109 b.consume(size)
110 onstreamend()
111 }
112
113 var ongnulonglinkpath = function () {
114 var size = self._header.size
115 this._gnuLongLinkPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding)
116 b.consume(size)
117 onstreamend()
118 }
119
120 var onheader = function () {
121 var offset = self._offset
122 var header
123 try {
124 header = self._header = headers.decode(b.slice(0, 512), opts.filenameEncoding)
125 } catch (err) {
126 self.emit('error', err)
127 }
128 b.consume(512)
129
130 if (!header) {
131 self._parse(512, onheader)
132 oncontinue()
133 return
134 }
135 if (header.type === 'gnu-long-path') {
136 self._parse(header.size, ongnulongpath)
137 oncontinue()
138 return
139 }
140 if (header.type === 'gnu-long-link-path') {
141 self._parse(header.size, ongnulonglinkpath)
142 oncontinue()
143 return
144 }
145 if (header.type === 'pax-global-header') {
146 self._parse(header.size, onpaxglobalheader)
147 oncontinue()
148 return
149 }
150 if (header.type === 'pax-header') {
151 self._parse(header.size, onpaxheader)
152 oncontinue()
153 return
154 }
155
156 if (self._gnuLongPath) {
157 header.name = self._gnuLongPath
158 self._gnuLongPath = null
159 }
160
161 if (self._gnuLongLinkPath) {
162 header.linkname = self._gnuLongLinkPath
163 self._gnuLongLinkPath = null
164 }
165
166 if (self._pax) {
167 self._header = header = mixinPax(header, self._pax)
168 self._pax = null
169 }
170
171 self._locked = true
172
173 if (!header.size || header.type === 'directory') {
174 self._parse(512, onheader)
175 self.emit('entry', header, emptyStream(self, offset), onunlock)
176 return
177 }
178
179 self._stream = new Source(self, offset)
180
181 self.emit('entry', header, self._stream, onunlock)
182 self._parse(header.size, onstreamend)
183 oncontinue()
184 }
185
186 this._onheader = onheader
187 this._parse(512, onheader)
188}
189
190util.inherits(Extract, Writable)
191
192Extract.prototype.destroy = function (err) {
193 if (this._destroyed) return
194 this._destroyed = true
195
196 if (err) this.emit('error', err)
197 this.emit('close')
198 if (this._stream) this._stream.emit('close')
199}
200
201Extract.prototype._parse = function (size, onparse) {
202 if (this._destroyed) return
203 this._offset += size
204 this._missing = size
205 if (onparse === this._onheader) this._partial = false
206 this._onparse = onparse
207}
208
209Extract.prototype._continue = function () {
210 if (this._destroyed) return
211 var cb = this._cb
212 this._cb = noop
213 if (this._overflow) this._write(this._overflow, undefined, cb)
214 else cb()
215}
216
217Extract.prototype._write = function (data, enc, cb) {
218 if (this._destroyed) return
219
220 var s = this._stream
221 var b = this._buffer
222 var missing = this._missing
223 if (data.length) this._partial = true
224
225 // we do not reach end-of-chunk now. just forward it
226
227 if (data.length < missing) {
228 this._missing -= data.length
229 this._overflow = null
230 if (s) return s.write(data, cb)
231 b.append(data)
232 return cb()
233 }
234
235 // end-of-chunk. the parser should call cb.
236
237 this._cb = cb
238 this._missing = 0
239
240 var overflow = null
241 if (data.length > missing) {
242 overflow = data.slice(missing)
243 data = data.slice(0, missing)
244 }
245
246 if (s) s.end(data)
247 else b.append(data)
248
249 this._overflow = overflow
250 this._onparse()
251}
252
253Extract.prototype._final = function (cb) {
254 if (this._partial) return this.destroy(new Error('Unexpected end of data'))
255 cb()
256}
257
258module.exports = Extract