module.exports = function (req, res, opts, cb) { var form = new Form(); var fields = {}; var files = {}; var result = { fields: fields, files: files }; var counter = 1; form.parse(req); form.on("field", function (key, value) { // BUG: do not handle case where name appears multiple // times in a form. fields[key] = value; console.log('on field', fields, value); }); form.on("part", function (part) { if (part.filename === null) { return; } counter++; opts.handlePart.call(result, part, function (err, result) { if (err) { return cb(err); } files[part.name] = result; finish(); }); }); function finish() { if (--counter === 0) { cb(null, {fields: fields, files: files}); } } form.once("error", cb); form.once("close", finish); };
internals.multipart = function (request, source, next) { var options = request.route.payload; next = Utils.once(next); // Modify next() for async events var form = new Multiparty.Form({ maxFields: Infinity, maxFieldsSize: Infinity, uploadDir: options.uploads }); form.once('error', function (err) { return next(Boom.badRequest('Invalid multipart payload format')); }); var arrayFields = false; var finalize = function (data) { if (arrayFields) { data = Qs.parse(data); } request.payload = data; return next(); }; var data = {}; var set = function (name, value) { arrayFields = arrayFields || (name.indexOf('[') !== -1); if (!data.hasOwnProperty(name)) { data[name] = value; } else if (Array.isArray(data[name])) { data[name].push(value); } else { data[name] = [data[name], value]; } }; var pendingFiles = {}; var nextId = 0; var closed = false; form.on('part', function (part) { if (!part.filename) { return; } if (options.output === 'stream') { // Output: 'stream' set(part.name, part); } else if (options.output === 'data') { // Output: 'data' Nipple.read(part, {}, function (err, payload) { // err handled by form.once('error') var contentType = (part.headers && part.headers['content-type']) || ''; var mime = contentType.split(';')[0].trim().toLowerCase(); if (!mime) { return set(part.name, payload); } if (!payload.length) { return set(part.name, {}); } internals.object(payload, mime, function (err, result) { return set(part.name, err ? payload : result); }); }); } else { // Output: 'file' var id = nextId++; pendingFiles[id] = true; internals.writeFile(part, options.uploads, function (err, path, bytes) { delete pendingFiles[id]; if (err) { return next(err); } var item = { filename: part.filename, path: path, headers: part.headers, bytes: bytes }; set(part.name, item); if (closed) { return finalize(data); } }); } }); form.on('field', function (name, value) { set(name, value); }); form.once('close', function () { form.removeAllListeners(); if (Object.keys(pendingFiles).length) { closed = true; return; } return finalize(data); }); source.headers = source.headers || request.headers; form.parse(source); };
internals.parse = function (payload, mime, headers, options, callback) { callback = Utils.nextTick(callback); // Binary if (mime === 'application/octet-stream') { return callback(null, payload); } // Text if (mime.match(/^text\/.+$/)) { return callback(null, payload.toString('utf-8')); } // JSON if (mime === 'application/json') { return internals.jsonParse(payload, callback); // Isolate try...catch for V8 optimization } // Form-encoded if (mime === 'application/x-www-form-urlencoded') { return callback(null, Querystring.parse(payload.toString('utf-8'))); } // Multipart if (mime === 'multipart/form-data' && options.multipart) { var stream = new internals.Replay(headers, payload); var form = new Multiparty.Form({ encoding: options.multipart.encoding, maxFieldsSize: options.multipart.maxFieldBytes, maxFields: options.multipart.maxFields, uploadDir: options.multipart.uploadDir, hash: options.multipart.hash }); var data = {}; form.once('error', function (err) { return callback(Boom.badRequest('Invalid request multipart payload format')); }); var set = function (name, value) { if (!data.hasOwnProperty(name)) { data[name] = value; } else if (Array.isArray(data[name])) { data[name].push(value); } else { data[name] = [data[name], value]; } }; if (options.multipart.mode === 'stream') { form.on('part', function (part) { if (!part.filename) { return; } part.type = part.headers && part.headers['content-type']; set(part.name, part); }); } else { form.on('file', function (name, file) { var item = { type: file.headers && file.headers['content-type'], fieldName: file.fieldName, originalFilename: file.originalFilename, path: file.path, headers: file.headers, size: file.size }; set(name, item); }); } form.on('field', function (name, value) { set(name, value); }); form.once('close', function () { stream.removeAllListeners(); form.removeAllListeners(); return callback(null, data); }); form.parse(stream); return; } return callback(new Boom.unsupportedMediaType()); };
internals.parse = function (payload, mime, headers, callback) { callback = Utils.nextTick(callback); // Binary if (mime === 'application/octet-stream') { return callback(null, payload); } // Text if (mime.match(/^text\/.+$/)) { return callback(null, payload.toString('utf-8')); } // JSON if (mime === 'application/json') { var obj = null; var error = null; try { obj = JSON.parse(payload.toString('utf-8')); } catch (exp) { error = Boom.badRequest('Invalid request payload format'); } return callback(error, obj); } // Form-encoded if (mime === 'application/x-www-form-urlencoded') { return callback(null, Querystring.parse(payload.toString('utf-8'))); } // Multipart if (mime === 'multipart/form-data') { var stream = new internals.Replay(headers, payload); var form = new Multiparty.Form(); var data = {}; form.once('error', function (err) { return callback(Boom.badRequest('Invalid request multipart payload format')); }); var set = function (name, value) { if (!data.hasOwnProperty(name)) { data[name] = value; } else if (data[name] instanceof Array) { data[name].push(value); } else { data[name] = [data[name], value]; } }; form.on('file', function (name, value) { value.name = value.originalFilename; // For backwards compatibility with formidable value.type = value.headers && value.headers['content-type']; delete value.ws; set(name, value); }); form.on('field', function (name, value) { set(name, value); }); form.once('close', function () { stream.removeAllListeners(); form.removeAllListeners(); return callback(null, data); }); form.parse(stream); return; } return callback(new Boom(415)); // Unsupported Media Type };