blob: 994f1f8d9224d4d39e2346393c81f92b403049f5 [file] [log] [blame]
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() {
var s = new PassThrough()
s.end()
return s
}
var mixinPax = function(header, pax) {
if (pax.path) header.name = pax.path
if (pax.linkpath) header.linkname = pax.linkpath
return header
}
var Extract = function(opts) {
if (!(this instanceof Extract)) return new Extract(opts)
Writable.call(this, opts)
this._buffer = bl()
this._missing = 0
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
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 onheader = function() {
var header
try {
header = self._header = headers.decode(b.slice(0, 512))
} catch (err) {
self.emit('error', err)
}
b.consume(512)
if (!header) {
self._parse(512, onheader)
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._pax) {
self._header = header = mixinPax(header, self._pax)
self._pax = null
}
self._locked = true
if (!header.size) {
self._parse(512, onheader)
self.emit('entry', header, emptyStream(), onunlock)
return
}
self._stream = new PassThrough()
self.emit('entry', header, self._stream, onunlock)
self._parse(header.size, onstreamend)
oncontinue()
}
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._missing = size
this._onparse = onparse
}
Extract.prototype._continue = function(err) {
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
// 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()
}
module.exports = Extract