2021-12-03 17:58:48 +01:00

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;