Description
I came across your filename handling and filtering with the CVE-2022-29622 and this issue
#856 (comment)_
First you got blamed also inappropriate by this CVE-2022-29622 whoever is responsible for publishing this without correct approval.
Filenames of forms can have html-tags and js-like-text, like any other form inputs and it is the responsibility of the lib user to handle this, because only he/she knows where this filename is used and what is a safety risc. All this filtering and replacement makes it worse, because the original filename of upload gets lost.
Why I've opened this issue: Now your current code of formidable has some filename filtering which is dysfunctional.
This is an example you can put in e.g. https://replit.com/languages/nodejs
The code is from your current master https://github.com/node-formidable/formidable/blob/master/src/Formidable.js and I've added some asserts to show the problem.
const assert = require('assert');
const path = require('path');
const invalidExtensionChar = (c) => {
const code = c.charCodeAt(0);
return !(
code === 46 || // .
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122)
);
};
function _getFileName(headerValue) {
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
const m = headerValue.match(
/\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i,
);
if (!m) return null;
const match = m[2] || m[3] || '';
let originalFilename = match.substr(match.lastIndexOf('\\') + 1);
originalFilename = originalFilename.replace(/%22/g, '"');
originalFilename = originalFilename.replace(/&#([\d]{4});/g, (_, code) =>
String.fromCharCode(code),
);
return originalFilename;
}
// able to get composed extension with multiple dots
// "a.b.c" -> ".b.c"
// as opposed to path.extname -> ".c"
function _getExtension(str) {
if (!str) {
return '';
}
const basename = path.basename(str);
const firstDot = basename.indexOf('.');
const lastDot = basename.lastIndexOf('.');
let rawExtname = path.extname(basename);
assert(rawExtname === '.txt'); // node knows how to do it
if (firstDot !== lastDot) {
rawExtname = basename.slice(firstDot);
}
let filtered;
const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar);
if (firstInvalidIndex === -1) {
filtered = rawExtname;
} else {
filtered = rawExtname.substring(0, firstInvalidIndex);
}
if (filtered === '.') {
return '';
}
return filtered;
}
let headerValue = 'filename="data-<test@example.com>.txt"';
let filename = _getFileName(headerValue);
let ext = _getExtension(filename);
assert(ext !== '.com', 'wrong extension .com !== .txt: dangerous executable');
assert(ext === '.txt'); // all would be fine