You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
5.8 KiB
258 lines
5.8 KiB
var util = require('util') |
|
var bl = require('bl') |
|
var xtend = require('xtend') |
|
var headers = require('./headers') |
|
|
|
var Writable = require('readable-stream').Writable |
|
var PassThrough = require('readable-stream').PassThrough |
|
|
|
var noop = function () {} |
|
|
|
var overflow = function (size) { |
|
size &= 511 |
|
return size && 512 - size |
|
} |
|
|
|
var emptyStream = function (self, offset) { |
|
var s = new Source(self, offset) |
|
s.end() |
|
return s |
|
} |
|
|
|
var mixinPax = function (header, pax) { |
|
if (pax.path) header.name = pax.path |
|
if (pax.linkpath) header.linkname = pax.linkpath |
|
if (pax.size) header.size = parseInt(pax.size, 10) |
|
header.pax = pax |
|
return header |
|
} |
|
|
|
var Source = function (self, offset) { |
|
this._parent = self |
|
this.offset = offset |
|
PassThrough.call(this) |
|
} |
|
|
|
util.inherits(Source, PassThrough) |
|
|
|
Source.prototype.destroy = function (err) { |
|
this._parent.destroy(err) |
|
} |
|
|
|
var Extract = function (opts) { |
|
if (!(this instanceof Extract)) return new Extract(opts) |
|
Writable.call(this, opts) |
|
|
|
opts = opts || {} |
|
|
|
this._offset = 0 |
|
this._buffer = bl() |
|
this._missing = 0 |
|
this._partial = false |
|
this._onparse = noop |
|
this._header = null |
|
this._stream = null |
|
this._overflow = null |
|
this._cb = null |
|
this._locked = false |
|
this._destroyed = false |
|
this._pax = null |
|
this._paxGlobal = null |
|
this._gnuLongPath = null |
|
this._gnuLongLinkPath = null |
|
|
|
var self = this |
|
var b = self._buffer |
|
|
|
var oncontinue = function () { |
|
self._continue() |
|
} |
|
|
|
var onunlock = function (err) { |
|
self._locked = false |
|
if (err) return self.destroy(err) |
|
if (!self._stream) oncontinue() |
|
} |
|
|
|
var onstreamend = function () { |
|
self._stream = null |
|
var drain = overflow(self._header.size) |
|
if (drain) self._parse(drain, ondrain) |
|
else self._parse(512, onheader) |
|
if (!self._locked) oncontinue() |
|
} |
|
|
|
var ondrain = function () { |
|
self._buffer.consume(overflow(self._header.size)) |
|
self._parse(512, onheader) |
|
oncontinue() |
|
} |
|
|
|
var onpaxglobalheader = function () { |
|
var size = self._header.size |
|
self._paxGlobal = headers.decodePax(b.slice(0, size)) |
|
b.consume(size) |
|
onstreamend() |
|
} |
|
|
|
var onpaxheader = function () { |
|
var size = self._header.size |
|
self._pax = headers.decodePax(b.slice(0, size)) |
|
if (self._paxGlobal) self._pax = xtend(self._paxGlobal, self._pax) |
|
b.consume(size) |
|
onstreamend() |
|
} |
|
|
|
var ongnulongpath = function () { |
|
var size = self._header.size |
|
this._gnuLongPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding) |
|
b.consume(size) |
|
onstreamend() |
|
} |
|
|
|
var ongnulonglinkpath = function () { |
|
var size = self._header.size |
|
this._gnuLongLinkPath = headers.decodeLongPath(b.slice(0, size), opts.filenameEncoding) |
|
b.consume(size) |
|
onstreamend() |
|
} |
|
|
|
var onheader = function () { |
|
var offset = self._offset |
|
var header |
|
try { |
|
header = self._header = headers.decode(b.slice(0, 512), opts.filenameEncoding) |
|
} catch (err) { |
|
self.emit('error', err) |
|
} |
|
b.consume(512) |
|
|
|
if (!header) { |
|
self._parse(512, onheader) |
|
oncontinue() |
|
return |
|
} |
|
if (header.type === 'gnu-long-path') { |
|
self._parse(header.size, ongnulongpath) |
|
oncontinue() |
|
return |
|
} |
|
if (header.type === 'gnu-long-link-path') { |
|
self._parse(header.size, ongnulonglinkpath) |
|
oncontinue() |
|
return |
|
} |
|
if (header.type === 'pax-global-header') { |
|
self._parse(header.size, onpaxglobalheader) |
|
oncontinue() |
|
return |
|
} |
|
if (header.type === 'pax-header') { |
|
self._parse(header.size, onpaxheader) |
|
oncontinue() |
|
return |
|
} |
|
|
|
if (self._gnuLongPath) { |
|
header.name = self._gnuLongPath |
|
self._gnuLongPath = null |
|
} |
|
|
|
if (self._gnuLongLinkPath) { |
|
header.linkname = self._gnuLongLinkPath |
|
self._gnuLongLinkPath = null |
|
} |
|
|
|
if (self._pax) { |
|
self._header = header = mixinPax(header, self._pax) |
|
self._pax = null |
|
} |
|
|
|
self._locked = true |
|
|
|
if (!header.size || header.type === 'directory') { |
|
self._parse(512, onheader) |
|
self.emit('entry', header, emptyStream(self, offset), onunlock) |
|
return |
|
} |
|
|
|
self._stream = new Source(self, offset) |
|
|
|
self.emit('entry', header, self._stream, onunlock) |
|
self._parse(header.size, onstreamend) |
|
oncontinue() |
|
} |
|
|
|
this._onheader = onheader |
|
this._parse(512, onheader) |
|
} |
|
|
|
util.inherits(Extract, Writable) |
|
|
|
Extract.prototype.destroy = function (err) { |
|
if (this._destroyed) return |
|
this._destroyed = true |
|
|
|
if (err) this.emit('error', err) |
|
this.emit('close') |
|
if (this._stream) this._stream.emit('close') |
|
} |
|
|
|
Extract.prototype._parse = function (size, onparse) { |
|
if (this._destroyed) return |
|
this._offset += size |
|
this._missing = size |
|
if (onparse === this._onheader) this._partial = false |
|
this._onparse = onparse |
|
} |
|
|
|
Extract.prototype._continue = function () { |
|
if (this._destroyed) return |
|
var cb = this._cb |
|
this._cb = noop |
|
if (this._overflow) this._write(this._overflow, undefined, cb) |
|
else cb() |
|
} |
|
|
|
Extract.prototype._write = function (data, enc, cb) { |
|
if (this._destroyed) return |
|
|
|
var s = this._stream |
|
var b = this._buffer |
|
var missing = this._missing |
|
if (data.length) this._partial = true |
|
|
|
// we do not reach end-of-chunk now. just forward it |
|
|
|
if (data.length < missing) { |
|
this._missing -= data.length |
|
this._overflow = null |
|
if (s) return s.write(data, cb) |
|
b.append(data) |
|
return cb() |
|
} |
|
|
|
// end-of-chunk. the parser should call cb. |
|
|
|
this._cb = cb |
|
this._missing = 0 |
|
|
|
var overflow = null |
|
if (data.length > missing) { |
|
overflow = data.slice(missing) |
|
data = data.slice(0, missing) |
|
} |
|
|
|
if (s) s.end(data) |
|
else b.append(data) |
|
|
|
this._overflow = overflow |
|
this._onparse() |
|
} |
|
|
|
Extract.prototype._final = function (cb) { |
|
if (this._partial) return this.destroy(new Error('Unexpected end of data')) |
|
cb() |
|
} |
|
|
|
module.exports = Extract
|
|
|