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
			| 
											2 years ago
										 | 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 |