var util = require('util'); var bl = require('bl'); 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, { autoDestroy: false }); }; 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 = Object.assign({}, 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, opts.allowUnknownFormat ); } 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;