Socket
Socket
Sign inDemoInstall

formidable

Package Overview
Dependencies
Maintainers
5
Versions
78
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

formidable - npm Package Compare versions

Comparing version 2.1.2 to 3.5.1

dist/helpers/firstValues.cjs

87

package.json
{
"name": "formidable",
"version": "2.1.2",
"version": "3.5.1",
"license": "MIT",

@@ -9,12 +9,45 @@ "description": "A node.js module for parsing form data, especially file uploads.",

"repository": "node-formidable/formidable",
"main": "./src/index.js",
"type": "module",
"main": "./dist/index.cjs",
"exports": {
".": {
"import": {
"default": "./src/index.js"
},
"require": {
"default": "./dist/index.cjs"
},
"default": "./dist/index.cjs"
},
"./src/helpers/*.js": {
"import": {
"default": "./src/helpers/*.js"
},
"require": {
"default": "./dist/helpers/*.cjs"
}
},
"./src/parsers/*.js": {
"import": {
"default": "./src/parsers/*.js"
},
"require": {
"default": "./dist/index.cjs"
}
}
},
"files": [
"src"
"src",
"dist"
],
"publishConfig": {
"access": "public",
"tag": "v2-latest"
"tag": "latest"
},
"scripts": {
"build-package": "rollup --config ./tool/rollup.config.js",
"prepublishOnly": "npm run build-package",
"bench": "node benchmark",
"bench2prep": "node benchmark/server.js",
"bench2": "bombardier --body-file=\"./README.md\" --method=POST --duration=10s --connections=100 http://localhost:3000/api/upload",
"fmt": "yarn run fmt:prepare '**/*'",

@@ -28,6 +61,8 @@ "fmt:prepare": "prettier --write",

"pretest": "del-cli ./test/tmp && make-dir ./test/tmp",
"test": "jest --coverage",
"test-specific": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/standalone/keep-alive-error.test.js",
"test": "npm run test-jest && npm run test-node",
"test-jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage",
"test-node": "node --test test-node/",
"pretest:ci": "yarn run pretest",
"test:ci": "nyc jest --coverage",
"test:jest": "jest --coverage"
"test:ci": "node --experimental-vm-modules node_modules/.bin/nyc jest --testPathPattern=test/ --coverage && node --experimental-vm-modules node_modules/.bin/nyc node --test test-node/"
},

@@ -37,4 +72,3 @@ "dependencies": {

"hexoid": "^1.0.0",
"once": "^1.4.0",
"qs": "^6.11.0"
"once": "^1.4.0"
},

@@ -44,2 +78,5 @@ "devDependencies": {

"@commitlint/config-conventional": "8.3.4",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-node-resolve": "^15.1.0",
"@sindresorhus/slugify": "^2.1.0",
"@tunnckocore/prettier-config": "1.3.8",

@@ -53,12 +90,13 @@ "del-cli": "3.0.0",

"express": "4.17.1",
"formdata-polyfill": "^4.0.10",
"husky": "4.2.5",
"jest": "25.4.0",
"jest": "27.2.4",
"koa": "2.11.0",
"lint-staged": "10.2.7",
"make-dir-cli": "2.0.0",
"nyc": "15.0.1",
"nyc": "15.1.0",
"prettier": "2.0.5",
"prettier-plugin-pkgjson": "0.2.8",
"request": "2.88.2",
"supertest": "4.0.2"
"rollup": "^3.25.3",
"supertest": "6.1.6"
},

@@ -68,12 +106,2 @@ "jest": {

},
"keywords": [
"multipart",
"form",
"data",
"querystring",
"www",
"json",
"ulpoad",
"file"
],
"husky": {

@@ -103,3 +131,14 @@ "hooks": {

]
}
},
"packageManager": "yarn@1.22.17",
"keywords": [
"multipart",
"form",
"data",
"querystring",
"www",
"json",
"ulpoad",
"file"
]
}

@@ -23,3 +23,3 @@ <p align="center">

[![Minimum Required Nodejs][nodejs-img]][npmv-url]
[![Tidelift Subcsription][tidelift-img]][tidelift-url]
[![Tidelift Subscription][tidelift-img]][tidelift-url]
[![Buy me a Kofi][kofi-img]][kofi-url]

@@ -72,2 +72,4 @@ [![Renovate App Status][renovateapp-img]][renovateapp-url]

This package is a dual ESM/commonjs package.
This project requires `Node.js >= 10.13`. Install it using

