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.
		
		
		
		
		
			
		
			
				
					241 lines
				
				6.3 KiB
			
		
		
			
		
	
	
					241 lines
				
				6.3 KiB
			| 
											3 years ago
										 | var WritableStream = require('stream').Writable | ||
|  |                      || require('readable-stream').Writable, | ||
|  |     inherits = require('util').inherits; | ||
|  | 
 | ||
|  | var StreamSearch = require('streamsearch'); | ||
|  | 
 | ||
|  | var PartStream = require('./PartStream'), | ||
|  |     HeaderParser = require('./HeaderParser'); | ||
|  | 
 | ||
|  | var DASH = 45, | ||
|  |     B_ONEDASH = new Buffer('-'), | ||
|  |     B_CRLF = new Buffer('\r\n'), | ||
|  |     EMPTY_FN = function() {}; | ||
|  | 
 | ||
|  | function Dicer(cfg) { | ||
|  |   if (!(this instanceof Dicer)) | ||
|  |     return new Dicer(cfg); | ||
|  |   WritableStream.call(this, cfg); | ||
|  | 
 | ||
|  |   if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) | ||
|  |     throw new TypeError('Boundary required'); | ||
|  | 
 | ||
|  |   if (typeof cfg.boundary === 'string') | ||
|  |     this.setBoundary(cfg.boundary); | ||
|  |   else | ||
|  |     this._bparser = undefined; | ||
|  | 
 | ||
|  |   this._headerFirst = cfg.headerFirst; | ||
|  | 
 | ||
|  |   var self = this; | ||
|  | 
 | ||
|  |   this._dashes = 0; | ||
|  |   this._parts = 0; | ||
|  |   this._finished = false; | ||
|  |   this._realFinish = false; | ||
|  |   this._isPreamble = true; | ||
|  |   this._justMatched = false; | ||
|  |   this._firstWrite = true; | ||
|  |   this._inHeader = true; | ||
|  |   this._part = undefined; | ||
|  |   this._cb = undefined; | ||
|  |   this._ignoreData = false; | ||
|  |   this._partOpts = (typeof cfg.partHwm === 'number' | ||
|  |                     ? { highWaterMark: cfg.partHwm } | ||
|  |                     : {}); | ||
|  |   this._pause = false; | ||
|  | 
 | ||
|  |   this._hparser = new HeaderParser(cfg); | ||
|  |   this._hparser.on('header', function(header) { | ||
|  |     self._inHeader = false; | ||
|  |     self._part.emit('header', header); | ||
|  |   }); | ||
|  | 
 | ||
|  | } | ||
|  | inherits(Dicer, WritableStream); | ||
|  | 
 | ||
