function Form(options) { var self = this; stream.Writable.call(self); options = options || {}; self.error = null; self.autoFields = !!options.autoFields; self.autoFiles = !!options.autoFiles; self.maxFields = options.maxFields || 1000; self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024; self.maxFilesSize = options.maxFilesSize || Infinity; self.uploadDir = options.uploadDir || os.tmpdir(); self.encoding = options.encoding || 'utf8'; self.bytesReceived = 0; self.bytesExpected = null; self.openedFiles = []; self.totalFieldSize = 0; self.totalFieldCount = 0; self.totalFileSize = 0; self.flushing = 0; self.backpressure = false; self.writeCbs = []; self.emitQueue = []; self.on('newListener', function(eventName) { if (eventName === 'file') { self.autoFiles = true; } else if (eventName === 'field') { self.autoFields = true; } }); }
...
var multiparty = require('multiparty');
var http = require('http');
var util = require('util');
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
...
function Form(options) { var self = this; stream.Writable.call(self); options = options || {}; self.error = null; self.autoFields = !!options.autoFields; self.autoFiles = !!options.autoFiles; self.maxFields = options.maxFields || 1000; self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024; self.maxFilesSize = options.maxFilesSize || Infinity; self.uploadDir = options.uploadDir || os.tmpdir(); self.encoding = options.encoding || 'utf8'; self.bytesReceived = 0; self.bytesExpected = null; self.openedFiles = []; self.totalFieldSize = 0; self.totalFieldCount = 0; self.totalFileSize = 0; self.flushing = 0; self.backpressure = false; self.writeCbs = []; self.emitQueue = []; self.on('newListener', function(eventName) { if (eventName === 'file') { self.autoFiles = true; } else if (eventName === 'field') { self.autoFields = true; } }); }
...
var multiparty = require('multiparty');
var http = require('http');
var util = require('util');
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
...
function Writable(options) { // Writable ctor is applied to Duplexes, too. // `realHasInstance` is necessary because using plain `instanceof` // would return false, as no `_writableState` property is attached. // Trying to use the custom `instanceof` for Writable here will also break the // Node.js LazyTransform implementation, which has a non-trivial getter for // `_writableState` that would lead to infinite recursion. if (!(realHasInstance.call(Writable, this)) && !(this instanceof Stream.Duplex)) { return new Writable(options); } this._writableState = new WritableState(options, this); // legacy. this.writable = true; if (options) { if (typeof options.write === 'function') this._write = options.write; if (typeof options.writev === 'function') this._writev = options.writev; } Stream.call(this); }
n/a
_write = function (buffer, encoding, cb) {
if (this.error) return;
var self = this;
var i = 0;
var len = buffer.length;
var prevIndex = self.index;
var index = self.index;
var state = self.state;
var lookbehind = self.lookbehind;
var boundary = self.boundary;
var boundaryChars = self.boundaryChars;
var boundaryLength = self.boundary.length;
var boundaryEnd = boundaryLength - 1;
var bufferLength = buffer.length;
var c;
var cl;
for (i = 0; i < len; i++) {
c = buffer[i];
switch (state) {
case START:
index = 0;
state = START_BOUNDARY;
/* falls through */
case START_BOUNDARY:
if (index === boundaryLength - 2 && c === HYPHEN) {
index = 1;
state = CLOSE_BOUNDARY;
break;
} else if (index === boundaryLength - 2) {
if (c !== CR) return self.handleError(createError(400, 'Expected CR Received ' + c));
index++;
break;
} else if (index === boundaryLength - 1) {
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
index = 0;
self.onParsePartBegin();
state = HEADER_FIELD_START;
break;
}
if (c !== boundary[index+2]) index = -2;
if (c === boundary[index+2]) index++;
break;
case HEADER_FIELD_START:
state = HEADER_FIELD;
self.headerFieldMark = i;
index = 0;
/* falls through */
case HEADER_FIELD:
if (c === CR) {
self.headerFieldMark = null;
state = HEADERS_ALMOST_DONE;
break;
}
index++;
if (c === HYPHEN) break;
if (c === COLON) {
if (index === 1) {
// empty header field
self.handleError(createError(400, 'Empty header field'));
return;
}
self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
self.headerFieldMark = null;
state = HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
self.handleError(createError(400, 'Expected alphabetic character, received ' + c));
return;
}
break;
case HEADER_VALUE_START:
if (c === SPACE) break;
self.headerValueMark = i;
state = HEADER_VALUE;
/* falls through */
case HEADER_VALUE:
if (c === CR) {
self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
self.headerValueMark = null;
self.onParseHeaderEnd();
state = HEADER_VALUE_ALMOST_DONE;
}
break;
case HEADER_VALUE_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
state = HEADER_FIELD_START;
break;
case HEADERS_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
var err = self.onParseHeadersEnd(i + 1);
if (err) return self.handleError(err);
state = PART_DATA_START;
break;
case PART_DATA_START:
state = PART_DATA;
self.partDataMark = i;
/* falls through */
case PART_DATA:
prevIndex = index;
if (index === 0) {
// boyer-moore derrived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundaryLength) {
if (boundary[index] === c) {
if (index === 0) {
self.onParsePartData(buffer.slice(self.partDataMark, i));
self.partDataMark = null;
}
index++;
} else {
index = 0;
}
} else if (index === boundaryLength) {
index++;
if (c === CR) {
// CR = part boundary
self.partBoundaryFlag = true;
} else i ...
n/a
onParseHeaderEnd = function () { this.headerField = this.headerField.toLowerCase(); this.partHeaders[this.headerField] = this.headerValue; var m; if (this.headerField === 'content-disposition') { if (m = this.headerValue.match(/\bname="([^"]+)"/i)) { this.partName = m[1]; } this.partFilename = parseFilename(this.headerValue); } else if (this.headerField === 'content-transfer-encoding') { this.partTransferEncoding = this.headerValue.toLowerCase(); } this.headerFieldDecoder = new StringDecoder(this.encoding); this.headerField = ''; this.headerValueDecoder = new StringDecoder(this.encoding); this.headerValue = ''; }
...
self.headerValueMark = i;
state = HEADER_VALUE;
/* falls through */
case HEADER_VALUE:
if (c === CR) {
self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
self.headerValueMark = null;
self.onParseHeaderEnd();
state = HEADER_VALUE_ALMOST_DONE;
}
break;
case HEADER_VALUE_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
state = HEADER_FIELD_START;
break;
...
onParseHeaderField = function (b) { this.headerField += this.headerFieldDecoder.write(b); }
...
if (c === COLON) {
if (index === 1) {
// empty header field
self.handleError(createError(400, 'Empty header field'));
return;
}
self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
self.headerFieldMark = null;
state = HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
...
onParseHeaderValue = function (b) { this.headerValue += this.headerValueDecoder.write(b); }
...
if (c === SPACE) break;
self.headerValueMark = i;
state = HEADER_VALUE;
/* falls through */
case HEADER_VALUE:
if (c === CR) {
self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
self.headerValueMark = null;
self.onParseHeaderEnd();
state = HEADER_VALUE_ALMOST_DONE;
}
break;
case HEADER_VALUE_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
...
onParseHeadersEnd = function (offset) { var self = this; switch(self.partTransferEncoding){ case 'binary': case '7bit': case '8bit': self.partTransferEncoding = 'binary'; break; case 'base64': break; default: return createError(400, 'unknown transfer-encoding: ' + self.partTransferEncoding); } self.totalFieldCount += 1; if (self.totalFieldCount > self.maxFields) { return createError(413, 'maxFields ' + self.maxFields + ' exceeded.'); } self.destStream = new stream.PassThrough(); self.destStream.on('drain', function() { flushWriteCbs(self); }); self.destStream.headers = self.partHeaders; self.destStream.name = self.partName; self.destStream.filename = self.partFilename; self.destStream.byteOffset = self.bytesReceived + offset; var partContentLength = self.destStream.headers['content-length']; self.destStream.byteCount = partContentLength ? parseInt(partContentLength, 10) : self.bytesExpected ? (self.bytesExpected - self.destStream.byteOffset - self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN) : undefined; if (self.destStream.filename == null && self.autoFields) { handleField(self, self.destStream); } else if (self.destStream.filename != null && self.autoFiles) { handleFile(self, self.destStream); } else { handlePart(self, self.destStream); } }
...
break;
case HEADER_VALUE_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
state = HEADER_FIELD_START;
break;
case HEADERS_ALMOST_DONE:
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
var err = self.onParseHeadersEnd(i + 1);
if (err) return self.handleError(err);
state = PART_DATA_START;
break;
case PART_DATA_START:
state = PART_DATA;
self.partDataMark = i;
/* falls through */
...
onParsePartBegin = function () { clearPartVars(this); }
...
} else if (index === boundaryLength - 2) {
if (c !== CR) return self.handleError(createError(400, 'Expected CR Received ' + c));
index++;
break;
} else if (index === boundaryLength - 1) {
if (c !== LF) return self.handleError(createError(400, 'Expected LF Received ' + c));
index = 0;
self.onParsePartBegin();
state = HEADER_FIELD_START;
break;
}
if (c !== boundary[index+2]) index = -2;
if (c === boundary[index+2]) index++;
break;
...
onParsePartData = function (b) { if (this.partTransferEncoding === 'base64') { this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64'); } else { this.backpressure = ! this.destStream.write(b); } }
...
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundaryLength) {
if (boundary[index] === c) {
if (index === 0) {
self.onParsePartData(buffer.slice(self.partDataMark, i));
self.partDataMark = null;
}
index++;
} else {
index = 0;
}
} else if (index === boundaryLength) {
...
onParsePartEnd = function () { if (this.destStream) { flushWriteCbs(this); var s = this.destStream; process.nextTick(function() { s.end(); }); } clearPartVars(this); }
...
index = 0;
}
} else if (index - 1 === boundaryLength) {
if (self.partBoundaryFlag) {
index = 0;
if (c === LF) {
self.partBoundaryFlag = false;
self.onParsePartEnd();
self.onParsePartBegin();
state = HEADER_FIELD_START;
break;
}
} else {
index = 0;
}
...
parse = function (req, cb) { var called = false; var self = this; var waitend = true; if (cb) { // if the user supplies a callback, this implies autoFields and autoFiles self.autoFields = true; self.autoFiles = true; // wait for request to end before calling cb var end = function (done) { if (called) return; called = true; // wait for req events to fire process.nextTick(function() { if (waitend && req.readable) { // dump rest of request req.resume(); req.once('end', done); return; } done(); }); }; var fields = {}; var files = {}; self.on('error', function(err) { end(function() { cb(err); }); }); self.on('field', function(name, value) { var fieldsArray = fields[name] || (fields[name] = []); fieldsArray.push(value); }); self.on('file', function(name, file) { var filesArray = files[name] || (files[name] = []); filesArray.push(file); }); self.on('close', function() { end(function() { cb(null, fields, files); }); }); } self.handleError = handleError; self.bytesExpected = getBytesExpected(req.headers); req.on('end', onReqEnd); req.on('error', function(err) { waitend = false; handleError(err); }); req.on('aborted', onReqAborted); var state = req._readableState; if (req._decoder || (state && (state.encoding || state.decoder))) { // this is a binary protocol // if an encoding is set, input is likely corrupted validationError(new Error('request encoding must not be set')); return; } var contentType = req.headers['content-type']; if (!contentType) { validationError(createError(415, 'missing content-type header')); return; } var m = CONTENT_TYPE_RE.exec(contentType); if (!m) { validationError(createError(415, 'unsupported content-type')); return; } var boundary; CONTENT_TYPE_PARAM_RE.lastIndex = m.index + m[0].length - 1; while ((m = CONTENT_TYPE_PARAM_RE.exec(contentType))) { if (m[1].toLowerCase() !== 'boundary') continue; boundary = m[2] || m[3]; break; } if (!boundary) { validationError(createError(400, 'content-type missing boundary')); return; } setUpParser(self, boundary); req.pipe(self); function onReqAborted() { waitend = false; self.emit('aborted'); handleError(new Error("Request aborted")); } function onReqEnd() { waitend = false; } function handleError(err) { var first = !self.error; if (first) { self.error = err; req.removeListener('aborted', onReqAborted); req.removeListener('end', onReqEnd); if (self.destStream) { errorEventQueue(self, self.destStream, err); } } cleanupOpenFiles(self); if (first) { self.emit('error', err); } } function validationError(err) { // handle error on next tick for event listeners to attach process.nextTick(handleError.bind(null, err)) } }
...
var util = require('util');
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
return;
}
...