158 lines
5.3 KiB
JavaScript
158 lines
5.3 KiB
JavaScript
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
|
|
|
|
'use strict';
|
|
|
|
var fs = require('fs');
|
|
|
|
var assert = require('assert-plus');
|
|
var formidable = require('formidable');
|
|
var once = require('once');
|
|
var errors = require('restify-errors');
|
|
var vasync = require('vasync');
|
|
|
|
///--- API
|
|
|
|
/**
|
|
* Returns a plugin that will parse the HTTP request body IFF the
|
|
* contentType is multipart/form-data
|
|
*
|
|
* If req.params already contains a given key, that key is skipped and an
|
|
* error is logged.
|
|
*
|
|
* @public
|
|
* @function multipartBodyParser
|
|
* @param {Object} options - an options object
|
|
* @throws {BadRequestError}
|
|
* @returns {Function} Handler
|
|
*/
|
|
function multipartBodyParser(options) {
|
|
var opts = options || {};
|
|
assert.object(opts, 'opts');
|
|
assert.optionalBool(opts.overrideParams, 'opts.overrideParams');
|
|
assert.optionalBool(opts.multiples, 'opts.multiples');
|
|
assert.optionalBool(opts.keepExtensions, 'opts.keepExtensions');
|
|
assert.optionalString(opts.uploadDir, 'opts.uploadDir');
|
|
assert.optionalNumber(opts.maxFieldsSize, 'opts.maxFieldsSize');
|
|
assert.optionalString(opts.hash, 'opts.hash');
|
|
assert.optionalFunc(opts.multipartFileHandler, 'opts.multipartFileHandler');
|
|
assert.optionalFunc(opts.multipartHandler, 'opts.multipartHandler');
|
|
assert.optionalBool(opts.mapParams, 'opts.mapParams');
|
|
assert.optionalBool(opts.mapFiles, 'opts.mapFiles');
|
|
assert.optionalNumber(opts.maxFileSize, 'opts.maxFileSize');
|
|
|
|
var override = opts.overrideParams;
|
|
|
|
function parseMultipartBody(req, res, originalNext) {
|
|
// save original body on req.rawBody and req._body
|
|
req.rawBody = req._body = undefined;
|
|
|
|
var next = once(originalNext);
|
|
|
|
if (
|
|
req.getContentType() !== 'multipart/form-data' ||
|
|
(req.getContentLength() === 0 && !req.isChunked())
|
|
) {
|
|
return next();
|
|
}
|
|
|
|
var form = new formidable.IncomingForm();
|
|
|
|
// enable multiple files on a single upload field
|
|
// (html5 multiple attribute)
|
|
form.multiples = opts.multiples || false;
|
|
form.keepExtensions = opts.keepExtensions ? true : false;
|
|
|
|
if (opts.uploadDir) {
|
|
form.uploadDir = opts.uploadDir;
|
|
}
|
|
|
|
if (opts.maxFieldsSize) {
|
|
form.maxFieldsSize = opts.maxFieldsSize;
|
|
}
|
|
|
|
if (opts.maxFileSize) {
|
|
form.maxFileSize = opts.maxFileSize;
|
|
}
|
|
|
|
if (opts.hash) {
|
|
form.hash = opts.hash;
|
|
}
|
|
|
|
form.onPart = function onPart(part) {
|
|
if (part.filename && opts.multipartFileHandler) {
|
|
opts.multipartFileHandler(part, req);
|
|
} else if (!part.filename && opts.multipartHandler) {
|
|
opts.multipartHandler(part, req);
|
|
} else {
|
|
form.handlePart(part);
|
|
}
|
|
};
|
|
|
|
return form.parse(req, function parse(err, fields, files) {
|
|
if (err) {
|
|
return next(new errors.BadRequestError(err.message));
|
|
}
|
|
|
|
req.body = fields;
|
|
req.files = files;
|
|
|
|
if (opts.mapParams !== false) {
|
|
Object.keys(fields).forEach(function forEach(k) {
|
|
if (req.params[k] && !override) {
|
|
return;
|
|
}
|
|
|
|
req.params[k] = fields[k];
|
|
});
|
|
|
|
if (opts.mapFiles) {
|
|
var barrier = vasync.barrier();
|
|
barrier.on('drain', function onDrain() {
|
|
return next();
|
|
});
|
|
|
|
barrier.start('fs');
|
|
Object.keys(files).forEach(function forEach(f) {
|
|
if (req.params[f] && !override) {
|
|
return;
|
|
}
|
|
barrier.start('fs' + f);
|
|
fs.readFile(files[f].path, function readFile(ex, data) {
|
|
barrier.done('fs' + f);
|
|
/*
|
|
* We want to stop the request here, if there's
|
|
* an error trying to read the file from disk.
|
|
* Ideally we'd like to stop the other oustanding
|
|
* file reads too, but there's no way to cancel
|
|
* in flight fs reads. So we just return an
|
|
* error, and be grudgingly let the other file
|
|
* reads finish.
|
|
*/
|
|
if (ex) {
|
|
return next(
|
|
new errors.InternalError(
|
|
ex,
|
|
'unable to read file' + f
|
|
)
|
|
);
|
|
}
|
|
req.params[f] = data;
|
|
return true;
|
|
});
|
|
});
|
|
barrier.done('fs');
|
|
return false;
|
|
} else {
|
|
return next();
|
|
}
|
|
} else {
|
|
return next();
|
|
}
|
|
});
|
|
}
|
|
|
|
return parseMultipartBody;
|
|
}
|
|
|
|
module.exports = multipartBodyParser;
|