259 lines
5.8 KiB
JavaScript
259 lines
5.8 KiB
JavaScript
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
|