123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- 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
|