diff --git a/examples/with-http.js b/examples/with-http.js
index 4d0fe54a..b19b0712 100644
--- a/examples/with-http.js
+++ b/examples/with-http.js
@@ -1,4 +1,5 @@
import http from 'node:http';
+import slugify from '@sindresorhus/slugify';
import formidable from '../src/index.js';
@@ -14,16 +15,16 @@ const server = http.createServer((req, res) => {
const form = formidable({
// uploadDir: `uploads`,
keepExtensions: true,
- // filename(/*name, ext, part, form*/) {
- // /* name basename of the http originalFilename
- // ext with the dot ".txt" only if keepExtensions is true
- // */
- // // slugify to avoid invalid filenames
- // // substr to define a maximum length
- // // return `${slugify(name)}.${slugify(ext, {separator: ''})}`.substr(0, 100);
- // return 'yo.txt'; // or completly different name
- // // return 'z/yo.txt'; // subdirectory
- // },
+ filename(name, ext, part, form) {
+ /* name basename of the http originalFilename
+ ext with the dot ".txt" only if keepExtensions is true
+ */
+ // slugify to avoid invalid filenames
+ // substr to define a maximum
+ return `${slugify(name)}.${slugify(ext, {separator: ''})}`.substr(0, 100);
+ // return 'yo.txt'; // or completly different name
+ // return 'z/yo.txt'; // subdirectory
+ },
// filter: function ({name, originalFilename, mimetype}) {
// // keep only images
// return mimetype && mimetype.includes("image");
diff --git a/package.json b/package.json
index da21d4e8..32df036b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "formidable",
- "version": "3.2.3",
+ "version": "3.2.4",
"license": "MIT",
"description": "A node.js module for parsing form data, especially file uploads.",
"homepage": "https://github.com/node-formidable/formidable",
@@ -37,6 +37,7 @@
"devDependencies": {
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.3.4",
+ "@sindresorhus/slugify": "^2.1.0",
"@tunnckocore/prettier-config": "1.3.8",
"del-cli": "3.0.0",
"eslint": "6.8.0",
diff --git a/src/Formidable.js b/src/Formidable.js
index b777121a..11aeda3c 100644
--- a/src/Formidable.js
+++ b/src/Formidable.js
@@ -42,6 +42,16 @@ function hasOwnProp(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
+const invalidExtensionChar = (c) => {
+ const code = c.charCodeAt(0);
+ return !(
+ code === 46 || // .
+ (code >= 48 && code <= 57) ||
+ (code >= 65 && code <= 90) ||
+ (code >= 97 && code <= 122)
+ );
+};
+
class IncomingForm extends EventEmitter {
constructor(options = {}) {
super();
@@ -497,6 +507,9 @@ class IncomingForm extends EventEmitter {
return originalFilename;
}
+ // able to get composed extension with multiple dots
+ // "a.b.c" -> ".b.c"
+ // as opposed to path.extname -> ".c"
_getExtension(str) {
if (!str) {
return '';
@@ -505,13 +518,23 @@ class IncomingForm extends EventEmitter {
const basename = path.basename(str);
const firstDot = basename.indexOf('.');
const lastDot = basename.lastIndexOf('.');
- const extname = path.extname(basename).replace(/(\.[a-z0-9]+).*/i, '$1');
+ let rawExtname = path.extname(basename);
- if (firstDot === lastDot) {
- return extname;
+ if (firstDot !== lastDot) {
+ rawExtname = basename.slice(firstDot);
}
- return basename.slice(firstDot, lastDot) + extname;
+ 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;
}
_joinDirectoryName(name) {
diff --git a/src/PersistentFile.js b/src/PersistentFile.js
index fec6a4de..af8c7785 100644
--- a/src/PersistentFile.js
+++ b/src/PersistentFile.js
@@ -1,7 +1,7 @@
/* eslint-disable no-underscore-dangle */
-import { WriteStream, unlink } from 'node:fs';
-import { createHash } from 'node:crypto';
+import fs from 'node:fs';
+import crypto from 'node:crypto';
import { EventEmitter } from 'node:events';
class PersistentFile extends EventEmitter {
@@ -15,14 +15,14 @@ class PersistentFile extends EventEmitter {
this._writeStream = null;
if (typeof this.hashAlgorithm === 'string') {
- this.hash = createHash(this.hashAlgorithm);
+ this.hash = crypto.createHash(this.hashAlgorithm);
} else {
this.hash = null;
}
}
open() {
- this._writeStream = new WriteStream(this.filepath);
+ this._writeStream = fs.createWriteStream(this.filepath);
this._writeStream.on('error', (err) => {
this.emit('error', err);
});
@@ -80,7 +80,7 @@ class PersistentFile extends EventEmitter {
this._writeStream.destroy();
const filepath = this.filepath;
setTimeout(function () {
- unlink(filepath, () => {});
+ fs.unlink(filepath, () => {});
}, 1)
}
}
diff --git a/test/unit/formidable.test.js b/test/unit/formidable.test.js
index 4fd4357e..ab6de22a 100644
--- a/test/unit/formidable.test.js
+++ b/test/unit/formidable.test.js
@@ -65,6 +65,7 @@ function makeHeader(originalFilename) {
const getBasename = (part) => path.basename(form._getNewName(part));
+ // tests below assume baseline hexoid 25 chars + a few more for the extension
let basename = getBasename('fine.jpg?foo=bar');
expect(basename).toHaveLength(29);
let ext = path.extname(basename);
@@ -94,6 +95,16 @@ function makeHeader(originalFilename) {
expect(basename).toHaveLength(30);
ext = path.extname(basename);
expect(ext).toBe('.QxZs');
+
+ basename = getBasename('test.pdf.jqlnn.png');
+ expect(basename).toHaveLength(35);
+ ext = path.extname(basename);
+ expect(ext).toBe('.jqlnn');
+
+ basename = getBasename('test. {