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