|  | Dicer.prototype.emit = function(ev) { | ||
|  |   if (ev === 'finish' && !this._realFinish) { | ||
|  |     if (!this._finished) { | ||
|  |       var self = this; | ||
|  |       process.nextTick(function() { | ||
|  |         self.emit('error', new Error('Unexpected end of multipart data')); | ||
|  |         if (self._part && !self._ignoreData) { | ||
|  |           var type = (self._isPreamble ? 'Preamble' : 'Part'); | ||
|  |           self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')); | ||
|  |           self._part.push(null); | ||
|  |           process.nextTick(function() { | ||
|  |             self._realFinish = true; | ||
|  |             self.emit('finish'); | ||
|  |             self._realFinish = false; | ||
|  |           }); | ||
|  |           return; | ||
|  |         } | ||
|  |         self._realFinish = true; | ||
|  |         self.emit('finish'); | ||
|  |         self._realFinish = false; | ||
|  |       }); | ||
|  |     } | ||
|  |   } else | ||
|  |     WritableStream.prototype.emit.apply(this, arguments); | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype._write = function(data, encoding, cb) { | ||
|  |   // ignore unexpected data (e.g. extra trailer data after finished)
 | ||
|  |   if (!this._hparser && !this._bparser) | ||
|  |     return cb(); | ||
|  | 
 | ||
|  |   if (this._headerFirst && this._isPreamble) { | ||
|  |     if (!this._part) { | ||
|  |       this._part = new PartStream(this._partOpts); | ||
|  |       if (this._events.preamble) | ||
|  |         this.emit('preamble', this._part); | ||
|  |       else | ||
|  |         this._ignore(); | ||
|  |     } | ||
|  |     var r = this._hparser.push(data); | ||
|  |     if (!this._inHeader && r !== undefined && r < data.length) | ||
|  |       data = data.slice(r); | ||
|  |     else | ||
|  |       return cb(); | ||
|  |   } | ||
|  | 
 | ||
|  |   // allows for "easier" testing
 | ||
|  |   if (this._firstWrite) { | ||
|  |     this._bparser.push(B_CRLF); | ||
|  |     this._firstWrite = false; | ||
|  |   } | ||
|  | 
 | ||
|  |   this._bparser.push(data); | ||
|  | 
 | ||
|  |   if (this._pause) | ||
|  |     this._cb = cb; | ||
|  |   else | ||
|  |     cb(); | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype.reset = function() { | ||
|  |   this._part = undefined; | ||
|  |   this._bparser = undefined; | ||
|  |   this._hparser = undefined; | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype.setBoundary = function(boundary) { | ||
|  |   var self = this; | ||
|  |   this._bparser = new StreamSearch('\r\n--' + boundary); | ||
|  |   this._bparser.on('info', function(isMatch, data, start, end) { | ||
|  |     self._oninfo(isMatch, data, start, end); | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype._ignore = function() { | ||
|  |   if (this._part && !this._ignoreData) { | ||
|  |     this._ignoreData = true; | ||
|  |     this._part.on('error', EMPTY_FN); | ||
|  |     // we must perform some kind of read on the stream even though we are
 | ||
|  |     // ignoring the data, otherwise node's Readable stream will not emit 'end'
 | ||
|  |     // after pushing null to the stream
 | ||
|  |     this._part.resume(); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype._oninfo = function(isMatch, data, start, end) { | ||
|  |   var buf, self = this, i = 0, r, ev, shouldWriteMore = true; | ||
|  | 
 | ||
|  |   if (!this._part && this._justMatched && data) { | ||
|  |     while (this._dashes < 2 && (start + i) < end) { | ||
|  |       if (data[start + i] === DASH) { | ||
|  |         ++i; | ||
|  |         ++this._dashes; | ||
|  |       } else { | ||
|  |         if (this._dashes) | ||
|  |           buf = B_ONEDASH; | ||
|  |         this._dashes = 0; | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  |     if (this._dashes === 2) { | ||
|  |       if ((start + i) < end && this._events.trailer) | ||
|  |         this.emit('trailer', data.slice(start + i, end)); | ||
|  |       this.reset(); | ||
|  |       this._finished = true; | ||
|  |       // no more parts will be added
 | ||
|  |       if (self._parts === 0) { | ||
|  |         self._realFinish = true; | ||
|  |         self.emit('finish'); | ||
|  |         self._realFinish = false; | ||
|  |       } | ||
|  |     } | ||
|  |     if (this._dashes) | ||
|  |       return; | ||
|  |   } | ||
|  |   if (this._justMatched) | ||
|  |     this._justMatched = false; | ||
|  |   if (!this._part) { | ||
|  |     this._part = new PartStream(this._partOpts); | ||
|  |     this._part._read = function(n) { | ||
|  |       self._unpause(); | ||
|  |     }; | ||
|  |     ev = this._isPreamble ? 'preamble' : 'part'; | ||
|  |     if (this._events[ev]) | ||
|  |       this.emit(ev, this._part); | ||
|  |     else | ||
|  |       this._ignore(); | ||
|  |     if (!this._isPreamble) | ||
|  |       this._inHeader = true; | ||
|  |   } | ||
|  |   if (data && start < end && !this._ignoreData) { | ||
|  |     if (this._isPreamble || !this._inHeader) { | ||
|  |       if (buf) | ||
|  |         shouldWriteMore = this._part.push(buf); | ||
|  |       shouldWriteMore = this._part.push(data.slice(start, end)); | ||
|  |       if (!shouldWriteMore) | ||
|  |         this._pause = true; | ||
|  |     } else if (!this._isPreamble && this._inHeader) { | ||
|  |       if (buf) | ||
|  |         this._hparser.push(buf); | ||
|  |       r = this._hparser.push(data.slice(start, end)); | ||
|  |       if (!this._inHeader && r !== undefined && r < end) | ||
|  |         this._oninfo(false, data, start + r, end); | ||
|  |     } | ||
|  |   } | ||
|  |   if (isMatch) { | ||
|  |     this._hparser.reset(); | ||
|  |     if (this._isPreamble) | ||
|  |       this._isPreamble = false; | ||
|  |     else { | ||
|  |       ++this._parts; | ||
|  |       this._part.on('end', function() { | ||
|  |         if (--self._parts === 0) { | ||
|  |           if (self._finished) { | ||
|  |             self._realFinish = true; | ||
|  |             self.emit('finish'); | ||
|  |             self._realFinish = false; | ||
|  |           } else { | ||
|  |             self._unpause(); | ||
|  |           } | ||
|  |         } | ||
|  |       }); | ||
|  |     } | ||
|  |     this._part.push(null); | ||
|  |     this._part = undefined; | ||
|  |     this._ignoreData = false; | ||
|  |     this._justMatched = true; | ||
|  |     this._dashes = 0; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | Dicer.prototype._unpause = function() { | ||
|  |   if (!this._pause) | ||
|  |     return; | ||
|  | 
 | ||
|  |   this._pause = false; | ||
|  |   if (this._cb) { | ||
|  |     var cb = this._cb; | ||
|  |     this._cb = undefined; | ||
|  |     cb(); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports = Dicer; |