@@ -80,13 +82,12 @@ [yarn](https://yarnpkg.com) or [npm](https://npmjs.com).<br /> _We highly

```sh
```
# v2
npm install formidable
npm install formidable@latest
npm install formidable@v2
# or v3
# v3
npm install formidable
npm install formidable@v3
```
_**Note:** In near future v3 will be published on the `latest` NPM dist-tag. Future not ready releases will continue to be published on `canary` dist-tag._
_**Note:** Future not ready releases will be published on `*-next` dist-tags for the corresponding version._

@@ -104,20 +105,25 @@

```js
const http = require('http');
const formidable = require('formidable');
import http from 'node:http';
import formidable, {errors as formidableErrors} from 'formidable';
const server = http.createServer((req, res) => {
const server = http.createServer(async (req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// parse a file upload
const form = formidable({ multiples: true });
const form = formidable({});
let fields;
let files;
try {
[fields, files] = await form.parse(req);
} catch (err) {
// example to check for a very specific error
if (err.code === formidableErrors.maxFieldsExceeded) {
form.parse(req, (err, fields, files) => {
if (err) {
}
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.end(JSON.stringify({ fields, files }, null, 2));
});
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
return;

@@ -153,4 +159,4 @@ }

```js
const express = require('express');
const formidable = require('formidable');
import express from 'express';
import formidable from 'formidable';

@@ -171,3 +177,3 @@ const app = express();

app.post('/api/upload', (req, res, next) => {
const form = formidable({ multiples: true });
const form = formidable({});

@@ -201,4 +207,4 @@ form.parse(req, (err, fields, files) => {

```js
const Koa = require('koa');
const formidable = require('formidable');
import Koa from 'Koa';
import formidable from 'formidable';

@@ -213,3 +219,3 @@ const app = new Koa();

if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') {
const form = formidable({ multiples: true });
const form = formidable({});

@@ -303,16 +309,4 @@ // not very elegant, but that's for now if you don't want to use `koa-better-body`

```js
const formidable = require('formidable');
import formidable from 'formidable';
const form = formidable(options);
// or
const { formidable } = require('formidable');
const form = formidable(options);
// or
const { IncomingForm } = require('formidable');
const form = new IncomingForm(options);
// or
const { Formidable } = require('formidable');
const form = new Formidable(options);
```

@@ -331,9 +325,13 @@

extensions of the original files or not
- `options.allowEmptyFiles` **{boolean}** - default `true`; allow upload empty
- `options.allowEmptyFiles` **{boolean}** - default `false`; allow upload empty
files
- `options.minFileSize` **{number}** - default `1` (1byte); the minium size of
uploaded file.
- `options.maxFiles` **{number}** - default `Infinity`;
limit the amount of uploaded files, set Infinity for unlimited
- `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb);
limit the size of uploaded file.
- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set 0 for unlimited
limit the size of each uploaded file.
- `options.maxTotalFileSize` **{number}** - default `options.maxFileSize`;
limit the size of the batch of uploaded files.
- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set Infinity for unlimited
- `options.maxFieldsSize` **{number}** - default `20 * 1024 * 1024` (20mb);

@@ -356,7 +354,2 @@ limit the amount of memory all fields together (except files) can allocate in

behavior of writing the file in the host machine file system is lost.
- `options.multiples` **{boolean}** - default `false`; when you call the
`.parse` method, the `files` argument (of the callback) will contain arrays of
files for inputs which submit multiple files using the HTML5 `multiple`
attribute. Also, the `fields` argument will contain arrays of values for
fields that have names ending with '[]'.
- `options.filename` **{function}** - default `undefined` Use it to control

@@ -366,7 +359,15 @@ newFilename. Must return a string. Will be joined with options.uploadDir.

- `options.filter` **{function}** - default function that always returns true.
Use it to filter files before they are uploaded. Must return a boolean.
Use it to filter files before they are uploaded. Must return a boolean. Will not make the form.parse error
- `options.createDirsFromUploads` **{boolean}** - default false. If true, makes direct folder uploads possible. Use `<input type="file" name="folders" webkitdirectory directory multiple>` 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
where part can be decomposed as
```js
const { originalFilename, mimetype} = part;
```
_**Note:** If this size of combined fields, or size of some file is exceeded, an

@@ -387,3 +388,3 @@ `'error'` event is fired._

**Note:** use an outside variable to cancel all uploads upon the first error
Behaves like Array.filter: Returning false will simply ignore the file and go to the next.

@@ -399,13 +400,29 @@ ```js

**Note:** use an outside variable to cancel all uploads upon the first error
### .parse(request, callback)
**Note:** use form.emit('error') to make form.parse error
Parses an incoming Node.js `request` containing form data. If `callback` is
provided, all fields and files are collected and passed to the callback.
```js
let cancelUploads = false;// create variable at the same scope as form
const options = {
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
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.
```js
const formidable = require('formidable');
const form = formidable({ uploadDir: __dirname });
const form = formidable({ multiples: true, uploadDir: __dirname });
form.parse(req, (err, fields, files) => {

@@ -415,2 +432,5 @@ console.log('fields:', fields);

});
// with Promise
const [fields, files] = await form.parse(req);
```

@@ -515,3 +535,3 @@

A method that allows you to extend the Formidable library. By default we include
4 plugins, which esentially are adapters to plug the different built-in parsers.
4 plugins, which essentially are adapters to plug the different built-in parsers.

@@ -534,4 +554,2 @@ **The plugins added by this method are always enabled.**

```js
const formidable = require('formidable');
const form = formidable({ keepExtensions: true });

@@ -561,7 +579,6 @@

```js
const { Formidable } = require('formidable');
const form = new Formidable({
import formidable, {octetstream, querystring, json} from "formidable";
const form = formidable({
hashAlgorithm: 'sha1',
enabledPlugins: ['octetstream', 'querystring', 'json'],
enabledPlugins: [octetstream, querystring, json],
});

@@ -652,3 +669,3 @@ ```

Emitted after each incoming chunk of data that has been parsed. Can be used to
roll your own progress bar.
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 =`

@@ -679,3 +696,3 @@ ```js

// file.newFilename generated hexoid or what options.filename returned
// file.filepath default pathnme as per options.uploadDir and options.filename
// file.filepath default pathname as per options.uploadDir and options.filename
// file.filepath = CUSTOM_PATH // to change the final path

@@ -730,2 +747,43 @@ });

### Helpers
#### firstValues
Gets first values of fields, like pre 3.0.0 without multiples pass in a list of optional exceptions where arrays of strings is still wanted (`<select multiple>` for example)
```js
import { firstValues } from 'formidable/src/helpers/firstValues.js';
// ...
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
//...
}
const exceptions = ['thisshouldbeanarray'];
const fieldsSingle = firstValues(form, fieldsMultiple, exceptions);
// ...
```
#### readBooleans
Html form input type="checkbox" only send the value "on" if checked,
convert it to booleans for each input that is expected to be sent as a checkbox, only use after firstValues or similar was called.
```js
import { firstValues } from 'formidable/src/helpers/firstValues.js';
import { readBooleans } from 'formidable/src/helpers/readBooleans.js';
// ...
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
//...
}
const fieldsSingle = firstValues(form, fieldsMultiple);
const expectedBooleans = ['checkbox1', 'wantsNewsLetter', 'hasACar'];
const fieldsWithBooleans = readBooleans(fieldsSingle, expectedBooleans);
// ...
```
## Changelog

@@ -732,0 +790,0 @@

/* eslint-disable class-methods-use-this */
/* eslint-disable no-underscore-dangle */
'use strict';
import os from 'node:os';
import path from 'node:path';
import fsPromises from 'node:fs/promises';
import { EventEmitter } from 'node:events';
import { StringDecoder } from 'node:string_decoder';
import hexoid from 'hexoid';
import once from 'once';
import dezalgo from 'dezalgo';
import { octetstream, querystring, multipart, json } from './plugins/index.js';
import PersistentFile from './PersistentFile.js';
import VolatileFile from './VolatileFile.js';
import DummyParser from './parsers/Dummy.js';
import MultipartParser from './parsers/Multipart.js';
import * as errors from './FormidableError.js';
import FormidableError from './FormidableError.js';
const os = require('os');
const path = require('path');
const hexoid = require('hexoid');
const once = require('once');
const dezalgo = require('dezalgo');
const { EventEmitter } = require('events');
const { StringDecoder } = require('string_decoder');
const qs = require('qs');
const toHexoId = hexoid(25);

@@ -19,5 +24,8 @@ const DEFAULT_OPTIONS = {

maxFieldsSize: 20 * 1024 * 1024,
maxFiles: Infinity,
maxFileSize: 200 * 1024 * 1024,
maxTotalFileSize: undefined,
minFileSize: 1,
allowEmptyFiles: true,
allowEmptyFiles: false,
createDirsFromUploads: false,
keepExtensions: false,

@@ -27,19 +35,11 @@ encoding: 'utf-8',

uploadDir: os.tmpdir(),
multiples: false,
enabledPlugins: ['octetstream', 'querystring', 'multipart', 'json'],
enabledPlugins: [octetstream, querystring, multipart, json],
fileWriteStreamHandler: null,
defaultInvalidName: 'invalid-name',
filter: function () {
filter(_part) {
return true;
},
filename: undefined,
};
const PersistentFile = require('./PersistentFile');
const VolatileFile = require('./VolatileFile');
const DummyParser = require('./parsers/Dummy');
const MultipartParser = require('./parsers/Multipart');
const errors = require('./FormidableError.js');
const { FormidableError } = errors;
function hasOwnProp(obj, key) {

@@ -49,2 +49,38 @@ 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 createNecessaryDirectoriesAsync = decorateForceSequential(function (filePath) {
const directoryname = path.dirname(filePath);
return fsPromises.mkdir(directoryname, { recursive: true });
});
const invalidExtensionChar = (c) => {
const code = c.charCodeAt(0);
return !(
code === 46 || // .
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122)
);
};
class IncomingForm extends EventEmitter {

@@ -55,2 +91,5 @@ constructor(options = {}) {

this.options = { ...DEFAULT_OPTIONS, ...options };
if (!this.options.maxTotalFileSize) {
this.options.maxTotalFileSize = this.options.maxFileSize
}

@@ -72,2 +111,3 @@ const dir = path.resolve(

'_parser',
'req',
].forEach((key) => {

@@ -81,3 +121,3 @@ this[key] = null;

this._fieldsSize = 0;
this._fileSize = 0;
this._totalFileSize = 0;
this._plugins = [];

@@ -97,9 +137,10 @@ this.openedFiles = [];

this.options.enabledPlugins.forEach((pluginName) => {
const plgName = pluginName.toLowerCase();
// eslint-disable-next-line import/no-dynamic-require, global-require
this.use(require(path.join(__dirname, 'plugins', `${plgName}.js`)));
this.options.enabledPlugins.forEach((plugin) => {
this.use(plugin);
});
this._setUpMaxFields();
this._setUpMaxFiles();
this.ended = undefined;
this.type = undefined;
}

@@ -118,80 +159,83 @@

parse(req, cb) {
this.pause = () => {
try {
req.pause();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
pause () {
try {
this.req.pause();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return true;
};
return false;
}
return true;
}
this.resume = () => {
try {
req.resume();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
resume () {
try {
this.req.resume();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
};
return true;
}
// returns a promise if no callback is provided
async parse(req, cb) {
this.req = req;
let promise;
// Setup callback first, so we don't miss anything from data events emitted immediately.
if (cb) {
const callback = once(dezalgo(cb));
const fields = {};
let mockFields = '';
const files = {};
this.on('field', (name, value) => {
if (
this.options.multiples &&
(this.type === 'multipart' || this.type === 'urlencoded')
) {
const mObj = { [name]: value };
mockFields = mockFields
? `${mockFields}&${qs.stringify(mObj)}`
: `${qs.stringify(mObj)}`;
} else {
fields[name] = value;
}
if (!cb) {
let resolveRef;
let rejectRef;
promise = new Promise((resolve, reject) => {
resolveRef = resolve;
rejectRef = reject;
});
this.on('file', (name, file) => {
// TODO: too much nesting
if (this.options.multiples) {
if (hasOwnProp(files, name)) {
if (!Array.isArray(files[name])) {
files[name] = [files[name]];
}
files[name].push(file);
} else {
files[name] = file;
}
cb = (err, fields, files) => {
if (err) {
rejectRef(err);
} else {
files[name] = file;
resolveRef([fields, files]);
}
});
this.on('error', (err) => {
callback(err, fields, files);
});
this.on('end', () => {
if (this.options.multiples) {
Object.assign(fields, qs.parse(mockFields));
}
callback(null, fields, files);
});
}
}
const callback = once(dezalgo(cb));
this.fields = {};
const files = {};
this.on('field', (name, value) => {
if (this.type === 'multipart' || this.type === 'urlencoded') {
if (!hasOwnProp(this.fields, name)) {
this.fields[name] = [value];
} else {
this.fields[name].push(value);
}
} else {
this.fields[name] = value;
}
});
this.on('file', (name, file) => {
if (!hasOwnProp(files, name)) {
files[name] = [file];
} else {
files[name].push(file);
}
});
this.on('error', (err) => {
callback(err, this.fields, files);
});
this.on('end', () => {
callback(null, this.fields, files);
});
// Parse headers and setup the parser, ready to start listening for data.
this.writeHeaders(req.headers);
await this.writeHeaders(req.headers);

@@ -221,12 +265,13 @@ // Start listening for data.

}
this._maybeEnd();
});
if (promise) {
return promise;
}
return this;
}
writeHeaders(headers) {
async writeHeaders(headers) {
this.headers = headers;
this._parseContentLength();
this._parseContentType();
await this._parseContentType();

@@ -268,18 +313,8 @@ if (!this._parser) {

pause() {
// this does nothing, unless overwritten in IncomingForm.parse
return false;
}
resume() {
// this does nothing, unless overwritten in IncomingForm.parse
return false;
}
onPart(part) {
// this method can be overwritten by the user
this._handlePart(part);
return this._handlePart(part);
}
_handlePart(part) {
async _handlePart(part) {
if (part.originalFilename && typeof part.originalFilename !== 'string') {

@@ -338,5 +373,6 @@ this._error(

let fileSize = 0;
const newFilename = this._getNewName(part);
const filepath = this._joinDirectoryName(newFilename);
const file = this._newFile({
const file = await this._newFile({
newFilename,

@@ -356,18 +392,10 @@ filepath,

part.on('data', (buffer) => {
this._fileSize += buffer.length;
if (this._fileSize < this.options.minFileSize) {
this._totalFileSize += buffer.length;
fileSize += buffer.length;
if (this._totalFileSize > this.options.maxTotalFileSize) {
this._error(
new FormidableError(
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${this._fileSize} bytes of file data`,
errors.smallerThanMinFileSize,
400,
),
);
return;
}
if (this._fileSize > this.options.maxFileSize) {
this._error(
new FormidableError(
`options.maxFileSize (${this.options.maxFileSize} bytes) exceeded, received ${this._fileSize} bytes of file data`,
errors.biggerThanMaxFileSize,
`options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`,
errors.biggerThanTotalMaxFileSize,
413,

@@ -388,6 +416,6 @@ ),

part.on('end', () => {
if (!this.options.allowEmptyFiles && this._fileSize === 0) {
if (!this.options.allowEmptyFiles && fileSize === 0) {
this._error(
new FormidableError(
`options.allowEmptyFiles is false, file size should be greather than 0`,
`options.allowEmptyFiles is false, file size should be greater than 0`,
errors.noEmptyFiles,

@@ -399,2 +427,22 @@ 400,

}
if (fileSize < this.options.minFileSize) {
this._error(
new FormidableError(
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${fileSize} bytes of file data`,
errors.smallerThanMinFileSize,
400,
),
);
return;
}
if (fileSize > this.options.maxFileSize) {
this._error(
new FormidableError(
`options.maxFileSize (${this.options.maxFileSize} bytes), received ${fileSize} bytes of file data`,
errors.biggerThanMaxFileSize,
413,
),
);
return;
}

@@ -410,3 +458,3 @@ file.end(() => {

// eslint-disable-next-line max-statements
_parseContentType() {
async _parseContentType() {
if (this.bytesExpected === 0) {

@@ -428,13 +476,10 @@ this._parser = new DummyParser(this, this.options);

const results = [];
const _dummyParser = new DummyParser(this, this.options);
// eslint-disable-next-line no-plusplus
for (let idx = 0; idx < this._plugins.length; idx++) {
const plugin = this._plugins[idx];
new DummyParser(this, this.options);
const results = [];
await Promise.all(this._plugins.map(async (plugin, idx) => {
let pluginReturn = null;
try {
pluginReturn = plugin(this, this.options) || this;
pluginReturn = await plugin(this, this.options) || this;
} catch (err) {

@@ -451,3 +496,2 @@ // directly throw from the `form.parse` method;

}
Object.assign(this, pluginReturn);

@@ -457,22 +501,7 @@

this.emit('plugin', idx, pluginReturn);
results.push(pluginReturn);
}
}));
this.emit('pluginsResults', results);
// NOTE: probably not needed, because we check options.enabledPlugins in the constructor
// if (results.length === 0 /* && results.length !== this._plugins.length */) {
// this._error(
// new Error(
// `bad content-type header, unknown content-type: ${this.headers['content-type']}`,
// ),
// );
// }
}
_error(err, eventName = 'error') {
// if (!err && this.error) {
// this.emit('error', this.error);
// return;
// }
if (this.error || this.ended) {

@@ -482,10 +511,9 @@ return;

this.req = null;
this.error = err;
this.emit(eventName, err);
if (Array.isArray(this.openedFiles)) {
this.openedFiles.forEach((file) => {
file.destroy();
});
}
this.openedFiles.forEach((file) => {
file.destroy();
});
}

@@ -510,19 +538,31 @@

_newFile({ filepath, originalFilename, mimetype, newFilename }) {
return this.options.fileWriteStreamHandler
? new VolatileFile({
newFilename,
filepath,
originalFilename,
mimetype,
createFileWriteStream: this.options.fileWriteStreamHandler,
hashAlgorithm: this.options.hashAlgorithm,
})
: new PersistentFile({
newFilename,
filepath,
originalFilename,
mimetype,
hashAlgorithm: this.options.hashAlgorithm,
});
async _newFile({ filepath, originalFilename, mimetype, newFilename }) {
if (this.options.fileWriteStreamHandler) {
return new VolatileFile({
newFilename,
filepath,
originalFilename,
mimetype,
createFileWriteStream: this.options.fileWriteStreamHandler,
hashAlgorithm: this.options.hashAlgorithm,
});
}
if (this.options.createDirsFromUploads) {
try {
await createNecessaryDirectoriesAsync(filepath);
} catch (errorCreatingDir) {
this._error(new FormidableError(
`cannot create directory`,
errors.cannotCreateDir,
409,
));
}
}
return new PersistentFile({
newFilename,
filepath,
originalFilename,
mimetype,
hashAlgorithm: this.options.hashAlgorithm,
});
}

@@ -547,2 +587,5 @@

// able to get composed extension with multiple dots
// "a.b.c" -> ".b.c"
// as opposed to path.extname -> ".c"
_getExtension(str) {

@@ -556,13 +599,21 @@ if (!str) {

const lastDot = basename.lastIndexOf('.');
const extname = path.extname(basename).replace(/(\.[a-z0-9]+).*/i, '$1');
let rawExtname = path.extname(basename);
if (firstDot === lastDot) {
return extname;
if (firstDot !== lastDot) {
rawExtname = basename.slice(firstDot);
}
return basename.slice(firstDot, lastDot) + extname;
let filtered;
const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar);
if (firstInvalidIndex === -1) {
filtered = rawExtname;
} else {
filtered = rawExtname.substring(0, firstInvalidIndex);
}
if (filtered === '.') {
return '';
}
return filtered;
}
_joinDirectoryName(name) {

@@ -599,8 +650,9 @@ const newPath = path.join(this.uploadDir, name);

if (part && this.options.keepExtensions) {
const originalFilename = typeof part === 'string' ? part : part.originalFilename;
const originalFilename =
typeof part === 'string' ? part : part.originalFilename;
return `${name}${this._getExtension(originalFilename)}`;
}
return name;
}
};
}

@@ -610,3 +662,3 @@ }

_setUpMaxFields() {
if (this.options.maxFields !== 0) {
if (this.options.maxFields !== Infinity) {
let fieldsCount = 0;

@@ -628,10 +680,25 @@ this.on('field', () => {

_setUpMaxFiles() {
if (this.options.maxFiles !== Infinity) {
let fileCount = 0;
this.on('fileBegin', () => {
fileCount += 1;
if (fileCount > this.options.maxFiles) {
this._error(
new FormidableError(
`options.maxFiles (${this.options.maxFiles}) exceeded`,
errors.maxFilesExceeded,
413,
),
);
}
});
}
}
_maybeEnd() {
// console.log('ended', this.ended);
// console.log('_flushing', this._flushing);
// console.log('error', this.error);
if (!this.ended || this._flushing || this.error) {
return;
}
this.req = null;
this.emit('end');

@@ -641,3 +708,3 @@ }

IncomingForm.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
module.exports = IncomingForm;
export default IncomingForm;
export { DEFAULT_OPTIONS };

@@ -1,3 +0,1 @@

/* eslint-disable no-plusplus */
const missingPlugin = 1000;

@@ -12,3 +10,3 @@ const pluginFunction = 1001;

const smallerThanMinFileSize = 1008;
const biggerThanMaxFileSize = 1009;
const biggerThanTotalMaxFileSize = 1009;
const noEmptyFiles = 1010;

@@ -19,2 +17,6 @@ const missingContentType = 1011;

const unknownTransferEncoding = 1014;
const maxFilesExceeded = 1015;
const biggerThanMaxFileSize = 1016;
const pluginFailed = 1017;
const cannotCreateDir = 1018;

@@ -29,3 +31,3 @@ const FormidableError = class extends Error {

module.exports = {
export {
missingPlugin,

@@ -39,2 +41,3 @@ pluginFunction,

maxFieldsExceeded,
maxFilesExceeded,
smallerThanMinFileSize,

@@ -47,4 +50,7 @@ biggerThanMaxFileSize,

unknownTransferEncoding,
biggerThanTotalMaxFileSize,
pluginFailed,
cannotCreateDir,
};
FormidableError,
};
export default FormidableError;

@@ -1,10 +0,6 @@

'use strict';
import PersistentFile from './PersistentFile.js';
import VolatileFile from './VolatileFile.js';
import Formidable, { DEFAULT_OPTIONS } from './Formidable.js';
const PersistentFile = require('./PersistentFile');
const VolatileFile = require('./VolatileFile');
const Formidable = require('./Formidable');
const FormidableError = require('./FormidableError');
const plugins = require('./plugins/index');
const parsers = require('./parsers/index');

@@ -14,26 +10,24 @@ // make it available without requiring the `new` keyword

const formidable = (...args) => new Formidable(...args);
const {enabledPlugins} = DEFAULT_OPTIONS;
module.exports = Object.assign(formidable, {
errors: FormidableError,
File: PersistentFile,
export default formidable;
export {
PersistentFile as File,
PersistentFile,
VolatileFile,
Formidable,
// alias
Formidable as IncomingForm,
// as named
formidable,
// alias
IncomingForm: Formidable,
// parsers
...parsers,
parsers,
// misc
defaultOptions: Formidable.DEFAULT_OPTIONS,
enabledPlugins: Formidable.DEFAULT_OPTIONS.enabledPlugins,
DEFAULT_OPTIONS as defaultOptions,
enabledPlugins,
};
// plugins
plugins: {
...plugins,
},
});
export * from './parsers/index.js';
export * from './plugins/index.js';
export * as errors from './FormidableError.js';
/* eslint-disable no-underscore-dangle */
'use strict';
import { Transform } from 'node:stream';
const { Transform } = require('stream');
class DummyParser extends Transform {

@@ -21,2 +19,2 @@ constructor(incomingForm, options = {}) {

module.exports = DummyParser;
export default DummyParser;

@@ -1,10 +0,8 @@

'use strict';
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';
const JSONParser = require('./JSON');
const DummyParser = require('./Dummy');
const MultipartParser = require('./Multipart');
const OctetStreamParser = require('./OctetStream');
const QueryStringParser = require('./Querystring');
Object.assign(exports, {
export {
JSONParser,

@@ -14,5 +12,5 @@ DummyParser,

OctetStreamParser,
OctetstreamParser: OctetStreamParser,
OctetStreamParser as OctetstreamParser,
QueryStringParser,
QuerystringParser: QueryStringParser,
});
QueryStringParser as QuerystringParser,
};
/* eslint-disable no-underscore-dangle */
'use strict';
import { Transform } from 'node:stream';
const { Transform } = require('stream');
class JSONParser extends Transform {

@@ -22,6 +20,3 @@ constructor(options = {}) {

const fields = JSON.parse(this.chunks.join(''));
Object.keys(fields).forEach((key) => {
const value = fields[key];
this.push({ key, value });
});
this.push(fields);
} catch (e) {

@@ -36,2 +31,2 @@ callback(e);

module.exports = JSONParser;
export default JSONParser;

@@ -6,9 +6,6 @@ /* eslint-disable no-fallthrough */

'use strict';
import { Transform } from 'node:stream';
import * as errors from '../FormidableError.js';
import FormidableError from '../FormidableError.js';
const { Transform } = require('stream');
const errors = require('../FormidableError.js');
const { FormidableError } = errors;
let s = 0;

@@ -46,6 +43,6 @@ const STATE = {

exports.STATES = {};
export const STATES = {};
Object.keys(STATE).forEach((stateName) => {
exports.STATES[stateName] = STATE[stateName];
STATES[stateName] = STATE[stateName];
});

@@ -67,2 +64,10 @@

_endUnexpected() {
return new FormidableError(
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
errors.malformedMultipart,
400,
);
}
_flush(done) {

@@ -77,9 +82,5 @@ if (

} else if (this.state !== STATE.END) {
done(
new FormidableError(
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
errors.malformedMultipart,
400,
),
);
done(this._endUnexpected());
} else {
done();
}

@@ -146,3 +147,4 @@ }

case STATE.PARSER_UNINITIALIZED:
return i;
done(this._endUnexpected());
return;
case STATE.START:

@@ -156,3 +158,4 @@ index = 0;

} else if (c !== CR) {
return i;
done(this._endUnexpected());
return;
}

@@ -171,3 +174,4 @@ index++;

} else {
return i;
done(this._endUnexpected());
return;
}

@@ -203,3 +207,4 @@ break;

// empty header field
return i;
done(this._endUnexpected());
return;
}

@@ -213,3 +218,4 @@ dataCallback('headerField', true);

if (cl < A || cl > Z) {
return i;
done(this._endUnexpected());
return;
}

@@ -233,3 +239,4 @@ break;

if (c !== LF) {
return i;
done(this._endUnexpected());
return;
}

@@ -240,3 +247,4 @@ state = STATE.HEADER_FIELD_START;

if (c !== LF) {
return i;
done(this._endUnexpected());
return;
}

@@ -254,3 +262,3 @@

if (index === 0) {
// boyer-moore derrived algorithm to safely skip non-boundary data
// boyer-moore derived algorithm to safely skip non-boundary data
i += boundaryEnd;

@@ -329,3 +337,4 @@ while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {

default:
return i;
done(this._endUnexpected());
return;
}

@@ -360,2 +369,2 @@ }

module.exports = Object.assign(MultipartParser, { STATES: exports.STATES });
export default Object.assign(MultipartParser, { STATES });

@@ -1,5 +0,3 @@

'use strict';
import { PassThrough } from 'node:stream';
const { PassThrough } = require('stream');
class OctetStreamParser extends PassThrough {

@@ -12,2 +10,2 @@ constructor(options = {}) {

module.exports = OctetStreamParser;
export default OctetStreamParser;
/* eslint-disable no-underscore-dangle */
'use strict';
import { Transform } from 'node:stream';
const { Transform } = require('stream');
const querystring = require('querystring');
// This is a buffering parser, not quite as nice as the multipart one.
// If I find time I'll rewrite this to be fully streaming as well
// This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser
class QuerystringParser extends Transform {

@@ -25,8 +21,7 @@ constructor(options = {}) {

_flush(callback) {
const fields = querystring.parse(this.buffer, '&', '=');
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const key in fields) {
const fields = new URLSearchParams(this.buffer);
for (const [key, value] of fields) {
this.push({
key,
value: fields[key],
value,
});

@@ -39,2 +34,2 @@ }

module.exports = QuerystringParser;
export default QuerystringParser;
// not used
/* eslint-disable no-underscore-dangle */
'use strict';
import { Transform } from 'node:stream';
import FormidableError, { maxFieldsSizeExceeded } from '../FormidableError.js';
const { Transform } = require('stream');
const errors = require('../FormidableError.js');
const { FormidableError } = errors;
const AMPERSAND = 38;

@@ -64,3 +60,3 @@ const EQUALS = 61;

),
errors.maxFieldsSizeExceeded,
maxFieldsSizeExceeded,
413,

@@ -86,5 +82,5 @@ );

// 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'));
}
if (this.buffer && this.buffer.length) {
this.emitField(this.buffer.toString('ascii'));
}
} else {

@@ -114,3 +110,3 @@ // We have a key, we may or may not have a value

module.exports = QuerystringParser;
export default QuerystringParser;

@@ -117,0 +113,0 @@ // const q = new QuerystringParser({maxFieldSize: 100});

/* eslint-disable no-underscore-dangle */
'use strict';
import fs from 'node:fs';
import crypto from 'node:crypto';
import { EventEmitter } from 'node:events';
const fs = require('fs');
const crypto = require('crypto');
const { EventEmitter } = require('events');
class PersistentFile extends EventEmitter {

@@ -27,3 +25,3 @@ constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm }) {

open() {
this._writeStream = new fs.WriteStream(this.filepath);
this._writeStream = fs.createWriteStream(this.filepath);
this._writeStream.on('error', (err) => {

@@ -84,6 +82,9 @@ this.emit('error', err);

this._writeStream.destroy();
fs.unlink(this.filepath, () => {});
const filepath = this.filepath;
setTimeout(function () {
fs.unlink(filepath, () => {});
}, 1)
}
}
module.exports = PersistentFile;
export default PersistentFile;

@@ -1,13 +0,6 @@

'use strict';
import octetstream from './octetstream.js';
import querystring from './querystring.js';
import multipart from './multipart.js';
import json from './json.js';
const octetstream = require('./octetstream');
const querystring = require('./querystring');
const multipart = require('./multipart');
const json = require('./json');
Object.assign(exports, {
octetstream,
querystring,
multipart,
json,
});
export { octetstream, querystring, multipart, json };
/* eslint-disable no-underscore-dangle */
'use strict';
import JSONParser from '../parsers/JSON.js';
const JSONParser = require('../parsers/JSON');
export const jsonType = 'json';
// the `options` is also available through the `this.options` / `formidable.options`
module.exports = function plugin(formidable, options) {
export default function plugin(formidable, options) {
// the `this` context is always formidable, as the first argument of a plugin

@@ -18,2 +17,4 @@ // but this allows us to customize/test each plugin

}
return self;
};

@@ -25,8 +26,8 @@

function init(_self, _opts) {
this.type = 'json';
this.type = jsonType;
const parser = new JSONParser(this.options);
parser.on('data', ({ key, value }) => {
this.emit('field', key, value);
parser.on('data', (fields) => {
this.fields = fields;
});

@@ -33,0 +34,0 @@

/* eslint-disable no-underscore-dangle */
'use strict';
import { Stream } from 'node:stream';
import MultipartParser from '../parsers/Multipart.js';
import * as errors from '../FormidableError.js';
import FormidableError from '../FormidableError.js';
const { Stream } = require('stream');
const MultipartParser = require('../parsers/Multipart');
const errors = require('../FormidableError.js');
const { FormidableError } = errors;
export const multipartType = 'multipart';
// the `options` is also available through the `options` / `formidable.options`
module.exports = function plugin(formidable, options) {
export default function plugin(formidable, options) {
// the `this` context is always formidable, as the first argument of a plugin

@@ -38,3 +36,4 @@ // but this allows us to customize/test each plugin

}
};
return self;
}

@@ -46,3 +45,3 @@ // Note that it's a good practice (but it's up to you) to use the `this.options` instead

return function initMultipart() {
this.type = 'multipart';
this.type = multipartType;

@@ -57,3 +56,3 @@ const parser = new MultipartParser(this.options);

// eslint-disable-next-line max-statements, consistent-return
parser.on('data', ({ name, buffer, start, end }) => {
parser.on('data', async ({ name, buffer, start, end }) => {
if (name === 'partBegin') {

@@ -131,3 +130,3 @@ part = new Stream();

encoding. So we should always work with a number of bytes that
can be divided by 4, it will result in a number of buytes that
can be divided by 4, it will result in a number of bytes that
can be divided vy 3.

@@ -167,4 +166,5 @@ */

}
this.onPart(part);
this._parser.pause();
await this.onPart(part);
this._parser.resume();
} else if (name === 'end') {

@@ -171,0 +171,0 @@ this.ended = true;

/* eslint-disable no-underscore-dangle */
'use strict';
import OctetStreamParser from '../parsers/OctetStream.js';
const OctetStreamParser = require('../parsers/OctetStream');
export const octetStreamType = 'octet-stream';
// the `options` is also available through the `options` / `formidable.options`
module.exports = function plugin(formidable, options) {
export default async function plugin(formidable, options) {
// the `this` context is always formidable, as the first argument of a plugin

@@ -16,7 +15,6 @@ // but this allows us to customize/test each plugin

if (/octet-stream/i.test(self.headers['content-type'])) {
init.call(self, self, options);
await init.call(self, self, options);
}
return self;
};
}

@@ -26,4 +24,4 @@ // Note that it's a good practice (but it's up to you) to use the `this.options` instead

// to test the plugin you can pass custom `this` context to it (and so `this.options`)
function init(_self, _opts) {
this.type = 'octet-stream';
async function init(_self, _opts) {
this.type = octetStreamType;
const originalFilename = this.headers['x-file-name'];

@@ -38,3 +36,3 @@ const mimetype = this.headers['content-type'];

const filepath = this._joinDirectoryName(newFilename);
const file = this._newFile({
const file = await this._newFile({
newFilename,

@@ -41,0 +39,0 @@ filepath,

/* eslint-disable no-underscore-dangle */
'use strict';
const QuerystringParser = require('../parsers/Querystring');
import QuerystringParser from '../parsers/Querystring.js';
export const querystringType = 'urlencoded';
// the `options` is also available through the `this.options` / `formidable.options`
module.exports = function plugin(formidable, options) {
export default function plugin(formidable, options) {
// the `this` context is always formidable, as the first argument of a plugin

@@ -18,3 +18,2 @@ // but this allows us to customize/test each plugin

}
return self;

@@ -27,3 +26,3 @@ };

function init(_self, _opts) {
this.type = 'urlencoded';
this.type = querystringType;

@@ -30,0 +29,0 @@ const parser = new QuerystringParser(this.options);

/* eslint-disable no-underscore-dangle */
'use strict';
import { createHash } from 'node:crypto';
import { EventEmitter } from 'node:events';
const crypto = require('crypto');
const { EventEmitter } = require('events');
class VolatileFile extends EventEmitter {

@@ -19,3 +17,3 @@ constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) {

if (typeof this.hashAlgorithm === 'string') {
this.hash = crypto.createHash(this.hashAlgorithm);
this.hash = createHash(this.hashAlgorithm);
} else {

@@ -83,2 +81,2 @@ this.hash = null;

module.exports = VolatileFile;
export default VolatileFile;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc