diff --git a/CHANGELOG.md b/CHANGELOG.md index 51e30ce..09b9ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.4.0-alpha.7 (WIP) * End requests with an error when parsing posted body contents fail +* Make the max body & file size of a request configurable (per route) ## 1.4.0-alpha.6 (2024-09-11) diff --git a/lib/core/alchemy.js b/lib/core/alchemy.js index 7ca43c4..61813ba 100644 --- a/lib/core/alchemy.js +++ b/lib/core/alchemy.js @@ -1745,6 +1745,37 @@ Alchemy.setMethod(function addAppcacheEntry(entry) { ac_entries[entry.type].push(entry); }); +/** + * Get a size limit for the given route + * + * @author Jelle De Loecker + * @since 1.4.0 + * @version 1.4.0 + * + * @param {Route?} route + * @param {string} name + */ +function getRouteSizeLimit(route, name) { + + let global_size = alchemy.settings.network[name]; + + if (!route) { + return global_size; + } + + let route_value = route.options[name]; + + if (route_value == null || typeof route_value != 'number') { + return global_size; + } + + if (route_value <= 0) { + return Infinity; + } + + return route_value; +} + /** * Get the body of an IncomingMessage * @@ -1777,12 +1808,22 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) { let content_type = req.headers['content-type']; + let request_body_size_limit = getRouteSizeLimit(conduit?.route, 'request_body_size_limit'); + // Multipart data is handled by "formidable" if (content_type && content_type.startsWith('multipart/form-data')) { + let request_individual_file_size_limit = getRouteSizeLimit(conduit?.route, 'request_individual_file_size_limit'), + request_total_file_size_limit = getRouteSizeLimit(conduit?.route, 'request_total_file_size_limit'); + let form = new this.formidable.IncomingForm({ multiples : true, hashAlgorithm : this.settings.data_management.file_hash_algorithm || 'sha1', + minFileSize : 0, + allowEmptyFiles : true, + maxFileSize : request_individual_file_size_limit, + maxFieldsSize : request_body_size_limit, + maxTotalFileSize : request_total_file_size_limit, }); form.parse(req, function parsedMultipart(err, form_fields, form_files) { @@ -1838,7 +1879,7 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) { // Regular form-encoded data if (content_type && content_type.indexOf('form-urlencoded') > -1) { - this.url_form_body(req, res, function parsedBody(err) { + this.url_form_body(req, res, {limit: request_body_size_limit}, function parsedBody(err) { if (err) { @@ -1870,7 +1911,7 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) { } // Any other encoded data (like JSON) - this.any_body(req, res, function parsedBody(err, body) { + this.any_body(req, res, {limit: request_body_size_limit}, function parsedBody(err, body) { function handleResponse(err, body) { diff --git a/lib/scripts/create_settings.js b/lib/scripts/create_settings.js index 80f9e05..b38f17f 100644 --- a/lib/scripts/create_settings.js +++ b/lib/scripts/create_settings.js @@ -73,6 +73,24 @@ network.addSetting('use_compression', { description : 'Compress responses using gzip/deflate', }); +network.addSetting('request_body_size_limit', { + type : 'number', + default : 20 * 1024 * 1024, + description : 'Maximum allowed size in bytes for the request body, excluding files', +}); + +network.addSetting('request_individual_file_size_limit', { + type : 'number', + default : 200 * 1024 * 1024, + description : 'Maximum allowed size in bytes for individual uploaded files', +}); + +network.addSetting('request_total_file_size_limit', { + type : 'number', + default : 200 * 1024 * 1024, + description : 'Maximum allowed size in bytes for all uploaded files', +}); + network.addSetting('use_json_dry_responses', { type : 'boolean', default : true,