diff --git a/.eslintrc.cjs b/.eslintrc.cjs index fd6e9583..24315567 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,53 +1,53 @@ -'use strict'; +"use strict"; -const airbnbBase = require('eslint-config-airbnb-base'); +const airbnbBase = require("eslint-config-airbnb-base"); // eslint-disable-next-line import/no-dynamic-require const bestPractices = require(airbnbBase.extends[0]); const ignoredProps = bestPractices.rules[ - 'no-param-reassign' + "no-param-reassign" ][1].ignorePropertyModificationsFor.concat( - 'err', - 'x', - '_', - 'opts', - 'options', - 'settings', - 'config', - 'cfg', + "err", + "x", + "_", + "opts", + "options", + "settings", + "config", + "cfg" ); // Additional rules that are specific and overriding previous const additionalChanges = { - strict: 'off', + strict: "off", // Enforce using named functions when regular function is used, // otherwise use arrow functions - 'func-names': ['error', 'always'], + "func-names": ["error", "always"], // Always use parens (for consistency). // https://eslint.org/docs/rules/arrow-parens - 'arrow-parens': ['error', 'always', { requireForBlockBody: true }], - 'prefer-arrow-callback': [ - 'error', + "arrow-parens": ["error", "always", { requireForBlockBody: true }], + "prefer-arrow-callback": [ + "error", { allowNamedFunctions: true, allowUnboundThis: true }, ], // http://eslint.org/docs/rules/max-params - 'max-params': ['error', { max: 3 }], + "max-params": ["error", { max: 3 }], // http://eslint.org/docs/rules/max-statements - 'max-statements': ['error', { max: 20 }], + "max-statements": ["error", { max: 20 }], // http://eslint.org/docs/rules/max-statements-per-line - 'max-statements-per-line': ['error', { max: 1 }], + "max-statements-per-line": ["error", { max: 1 }], // http://eslint.org/docs/rules/max-nested-callbacks - 'max-nested-callbacks': ['error', { max: 4 }], + "max-nested-callbacks": ["error", { max: 4 }], // http://eslint.org/docs/rules/max-depth - 'max-depth': ['error', { max: 4 }], + "max-depth": ["error", { max: 4 }], // enforces no braces where they can be omitted // https://eslint.org/docs/rules/arrow-body-style // Never enable for object literal. - 'arrow-body-style': [ - 'error', - 'as-needed', + "arrow-body-style": [ + "error", + "as-needed", { requireReturnForObjectLiteral: false }, ], // Allow functions to be use before define because: @@ -55,8 +55,8 @@ const additionalChanges = { // 2) because ensure read flow is from top to bottom // 3) logically order of the code. // 4) the only addition is 'typedefs' option, see overrides for TS files - 'no-use-before-define': [ - 'error', + "no-use-before-define": [ + "error", { functions: false, classes: true, @@ -67,8 +67,8 @@ const additionalChanges = { // disallow reassignment of function parameters // disallow parameter object manipulation except for specific exclusions // rule: https://eslint.org/docs/rules/no-param-reassign.html - 'no-param-reassign': [ - 'error', + "no-param-reassign": [ + "error", { props: true, ignorePropertyModificationsFor: ignoredProps, @@ -76,27 +76,27 @@ const additionalChanges = { ], // disallow declaration of variables that are not used in the code - 'no-unused-vars': [ - 'error', + "no-unused-vars": [ + "error", { ignoreRestSiblings: true, // airbnb's default - vars: 'all', // airbnb's default - varsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', - args: 'after-used', // airbnb's default - argsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', + vars: "all", // airbnb's default + varsIgnorePattern: "^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))", + args: "after-used", // airbnb's default + argsIgnorePattern: "^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))", // catch blocks are handled by Unicorns - caughtErrors: 'none', + caughtErrors: "none", // caughtErrorsIgnorePattern: '^(?:$$|xx|_|__|[iI]gnor(?:e|ing|ed))', }, ], }; const importRules = { - 'import/namespace': ['error', { allowComputed: true }], - 'import/no-absolute-path': 'error', - 'import/no-webpack-loader-syntax': 'error', - 'import/no-self-import': 'error', + "import/namespace": ["error", { allowComputed: true }], + "import/no-absolute-path": "error", + "import/no-webpack-loader-syntax": "error", + "import/no-self-import": "error", // Enable this sometime in the future when Node.js has ES2015 module support // 'import/no-cycle': 'error', @@ -104,8 +104,8 @@ const importRules = { // Disabled as it doesn't work with TypeScript // 'import/newline-after-import': 'error', - 'import/no-amd': 'error', - 'import/no-duplicates': 'error', + "import/no-amd": "error", + "import/no-duplicates": "error", // Enable this sometime in the future when Node.js has ES2015 module support // 'import/unambiguous': 'error', @@ -116,10 +116,10 @@ const importRules = { // Looks useful, but too unstable at the moment // 'import/no-deprecated': 'error', - 'import/no-extraneous-dependencies': 'off', - 'import/no-mutable-exports': 'error', - 'import/no-named-as-default-member': 'error', - 'import/no-named-as-default': 'error', + "import/no-extraneous-dependencies": "off", + "import/no-mutable-exports": "error", + "import/no-named-as-default-member": "error", + "import/no-named-as-default": "error", // Disabled because it's buggy and it also doesn't work with TypeScript // 'import/no-unresolved': [ @@ -129,32 +129,32 @@ const importRules = { // } // ], - 'import/order': 'error', - 'import/no-unassigned-import': [ - 'error', - { allow: ['@babel/polyfill', '@babel/register'] }, + "import/order": "error", + "import/no-unassigned-import": [ + "error", + { allow: ["@babel/polyfill", "@babel/register"] }, ], - 'import/prefer-default-export': 'off', + "import/prefer-default-export": "off", // Ensure more web-compat // ! note that it doesn't work in CommonJS // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md - 'import/extensions': 'off', + "import/extensions": "off", // ? Always use named exports. Enable? // 'import/no-default-export': 'error', // ? enable? - 'import/exports-last': 'off', + "import/exports-last": "off", // todo: Enable in future. // Ensures everything is tested (all exports should be used). // For cases when you don't want or can't test, add eslint-ignore comment! // see: https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unused-modules.md - 'import/no-unused-modules': 'off', + "import/no-unused-modules": "off", - 'import/no-useless-path-segments': ['error', { noUselessIndex: false }], + "import/no-useless-path-segments": ["error", { noUselessIndex: false }], }; module.exports = { @@ -165,8 +165,8 @@ module.exports = { node: true, commonjs: true, }, - extends: ['eslint:recommended', 'airbnb-base', 'plugin:prettier/recommended'], - plugins: ['prettier'], + extends: ["eslint:recommended", "airbnb-base", "plugin:prettier/recommended"], + plugins: ["prettier"], rules: { ...additionalChanges, ...importRules, diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 38a96784..d504a4d0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,6 @@ version: 2 updates: - # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 47c50394..7cc44b6e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - - cron: '0 2 * * *' + - cron: "0 2 * * *" jobs: analyze: @@ -32,39 +32,39 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] + language: ["javascript"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - - name: Checkout repository - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v5 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1ada2cea..294042b6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,10 +52,10 @@ name: ci on: push: branches: - - '*' + - "*" pull_request: branches: - - '*' + - "*" jobs: build-and-test: @@ -65,7 +65,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] - node-version: [18, 20, 22, 'lts/*'] + node-version: [18, 20, 22, "lts/*"] steps: - name: Checkout diff --git a/.prettierignore b/.prettierignore index e0d0cdf8..a5ba8285 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,6 +8,8 @@ !*.md* !*.y*ml +pnpm-lock.yaml + !**/src !**/src/** diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 00000000..823cf706 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,23 @@ +"use strict"; + +const config = require("@tunnckocore/prettier-config"); + +module.exports = { + ...config, + overrides: [ + { + files: ["**/*.md*"], + options: { + proseWrap: "always", + printWidth: 80, + }, + }, + { + files: ["**/.all-contributorsrc"], + options: { + parser: "json-stringify", + singleQuote: false, + }, + }, + ], +}; diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 88a5da2e..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const config = require('@tunnckocore/prettier-config'); - -module.exports = { - ...config, - overrides: [ - { - files: ['**/*.md*'], - options: { - proseWrap: 'always', - printWidth: 80, - }, - }, - { - files: ['**/.all-contributorsrc'], - options: { - parser: 'json-stringify', - singleQuote: false, - }, - }, - ], -}; diff --git a/README.md b/README.md index 0efe3ec0..700b0c4c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Code style][codestyle-img]][codestyle-url] [![linux build status][linux-build-img]][build-url] [![macos build status][macos-build-img]][build-url] + If you have any _how-to_ kind of questions, please read the [Contributing @@ -21,6 +22,7 @@ at Twitter. [![Minimum Required Nodejs][nodejs-img]][npmv-url] [![Buy me a Kofi][kofi-img]][kofi-url] [![Make A Pull Request][prs-welcome-img]][prs-welcome-url] + @@ -49,7 +51,7 @@ use._ --> ## Project Status: Maintained > [!NOTE] -> Check [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) for more information on v1, v2, and v3 plans, NPM dist-tags and branches._ +> Check [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) for more information on v1, v2, and v3 plans, NPM dist-tags and branches.\_ This module was initially developed by [**@felixge**](https://github.com/felixge) for @@ -95,7 +97,6 @@ npm install formidable@v3 _**Note:** Future not ready releases will be published on `*-next` dist-tags for the corresponding version._ - ## Examples For more examples look at the `examples/` directory. @@ -106,34 +107,33 @@ Parse an incoming file upload, with the [Node.js's built-in `http` module](https://nodejs.org/api/http.html). ```js -import http from 'node:http'; -import formidable, {errors as formidableErrors} from 'formidable'; +import http from "node:http"; +import formidable, { errors as formidableErrors } from "formidable"; const server = http.createServer(async (req, res) => { - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // parse a file upload const form = formidable({}); let fields; let files; try { - [fields, files] = await form.parse(req); + [fields, files] = await form.parse(req); } catch (err) { - // example to check for a very specific error - if (err.code === formidableErrors.maxFieldsExceeded) { - - } - console.error(err); - res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); - res.end(String(err)); - return; + // example to check for a very specific error + if (err.code === formidableErrors.maxFieldsExceeded) { + } + console.error(err); + res.writeHead(err.httpCode || 400, { "Content-Type": "text/plain" }); + res.end(String(err)); + return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ fields, files }, null, 2)); return; } // show a file upload form - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -145,7 +145,7 @@ const server = http.createServer(async (req, res) => { }); server.listen(8080, () => { - console.log('Server listening on http://localhost:8080/ ...'); + console.log("Server listening on http://localhost:8080/ ..."); }); ``` @@ -159,12 +159,12 @@ Or try the [examples/with-express.js](https://github.com/node-formidable/formidable/blob/master/examples/with-express.js) ```js -import express from 'express'; -import formidable from 'formidable'; +import express from "express"; +import formidable from "formidable"; const app = express(); -app.get('/', (req, res) => { +app.get("/", (req, res) => { res.send(`

With "express" npm package

@@ -175,7 +175,7 @@ app.get('/', (req, res) => { `); }); -app.post('/api/upload', (req, res, next) => { +app.post("/api/upload", (req, res, next) => { const form = formidable({}); form.parse(req, (err, fields, files) => { @@ -188,7 +188,7 @@ app.post('/api/upload', (req, res, next) => { }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); ``` @@ -205,17 +205,17 @@ which is Node.js's Request, and **NOT** the `ctx.request` which is Koa's Request object - there is a difference._ ```js -import Koa from 'Koa'; -import formidable from 'formidable'; +import Koa from "Koa"; +import formidable from "formidable"; const app = new Koa(); -app.on('error', (err) => { - console.error('server error', err); +app.on("error", (err) => { + console.error("server error", err); }); app.use(async (ctx, next) => { - if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { + if (ctx.url === "/api/upload" && ctx.method.toLowerCase() === "post") { const form = formidable({}); // not very elegant, but that's for now if you don't want to use `koa-better-body` @@ -227,7 +227,7 @@ app.use(async (ctx, next) => { return; } - ctx.set('Content-Type', 'application/json'); + ctx.set("Content-Type", "application/json"); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); @@ -239,7 +239,7 @@ app.use(async (ctx, next) => { } // show a file upload form - ctx.set('Content-Type', 'text/html'); + ctx.set("Content-Type", "text/html"); ctx.status = 200; ctx.body = `

With "koa" npm package

@@ -252,12 +252,12 @@ app.use(async (ctx, next) => { }); app.use((ctx) => { - console.log('The next middleware is called'); - console.log('Results:', ctx.state); + console.log("The next middleware is called"); + console.log("Results:", ctx.state); }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); ``` @@ -305,7 +305,7 @@ _Please pass [`options`](#options) to the function/constructor, not by assigning them to the instance `form`_ ```js -import formidable from 'formidable'; +import formidable from "formidable"; const form = formidable(options); ``` @@ -356,13 +356,12 @@ See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js) - `options.createDirsFromUploads` **{boolean}** - default false. If true, makes direct folder uploads possible. Use `` to create a form to upload folders. Has to be used with the options `options.uploadDir` and `options.filename` where `options.filename` has to return a string with the character `/` for folders to be created. The base will be `options.uploadDir`. - -#### `options.filename` **{function}** function (name, ext, part, form) -> string +#### `options.filename` **{function}** function (name, ext, part, form) -> string where part can be decomposed as ```js -const { originalFilename, mimetype} = part; +const { originalFilename, mimetype } = part; ``` _**Note:** If this size of combined fields, or size of some file is exceeded, an @@ -378,16 +377,16 @@ form.bytesReceived; form.bytesExpected; ``` -#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean +#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean Behaves like Array.filter: Returning false will simply ignore the file and go to the next. ```js const options = { - filter: function ({name, originalFilename, mimetype}) { + filter: function ({ name, originalFilename, mimetype }) { // keep only images return mimetype && mimetype.includes("image"); - } + }, }; ``` @@ -396,21 +395,20 @@ const options = { **Note:** use form.emit('error') to make form.parse error ```js -let cancelUploads = false;// create variable at the same scope as form +let cancelUploads = false; // create variable at the same scope as form const options = { - filter: function ({name, originalFilename, mimetype}) { + filter: function ({ name, originalFilename, mimetype }) { // keep only images const valid = mimetype && mimetype.includes("image"); if (!valid) { - form.emit('error', new formidableErrors.default('invalid type', 0, 400)); // optional make form.parse error + form.emit("error", new formidableErrors.default("invalid type", 0, 400)); // optional make form.parse error cancelUploads = true; //variable to make filter return false after the first problem } return valid && !cancelUploads; - } + }, }; ``` - ### .parse(request, ?callback) Parses an incoming Node.js `request` containing form data. If `callback` is not provided a promise is returned. @@ -419,8 +417,8 @@ Parses an incoming Node.js `request` containing form data. If `callback` is not const form = formidable({ uploadDir: __dirname }); form.parse(req, (err, fields, files) => { - console.log('fields:', fields); - console.log('files:', files); + console.log("fields:", fields); + console.log("files:", files); }); // with Promise @@ -433,6 +431,7 @@ processing which would occur otherwise, making you fully responsible for handling the processing. About `uploadDir`, given the following directory structure + ``` project-name ├── src @@ -444,29 +443,25 @@ project-name `__dirname` would be the same directory as the source file itself (src) - ```js - `${__dirname}/../uploads` +`${__dirname}/../uploads`; ``` to put files in uploads. Omitting `__dirname` would make the path relative to the current working directory. This would be the same if server.js is launched from src but not project-name. - `null` will use default which is `os.tmpdir()` -Note: If the directory does not exist, the uploaded files are __silently discarded__. To make sure it exists: +Note: If the directory does not exist, the uploaded files are **silently discarded**. To make sure it exists: ```js -import {createNecessaryDirectoriesSync} from "filesac"; - +import { createNecessaryDirectoriesSync } from "filesac"; const uploadPath = `${__dirname}/../uploads`; createNecessaryDirectoriesSync(`${uploadPath}/x`); ``` - In the example below, we listen on couple of events and direct them to the `data` listener, so you can do whatever you choose there, based on whether its before the file been emitted, the header value, the header name, on field, on @@ -476,49 +471,52 @@ Or the other way could be to just override the `form.onPart` as it's shown a bit later. ```js -form.once('error', console.error); +form.once("error", console.error); -form.on('fileBegin', (formname, file) => { - form.emit('data', { name: 'fileBegin', formname, value: file }); +form.on("fileBegin", (formname, file) => { + form.emit("data", { name: "fileBegin", formname, value: file }); }); -form.on('file', (formname, file) => { - form.emit('data', { name: 'file', formname, value: file }); +form.on("file", (formname, file) => { + form.emit("data", { name: "file", formname, value: file }); }); -form.on('field', (fieldName, fieldValue) => { - form.emit('data', { name: 'field', key: fieldName, value: fieldValue }); +form.on("field", (fieldName, fieldValue) => { + form.emit("data", { name: "field", key: fieldName, value: fieldValue }); }); -form.once('end', () => { - console.log('Done!'); +form.once("end", () => { + console.log("Done!"); }); // If you want to customize whatever you want... -form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => { - if (name === 'partBegin') { - } - if (name === 'partData') { - } - if (name === 'headerField') { - } - if (name === 'headerValue') { - } - if (name === 'headerEnd') { - } - if (name === 'headersEnd') { - } - if (name === 'field') { - console.log('field name:', key); - console.log('field value:', value); - } - if (name === 'file') { - console.log('file:', formname, value); - } - if (name === 'fileBegin') { - console.log('fileBegin:', formname, value); +form.on( + "data", + ({ name, key, value, buffer, start, end, formname, ...more }) => { + if (name === "partBegin") { + } + if (name === "partData") { + } + if (name === "headerField") { + } + if (name === "headerValue") { + } + if (name === "headerEnd") { + } + if (name === "headersEnd") { + } + if (name === "field") { + console.log("field name:", key); + console.log("field value:", value); + } + if (name === "file") { + console.log("file:", formname, value); + } + if (name === "fileBegin") { + console.log("fileBegin:", formname, value); + } } -}); +); ``` ### .use(plugin: Plugin) @@ -546,12 +544,12 @@ const form = formidable({ keepExtensions: true }); form.use((self, options) => { // self === this === form - console.log('woohoo, custom plugin'); + console.log("woohoo, custom plugin"); // do your stuff; check `src/plugins` for inspiration }); form.parse(req, (error, fields, files) => { - console.log('done!'); + console.log("done!"); }); ``` @@ -566,9 +564,9 @@ which is used in [src/plugins/multipart.js](./src/plugins/multipart.js)), then you can remove it from the `options.enabledPlugins`, like so ```js -import formidable, {octetstream, querystring, json} from "formidable"; +import formidable, { octetstream, querystring, json } from "formidable"; const form = formidable({ - hashAlgorithm: 'sha1', + hashAlgorithm: "sha1", enabledPlugins: [octetstream, querystring, json], }); ``` @@ -591,7 +589,7 @@ inspiration, you can for example validate the mime-type. const form = formidable(); form.onPart = (part) => { - part.on('data', (buffer) => { + part.on("data", (buffer) => { // do whatever you want here }); }; @@ -605,7 +603,7 @@ const form = formidable(); form.onPart = function (part) { // let formidable handle only non-file parts - if (part.originalFilename === '' || !part.mimetype) { + if (part.originalFilename === "" || !part.mimetype) { // used internally, please do not override! form._handlePart(part); } @@ -658,7 +656,7 @@ Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar. **Warning** Use this only for server side progress bar. On the client side better use `XMLHttpRequest` with `xhr.upload.onprogress =` ```js -form.on('progress', (bytesReceived, bytesExpected) => {}); +form.on("progress", (bytesReceived, bytesExpected) => {}); ``` #### `'field'` @@ -666,7 +664,7 @@ form.on('progress', (bytesReceived, bytesExpected) => {}); Emitted whenever a field / value pair has been received. ```js -form.on('field', (name, value) => {}); +form.on("field", (name, value) => {}); ``` #### `'fileBegin'` @@ -676,13 +674,13 @@ you want to stream the file to somewhere else while buffering the upload on the file system. ```js -form.on('fileBegin', (formName, file) => { - // accessible here - // formName the name in the form () or http filename for octetstream - // file.originalFilename http filename or null if there was a parsing error - // file.newFilename generated hexoid or what options.filename returned - // file.filepath default pathname as per options.uploadDir and options.filename - // file.filepath = CUSTOM_PATH // to change the final path +form.on("fileBegin", (formName, file) => { + // accessible here + // formName the name in the form () or http filename for octetstream + // file.originalFilename http filename or null if there was a parsing error + // file.newFilename generated hexoid or what options.filename returned + // file.filepath default pathname as per options.uploadDir and options.filename + // file.filepath = CUSTOM_PATH // to change the final path }); ``` @@ -692,10 +690,10 @@ Emitted whenever a field / file pair has been received. `file` is an instance of `File`. ```js -form.on('file', (formname, file) => { - // same as fileBegin, except - // it is too late to change file.filepath - // file.hash is available if options.hash was used +form.on("file", (formname, file) => { + // same as fileBegin, except + // it is too late to change file.filepath + // file.hash is available if options.hash was used }); ``` @@ -708,7 +706,7 @@ experiences an error is automatically paused, you will have to manually call May have `error.httpCode` and `error.code` attached. ```js -form.on('error', (err) => {}); +form.on("error", (err) => {}); ``` #### `'aborted'` @@ -719,7 +717,7 @@ Emitted when the request was aborted by the user. Right now this can be due to a event (needs a change in the node core). ```js -form.on('aborted', () => {}); +form.on("aborted", () => {}); ``` #### `'end'` @@ -728,10 +726,9 @@ Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response. ```js -form.on('end', () => {}); +form.on("end", () => {}); ``` - ### Helpers #### firstValues diff --git a/README_pt_BR.md b/README_pt_BR.md index 06977371..ec80041a 100644 --- a/README_pt_BR.md +++ b/README_pt_BR.md @@ -58,7 +58,7 @@ fechado, mas se você estiver interessado, podemos discuti-lo e adicioná-lo ap - [Rápido (~ 900-2500 mb/seg)](#benchmarks) e analisador multiparte de streaming - Gravar uploads de arquivos automaticamente no disco (opcional, consulte - [`options.fileWriteStreamHandler`](#options)) + [`options.fileWriteStreamHandler`](#options)) - [API de plug-ins](#useplugin-plugin) - permitindo analisadores e plug-ins personalizados - Baixo consumo de memória - Tratamento de erros gracioso @@ -85,7 +85,6 @@ npm install formidable@v3 _**Nota:** Em um futuro próximo, a v3 será publicada na dist-tag `latest` do NPM. Versões futuras não prontas serão publicadas nas dist-tags `*-next` para a versão correspondente._ - ## Exemplos Para mais exemplos veja o diretório `examples/`. @@ -96,11 +95,11 @@ Analisar um upload de arquivo de entrada, com o [Módulo `http` integrado do Node.js](https://nodejs.org/api/http.html). ```js -import http from 'node:http'; -import formidable, {errors as formidableErrors} from 'formidable'; +import http from "node:http"; +import formidable, { errors as formidableErrors } from "formidable"; const server = http.createServer((req, res) => { - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // analisar um upload de arquivo const form = formidable({}); @@ -108,13 +107,12 @@ const server = http.createServer((req, res) => { if (err) { // exemplo para verificar um erro muito específico if (err.code === formidableErrors.maxFieldsExceeded) { - } - res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); + res.writeHead(err.httpCode || 400, { "Content-Type": "text/plain" }); res.end(String(err)); return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ fields, files }, null, 2)); }); @@ -122,7 +120,7 @@ const server = http.createServer((req, res) => { } // mostrar um formulário de upload de arquivo - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -134,7 +132,7 @@ const server = http.createServer((req, res) => { }); server.listen(8080, () => { - console.log('Server listening on http://localhost:8080/ ...'); + console.log("Server listening on http://localhost:8080/ ..."); }); ``` @@ -147,12 +145,12 @@ Ou tente o [examples/with-express.js](https://github.com/node-formidable/formidable/blob/master/examples/with-express.js) ```js -import express from 'express'; -import formidable from 'formidable'; +import express from "express"; +import formidable from "formidable"; const app = express(); -app.get('/', (req, res) => { +app.get("/", (req, res) => { res.send(`

With "express" npm package

@@ -163,7 +161,7 @@ app.get('/', (req, res) => { `); }); -app.post('/api/upload', (req, res, next) => { +app.post("/api/upload", (req, res, next) => { const form = formidable({}); form.parse(req, (err, fields, files) => { @@ -176,7 +174,7 @@ app.post('/api/upload', (req, res, next) => { }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); ``` @@ -193,21 +191,21 @@ que é a solicitação do Node.js e **NÃO** o `ctx.request` que é a solicitaç objeto - há uma diferença._ ```js -import Koa from 'Koa'; -import formidable from 'formidable'; +import Koa from "Koa"; +import formidable from "formidable"; const app = new Koa(); -app.on('error', (err) => { - console.error('server error', err); +app.on("error", (err) => { + console.error("server error", err); }); app.use(async (ctx, next) => { - if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { + if (ctx.url === "/api/upload" && ctx.method.toLowerCase() === "post") { const form = formidable({}); - // não muito elegante, mas é por enquanto se você não quiser usar `koa-better-body` - // ou outros middlewares. + // não muito elegante, mas é por enquanto se você não quiser usar `koa-better-body` + // ou outros middlewares. await new Promise((resolve, reject) => { form.parse(ctx.req, (err, fields, files) => { if (err) { @@ -215,7 +213,7 @@ app.use(async (ctx, next) => { return; } - ctx.set('Content-Type', 'application/json'); + ctx.set("Content-Type", "application/json"); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); @@ -227,7 +225,7 @@ app.use(async (ctx, next) => { } // mostrar um formulário de upload de arquivo - ctx.set('Content-Type', 'text/html'); + ctx.set("Content-Type", "text/html"); ctx.status = 200; ctx.body = `

With "koa" npm package

@@ -240,12 +238,12 @@ app.use(async (ctx, next) => { }); app.use((ctx) => { - console.log('The next middleware is called'); - console.log('Results:', ctx.state); + console.log("The next middleware is called"); + console.log("Results:", ctx.state); }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); ``` @@ -293,7 +291,7 @@ _Por favor, passe [`options`](#options) para a função/construtor, não atribui eles para a instância `form`_ ```js -import formidable from 'formidable'; +import formidable from "formidable"; const form = formidable(options); ``` @@ -334,13 +332,12 @@ Veja seus padrões em [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js) - `options.filter` **{function}** - função padrão que sempre retorna verdadeiro. Use-o para filtrar arquivos antes de serem carregados. Deve retornar um booleano. - -#### `options.filename` **{function}** function (name, ext, part, form) -> string +#### `options.filename` **{function}** function (name, ext, part, form) -> string onde a parte pode ser decomposta como ```js -const { originalFilename, mimetype} = part; +const { originalFilename, mimetype } = part; ``` _**Observação:** Se este tamanho de campos combinados, ou tamanho de algum arquivo for excedido, um @@ -356,20 +353,19 @@ form.bytesReceived; form.bytesExpected; ``` -#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean +#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean **Observação:** use uma variável externa para cancelar todos os uploads no primeiro erro ```js const options = { - filter: function ({name, originalFilename, mimetype}) { + filter: function ({ name, originalFilename, mimetype }) { // manter apenas imagens return mimetype && mimetype.includes("image"); - } + }, }; ``` - ### .parse(request, callback) Analisa uma `request` do Node.js recebida contendo dados de formulário. Se `callback` for @@ -379,8 +375,8 @@ fornecido, todos os campos e arquivos são coletados e passados para o retorno d const form = formidable({ uploadDir: __dirname }); form.parse(req, (err, fields, files) => { - console.log('fields:', fields); - console.log('files:', files); + console.log("fields:", fields); + console.log("files:", files); }); ``` @@ -389,6 +385,7 @@ fluxo de várias partes. Fazer isso desativará qualquer processamento de evento que ocorreria de outra forma, tornando você totalmente responsável por lidar com o processamento. Sobre `uploadDir`, dada a seguinte estrutura de diretório + ``` project-name ├── src @@ -400,29 +397,25 @@ project-name `__dirname` seria o mesmo diretório que o próprio arquivo de origem (src) - ```js - `${__dirname}/../uploads` +`${__dirname}/../uploads`; ``` para colocar arquivos em uploads. Omitir `__dirname` tornaria o caminho relativo ao diretório de trabalho atual. Isso seria o mesmo se server.js fosse iniciado a partir de src, mas não de project-name. - `null` usará o padrão que é `os.tmpdir()` -Nota: Se o diretório não existir, os arquivos carregados são __silenciosamente descartados__. Para ter certeza de que existe: +Nota: Se o diretório não existir, os arquivos carregados são **silenciosamente descartados**. Para ter certeza de que existe: ```js -import {createNecessaryDirectoriesSync} from "filesac"; - +import { createNecessaryDirectoriesSync } from "filesac"; const uploadPath = `${__dirname}/../uploads`; createNecessaryDirectoriesSync(`${uploadPath}/x`); ``` - No exemplo abaixo, escutamos alguns eventos e os direcionamos para o ouvinte `data`, para que você possa fazer o que quiser lá, com base em se é antes do arquivo ser emitido, o valor do cabeçalho, o nome do cabeçalho, no campo , em arquivo e etc. @@ -431,49 +424,52 @@ Ou a outra maneira poderia ser apenas substituir o `form.onPart` como é mostrad mais tarde. ```js -form.once('error', console.error); +form.once("error", console.error); -form.on('fileBegin', (formname, file) => { - form.emit('data', { name: 'fileBegin', formname, value: file }); +form.on("fileBegin", (formname, file) => { + form.emit("data", { name: "fileBegin", formname, value: file }); }); -form.on('file', (formname, file) => { - form.emit('data', { name: 'file', formname, value: file }); +form.on("file", (formname, file) => { + form.emit("data", { name: "file", formname, value: file }); }); -form.on('field', (fieldName, fieldValue) => { - form.emit('data', { name: 'field', key: fieldName, value: fieldValue }); +form.on("field", (fieldName, fieldValue) => { + form.emit("data", { name: "field", key: fieldName, value: fieldValue }); }); -form.once('end', () => { - console.log('Done!'); +form.once("end", () => { + console.log("Done!"); }); // Se você quiser personalizar o que quiser... -form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => { - if (name === 'partBegin') { - } - if (name === 'partData') { - } - if (name === 'headerField') { - } - if (name === 'headerValue') { - } - if (name === 'headerEnd') { - } - if (name === 'headersEnd') { - } - if (name === 'field') { - console.log('field name:', key); - console.log('field value:', value); - } - if (name === 'file') { - console.log('file:', formname, value); - } - if (name === 'fileBegin') { - console.log('fileBegin:', formname, value); +form.on( + "data", + ({ name, key, value, buffer, start, end, formname, ...more }) => { + if (name === "partBegin") { + } + if (name === "partData") { + } + if (name === "headerField") { + } + if (name === "headerValue") { + } + if (name === "headerEnd") { + } + if (name === "headersEnd") { + } + if (name === "field") { + console.log("field name:", key); + console.log("field value:", value); + } + if (name === "file") { + console.log("file:", formname, value); + } + if (name === "fileBegin") { + console.log("fileBegin:", formname, value); + } } -}); +); ``` ### .use(plugin: Plugin) @@ -500,14 +496,15 @@ const form = formidable({ keepExtensions: true }); form.use((self, options) => { // self === this === form - console.log('woohoo, custom plugin'); + console.log("woohoo, custom plugin"); // faça suas coisas; verifique `src/plugins` para inspiração }); form.parse(req, (error, fields, files) => { - console.log('done!'); + console.log("done!"); }); ``` + **Importante observar**, é que dentro do plugin `this.options`, `self.options` e `options` PODEM ou NÃO ser iguais. A melhor prática geral é sempre usar o `this`, para que você possa testar seu plugin mais tarde de forma independente e mais fácil. @@ -519,9 +516,9 @@ que é usado em [src/plugins/multipart.js](./src/plugins/multipart.js)), então você pode removê-lo do `options.enabledPlugins`, assim ```js -import formidable, {octetstream, querystring, json} from "formidable"; +import formidable, { octetstream, querystring, json } from "formidable"; const form = formidable({ - hashAlgorithm: 'sha1', + hashAlgorithm: "sha1", enabledPlugins: [octetstream, querystring, json], }); ``` @@ -544,7 +541,7 @@ inspiração, você pode, por exemplo, validar o tipo mime. const form = formidable(); form.onPart = (part) => { - part.on('data', (buffer) => { + part.on("data", (buffer) => { // faça o que quiser aqui }); }; @@ -558,7 +555,7 @@ const form = formidable(); form.onPart = function (part) { // deixe formidável lidar apenas com partes não arquivadas - if (part.originalFilename === '' || !part.mimetype) { + if (part.originalFilename === "" || !part.mimetype) { // usado internamente, por favor, não substitua! form._handlePart(part); } @@ -605,11 +602,12 @@ o arquivo que é útil para registrar e responder a solicitações. ### Eventos #### `'progress'` + Emitido após cada bloco de entrada de dados que foi analisado. Pode ser usado para rolar sua própria barra de progresso. **Aviso** Use isso apenas para a barra de progresso do lado do servidor. No lado do cliente, é melhor usar `XMLHttpRequest` com `xhr.upload.onprogress =` ```js -form.on('progress', (bytesReceived, bytesExpected) => {}); +form.on("progress", (bytesReceived, bytesExpected) => {}); ``` #### `'field'` @@ -617,7 +615,7 @@ form.on('progress', (bytesReceived, bytesExpected) => {}); Emitido sempre que um par campo/valor é recebido. ```js -form.on('field', (name, value) => {}); +form.on("field", (name, value) => {}); ``` #### `'fileBegin'` @@ -626,13 +624,13 @@ Emitido sempre que um novo arquivo é detectado no fluxo de upload. Use este evento se desejar transmitir o arquivo para outro lugar enquanto armazena o upload no sistema de arquivos. ```js -form.on('fileBegin', (formName, file) => { - // acessível aqui - // formName o nome no formulário () ou http filename para octetstream - // file.originalFilename http filename ou null se houver um erro de análise - // file.newFilename gerou hexoid ou o que options.filename retornou - // file.filepath nome do caminho padrão de acordo com options.uploadDir e options.filename - // file.filepath = CUSTOM_PATH // para alterar o caminho final +form.on("fileBegin", (formName, file) => { + // acessível aqui + // formName o nome no formulário () ou http filename para octetstream + // file.originalFilename http filename ou null se houver um erro de análise + // file.newFilename gerou hexoid ou o que options.filename retornou + // file.filepath nome do caminho padrão de acordo com options.uploadDir e options.filename + // file.filepath = CUSTOM_PATH // para alterar o caminho final }); ``` @@ -642,10 +640,10 @@ Emitido sempre que um par campo/arquivo é recebido. `file` é uma instância de `File`. ```js -form.on('file', (formname, file) => { - // o mesmo que fileBegin, exceto - // é muito tarde para alterar file.filepath - // file.hash está disponível se options.hash foi usado +form.on("file", (formname, file) => { + // o mesmo que fileBegin, exceto + // é muito tarde para alterar file.filepath + // file.hash está disponível se options.hash foi usado }); ``` @@ -658,7 +656,7 @@ apresenta um erro é pausada automaticamente, você terá que chamar manualmente Pode ter `error.httpCode` e `error.code` anexados. ```js -form.on('error', (err) => {}); +form.on("error", (err) => {}); ``` #### `'aborted'` @@ -669,7 +667,7 @@ O evento `error` seguirá. No futuro, haverá um 'timeout' separado evento (precisa de uma mudança no núcleo do nó). ```js -form.on('aborted', () => {}); +form.on("aborted", () => {}); ``` #### `'end'` @@ -678,10 +676,9 @@ Emitido quando toda a solicitação foi recebida e todos os arquivos contidos fo liberados para o disco. Este é um ótimo lugar para você enviar sua resposta. ```js -form.on('end', () => {}); +form.on("end", () => {}); ``` - ### Helpers #### firstValues diff --git a/VERSION_NOTES.md b/VERSION_NOTES.md index 3645f38e..d423b819 100644 --- a/VERSION_NOTES.md +++ b/VERSION_NOTES.md @@ -1,4 +1,3 @@ - # Important Notes for v1, v2, and v3 For more info, check the [CHANGELOG](https://github.com/node-formidable/formidable/blob/master/CHANGELOG.md) on the master branch. @@ -11,14 +10,14 @@ We highly recommend to use `v2` or `v3`. Both are already in use by many, especi - **Status: Not Maintained!** - We won't provide support or accept reports on that version. - **No Backporting:** bugfixes, security fixes, or new features WILL NOT happen! -- Please move to at least **v2**! +- Please move to at least **v2**! - Try with installing `formidable@v2` and if still have the problem - report! ## v2 is going to be deprecated The `v2` is available as `formidable@v2`. The source code is available **only** on [v2 branch][v2branch]. -If you want to use v2, it's recommended to lock and use the v2 dist-tag `formidable@v2-latest`. +If you want to use v2, it's recommended to lock and use the v2 dist-tag `formidable@v2-latest`. **Main Differences from v1:** diff --git a/benchmark/index.js b/benchmark/index.js index 9dbb4c46..2407609d 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,9 +1,8 @@ import assert from "node:assert"; -import MultipartParser from '../src/parsers/Multipart.js'; - +import MultipartParser from "../src/parsers/Multipart.js"; const parser = new MultipartParser(); -const customBoundary = '-----------------------------168072824752491622650073'; +const customBoundary = "-----------------------------168072824752491622650073"; const mb = 1000; // 1GB const buf = createMultipartBuffer(customBoundary, mb * 1024 * 1024); @@ -21,7 +20,7 @@ const calls = { const start = performance.now(); parser.initWithBoundary(customBoundary); -parser.on('data', ({ name }) => { +parser.on("data", ({ name }) => { calls[name] += 1; }); @@ -40,12 +39,12 @@ function createMultipartBuffer(boundary, size) { const tail = `\r\n--${boundary}--\r\n`; const buffer = Buffer.alloc(size); - buffer.write(head, 0, 'ascii'); - buffer.write(tail, buffer.length - tail.length, 'ascii'); + buffer.write(head, 0, "ascii"); + buffer.write(tail, buffer.length - tail.length, "ascii"); return buffer; } -process.on('exit', () => { +process.on("exit", () => { assert.deepStrictEqual(calls, { partBegin: 1, headerField: 1, diff --git a/benchmark/server.js b/benchmark/server.js index f99f176b..918856dd 100644 --- a/benchmark/server.js +++ b/benchmark/server.js @@ -1,17 +1,16 @@ // inital copy of with-http.js // made a copy so that examples can be changed without impacting tests -import http from 'node:http'; -import slugify from '@sindresorhus/slugify'; -import formidable, {errors as formidableErrors} from '../src/index.js'; +import http from "node:http"; +import slugify from "@sindresorhus/slugify"; +import formidable, { errors as formidableErrors } from "../src/index.js"; const server = http.createServer((req, res) => { // handle common internet errors // to avoid server crash - req.on('error', console.error); - res.on('error', console.error); + req.on("error", console.error); + res.on("error", console.error); - - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { const form = formidable({ uploadDir: `benchmark/testuploads`, keepExtensions: true, @@ -20,21 +19,18 @@ const server = http.createServer((req, res) => { form.parse(req, (err, fields, files) => { if (err) { console.error(err); - res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); + res.writeHead(err.httpCode || 400, { "Content-Type": "text/plain" }); res.end(String(err)); return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ fields, files }, null, 2)); }); - - return; } // else not used in tests - }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/express-middleware.js b/examples/express-middleware.js index 2ecb7dd6..bd085ca0 100644 --- a/examples/express-middleware.js +++ b/examples/express-middleware.js @@ -1,24 +1,24 @@ -import express from 'express'; -import formidable from '../src/index.js'; +import express from "express"; +import formidable from "../src/index.js"; const app = express(); // middlewares that populates req.fields and req.body const formMiddleWare = (req, res, next) => { - const form = formidable({}); + const form = formidable({}); - form.parse(req, (err, fields, files) => { - if (err) { - next(err); - return; - } - req.fields = fields; - req.files = files; - next(); - }); + form.parse(req, (err, fields, files) => { + if (err) { + next(err); + return; + } + req.fields = fields; + req.files = files; + next(); + }); }; -app.get('/', (req, res) => { +app.get("/", (req, res) => { res.send(`

With "express" npm package

@@ -30,13 +30,13 @@ app.get('/', (req, res) => { }); // use middleware -app.post('/api/upload', formMiddleWare, (req, res, next) => { - res.json({ - fields: req.fields, - files: req.files, - }); +app.post("/api/upload", formMiddleWare, (req, res, next) => { + res.json({ + fields: req.fields, + files: req.files, + }); }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/forceBuffer.js b/examples/forceBuffer.js index 47411499..e3a8c3a2 100644 --- a/examples/forceBuffer.js +++ b/examples/forceBuffer.js @@ -1,21 +1,20 @@ // warning: forcing file into a Buffer elminates the benefits of using streams and may cause memory overflow -import http from 'node:http'; -import { Buffer } from 'node:buffer' -import { Writable } from 'node:stream'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import { Buffer } from "node:buffer"; +import { Writable } from "node:stream"; +import formidable from "../src/index.js"; const server = http.createServer((req, res) => { - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // parse a file upload const endBuffers = {}; const form = formidable({ fileWriteStreamHandler: (file) => { const chunks = []; - + const writable = new Writable({ - write (chunk, enc, next) { - chunks.push(chunk); + write(chunk, enc, next) { + chunks.push(chunk); next(); }, destroy() { @@ -27,7 +26,7 @@ const server = http.createServer((req, res) => { endBuffers[file.newFilename] = buffer; cb(); }, - }) + }); return writable; }, }); @@ -36,11 +35,11 @@ const server = http.createServer((req, res) => { // available here endBuffers if (err) { console.error(err); - res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); + res.writeHead(err.httpCode || 400, { "Content-Type": "text/plain" }); res.end(String(err)); return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ fields, files }, null, 2)); Object.entries(endBuffers).map(([key, value]) => { @@ -53,7 +52,7 @@ const server = http.createServer((req, res) => { } // show a file upload form - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -65,5 +64,5 @@ const server = http.createServer((req, res) => { }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/json.js b/examples/json.js index dc872fb4..d78a9fbf 100644 --- a/examples/json.js +++ b/examples/json.js @@ -1,12 +1,11 @@ -import http from 'node:http'; -import util from 'node:util'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import util from "node:util"; +import formidable from "../src/index.js"; const PORT = 3000; const server = http.createServer((req, res) => { - if (req.method !== 'POST') { - res.writeHead(200, { 'Content-Type': 'text/plain' }); + if (req.method !== "POST") { + res.writeHead(200, { "Content-Type": "text/plain" }); res.end(`Please POST a JSON payload to http://localhost:${PORT}/`); return; } @@ -15,18 +14,18 @@ const server = http.createServer((req, res) => { const fields = {}; form - .on('error', (err) => { + .on("error", (err) => { console.error(err); - res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.writeHead(500, { "Content-Type": "text/plain" }); res.end(`error:\n\n${util.inspect(err)}`); }) - .on('field', (field, value) => { + .on("field", (field, value) => { console.log(field, value); fields[field] = value; }) - .on('end', () => { + .on("end", () => { console.log('-> post done from "end" event'); - res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.writeHead(200, { "Content-Type": "text/plain" }); res.end(`received fields:\n\n${util.inspect(fields)}`); }); @@ -39,26 +38,26 @@ server.listen(PORT, () => { const body = JSON.stringify({ numbers: [1, 2, 3, 4, 5], - nested: { key: 'some val' }, + nested: { key: "some val" }, }); const request = http.request( { - host: 'localhost', - path: '/', + host: "localhost", + path: "/", port: chosenPort, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'content-length': body.length, + "Content-Type": "application/json", + "content-length": body.length, }, }, (response) => { - console.log('\nServer responded with:'); - console.log('Status:', response.statusCode); + console.log("\nServer responded with:"); + console.log("Status:", response.statusCode); response.pipe(process.stdout); - response.on('end', () => { - console.log('\n'); + response.on("end", () => { + console.log("\n"); process.exit(); }); // const data = ''; @@ -70,7 +69,7 @@ server.listen(PORT, () => { // console.log(data); // process.exit(); // }); - }, + } ); request.end(body); }); diff --git a/examples/log-file-content-to-console.js b/examples/log-file-content-to-console.js index e847c971..61cf2527 100644 --- a/examples/log-file-content-to-console.js +++ b/examples/log-file-content-to-console.js @@ -1,10 +1,9 @@ -import http from 'node:http'; -import { Writable } from 'node:stream'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import { Writable } from "node:stream"; +import formidable from "../src/index.js"; const server = http.createServer((req, res) => { - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // parse a file upload const form = formidable({ fileWriteStreamHandler: (/* file */) => { @@ -27,7 +26,7 @@ const server = http.createServer((req, res) => { } // show a file upload form - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -39,5 +38,5 @@ const server = http.createServer((req, res) => { }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/multipart-parser.js b/examples/multipart-parser.js index ce9bee85..591dd2b3 100644 --- a/examples/multipart-parser.js +++ b/examples/multipart-parser.js @@ -1,36 +1,32 @@ -import { Blob } from 'node:buffer'; -import { Readable } from 'node:stream'; -import { FormData, formDataToBlob } from 'formdata-polyfill/esm.min.js' -import { MultipartParser } from '../src/index.js'; +import { Blob } from "node:buffer"; +import { Readable } from "node:stream"; +import { FormData, formDataToBlob } from "formdata-polyfill/esm.min.js"; +import { MultipartParser } from "../src/index.js"; -const blob1 = new Blob( - ['Content of a.txt.'], - { type: 'text/plain' } -); +const blob1 = new Blob(["Content of a.txt."], { type: "text/plain" }); -const blob2 = new Blob( - ['Content of a.html.'], - { type: 'text/html' } -); +const blob2 = new Blob(["Content of a.html."], { + type: "text/html", +}); const fd = new FormData(); -fd.set('text', 'some text ...'); -fd.set('z', 'text inside z'); -fd.set('file1', blob1, 'a.txt'); -fd.set('file2', blob2, 'a.html'); +fd.set("text", "some text ..."); +fd.set("z", "text inside z"); +fd.set("file1", blob1, "a.txt"); +fd.set("file2", blob2, "a.html"); const multipartParser = new MultipartParser(); -multipartParser.on('data', ({ name, buffer, start, end }) => { +multipartParser.on("data", ({ name, buffer, start, end }) => { console.log(`${name}:`); if (buffer && start && end) { console.log(String(buffer.slice(start, end))); } console.log(); }); -multipartParser.on('error', console.error); +multipartParser.on("error", console.error); const blob = formDataToBlob(fd); -const boundary = blob.type.split('boundary=')[1]; +const boundary = blob.type.split("boundary=")[1]; multipartParser.initWithBoundary(boundary); diff --git a/examples/multiples.js b/examples/multiples.js index 03158492..aa11c02b 100644 --- a/examples/multiples.js +++ b/examples/multiples.js @@ -1,11 +1,10 @@ -import http from 'node:http'; -import os from 'node:os'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import os from "node:os"; +import formidable from "../src/index.js"; const server = http.createServer((req, res) => { - if (req.url === '/') { - res.writeHead(200, { 'Content-Type': 'text/html' }); + if (req.url === "/") { + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`
@@ -26,19 +25,19 @@ const server = http.createServer((req, res) => {
`); - } else if (req.url === '/upload') { + } else if (req.url === "/upload") { const form = formidable({ uploadDir: os.tmpdir() }); form.parse(req, (err, fields, files) => { - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ err, fields, files }, null, 2)); }); } else { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('404'); + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("404"); } }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/store-files-on-s3.js b/examples/store-files-on-s3.js index 018468da..6fee5fcd 100644 --- a/examples/store-files-on-s3.js +++ b/examples/store-files-on-s3.js @@ -1,10 +1,9 @@ // To test this example you have to install aws-sdk nodejs package and create a bucket named "demo-bucket" -import http from 'node:http'; -import { PassThrough } from 'node:stream'; -import AWS from 'aws-sdk'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import { PassThrough } from "node:stream"; +import AWS from "aws-sdk"; +import formidable from "../src/index.js"; const s3Client = new AWS.S3({ credentials: { @@ -17,20 +16,20 @@ const uploadStream = (file) => { const pass = new PassThrough(); s3Client.upload( { - Bucket: 'demo-bucket', + Bucket: "demo-bucket", Key: file.newFilename, Body: pass, }, (err, data) => { console.log(err, data); - }, + } ); return pass; }; const server = http.createServer((req, res) => { - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // parse a file upload const form = formidable({ fileWriteStreamHandler: uploadStream, @@ -45,7 +44,7 @@ const server = http.createServer((req, res) => { } // show a file upload form - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -57,5 +56,5 @@ const server = http.createServer((req, res) => { }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/upload-multiple-files.js b/examples/upload-multiple-files.js index 42eb5aa8..9a48fcb3 100644 --- a/examples/upload-multiple-files.js +++ b/examples/upload-multiple-files.js @@ -1,12 +1,11 @@ -import http from 'node:http'; -import util from 'node:util'; -import os from 'node:os'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import util from "node:util"; +import os from "node:os"; +import formidable from "../src/index.js"; const server = http.createServer((req, res) => { - if (req.url === '/') { - res.writeHead(200, { 'Content-Type': 'text/html' }); + if (req.url === "/") { + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`
@@ -14,35 +13,35 @@ const server = http.createServer((req, res) => {
`); - } else if (req.url === '/upload') { + } else if (req.url === "/upload") { const form = formidable({ uploadDir: os.tmpdir() }); const files = []; const fields = []; form - .on('field', (fieldName, value) => { + .on("field", (fieldName, value) => { console.log(fieldName, value); fields.push({ fieldName, value }); }) - .on('file', (fieldName, file) => { + .on("file", (fieldName, file) => { console.log(fieldName, file); files.push({ fieldName, file }); }) - .on('end', () => { - console.log('-> upload done'); - res.writeHead(200, { 'Content-Type': 'text/plain' }); + .on("end", () => { + console.log("-> upload done"); + res.writeHead(200, { "Content-Type": "text/plain" }); res.write(`received fields:\n\n${util.inspect(fields)}`); - res.write('\n\n'); + res.write("\n\n"); res.end(`received files:\n\n${util.inspect(files)}`); }); form.parse(req); } else { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('404'); + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("404"); } }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/urlencoded-no-enctype.js b/examples/urlencoded-no-enctype.js index db884b1d..876af5d2 100644 --- a/examples/urlencoded-no-enctype.js +++ b/examples/urlencoded-no-enctype.js @@ -1,11 +1,10 @@ -import http from 'node:http'; -import util from 'node:util'; -import formidable from '../src/index.js'; - +import http from "node:http"; +import util from "node:util"; +import formidable from "../src/index.js"; const server = http.createServer((req, res) => { - if (req.url === '/') { - res.writeHead(200, { 'Content-Type': 'text/html' }); + if (req.url === "/") { + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`
Title:
@@ -13,39 +12,39 @@ const server = http.createServer((req, res) => {
`); - } else if (req.url === '/post') { + } else if (req.url === "/post") { const form = formidable(); const fields = []; form - .on('error', (err) => { - console.log('err!', err); - res.writeHead(200, { 'Content-Type': 'text/plain' }); + .on("error", (err) => { + console.log("err!", err); + res.writeHead(200, { "Content-Type": "text/plain" }); res.end(`error:\n\n${util.inspect(err)}`); }) - .on('field', (fieldName, fieldValue) => { - console.log('fieldName:', fieldName); - console.log('fieldValue:', fieldValue); + .on("field", (fieldName, fieldValue) => { + console.log("fieldName:", fieldName); + console.log("fieldValue:", fieldValue); fields.push({ fieldName, fieldValue }); }) - .on('end', () => { + .on("end", () => { console.log('-> post done from "end" event'); - res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.writeHead(200, { "Content-Type": "text/plain" }); res.end(`received fields:\n\n${util.inspect(fields)}`); }); form.parse(req, () => { - console.log('-> post done from callback'); + console.log("-> post done from callback"); // res.writeHead(200, { 'Content-Type': 'text/plain' }); // res.end(`received fields:\n\n${util.inspect(fields)}`); }); } else { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('404'); + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("404"); } }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/with-express.js b/examples/with-express.js index 0c7a8ebf..43dbc6c3 100644 --- a/examples/with-express.js +++ b/examples/with-express.js @@ -1,9 +1,9 @@ -import express from 'express'; -import formidable from '../src/index.js'; +import express from "express"; +import formidable from "../src/index.js"; const app = express(); -app.get('/', (req, res) => { +app.get("/", (req, res) => { res.send(`

With "express" npm package

@@ -14,8 +14,8 @@ app.get('/', (req, res) => { `); }); -app.post('/api/upload', (req, res, next) => { - const form = formidable({ }); +app.post("/api/upload", (req, res, next) => { + const form = formidable({}); form.parse(req, (err, fields, files) => { if (err) { @@ -27,5 +27,5 @@ app.post('/api/upload', (req, res, next) => { }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/with-http.js b/examples/with-http.js index 91d61a3b..5936ca08 100644 --- a/examples/with-http.js +++ b/examples/with-http.js @@ -1,18 +1,17 @@ -import http from 'node:http'; -import slugify from '@sindresorhus/slugify'; -import formidable, {errors as formidableErrors} from '../src/index.js'; +import http from "node:http"; +import slugify from "@sindresorhus/slugify"; +import formidable, { errors as formidableErrors } from "../src/index.js"; const server = http.createServer((req, res) => { // handle common internet errors // to avoid server crash - req.on('error', console.error); - res.on('error', console.error); + req.on("error", console.error); + res.on("error", console.error); - - if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { + if (req.url === "/api/upload" && req.method.toLowerCase() === "post") { // parse a file upload const form = formidable({ - defaultInvalidName: 'invalid', + defaultInvalidName: "invalid", uploadDir: `uploads`, keepExtensions: true, createDirsFromUploads: true, @@ -24,36 +23,39 @@ const server = http.createServer((req, res) => { */ // originalFilename will have slashes with relative path if a // directory was uploaded - const {originalFilename} = part; + const { originalFilename } = part; if (!originalFilename) { - return 'invalid'; + return "invalid"; } - + // return 'yo.txt'; // or completly different name // return 'z/yo.txt'; // subdirectory - return originalFilename.split("/").map((subdir) => { - return slugify(subdir, {separator: ''}); // slugify to avoid invalid filenames - }).join("/").substr(0, 100); // substr to define a maximum + return originalFilename + .split("/") + .map( + (subdir) => slugify(subdir, { separator: "" }) // slugify to avoid invalid filenames + ) + .join("/") + .substr(0, 100); // substr to define a maximum }, - filter: function ({name, originalFilename, mimetype}) { + filter({ name, originalFilename, mimetype }) { return Boolean(originalFilename); // keep only images // return mimetype?.includes("image"); - } + }, // maxTotalFileSize: 4000, // maxFileSize: 1000, - }); form.parse(req, (err, fields, files) => { if (err) { console.error(err); - res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); + res.writeHead(err.httpCode || 400, { "Content-Type": "text/plain" }); res.end(String(err)); return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ fields, files }, null, 2)); }); @@ -61,7 +63,7 @@ const server = http.createServer((req, res) => { } // else show a file upload form - res.writeHead(200, { 'Content-Type': 'text/html' }); + res.writeHead(200, { "Content-Type": "text/html" }); res.end(`

With Node.js "http" module

@@ -81,5 +83,5 @@ const server = http.createServer((req, res) => { }); server.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/examples/with-koa2.js b/examples/with-koa2.js index 336ae076..030d4512 100644 --- a/examples/with-koa2.js +++ b/examples/with-koa2.js @@ -1,15 +1,14 @@ -import Koa from 'koa'; -import formidable from '../src/index.js'; - +import Koa from "koa"; +import formidable from "../src/index.js"; const app = new Koa(); -app.on('error', (err) => { - console.error('server error', err); +app.on("error", (err) => { + console.error("server error", err); }); app.use(async (ctx, next) => { - if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { + if (ctx.url === "/api/upload" && ctx.method.toLowerCase() === "post") { let i = 0; const form = formidable({ keepExtensions: true, @@ -29,7 +28,7 @@ app.use(async (ctx, next) => { return; } - ctx.set('Content-Type', 'application/json'); + ctx.set("Content-Type", "application/json"); ctx.status = 200; ctx.state = { fields, files }; ctx.body = JSON.stringify(ctx.state, null, 2); @@ -41,7 +40,7 @@ app.use(async (ctx, next) => { } // show a file upload form - ctx.set('Content-Type', 'text/html'); + ctx.set("Content-Type", "text/html"); ctx.status = 200; ctx.body = `

With "koa" npm package

@@ -54,10 +53,10 @@ app.use(async (ctx, next) => { }); app.use((ctx) => { - console.log('The next middleware is called'); - console.log('Results:', ctx.state); + console.log("The next middleware is called"); + console.log("Results:", ctx.state); }); app.listen(3000, () => { - console.log('Server listening on http://localhost:3000 ...'); + console.log("Server listening on http://localhost:3000 ..."); }); diff --git a/nyc.config.js b/nyc.config.js index 62984972..c22c7314 100644 --- a/nyc.config.js +++ b/nyc.config.js @@ -1,4 +1,4 @@ -'use strict'; +"use strict"; module.exports = { statements: 70, @@ -6,8 +6,8 @@ module.exports = { functions: 70, lines: 70, - 'check-coverage': true, - exclude: ['test'], - include: ['src'], - reporter: ['text', 'text-summary', 'lcov', 'clover'], + "check-coverage": true, + exclude: ["test"], + include: ["src"], + reporter: ["text", "text-summary", "lcov", "clover"], }; diff --git a/package.json b/package.json index cc93fb22..c4c840fb 100644 --- a/package.json +++ b/package.json @@ -57,15 +57,11 @@ "lint": "pnpm run lint:prepare .", "lint:prepare": "eslint --cache --fix --quiet --format codeframe", "fresh": "rm -rf ./node_modules", - "test-specific": "node --disable-warning=ExperimentalWarning --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/standalone/keep-alive-error.test.js", "test-jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage", "test-node": "node --disable-warning=ExperimentalWarning --test ./test-node/**/*.test.js", - "test-jest:ci": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage", - "test:local": "pnpm run test-node && pnpm run test-jest", - "audit": "pnpm audit --prod --fix", "pretest": "rm -rf ./test/tmp && mkdir ./test/tmp", "test": "pnpm run audit && node --test ./test-node/**/*.test.js && pnpm run test-jest:ci" @@ -75,7 +71,7 @@ "dezalgo": "^1.0.4", "once": "^1.4.0" }, -"packageManager": "pnpm@10.8.1", + "packageManager": "pnpm@10.8.1", "devDependencies": { "@rollup/plugin-commonjs": "^25.0.2", "@rollup/plugin-node-resolve": "^15.1.0", diff --git a/src/Formidable.js b/src/Formidable.js index 0120f732..0b04539d 100644 --- a/src/Formidable.js +++ b/src/Formidable.js @@ -1,30 +1,35 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable no-underscore-dangle */ -import { init as cuid2init } from '@paralleldrive/cuid2'; -import dezalgo from 'dezalgo'; -import { EventEmitter } from 'node:events'; -import fsPromises from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { StringDecoder } from 'node:string_decoder'; -import { Transform } from 'node:stream'; +import { init as cuid2init } from "@paralleldrive/cuid2"; +import dezalgo from "dezalgo"; +import { EventEmitter } from "node:events"; +import fsPromises from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { StringDecoder } from "node:string_decoder"; +import { Transform } from "node:stream"; import { createGunzip, createInflate, createBrotliDecompress, createUnzip, -} from 'node:zlib'; -import once from 'once'; -import FormidableError, * as errors from './FormidableError.js'; -import PersistentFile from './PersistentFile.js'; -import VolatileFile from './VolatileFile.js'; -import DummyParser from './parsers/Dummy.js'; -import MultipartParser from './parsers/Multipart.js'; -import { json, multipart, octetstream, querystring } from './plugins/index.js'; - -const CUID2_FINGERPRINT = `${process.env.NODE_ENV}-${os.platform()}-${os.hostname()}` -const createId = cuid2init({ length: 25, fingerprint: CUID2_FINGERPRINT.toLowerCase() }); +} from "node:zlib"; +import once from "once"; +import FormidableError, * as errors from "./FormidableError.js"; +import PersistentFile from "./PersistentFile.js"; +import VolatileFile from "./VolatileFile.js"; +import DummyParser from "./parsers/Dummy.js"; +import MultipartParser from "./parsers/Multipart.js"; +import { json, multipart, octetstream, querystring } from "./plugins/index.js"; + +const CUID2_FINGERPRINT = `${ + process.env.NODE_ENV +}-${os.platform()}-${os.hostname()}`; +const createId = cuid2init({ + length: 25, + fingerprint: CUID2_FINGERPRINT.toLowerCase(), +}); const DEFAULT_OPTIONS = { maxFields: 1000, @@ -36,12 +41,12 @@ const DEFAULT_OPTIONS = { allowEmptyFiles: false, createDirsFromUploads: false, keepExtensions: false, - encoding: 'utf-8', + encoding: "utf-8", hashAlgorithm: false, uploadDir: os.tmpdir(), enabledPlugins: [octetstream, querystring, multipart, json], fileWriteStreamHandler: null, - defaultInvalidName: 'invalid-name', + defaultInvalidName: "invalid-name", filter(_part) { return true; }, @@ -52,28 +57,27 @@ function hasOwnProp(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); } - const decorateForceSequential = function (promiseCreator) { /* forces a function that returns a promise to be sequential useful for fs for example */ let lastPromise = Promise.resolve(); return async function (...x) { - const promiseWeAreWaitingFor = lastPromise; - let currentPromise; - let callback; - // we need to change lastPromise before await anything, - // otherwise 2 calls might wait the same thing - lastPromise = new Promise(function (resolve) { - callback = resolve; - }); - await promiseWeAreWaitingFor; - currentPromise = promiseCreator(...x); - currentPromise.then(callback).catch(callback); - return currentPromise; + const promiseWeAreWaitingFor = lastPromise; + let currentPromise; + let callback; + // we need to change lastPromise before await anything, + // otherwise 2 calls might wait the same thing + lastPromise = new Promise((resolve) => { + callback = resolve; + }); + await promiseWeAreWaitingFor; + currentPromise = promiseCreator(...x); + currentPromise.then(callback).catch(callback); + return currentPromise; }; }; -const createNecessaryDirectoriesAsync = decorateForceSequential(function (filePath) { +const createNecessaryDirectoriesAsync = decorateForceSequential((filePath) => { const directoryname = path.dirname(filePath); return fsPromises.mkdir(directoryname, { recursive: true }); }); @@ -94,11 +98,11 @@ class IncomingForm extends EventEmitter { this.options = { ...DEFAULT_OPTIONS, ...options }; if (!this.options.maxTotalFileSize) { - this.options.maxTotalFileSize = this.options.maxFileSize + this.options.maxTotalFileSize = this.options.maxFileSize; } const dir = path.resolve( - this.options.uploadDir || this.options.uploaddir || os.tmpdir(), + this.options.uploadDir || this.options.uploaddir || os.tmpdir() ); this.uploaddir = dir; @@ -106,13 +110,13 @@ class IncomingForm extends EventEmitter { // initialize with null [ - 'error', - 'headers', - 'type', - 'bytesExpected', - 'bytesReceived', - '_parser', - 'req', + "error", + "headers", + "type", + "bytesExpected", + "bytesReceived", + "_parser", + "req", ].forEach((key) => { this[key] = null; }); @@ -131,8 +135,8 @@ class IncomingForm extends EventEmitter { if (this.options.enabledPlugins.length === 0) { throw new FormidableError( - 'expect at least 1 enabled builtin plugin, see options.enabledPlugins', - errors.missingPlugin, + "expect at least 1 enabled builtin plugin, see options.enabledPlugins", + errors.missingPlugin ); } @@ -147,17 +151,17 @@ class IncomingForm extends EventEmitter { } use(plugin) { - if (typeof plugin !== 'function') { + if (typeof plugin !== "function") { throw new FormidableError( - '.use: expect `plugin` to be a function', - errors.pluginFunction, + ".use: expect `plugin` to be a function", + errors.pluginFunction ); } this._plugins.push(plugin.bind(this)); return this; } - pause () { + pause() { try { this.req.pause(); } catch (err) { @@ -171,7 +175,7 @@ class IncomingForm extends EventEmitter { return true; } - resume () { + resume() { try { this.req.resume(); } catch (err) { @@ -205,14 +209,14 @@ class IncomingForm extends EventEmitter { } else { resolveRef([fields, files]); } - } + }; } const callback = once(dezalgo(cb)); this.fields = Object.create(null); const files = Object.create(null); - this.on('field', (name, value) => { - if (this.type === 'multipart' || this.type === 'urlencoded') { + this.on("field", (name, value) => { + if (this.type === "multipart" || this.type === "urlencoded") { if (!hasOwnProp(this.fields, name)) { this.fields[name] = [value]; } else { @@ -222,51 +226,51 @@ class IncomingForm extends EventEmitter { this.fields[name] = value; } }); - this.on('file', (name, file) => { + this.on("file", (name, file) => { if (!hasOwnProp(files, name)) { files[name] = [file]; } else { files[name].push(file); } }); - this.on('error', (err) => { + this.on("error", (err) => { callback(err, this.fields, files); }); - this.on('end', () => { + this.on("end", () => { callback(null, this.fields, files); }); // Parse headers and setup the parser, ready to start listening for data. await this.writeHeaders(req.headers); - let datafn = (buffer) => { + const datafn = (buffer) => { try { this.write(buffer); } catch (err) { this._error(err); } - } - let endfn = () => { + }; + const endfn = () => { if (this.error) { return; } if (this._parser) { this._parser.end(); } - } + }; let pipe = null; - + // Start listening for data. req - .on('error', (err) => { + .on("error", (err) => { this._error(err); }) - .on('aborted', () => { - this.emit('aborted'); - this._error(new FormidableError('Request aborted', errors.aborted)); - }) + .on("aborted", () => { + this.emit("aborted"); + this._error(new FormidableError("Request aborted", errors.aborted)); + }); - switch (this.headers['content-encoding']) { + switch (this.headers["content-encoding"]) { case "gzip": pipe = createGunzip(); break; @@ -279,7 +283,7 @@ class IncomingForm extends EventEmitter { case "compress": pipe = createUnzip(); break; - + default: pipe = new Transform({ transform(chunk, encoding, callback) { @@ -287,9 +291,9 @@ class IncomingForm extends EventEmitter { }, }); } - pipe.on("data", datafn).on('end', endfn); - req.pipe(pipe) - + pipe.on("data", datafn).on("end", endfn); + req.pipe(pipe); + if (promise) { return promise; } @@ -304,15 +308,15 @@ class IncomingForm extends EventEmitter { if (!this._parser) { this._error( new FormidableError( - 'no parser found', + "no parser found", errors.noParser, - 415, // Unsupported Media Type - ), + 415 // Unsupported Media Type + ) ); return; } - this._parser.once('error', (error) => { + this._parser.once("error", (error) => { this._error(error); }); } @@ -323,13 +327,13 @@ class IncomingForm extends EventEmitter { } if (!this._parser) { this._error( - new FormidableError('uninitialized parser', errors.uninitializedParser), + new FormidableError("uninitialized parser", errors.uninitializedParser) ); return null; } this.bytesReceived += buffer.length; - this.emit('progress', this.bytesReceived, this.bytesExpected); + this.emit("progress", this.bytesReceived, this.bytesExpected); this._parser.write(buffer); @@ -342,12 +346,12 @@ class IncomingForm extends EventEmitter { } async _handlePart(part) { - if (part.originalFilename && typeof part.originalFilename !== 'string') { + if (part.originalFilename && typeof part.originalFilename !== "string") { this._error( new FormidableError( `the part.originalFilename should be string when it exists`, - errors.filenameNotString, - ), + errors.filenameNotString + ) ); return; } @@ -363,28 +367,28 @@ class IncomingForm extends EventEmitter { // ? NOTE(@tunnckocore): or even better, if there is no mimetype, then it's for sure a field // ? NOTE(@tunnckocore): originalFilename is an empty string when a field? if (!part.mimetype) { - let value = ''; + let value = ""; const decoder = new StringDecoder( - part.transferEncoding || this.options.encoding, + part.transferEncoding || this.options.encoding ); - part.on('data', (buffer) => { + part.on("data", (buffer) => { this._fieldsSize += buffer.length; if (this._fieldsSize > this.options.maxFieldsSize) { this._error( new FormidableError( `options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`, errors.maxFieldsSizeExceeded, - 413, // Payload Too Large - ), + 413 // Payload Too Large + ) ); return; } value += decoder.write(buffer); }); - part.on('end', () => { - this.emit('field', part.name, value); + part.on("end", () => { + this.emit("field", part.name, value); }); return; } @@ -404,15 +408,15 @@ class IncomingForm extends EventEmitter { originalFilename: part.originalFilename, mimetype: part.mimetype, }); - file.on('error', (err) => { + file.on("error", (err) => { this._error(err); }); - this.emit('fileBegin', part.name, file); + this.emit("fileBegin", part.name, file); file.open(); this.openedFiles.push(file); - part.on('data', (buffer) => { + part.on("data", (buffer) => { this._totalFileSize += buffer.length; fileSize += buffer.length; @@ -421,8 +425,8 @@ class IncomingForm extends EventEmitter { new FormidableError( `options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`, errors.biggerThanTotalMaxFileSize, - 413, - ), + 413 + ) ); return; } @@ -435,14 +439,14 @@ class IncomingForm extends EventEmitter { }); }); - part.on('end', () => { + part.on("end", () => { if (!this.options.allowEmptyFiles && fileSize === 0) { this._error( new FormidableError( `options.allowEmptyFiles is false, file size should be greater than 0`, errors.noEmptyFiles, - 400, - ), + 400 + ) ); return; } @@ -451,8 +455,8 @@ class IncomingForm extends EventEmitter { new FormidableError( `options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${fileSize} bytes of file data`, errors.smallerThanMinFileSize, - 400, - ), + 400 + ) ); return; } @@ -461,15 +465,15 @@ class IncomingForm extends EventEmitter { new FormidableError( `options.maxFileSize (${this.options.maxFileSize} bytes), received ${fileSize} bytes of file data`, errors.biggerThanMaxFileSize, - 413, - ), + 413 + ) ); return; } file.end(() => { this._flushing -= 1; - this.emit('file', part.name, file); + this.emit("file", part.name, file); this._maybeEnd(); }); }); @@ -482,45 +486,46 @@ class IncomingForm extends EventEmitter { return; } - if (!this.headers['content-type']) { + if (!this.headers["content-type"]) { this._error( new FormidableError( - 'bad content-type header, no content-type', + "bad content-type header, no content-type", errors.missingContentType, - 400, - ), + 400 + ) ); return; } - new DummyParser(this, this.options); const results = []; - await Promise.all(this._plugins.map(async (plugin, idx) => { - let pluginReturn = null; - try { - pluginReturn = await plugin(this, this.options) || this; - } catch (err) { - // directly throw from the `form.parse` method; - // there is no other better way, except a handle through options - const error = new FormidableError( - `plugin on index ${idx} failed with: ${err.message}`, - errors.pluginFailed, - 500, - ); - error.idx = idx; - throw error; - } - Object.assign(this, pluginReturn); + await Promise.all( + this._plugins.map(async (plugin, idx) => { + let pluginReturn = null; + try { + pluginReturn = (await plugin(this, this.options)) || this; + } catch (err) { + // directly throw from the `form.parse` method; + // there is no other better way, except a handle through options + const error = new FormidableError( + `plugin on index ${idx} failed with: ${err.message}`, + errors.pluginFailed, + 500 + ); + error.idx = idx; + throw error; + } + Object.assign(this, pluginReturn); - // todo: use Set/Map and pass plugin name instead of the `idx` index - this.emit('plugin', idx, pluginReturn); - })); - this.emit('pluginsResults', results); + // todo: use Set/Map and pass plugin name instead of the `idx` index + this.emit("plugin", idx, pluginReturn); + }) + ); + this.emit("pluginsResults", results); } - _error(err, eventName = 'error') { + _error(err, eventName = "error") { if (this.error || this.ended) { return; } @@ -536,14 +541,14 @@ class IncomingForm extends EventEmitter { _parseContentLength() { this.bytesReceived = 0; - if (this.headers['content-length']) { - this.bytesExpected = parseInt(this.headers['content-length'], 10); - } else if (this.headers['transfer-encoding'] === undefined) { + if (this.headers["content-length"]) { + this.bytesExpected = parseInt(this.headers["content-length"], 10); + } else if (this.headers["transfer-encoding"] === undefined) { this.bytesExpected = 0; } if (this.bytesExpected !== null) { - this.emit('progress', this.bytesReceived, this.bytesExpected); + this.emit("progress", this.bytesReceived, this.bytesExpected); } } @@ -566,11 +571,13 @@ class IncomingForm extends EventEmitter { try { await createNecessaryDirectoriesAsync(filepath); } catch (errorCreatingDir) { - this._error(new FormidableError( - `cannot create directory`, - errors.cannotCreateDir, - 409, - )); + this._error( + new FormidableError( + `cannot create directory`, + errors.cannotCreateDir, + 409 + ) + ); } } return new PersistentFile({ @@ -585,15 +592,15 @@ class IncomingForm extends EventEmitter { _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, + /\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i ); if (!m) return null; - const match = m[2] || m[3] || ''; - let originalFilename = match.substr(match.lastIndexOf('\\') + 1); + 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), + String.fromCharCode(code) ); return originalFilename; @@ -604,27 +611,29 @@ class IncomingForm extends EventEmitter { // as opposed to path.extname -> ".c" _getExtension(str) { if (!str) { - return ''; + return ""; } const basename = path.basename(str); - const firstDot = basename.indexOf('.'); - const lastDot = basename.lastIndexOf('.'); + const firstDot = basename.indexOf("."); + const lastDot = basename.lastIndexOf("."); let rawExtname = path.extname(basename); if (firstDot !== lastDot) { - rawExtname = basename.slice(firstDot); + rawExtname = basename.slice(firstDot); } let filtered; - const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar); + const firstInvalidIndex = Array.from(rawExtname).findIndex( + invalidExtensionChar + ); if (firstInvalidIndex === -1) { filtered = rawExtname; } else { filtered = rawExtname.substring(0, firstInvalidIndex); } - if (filtered === '.') { - return ''; + if (filtered === ".") { + return ""; } return filtered; } @@ -636,7 +645,10 @@ class IncomingForm extends EventEmitter { // prevent directory traversal attacks // use resolvedDir + path.sep to avoid prefix collisions with sibling directories // e.g. uploadDir "/tmp/uploads" should not allow writes to "/tmp/uploads-evil/" - if (resolvedPath === resolvedDir || !resolvedPath.startsWith(resolvedDir + path.sep)) { + if ( + resolvedPath === resolvedDir || + !resolvedPath.startsWith(resolvedDir + path.sep) + ) { return path.join(this.uploadDir, this.options.defaultInvalidName); } @@ -644,16 +656,16 @@ class IncomingForm extends EventEmitter { } _setUpRename() { - const hasRename = typeof this.options.filename === 'function'; + const hasRename = typeof this.options.filename === "function"; if (hasRename) { this._getNewName = (part) => { - let ext = ''; + let ext = ""; let name = this.options.defaultInvalidName; if (part.originalFilename) { // can be null ({ ext, name } = path.parse(part.originalFilename)); if (this.options.keepExtensions !== true) { - ext = ''; + ext = ""; } } return this.options.filename.call(this, name, ext, part, this); @@ -664,7 +676,7 @@ class IncomingForm extends EventEmitter { if (part && this.options.keepExtensions) { const originalFilename = - typeof part === 'string' ? part : part.originalFilename; + typeof part === "string" ? part : part.originalFilename; return `${name}${this._getExtension(originalFilename)}`; } @@ -676,15 +688,15 @@ class IncomingForm extends EventEmitter { _setUpMaxFields() { if (this.options.maxFields !== Infinity) { let fieldsCount = 0; - this.on('field', () => { + this.on("field", () => { fieldsCount += 1; if (fieldsCount > this.options.maxFields) { this._error( new FormidableError( `options.maxFields (${this.options.maxFields}) exceeded`, errors.maxFieldsExceeded, - 413, - ), + 413 + ) ); } }); @@ -694,15 +706,15 @@ class IncomingForm extends EventEmitter { _setUpMaxFiles() { if (this.options.maxFiles !== Infinity) { let fileCount = 0; - this.on('fileBegin', () => { + this.on("fileBegin", () => { fileCount += 1; if (fileCount > this.options.maxFiles) { this._error( new FormidableError( `options.maxFiles (${this.options.maxFiles}) exceeded`, errors.maxFilesExceeded, - 413, - ), + 413 + ) ); } }); @@ -714,7 +726,7 @@ class IncomingForm extends EventEmitter { return; } this.req = null; - this.emit('end'); + this.emit("end"); } } diff --git a/src/PersistentFile.js b/src/PersistentFile.js index af8c7785..02f2a438 100644 --- a/src/PersistentFile.js +++ b/src/PersistentFile.js @@ -1,20 +1,32 @@ /* eslint-disable no-underscore-dangle */ -import fs from 'node:fs'; -import crypto from 'node:crypto'; -import { EventEmitter } from 'node:events'; +import fs from "node:fs"; +import crypto from "node:crypto"; +import { EventEmitter } from "node:events"; class PersistentFile extends EventEmitter { - constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm }) { + constructor({ + filepath, + newFilename, + originalFilename, + mimetype, + hashAlgorithm, + }) { super(); this.lastModifiedDate = null; - Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm }); + Object.assign(this, { + filepath, + newFilename, + originalFilename, + mimetype, + hashAlgorithm, + }); this.size = 0; this._writeStream = null; - if (typeof this.hashAlgorithm === 'string') { + if (typeof this.hashAlgorithm === "string") { this.hash = crypto.createHash(this.hashAlgorithm); } else { this.hash = null; @@ -23,8 +35,8 @@ class PersistentFile extends EventEmitter { open() { this._writeStream = fs.createWriteStream(this.filepath); - this._writeStream.on('error', (err) => { - this.emit('error', err); + this._writeStream.on("error", (err) => { + this.emit("error", err); }); } @@ -38,7 +50,7 @@ class PersistentFile extends EventEmitter { length: this.length, originalFilename: this.originalFilename, }; - if (this.hash && this.hash !== '') { + if (this.hash && this.hash !== "") { json.hash = this.hash; } return json; @@ -61,27 +73,27 @@ class PersistentFile extends EventEmitter { this._writeStream.write(buffer, () => { this.lastModifiedDate = new Date(); this.size += buffer.length; - this.emit('progress', this.size); + this.emit("progress", this.size); cb(); }); } end(cb) { if (this.hash) { - this.hash = this.hash.digest('hex'); + this.hash = this.hash.digest("hex"); } this._writeStream.end(() => { - this.emit('end'); + this.emit("end"); cb(); }); } destroy() { this._writeStream.destroy(); - const filepath = this.filepath; - setTimeout(function () { - fs.unlink(filepath, () => {}); - }, 1) + const { filepath } = this; + setTimeout(() => { + fs.unlink(filepath, () => {}); + }, 1); } } diff --git a/src/VolatileFile.js b/src/VolatileFile.js index 6f594b0c..cf3c7995 100644 --- a/src/VolatileFile.js +++ b/src/VolatileFile.js @@ -1,19 +1,33 @@ /* eslint-disable no-underscore-dangle */ -import { createHash } from 'node:crypto'; -import { EventEmitter } from 'node:events'; +import { createHash } from "node:crypto"; +import { EventEmitter } from "node:events"; class VolatileFile extends EventEmitter { - constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) { + constructor({ + filepath, + newFilename, + originalFilename, + mimetype, + hashAlgorithm, + createFileWriteStream, + }) { super(); this.lastModifiedDate = null; - Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }); + Object.assign(this, { + filepath, + newFilename, + originalFilename, + mimetype, + hashAlgorithm, + createFileWriteStream, + }); this.size = 0; this._writeStream = null; - if (typeof this.hashAlgorithm === 'string') { + if (typeof this.hashAlgorithm === "string") { this.hash = createHash(this.hashAlgorithm); } else { this.hash = null; @@ -22,8 +36,8 @@ class VolatileFile extends EventEmitter { open() { this._writeStream = this.createFileWriteStream(this); - this._writeStream.on('error', (err) => { - this.emit('error', err); + this._writeStream.on("error", (err) => { + this.emit("error", err); }); } @@ -39,7 +53,7 @@ class VolatileFile extends EventEmitter { originalFilename: this.originalFilename, mimetype: this.mimetype, }; - if (this.hash && this.hash !== '') { + if (this.hash && this.hash !== "") { json.hash = this.hash; } return json; @@ -61,17 +75,17 @@ class VolatileFile extends EventEmitter { this._writeStream.write(buffer, () => { this.size += buffer.length; - this.emit('progress', this.size); + this.emit("progress", this.size); cb(); }); } end(cb) { if (this.hash) { - this.hash = this.hash.digest('hex'); + this.hash = this.hash.digest("hex"); } this._writeStream.end(() => { - this.emit('end'); + this.emit("end"); cb(); }); } diff --git a/src/helpers/firstValues.js b/src/helpers/firstValues.js index 6c9944cc..f3d8c327 100644 --- a/src/helpers/firstValues.js +++ b/src/helpers/firstValues.js @@ -1,5 +1,5 @@ -import { multipartType } from '../plugins/multipart.js'; -import { querystringType } from '../plugins/querystring.js'; +import { multipartType } from "../plugins/multipart.js"; +import { querystringType } from "../plugins/querystring.js"; const firstValues = (form, fields, exceptions = []) => { if (form.type !== querystringType && form.type !== multipartType) { @@ -12,7 +12,7 @@ const firstValues = (form, fields, exceptions = []) => { return [key, value]; } return [key, value[0]]; - }), + }) ); }; diff --git a/src/index.js b/src/index.js index 1c5d9433..c2ec0a18 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,11 @@ -import PersistentFile from './PersistentFile.js'; -import VolatileFile from './VolatileFile.js'; -import Formidable, { DEFAULT_OPTIONS } from './Formidable.js'; - - +import PersistentFile from "./PersistentFile.js"; +import VolatileFile from "./VolatileFile.js"; +import Formidable, { DEFAULT_OPTIONS } from "./Formidable.js"; // make it available without requiring the `new` keyword // if you want it access `const formidable.IncomingForm` as v1 const formidable = (...args) => new Formidable(...args); -const {enabledPlugins} = DEFAULT_OPTIONS; +const { enabledPlugins } = DEFAULT_OPTIONS; export default formidable; export { @@ -17,16 +15,13 @@ export { Formidable, // alias Formidable as IncomingForm, - // as named formidable, - - // misc DEFAULT_OPTIONS as defaultOptions, - enabledPlugins, + enabledPlugins, }; -export * from './parsers/index.js'; -export * from './plugins/index.js'; -export * as errors from './FormidableError.js'; \ No newline at end of file +export * from "./parsers/index.js"; +export * from "./plugins/index.js"; +export * as errors from "./FormidableError.js"; diff --git a/src/parsers/Dummy.js b/src/parsers/Dummy.js index be074a55..dc3992f6 100644 --- a/src/parsers/Dummy.js +++ b/src/parsers/Dummy.js @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; +import { Transform } from "node:stream"; class DummyParser extends Transform { constructor(incomingForm, options = {}) { diff --git a/src/parsers/JSON.js b/src/parsers/JSON.js index 28af758d..610ebea6 100644 --- a/src/parsers/JSON.js +++ b/src/parsers/JSON.js @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; +import { Transform } from "node:stream"; class JSONParser extends Transform { constructor(options = {}) { @@ -16,7 +16,7 @@ class JSONParser extends Transform { _flush(callback) { try { - const fields = JSON.parse(this.chunks.join('')); + const fields = JSON.parse(this.chunks.join("")); this.push(fields); } catch (e) { callback(e); diff --git a/src/parsers/Multipart.js b/src/parsers/Multipart.js index 9d7e4a55..f87636eb 100644 --- a/src/parsers/Multipart.js +++ b/src/parsers/Multipart.js @@ -3,9 +3,9 @@ /* eslint-disable no-plusplus */ /* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; -import * as errors from '../FormidableError.js'; -import FormidableError from '../FormidableError.js'; +import { Transform } from "node:stream"; +import * as errors from "../FormidableError.js"; +import FormidableError from "../FormidableError.js"; let s = 0; const STATE = { @@ -63,7 +63,7 @@ class MultipartParser extends Transform { return new FormidableError( `MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`, errors.malformedMultipart, - 400, + 400 ); } @@ -72,8 +72,8 @@ class MultipartParser extends Transform { (this.state === STATE.HEADER_FIELD_START && this.index === 0) || (this.state === STATE.PART_DATA && this.index === this.boundary.length) ) { - this._handleCallback('partEnd'); - this._handleCallback('end'); + this._handleCallback("partEnd"); + this._handleCallback("end"); done(); } else if (this.state !== STATE.END) { done(this._endUnexpected()); @@ -114,7 +114,7 @@ class MultipartParser extends Transform { let cl = null; const setMark = (name, idx) => { - this[`${name}Mark`] = typeof idx === 'number' ? idx : i; + this[`${name}Mark`] = typeof idx === "number" ? idx : i; }; const clearMarkSymbol = (name) => { @@ -157,12 +157,12 @@ class MultipartParser extends Transform { break; } else if (index - 1 === boundary.length - 2) { if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) { - this._handleCallback('end'); + this._handleCallback("end"); state = STATE.END; flags = 0; } else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) { index = 0; - this._handleCallback('partBegin'); + this._handleCallback("partBegin"); state = STATE.HEADER_FIELD_START; } else { done(this._endUnexpected()); @@ -180,11 +180,11 @@ class MultipartParser extends Transform { break; case STATE.HEADER_FIELD_START: state = STATE.HEADER_FIELD; - setMark('headerField'); + setMark("headerField"); index = 0; case STATE.HEADER_FIELD: if (c === CR) { - clearMarkSymbol('headerField'); + clearMarkSymbol("headerField"); state = STATE.HEADERS_ALMOST_DONE; break; } @@ -200,7 +200,7 @@ class MultipartParser extends Transform { done(this._endUnexpected()); return; } - dataCallback('headerField', true); + dataCallback("headerField", true); state = STATE.HEADER_VALUE_START; break; } @@ -216,19 +216,19 @@ class MultipartParser extends Transform { break; } - setMark('headerValue'); + setMark("headerValue"); state = STATE.HEADER_VALUE; case STATE.HEADER_VALUE: if (c === CR) { - dataCallback('headerValue', true); - this._handleCallback('headerEnd'); + dataCallback("headerValue", true); + this._handleCallback("headerEnd"); state = STATE.HEADER_VALUE_ALMOST_DONE; } break; case STATE.HEADER_VALUE_ALMOST_DONE: if (c !== LF) { done(this._endUnexpected()); -return; + return; } state = STATE.HEADER_FIELD_START; break; @@ -238,12 +238,12 @@ return; return; } - this._handleCallback('headersEnd'); + this._handleCallback("headersEnd"); state = STATE.PART_DATA_START; break; case STATE.PART_DATA_START: state = STATE.PART_DATA; - setMark('partData'); + setMark("partData"); case STATE.PART_DATA: prevIndex = index; @@ -260,7 +260,7 @@ return; if (index < boundary.length) { if (boundary[index] === c) { if (index === 0) { - dataCallback('partData', true); + dataCallback("partData", true); } index++; } else { @@ -283,15 +283,15 @@ return; if (c === LF) { // unset the PART_BOUNDARY flag flags &= ~FBOUNDARY.PART_BOUNDARY; - this._handleCallback('partEnd'); - this._handleCallback('partBegin'); + this._handleCallback("partEnd"); + this._handleCallback("partBegin"); state = STATE.HEADER_FIELD_START; break; } } else if (flags & FBOUNDARY.LAST_BOUNDARY) { if (c === HYPHEN) { - this._handleCallback('partEnd'); - this._handleCallback('end'); + this._handleCallback("partEnd"); + this._handleCallback("end"); state = STATE.END; flags = 0; } else { @@ -309,9 +309,9 @@ return; } else if (prevIndex > 0) { // if our boundary turned out to be rubbish, the captured lookbehind // belongs to partData - this._handleCallback('partData', lookbehind, 0, prevIndex); + this._handleCallback("partData", lookbehind, 0, prevIndex); prevIndex = 0; - setMark('partData'); + setMark("partData"); // reconsider the current character even so it interrupted the sequence // it could be the beginning of a new sequence @@ -327,9 +327,9 @@ return; } } - dataCallback('headerField'); - dataCallback('headerValue'); - dataCallback('partData'); + dataCallback("headerField"); + dataCallback("headerValue"); + dataCallback("partData"); this.index = index; this.state = state; diff --git a/src/parsers/OctetStream.js b/src/parsers/OctetStream.js index 541862e0..753591e9 100644 --- a/src/parsers/OctetStream.js +++ b/src/parsers/OctetStream.js @@ -1,4 +1,4 @@ -import { PassThrough } from 'node:stream'; +import { PassThrough } from "node:stream"; class OctetStreamParser extends PassThrough { constructor(options = {}) { diff --git a/src/parsers/Querystring.js b/src/parsers/Querystring.js index 88267114..63f51ff9 100644 --- a/src/parsers/Querystring.js +++ b/src/parsers/Querystring.js @@ -1,18 +1,18 @@ /* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; +import { Transform } from "node:stream"; // This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser class QuerystringParser extends Transform { constructor(options = {}) { super({ readableObjectMode: true }); this.globalOptions = { ...options }; - this.buffer = ''; + this.buffer = ""; this.bufferLength = 0; } _transform(buffer, encoding, callback) { - this.buffer += buffer.toString('ascii'); + this.buffer += buffer.toString("ascii"); this.bufferLength = this.buffer.length; callback(); } @@ -25,7 +25,7 @@ class QuerystringParser extends Transform { value, }); } - this.buffer = ''; + this.buffer = ""; callback(); } } diff --git a/src/parsers/StreamingQuerystring.js b/src/parsers/StreamingQuerystring.js index 072eb366..e7f6a608 100644 --- a/src/parsers/StreamingQuerystring.js +++ b/src/parsers/StreamingQuerystring.js @@ -1,8 +1,8 @@ // not used /* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; -import FormidableError, { maxFieldsSizeExceeded } from '../FormidableError.js'; +import { Transform } from "node:stream"; +import FormidableError, { maxFieldsSizeExceeded } from "../FormidableError.js"; const AMPERSAND = 38; const EQUALS = 61; @@ -13,10 +13,10 @@ class QuerystringParser extends Transform { const { maxFieldSize } = options; this.maxFieldLength = maxFieldSize; - this.buffer = Buffer.from(''); + this.buffer = Buffer.from(""); this.fieldCount = 0; this.sectionStart = 0; - this.key = ''; + this.key = ""; this.readingKey = true; } @@ -54,11 +54,11 @@ class QuerystringParser extends Transform { callback( new FormidableError( `${ - this.readingKey ? 'Key' : `Value for ${this.key}` - } longer than maxFieldLength`, + this.readingKey ? "Key" : `Value for ${this.key}` + } longer than maxFieldLength` ), maxFieldsSizeExceeded, - 413, + 413 ); } } @@ -79,29 +79,29 @@ class QuerystringParser extends Transform { if (this.readingKey) { // we only have a key if there's something in the buffer. We definitely have no value if (this.buffer && this.buffer.length) { - this.emitField(this.buffer.toString('ascii')); + this.emitField(this.buffer.toString("ascii")); } } else { // We have a key, we may or may not have a value this.emitField( this.key, - this.buffer && this.buffer.length && this.buffer.toString('ascii'), + this.buffer && this.buffer.length && this.buffer.toString("ascii") ); } - this.buffer = ''; + this.buffer = ""; callback(); } getSection(buffer, i) { - if (i === this.sectionStart) return ''; + if (i === this.sectionStart) return ""; - return buffer.toString('ascii', this.sectionStart, i); + return buffer.toString("ascii", this.sectionStart, i); } emitField(key, val) { - this.key = ''; + this.key = ""; this.readingKey = true; - this.push({ key, value: val || '' }); + this.push({ key, value: val || "" }); } } diff --git a/src/parsers/index.js b/src/parsers/index.js index a24b4f12..d72dfe69 100644 --- a/src/parsers/index.js +++ b/src/parsers/index.js @@ -1,8 +1,8 @@ -import JSONParser from './JSON.js'; -import DummyParser from './Dummy.js'; -import MultipartParser from './Multipart.js'; -import OctetStreamParser from './OctetStream.js'; -import QueryStringParser from './Querystring.js'; +import JSONParser from "./JSON.js"; +import DummyParser from "./Dummy.js"; +import MultipartParser from "./Multipart.js"; +import OctetStreamParser from "./OctetStream.js"; +import QueryStringParser from "./Querystring.js"; export { JSONParser, diff --git a/src/plugins/index.js b/src/plugins/index.js index 18318c5f..e102f471 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,6 +1,6 @@ -import octetstream from './octetstream.js'; -import querystring from './querystring.js'; -import multipart from './multipart.js'; -import json from './json.js'; +import octetstream from "./octetstream.js"; +import querystring from "./querystring.js"; +import multipart from "./multipart.js"; +import json from "./json.js"; export { octetstream, querystring, multipart, json }; diff --git a/src/plugins/json.js b/src/plugins/json.js index 9503f0ac..00ef60da 100644 --- a/src/plugins/json.js +++ b/src/plugins/json.js @@ -1,8 +1,8 @@ /* eslint-disable no-underscore-dangle */ -import JSONParser from '../parsers/JSON.js'; +import JSONParser from "../parsers/JSON.js"; -export const jsonType = 'json'; +export const jsonType = "json"; // the `options` is also available through the `this.options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin @@ -11,12 +11,12 @@ export default function plugin(formidable, options) { /* istanbul ignore next */ const self = this || formidable; - if (/^[^;]*json/i.test(self.headers['content-type'])) { + if (/^[^;]*json/i.test(self.headers["content-type"])) { init.call(self, self, options); } return self; -}; +} // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide @@ -26,11 +26,11 @@ function init(_self, _opts) { const parser = new JSONParser(this.options); - parser.on('data', (fields) => { + parser.on("data", (fields) => { this.fields = Object.assign(Object.create(null), fields); }); - parser.once('end', () => { + parser.once("end", () => { this.ended = true; this._maybeEnd(); }); diff --git a/src/plugins/multipart.js b/src/plugins/multipart.js index b87f8235..1cd9c6bf 100644 --- a/src/plugins/multipart.js +++ b/src/plugins/multipart.js @@ -1,11 +1,11 @@ /* eslint-disable no-underscore-dangle */ -import { Stream } from 'node:stream'; -import MultipartParser from '../parsers/Multipart.js'; -import * as errors from '../FormidableError.js'; -import FormidableError from '../FormidableError.js'; +import { Stream } from "node:stream"; +import MultipartParser from "../parsers/Multipart.js"; +import * as errors from "../FormidableError.js"; +import FormidableError from "../FormidableError.js"; -export const multipartType = 'multipart'; +export const multipartType = "multipart"; // the `options` is also available through the `options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin @@ -15,20 +15,20 @@ export default function plugin(formidable, options) { const self = this || formidable; // NOTE: we (currently) support both multipart/form-data and multipart/related - const multipart = /^[^;]*multipart/i.test(self.headers['content-type']); + const multipart = /^[^;]*multipart/i.test(self.headers["content-type"]); if (multipart) { - const m = self.headers['content-type'].match( - /boundary=(?:"([^"]+)"|([^;]+))/i, + const m = self.headers["content-type"].match( + /boundary=(?:"([^"]+)"|([^;]+))/i ); if (m) { const initMultipart = createInitMultipart(m[1] || m[2]); initMultipart.call(self, self, options); // lgtm [js/superfluous-trailing-arguments] } else { const err = new FormidableError( - 'bad content-type header, no multipart boundary', + "bad content-type header, no multipart boundary", errors.missingMultipartBoundary, - 400, + 400 ); self._error(err); } @@ -51,8 +51,8 @@ function createInitMultipart(boundary) { parser.initWithBoundary(boundary); // eslint-disable-next-line max-statements, consistent-return - parser.on('data', async ({ name, buffer, start, end }) => { - if (name === 'partBegin') { + parser.on("data", async ({ name, buffer, start, end }) => { + if (name === "partBegin") { part = new Stream(); part.readable = true; part.headers = {}; @@ -61,65 +61,65 @@ function createInitMultipart(boundary) { part.mimetype = null; part.transferEncoding = this.options.encoding; - part.transferBuffer = ''; + part.transferBuffer = ""; - headerField = ''; - headerValue = ''; - } else if (name === 'headerField') { + headerField = ""; + headerValue = ""; + } else if (name === "headerField") { headerField += buffer.toString(this.options.encoding, start, end); - } else if (name === 'headerValue') { + } else if (name === "headerValue") { headerValue += buffer.toString(this.options.encoding, start, end); - } else if (name === 'headerEnd') { + } else if (name === "headerEnd") { headerField = headerField.toLowerCase(); part.headers[headerField] = headerValue; // matches either a quoted-string or a token (RFC 2616 section 19.5.1) const m = headerValue.match( // eslint-disable-next-line no-useless-escape - /\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i, + /\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i ); - if (headerField === 'content-disposition') { + if (headerField === "content-disposition") { if (m) { - part.name = m[2] || m[3] || ''; + part.name = m[2] || m[3] || ""; } part.originalFilename = this._getFileName(headerValue); - } else if (headerField === 'content-type') { + } else if (headerField === "content-type") { part.mimetype = headerValue; - } else if (headerField === 'content-transfer-encoding') { + } else if (headerField === "content-transfer-encoding") { part.transferEncoding = headerValue.toLowerCase(); } - headerField = ''; - headerValue = ''; - } else if (name === 'headersEnd') { + headerField = ""; + headerValue = ""; + } else if (name === "headersEnd") { switch (part.transferEncoding) { - case 'binary': - case '7bit': - case '8bit': - case 'utf-8': { + case "binary": + case "7bit": + case "8bit": + case "utf-8": { const dataPropagation = (ctx) => { - if (ctx.name === 'partData') { - part.emit('data', ctx.buffer.slice(ctx.start, ctx.end)); + if (ctx.name === "partData") { + part.emit("data", ctx.buffer.slice(ctx.start, ctx.end)); } }; const dataStopPropagation = (ctx) => { - if (ctx.name === 'partEnd') { - part.emit('end'); - parser.off('data', dataPropagation); - parser.off('data', dataStopPropagation); + if (ctx.name === "partEnd") { + part.emit("end"); + parser.off("data", dataPropagation); + parser.off("data", dataStopPropagation); } }; - parser.on('data', dataPropagation); - parser.on('data', dataStopPropagation); + parser.on("data", dataPropagation); + parser.on("data", dataStopPropagation); break; } - case 'base64': { + case "base64": { const dataPropagation = (ctx) => { - if (ctx.name === 'partData') { + if (ctx.name === "partData") { part.transferBuffer += ctx.buffer .slice(ctx.start, ctx.end) - .toString('ascii'); + .toString("ascii"); /* four bytes (chars) in base64 converts to three bytes in binary @@ -129,40 +129,40 @@ function createInitMultipart(boundary) { */ const offset = parseInt(part.transferBuffer.length / 4, 10) * 4; part.emit( - 'data', + "data", Buffer.from( part.transferBuffer.substring(0, offset), - 'base64', - ), + "base64" + ) ); part.transferBuffer = part.transferBuffer.substring(offset); } }; const dataStopPropagation = (ctx) => { - if (ctx.name === 'partEnd') { - part.emit('data', Buffer.from(part.transferBuffer, 'base64')); - part.emit('end'); - parser.off('data', dataPropagation); - parser.off('data', dataStopPropagation); + if (ctx.name === "partEnd") { + part.emit("data", Buffer.from(part.transferBuffer, "base64")); + part.emit("end"); + parser.off("data", dataPropagation); + parser.off("data", dataStopPropagation); } }; - parser.on('data', dataPropagation); - parser.on('data', dataStopPropagation); + parser.on("data", dataPropagation); + parser.on("data", dataStopPropagation); break; } default: return this._error( new FormidableError( - 'unknown transfer-encoding', + "unknown transfer-encoding", errors.unknownTransferEncoding, - 501, - ), + 501 + ) ); } this._parser.pause(); await this.onPart(part); this._parser.resume(); - } else if (name === 'end') { + } else if (name === "end") { this.ended = true; this._maybeEnd(); } diff --git a/src/plugins/octetstream.js b/src/plugins/octetstream.js index 486e332a..8a0b2709 100644 --- a/src/plugins/octetstream.js +++ b/src/plugins/octetstream.js @@ -1,8 +1,8 @@ /* eslint-disable no-underscore-dangle */ -import OctetStreamParser from '../parsers/OctetStream.js'; +import OctetStreamParser from "../parsers/OctetStream.js"; -export const octetStreamType = 'octet-stream'; +export const octetStreamType = "octet-stream"; // the `options` is also available through the `options` / `formidable.options` export default async function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin @@ -11,7 +11,7 @@ export default async function plugin(formidable, options) { /* istanbul ignore next */ const self = this || formidable; - if (/^[^;]*octet-stream/i.test(self.headers['content-type'])) { + if (/^[^;]*octet-stream/i.test(self.headers["content-type"])) { await init.call(self, self, options); } return self; @@ -22,8 +22,8 @@ export default async function plugin(formidable, options) { // to test the plugin you can pass custom `this` context to it (and so `this.options`) async function init(_self, _opts) { this.type = octetStreamType; - const originalFilename = this.headers['x-file-name']; - const mimetype = this.headers['content-type']; + const originalFilename = this.headers["x-file-name"]; + const mimetype = this.headers["content-type"]; const thisPart = { originalFilename, @@ -38,7 +38,7 @@ async function init(_self, _opts) { mimetype, }); - this.emit('fileBegin', originalFilename, file); + this.emit("fileBegin", originalFilename, file); file.open(); this.openedFiles.push(file); this._flushing += 1; @@ -48,7 +48,7 @@ async function init(_self, _opts) { // Keep track of writes that haven't finished so we don't emit the file before it's done being written let outstandingWrites = 0; - this._parser.on('data', (buffer) => { + this._parser.on("data", (buffer) => { this.pause(); outstandingWrites += 1; @@ -57,18 +57,18 @@ async function init(_self, _opts) { this.resume(); if (this.ended) { - this._parser.emit('doneWritingFile'); + this._parser.emit("doneWritingFile"); } }); }); - this._parser.on('end', () => { + this._parser.on("end", () => { this._flushing -= 1; this.ended = true; const done = () => { file.end(() => { - this.emit('file', 'file', file); + this.emit("file", "file", file); this._maybeEnd(); }); }; @@ -76,7 +76,7 @@ async function init(_self, _opts) { if (outstandingWrites === 0) { done(); } else { - this._parser.once('doneWritingFile', done); + this._parser.once("doneWritingFile", done); } }); diff --git a/src/plugins/querystring.js b/src/plugins/querystring.js index 73113bce..bc9df6d2 100644 --- a/src/plugins/querystring.js +++ b/src/plugins/querystring.js @@ -1,9 +1,8 @@ /* eslint-disable no-underscore-dangle */ +import QuerystringParser from "../parsers/Querystring.js"; -import QuerystringParser from '../parsers/Querystring.js'; - -export const querystringType = 'urlencoded'; +export const querystringType = "urlencoded"; // the `options` is also available through the `this.options` / `formidable.options` export default function plugin(formidable, options) { // the `this` context is always formidable, as the first argument of a plugin @@ -12,11 +11,11 @@ export default function plugin(formidable, options) { /* istanbul ignore next */ const self = this || formidable; - if (/^[^;]*urlencoded/i.test(self.headers['content-type'])) { + if (/^[^;]*urlencoded/i.test(self.headers["content-type"])) { init.call(self, self, options); } return self; -}; +} // Note that it's a good practice (but it's up to you) to use the `this.options` instead // of the passed `options` (second) param, because when you decide @@ -26,11 +25,11 @@ function init(_self, _opts) { const parser = new QuerystringParser(this.options); - parser.on('data', ({ key, value }) => { - this.emit('field', key, value); + parser.on("data", ({ key, value }) => { + this.emit("field", key, value); }); - parser.once('end', () => { + parser.once("end", () => { this.ended = true; this._maybeEnd(); }); diff --git a/test/fixture/js/encoding.js b/test/fixture/js/encoding.js index b887db3e..6254aad6 100644 --- a/test/fixture/js/encoding.js +++ b/test/fixture/js/encoding.js @@ -1,50 +1,50 @@ const menu_separator_png_http = [ { - type: 'file', - name: 'image', - originalFilename: 'menu_separator.png', - fixture: 'menu_separator.png', - sha1: 'c845ca3ea794be298f2a1b79769b71939eaf4e54', + type: "file", + name: "image", + originalFilename: "menu_separator.png", + fixture: "menu_separator.png", + sha1: "c845ca3ea794be298f2a1b79769b71939eaf4e54", }, ]; const beta_sticker_1_png_http = [ { - type: 'file', - name: 'sticker', - originalFilename: 'beta-sticker-1.png', - fixture: 'beta-sticker-1.png', - sha1: '6abbcffd12b4ada5a6a084fe9e4584f846331bc4', + type: "file", + name: "sticker", + originalFilename: "beta-sticker-1.png", + fixture: "beta-sticker-1.png", + sha1: "6abbcffd12b4ada5a6a084fe9e4584f846331bc4", }, ]; const blank_gif_http = [ { - type: 'file', - name: 'file', - originalFilename: 'blank.gif', - fixture: 'blank.gif', - sha1: 'a1fdee122b95748d81cee426d717c05b5174fe96', + type: "file", + name: "file", + originalFilename: "blank.gif", + fixture: "blank.gif", + sha1: "a1fdee122b95748d81cee426d717c05b5174fe96", }, ]; const binaryfile_tar_gz_http = [ { - type: 'file', - name: 'file', - originalFilename: 'binaryfile.tar.gz', - fixture: 'binaryfile.tar.gz', - sha1: 'cfabe13b348e5e69287d677860880c52a69d2155', + type: "file", + name: "file", + originalFilename: "binaryfile.tar.gz", + fixture: "binaryfile.tar.gz", + sha1: "cfabe13b348e5e69287d677860880c52a69d2155", }, ]; const plain_txt_http = [ { - type: 'file', - name: 'file', - originalFilename: 'plain.txt', - fixture: 'plain.txt', - sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', + type: "file", + name: "file", + originalFilename: "plain.txt", + fixture: "plain.txt", + sha1: "b31d07bac24ac32734de88b3687dddb10e976872", }, ]; diff --git a/test/fixture/js/misc.js b/test/fixture/js/misc.js index 31cf174e..663d722b 100644 --- a/test/fixture/js/misc.js +++ b/test/fixture/js/misc.js @@ -1,9 +1,9 @@ const boundary_substring_json = [ { - type: 'file', - name: 'upload', - originalFilename: 'plain.txt', - fixture: 'boundary-substring-json', + type: "file", + name: "upload", + originalFilename: "plain.txt", + fixture: "boundary-substring-json", }, ]; diff --git a/test/fixture/js/no-filename.js b/test/fixture/js/no-filename.js index 21320026..8b0cf9ce 100644 --- a/test/fixture/js/no-filename.js +++ b/test/fixture/js/no-filename.js @@ -1,20 +1,20 @@ const generic_http = [ { - type: 'file', - name: 'upload', - originalFilename: '', - fixture: 'generic', - sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', + type: "file", + name: "upload", + originalFilename: "", + fixture: "generic", + sha1: "b31d07bac24ac32734de88b3687dddb10e976872", }, ]; const filename_name_http = [ { - type: 'file', - name: 'upload', - originalFilename: 'plain.txt', - fixture: 'filename-name', - sha1: 'a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2', + type: "file", + name: "upload", + originalFilename: "plain.txt", + fixture: "filename-name", + sha1: "a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2", }, ]; diff --git a/test/fixture/js/preamble.js b/test/fixture/js/preamble.js index aed3d462..8a263608 100644 --- a/test/fixture/js/preamble.js +++ b/test/fixture/js/preamble.js @@ -1,20 +1,20 @@ const crlf_http = [ { - type: 'file', - name: 'upload', - originalFilename: 'plain.txt', - fixture: 'crlf', - sha1: 'b31d07bac24ac32734de88b3687dddb10e976872', + type: "file", + name: "upload", + originalFilename: "plain.txt", + fixture: "crlf", + sha1: "b31d07bac24ac32734de88b3687dddb10e976872", }, ]; const preamble_http = [ { - type: 'file', - name: 'upload', - originalFilename: 'plain.txt', - fixture: 'preamble', - sha1: 'a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2', + type: "file", + name: "upload", + originalFilename: "plain.txt", + fixture: "preamble", + sha1: "a47f7a8a7959f36c3f151ba8b0bd28f2d6b606e2", }, ]; diff --git a/test/fixture/js/special-chars-in-filename.js b/test/fixture/js/special-chars-in-filename.js index 3940cd13..b0f6f517 100644 --- a/test/fixture/js/special-chars-in-filename.js +++ b/test/fixture/js/special-chars-in-filename.js @@ -1,28 +1,40 @@ -const properFilename = 'funkyfilename.txt'; +const properFilename = "funkyfilename.txt"; function expect(originalFilename, fixtureName) { return [ { - type: 'field', - name: 'title', + type: "field", + name: "title", originalFilename: properFilename, fixture: fixtureName, }, { - type: 'file', - name: 'upload', + type: "file", + name: "upload", originalFilename, fixture: fixtureName, }, ]; } -const osx_chrome_13_http = expect(' ? % * | " < > . ? ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'osx-chrome-13'); -const osx_firefox_3_6_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'osx-firefox-3.6'); +const osx_chrome_13_http = expect( + " ? % * | \" < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt", + "osx-chrome-13" +); +const osx_firefox_3_6_http = expect( + " ? % * | \" < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt", + "osx-firefox-3.6" +); -const xp_ie_7_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'xp-ie-7'); -const xp_ie_8_http = expect(' ? % * | " < > . ☃ ; \' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt', 'xp-ie-8'); -const lineSeparator = expect(null, 'line-separator'); +const xp_ie_7_http = expect( + " ? % * | \" < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt", + "xp-ie-7" +); +const xp_ie_8_http = expect( + " ? % * | \" < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt", + "xp-ie-8" +); +const lineSeparator = expect(null, "line-separator"); export { osx_chrome_13_http, diff --git a/test/fixture/js/workarounds.js b/test/fixture/js/workarounds.js index c3ab14b7..f8c78532 100644 --- a/test/fixture/js/workarounds.js +++ b/test/fixture/js/workarounds.js @@ -1,20 +1,20 @@ const missing_hyphens1_http = [ { - type: 'file', - name: 'upload', - originalFilename: 'plain.txt', - fixture: 'missing-hyphens1', - sha1: '8c26b82ec9107e99b3486844644e92558efe0c73', + type: "file", + name: "upload", + originalFilename: "plain.txt", + fixture: "missing-hyphens1", + sha1: "8c26b82ec9107e99b3486844644e92558efe0c73", }, ]; const missing_hyphens2_http = [ { - type: 'file', - name: 'upload', - originalFilename: 'second-plaintext.txt', - fixture: 'missing-hyphens2', - sha1: '798e39a4a1034232ed26e0aadd67f5d1ff10b966', + type: "file", + name: "upload", + originalFilename: "second-plaintext.txt", + fixture: "missing-hyphens2", + sha1: "798e39a4a1034232ed26e0aadd67f5d1ff10b966", }, ]; diff --git a/test/fixture/multipart.js b/test/fixture/multipart.js index 18b9d6d7..a6b4fd68 100644 --- a/test/fixture/multipart.js +++ b/test/fixture/multipart.js @@ -1,76 +1,76 @@ exports.rfc1867 = { - boundary: 'AaB03x', + boundary: "AaB03x", raw: - '--AaB03x\r\n' + + "--AaB03x\r\n" + 'content-disposition: form-data; name="field1"\r\n' + - '\r\n' + - 'Joe Blow\r\nalmost tricked you!\r\n' + - '--AaB03x\r\n' + + "\r\n" + + "Joe Blow\r\nalmost tricked you!\r\n" + + "--AaB03x\r\n" + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + - 'Content-Type: text/plain\r\n' + - '\r\n' + - '... contents of file1.txt ...\r\r\n' + - '--AaB03x--\r\n', + "Content-Type: text/plain\r\n" + + "\r\n" + + "... contents of file1.txt ...\r\r\n" + + "--AaB03x--\r\n", parts: [ { headers: { - 'content-disposition': 'form-data; name="field1"', + "content-disposition": 'form-data; name="field1"', }, - data: 'Joe Blow\r\nalmost tricked you!', + data: "Joe Blow\r\nalmost tricked you!", }, { headers: { - 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', - 'Content-Type': 'text/plain', + "content-disposition": 'form-data; name="pics"; filename="file1.txt"', + "Content-Type": "text/plain", }, - data: '... contents of file1.txt ...\r', + data: "... contents of file1.txt ...\r", }, ], }; -exports['noTrailing\r\n'] = { - boundary: 'AaB03x', +exports["noTrailing\r\n"] = { + boundary: "AaB03x", raw: - '--AaB03x\r\n' + + "--AaB03x\r\n" + 'content-disposition: form-data; name="field1"\r\n' + - '\r\n' + - 'Joe Blow\r\nalmost tricked you!\r\n' + - '--AaB03x\r\n' + + "\r\n" + + "Joe Blow\r\nalmost tricked you!\r\n" + + "--AaB03x\r\n" + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + - 'Content-Type: text/plain\r\n' + - '\r\n' + - '... contents of file1.txt ...\r\r\n' + - '--AaB03x--', + "Content-Type: text/plain\r\n" + + "\r\n" + + "... contents of file1.txt ...\r\r\n" + + "--AaB03x--", parts: [ { headers: { - 'content-disposition': 'form-data; name="field1"', + "content-disposition": 'form-data; name="field1"', }, - data: 'Joe Blow\r\nalmost tricked you!', + data: "Joe Blow\r\nalmost tricked you!", }, { headers: { - 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', - 'Content-Type': 'text/plain', + "content-disposition": 'form-data; name="pics"; filename="file1.txt"', + "Content-Type": "text/plain", }, - data: '... contents of file1.txt ...\r', + data: "... contents of file1.txt ...\r", }, ], }; exports.emptyHeader = { - boundary: 'AaB03x', + boundary: "AaB03x", raw: - '--AaB03x\r\n' + + "--AaB03x\r\n" + 'content-disposition: form-data; name="field1"\r\n' + - ': foo\r\n' + - '\r\n' + - 'Joe Blow\r\nalmost tricked you!\r\n' + - '--AaB03x\r\n' + + ": foo\r\n" + + "\r\n" + + "Joe Blow\r\nalmost tricked you!\r\n" + + "--AaB03x\r\n" + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + - 'Content-Type: text/plain\r\n' + - '\r\n' + - '... contents of file1.txt ...\r\r\n' + - '--AaB03x--\r\n', + "Content-Type: text/plain\r\n" + + "\r\n" + + "... contents of file1.txt ...\r\r\n" + + "--AaB03x--\r\n", expectError: true, }; diff --git a/test/integration/file-write-stream-handler-option.test.js b/test/integration/file-write-stream-handler-option.test.js index aac78e0d..07e78bdf 100644 --- a/test/integration/file-write-stream-handler-option.test.js +++ b/test/integration/file-write-stream-handler-option.test.js @@ -1,30 +1,32 @@ -import { existsSync, mkdirSync, createWriteStream, readdirSync, statSync, unlinkSync, createReadStream } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { createServer, request as _request } from 'node:http'; -import path, { join, dirname } from 'node:path'; -import url from 'node:url'; -import assert, { strictEqual, ok } from 'node:assert'; +import { + existsSync, + mkdirSync, + createWriteStream, + readdirSync, + statSync, + unlinkSync, + createReadStream, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { createServer, request as _request } from "node:http"; +import path, { join, dirname } from "node:path"; +import url from "node:url"; +import assert, { strictEqual, ok } from "node:assert"; -import formidable from '../../src/index.js'; +import formidable from "../../src/index.js"; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 13533; -const DEFAULT_UPLOAD_DIR = join( - tmpdir(), - 'test-store-files-option-default', -); -const CUSTOM_UPLOAD_DIR = join( - tmpdir(), - 'test-store-files-option-custom', -); -const CUSTOM_UPLOAD_FILE_PATH = join(CUSTOM_UPLOAD_DIR, 'test-file'); +const DEFAULT_UPLOAD_DIR = join(tmpdir(), "test-store-files-option-default"); +const CUSTOM_UPLOAD_DIR = join(tmpdir(), "test-store-files-option-custom"); +const CUSTOM_UPLOAD_FILE_PATH = join(CUSTOM_UPLOAD_DIR, "test-file"); const testFilePath = join( dirname(__dirname), - 'fixture', - 'file', - 'binaryfile.tar.gz', + "fixture", + "file", + "binaryfile.tar.gz" ); const createDirs = (dirs) => { @@ -35,13 +37,12 @@ const createDirs = (dirs) => { }); }; -test('file write stream handler', (done) => { +test("file write stream handler", (done) => { const server = createServer((req, res) => { createDirs([DEFAULT_UPLOAD_DIR, CUSTOM_UPLOAD_DIR]); const form = formidable({ uploadDir: DEFAULT_UPLOAD_DIR, - fileWriteStreamHandler: () => - createWriteStream(CUSTOM_UPLOAD_FILE_PATH), + fileWriteStreamHandler: () => createWriteStream(CUSTOM_UPLOAD_FILE_PATH), }); form.parse(req, (err, fields, files) => { @@ -49,7 +50,7 @@ test('file write stream handler', (done) => { const file = files.file[0]; strictEqual(file.size, 301); - strictEqual(typeof file.filepath, 'string'); + strictEqual(typeof file.filepath, "string"); const dirFiles = readdirSync(DEFAULT_UPLOAD_DIR); ok(dirFiles.length === 0); @@ -65,13 +66,13 @@ test('file write stream handler', (done) => { }); server.listen(PORT, (err) => { - assert(!err, 'should not have error, but be falsey'); + assert(!err, "should not have error, but be falsey"); const request = _request({ port: PORT, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/octet-stream', + "Content-Type": "application/octet-stream", }, }); diff --git a/test/integration/fixtures.test.js b/test/integration/fixtures.test.js index 9269ab90..2ceafa06 100644 --- a/test/integration/fixtures.test.js +++ b/test/integration/fixtures.test.js @@ -1,18 +1,13 @@ /* eslint-disable global-require */ /* eslint-disable import/no-dynamic-require */ -import { createReadStream } from 'node:fs'; -import { createConnection } from 'node:net'; -import { join } from 'node:path'; -import { createServer } from 'node:http'; -import { strictEqual } from 'node:assert'; +import { createReadStream } from "node:fs"; +import { createConnection } from "node:net"; +import { join } from "node:path"; +import { createServer } from "node:http"; +import { strictEqual } from "node:assert"; -import formidable from '../../src/index.js'; - -const PORT = 13534; -const CWD = process.cwd(); -const FIXTURES_HTTP = join(CWD, 'test', 'fixture', 'http'); -const UPLOAD_DIR = join(CWD, 'test', 'tmp'); +import formidable from "../../src/index.js"; import * as encoding from "../fixture/js/encoding.js"; import * as misc from "../fixture/js/misc.js"; import * as noFilename from "../fixture/js/no-filename.js"; @@ -20,7 +15,12 @@ import * as preamble from "../fixture/js/preamble.js"; import * as workarounds from "../fixture/js/workarounds.js"; import * as specialCharsInFilename from "../fixture/js/special-chars-in-filename.js"; -const fixtures= { +const PORT = 13534; +const CWD = process.cwd(); +const FIXTURES_HTTP = join(CWD, "test", "fixture", "http"); +const UPLOAD_DIR = join(CWD, "test", "tmp"); + +const fixtures = { encoding, misc, [`no-filename`]: noFilename, @@ -29,22 +29,22 @@ const fixtures= { // workarounds, // todo uncomment this and make it work }; -test('fixtures', (done) => { +test("fixtures", (done) => { const server = createServer(); server.listen(PORT, findFixtures); function findFixtures() { - const results = Object.entries(fixtures).map(([fixtureGroup, fixture]) => { - return Object.entries(fixture).map(([k, v]) => { - return v.map(details => { - return { - fixture: v, - name: `${fixtureGroup}/${details.fixture}.http` - }; - }); - }); - }).flat(Infinity); - testNext(results); + const results = Object.entries(fixtures) + .map(([fixtureGroup, fixture]) => + Object.entries(fixture).map(([k, v]) => + v.map((details) => ({ + fixture: v, + name: `${fixtureGroup}/${details.fixture}.http`, + })) + ) + ) + .flat(Infinity); + testNext(results); } function testNext(results) { @@ -55,7 +55,7 @@ test('fixtures', (done) => { return; } const fixtureName = fixtureWithName.name; - const fixture = fixtureWithName.fixture; + const { fixture } = fixtureWithName; uploadFixture(fixtureName, (err, parts) => { try { @@ -69,16 +69,21 @@ test('fixtures', (done) => { strictEqual(parsedPart.type, expectedPart.type); strictEqual(parsedPart.name, expectedPart.name); - if (parsedPart.type === 'file') { + if (parsedPart.type === "file") { const file = parsedPart.value; - strictEqual(file.originalFilename, expectedPart.originalFilename, - `${JSON.stringify([expectedPart, file])}`); + strictEqual( + file.originalFilename, + expectedPart.originalFilename, + `${JSON.stringify([expectedPart, file])}` + ); if (expectedPart.sha1) { strictEqual( file.hash, expectedPart.sha1, - `SHA1 error ${file.originalFilename} on ${file.filepath} ${JSON.stringify([expectedPart, file])}`, + `SHA1 error ${file.originalFilename} on ${ + file.filepath + } ${JSON.stringify([expectedPart, file])}` ); } } @@ -94,10 +99,10 @@ test('fixtures', (done) => { } function uploadFixture(fixtureName, cb) { - server.once('request', (req, res) => { + server.once("request", (req, res) => { const form = formidable({ uploadDir: UPLOAD_DIR, - hashAlgorithm: 'sha1', + hashAlgorithm: "sha1", }); function callback(...args) { @@ -110,14 +115,14 @@ test('fixtures', (done) => { const parts = []; form - .on('error', callback) - .on('fileBegin', (name, value) => { - parts.push({ type: 'file', name, value }); + .on("error", callback) + .on("fileBegin", (name, value) => { + parts.push({ type: "file", name, value }); }) - .on('field', (name, value) => { - parts.push({ type: 'field', name, value }); + .on("field", (name, value) => { + parts.push({ type: "field", name, value }); }) - .on('end', () => { + .on("end", () => { res.end(); callback(null, parts); }); @@ -131,7 +136,7 @@ test('fixtures', (done) => { file.pipe(socket, { end: false }); - socket.on('data', () => { + socket.on("data", () => { socket.end(); }); } diff --git a/test/integration/json.test.js b/test/integration/json.test.js index 167d1eed..c515f1a3 100644 --- a/test/integration/json.test.js +++ b/test/integration/json.test.js @@ -1,21 +1,21 @@ -import { createServer, request as _request } from 'node:http'; -import assert, { deepStrictEqual } from 'node:assert'; -import formidable from '../../src/index.js'; +import { createServer, request as _request } from "node:http"; +import assert, { deepStrictEqual } from "node:assert"; +import formidable from "../../src/index.js"; const testData = { numbers: [1, 2, 3, 4, 5], - nested: { key: 'val' }, + nested: { key: "val" }, }; const PORT = 13535; -test('json', (done) => { +test("json", (done) => { const server = createServer((req, res) => { - const form = formidable({ }); + const form = formidable({}); form.parse(req, (err, fields) => { deepStrictEqual(fields, { numbers: [1, 2, 3, 4, 5], - nested: { key: 'val' }, + nested: { key: "val" }, }); res.end(); @@ -25,13 +25,13 @@ test('json', (done) => { }); server.listen(PORT, (err) => { - assert(!err, 'should not have error, but be falsey'); + assert(!err, "should not have error, but be falsey"); const request = _request({ port: PORT, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }); diff --git a/test/integration/octet-stream.test.js b/test/integration/octet-stream.test.js index 7f03da9d..b6b6ab11 100644 --- a/test/integration/octet-stream.test.js +++ b/test/integration/octet-stream.test.js @@ -1,10 +1,10 @@ -import { readFileSync, createReadStream } from 'node:fs'; -import { createServer, request as _request } from 'node:http'; -import path, { join, dirname } from 'node:path'; -import url from 'node:url'; -import assert, { strictEqual, deepStrictEqual } from 'node:assert'; +import { readFileSync, createReadStream } from "node:fs"; +import { createServer, request as _request } from "node:http"; +import path, { join, dirname } from "node:path"; +import url from "node:url"; +import assert, { strictEqual, deepStrictEqual } from "node:assert"; -import formidable from '../../src/index.js'; +import formidable from "../../src/index.js"; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -12,12 +12,12 @@ const __dirname = path.dirname(__filename); const PORT = 13536; const testFilePath = join( dirname(__dirname), - 'fixture', - 'file', - 'binaryfile.tar.gz', + "fixture", + "file", + "binaryfile.tar.gz" ); -test('octet stream', (done) => { +test("octet stream", (done) => { const server = createServer((req, res) => { const form = formidable(); @@ -39,13 +39,13 @@ test('octet stream', (done) => { }); server.listen(PORT, (err) => { - assert(!err, 'should not have error, but be falsey'); + assert(!err, "should not have error, but be falsey"); const request = _request({ port: PORT, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/octet-stream', + "Content-Type": "application/octet-stream", }, }); diff --git a/test/integration/store-files-option.test.js b/test/integration/store-files-option.test.js index 86f4a11d..f72a5544 100644 --- a/test/integration/store-files-option.test.js +++ b/test/integration/store-files-option.test.js @@ -1,29 +1,33 @@ -import { existsSync, mkdirSync, WriteStream, statSync, unlinkSync, createReadStream } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { createServer, request as _request } from 'http'; -import assert, { strictEqual, ok } from 'node:assert'; +import { + existsSync, + mkdirSync, + WriteStream, + statSync, + unlinkSync, + createReadStream, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { createServer, request as _request } from "http"; +import assert, { strictEqual, ok } from "node:assert"; -import path, { join, dirname } from 'node:path'; -import url from 'node:url'; +import path, { join, dirname } from "node:path"; +import url from "node:url"; -import formidable from '../../src/index.js'; +import formidable from "../../src/index.js"; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 13537; -const DEFAULT_UPLOAD_DIR = join( - tmpdir(), - 'test-store-files-option-default', -); -const CUSTOM_UPLOAD_FILE_PATH = join(DEFAULT_UPLOAD_DIR, 'test-file'); +const DEFAULT_UPLOAD_DIR = join(tmpdir(), "test-store-files-option-default"); +const CUSTOM_UPLOAD_FILE_PATH = join(DEFAULT_UPLOAD_DIR, "test-file"); const testFilePath = join( dirname(__dirname), - 'fixture', - 'file', - 'binaryfile.tar.gz', + "fixture", + "file", + "binaryfile.tar.gz" ); -test('store files option', (done) => { +test("store files option", (done) => { const server = createServer((req, res) => { if (!existsSync(DEFAULT_UPLOAD_DIR)) { mkdirSync(DEFAULT_UPLOAD_DIR); @@ -38,7 +42,7 @@ test('store files option', (done) => { const file = files.file[0]; strictEqual(file.size, 301); - strictEqual(typeof file.filepath, 'string'); + strictEqual(typeof file.filepath, "string"); const uploadedFileStats = statSync(CUSTOM_UPLOAD_FILE_PATH); ok(uploadedFileStats.size === file.size); @@ -51,13 +55,13 @@ test('store files option', (done) => { }); server.listen(PORT, (err) => { - assert(!err, 'should not have error, but be falsey'); + assert(!err, "should not have error, but be falsey"); const request = _request({ port: PORT, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/octet-stream', + "Content-Type": "application/octet-stream", }, }); diff --git a/test/standalone/connection-aborted.test.js b/test/standalone/connection-aborted.test.js index f7a7afda..2efede38 100644 --- a/test/standalone/connection-aborted.test.js +++ b/test/standalone/connection-aborted.test.js @@ -1,7 +1,7 @@ -import assert from 'node:assert'; -import { createServer } from 'node:http'; -import { connect } from 'node:net'; -import formidable from '../../src/index.js'; +import assert from "node:assert"; +import { createServer } from "node:http"; +import { connect } from "node:net"; +import formidable from "../../src/index.js"; let server; let port = 13540; @@ -11,47 +11,48 @@ beforeEach(() => { port += 1; }); -afterEach(() => { - return new Promise((resolve) => { - if (server.listening) { - server.close(() => resolve()); - } else { - resolve(); - } - }); -}); +afterEach( + () => + new Promise((resolve) => { + if (server.listening) { + server.close(() => resolve()); + } else { + resolve(); + } + }) +); -test('connection aborted', (done) => { - server.on('request', (req) => { +test("connection aborted", (done) => { + server.on("request", (req) => { const form = formidable(); let abortedReceived = false; - form.on('aborted', () => { + form.on("aborted", () => { abortedReceived = true; }); - form.on('error', () => { - assert(abortedReceived, 'Error event should follow aborted'); + form.on("error", () => { + assert(abortedReceived, "Error event should follow aborted"); }); - form.on('end', () => { + form.on("end", () => { throw new Error('Unexpected "end" event'); }); form.parse(req, () => { assert( abortedReceived, - 'from .parse() callback: Error event should follow aborted', + "from .parse() callback: Error event should follow aborted" ); done(); }); }); - server.listen(port, 'localhost', () => { + server.listen(port, "localhost", () => { const client = connect(port); client.write( - 'POST / HTTP/1.1\r\n' + - 'Host: localhost\r\n' + - 'Content-Length: 70\r\n' + - 'Content-Type: multipart/form-data; boundary=foo\r\n\r\n', + "POST / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Length: 70\r\n" + + "Content-Type: multipart/form-data; boundary=foo\r\n\r\n" ); client.end(); }); diff --git a/test/standalone/content-transfer-encoding.test.js b/test/standalone/content-transfer-encoding.test.js index faf67ff2..567fe994 100644 --- a/test/standalone/content-transfer-encoding.test.js +++ b/test/standalone/content-transfer-encoding.test.js @@ -1,59 +1,58 @@ -import { join } from 'node:path'; -import { createServer, request } from 'node:http'; -import { strictEqual } from 'node:assert'; +import { join } from "node:path"; +import { createServer, request } from "node:http"; +import { strictEqual } from "node:assert"; -import formidable from '../../src/index.js'; +import formidable from "../../src/index.js"; -const UPLOAD_DIR = join(process.cwd(), 'test', 'tmp'); +const UPLOAD_DIR = join(process.cwd(), "test", "tmp"); // OS choosing port const PORT = 13530; -test('content transfer encoding', (done) => { +test("content transfer encoding", (done) => { const server = createServer(async (req, res) => { const form = formidable({ - uploadDir: UPLOAD_DIR + uploadDir: UPLOAD_DIR, }); - form.on('end', () => { + form.on("end", () => { throw new Error('Unexpected "end" event'); }); - form.on('error', (e) => { + form.on("error", (e) => { res.writeHead(500); res.end(e.message); }); try { await form.parse(req); - } catch (formidableError) { - } + } catch (formidableError) {} }); server.listen(PORT, () => { const chosenPort = server.address().port; const body = - '--foo\r\n' + + "--foo\r\n" + 'Content-Disposition: form-data; name="file1"; filename="file1"\r\n' + - 'Content-Type: application/octet-stream\r\n' + - '\r\nThis is the first file\r\n' + - '--foo\r\n' + - 'Content-Type: application/octet-stream\r\n' + + "Content-Type: application/octet-stream\r\n" + + "\r\nThis is the first file\r\n" + + "--foo\r\n" + + "Content-Type: application/octet-stream\r\n" + 'Content-Disposition: form-data; name="file2"; filename="file2"\r\n' + - 'Content-Transfer-Encoding: unknown\r\n' + - '\r\nThis is the second file\r\n' + - '--foo--\r\n'; + "Content-Transfer-Encoding: unknown\r\n" + + "\r\nThis is the second file\r\n" + + "--foo--\r\n"; const req = request({ - method: 'POST', + method: "POST", port: chosenPort, headers: { - 'Content-Length': body.length, - 'Content-Type': 'multipart/form-data; boundary=foo', + "Content-Length": body.length, + "Content-Type": "multipart/form-data; boundary=foo", }, }); - req.on('response', (res) => { + req.on("response", (res) => { strictEqual(res.statusCode, 500); - res.on('data', () => {}); - res.on('end', () => { + res.on("data", () => {}); + res.on("end", () => { server.close(); done(); }); diff --git a/test/standalone/issue-46.test.js b/test/standalone/issue-46.test.js index 52c564a7..ff79e3ce 100644 --- a/test/standalone/issue-46.test.js +++ b/test/standalone/issue-46.test.js @@ -1,21 +1,22 @@ import { createServer, request } from "node:http"; import { ok, strictEqual } from "node:assert"; -import { Buffer } from 'node:buffer'; +import { Buffer } from "node:buffer"; import formidable from "../../src/index.js"; // OS choosing port const PORT = 13531; -const type = "multipart/related; boundary=a7a65b99-8a61-4e2c-b149-f73a3b35f923" -const body = "LS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMNCmNvbnRlbnQtZGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iZm9vIg0KDQpiYXJyeQ0KLS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMtLQ"; -const buffer = Buffer.from(body, 'base64url'); +const type = "multipart/related; boundary=a7a65b99-8a61-4e2c-b149-f73a3b35f923"; +const body = + "LS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMNCmNvbnRlbnQtZGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iZm9vIg0KDQpiYXJyeQ0KLS1hN2E2NWI5OS04YTYxLTRlMmMtYjE0OS1mNzNhM2IzNWY5MjMtLQ"; +const buffer = Buffer.from(body, "base64url"); test("issue 46", (done) => { const server = createServer(async (req, res) => { // Parse form and write results to response. const form = formidable(); const [fields] = await form.parse(req); - ok(fields.foo, 'should have fields.foo === barry'); - strictEqual(fields.foo[0], 'barry'); + ok(fields.foo, "should have fields.foo === barry"); + strictEqual(fields.foo[0], "barry"); res.end(); server.close(() => { done(); @@ -30,12 +31,11 @@ test("issue 46", (done) => { method: "POST", headers: { "Content-Type": type, - "Content-Length": buffer.byteLength - } + "Content-Length": buffer.byteLength, + }, }); req.write(buffer); req.end(); - }); }); diff --git a/test/standalone/keep-alive-error.test.js b/test/standalone/keep-alive-error.test.js index dc5e117e..10d63a0d 100644 --- a/test/standalone/keep-alive-error.test.js +++ b/test/standalone/keep-alive-error.test.js @@ -1,9 +1,9 @@ /* eslint-disable max-nested-callbacks */ -import assert from 'node:assert/strict'; -import { createServer } from 'node:http'; -import { createConnection } from 'node:net'; -import formidable from '../../src/index.js'; +import assert from "node:assert/strict"; +import { createServer } from "node:http"; +import { createConnection } from "node:net"; +import formidable from "../../src/index.js"; let server; let port = 13539; @@ -17,25 +17,26 @@ beforeEach(() => { port += 1; }); -afterEach(() => { - return new Promise((resolve) => { - if (server.listening) { - server.close(() => resolve()); - } else { - resolve(); - } - }); -}); +afterEach( + () => + new Promise((resolve) => { + if (server.listening) { + server.close(() => resolve()); + } else { + resolve(); + } + }) +); -test('keep alive error', (done) => { - server.on('request', async (req, res) => { +test("keep alive error", (done) => { + server.on("request", async (req, res) => { const form = formidable(); - form.on('error', () => { + form.on("error", () => { errors += 1; res.writeHead(500); res.end(); }); - form.on('end', () => { + form.on("end", () => { ok += 1; res.writeHead(200); res.end(); @@ -52,12 +53,12 @@ test('keep alive error', (done) => { // correct post upload (with hyphens) clientTwo.write( - 'POST /upload-test HTTP/1.1\r\n' + - 'Host: localhost\r\n' + - 'Connection: keep-alive\r\n' + - 'Content-Type: multipart/form-data; boundary=----aaa\r\n' + - 'Content-Length: 13\r\n\r\n' + - '------aaa--\r\n', + "POST /upload-test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Content-Type: multipart/form-data; boundary=----aaa\r\n" + + "Content-Length: 13\r\n\r\n" + + "------aaa--\r\n" ); clientTwo.end(); } @@ -68,17 +69,17 @@ test('keep alive error', (done) => { // first send malformed (boundary / hyphens) post upload client.write( - 'POST /upload-test HTTP/1.1\r\n' + - 'Host: localhost\r\n' + - 'Connection: keep-alive\r\n' + - 'Content-Type: multipart/form-data; boundary=----aaa\r\n' + - 'Content-Length: 10011\r\n\r\n' + - '------XXX\n\r', + "POST /upload-test HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Content-Type: multipart/form-data; boundary=----aaa\r\n" + + "Content-Length: 10011\r\n\r\n" + + "------XXX\n\r" ); setTimeout(() => { const buf = Buffer.alloc(10000); - buf.fill('a'); + buf.fill("a"); client.write(buf); client.end(); }, 150); diff --git a/test/tools/base64.html b/test/tools/base64.html index dddc5f5a..fd1e7c04 100644 --- a/test/tools/base64.html +++ b/test/tools/base64.html @@ -6,13 +6,13 @@ function form_submit(e) { console.log(e); - var resultOutput = document.getElementById('resultOutput'); - var fileInput = document.getElementById('fileInput'); - var fieldInput = document.getElementById('fieldInput'); + var resultOutput = document.getElementById("resultOutput"); + var fileInput = document.getElementById("fileInput"); + var fieldInput = document.getElementById("fieldInput"); makeRequestBase64(fileInput.files[0], fieldInput.value, function ( err, - result, + result ) { resultOutput.value = result; }); @@ -22,14 +22,14 @@ function makeRequestBase64(file, fieldName, cb) { var boundary = - '\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/'; - var crlf = '\r\n'; + "\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/"; + var crlf = "\r\n"; var reader = new FileReader(); reader.onload = function (e) { - var body = ''; + var body = ""; - body += '--' + boundary + crlf; + body += "--" + boundary + crlf; body += 'Content-Disposition: form-data; name="' + fieldName + @@ -37,23 +37,23 @@ escape(file.name) + '"' + crlf; - body += 'Content-Type: ' + file.type + '' + crlf; - body += 'Content-Transfer-Encoding: base64' + crlf; + body += "Content-Type: " + file.type + "" + crlf; + body += "Content-Transfer-Encoding: base64" + crlf; body += crlf; body += - e.target.result.substring(e.target.result.indexOf(',') + 1) + crlf; + e.target.result.substring(e.target.result.indexOf(",") + 1) + crlf; - body += '--' + boundary + '--'; + body += "--" + boundary + "--"; - var head = ''; - head += 'POST /upload HTTP/1.1' + crlf; - head += 'Host: localhost:8080' + crlf; + var head = ""; + head += "POST /upload HTTP/1.1" + crlf; + head += "Host: localhost:8080" + crlf; head += - 'Content-Type: multipart/form-data; boundary=' + + "Content-Type: multipart/form-data; boundary=" + boundary + - '' + + "" + crlf; - head += 'Content-Length: ' + body.length + '' + crlf; + head += "Content-Length: " + body.length + "" + crlf; cb(null, head + crlf + body); }; diff --git a/test/unit/custom-plugins.test.js b/test/unit/custom-plugins.test.js index 6698fa03..d23b8b3a 100644 --- a/test/unit/custom-plugins.test.js +++ b/test/unit/custom-plugins.test.js @@ -1,11 +1,17 @@ /* eslint-disable no-underscore-dangle */ -import { join } from 'node:path'; +import { join } from "node:path"; -import Koa from 'koa'; -import request from 'supertest'; +import Koa from "koa"; +import request from "supertest"; -import { formidable, json, octetstream, multipart, errors } from '../../src/index.js'; +import { + formidable, + json, + octetstream, + multipart, + errors, +} from "../../src/index.js"; function createServer(options, handler) { const app = new Koa(); @@ -20,7 +26,7 @@ function createServer(options, handler) { } function fromFixtures(...args) { - return join(process.cwd(), 'test', 'fixture', ...args); + return join(process.cwd(), "test", "fixture", ...args); } // function makeRequest(server, options) { @@ -54,13 +60,13 @@ function fromFixtures(...args) { // ! tests -test('should call 3 custom and 1 builtin plugins, when .parse() is called', async () => { +test("should call 3 custom and 1 builtin plugins, when .parse() is called", async () => { const server = createServer({ enabledPlugins: [json] }, (ctx, form) => { - form.on('plugin', () => { + form.on("plugin", () => { ctx.__pluginsCount = ctx.__pluginsCount || 0; ctx.__pluginsCount += 1; }); - form.on('end', () => { + form.on("end", () => { ctx.__ends = 1; expect(ctx.__customPlugin1).toBe(111); expect(ctx.__customPlugin2).toBe(222); @@ -82,22 +88,22 @@ test('should call 3 custom and 1 builtin plugins, when .parse() is called', asyn }); form.parse(ctx.req, (err, fields) => { - expect(fields.qux).toBe('zaz'); - expect(fields.a).toBe('bbb'); + expect(fields.qux).toBe("zaz"); + expect(fields.a).toBe("bbb"); expect(ctx.__pluginsCount).toBe(4); }); }); await new Promise((resolve, reject) => { request(server.callback()) - .post('/') - .type('application/json') - .send({ qux: 'zaz', a: 'bbb' }) + .post("/") + .type("application/json") + .send({ qux: "zaz", a: "bbb" }) .end((err) => (err ? reject(err) : resolve())); }); }); -test('.parse throw error when some plugin fail', async () => { +test(".parse throw error when some plugin fail", async () => { const server = createServer( { enabledPlugins: [octetstream, json] }, async (ctx, form) => { @@ -108,17 +114,17 @@ test('.parse throw error when some plugin fail', async () => { // ctx.__onFileCalled += 1; // }); - form.on('plugin', () => { + form.on("plugin", () => { ctx.__pluginsCount = ctx.__pluginsCount || 0; ctx.__pluginsCount += 1; }); - form.once('error', () => { - throw new Error('error event should not be fired when plugin throw'); + form.once("error", () => { + throw new Error("error event should not be fired when plugin throw"); }); form.use(() => { - throw new Error('custom plugin err'); + throw new Error("custom plugin err"); }); let res = null; @@ -137,28 +143,28 @@ test('.parse throw error when some plugin fail', async () => { if (!res) { throw new Error( - '^ .parse should throw & be caught with the try/catch ^', + "^ .parse should throw & be caught with the try/catch ^" ); } - }, + } ); return new Promise((resolve, reject) => { request(server.callback()) - .post('/') - .type('application/octet-stream') - .attach('bin', fromFixtures('file', 'binaryfile.tar.gz')) + .post("/") + .type("application/octet-stream") + .attach("bin", fromFixtures("file", "binaryfile.tar.gz")) .end((err) => (err ? reject(err) : resolve())); }); }); -test('multipart plugin fire `error` event when malformed boundary', async () => { +test("multipart plugin fire `error` event when malformed boundary", async () => { const server = createServer( { enabledPlugins: [json, multipart] }, (ctx, form) => { let failedIsOkay = false; - form.once('error', (err) => { + form.once("error", (err) => { expect(form._plugins.length).toBe(2); expect(err).toBeTruthy(); expect(err.message).toMatch(/bad content-type header/); @@ -167,15 +173,15 @@ test('multipart plugin fire `error` event when malformed boundary', async () => }); // Should never be called when `error` - form.on('end', () => { - throw new Error('should not fire `end` event when error'); + form.on("end", () => { + throw new Error("should not fire `end` event when error"); }); form.parse(ctx.req, (err) => { expect(err).toBeTruthy(); expect(failedIsOkay).toBe(true); }); - }, + } ); // 'Content-Length': 1111111, @@ -183,16 +189,16 @@ test('multipart plugin fire `error` event when malformed boundary', async () => // 'Content-Type': 'multipart/form-data; bouZndary=', await new Promise((resolve, reject) => { request(server.callback()) - .post('/') - .type('multipart/form-data') - .set('Content-Length', 11111111) - .set('Content-Disposition', 'form-data; bouZndary=') - .set('Content-Type', 'multipart/form-data; bouZndary=') + .post("/") + .type("multipart/form-data") + .set("Content-Length", 11111111) + .set("Content-Disposition", "form-data; bouZndary=") + .set("Content-Type", "multipart/form-data; bouZndary=") .end((err) => (err ? reject(err) : resolve())); }); }); -test('formidable() throw if not at least 1 built-in plugin in options.enabledPlugins', () => { +test("formidable() throw if not at least 1 built-in plugin in options.enabledPlugins", () => { try { formidable({ enabledPlugins: [] }); } catch (err) { diff --git a/test/unit/formidable.test.js b/test/unit/formidable.test.js index 6efc6f1e..a602c6e9 100644 --- a/test/unit/formidable.test.js +++ b/test/unit/formidable.test.js @@ -1,35 +1,34 @@ /* eslint-disable max-statements */ /* eslint-disable no-underscore-dangle */ -import {jest} from '@jest/globals'; -import Stream from 'node:stream'; -import http from 'node:http'; -import path from 'node:path'; - -import formidable from '../../src/index.js'; -import * as mod from '../../src/index.js'; +import { jest } from "@jest/globals"; +import Stream from "node:stream"; +import http from "node:http"; +import path from "node:path"; +import formidable from "../../src/index.js"; +import * as mod from "../../src/index.js"; function requestStub() { - return Object.assign(new Stream (), { + return Object.assign(new Stream(), { pause() {}, resume() {}, }); } function getForm(name, opts) { - return name === 'formidable' ? formidable(opts) : new mod[name](opts); + return name === "formidable" ? formidable(opts) : new mod[name](opts); } function makeHeader(originalFilename) { return `Content-Disposition: form-data; name="upload"; filename="${originalFilename}"`; } -['IncomingForm', 'Formidable', 'formidable'].forEach((name) => { +["IncomingForm", "Formidable", "formidable"].forEach((name) => { test(`${name}#_getFileName with regular characters`, () => { - const originalFilename = 'foo.txt'; + const originalFilename = "foo.txt"; const form = getForm(name); - expect(form._getFileName(makeHeader(originalFilename))).toBe('foo.txt'); + expect(form._getFileName(makeHeader(originalFilename))).toBe("foo.txt"); }); test(`${name}#_getFileName with unescaped quote`, () => { @@ -40,7 +39,7 @@ function makeHeader(originalFilename) { }); test(`${name}#_getFileName with escaped quote`, () => { - const originalFilename = 'my%22.txt'; + const originalFilename = "my%22.txt"; const form = getForm(name); expect(form._getFileName(makeHeader(originalFilename))).toBe('my".txt'); @@ -55,17 +54,17 @@ function makeHeader(originalFilename) { }); test(`${name}#_getFileName with semicolon`, () => { - const originalFilename = 'my;.txt'; + const originalFilename = "my;.txt"; const form = getForm(name); - expect(form._getFileName(makeHeader(originalFilename))).toBe('my;.txt'); + expect(form._getFileName(makeHeader(originalFilename))).toBe("my;.txt"); }); test(`${name}#_getFileName with utf8 character`, () => { - const originalFilename = 'my☃.txt'; + const originalFilename = "my☃.txt"; const form = getForm(name); - expect(form._getFileName(makeHeader(originalFilename))).toBe('my☃.txt'); + expect(form._getFileName(makeHeader(originalFilename))).toBe("my☃.txt"); }); test(`${name}#_getNewName strips harmful characters from extension when keepExtensions`, () => { @@ -74,230 +73,229 @@ 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'); + let basename = getBasename("fine.jpg?foo=bar"); expect(basename).toHaveLength(29); let ext = path.extname(basename); - expect(ext).toBe('.jpg'); + expect(ext).toBe(".jpg"); - basename = getBasename('fine-no-ext?foo=qux'); + basename = getBasename("fine-no-ext?foo=qux"); expect(basename).toHaveLength(25); ext = path.extname(basename); - expect(ext).toBe(''); + expect(ext).toBe(""); - basename = getBasename({ originalFilename: 'super.cr2+dsad' }); + basename = getBasename({ originalFilename: "super.cr2+dsad" }); expect(basename).toHaveLength(29); ext = path.extname(basename); - expect(ext).toBe('.cr2'); + expect(ext).toBe(".cr2"); - basename = getBasename({ originalFilename: 'super.gz' }); + basename = getBasename({ originalFilename: "super.gz" }); expect(basename).toHaveLength(28); ext = path.extname(basename); - expect(ext).toBe('.gz'); + expect(ext).toBe(".gz"); - basename = getBasename('file.aAa'); + basename = getBasename("file.aAa"); expect(basename).toHaveLength(29); ext = path.extname(basename); - expect(ext).toBe('.aAa'); + expect(ext).toBe(".aAa"); - basename = getBasename('file#!@#koh.QxZs?sa=1'); + basename = getBasename("file#!@#koh.QxZs?sa=1"); expect(basename).toHaveLength(30); ext = path.extname(basename); - expect(ext).toBe('.QxZs'); + expect(ext).toBe(".QxZs"); - basename = getBasename('test.pdf.jqlnn.png'); + basename = getBasename("test.pdf.jqlnn.png"); expect(basename).toHaveLength(35); ext = path.extname(basename); - expect(ext).toBe('.jqlnn'); + expect(ext).toBe(".jqlnn"); - basename = getBasename('test. { - const form = getForm(name, { }); + const form = getForm(name, {}); const req = new http.ClientRequest(); req.headers = { - 'content-length': '8', - 'content-type': 'multipart/form-data; boundary=----TLVx', + "content-length": "8", + "content-type": "multipart/form-data; boundary=----TLVx", }; form.parse(req, (error, fields) => { expect(Array.isArray(fields.a)).toBe(true); - expect(fields.a[0]).toBe('1'); - expect(fields.a[1]).toBe('2'); + expect(fields.a[0]).toBe("1"); + expect(fields.a[1]).toBe("2"); }); - form.emit('field', 'a', '1'); - form.emit('field', 'a', '2'); - form.emit('end'); + form.emit("field", "a", "1"); + form.emit("field", "a", "2"); + form.emit("end"); }); test(`${name}#_Nested array parameters support`, () => { - const form = getForm(name, { }); + const form = getForm(name, {}); const req = new http.ClientRequest(); req.headers = { - 'content-length': '8', - 'content-type': 'multipart/form-data; boundary=----TLVx', + "content-length": "8", + "content-type": "multipart/form-data; boundary=----TLVx", }; form.parse(req, (error, fields) => { expect(Array.isArray(fields[`a[0]`])).toBe(true); - expect(fields[`a[0]`][0]).toBe('a'); - expect(fields[`a[0]`][1]).toBe('b'); - expect(fields[`a[1]`][0]).toBe('c'); + expect(fields[`a[0]`][0]).toBe("a"); + expect(fields[`a[0]`][1]).toBe("b"); + expect(fields[`a[1]`][0]).toBe("c"); }); - form.emit('field', 'a[0]', 'a'); - form.emit('field', 'a[0]', 'b'); - form.emit('field', 'a[1]', 'c'); - form.emit('end'); + form.emit("field", "a[0]", "a"); + form.emit("field", "a[0]", "b"); + form.emit("field", "a[1]", "c"); + form.emit("end"); }); test(`${name}#_Object parameters support`, () => { - const form = getForm(name, { }); + const form = getForm(name, {}); const req = new http.ClientRequest(); req.headers = { - 'content-length': '8', - 'content-type': 'multipart/form-data; boundary=----TLVx', + "content-length": "8", + "content-type": "multipart/form-data; boundary=----TLVx", }; form.parse(req, (error, fields) => { - expect(fields[`a[x]`][0]).toBe('1'); - expect(fields[`a[y]`][0]).toBe('2'); + expect(fields[`a[x]`][0]).toBe("1"); + expect(fields[`a[y]`][0]).toBe("2"); }); - form.emit('field', 'a[x]', '1'); - form.emit('field', 'a[y]', '2'); - form.emit('end'); + form.emit("field", "a[x]", "1"); + form.emit("field", "a[y]", "2"); + form.emit("end"); }); xtest(`${name}#_Nested object parameters support`, () => { - const form = getForm(name, { }); + const form = getForm(name, {}); const req = new http.ClientRequest(); req.headers = { - 'content-length': '8', - 'content-type': 'multipart/form-data; boundary=----TLVx', + "content-length": "8", + "content-type": "multipart/form-data; boundary=----TLVx", }; form.parse(req, (error, fields) => { - expect(fields.a.l1.k1).toBe('2'); - expect(fields.a.l1.k2).toBe('3'); - expect(fields.a.l2.k3).toBe('5'); + expect(fields.a.l1.k1).toBe("2"); + expect(fields.a.l1.k2).toBe("3"); + expect(fields.a.l2.k3).toBe("5"); }); - form.emit('field', 'a[l1][k1]', '2'); - form.emit('field', 'a[l1][k2]', '3'); - form.emit('field', 'a[l2][k3]', '5'); - form.emit('end'); + form.emit("field", "a[l1][k1]", "2"); + form.emit("field", "a[l1][k2]", "3"); + form.emit("field", "a[l2][k3]", "5"); + form.emit("end"); }); describe(`${name}#_onPart`, () => { - describe('when not allow empty files', () => { - describe('when file is empty', () => { - test('emits error when part is received', (done) => { + describe("when not allow empty files", () => { + describe("when file is empty", () => { + test("emits error when part is received", (done) => { const form = getForm(name, { allowEmptyFiles: false, }); form.req = requestStub(); const part = new Stream(); - part.mimetype = 'text/plain'; + part.mimetype = "text/plain"; // eslint-disable-next-line max-nested-callbacks - form.on('error', (error) => { + form.on("error", (error) => { expect(error.message).toBe( - 'options.allowEmptyFiles is false, file size should be greater than 0', + "options.allowEmptyFiles is false, file size should be greater than 0" ); done(); }); - form.onPart(part).then (function () { - part.emit('end'); - form.emit('end'); + form.onPart(part).then(() => { + part.emit("end"); + form.emit("end"); }); }); }); - describe('when file is not empty', () => { - test('not emits error when part is received', () => { + describe("when file is not empty", () => { + test("not emits error when part is received", () => { const form = getForm(name, { allowEmptyFiles: false, }); - const formEmitSpy = jest.spyOn(form, 'emit'); + const formEmitSpy = jest.spyOn(form, "emit"); const part = new Stream(); - part.mimetype = 'text/plain'; + part.mimetype = "text/plain"; form.onPart(part); - part.emit('data', Buffer.alloc(1)); - part.emit('end'); - form.emit('end'); - expect(formEmitSpy).not.toBeCalledWith('error'); + part.emit("data", Buffer.alloc(1)); + part.emit("end"); + form.emit("end"); + expect(formEmitSpy).not.toBeCalledWith("error"); }); }); }); - describe('when allow empty files', () => { - test('not emits error when part is received', () => { - const form = getForm(name, { }); - const formEmitSpy = jest.spyOn(form, 'emit'); + describe("when allow empty files", () => { + test("not emits error when part is received", () => { + const form = getForm(name, {}); + const formEmitSpy = jest.spyOn(form, "emit"); const part = new Stream(); - part.mimetype = 'text/plain'; + part.mimetype = "text/plain"; form.onPart(part); - part.emit('end'); - form.emit('end'); - expect(formEmitSpy).not.toBeCalledWith('error'); + part.emit("end"); + form.emit("end"); + expect(formEmitSpy).not.toBeCalledWith("error"); }); }); - describe('when file uploaded size is inferior than minFileSize option', () => { - test('emits error when part is received', (done) => { + describe("when file uploaded size is inferior than minFileSize option", () => { + test("emits error when part is received", (done) => { const form = getForm(name, { minFileSize: 5 }); const part = new Stream(); const req = requestStub(); - part.mimetype = 'text/plain'; - form.on('error', (error) => { + part.mimetype = "text/plain"; + form.on("error", (error) => { expect(error.message).toBe( - 'options.minFileSize (5 bytes) inferior, received 4 bytes of file data', + "options.minFileSize (5 bytes) inferior, received 4 bytes of file data" ); done(); }); form.req = req; - form.onPart(part).then(function () { - part.emit('data', Buffer.alloc(4)); - part.emit('end'); - form.emit('end'); + form.onPart(part).then(() => { + part.emit("data", Buffer.alloc(4)); + part.emit("end"); + form.emit("end"); }); - }); }); - describe('when file uploaded size is superior than minFileSize option', () => { - test('not emits error when part is received', () => { + describe("when file uploaded size is superior than minFileSize option", () => { + test("not emits error when part is received", () => { const form = getForm(name, { minFileSize: 10 }); - const formEmitSpy = jest.spyOn(form, 'emit'); + const formEmitSpy = jest.spyOn(form, "emit"); const part = new Stream(); - part.mimetype = 'text/plain'; + part.mimetype = "text/plain"; form.onPart(part); - part.emit('data', Buffer.alloc(11)); - part.emit('end'); - form.emit('end'); - expect(formEmitSpy).not.toBeCalledWith('error'); + part.emit("data", Buffer.alloc(11)); + part.emit("end"); + form.emit("end"); + expect(formEmitSpy).not.toBeCalledWith("error"); }); }); - describe('when there are more fields than maxFields', () => { - test('emits error', (done) => { + describe("when there are more fields than maxFields", () => { + test("emits error", (done) => { const form = getForm(name, { maxFields: 1, }); - form.on('error', (error) => { - expect(error.message.includes('maxFields')).toBe(true); + form.on("error", (error) => { + expect(error.message.includes("maxFields")).toBe(true); done(); }); - form.emit('field', 'a', '1'); - form.emit('field', 'b', '2'); - form.emit('end'); + form.emit("field", "a", "1"); + form.emit("field", "b", "2"); + form.emit("end"); }); }); }); @@ -306,19 +304,19 @@ function makeHeader(originalFilename) { const form = getForm(name, { maxFiles: 1 }); form.req = requestStub(); - form.on('error', (error) => { - expect(error.message.includes('maxFiles')).toBe(true); + form.on("error", (error) => { + expect(error.message.includes("maxFiles")).toBe(true); done(); }); const part1 = new Stream(); - part1.mimetype = 'text/plain'; + part1.mimetype = "text/plain"; const part2 = new Stream(); - part2.mimetype = 'text/plain'; + part2.mimetype = "text/plain"; form.onPart(part1).then(() => { - part1.emit('data', Buffer.alloc(1)); - part1.emit('end'); + part1.emit("data", Buffer.alloc(1)); + part1.emit("end"); form.onPart(part2); }); }); @@ -335,26 +333,26 @@ function makeHeader(originalFilename) { // }); test(`${name}#_joinDirectoryName blocks standard directory traversal`, () => { - const form = getForm(name, { uploadDir: '/tmp/uploads' }); - const result = form._joinDirectoryName('../../../etc/passwd'); - expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); + const form = getForm(name, { uploadDir: "/tmp/uploads" }); + const result = form._joinDirectoryName("../../../etc/passwd"); + expect(result).toBe(path.join("/tmp/uploads", "invalid-name")); }); test(`${name}#_joinDirectoryName blocks sibling directory prefix collision`, () => { - const form = getForm(name, { uploadDir: '/tmp/uploads' }); - const result = form._joinDirectoryName('../uploads-evil/payload.sh'); - expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); + const form = getForm(name, { uploadDir: "/tmp/uploads" }); + const result = form._joinDirectoryName("../uploads-evil/payload.sh"); + expect(result).toBe(path.join("/tmp/uploads", "invalid-name")); }); test(`${name}#_joinDirectoryName allows subdirectories within uploadDir`, () => { - const form = getForm(name, { uploadDir: '/tmp/uploads' }); - const result = form._joinDirectoryName('images/photo.jpg'); - expect(result).toBe(path.resolve('/tmp/uploads', 'images/photo.jpg')); + const form = getForm(name, { uploadDir: "/tmp/uploads" }); + const result = form._joinDirectoryName("images/photo.jpg"); + expect(result).toBe(path.resolve("/tmp/uploads", "images/photo.jpg")); }); test(`${name}#_joinDirectoryName blocks name resolving to uploadDir itself`, () => { - const form = getForm(name, { uploadDir: '/tmp/uploads' }); - const result = form._joinDirectoryName('.'); - expect(result).toBe(path.join('/tmp/uploads', 'invalid-name')); + const form = getForm(name, { uploadDir: "/tmp/uploads" }); + const result = form._joinDirectoryName("."); + expect(result).toBe(path.join("/tmp/uploads", "invalid-name")); }); }); diff --git a/test/unit/multipart-parser.test.js b/test/unit/multipart-parser.test.js index 4e863615..37353712 100644 --- a/test/unit/multipart-parser.test.js +++ b/test/unit/multipart-parser.test.js @@ -1,6 +1,6 @@ -import { MultipartParser } from '../../src/index.js'; +import { MultipartParser } from "../../src/index.js"; -test('on constructor', () => { +test("on constructor", () => { const parser = new MultipartParser(); expect(parser.boundary).toBeNull(); expect(parser.state).toBe(0); @@ -8,11 +8,11 @@ test('on constructor', () => { expect(parser.boundaryChars).toBeNull(); expect(parser.index).toBeNull(); expect(parser.lookbehind).toBeNull(); - expect(parser.constructor.name).toBe('MultipartParser'); + expect(parser.constructor.name).toBe("MultipartParser"); }); -test('initWithBoundary', () => { - const boundary = 'abc'; +test("initWithBoundary", () => { + const boundary = "abc"; const parser = new MultipartParser(); parser.initWithBoundary(boundary); @@ -37,22 +37,22 @@ test('initWithBoundary', () => { }); }); -test('initWithBoundary failing', () => { +test("initWithBoundary failing", () => { const parser = new MultipartParser(); - const boundary = 'abc'; + const boundary = "abc"; const buffer = Buffer.alloc(5); parser.initWithBoundary(boundary); - buffer.write('--ad', 0); + buffer.write("--ad", 0); expect(parser.bufferLength).toBe(0); parser.write(buffer); expect(parser.bufferLength).toBe(5); }); -test('on .end() throwing', () => { +test("on .end() throwing", () => { const parser = new MultipartParser(); - parser.once('error', () => {}); + parser.once("error", () => {}); const res = parser.end(); expect(res.state).toBe(0); @@ -62,7 +62,7 @@ test('on .end() throwing', () => { // expect(() => { parser.end() }).toThrow(parser.explain()); }); -test('on .end() successful', () => { +test("on .end() successful", () => { const parser = new MultipartParser(); parser.state = MultipartParser.STATES.END; diff --git a/test/unit/persistent-file.disabled-test.js b/test/unit/persistent-file.disabled-test.js index 99142245..c230788a 100644 --- a/test/unit/persistent-file.disabled-test.js +++ b/test/unit/persistent-file.disabled-test.js @@ -1,44 +1,44 @@ -import {jest} from '@jest/globals'; -import fs from 'node:fs'; -import PersistentFile from '../../src/PersistentFile.js'; +import { jest } from "@jest/globals"; +import fs from "node:fs"; +import PersistentFile from "../../src/PersistentFile.js"; const mockFs = fs; const now = new Date(); const file = new PersistentFile({ size: 1024, - filepath: '/tmp/cat.png', - name: 'cat.png', - type: 'image/png', + filepath: "/tmp/cat.png", + name: "cat.png", + type: "image/png", lastModifiedDate: now, - originalFilename: 'cat.png', - newFilename: 'dff1d2eaab9752165764dcd00', - mimetype: 'image/png', + originalFilename: "cat.png", + newFilename: "dff1d2eaab9752165764dcd00", + mimetype: "image/png", }); const mockFn = jest.fn(); -jest.mock('fs', () => { - return { - ...mockFs, - unlink: mockFn, - }; -}); +jest.mock("fs", () => ({ + ...mockFs, + unlink: mockFn, +})); -describe('PersistentFile', () => { - test('toJSON()', () => { +describe("PersistentFile", () => { + test("toJSON()", () => { const obj = file.toJSON(); const len = Object.keys(obj).length; - expect(obj.filepath).toBe('/tmp/cat.png'); - expect(obj.mimetype).toBe('image/png'); - expect(obj.originalFilename).toBe('cat.png'); + expect(obj.filepath).toBe("/tmp/cat.png"); + expect(obj.mimetype).toBe("image/png"); + expect(obj.originalFilename).toBe("cat.png"); }); - test('toString()', () => { + test("toString()", () => { const result = file.toString(); - expect(result).toBe('PersistentFile: dff1d2eaab9752165764dcd00, Original: cat.png, Path: /tmp/cat.png') + expect(result).toBe( + "PersistentFile: dff1d2eaab9752165764dcd00, Original: cat.png, Path: /tmp/cat.png" + ); }); - test('destroy()', () => { + test("destroy()", () => { file.open(); file.destroy(); // eslint-disable-next-line global-require diff --git a/test/unit/querystring-parser.test.js b/test/unit/querystring-parser.test.js index efccfc1e..88b44620 100644 --- a/test/unit/querystring-parser.test.js +++ b/test/unit/querystring-parser.test.js @@ -1,8 +1,8 @@ -import { QuerystringParser } from '../../src/index.js'; +import { QuerystringParser } from "../../src/index.js"; -test('on constructor', () => { +test("on constructor", () => { const parser = new QuerystringParser(); - expect(parser.constructor.name).toBe('QuerystringParser'); + expect(parser.constructor.name).toBe("QuerystringParser"); }); // ! skip diff --git a/test/unit/volatile-file.test.js b/test/unit/volatile-file.test.js index 8d784daf..83490ff0 100644 --- a/test/unit/volatile-file.test.js +++ b/test/unit/volatile-file.test.js @@ -1,8 +1,7 @@ -import {jest} from '@jest/globals'; -import VolatileFile from '../../src/VolatileFile.js'; +import { jest } from "@jest/globals"; +import VolatileFile from "../../src/VolatileFile.js"; - -describe('VolatileFile', () => { +describe("VolatileFile", () => { let file; let writeStreamInstanceMock; let writeStreamMock; @@ -17,43 +16,43 @@ describe('VolatileFile', () => { writeStreamMock = jest.fn(() => writeStreamInstanceMock); file = new VolatileFile({ - xname: 'cat.png', - originalFilename: 'cat.png', - mimetype: 'image/png', + xname: "cat.png", + originalFilename: "cat.png", + mimetype: "image/png", createFileWriteStream: writeStreamMock, }); file.open(); }); - test('open()', (done) => { - const error = new Error('test'); - file.on('error', (err) => { + test("open()", (done) => { + const error = new Error("test"); + file.on("error", (err) => { expect(err).toBe(error); done(); }); - file.emit('error', error); + file.emit("error", error); expect(writeStreamMock).toBeCalled(); expect(writeStreamInstanceMock.on).toBeCalledWith( - 'error', - expect.any(Function), + "error", + expect.any(Function) ); }); - test('toJSON()', () => { + test("toJSON()", () => { const obj = file.toJSON(); - expect(obj.mimetype).toBe('image/png'); - expect(obj.originalFilename).toBe('cat.png'); + expect(obj.mimetype).toBe("image/png"); + expect(obj.originalFilename).toBe("cat.png"); }); - test('toString()', () => { - expect(file.toString()).toBe('VolatileFile: cat.png'); + test("toString()", () => { + expect(file.toString()).toBe("VolatileFile: cat.png"); }); - test('write()', (done) => { + test("write()", (done) => { const buffer = Buffer.alloc(5); writeStreamInstanceMock.write.mockImplementationOnce((writeBuffer, cb) => { expect(buffer).toBe(writeBuffer); @@ -65,18 +64,18 @@ describe('VolatileFile', () => { }); }); - test('end()', (done) => { + test("end()", (done) => { writeStreamInstanceMock.end.mockImplementationOnce((cb) => { cb(); }); - const fileEmitSpy = jest.spyOn(file, 'emit'); + const fileEmitSpy = jest.spyOn(file, "emit"); file.end(() => done()); - expect(fileEmitSpy).toBeCalledWith('end'); + expect(fileEmitSpy).toBeCalledWith("end"); }); - test('destroy()', () => { + test("destroy()", () => { file.destroy(); expect(writeStreamInstanceMock.destroy).toBeCalled(); });