Socket
Socket
Sign inDemoInstall

formidable

Package Overview
Dependencies
4
Maintainers
4
Versions
78
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.0-canary.20200212.1 to 2.0.0-canary.20200226.1

src/parsers/index.js

2

CHANGELOG.md

@@ -23,2 +23,4 @@ ### Unreleased (`canary` & `dev` dist-tags)

* fix: update docs and examples [#544](https://github.com/node-formidable/node-formidable/pull/544) ([#248](https://github.com/node-formidable/node-formidable/issues/248), [#335](https://github.com/node-formidable/node-formidable/issues/335), [#371](https://github.com/node-formidable/node-formidable/issues/371), [#372](https://github.com/node-formidable/node-formidable/issues/372), [#387](https://github.com/node-formidable/node-formidable/issues/387), partly [#471](https://github.com/node-formidable/node-formidable/issues/471), [#535](https://github.com/node-formidable/node-formidable/issues/535))
* feat: introduce Plugins API, fix silent failing tests ([#545](https://github.com/node-formidable/node-formidable/pull/545), [#391](https://github.com/node-formidable/node-formidable/pull/391), [#407](https://github.com/node-formidable/node-formidable/pull/407), [#386](https://github.com/node-formidable/node-formidable/pull/386), [#374](https://github.com/node-formidable/node-formidable/pull/374), [#521](https://github.com/node-formidable/node-formidable/pull/521), [#267](https://github.com/node-formidable/node-formidable/pull/267))
* respect form hash option on incoming octect/stream requests ([#407](https://github.com/node-formidable/node-formidable/pull/407))
* fix: exposing file writable stream errors ([#520](https://github.com/node-formidable/node-formidable/pull/520), [#316](https://github.com/node-formidable/node-formidable/pull/316), [#469](https://github.com/node-formidable/node-formidable/pull/469), [#470](https://github.com/node-formidable/node-formidable/pull/470))

@@ -25,0 +27,0 @@

154

package.json
{
"name": "formidable",
"version": "2.0.0-canary.20200212.1",
"license": "MIT",
"description": "A node.js module for parsing form data, especially file uploads.",
"homepage": "https://github.com/node-formidable/node-formidable",
"funding": "https://ko-fi.com/tunnckoCore/commissions",
"repository": "node-formidable/node-formidable",
"main": "./src/index.js",
"files": [
"src"
],
"publishConfig": {
"access": "public",
"tag": "canary"
},
"scripts": {
"bench": "node benchmark",
"fmt": "yarn run fmt:prepare '**/*'",
"fmt:prepare": "prettier --write",
"lint": "yarn run lint:prepare .",
"lint:prepare": "eslint --cache --fix --quiet --format codeframe",
"reinstall": "rm -rf node_modules yarn.lock && yarn",
"pretest": "rm -rf test/tmp && mkdir test/tmp",
"test": "node test/run.js",
"pretest:ci": "yarn pretest",
"test:ci": "nyc node test/run.js"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@tunnckocore/prettier-config": "^1.3.3",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.2",
"husky": "^4.2.2",
"jest": "^25.1.0",
"lint-staged": "^10.0.7",
"nyc": "^15.0.0",
"prettier": "^1.19.1",
"prettier-plugin-pkgjson": "^0.2.3",
"request": "^2.88.2",
"urun": "^0.0.8",
"utest": "^0.0.8"
},
"husky": {
"hooks": {
"pre-commit": "git status --porcelain && yarn lint-staged",
"commit-msg": "yarn commitlint -E HUSKY_GIT_PARAMS"
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"lint-staged": {
"!*.{js,jsx,ts,tsx}": [
"yarn run fmt:prepare"
],
"*.{js,jsx,ts,tsx}": [
"yarn run lint"
]
},
"renovate": {
"extends": [
"@tunnckocore"
]
}
"name": "formidable",
"version": "2.0.0-canary.20200226.1",
"license": "MIT",
"description": "A node.js module for parsing form data, especially file uploads.",
"homepage": "https://github.com/node-formidable/formidable",
"funding": "https://ko-fi.com/tunnckoCore/commissions",
"repository": "node-formidable/formidable",
"main": "./src/index.js",
"files": [
"src",
"test"
],
"publishConfig": {
"access": "public",
"tag": "canary"
},
"scripts": {
"bench": "node benchmark",
"fmt": "yarn run fmt:prepare '**/*'",
"fmt:prepare": "prettier --write",
"lint": "yarn run lint:prepare .",
"lint:prepare": "eslint --cache --fix --quiet --format codeframe",
"pretest": "yarn del ./test/tmp",
"postpretest": "yarn make-dir ./test/tmp",
"reinstall": "yarn del ./node_modules ./yarn.lock",
"postreinstall": "yarn setup",
"setup": "yarn",
"test": "yarn node test/run.js",
"pretest:ci": "yarn pretest",
"test:ci": "nyc node test/run.js",
"test:jest": "jest --coverage"
},
"dependencies": {
"dezalgo": "^1.0.3",
"once": "^1.4.0"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@tunnckocore/prettier-config": "^1.3.3",
"del-cli": "^3.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.2",
"express": "^4.17.1",
"husky": "^4.2.2",
"jest": "^25.1.0",
"koa": "^2.11.0",
"lint-staged": "^10.0.7",
"make-dir-cli": "^2.0.0",
"nyc": "^15.0.0",
"prettier": "^1.19.1",
"prettier-plugin-pkgjson": "^0.2.3",
"request": "^2.88.2",
"supertest": "^4.0.2",
"urun": "^0.0.8",
"utest": "^0.0.8"
},
"husky": {
"hooks": {
"pre-commit": "git status --porcelain && yarn lint-staged",
"commit-msg": "yarn commitlint -E HUSKY_GIT_PARAMS"
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"lint-staged": {
"!*.{js,jsx,ts,tsx}": [
"yarn run fmt:prepare"
],
"*.{js,jsx,ts,tsx}": [
"yarn run lint"
]
},
"renovate": {
"extends": [
"@tunnckocore"
]
}
}
<p align="center">
<img alt="node formidable logo" src="./logo.png" />
<img alt="npm formidable package logo" src="https://raw.githubusercontent.com/node-formidable/formidable/master/logo.png" />
</p>
# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url]
# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url] [![Twitter][twitter-img]][twitter-url]

@@ -10,12 +10,35 @@ > A Node.js module for parsing form data, especially file uploads.

[![Code style][codestyle-img]][codestyle-url]
[![build status][build-img]][build-url]
[![codecoverage][codecov-img]][codecov-url]
[![linux build status][linux-build-img]][build-url]
[![windows build status][windows-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
Guide][contributing-url] and [Code of Conduct][code_of_conduct-url]
documents.<br /> For bugs reports and feature requests, [please create an
issue][open-issue-url] or ping [@tunnckoCore](https://twitter.com/tunnckoCore)
at Twitter.
[![Conventional Commits][ccommits-img]][ccommits-url]
[![Minimum Required Nodejs][nodejs-img]][npmv-url]
[![Tidelift Subcsription][tidelift-img]][tidelift-url]
[![Buy me a Kofi][kofi-img]][kofi-url]
[![Renovate App Status][renovateapp-img]][renovateapp-url]
[![Make A Pull Request][prs-welcome-img]][prs-welcome-url]
[![Twitter][twitter-img]][twitter-url]
This project is [semantically versioned](https://semver.org) and available as
part of the [Tidelift Subscription][tidelift-url] for professional grade
assurances, enhanced support and security.
[Learn more.](https://tidelift.com/subscription/pkg/npm-formidable?utm_source=npm-formidable&utm_medium=referral&utm_campaign=enterprise)
_The maintainers of `formidable` and thousands of other packages are working
with Tidelift to deliver commercial support and maintenance for the Open Source
dependencies you use to build your applications. Save time, reduce risk, and
improve code health, while paying the maintainers of the exact dependencies you
use._
[![][npm-weekly-img]][npmv-url] [![][npm-monthly-img]][npmv-url]
[![][npm-yearly-img]][npmv-url] [![][npm-alltime-img]][npmv-url]
## Status: Maintained [![npm version][npmv-canary-img]][npmv-url] [![npm version][npmv-dev-img]][npmv-url]
## Status: Maintained [![npm version][npmv-canary-img]][npmv-url]

@@ -31,21 +54,18 @@ This module was initially developed by

are always welcome! :heart: Jump on
[issue #412](https://github.com/felixge/node-formidable/issues/412) if you are
interested.
[issue #412](https://github.com/felixge/node-formidable/issues/412) which is
closed, but if you are interested we can discuss it and add you after strict
rules, like enabling Two-Factor Auth in your npm and GitHub accounts.
_**Note:** Master is a "canary" branch - try it with `npm i formidable@canary`.
Do not expect (for now) things from it to be inside the`latest`"dist-tag" in the
Npm. The`formidable@latest`is the`v1.2.1` version and probably it will be the
last`v1` release!_
_**Note:** The github `master` branch is a "canary" branch - try it with
`npm i formidable@canary`. Do not expect (for now) things from it to be inside
the`latest` "dist-tag" in the Npm. The`formidable@latest`is the`v1.2.1` version
and probably it will be the last`v1` release!_
_**Note: v2 is coming soon!**_
You can try the
[Plugins API](https://github.com/felixge/node-formidable/tree/plugins-api)
([#545](https://github.com/felixge/node-formidable/pull/545)), which is
available through `formidable@dev`.
## Highlights
- Fast (~900-2500 mb/sec), streaming multipart parser
- Automatically writing file uploads to disk
- [Fast (~900-2500 mb/sec)](#benchmarks) & streaming multipart parser
- Automatically writing file uploads to disk (soon optionally)
- [Plugins API](#useplugin-plugin) - allowing custom parsers and plugins
- Low memory footprint

@@ -57,2 +77,6 @@ - Graceful error handling

This project requires `Node.js >= 10.13`. Install it using
[yarn](https://yarnpkg.com) or [npm](https://npmjs.com).<br /> _We highly
recommend to use Yarn when you think to contribute to this project._
```sh

@@ -72,51 +96,160 @@ npm install formidable

This is a low-level package, and if you're using a high-level framework it may
already be included.
This is a low-level package, and if you're using a high-level framework it _may_
already be included. Check the examples below and the `examples/` folder.
However, [Express v4](http://expressjs.com) does not include any multipart
handling, nor does [body-parser](https://github.com/expressjs/body-parser).
## Examples
For `koa` there is [koa-better-body](https://ghub.now.sh/koa-better-body) which
can handle ANY type of body / form-data - JSON, urlencoded, multpart and so on.
A new major release is coming there too.
For more examples look at the `examples/` directory.
## Example
### with Node.js http module
Parse an incoming file upload.
Parse an incoming file upload, with the
[Node.js's built-in `http` module](https://nodejs.org/api/http.html).
```js
const http = require('http');
const util = require('util');
const formidable = require('formidable');
http
.createServer((req, res) => {
if (req.url === '/upload' && req.method.toLowerCase() === 'post') {
// parse a file upload
const form = formidable();
const server = http.createServer((req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// parse a file upload
const form = formidable({ multiples: true });
form.parse(req, (err, fields, files) => {
res.writeHead(200, { 'content-type': 'text/plain' });
res.write('received upload:\n\n');
res.end(util.inspect({ fields: fields, files: files }));
});
form.parse(req, (err, fields, files) => {
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.end(`
<h2>With Node.js <code>"http"</code> module</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="multipleFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
server.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...');
});
```
### with Express.js
There are multiple variants to do this, but Formidable just need Node.js Request
stream, so something like the following example should work just fine, without
any third-party [Express.js](https://ghub.now.sh/express) middleware.
Or try the
[examples/with-express.js](https://github.com/node-formidable/node-formidable/blob/master/examples/with-express.js)
```js
const express = require('express');
const formidable = require('formidable');
const app = express();
app.get('/', (req, res) => {
res.send(`
<h2>With <code>"express"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="someExpressFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
app.post('/api/upload', (req, res, next) => {
const form = formidable({ multiples: true });
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.json({ fields, files });
});
});
// show a file upload form
res.writeHead(200, { 'content-type': 'text/html' });
res.end(`
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="text" name="title" /><br/>
<input type="file" name="upload" multiple="multiple" /><br/>
<input type="submit" value="Upload" />
</form>
`);
})
.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...');
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
```
### with Koa and Formidable
Of course, with [Koa v1, v2 or future v3](https://ghub.now.sh/koa) the things
are very similar. You can use `formidable` manually as shown below or through
the [koa-better-body](https://ghub.now.sh/koa-better-body) package which is
using `formidable` under the hood and support more features and different
request bodies, check its documentation for more info.
_Note: this example is assuming Koa v2. Be aware that you should pass `ctx.req`
which is Node.js's Request, and **NOT** the `ctx.request` which is Koa's Request
object - there is a difference._
```js
const Koa = require('koa');
const formidable = require('formidable');
const app = new Koa();
app.on('error', (err) => {
console.error('server error', err);
});
app.use(async (ctx, next) => {
if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') {
const form = formidable({ multiples: true });
// not very elegant, but that's for now if you don't want touse `koa-better-body`
// or other middlewares.
await new Promise((resolve, reject) => {
form.parse(ctx.req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
ctx.set('Content-Type', 'application/json');
ctx.status = 200;
ctx.state = { fields, files };
ctx.body = JSON.stringify(ctx.state, null, 2);
resolve();
});
});
await next();
return;
}
// show a file upload form
ctx.set('Content-Type', 'text/html');
ctx.status = 200;
ctx.body = `
<h2>With <code>"koa"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="koaFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`;
});
app.use((ctx) => {
console.log('The next middleware is called');
console.log('Results:', ctx.state);
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
```
## Benchmarks

@@ -162,4 +295,4 @@

_Please pass [`options`](#options) to the function/constructor, not by passing
assigning them to the instance `form`_
_Please pass [`options`](#options) to the function/constructor, not by assigning
them to the instance `form`_

@@ -211,3 +344,4 @@ ```js

_**Note:** If this value is exceeded, an `'error'` event is emitted._
_**Note:** If this size of combined fields, or size of some file is exceeded, an
`'error'` event is fired._

@@ -299,2 +433,64 @@ ```js

### .use(plugin: Plugin)
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.
**The plugins added by this method are always enabled.**
_See [src/plugins/](./src/plugins/) for more detailed look on default plugins._
The `plugin` param has such signature:
```typescript
function(formidable: Formidable, options: Options): void;
```
The architecture is simple. The `plugin` is a function that is passed with the
Formidable instance (the `form` across the README examples) and the options.
**Note:** the plugin function's `this` context is also the same instance.
```js
const formidable = require('formidable');
const form = formidable({ keepExtensions: true });
form.use((self, options) => {
// self === this === form
console.log('woohoo, custom plugin');
// do your stuff; check `src/plugins` for inspiration
});
form.parse(req, (error, fields, files) => {
console.log('done!');
});
```
**Important to note**, is that inside plugin `this.options`, `self.options` and
`options` MAY or MAY NOT be the same. General best practice is to always use the
`this`, so you can later test your plugin independently and more easily.
If you want to disable some parsing capabilities of Formidable, you can disable
the plugin which corresponds to the parser. For example, if you want to disable
multipart parsing (so the [src/parsers/Multipart.js](./src/parsers/Multipart.js)
which is used in [src/plugins/multipart.js](./src/plugins/multipart.js)), then
you can remove it from the `options.enabledPlugins`, like so
```js
const { Formidable } = require('formidable');
const form = new Formidable({
hash: 'sha1',
enabledPlugins: ['octetstream', 'querystring', 'json'],
});
```
**Be aware** that the order _MAY_ be important too. The names corresponds 1:1 to
files in [src/plugins/](./src/plugins) folder.
Pull requests for new built-in plugins MAY be accepted - for example, more
advanced querystring parser. Add your plugin as a new file in `src/plugins/`
folder (lowercased) and follow how the other plugins are made.
### form.onPart

@@ -477,2 +673,3 @@

<td align="center"><a href="https://github.com/dmolim"><img src="https://avatars2.githubusercontent.com/u/7090374?v=4" width="100px;" alt=""/><br /><sub><b>Dmitry Ivonin</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=dmolim" title="Documentation">📖</a></td>
<td align="center"><a href="https://audiobox.fm"><img src="https://avatars1.githubusercontent.com/u/12844?v=4" width="100px;" alt=""/><br /><sub><b>Claudio Poli</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=masterkain" title="Code">💻</a></td>
</tr>

@@ -497,4 +694,2 @@ </table>

[codecov-img]: https://badgen.net/codecov/c/github/node-formidable/node-formidable/master?icon=codecov
[build-img]: https://badgen.net/github/checks/node-formidable/node-formidable?label=build&icon=github
[build-url]: https://github.com/node-formidable/node-formidable/actions?query=workflow%3Anodejs
[npmv-canary-img]: https://badgen.net/npm/v/formidable/canary?icon=npm

@@ -520,3 +715,24 @@ [npmv-dev-img]: https://badgen.net/npm/v/formidable/dev?icon=npm

[npm-yearly-img]: https://badgen.net/npm/dy/formidable?icon=npm&cache=300
[npm-alltime-img]: https://badgen.net/npm/dt/formidable?icon=npm&cache=300
[npm-alltime-img]: https://badgen.net/npm/dt/formidable?icon=npm&cache=300&label=total%20downloads
[nodejs-img]: https://badgen.net/badge/node/>=%2010.13/green?cache=300
[ccommits-url]: https://conventionalcommits.org/
[ccommits-img]: https://badgen.net/badge/conventional%20commits/v1.0.0/green?cache=300
[contributing-url]: https://github.com/node-formidable/node-formidable/blob/master/CONTRIBUTING.md
[code_of_conduct-url]: https://github.com/node-formidable/node-formidable/blob/master/CODE_OF_CONDUCT.md
[open-issue-url]: https://github.com/node-formidable/node-formidable/issues/new
[tidelift-url]: https://tidelift.com/subscription/pkg/npm-formidable?utm_source=npm-formidable&utm_medium=referral&utm_campaign=enterprise
[tidelift-img]: https://badgen.net/badge/tidelift/subscription/4B5168?labelColor=F6914D
[kofi-url]: https://ko-fi.com/tunnckoCore/commissions
[kofi-img]: https://badgen.net/badge/ko-fi/support/29abe0c2?cache=300&icon=https://rawcdn.githack.com/tunnckoCore/badgen-icons/f8264c6414e0bec449dd86f2241d50a9b89a1203/icons/kofi.svg
[linux-build-img]: https://badgen-net.charlike.now.sh/github/checks/node-formidable/node-formidable?label=linux%20build&icon=github
[macos-build-img]: https://badgen-net.charlike.now.sh/github/checks/node-formidable/node-formidable?label=macos%20build&icon=github
[windows-build-img]: https://badgen-net.charlike.now.sh/github/checks/node-formidable/node-formidable?label=windows%20build&icon=github
[build-url]: https://github.com/node-formidable/node-formidable/actions?query=workflow%3Anodejs
<!-- prettier-ignore-end -->

@@ -10,3 +10,4 @@ /* eslint-disable class-methods-use-this */

const crypto = require('crypto');
const { Stream } = require('stream');
const once = require('once');
const dezalgo = require('dezalgo');
const { EventEmitter } = require('events');

@@ -23,12 +24,8 @@ const { StringDecoder } = require('string_decoder');

multiples: false,
enabledPlugins: ['octetstream', 'querystring', 'multipart', 'json'],
};
const File = require('./File');
/** Parsers */
const JSONParser = require('./parsers/JSON');
const DummyParser = require('./parsers/Dummy');
const OctetParser = require('./parsers/OctetStream');
const MultipartParser = require('./parsers/Multipart');
const QuerystringParser = require('./parsers/Querystring');

@@ -45,3 +42,3 @@ function hasOwnProp(obj, key) {

Object.assign(this, DEFAULT_OPTIONS, options);
this.options = { ...DEFAULT_OPTIONS, ...options };
this.uploadDir = this.uploadDir || os.tmpdir();

@@ -59,5 +56,30 @@

this._fileSize = 0;
this._plugins = [];
this.openedFiles = [];
const enabledPlugins = []
.concat(this.options.enabledPlugins)
.filter(Boolean);
if (enabledPlugins.length === 0) {
throw new Error(
'expect at least 1 enabled builtin plugin, see options.enabledPlugins',
);
}
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`)));
});
}
use(plugin) {
if (typeof plugin !== 'function') {
throw new Error('.use: expect `plugin` to be a function');
}
this._plugins.push(plugin.bind(this));
return this;
}
parse(req, cb) {

@@ -95,2 +117,3 @@ this.pause = () => {

if (cb) {
const callback = once(dezalgo(cb));
const fields = {};

@@ -101,3 +124,3 @@ const files = {};

// TODO: too much nesting
if (this.multiples && name.slice(-2) === '[]') {
if (this.options.multiples && name.slice(-2) === '[]') {
const realName = name.slice(0, name.length - 2);

@@ -115,10 +138,6 @@ if (hasOwnProp(fields, realName)) {

}
// if (name === 'simple') {
// console.log('fields name!!', name);
// console.log('fields value!!', value);
// }
});
this.on('file', (name, file) => {
// TODO: too much nesting
if (this.multiples) {
if (this.options.multiples) {
if (hasOwnProp(files, name)) {

@@ -135,13 +154,8 @@ if (!Array.isArray(files[name])) {

}
// console.log('files!!', files);
// if (name === 'simple') {
// console.log('files name!!', name);
// console.log('files value!!', file);
// }
});
this.on('error', (err) => {
cb(err, fields, files);
callback(err, fields, files);
});
this.on('end', () => {
cb(null, fields, files);
callback(null, fields, files);
});

@@ -173,4 +187,6 @@ }

}
this._parser.end();
if (this._parser) {
this._parser.end();
}
this._maybeEnd();
});

@@ -185,2 +201,8 @@

this._parseContentType();
if (!this._parser) {
this._error(new Error('not parser found'));
return;
}
this._parser.once('error', (error) => {

@@ -220,6 +242,6 @@ this._error(error);

// this method can be overwritten by the user
this.handlePart(part);
this._handlePart(part);
}
handlePart(part) {
_handlePart(part) {
if (part.filename && typeof part.filename !== 'string') {

@@ -242,10 +264,12 @@ this._error(new Error(`the part.filename should be string when exists`));

let value = '';
const decoder = new StringDecoder(part.transferEncoding || this.encoding);
const decoder = new StringDecoder(
part.transferEncoding || this.options.encoding,
);
part.on('data', (buffer) => {
this._fieldsSize += buffer.length;
if (this._fieldsSize > this.maxFieldsSize) {
if (this._fieldsSize > this.options.maxFieldsSize) {
this._error(
new Error(
`maxFieldsSize exceeded, received ${this._fieldsSize} bytes of field data`,
`options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`,
),

@@ -270,3 +294,3 @@ );

type: part.mime,
hash: this.hash,
hash: this.options.hash,
});

@@ -283,6 +307,6 @@ file.on('error', (err) => {

this._fileSize += buffer.length;
if (this._fileSize > this.maxFileSize) {
if (this._fileSize > this.options.maxFileSize) {
this._error(
new Error(
`maxFileSize exceeded, received ${this._fileSize} bytes of file data`,
`options.maxFileSize (${this.options.maxFileSize} bytes) exceeded, received ${this._fileSize} bytes of file data`,
),

@@ -313,3 +337,3 @@ );

if (this.bytesExpected === 0) {
this._parser = new DummyParser(this);
this._parser = new DummyParser(this, this.options);
return;

@@ -323,39 +347,47 @@ }

if (this.headers['content-type'].match(/octet-stream/i)) {
this._initOctetStream();
return;
}
const results = [];
const _dummyParser = new DummyParser(this, this.options);
if (this.headers['content-type'].match(/urlencoded/i)) {
this._initUrlencoded();
return;
}
// eslint-disable-next-line no-plusplus
for (let idx = 0; idx < this._plugins.length; idx++) {
const plugin = this._plugins[idx];
if (this.headers['content-type'].match(/multipart/i)) {
const m = this.headers['content-type'].match(
/boundary=(?:"([^"]+)"|([^;]+))/i,
);
if (m) {
this._initMultipart(m[1] || m[2]);
} else {
this._error(
new Error('bad content-type header, no multipart boundary'),
let pluginReturn = null;
try {
pluginReturn = 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 Error(
`plugin on index ${idx} failed with: ${err.message}`,
);
error.idx = idx;
throw error;
}
return;
}
if (this.headers['content-type'].match(/json/i)) {
this._initJSONencoded();
return;
Object.assign(this, pluginReturn);
// todo: use Set/Map and pass plugin name instead of the `idx` index
this.emit('plugin', idx, pluginReturn);
results.push(pluginReturn);
}
this._error(
new Error(
`bad content-type header, unknown content-type: ${this.headers['content-type']}`,
),
);
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) {
_error(err, eventName = 'error') {
// if (!err && this.error) {
// this.emit('error', this.error);
// return;
// }
if (this.error || this.ended) {

@@ -366,3 +398,3 @@ return;

this.error = err;
this.emit('error', err);
this.emit(eventName, err);

@@ -391,133 +423,9 @@ if (Array.isArray(this.openedFiles)) {

_newParser() {
return new MultipartParser();
return new MultipartParser(this.options);
}
_initMultipart(boundary) {
this.type = 'multipart';
const parser = new MultipartParser();
let headerField;
let headerValue;
let part;
parser.initWithBoundary(boundary);
// eslint-disable-next-line max-statements, consistent-return
parser.on('data', ({ name, buffer, start, end }) => {
if (name === 'partBegin') {
part = new Stream();
part.readable = true;
part.headers = {};
part.name = null;
part.filename = null;
part.mime = null;
part.transferEncoding = 'binary';
part.transferBuffer = '';
headerField = '';
headerValue = '';
} else if (name === 'headerField') {
headerField += buffer.toString(this.encoding, start, end);
} else if (name === 'headerValue') {
headerValue += buffer.toString(this.encoding, start, end);
} 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,
);
if (headerField === 'content-disposition') {
if (m) {
part.name = m[2] || m[3] || '';
}
part.filename = this._fileName(headerValue);
} else if (headerField === 'content-type') {
part.mime = headerValue;
} else if (headerField === 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase();
}
headerField = '';
headerValue = '';
} else if (name === 'headersEnd') {
switch (part.transferEncoding) {
case 'binary':
case '7bit':
case '8bit': {
const dataPropagation = (ctx) => {
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);
}
};
parser.on('data', dataPropagation);
parser.on('data', dataStopPropagation);
break;
}
case 'base64': {
const dataPropagation = (ctx) => {
if (ctx.name === 'partData') {
part.transferBuffer += ctx.buffer
.slice(ctx.start, ctx.end)
.toString('ascii');
/*
four bytes (chars) in base64 converts to three bytes in binary
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 vy 3.
*/
const offset = parseInt(part.transferBuffer.length / 4, 10) * 4;
part.emit(
'data',
Buffer.from(
part.transferBuffer.substring(0, offset),
'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);
}
};
parser.on('data', dataPropagation);
parser.on('data', dataStopPropagation);
break;
}
default:
return this._error(new Error('unknown transfer-encoding'));
}
this.onPart(part);
} else if (name === 'end') {
this.ended = true;
this._maybeEnd();
}
});
this._parser = parser;
}
_fileName(headerValue) {
_getFileName(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
/\bfilename=("(.*?)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))($|;\s)/i,
/\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i,
);

@@ -535,94 +443,2 @@ if (!m) return null;

_initUrlencoded() {
this.type = 'urlencoded';
const parser = new QuerystringParser(this.maxFields);
parser.on('data', ({ key, value }) => {
this.emit('field', key, value);
});
parser.once('end', () => {
this.ended = true;
this._maybeEnd();
});
this._parser = parser;
}
_initOctetStream() {
this.type = 'octet-stream';
const filename = this.headers['x-file-name'];
const mime = this.headers['content-type'];
const file = new File({
path: this._uploadPath(filename),
name: filename,
type: mime,
});
file.on('error', (err) => {
this._error(err);
});
this.emit('fileBegin', filename, file);
file.open();
this.openedFiles.push(file);
this._flushing += 1;
this._parser = new OctetParser();
// 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.pause();
outstandingWrites += 1;
file.write(buffer, () => {
outstandingWrites -= 1;
this.resume();
if (this.ended) {
this._parser.emit('doneWritingFile');
}
});
});
this._parser.on('end', () => {
this._flushing -= 1;
this.ended = true;
const done = () => {
file.end(() => {
this.emit('file', 'file', file);
this._maybeEnd();
});
};
if (outstandingWrites === 0) {
done();
} else {
this._parser.once('doneWritingFile', done);
}
});
}
_initJSONencoded() {
this.type = 'json';
const parser = new JSONParser();
parser.on('data', ({ key, value }) => {
this.emit('field', key, value);
});
parser.once('end', () => {
this.ended = true;
this._maybeEnd();
});
this._parser = parser;
}
_uploadPath(filename) {

@@ -632,3 +448,3 @@ const buf = crypto.randomBytes(16);

if (this.keepExtensions) {
if (this.options.keepExtensions) {
let ext = path.extname(filename);

@@ -655,2 +471,3 @@ ext = ext.replace(/(\.[a-z0-9]+).*/i, '$1');

IncomingForm.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
module.exports = IncomingForm;

@@ -6,7 +6,4 @@ 'use strict';

const JSONParser = require('./parsers/JSON');
const DummyParser = require('./parsers/Dummy');
const MultipartParser = require('./parsers/Multipart');
const OctetStreamParser = require('./parsers/OctetStream');
const QuerystringParser = require('./parsers/Querystring');
const plugins = require('./plugins/index');
const parsers = require('./parsers/index');

@@ -26,11 +23,13 @@ // make it available without requiring the `new` keyword

// parsers
JSONParser,
DummyParser,
MultipartParser,
OctetStreamParser,
QuerystringParser,
...parsers,
parsers,
// typo aliases
OctetstreamParser: OctetStreamParser,
QueryStringParser: QuerystringParser,
// misc
defaultOptions: Formidable.DEFAULT_OPTIONS,
enabledPlugins: Formidable.DEFAULT_OPTIONS.enabledPlugins,
// plugins
plugins: {
...plugins,
},
});

@@ -8,4 +8,5 @@ /* eslint-disable no-underscore-dangle */

class DummyParser extends Transform {
constructor(incomingForm) {
constructor(incomingForm, options = {}) {
super();
this.globalOptions = { ...options };
this.incomingForm = incomingForm;

@@ -12,0 +13,0 @@ }

@@ -8,5 +8,6 @@ /* eslint-disable no-underscore-dangle */

class JSONParser extends Transform {
constructor() {
constructor(options = {}) {
super({ readableObjectMode: true });
this.chunks = [];
this.globalOptions = { ...options };
}

@@ -13,0 +14,0 @@

@@ -49,3 +49,3 @@ /* eslint-disable no-fallthrough */

class MultipartParser extends Transform {
constructor() {
constructor(options = {}) {
super({ readableObjectMode: true });

@@ -58,2 +58,3 @@ this.boundary = null;

this.globalOptions = { ...options };
this.index = null;

@@ -60,0 +61,0 @@ this.flags = 0;

@@ -5,4 +5,9 @@ 'use strict';

class OctetStreamParser extends PassThrough {}
class OctetStreamParser extends PassThrough {
constructor(options = {}) {
super();
this.globalOptions = { ...options };
}
}
module.exports = OctetStreamParser;

@@ -11,5 +11,6 @@ /* eslint-disable no-underscore-dangle */

class QuerystringParser extends Transform {
constructor(maxKeys) {
constructor(options = {}) {
super({ readableObjectMode: true });
this.maxKeys = maxKeys;
this.globalOptions = { ...options };
this.maxKeys = this.globalOptions.maxFields;
this.buffer = '';

@@ -16,0 +17,0 @@ this.bufferLength = 0;

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc