3 var stream = require('readable-stream')
4 , util = require('util')
6 , crypto = require('crypto')
7 , path = require('path')
9 , StringDecoder = require('string_decoder').StringDecoder
10 , StreamCounter = require('stream-counter')
14 , HEADER_FIELD_START = 2
16 , HEADER_VALUE_START = 4
18 , HEADER_VALUE_ALMOST_DONE = 6
19 , HEADERS_ALMOST_DONE = 7
34 var CONTENT_TYPE_RE = /^multipart\/(?:form-data|related)(?:;|$)/i;
35 var CONTENT_TYPE_PARAM_RE = /;\s*([^=]+)=(?:"([^"]+)"|([^;]+))/gi;
36 var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16}).*/;
37 var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
39 util.inherits(Form, stream.Writable);
40 function Form(options) {
42 stream.Writable.call(self);
44 options = options || {};
47 self.finished = false;
49 self.autoFields = !!options.autoFields;
50 self.autoFiles = !!options.autoFiles;
52 self.maxFields = options.maxFields || 1000;
53 self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024;
54 self.maxFilesSize = options.maxFilesSize || Infinity;
55 self.uploadDir = options.uploadDir || os.tmpDir();
56 self.encoding = options.encoding || 'utf8';
57 self.hash = options.hash || false;
59 self.bytesReceived = 0;
60 self.bytesExpected = null;
62 self.openedFiles = [];
63 self.totalFieldSize = 0;
64 self.totalFieldCount = 0;
65 self.totalFileSize = 0;
68 self.backpressure = false;
71 if (options.boundary) setUpParser(self, options.boundary);
73 self.on('newListener', function(eventName) {
74 if (eventName === 'file') {
75 self.autoFiles = true;
76 } else if (eventName === 'field') {
77 self.autoFields = true;
82 Form.prototype.parse = function(req, cb) {
88 // if the user supplies a callback, this implies autoFields and autoFiles
89 self.autoFields = true;
90 self.autoFiles = true;
92 // wait for request to end before calling cb
93 var end = function (done) {
98 // wait for req events to fire
99 process.nextTick(function() {
100 if (waitend && req.readable) {
101 // dump rest of request
103 req.once('end', done);
113 self.on('error', function(err) {
118 self.on('field', function(name, value) {
119 var fieldsArray = fields[name] || (fields[name] = []);
120 fieldsArray.push(value);
122 self.on('file', function(name, file) {
123 var filesArray = files[name] || (files[name] = []);
124 filesArray.push(file);
126 self.on('close', function() {
128 cb(null, fields, files);
133 self.handleError = handleError;
134 self.bytesExpected = getBytesExpected(req.headers);
136 req.on('end', onReqEnd);
137 req.on('error', function(err) {
141 req.on('aborted', onReqAborted);
143 var state = req._readableState;
144 if (req._decoder || (state && (state.encoding || state.decoder))) {
145 // this is a binary protocol
146 // if an encoding is set, input is likely corrupted
147 validationError(new Error('request encoding must not be set'));
151 var contentType = req.headers['content-type'];
153 validationError(new Error('missing content-type header'));
157 var m = CONTENT_TYPE_RE.exec(contentType);
159 validationError(new Error('unrecognized content-type: ' + contentType));
164 CONTENT_TYPE_PARAM_RE.lastIndex = m.index + m[0].length - 1;
165 while ((m = CONTENT_TYPE_PARAM_RE.exec(contentType))) {
166 if (m[1].toLowerCase() !== 'boundary') continue;
167 boundary = m[2] || m[3];
172 validationError(new Error('content-type missing boundary: ' + require('util').inspect(m)));
176 setUpParser(self, boundary);
179 function onReqAborted() {
181 self.emit('aborted');
182 handleError(new Error("Request aborted"));
185 function onReqEnd() {
189 function handleError(err) {
190 var first = !self.error;
193 req.removeListener('aborted', onReqAborted);
194 req.removeListener('end', onReqEnd);
197 self.openedFiles.forEach(function(file) {
198 destroyFile(self, file);
200 self.openedFiles = [];
203 self.emit('error', err);
207 function validationError(err) {
208 // handle error on next tick for event listeners to attach
209 process.nextTick(handleError.bind(null, err))
213 Form.prototype._write = function(buffer, encoding, cb) {
214 if (this.error) return;
218 , len = buffer.length
219 , prevIndex = self.index
222 , lookbehind = self.lookbehind
223 , boundary = self.boundary
224 , boundaryChars = self.boundaryChars
225 , boundaryLength = self.boundary.length
226 , boundaryEnd = boundaryLength - 1
227 , bufferLength = buffer.length
231 for (i = 0; i < len; i++) {
236 state = START_BOUNDARY;
239 if (index === boundaryLength - 2 && c === HYPHEN) {
241 state = CLOSE_BOUNDARY;
243 } else if (index === boundaryLength - 2) {
244 if (c !== CR) return self.handleError(new Error("Expected CR Received " + c));
247 } else if (index === boundaryLength - 1) {
248 if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
250 self.onParsePartBegin();
251 state = HEADER_FIELD_START;
255 if (c !== boundary[index+2]) index = -2;
256 if (c === boundary[index+2]) index++;
258 case HEADER_FIELD_START:
259 state = HEADER_FIELD;
260 self.headerFieldMark = i;
265 self.headerFieldMark = null;
266 state = HEADERS_ALMOST_DONE;
271 if (c === HYPHEN) break;
275 // empty header field
276 self.handleError(new Error("Empty header field"));
279 self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
280 self.headerFieldMark = null;
281 state = HEADER_VALUE_START;
286 if (cl < A || cl > Z) {
287 self.handleError(new Error("Expected alphabetic character, received " + c));
291 case HEADER_VALUE_START:
292 if (c === SPACE) break;
294 self.headerValueMark = i;
295 state = HEADER_VALUE;
299 self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
300 self.headerValueMark = null;
301 self.onParseHeaderEnd();
302 state = HEADER_VALUE_ALMOST_DONE;
305 case HEADER_VALUE_ALMOST_DONE:
306 if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
307 state = HEADER_FIELD_START;
309 case HEADERS_ALMOST_DONE:
310 if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
311 var err = self.onParseHeadersEnd(i + 1);
312 if (err) return self.handleError(err);
313 state = PART_DATA_START;
315 case PART_DATA_START:
317 self.partDataMark = i;
323 // boyer-moore derrived algorithm to safely skip non-boundary data
325 while (i < bufferLength && !(buffer[i] in boundaryChars)) {
332 if (index < boundaryLength) {
333 if (boundary[index] === c) {
335 self.onParsePartData(buffer.slice(self.partDataMark, i));
336 self.partDataMark = null;
342 } else if (index === boundaryLength) {
345 // CR = part boundary
346 self.partBoundaryFlag = true;
347 } else if (c === HYPHEN) {
349 state = CLOSE_BOUNDARY;
354 } else if (index - 1 === boundaryLength) {
355 if (self.partBoundaryFlag) {
358 self.partBoundaryFlag = false;
359 self.onParsePartEnd();
360 self.onParsePartBegin();
361 state = HEADER_FIELD_START;
370 // when matching a possible boundary, keep a lookbehind reference
371 // in case it turns out to be a false lead
372 lookbehind[index-1] = c;
373 } else if (prevIndex > 0) {
374 // if our boundary turned out to be rubbish, the captured lookbehind
375 // belongs to partData
376 self.onParsePartData(lookbehind.slice(0, prevIndex));
378 self.partDataMark = i;
380 // reconsider the current character even so it interrupted the sequence
381 // it could be the beginning of a new sequence
387 if (c !== HYPHEN) return self.handleError(new Error("Expected HYPHEN Received " + c));
389 self.onParsePartEnd();
391 } else if (index > 1) {
392 return self.handleError(new Error("Parser has invalid state."));
399 self.handleError(new Error("Parser has invalid state."));
404 if (self.headerFieldMark != null) {
405 self.onParseHeaderField(buffer.slice(self.headerFieldMark));
406 self.headerFieldMark = 0;
408 if (self.headerValueMark != null) {
409 self.onParseHeaderValue(buffer.slice(self.headerValueMark));
410 self.headerValueMark = 0;
412 if (self.partDataMark != null) {
413 self.onParsePartData(buffer.slice(self.partDataMark));
414 self.partDataMark = 0;
420 self.bytesReceived += buffer.length;
421 self.emit('progress', self.bytesReceived, self.bytesExpected);
423 if (self.backpressure) {
424 self.writeCbs.push(cb);
430 Form.prototype.onParsePartBegin = function() {
434 Form.prototype.onParseHeaderField = function(b) {
435 this.headerField += this.headerFieldDecoder.write(b);
438 Form.prototype.onParseHeaderValue = function(b) {
439 this.headerValue += this.headerValueDecoder.write(b);
442 Form.prototype.onParseHeaderEnd = function() {
443 this.headerField = this.headerField.toLowerCase();
444 this.partHeaders[this.headerField] = this.headerValue;
447 if (this.headerField === 'content-disposition') {
448 if (m = this.headerValue.match(/\bname="([^"]+)"/i)) {
449 this.partName = m[1];
451 this.partFilename = parseFilename(this.headerValue);
452 } else if (this.headerField === 'content-transfer-encoding') {
453 this.partTransferEncoding = this.headerValue.toLowerCase();
456 this.headerFieldDecoder = new StringDecoder(this.encoding);
457 this.headerField = '';
458 this.headerValueDecoder = new StringDecoder(this.encoding);
459 this.headerValue = '';
462 Form.prototype.onParsePartData = function(b) {
463 if (this.partTransferEncoding === 'base64') {
464 this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64');
466 this.backpressure = ! this.destStream.write(b);
470 Form.prototype.onParsePartEnd = function() {
471 if (this.destStream) {
473 var s = this.destStream;
474 process.nextTick(function() {
481 Form.prototype.onParseHeadersEnd = function(offset) {
483 switch(self.partTransferEncoding){
487 self.partTransferEncoding = 'binary';
490 case 'base64': break;
492 return new Error("unknown transfer-encoding: " + self.partTransferEncoding);
495 self.totalFieldCount += 1;
496 if (self.totalFieldCount > self.maxFields) {
497 return new Error("maxFields " + self.maxFields + " exceeded.");
500 self.destStream = new stream.PassThrough();
501 self.destStream.on('drain', function() {
504 self.destStream.headers = self.partHeaders;
505 self.destStream.name = self.partName;
506 self.destStream.filename = self.partFilename;
507 self.destStream.byteOffset = self.bytesReceived + offset;
508 var partContentLength = self.destStream.headers['content-length'];
509 self.destStream.byteCount = partContentLength ? parseInt(partContentLength, 10) :
510 self.bytesExpected ? (self.bytesExpected - self.destStream.byteOffset -
511 self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN) :
514 self.emit('part', self.destStream);
515 if (self.destStream.filename == null && self.autoFields) {
516 handleField(self, self.destStream);
517 } else if (self.destStream.filename != null && self.autoFiles) {
518 handleFile(self, self.destStream);
521 self.destStream.on('end', function(){
527 function flushWriteCbs(self) {
528 self.writeCbs.forEach(function(cb) {
529 process.nextTick(cb);
532 self.backpressure = false;
535 function getBytesExpected(headers) {
536 var contentLength = headers['content-length'];
538 return parseInt(contentLength, 10);
539 } else if (headers['transfer-encoding'] == null) {
546 function beginFlush(self) {
550 function endFlush(self) {
555 function maybeClose(self) {
556 if (!self.flushing && self.finished && !self.error) {
561 function destroyFile(self, file) {
562 if (!file.ws) return;
563 file.ws.removeAllListeners('close');
564 file.ws.on('close', function() {
565 fs.unlink(file.path, function(err) {
566 if (err && !self.error) self.handleError(err);
572 function handleFile(self, fileStream) {
573 if (self.error) return;
575 fieldName: fileStream.name,
576 originalFilename: fileStream.filename,
577 path: uploadPath(self.uploadDir, fileStream.filename),
578 headers: fileStream.headers,
580 beginFlush(self); // flush to write stream
581 file.ws = fs.createWriteStream(file.path);
582 self.openedFiles.push(file);
583 fileStream.pipe(file.ws);
584 var counter = new StreamCounter();
586 fileStream.pipe(counter);
587 var hashWorkaroundStream
590 // workaround stream because https://github.com/joyent/node/issues/5216
591 hashWorkaroundStream = stream.Writable();
592 hash = crypto.createHash(self.hash);
593 hashWorkaroundStream._write = function(buffer, encoding, callback) {
597 fileStream.pipe(hashWorkaroundStream);
599 counter.on('progress', function() {
600 var deltaBytes = counter.bytes - seenBytes;
601 seenBytes += deltaBytes;
602 self.totalFileSize += deltaBytes;
603 if (self.totalFileSize > self.maxFilesSize) {
604 if (hashWorkaroundStream) fileStream.unpipe(hashWorkaroundStream);
605 fileStream.unpipe(counter);
606 fileStream.unpipe(file.ws);
607 self.handleError(new Error("maxFilesSize " + self.maxFilesSize + " exceeded"));
610 file.ws.on('error', function(err) {
611 if (!self.error) self.handleError(err);
613 file.ws.on('close', function() {
614 if (hash) file.hash = hash.digest('hex');
615 file.size = counter.bytes;
616 self.emit('file', fileStream.name, file);
619 beginFlush(self); // flush from file stream
620 fileStream.on('end', function(){
625 function handleField(self, fieldStream) {
627 var decoder = new StringDecoder(self.encoding);
630 fieldStream.on('readable', function() {
631 var buffer = fieldStream.read();
634 self.totalFieldSize += buffer.length;
635 if (self.totalFieldSize > self.maxFieldsSize) {
636 self.handleError(new Error("maxFieldsSize " + self.maxFieldsSize + " exceeded"));
639 value += decoder.write(buffer);
642 fieldStream.on('end', function() {
643 self.emit('field', fieldStream.name, value);
648 function clearPartVars(self) {
649 self.partHeaders = {};
650 self.partName = null;
651 self.partFilename = null;
652 self.partTransferEncoding = 'binary';
653 self.destStream = null;
655 self.headerFieldDecoder = new StringDecoder(self.encoding);
656 self.headerField = "";
657 self.headerValueDecoder = new StringDecoder(self.encoding);
658 self.headerValue = "";
661 function setUpParser(self, boundary) {
662 self.boundary = new Buffer(boundary.length + 4);
663 self.boundary.write('\r\n--', 0, boundary.length + 4, 'ascii');
664 self.boundary.write(boundary, 4, boundary.length, 'ascii');
665 self.lookbehind = new Buffer(self.boundary.length + 8);
667 self.boundaryChars = {};
668 for (var i = 0; i < self.boundary.length; i++) {
669 self.boundaryChars[self.boundary[i]] = true;
673 self.partBoundaryFlag = false;
675 self.on('finish', function() {
676 if ((self.state === HEADER_FIELD_START && self.index === 0) ||
677 (self.state === PART_DATA && self.index === self.boundary.length))
679 self.onParsePartEnd();
680 } else if (self.state !== END) {
681 self.handleError(new Error('stream ended unexpectedly'));
683 self.finished = true;
688 function uploadPath(baseDir, filename) {
689 var ext = path.extname(filename).replace(FILE_EXT_RE, '$1');
690 var name = process.pid + '-' +
691 (Math.random() * 0x100000000 + 1).toString(36) + ext;
692 return path.join(baseDir, name);
695 function parseFilename(headerValue) {
696 var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
698 m = headerValue.match(/\bfilename\*=utf-8\'\'(.*?)($|; )/i);
700 m[1] = decodeURI(m[1]);
707 var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
708 filename = filename.replace(/%22/g, '"');
709 filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
710 return String.fromCharCode(code);