Change endpoint from persons to people

This commit is contained in:
xfarrow
2025-03-23 21:00:08 +01:00
parent 4ae263662c
commit d005193f63
7158 changed files with 700476 additions and 735 deletions

21
backend/apis/nodejs/node_modules/formidable/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2011-present Felix Geisendörfer, and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

884
backend/apis/nodejs/node_modules/formidable/README.md generated vendored Normal file
View File

@ -0,0 +1,884 @@
<p align="center">
<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] [![Twitter][twitter-img]][twitter-url]
> A Node.js module for parsing form data, especially file uploads.
[![Code style][codestyle-img]][codestyle-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 / @3a1FcBx0](https://twitter.com/3a1FcBx0)
at Twitter.
[![Conventional Commits][ccommits-img]][ccommits-url]
[![Minimum Required Nodejs][nodejs-img]][npmv-url]
[![Tidelift Subscription][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]
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]
## Project Status: Maintained
_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
[Transloadit](http://transloadit.com/), a service focused on uploading and
encoding images and videos. It has been battle-tested against hundreds of GBs of
file uploads from a large variety of clients and is considered production-ready
and is used in production for years.
Currently, we are few maintainers trying to deal with it. :) More contributors
are always welcome! :heart: Jump on
[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.
## Highlights
- [Fast (~900-2500 mb/sec)](#benchmarks) & streaming multipart parser
- Automatically writing file uploads to disk (optional, see
[`options.fileWriteStreamHandler`](#options))
- [Plugins API](#useplugin-plugin) - allowing custom parsers and plugins
- Low memory footprint
- Graceful error handling
- Very high test coverage
## Install
This package is a dual ESM/commonjs package.
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._
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/](https://github.com/node-formidable/formidable/tree/master/examples) folder.
```
# v2
npm install formidable@v2
# v3
npm install formidable
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.
### with Node.js http module
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';
const server = http.createServer(async (req, res) => {
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);
} 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;
}
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/formidable/blob/master/examples/with-express.js)
```js
import express from 'express';
import formidable from '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({});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.json({ fields, files });
});
});
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
import Koa from 'Koa';
import formidable from '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({});
// not very elegant, but that's for now if you don't want to use `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
The benchmark is quite old, from the old codebase. But maybe quite true though.
Previously the numbers was around ~500 mb/sec. Currently with moving to the new
Node.js Streams API it's faster. You can clearly see the differences between the
Node versions.
_Note: a lot better benchmarking could and should be done in future._
Benchmarked on 8GB RAM, Xeon X3440 (2.53 GHz, 4 cores, 8 threads)
```
~/github/node-formidable master
nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js
⬢ Node 8
1261.08 mb/sec
⬢ Node 10
1113.04 mb/sec
⬢ Node 12
2107.00 mb/sec
⬢ Node 13
2566.42 mb/sec
```
![benchmark January 29th, 2020](./benchmark/2020-01-29_xeon-x3440.png)
## API
### Formidable / IncomingForm
All shown are equivalent.
_Please pass [`options`](#options) to the function/constructor, not by assigning
them to the instance `form`_
```js
import formidable from 'formidable';
const form = formidable(options);
```
### Options
See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js)
(the `DEFAULT_OPTIONS` constant).
- `options.encoding` **{string}** - default `'utf-8'`; sets encoding for
incoming form fields,
- `options.uploadDir` **{string}** - default `os.tmpdir()`; the directory for
placing file uploads in. You can move them later by using `fs.rename()`.
- `options.keepExtensions` **{boolean}** - default `false`; to include the
extensions of the original files or not
- `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 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);
limit the amount of memory all fields together (except files) can allocate in
bytes.
- `options.hashAlgorithm` **{string | false}** - default `false`; include checksums calculated
for incoming files, set this to some hash algorithm, see
[crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options)
for available algorithms
- `options.fileWriteStreamHandler` **{function}** - default `null`, which by
default writes to host machine file system every file parsed; The function
should return an instance of a
[Writable stream](https://nodejs.org/api/stream.html#stream_class_stream_writable)
that will receive the uploaded file data. With this option, you can have any
custom behavior regarding where the uploaded file data will be streamed for.
If you are looking to write the file uploaded in other types of cloud storages
(AWS S3, Azure blob storage, Google cloud storage) or private file storage,
this is the option you're looking for. When this option is defined the default
behavior of writing the file in the host machine file system is lost.
- `options.filename` **{function}** - default `undefined` Use it to control
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. 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
`'error'` event is fired._
```js
// The amount of bytes received for this form so far.
form.bytesReceived;
```
```js
// The expected number of bytes in this form.
form.bytesExpected;
```
#### `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}) {
// keep only images
return mimetype && mimetype.includes("image");
}
};
```
**Note:** use an outside variable to cancel all uploads upon the first error
**Note:** use form.emit('error') to make form.parse error
```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 form = formidable({ uploadDir: __dirname });
form.parse(req, (err, fields, files) => {
console.log('fields:', fields);
console.log('files:', files);
});
// with Promise
const [fields, files] = await form.parse(req);
```
You may overwrite this method if you are interested in directly accessing the
multipart stream. Doing so will disable any `'field'` / `'file'` events
processing which would occur otherwise, making you fully responsible for
handling the processing.
About `uploadDir`, given the following directory structure
```
project-name
├── src
│ └── server.js
└── uploads
└── image.jpg
```
`__dirname` would be the same directory as the source file itself (src)
```js
`${__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:
```js
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
file and etc.
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.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('field', (fieldName, fieldValue) => {
form.emit('data', { name: 'field', key: fieldName, value: fieldValue });
});
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);
}
});
```
### .use(plugin: Plugin)
A method that allows you to extend the Formidable library. By default we include
4 plugins, which essentially 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 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
import formidable, {octetstream, querystring, json} from "formidable";
const form = formidable({
hashAlgorithm: '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
If you want to use Formidable to only handle certain parts for you, you can do
something similar. Or see
[#387](https://github.com/node-formidable/node-formidable/issues/387) for
inspiration, you can for example validate the mime-type.
```js
const form = formidable();
form.onPart = (part) => {
part.on('data', (buffer) => {
// do whatever you want here
});
};
```
For example, force Formidable to be used only on non-file "parts" (i.e., html
fields)
```js
const form = formidable();
form.onPart = function (part) {
// let formidable handle only non-file parts
if (part.originalFilename === '' || !part.mimetype) {
// used internally, please do not override!
form._handlePart(part);
}
};
```
### File
```ts
export interface File {
// The size of the uploaded file in bytes.
// If the file is still being uploaded (see `'fileBegin'` event),
// this property says how many bytes of the file have been written to disk yet.
file.size: number;
// The path this file is being written to. You can modify this in the `'fileBegin'` event in
// case you are unhappy with the way formidable generates a temporary path for your files.
file.filepath: string;
// The name this file had according to the uploading client.
file.originalFilename: string | null;
// calculated based on options provided
file.newFilename: string | null;
// The mime type of this file, according to the uploading client.
file.mimetype: string | null;
// A Date object (or `null`) containing the time this file was last written to.
// Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
file.mtime: Date | null;
file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256'
// If `options.hashAlgorithm` calculation was set, you can read the hex digest out of this var (at the end it will be a string)
file.hash: string | object | null;
}
```
#### file.toJSON()
This method returns a JSON-representation of the file, allowing you to
`JSON.stringify()` the file which is useful for logging and responding to
requests.
### Events
#### `'progress'`
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) => {});
```
#### `'field'`
Emitted whenever a field / value pair has been received.
```js
form.on('field', (name, value) => {});
```
#### `'fileBegin'`
Emitted whenever a new file is detected in the upload stream. Use this event if
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 (<input name="thisname" type="file">) 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
});
```
#### `'file'`
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
});
```
#### `'error'`
Emitted when there is an error processing the incoming form. A request that
experiences an error is automatically paused, you will have to manually call
`request.resume()` if you want the request to continue firing `'data'` events.
May have `error.httpCode` and `error.code` attached.
```js
form.on('error', (err) => {});
```
#### `'aborted'`
Emitted when the request was aborted by the user. Right now this can be due to a
'timeout' or 'close' event on the socket. After this event is emitted, an
`error` event will follow. In the future there will be a separate 'timeout'
event (needs a change in the node core).
```js
form.on('aborted', () => {});
```
#### `'end'`
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', () => {});
```
### 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
[./CHANGELOG.md](./CHANGELOG.md)
## Ports & Credits
- [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++
parser based on formidable
- [Ryan Dahl](http://twitter.com/ryah) for his work on
[http-parser](http://github.com/ry/http-parser) which heavily inspired the
initial `multipart_parser.js`.
## Contributing
If the documentation is unclear or has a typo, please click on the page's `Edit`
button (pencil icon) and suggest a correction. If you would like to help us fix
a bug or add a new feature, please check our [Contributing
Guide][contributing-url]. Pull requests are welcome!
Thanks goes to these wonderful people
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://twitter.com/felixge"><img src="https://avatars3.githubusercontent.com/u/15000?s=460&v=4" width="100px;" alt=""/><br /><sub><b>Felix Geisendörfer</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=felixge" title="Code">💻</a> <a href="#design-felixge" title="Design">🎨</a> <a href="#ideas-felixge" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=felixge" title="Documentation">📖</a></td>
<td align="center"><a href="https://tunnckoCore.com"><img src="https://avatars3.githubusercontent.com/u/5038030?v=4" width="100px;" alt=""/><br /><sub><b>Charlike Mike Reagent</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AtunnckoCore" title="Bug reports">🐛</a> <a href="#infra-tunnckoCore" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#design-tunnckoCore" title="Design">🎨</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Documentation">📖</a> <a href="#example-tunnckoCore" title="Examples">💡</a> <a href="#ideas-tunnckoCore" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-tunnckoCore" title="Maintenance">🚧</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/kedarv"><img src="https://avatars1.githubusercontent.com/u/1365665?v=4" width="100px;" alt=""/><br /><sub><b>Kedar</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=kedarv" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=kedarv" title="Tests">⚠️</a> <a href="#question-kedarv" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Akedarv" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/GrosSacASac"><img src="https://avatars0.githubusercontent.com/u/5721194?v=4" width="100px;" alt=""/><br /><sub><b>Walle Cyril</b></sub></a><br /><a href="#question-GrosSacASac" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AGrosSacASac" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=GrosSacASac" title="Code">💻</a> <a href="#financial-GrosSacASac" title="Financial">💵</a> <a href="#ideas-GrosSacASac" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-GrosSacASac" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/xarguments"><img src="https://avatars2.githubusercontent.com/u/40522463?v=4" width="100px;" alt=""/><br /><sub><b>Xargs</b></sub></a><br /><a href="#question-xarguments" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Axarguments" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=xarguments" title="Code">💻</a> <a href="#maintenance-xarguments" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/Amit-A"><img src="https://avatars1.githubusercontent.com/u/7987238?v=4" width="100px;" alt=""/><br /><sub><b>Amit-A</b></sub></a><br /><a href="#question-Amit-A" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AAmit-A" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=Amit-A" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://charmander.me/"><img src="https://avatars1.githubusercontent.com/u/1889843?v=4" width="100px;" alt=""/><br /><sub><b>Charmander</b></sub></a><br /><a href="#question-charmander" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Acharmander" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=charmander" title="Code">💻</a> <a href="#ideas-charmander" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-charmander" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://twitter.com/dylan_piercey"><img src="https://avatars2.githubusercontent.com/u/4985201?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Piercey</b></sub></a><br /><a href="#ideas-DylanPiercey" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://ochrona.jawne.info.pl"><img src="https://avatars1.githubusercontent.com/u/3618479?v=4" width="100px;" alt=""/><br /><sub><b>Adam Dobrawy</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Aad-m" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=ad-m" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/amitrohatgi"><img src="https://avatars3.githubusercontent.com/u/12177021?v=4" width="100px;" alt=""/><br /><sub><b>amitrohatgi</b></sub></a><br /><a href="#ideas-amitrohatgi" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/fengxinming"><img src="https://avatars2.githubusercontent.com/u/6262382?v=4" width="100px;" alt=""/><br /><sub><b>Jesse Feng</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Afengxinming" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://qtmsheep.com"><img src="https://avatars1.githubusercontent.com/u/7271496?v=4" width="100px;" alt=""/><br /><sub><b>Nathanael Demacon</b></sub></a><br /><a href="#question-quantumsheep" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=quantumsheep" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/pulls?q=is%3Apr+reviewed-by%3Aquantumsheep" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/MunMunMiao"><img src="https://avatars1.githubusercontent.com/u/18216142?v=4" width="100px;" alt=""/><br /><sub><b>MunMunMiao</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AMunMunMiao" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/gabipetrovay"><img src="https://avatars0.githubusercontent.com/u/1170398?v=4" width="100px;" alt=""/><br /><sub><b>Gabriel Petrovay</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Agabipetrovay" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=gabipetrovay" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Elzair"><img src="https://avatars0.githubusercontent.com/u/2352818?v=4" width="100px;" alt=""/><br /><sub><b>Philip Woods</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=Elzair" title="Code">💻</a> <a href="#ideas-Elzair" title="Ideas, Planning, & Feedback">🤔</a></td>
<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>
</table>
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
From a [Felix blog post](https://felixge.de/2013/03/11/the-pull-request-hack/):
- [Sven Lito](https://github.com/svnlto) for fixing bugs and merging patches
- [egirshov](https://github.com/egirshov) for contributing many improvements to the node-formidable multipart parser
- [Andrew Kelley](https://github.com/superjoe30) for also helping with fixing bugs and making improvements
- [Mike Frey](https://github.com/mikefrey) for contributing JSON support
## License
Formidable is licensed under the [MIT License][license-url].
<!-- badges -->
<!-- prettier-ignore-start -->
[codestyle-url]: https://github.com/airbnb/javascript
[codestyle-img]: https://badgen.net/badge/code%20style/airbnb%20%2B%20prettier/ff5a5f?icon=airbnb&cache=300
[codecov-url]: https://codecov.io/gh/node-formidable/formidable
[codecov-img]: https://badgen.net/codecov/c/github/node-formidable/formidable/master?icon=codecov
[npmv-canary-img]: https://badgen.net/npm/v/formidable/canary?icon=npm
[npmv-dev-img]: https://badgen.net/npm/v/formidable/dev?icon=npm
[npmv-img]: https://badgen.net/npm/v/formidable?icon=npm
[npmv-url]: https://npmjs.com/package/formidable
[license-img]: https://badgen.net/npm/license/formidable
[license-url]: https://github.com/node-formidable/formidable/blob/master/LICENSE
[chat-img]: https://badgen.net/badge/chat/on%20gitter/46BC99?icon=gitter
[chat-url]: https://gitter.im/node-formidable/Lobby
[libera-manifesto-url]: https://liberamanifesto.com
[libera-manifesto-img]: https://badgen.net/badge/libera/manifesto/grey
[renovateapp-url]: https://renovatebot.com
[renovateapp-img]: https://badgen.net/badge/renovate/enabled/green?cache=300
[prs-welcome-img]: https://badgen.net/badge/PRs/welcome/green?cache=300
[prs-welcome-url]: http://makeapullrequest.com
[twitter-url]: https://twitter.com/3a1fcBx0
[twitter-img]: https://badgen.net/twitter/follow/3a1fcBx0?icon=twitter&color=1da1f2&cache=300
[npm-weekly-img]: https://badgen.net/npm/dw/formidable?icon=npm&cache=300
[npm-monthly-img]: https://badgen.net/npm/dm/formidable?icon=npm&cache=300
[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&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/.github/blob/master/CONTRIBUTING.md
[code_of_conduct-url]: https://github.com/node-formidable/.github/blob/master/CODE_OF_CONDUCT.md
[open-issue-url]: https://github.com/node-formidable/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/github/checks/node-formidable/formidable/master/ubuntu?cache=300&label=linux%20build&icon=github
[macos-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/macos?cache=300&label=macos%20build&icon=github
[windows-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/windows?cache=300&label=windows%20build&icon=github
[build-url]: https://github.com/node-formidable/formidable/actions?query=workflow%3Anodejs
<!-- prettier-ignore-end -->

View File

@ -0,0 +1,841 @@
<p align="center">
<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] [![Twitter][twitter-img]][twitter-url]
> A Node.js module for parsing form data, especially file uploads.
[![Code style][codestyle-img]][codestyle-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]
Se você tiver qualquer tipo de pergunta sobre _como_ fazer, por favor leia o [Contributing
Guia][contributing-url] e [Código de Conduta][code_of_conduct-url]
documentos.<br /> Para relatórios de bugs e solicitações de recursos, [crie uma
issue][open-issue-url] ou ping [@tunnckoCore / @3a1FcBx0](https://twitter.com/3a1FcBx0)
no Twitter.
[![Conventional Commits][ccommits-img]][ccommits-url]
[![Minimum Required Nodejs][nodejs-img]][npmv-url]
[![Tidelift Subscription][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]
Este projeto é [semanticamente versionado](https://semver.org) e está disponível como
parte da [Assinatura Tidelift][tidelift-url] para nível profissional
garantias, suporte aprimorado e segurança.
[Saiba mais.](https://tidelift.com/subscription/pkg/npm-formidable?utm_source=npm-formidable&utm_medium=referral&utm_campaign=enterprise)
_Os mantenedores do `formidable` e milhares de outros pacotes estão trabalhando
com Tidelift para fornecer suporte comercial e manutenção para o Open Source
dependências que você usa para construir seus aplicativos. Economize tempo, reduza riscos e
melhorar a integridade do código, enquanto paga aos mantenedores das dependências exatas que você
usar._
[![][npm-weekly-img]][npmv-url] [![][npm-monthly-img]][npmv-url]
[![][npm-yearly-img]][npmv-url] [![][npm-alltime-img]][npmv-url]
## Status do Projeto: Mantido
_Verifique [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) para obter mais informações sobre os planos v1, v2 e v3, NPM dist-tags e branches._
Este módulo foi inicialmente desenvolvido por
[**@felixge**](https://github.com/felixge) para
[Transloadit](http://transloadit.com/), um serviço focado em upload e
codificação de imagens e vídeos. Foi testado em batalha contra centenas de GBs de
uploads de arquivos de uma grande variedade de clientes e é considerado pronto para produção
e é usado na produção por anos.
Atualmente, somos poucos mantenedores tentando lidar com isso. :) Mais contribuidores
são sempre bem-vindos! ❤️ Pule
[issue #412](https://github.com/felixge/node-formidable/issues/412) que está
fechado, mas se você estiver interessado, podemos discuti-lo e adicioná-lo após regras estritas, como ativar o Two-Factor Auth em suas contas npm e GitHub.
## Destaques
- [Rápido (~ 900-2500 mb/seg)](#benchmarks) e analisador multiparte de streaming
- Gravar uploads de arquivos automaticamente no disco (opcional, consulte
[`options.fileWriteStreamHandler`](#options))
- [API de plug-ins](#useplugin-plugin) - permitindo analisadores e plug-ins personalizados
- Baixo consumo de memória
- Tratamento de erros gracioso
- Cobertura de teste muito alta
## Instalar
Este projeto requer `Node.js >= 10.13`. Instale-o usando
[yarn](https://yarnpkg.com) ou [npm](https://npmjs.com).<br /> _Nós altamente
recomendamos usar o Yarn quando pensar em contribuir para este projeto._
Este é um pacote de baixo nível e, se você estiver usando uma estrutura de alto nível, _pode_ já estar incluído. Verifique os exemplos
abaixo e a pasta [examples/](https://github.com/node-formidable/formidable/tree/master/examples).
```
# v2
npm install formidable
npm install formidable@v2
# v3
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/`.
### com módulo http Node.js
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';
const server = http.createServer((req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// analisar um upload de arquivo
const form = formidable({});
form.parse(req, (err, fields, files) => {
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.end(String(err));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
});
return;
}
// mostrar um formulário de upload de arquivo
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/ ...');
});
```
### com Express.js
Existem várias variantes para fazer isso, mas o Formidable só precisa do Node.js Request
stream, então algo como o exemplo a seguir deve funcionar bem, sem nenhum middleware [Express.js](https://ghub.now.sh/express) de terceiros.
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';
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({});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.json({ fields, files });
});
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
```
### com Koa e Formidable
Claro, com [Koa v1, v2 ou future v3](https://ghub.now.sh/koa) as coisas
sao muito parecidas. Você pode usar `formidable` manualmente como mostrado abaixo ou através
do pacote [koa-better-body](https://ghub.now.sh/koa-better-body) que é
usando `formidable` sob o capô e suporte a mais recursos e diferentes
corpos de solicitação, verifique sua documentação para mais informações.
_Nota: este exemplo está assumindo Koa v2. Esteja ciente de que você deve passar `ctx.req`
que é a solicitação do Node.js e **NÃO** o `ctx.request` que é a solicitação do Koa
objeto - há uma diferença._
```js
import Koa from 'Koa';
import formidable from '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({});
// 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) {
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;
}
// mostrar um formulário de upload de arquivo
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
O benchmark é bastante antigo, da antiga base de código. Mas talvez seja bem verdade.
Anteriormente, os números giravam em torno de ~ 500 mb/s. Atualmente com a mudança para o novo
Node.js Streams API, é mais rápido. Você pode ver claramente as diferenças entre as
versões do Node.
_Observação: um benchmarking muito melhor pode e deve ser feito no futuro._
Benchmark realizado em 8 GB de RAM, Xeon X3440 (2,53 GHz, 4 núcleos, 8 threads)
```
~/github/node-formidable master
nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js
⬢ Node 8
1261.08 mb/sec
⬢ Node 10
1113.04 mb/sec
⬢ Node 12
2107.00 mb/sec
⬢ Node 13
2566.42 mb/sec
```
![benchmark 29 de janeiro de 2020](./benchmark/2020-01-29_xeon-x3440.png)
## API
### Formidable / IncomingForm
Todos os mostrados são equivalentes.
_Por favor, passe [`options`](#options) para a função/construtor, não atribuindo
eles para a instância `form`_
```js
import formidable from 'formidable';
const form = formidable(options);
```
### Opções
Veja seus padrões em [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js)
(a constante `DEFAULT_OPTIONS`).
- `options.encoding` **{string}** - padrão `'utf-8'`; define a codificação para campos de formulário de entrada,
- `options.uploadDir` **{string}** - padrão `os.tmpdir()`; o diretório para colocar os uploads de arquivos. Você pode movê-los mais tarde usando `fs.rename()`.
- `options.keepExtensions` **{boolean}** - padrão `false`; incluir as extensões dos arquivos originais ou não
- `options.allowEmptyFiles` **{boolean}** - padrão `false`; permitir upload de arquivos vazios
- `options.minFileSize` **{number}** - padrão `1` (1byte); o tamanho mínimo do arquivo carregado.
- `options.maxFiles` **{number}** - padrão `Infinity`;
limitar a quantidade de arquivos carregados, defina Infinity para ilimitado
- `options.maxFileSize` **{number}** - padrão `200 * 1024 * 1024` (200mb);
limitar o tamanho de cada arquivo carregado.
- `options.maxTotalFileSize` **{number}** - padrão `options.maxFileSize`;
limitar o tamanho do lote de arquivos carregados.
- `options.maxFields` **{number}** - padrão `1000`; limite o número de campos, defina Infinity para ilimitado
- `options.maxFieldsSize` **{number}** - padrão `20 * 1024 * 1024` (20mb);
limitar a quantidade de memória que todos os campos juntos (exceto arquivos) podem alocar em
bytes.
- `options.hashAlgorithm` **{string | false}** - padrão `false`; incluir checksums calculados
para arquivos recebidos, defina isso para algum algoritmo de hash, consulte
[crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options)
para algoritmos disponíveis
- `options.fileWriteStreamHandler` **{function}** - padrão `null`, que por padrão grava no sistema de arquivos da máquina host cada arquivo analisado; A função
deve retornar uma instância de um
[fluxo gravável](https://nodejs.org/api/stream.html#stream_class_stream_writable)
que receberá os dados do arquivo carregado. Com esta opção, você pode ter qualquer
comportamento personalizado em relação a onde os dados do arquivo carregado serão transmitidos.
Se você deseja gravar o arquivo carregado em outros tipos de armazenamento em nuvem
(AWS S3, armazenamento de blob do Azure, armazenamento em nuvem do Google) ou armazenamento de arquivo privado,
esta é a opção que você está procurando. Quando esta opção é definida, o comportamento padrão de gravar o arquivo no sistema de arquivos da máquina host é perdido.
- `options.filename` **{function}** - padrão `undefined` Use-o para controlar newFilename. Deve retornar uma string. Será associado a options.uploadDir.
- `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
onde a parte pode ser decomposta como
```js
const { originalFilename, mimetype} = part;
```
_**Observação:** Se este tamanho de campos combinados, ou tamanho de algum arquivo for excedido, um
O evento `'error'` é disparado._
```js
// A quantidade de bytes recebidos para este formulário até agora.
form.bytesReceived;
```
```js
// O número esperado de bytes neste formulário.
form.bytesExpected;
```
#### `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}) {
// 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
fornecido, todos os campos e arquivos são coletados e passados para o retorno de chamada.
```js
const form = formidable({ uploadDir: __dirname });
form.parse(req, (err, fields, files) => {
console.log('fields:', fields);
console.log('files:', files);
});
```
Você pode substituir esse método se estiver interessado em acessar diretamente o
fluxo de várias partes. Fazer isso desativará qualquer processamento de eventos `'field'` / `'file'`
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
│ └── server.js
└── uploads
└── image.jpg
```
`__dirname` seria o mesmo diretório que o próprio arquivo de origem (src)
```js
`${__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:
```js
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.
Ou a outra maneira poderia ser apenas substituir o `form.onPart` como é mostrado um pouco
mais tarde.
```js
form.once('error', console.error);
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('field', (fieldName, fieldValue) => {
form.emit('data', { name: 'field', key: fieldName, value: fieldValue });
});
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);
}
});
```
### .use(plugin: Plugin)
Um método que permite estender a biblioteca Formidable. Por padrão, incluímos
4 plug-ins, que são essencialmente adaptadores para conectar os diferentes analisadores integrados.
**Os plugins adicionados por este método estão sempre ativados.**
_Consulte [src/plugins/](./src/plugins/) para uma visão mais detalhada dos plug-ins padrão._
O parâmetro `plugin` tem essa assinatura:
```typescript
function(formidable: Formidable, options: Options): void;
```
A arquitetura é simples. O `plugin` é uma função que é passada com a instância Formidable (o `form` nos exemplos README) e as opções.
**Observação:** o contexto `this` da função do plug-in também é a mesma instância.
```js
const form = formidable({ keepExtensions: true });
form.use((self, options) => {
// self === this === form
console.log('woohoo, custom plugin');
// faça suas coisas; verifique `src/plugins` para inspiração
});
form.parse(req, (error, fields, files) => {
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.
Se você quiser desabilitar alguns recursos de análise do Formidable, você pode desabilitar
o plugin que corresponde ao analisador. Por exemplo, se você deseja desabilitar a análise de
várias partes (para que o [src/parsers/Multipart.js](./src/parsers/Multipart.js)
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";
const form = formidable({
hashAlgorithm: 'sha1',
enabledPlugins: [octetstream, querystring, json],
});
```
**Esteja ciente** de que a ordem _PODE_ ser importante também. Os nomes correspondem 1:1 a
arquivos na pasta [src/plugins/](./src/plugins).
Solicitações pull para novos plug-ins integrados PODEM ser aceitas - por exemplo, analisador de
querystring mais avançado. Adicione seu plugin como um novo arquivo na pasta `src/plugins/` (em letras minúsculas) e
siga como os outros plugins são feitos.
### form.onPart
Se você quiser usar Formidable para manipular apenas algumas partes para você, você pode fazer
alguma coisa similar. ou ver
[#387](https://github.com/node-formidable/node-formidable/issues/387) para
inspiração, você pode, por exemplo, validar o tipo mime.
```js
const form = formidable();
form.onPart = (part) => {
part.on('data', (buffer) => {
// faça o que quiser aqui
});
};
```
Por exemplo, force Formidable a ser usado apenas em "partes" que não sejam de arquivo (ou seja, html
Campos)
```js
const form = formidable();
form.onPart = function (part) {
// deixe formidável lidar apenas com partes não arquivadas
if (part.originalFilename === '' || !part.mimetype) {
// usado internamente, por favor, não substitua!
form._handlePart(part);
}
};
```
### Arquivo
```ts
export interface File {
// O tamanho do arquivo enviado em bytes.
// Se o arquivo ainda estiver sendo carregado (veja o evento `'fileBegin'`),
// esta propriedade diz quantos bytes do arquivo já foram gravados no disco.
file.size: number;
// O caminho em que este arquivo está sendo gravado. Você pode modificar isso no evento `'fileBegin'`
// caso você esteja insatisfeito com a forma como o formidable gera um caminho temporário para seus arquivos.
file.filepath: string;
// O nome que este arquivo tinha de acordo com o cliente de upload.
file.originalFilename: string | null;
// calculado com base nas opções fornecidas.
file.newFilename: string | null;
// O tipo mime deste arquivo, de acordo com o cliente de upload.
file.mimetype: string | null;
// Um objeto Date (ou `null`) contendo a hora em que este arquivo foi gravado pela última vez.
// Principalmente aqui para compatibilidade com o [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
file.mtime: Date | null;
file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256'
// Se o cálculo `options.hashAlgorithm` foi definido, você pode ler o resumo hexadecimal desta var (no final, será uma string)
file.hash: string | object | null;
}
```
#### file.toJSON()
Este método retorna uma representação JSON do arquivo, permitindo que você `JSON.stringify()`
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) => {});
```
#### `'field'`
Emitido sempre que um par campo/valor é recebido.
```js
form.on('field', (name, value) => {});
```
#### `'fileBegin'`
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 (<input name="thisname" type="file">) 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
});
```
#### `'file'`
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
});
```
#### `'error'`
Emitido quando há um erro no processamento do formulário recebido. Uma solicitação que
apresenta um erro é pausada automaticamente, você terá que chamar manualmente
`request.resume()` se você quiser que a requisição continue disparando eventos `'data'`.
Pode ter `error.httpCode` e `error.code` anexados.
```js
form.on('error', (err) => {});
```
#### `'aborted'`
Emitido quando a requisição foi abortada pelo usuário. Agora isso pode ser devido a um
evento 'timeout' ou 'close' no soquete. Após este evento ser emitido, um
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', () => {});
```
#### `'end'`
Emitido quando toda a solicitação foi recebida e todos os arquivos contidos foram
liberados para o disco. Este é um ótimo lugar para você enviar sua resposta.
```js
form.on('end', () => {});
```
### Helpers
#### firstValues
Obtém os primeiros valores dos campos, como pré 3.0.0 sem passar múltiplos em uma
lista de exceções opcionais onde arrays de strings ainda são desejados (`<select multiple>` por exemplo)
```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" envia apenas o valor "on" se marcado,
converta-o em booleanos para cada entrada que deve ser enviada como uma caixa de seleção, use somente após a chamada de firstValues ou similar.
```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
[./CHANGELOG.md](./CHANGELOG.md)
## Ports & Créditos
- [multipart-parser](http://github.com/FooBarWidget/multipart-parser): um analisador C++ baseado em formidável
- [Ryan Dahl](http://twitter.com/ryah) por seu trabalho em
[http-parser](http://github.com/ry/http-parser) que inspirou fortemente o `multipart_parser.js` inicial.
## Contribuindo
Se a documentação não estiver clara ou tiver um erro de digitação, clique no botão `Edit` da página (ícone de lápis) e sugira uma correção.
Se você gostaria de nos ajudar a corrigir
um bug ou adicionar um novo recurso, verifique nosso [Contributing
Guide][contribuindo-url]. Pull requests são bem-vindos!
Agradecimentos vão para essas pessoas maravilhosas
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://twitter.com/felixge"><img src="https://avatars3.githubusercontent.com/u/15000?s=460&v=4" width="100px;" alt=""/><br /><sub><b>Felix Geisendörfer</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=felixge" title="Code">💻</a> <a href="#design-felixge" title="Design">🎨</a> <a href="#ideas-felixge" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=felixge" title="Documentation">📖</a></td>
<td align="center"><a href="https://tunnckoCore.com"><img src="https://avatars3.githubusercontent.com/u/5038030?v=4" width="100px;" alt=""/><br /><sub><b>Charlike Mike Reagent</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AtunnckoCore" title="Bug reports">🐛</a> <a href="#infra-tunnckoCore" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#design-tunnckoCore" title="Design">🎨</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Documentation">📖</a> <a href="#example-tunnckoCore" title="Examples">💡</a> <a href="#ideas-tunnckoCore" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-tunnckoCore" title="Maintenance">🚧</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=tunnckoCore" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/kedarv"><img src="https://avatars1.githubusercontent.com/u/1365665?v=4" width="100px;" alt=""/><br /><sub><b>Kedar</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=kedarv" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=kedarv" title="Tests">⚠️</a> <a href="#question-kedarv" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Akedarv" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/GrosSacASac"><img src="https://avatars0.githubusercontent.com/u/5721194?v=4" width="100px;" alt=""/><br /><sub><b>Walle Cyril</b></sub></a><br /><a href="#question-GrosSacASac" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AGrosSacASac" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=GrosSacASac" title="Code">💻</a> <a href="#financial-GrosSacASac" title="Financial">💵</a> <a href="#ideas-GrosSacASac" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-GrosSacASac" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/xarguments"><img src="https://avatars2.githubusercontent.com/u/40522463?v=4" width="100px;" alt=""/><br /><sub><b>Xargs</b></sub></a><br /><a href="#question-xarguments" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Axarguments" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=xarguments" title="Code">💻</a> <a href="#maintenance-xarguments" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/Amit-A"><img src="https://avatars1.githubusercontent.com/u/7987238?v=4" width="100px;" alt=""/><br /><sub><b>Amit-A</b></sub></a><br /><a href="#question-Amit-A" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AAmit-A" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=Amit-A" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://charmander.me/"><img src="https://avatars1.githubusercontent.com/u/1889843?v=4" width="100px;" alt=""/><br /><sub><b>Charmander</b></sub></a><br /><a href="#question-charmander" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Acharmander" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=charmander" title="Code">💻</a> <a href="#ideas-charmander" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-charmander" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://twitter.com/dylan_piercey"><img src="https://avatars2.githubusercontent.com/u/4985201?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Piercey</b></sub></a><br /><a href="#ideas-DylanPiercey" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://ochrona.jawne.info.pl"><img src="https://avatars1.githubusercontent.com/u/3618479?v=4" width="100px;" alt=""/><br /><sub><b>Adam Dobrawy</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Aad-m" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=ad-m" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/amitrohatgi"><img src="https://avatars3.githubusercontent.com/u/12177021?v=4" width="100px;" alt=""/><br /><sub><b>amitrohatgi</b></sub></a><br /><a href="#ideas-amitrohatgi" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/fengxinming"><img src="https://avatars2.githubusercontent.com/u/6262382?v=4" width="100px;" alt=""/><br /><sub><b>Jesse Feng</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Afengxinming" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://qtmsheep.com"><img src="https://avatars1.githubusercontent.com/u/7271496?v=4" width="100px;" alt=""/><br /><sub><b>Nathanael Demacon</b></sub></a><br /><a href="#question-quantumsheep" title="Answering Questions">💬</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=quantumsheep" title="Code">💻</a> <a href="https://github.com/node-formidable/node-formidable/pulls?q=is%3Apr+reviewed-by%3Aquantumsheep" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/MunMunMiao"><img src="https://avatars1.githubusercontent.com/u/18216142?v=4" width="100px;" alt=""/><br /><sub><b>MunMunMiao</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3AMunMunMiao" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/gabipetrovay"><img src="https://avatars0.githubusercontent.com/u/1170398?v=4" width="100px;" alt=""/><br /><sub><b>Gabriel Petrovay</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/issues?q=author%3Agabipetrovay" title="Bug reports">🐛</a> <a href="https://github.com/node-formidable/node-formidable/commits?author=gabipetrovay" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Elzair"><img src="https://avatars0.githubusercontent.com/u/2352818?v=4" width="100px;" alt=""/><br /><sub><b>Philip Woods</b></sub></a><br /><a href="https://github.com/node-formidable/node-formidable/commits?author=Elzair" title="Code">💻</a> <a href="#ideas-Elzair" title="Ideas, Planning, & Feedback">🤔</a></td>
<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>
</table>
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
De uma [postagem do blog Felix](https://felixge.de/2013/03/11/the-pull-request-hack/):
- [Sven Lito](https://github.com/svnlto) por corrigir bugs e mesclar patches
- [egirshov](https://github.com/egirshov) por contribuir com muitas melhorias para o analisador multipartes formidável de nós
- [Andrew Kelley](https://github.com/superjoe30) por também ajudar a corrigir bugs e fazer melhorias
- [Mike Frey](https://github.com/mikefrey) por contribuir com suporte JSON
## Licença
Formidable é licenciado sob a [MIT License][license-url].
<!-- badges -->
<!-- prettier-ignore-start -->
[codestyle-url]: https://github.com/airbnb/javascript
[codestyle-img]: https://badgen.net/badge/code%20style/airbnb%20%2B%20prettier/ff5a5f?icon=airbnb&cache=300
[codecov-url]: https://codecov.io/gh/node-formidable/formidable
[codecov-img]: https://badgen.net/codecov/c/github/node-formidable/formidable/master?icon=codecov
[npmv-canary-img]: https://badgen.net/npm/v/formidable/canary?icon=npm
[npmv-dev-img]: https://badgen.net/npm/v/formidable/dev?icon=npm
[npmv-img]: https://badgen.net/npm/v/formidable?icon=npm
[npmv-url]: https://npmjs.com/package/formidable
[license-img]: https://badgen.net/npm/license/formidable
[license-url]: https://github.com/node-formidable/formidable/blob/master/LICENSE
[chat-img]: https://badgen.net/badge/chat/on%20gitter/46BC99?icon=gitter
[chat-url]: https://gitter.im/node-formidable/Lobby
[libera-manifesto-url]: https://liberamanifesto.com
[libera-manifesto-img]: https://badgen.net/badge/libera/manifesto/grey
[renovateapp-url]: https://renovatebot.com
[renovateapp-img]: https://badgen.net/badge/renovate/enabled/green?cache=300
[prs-welcome-img]: https://badgen.net/badge/PRs/welcome/green?cache=300
[prs-welcome-url]: http://makeapullrequest.com
[twitter-url]: https://twitter.com/3a1fcBx0
[twitter-img]: https://badgen.net/twitter/follow/3a1fcBx0?icon=twitter&color=1da1f2&cache=300
[npm-weekly-img]: https://badgen.net/npm/dw/formidable?icon=npm&cache=300
[npm-monthly-img]: https://badgen.net/npm/dm/formidable?icon=npm&cache=300
[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&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/.github/blob/master/CONTRIBUTING.md
[code_of_conduct-url]: https://github.com/node-formidable/.github/blob/master/CODE_OF_CONDUCT.md
[open-issue-url]: https://github.com/node-formidable/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/github/checks/node-formidable/formidable/master/ubuntu?cache=300&label=linux%20build&icon=github
[macos-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/macos?cache=300&label=macos%20build&icon=github
[windows-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/windows?cache=300&label=windows%20build&icon=github
[build-url]: https://github.com/node-formidable/formidable/actions?query=workflow%3Anodejs
<!-- prettier-ignore-end -->

View File

@ -0,0 +1,394 @@
'use strict';
var node_stream = require('node:stream');
const malformedMultipart = 1012;
const FormidableError = class extends Error {
constructor(message, internalCode, httpCode = 500) {
super(message);
this.code = internalCode;
this.httpCode = httpCode;
}
};
/* eslint-disable no-fallthrough */
/* eslint-disable no-bitwise */
/* eslint-disable no-plusplus */
/* eslint-disable no-underscore-dangle */
let s = 0;
const STATE = {
PARSER_UNINITIALIZED: s++,
START: s++,
START_BOUNDARY: s++,
HEADER_FIELD_START: s++,
HEADER_FIELD: s++,
HEADER_VALUE_START: s++,
HEADER_VALUE: s++,
HEADER_VALUE_ALMOST_DONE: s++,
HEADERS_ALMOST_DONE: s++,
PART_DATA_START: s++,
PART_DATA: s++,
PART_END: s++,
END: s++,
};
let f = 1;
const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) };
const LF = 10;
const CR = 13;
const SPACE = 32;
const HYPHEN = 45;
const COLON = 58;
const A = 97;
const Z = 122;
function lower(c) {
return c | 0x20;
}
const STATES = {};
Object.keys(STATE).forEach((stateName) => {
STATES[stateName] = STATE[stateName];
});
class MultipartParser extends node_stream.Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.boundary = null;
this.boundaryChars = null;
this.lookbehind = null;
this.bufferLength = 0;
this.state = STATE.PARSER_UNINITIALIZED;
this.globalOptions = { ...options };
this.index = null;
this.flags = 0;
}
_endUnexpected() {
return new FormidableError(
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
malformedMultipart,
400,
);
}
_flush(done) {
if (
(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');
done();
} else if (this.state !== STATE.END) {
done(this._endUnexpected());
} else {
done();
}
}
initWithBoundary(str) {
this.boundary = Buffer.from(`\r\n--${str}`);
this.lookbehind = Buffer.alloc(this.boundary.length + 8);
this.state = STATE.START;
this.boundaryChars = {};
for (let i = 0; i < this.boundary.length; i++) {
this.boundaryChars[this.boundary[i]] = true;
}
}
// eslint-disable-next-line max-params
_handleCallback(name, buf, start, end) {
if (start !== undefined && start === end) {
return;
}
this.push({ name, buffer: buf, start, end });
}
// eslint-disable-next-line max-statements
_transform(buffer, _, done) {
let i = 0;
let prevIndex = this.index;
let { index, state, flags } = this;
const { lookbehind, boundary, boundaryChars } = this;
const boundaryLength = boundary.length;
const boundaryEnd = boundaryLength - 1;
this.bufferLength = buffer.length;
let c = null;
let cl = null;
const setMark = (name, idx) => {
this[`${name}Mark`] = typeof idx === 'number' ? idx : i;
};
const clearMarkSymbol = (name) => {
delete this[`${name}Mark`];
};
const dataCallback = (name, shouldClear) => {
const markSymbol = `${name}Mark`;
if (!(markSymbol in this)) {
return;
}
if (!shouldClear) {
this._handleCallback(name, buffer, this[markSymbol], buffer.length);
setMark(name, 0);
} else {
this._handleCallback(name, buffer, this[markSymbol], i);
clearMarkSymbol(name);
}
};
for (i = 0; i < this.bufferLength; i++) {
c = buffer[i];
switch (state) {
case STATE.PARSER_UNINITIALIZED:
done(this._endUnexpected());
return;
case STATE.START:
index = 0;
state = STATE.START_BOUNDARY;
case STATE.START_BOUNDARY:
if (index === boundary.length - 2) {
if (c === HYPHEN) {
flags |= FBOUNDARY.LAST_BOUNDARY;
} else if (c !== CR) {
done(this._endUnexpected());
return;
}
index++;
break;
} else if (index - 1 === boundary.length - 2) {
if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) {
this._handleCallback('end');
state = STATE.END;
flags = 0;
} else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) {
index = 0;
this._handleCallback('partBegin');
state = STATE.HEADER_FIELD_START;
} else {
done(this._endUnexpected());
return;
}
break;
}
if (c !== boundary[index + 2]) {
index = -2;
}
if (c === boundary[index + 2]) {
index++;
}
break;
case STATE.HEADER_FIELD_START:
state = STATE.HEADER_FIELD;
setMark('headerField');
index = 0;
case STATE.HEADER_FIELD:
if (c === CR) {
clearMarkSymbol('headerField');
state = STATE.HEADERS_ALMOST_DONE;
break;
}
index++;
if (c === HYPHEN) {
break;
}
if (c === COLON) {
if (index === 1) {
// empty header field
done(this._endUnexpected());
return;
}
dataCallback('headerField', true);
state = STATE.HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
done(this._endUnexpected());
return;
}
break;
case STATE.HEADER_VALUE_START:
if (c === SPACE) {
break;
}
setMark('headerValue');
state = STATE.HEADER_VALUE;
case STATE.HEADER_VALUE:
if (c === CR) {
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;
}
state = STATE.HEADER_FIELD_START;
break;
case STATE.HEADERS_ALMOST_DONE:
if (c !== LF) {
done(this._endUnexpected());
return;
}
this._handleCallback('headersEnd');
state = STATE.PART_DATA_START;
break;
case STATE.PART_DATA_START:
state = STATE.PART_DATA;
setMark('partData');
case STATE.PART_DATA:
prevIndex = index;
if (index === 0) {
// boyer-moore derived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundary.length) {
if (boundary[index] === c) {
if (index === 0) {
dataCallback('partData', true);
}
index++;
} else {
index = 0;
}
} else if (index === boundary.length) {
index++;
if (c === CR) {
// CR = part boundary
flags |= FBOUNDARY.PART_BOUNDARY;
} else if (c === HYPHEN) {
// HYPHEN = end boundary
flags |= FBOUNDARY.LAST_BOUNDARY;
} else {
index = 0;
}
} else if (index - 1 === boundary.length) {
if (flags & FBOUNDARY.PART_BOUNDARY) {
index = 0;
if (c === LF) {
// unset the PART_BOUNDARY flag
flags &= ~FBOUNDARY.PART_BOUNDARY;
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');
state = STATE.END;
flags = 0;
} else {
index = 0;
}
} else {
index = 0;
}
}
if (index > 0) {
// when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index - 1] = c;
} else if (prevIndex > 0) {
// if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
this._handleCallback('partData', lookbehind, 0, prevIndex);
prevIndex = 0;
setMark('partData');
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--;
}
break;
case STATE.END:
break;
default:
done(this._endUnexpected());
return;
}
}
dataCallback('headerField');
dataCallback('headerValue');
dataCallback('partData');
this.index = index;
this.state = state;
this.flags = flags;
done();
return this.bufferLength;
}
explain() {
return `state = ${MultipartParser.stateToString(this.state)}`;
}
}
// eslint-disable-next-line consistent-return
MultipartParser.stateToString = (stateNumber) => {
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const stateName in STATE) {
const number = STATE[stateName];
if (number === stateNumber) return stateName;
}
};
Object.assign(MultipartParser, { STATES });
/* eslint-disable no-underscore-dangle */
const multipartType = 'multipart';
/* eslint-disable no-underscore-dangle */
const querystringType = 'urlencoded';
const firstValues = (form, fields, exceptions = []) => {
if (form.type !== querystringType && form.type !== multipartType) {
return fields;
}
return Object.fromEntries(
Object.entries(fields).map(([key, value]) => {
if (exceptions.includes(key)) {
return [key, value];
}
return [key, value[0]];
}),
);
};
exports.firstValues = firstValues;

View File

@ -0,0 +1,12 @@
'use strict';
const readBooleans = (fields, listOfBooleans) => {
// html forms do not send off at all
const fieldsWithBooleans = { ...fields };
listOfBooleans.forEach((key) => {
fieldsWithBooleans[key] = fields[key] === `on` || fields[key] === true;
});
return fieldsWithBooleans;
};
exports.readBooleans = readBooleans;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
/* eslint-disable no-underscore-dangle */
class JSONParser extends node_stream.Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.chunks = [];
this.globalOptions = { ...options };
}
_transform(chunk, encoding, callback) {
this.chunks.push(String(chunk)); // todo consider using a string decoder
callback();
}
_flush(callback) {
try {
const fields = JSON.parse(this.chunks.join(''));
this.push(fields);
} catch (e) {
callback(e);
return;
}
this.chunks = null;
callback();
}
}
exports.default = JSONParser;

View File

@ -0,0 +1,372 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
const malformedMultipart = 1012;
const FormidableError = class extends Error {
constructor(message, internalCode, httpCode = 500) {
super(message);
this.code = internalCode;
this.httpCode = httpCode;
}
};
/* eslint-disable no-fallthrough */
/* eslint-disable no-bitwise */
/* eslint-disable no-plusplus */
/* eslint-disable no-underscore-dangle */
let s = 0;
const STATE = {
PARSER_UNINITIALIZED: s++,
START: s++,
START_BOUNDARY: s++,
HEADER_FIELD_START: s++,
HEADER_FIELD: s++,
HEADER_VALUE_START: s++,
HEADER_VALUE: s++,
HEADER_VALUE_ALMOST_DONE: s++,
HEADERS_ALMOST_DONE: s++,
PART_DATA_START: s++,
PART_DATA: s++,
PART_END: s++,
END: s++,
};
let f = 1;
const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) };
const LF = 10;
const CR = 13;
const SPACE = 32;
const HYPHEN = 45;
const COLON = 58;
const A = 97;
const Z = 122;
function lower(c) {
return c | 0x20;
}
const STATES = {};
Object.keys(STATE).forEach((stateName) => {
STATES[stateName] = STATE[stateName];
});
class MultipartParser extends node_stream.Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.boundary = null;
this.boundaryChars = null;
this.lookbehind = null;
this.bufferLength = 0;
this.state = STATE.PARSER_UNINITIALIZED;
this.globalOptions = { ...options };
this.index = null;
this.flags = 0;
}
_endUnexpected() {
return new FormidableError(
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
malformedMultipart,
400,
);
}
_flush(done) {
if (
(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');
done();
} else if (this.state !== STATE.END) {
done(this._endUnexpected());
} else {
done();
}
}
initWithBoundary(str) {
this.boundary = Buffer.from(`\r\n--${str}`);
this.lookbehind = Buffer.alloc(this.boundary.length + 8);
this.state = STATE.START;
this.boundaryChars = {};
for (let i = 0; i < this.boundary.length; i++) {
this.boundaryChars[this.boundary[i]] = true;
}
}
// eslint-disable-next-line max-params
_handleCallback(name, buf, start, end) {
if (start !== undefined && start === end) {
return;
}
this.push({ name, buffer: buf, start, end });
}
// eslint-disable-next-line max-statements
_transform(buffer, _, done) {
let i = 0;
let prevIndex = this.index;
let { index, state, flags } = this;
const { lookbehind, boundary, boundaryChars } = this;
const boundaryLength = boundary.length;
const boundaryEnd = boundaryLength - 1;
this.bufferLength = buffer.length;
let c = null;
let cl = null;
const setMark = (name, idx) => {
this[`${name}Mark`] = typeof idx === 'number' ? idx : i;
};
const clearMarkSymbol = (name) => {
delete this[`${name}Mark`];
};
const dataCallback = (name, shouldClear) => {
const markSymbol = `${name}Mark`;
if (!(markSymbol in this)) {
return;
}
if (!shouldClear) {
this._handleCallback(name, buffer, this[markSymbol], buffer.length);
setMark(name, 0);
} else {
this._handleCallback(name, buffer, this[markSymbol], i);
clearMarkSymbol(name);
}
};
for (i = 0; i < this.bufferLength; i++) {
c = buffer[i];
switch (state) {
case STATE.PARSER_UNINITIALIZED:
done(this._endUnexpected());
return;
case STATE.START:
index = 0;
state = STATE.START_BOUNDARY;
case STATE.START_BOUNDARY:
if (index === boundary.length - 2) {
if (c === HYPHEN) {
flags |= FBOUNDARY.LAST_BOUNDARY;
} else if (c !== CR) {
done(this._endUnexpected());
return;
}
index++;
break;
} else if (index - 1 === boundary.length - 2) {
if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) {
this._handleCallback('end');
state = STATE.END;
flags = 0;
} else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) {
index = 0;
this._handleCallback('partBegin');
state = STATE.HEADER_FIELD_START;
} else {
done(this._endUnexpected());
return;
}
break;
}
if (c !== boundary[index + 2]) {
index = -2;
}
if (c === boundary[index + 2]) {
index++;
}
break;
case STATE.HEADER_FIELD_START:
state = STATE.HEADER_FIELD;
setMark('headerField');
index = 0;
case STATE.HEADER_FIELD:
if (c === CR) {
clearMarkSymbol('headerField');
state = STATE.HEADERS_ALMOST_DONE;
break;
}
index++;
if (c === HYPHEN) {
break;
}
if (c === COLON) {
if (index === 1) {
// empty header field
done(this._endUnexpected());
return;
}
dataCallback('headerField', true);
state = STATE.HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
done(this._endUnexpected());
return;
}
break;
case STATE.HEADER_VALUE_START:
if (c === SPACE) {
break;
}
setMark('headerValue');
state = STATE.HEADER_VALUE;
case STATE.HEADER_VALUE:
if (c === CR) {
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;
}
state = STATE.HEADER_FIELD_START;
break;
case STATE.HEADERS_ALMOST_DONE:
if (c !== LF) {
done(this._endUnexpected());
return;
}
this._handleCallback('headersEnd');
state = STATE.PART_DATA_START;
break;
case STATE.PART_DATA_START:
state = STATE.PART_DATA;
setMark('partData');
case STATE.PART_DATA:
prevIndex = index;
if (index === 0) {
// boyer-moore derived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundary.length) {
if (boundary[index] === c) {
if (index === 0) {
dataCallback('partData', true);
}
index++;
} else {
index = 0;
}
} else if (index === boundary.length) {
index++;
if (c === CR) {
// CR = part boundary
flags |= FBOUNDARY.PART_BOUNDARY;
} else if (c === HYPHEN) {
// HYPHEN = end boundary
flags |= FBOUNDARY.LAST_BOUNDARY;
} else {
index = 0;
}
} else if (index - 1 === boundary.length) {
if (flags & FBOUNDARY.PART_BOUNDARY) {
index = 0;
if (c === LF) {
// unset the PART_BOUNDARY flag
flags &= ~FBOUNDARY.PART_BOUNDARY;
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');
state = STATE.END;
flags = 0;
} else {
index = 0;
}
} else {
index = 0;
}
}
if (index > 0) {
// when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index - 1] = c;
} else if (prevIndex > 0) {
// if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
this._handleCallback('partData', lookbehind, 0, prevIndex);
prevIndex = 0;
setMark('partData');
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--;
}
break;
case STATE.END:
break;
default:
done(this._endUnexpected());
return;
}
}
dataCallback('headerField');
dataCallback('headerValue');
dataCallback('partData');
this.index = index;
this.state = state;
this.flags = flags;
done();
return this.bufferLength;
}
explain() {
return `state = ${MultipartParser.stateToString(this.state)}`;
}
}
// eslint-disable-next-line consistent-return
MultipartParser.stateToString = (stateNumber) => {
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const stateName in STATE) {
const number = STATE[stateName];
if (number === stateNumber) return stateName;
}
};
var Multipart = Object.assign(MultipartParser, { STATES });
exports.STATES = STATES;
exports.default = Multipart;

View File

@ -0,0 +1,14 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
class OctetStreamParser extends node_stream.PassThrough {
constructor(options = {}) {
super();
this.globalOptions = { ...options };
}
}
exports.default = OctetStreamParser;

View File

@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
/* eslint-disable no-underscore-dangle */
// This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser
class QuerystringParser extends node_stream.Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.globalOptions = { ...options };
this.buffer = '';
this.bufferLength = 0;
}
_transform(buffer, encoding, callback) {
this.buffer += buffer.toString('ascii');
this.bufferLength = this.buffer.length;
callback();
}
_flush(callback) {
const fields = new URLSearchParams(this.buffer);
for (const [key, value] of fields) {
this.push({
key,
value,
});
}
this.buffer = '';
callback();
}
}
exports.default = QuerystringParser;

View File

@ -0,0 +1,131 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var node_stream = require('node:stream');
const maxFieldsSizeExceeded = 1006;
const FormidableError = class extends Error {
constructor(message, internalCode, httpCode = 500) {
super(message);
this.code = internalCode;
this.httpCode = httpCode;
}
};
// not used
/* eslint-disable no-underscore-dangle */
const AMPERSAND = 38;
const EQUALS = 61;
class QuerystringParser extends node_stream.Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
const { maxFieldSize } = options;
this.maxFieldLength = maxFieldSize;
this.buffer = Buffer.from('');
this.fieldCount = 0;
this.sectionStart = 0;
this.key = '';
this.readingKey = true;
}
_transform(buffer, encoding, callback) {
let len = buffer.length;
if (this.buffer && this.buffer.length) {
// we have some data left over from the last write which we are in the middle of processing
len += this.buffer.length;
buffer = Buffer.concat([this.buffer, buffer], len);
}
for (let i = this.buffer.length || 0; i < len; i += 1) {
const c = buffer[i];
if (this.readingKey) {
// KEY, check for =
if (c === EQUALS) {
this.key = this.getSection(buffer, i);
this.readingKey = false;
this.sectionStart = i + 1;
} else if (c === AMPERSAND) {
// just key, no value. Prepare to read another key
this.emitField(this.getSection(buffer, i));
this.sectionStart = i + 1;
}
// VALUE, check for &
} else if (c === AMPERSAND) {
this.emitField(this.key, this.getSection(buffer, i));
this.sectionStart = i + 1;
}
if (
this.maxFieldLength &&
i - this.sectionStart === this.maxFieldLength
) {
callback(
new FormidableError(
`${
this.readingKey ? 'Key' : `Value for ${this.key}`
} longer than maxFieldLength`,
),
maxFieldsSizeExceeded,
413,
);
}
}
// Prepare the remaining key or value (from sectionStart to the end) for the next write() or for end()
len -= this.sectionStart;
if (len) {
// i.e. Unless the last character was a & or =
this.buffer = Buffer.from(this.buffer, 0, this.sectionStart);
} else this.buffer = null;
this.sectionStart = 0;
callback();
}
_flush(callback) {
// Emit the last field
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'));
}
} 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 = '';
callback();
}
getSection(buffer, i) {
if (i === this.sectionStart) return '';
return buffer.toString('ascii', this.sectionStart, i);
}
emitField(key, val) {
this.key = '';
this.readingKey = true;
this.push({ key, value: val || '' });
}
}
// const q = new QuerystringParser({maxFieldSize: 100});
// (async function() {
// for await (const chunk of q) {
// console.log(chunk);
// }
// })();
// q.write("a=b&c=d")
// q.end()
exports.default = QuerystringParser;

View File

@ -0,0 +1,140 @@
{
"name": "formidable",
"version": "3.5.2",
"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",
"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",
"./dist",
"./CHANGELOG",
"./README.md",
"./README_pt_BR.md"
],
"publishConfig": {
"access": "public",
"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 '**/*'",
"fmt:prepare": "prettier --write",
"lint": "yarn run lint:prepare .",
"lint:prepare": "eslint --cache --fix --quiet --format codeframe",
"reinstall": "del-cli ./node_modules ./yarn.lock",
"postreinstall": "yarn setup",
"setup": "yarn",
"pretest": "del-cli ./test/tmp && make-dir ./test/tmp",
"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": "node --experimental-vm-modules node_modules/.bin/nyc jest --testPathPattern=test/ --coverage && node --experimental-vm-modules node_modules/.bin/nyc node --test test-node/"
},
"dependencies": {
"dezalgo": "^1.0.4",
"hexoid": "^2.0.0",
"once": "^1.4.0"
},
"devDependencies": {
"@commitlint/cli": "8.3.5",
"@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",
"del-cli": "3.0.0",
"eslint": "6.8.0",
"eslint-config-airbnb-base": "14.1.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-prettier": "3.1.3",
"express": "4.17.1",
"formdata-polyfill": "^4.0.10",
"husky": "4.2.5",
"jest": "27.2.4",
"koa": "2.11.0",
"lint-staged": "10.2.7",
"make-dir-cli": "2.0.0",
"nyc": "15.1.0",
"prettier": "2.0.5",
"prettier-plugin-pkgjson": "0.2.8",
"rollup": "^3.25.3",
"supertest": "6.1.6"
},
"jest": {
"verbose": true
},
"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",
":pinAllExceptPeerDependencies"
]
},
"packageManager": "yarn@1.22.17",
"keywords": [
"multipart",
"form",
"data",
"querystring",
"www",
"json",
"ulpoad",
"file"
]
}

View File

@ -0,0 +1,684 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable no-underscore-dangle */
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 toHexoId = hexoid(25);
const DEFAULT_OPTIONS = {
maxFields: 1000,
maxFieldsSize: 20 * 1024 * 1024,
maxFiles: Infinity,
maxFileSize: 200 * 1024 * 1024,
maxTotalFileSize: undefined,
minFileSize: 1,
allowEmptyFiles: false,
createDirsFromUploads: false,
keepExtensions: false,
encoding: 'utf-8',
hashAlgorithm: false,
uploadDir: os.tmpdir(),
enabledPlugins: [octetstream, querystring, multipart, json],
fileWriteStreamHandler: null,
defaultInvalidName: 'invalid-name',
filter(_part) {
return true;
},
filename: undefined,
};
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 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 {
constructor(options = {}) {
super();
this.options = { ...DEFAULT_OPTIONS, ...options };
if (!this.options.maxTotalFileSize) {
this.options.maxTotalFileSize = this.options.maxFileSize
}
const dir = path.resolve(
this.options.uploadDir || this.options.uploaddir || os.tmpdir(),
);
this.uploaddir = dir;
this.uploadDir = dir;
// initialize with null
[
'error',
'headers',
'type',
'bytesExpected',
'bytesReceived',
'_parser',
'req',
].forEach((key) => {
this[key] = null;
});
this._setUpRename();
this._flushing = 0;
this._fieldsSize = 0;
this._totalFileSize = 0;
this._plugins = [];
this.openedFiles = [];
this.options.enabledPlugins = []
.concat(this.options.enabledPlugins)
.filter(Boolean);
if (this.options.enabledPlugins.length === 0) {
throw new FormidableError(
'expect at least 1 enabled builtin plugin, see options.enabledPlugins',
errors.missingPlugin,
);
}
this.options.enabledPlugins.forEach((plugin) => {
this.use(plugin);
});
this._setUpMaxFields();
this._setUpMaxFiles();
this.ended = undefined;
this.type = undefined;
}
use(plugin) {
if (typeof plugin !== 'function') {
throw new FormidableError(
'.use: expect `plugin` to be a function',
errors.pluginFunction,
);
}
this._plugins.push(plugin.bind(this));
return this;
}
pause () {
try {
this.req.pause();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
}
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;
}
// 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) {
let resolveRef;
let rejectRef;
promise = new Promise((resolve, reject) => {
resolveRef = resolve;
rejectRef = reject;
});
cb = (err, fields, files) => {
if (err) {
rejectRef(err);
} else {
resolveRef([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.
await this.writeHeaders(req.headers);
// Start listening for data.
req
.on('error', (err) => {
this._error(err);
})
.on('aborted', () => {
this.emit('aborted');
this._error(new FormidableError('Request aborted', errors.aborted));
})
.on('data', (buffer) => {
try {
this.write(buffer);
} catch (err) {
this._error(err);
}
})
.on('end', () => {
if (this.error) {
return;
}
if (this._parser) {
this._parser.end();
}
});
if (promise) {
return promise;
}
return this;
}
async writeHeaders(headers) {
this.headers = headers;
this._parseContentLength();
await this._parseContentType();
if (!this._parser) {
this._error(
new FormidableError(
'no parser found',
errors.noParser,
415, // Unsupported Media Type
),
);
return;
}
this._parser.once('error', (error) => {
this._error(error);
});
}
write(buffer) {
if (this.error) {
return null;
}
if (!this._parser) {
this._error(
new FormidableError('uninitialized parser', errors.uninitializedParser),
);
return null;
}
this.bytesReceived += buffer.length;
this.emit('progress', this.bytesReceived, this.bytesExpected);
this._parser.write(buffer);
return this.bytesReceived;
}
onPart(part) {
// this method can be overwritten by the user
return this._handlePart(part);
}
async _handlePart(part) {
if (part.originalFilename && typeof part.originalFilename !== 'string') {
this._error(
new FormidableError(
`the part.originalFilename should be string when it exists`,
errors.filenameNotString,
),
);
return;
}
// This MUST check exactly for undefined. You can not change it to !part.originalFilename.
// todo: uncomment when switch tests to Jest
// console.log(part);
// ? NOTE(@tunnckocore): no it can be any falsey value, it most probably depends on what's returned
// from somewhere else. Where recently I changed the return statements
// and such thing because code style
// ? 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 = '';
const decoder = new StringDecoder(
part.transferEncoding || this.options.encoding,
);
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
),
);
return;
}
value += decoder.write(buffer);
});
part.on('end', () => {
this.emit('field', part.name, value);
});
return;
}
if (!this.options.filter(part)) {
return;
}
this._flushing += 1;
let fileSize = 0;
const newFilename = this._getNewName(part);
const filepath = this._joinDirectoryName(newFilename);
const file = await this._newFile({
newFilename,
filepath,
originalFilename: part.originalFilename,
mimetype: part.mimetype,
});
file.on('error', (err) => {
this._error(err);
});
this.emit('fileBegin', part.name, file);
file.open();
this.openedFiles.push(file);
part.on('data', (buffer) => {
this._totalFileSize += buffer.length;
fileSize += buffer.length;
if (this._totalFileSize > this.options.maxTotalFileSize) {
this._error(
new FormidableError(
`options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`,
errors.biggerThanTotalMaxFileSize,
413,
),
);
return;
}
if (buffer.length === 0) {
return;
}
this.pause();
file.write(buffer, () => {
this.resume();
});
});
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,
),
);
return;
}
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;
}
file.end(() => {
this._flushing -= 1;
this.emit('file', part.name, file);
this._maybeEnd();
});
});
}
// eslint-disable-next-line max-statements
async _parseContentType() {
if (this.bytesExpected === 0) {
this._parser = new DummyParser(this, this.options);
return;
}
if (!this.headers['content-type']) {
this._error(
new FormidableError(
'bad content-type header, no content-type',
errors.missingContentType,
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);
// 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') {
if (this.error || this.ended) {
return;
}
this.req = null;
this.error = err;
this.emit(eventName, err);
this.openedFiles.forEach((file) => {
file.destroy();
});
}
_parseContentLength() {
this.bytesReceived = 0;
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);
}
}
_newParser() {
return new MultipartParser(this.options);
}
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,
});
}
_getFileName(headerValue) {
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
const m = headerValue.match(
/\bfilename=("(.*?)"|([^()<>{}[\]@,;:"?=\s/\t]+))($|;\s)/i,
);
if (!m) return null;
const match = m[2] || m[3] || '';
let originalFilename = match.substr(match.lastIndexOf('\\') + 1);
originalFilename = originalFilename.replace(/%22/g, '"');
originalFilename = originalFilename.replace(/&#([\d]{4});/g, (_, code) =>
String.fromCharCode(code),
);
return originalFilename;
}
// able to get composed extension with multiple dots
// "a.b.c" -> ".b.c"
// as opposed to path.extname -> ".c"
_getExtension(str) {
if (!str) {
return '';
}
const basename = path.basename(str);
const firstDot = basename.indexOf('.');
const lastDot = basename.lastIndexOf('.');
let rawExtname = path.extname(basename);
if (firstDot !== lastDot) {
rawExtname = basename.slice(firstDot);
}
let filtered;
const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar);
if (firstInvalidIndex === -1) {
filtered = rawExtname;
} else {
filtered = rawExtname.substring(0, firstInvalidIndex);
}
if (filtered === '.') {
return '';
}
return filtered;
}
_joinDirectoryName(name) {
const newPath = path.join(this.uploadDir, name);
// prevent directory traversal attacks
if (!newPath.startsWith(this.uploadDir)) {
return path.join(this.uploadDir, this.options.defaultInvalidName);
}
return newPath;
}
_setUpRename() {
const hasRename = typeof this.options.filename === 'function';
if (hasRename) {
this._getNewName = (part) => {
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 = '';
}
}
return this.options.filename.call(this, name, ext, part, this);
};
} else {
this._getNewName = (part) => {
const name = toHexoId();
if (part && this.options.keepExtensions) {
const originalFilename =
typeof part === 'string' ? part : part.originalFilename;
return `${name}${this._getExtension(originalFilename)}`;
}
return name;
};
}
}
_setUpMaxFields() {
if (this.options.maxFields !== Infinity) {
let fieldsCount = 0;
this.on('field', () => {
fieldsCount += 1;
if (fieldsCount > this.options.maxFields) {
this._error(
new FormidableError(
`options.maxFields (${this.options.maxFields}) exceeded`,
errors.maxFieldsExceeded,
413,
),
);
}
});
}
}
_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() {
if (!this.ended || this._flushing || this.error) {
return;
}
this.req = null;
this.emit('end');
}
}
export default IncomingForm;
export { DEFAULT_OPTIONS };

View File

@ -0,0 +1,51 @@
const missingPlugin = 1000;
const pluginFunction = 1001;
const aborted = 1002;
const noParser = 1003;
const uninitializedParser = 1004;
const filenameNotString = 1005;
const maxFieldsSizeExceeded = 1006;
const maxFieldsExceeded = 1007;
const smallerThanMinFileSize = 1008;
const biggerThanTotalMaxFileSize = 1009;
const noEmptyFiles = 1010;
const missingContentType = 1011;
const malformedMultipart = 1012;
const missingMultipartBoundary = 1013;
const unknownTransferEncoding = 1014;
const maxFilesExceeded = 1015;
const biggerThanMaxFileSize = 1016;
const pluginFailed = 1017;
const cannotCreateDir = 1018;
const FormidableError = class extends Error {
constructor(message, internalCode, httpCode = 500) {
super(message);
this.code = internalCode;
this.httpCode = httpCode;
}
};
export {
missingPlugin,
pluginFunction,
aborted,
noParser,
uninitializedParser,
filenameNotString,
maxFieldsSizeExceeded,
maxFieldsExceeded,
maxFilesExceeded,
smallerThanMinFileSize,
biggerThanMaxFileSize,
noEmptyFiles,
missingContentType,
malformedMultipart,
missingMultipartBoundary,
unknownTransferEncoding,
biggerThanTotalMaxFileSize,
pluginFailed,
cannotCreateDir,
};
export default FormidableError;

View File

@ -0,0 +1,88 @@
/* eslint-disable no-underscore-dangle */
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 }) {
super();
this.lastModifiedDate = null;
Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm });
this.size = 0;
this._writeStream = null;
if (typeof this.hashAlgorithm === 'string') {
this.hash = crypto.createHash(this.hashAlgorithm);
} else {
this.hash = null;
}
}
open() {
this._writeStream = fs.createWriteStream(this.filepath);
this._writeStream.on('error', (err) => {
this.emit('error', err);
});
}
toJSON() {
const json = {
size: this.size,
filepath: this.filepath,
newFilename: this.newFilename,
mimetype: this.mimetype,
mtime: this.lastModifiedDate,
length: this.length,
originalFilename: this.originalFilename,
};
if (this.hash && this.hash !== '') {
json.hash = this.hash;
}
return json;
}
toString() {
return `PersistentFile: ${this.newFilename}, Original: ${this.originalFilename}, Path: ${this.filepath}`;
}
write(buffer, cb) {
if (this.hash) {
this.hash.update(buffer);
}
if (this._writeStream.closed) {
cb();
return;
}
this._writeStream.write(buffer, () => {
this.lastModifiedDate = new Date();
this.size += buffer.length;
this.emit('progress', this.size);
cb();
});
}
end(cb) {
if (this.hash) {
this.hash = this.hash.digest('hex');
}
this._writeStream.end(() => {
this.emit('end');
cb();
});
}
destroy() {
this._writeStream.destroy();
const filepath = this.filepath;
setTimeout(function () {
fs.unlink(filepath, () => {});
}, 1)
}
}
export default PersistentFile;

View File

@ -0,0 +1,80 @@
/* eslint-disable no-underscore-dangle */
import { createHash } from 'node:crypto';
import { EventEmitter } from 'node:events';
class VolatileFile extends EventEmitter {
constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) {
super();
this.lastModifiedDate = null;
Object.assign(this, { filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream });
this.size = 0;
this._writeStream = null;
if (typeof this.hashAlgorithm === 'string') {
this.hash = createHash(this.hashAlgorithm);
} else {
this.hash = null;
}
}
open() {
this._writeStream = this.createFileWriteStream(this);
this._writeStream.on('error', (err) => {
this.emit('error', err);
});
}
destroy() {
this._writeStream.destroy();
}
toJSON() {
const json = {
size: this.size,
newFilename: this.newFilename,
length: this.length,
originalFilename: this.originalFilename,
mimetype: this.mimetype,
};
if (this.hash && this.hash !== '') {
json.hash = this.hash;
}
return json;
}
toString() {
return `VolatileFile: ${this.originalFilename}`;
}
write(buffer, cb) {
if (this.hash) {
this.hash.update(buffer);
}
if (this._writeStream.closed || this._writeStream.destroyed) {
cb();
return;
}
this._writeStream.write(buffer, () => {
this.size += buffer.length;
this.emit('progress', this.size);
cb();
});
}
end(cb) {
if (this.hash) {
this.hash = this.hash.digest('hex');
}
this._writeStream.end(() => {
this.emit('end');
cb();
});
}
}
export default VolatileFile;

View File

@ -0,0 +1,19 @@
import { multipartType } from '../plugins/multipart.js';
import { querystringType } from '../plugins/querystring.js';
const firstValues = (form, fields, exceptions = []) => {
if (form.type !== querystringType && form.type !== multipartType) {
return fields;
}
return Object.fromEntries(
Object.entries(fields).map(([key, value]) => {
if (exceptions.includes(key)) {
return [key, value];
}
return [key, value[0]];
}),
);
};
export { firstValues };

View File

@ -0,0 +1,10 @@
const readBooleans = (fields, listOfBooleans) => {
// html forms do not send off at all
const fieldsWithBooleans = { ...fields };
listOfBooleans.forEach((key) => {
fieldsWithBooleans[key] = fields[key] === `on` || fields[key] === true;
});
return fieldsWithBooleans;
};
export { readBooleans };

View File

@ -0,0 +1,32 @@
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;
export default formidable;
export {
PersistentFile as File,
PersistentFile,
VolatileFile,
Formidable,
// alias
Formidable as IncomingForm,
// as named
formidable,
// misc
DEFAULT_OPTIONS as defaultOptions,
enabledPlugins,
};
export * from './parsers/index.js';
export * from './plugins/index.js';
export * as errors from './FormidableError.js';

View File

@ -0,0 +1,19 @@
/* eslint-disable no-underscore-dangle */
import { Transform } from 'node:stream';
class DummyParser extends Transform {
constructor(incomingForm, options = {}) {
super();
this.globalOptions = { ...options };
this.incomingForm = incomingForm;
}
_flush(callback) {
this.incomingForm.ended = true;
this.incomingForm._maybeEnd();
callback();
}
}
export default DummyParser;

View File

@ -0,0 +1,30 @@
/* eslint-disable no-underscore-dangle */
import { Transform } from 'node:stream';
class JSONParser extends Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.chunks = [];
this.globalOptions = { ...options };
}
_transform(chunk, encoding, callback) {
this.chunks.push(String(chunk)); // todo consider using a string decoder
callback();
}
_flush(callback) {
try {
const fields = JSON.parse(this.chunks.join(''));
this.push(fields);
} catch (e) {
callback(e);
return;
}
this.chunks = null;
callback();
}
}
export default JSONParser;

View File

@ -0,0 +1,356 @@
/* eslint-disable no-fallthrough */
/* eslint-disable no-bitwise */
/* 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';
let s = 0;
const STATE = {
PARSER_UNINITIALIZED: s++,
START: s++,
START_BOUNDARY: s++,
HEADER_FIELD_START: s++,
HEADER_FIELD: s++,
HEADER_VALUE_START: s++,
HEADER_VALUE: s++,
HEADER_VALUE_ALMOST_DONE: s++,
HEADERS_ALMOST_DONE: s++,
PART_DATA_START: s++,
PART_DATA: s++,
PART_END: s++,
END: s++,
};
let f = 1;
const FBOUNDARY = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) };
const LF = 10;
const CR = 13;
const SPACE = 32;
const HYPHEN = 45;
const COLON = 58;
const A = 97;
const Z = 122;
function lower(c) {
return c | 0x20;
}
export const STATES = {};
Object.keys(STATE).forEach((stateName) => {
STATES[stateName] = STATE[stateName];
});
class MultipartParser extends Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
this.boundary = null;
this.boundaryChars = null;
this.lookbehind = null;
this.bufferLength = 0;
this.state = STATE.PARSER_UNINITIALIZED;
this.globalOptions = { ...options };
this.index = null;
this.flags = 0;
}
_endUnexpected() {
return new FormidableError(
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
errors.malformedMultipart,
400,
);
}
_flush(done) {
if (
(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');
done();
} else if (this.state !== STATE.END) {
done(this._endUnexpected());
} else {
done();
}
}
initWithBoundary(str) {
this.boundary = Buffer.from(`\r\n--${str}`);
this.lookbehind = Buffer.alloc(this.boundary.length + 8);
this.state = STATE.START;
this.boundaryChars = {};
for (let i = 0; i < this.boundary.length; i++) {
this.boundaryChars[this.boundary[i]] = true;
}
}
// eslint-disable-next-line max-params
_handleCallback(name, buf, start, end) {
if (start !== undefined && start === end) {
return;
}
this.push({ name, buffer: buf, start, end });
}
// eslint-disable-next-line max-statements
_transform(buffer, _, done) {
let i = 0;
let prevIndex = this.index;
let { index, state, flags } = this;
const { lookbehind, boundary, boundaryChars } = this;
const boundaryLength = boundary.length;
const boundaryEnd = boundaryLength - 1;
this.bufferLength = buffer.length;
let c = null;
let cl = null;
const setMark = (name, idx) => {
this[`${name}Mark`] = typeof idx === 'number' ? idx : i;
};
const clearMarkSymbol = (name) => {
delete this[`${name}Mark`];
};
const dataCallback = (name, shouldClear) => {
const markSymbol = `${name}Mark`;
if (!(markSymbol in this)) {
return;
}
if (!shouldClear) {
this._handleCallback(name, buffer, this[markSymbol], buffer.length);
setMark(name, 0);
} else {
this._handleCallback(name, buffer, this[markSymbol], i);
clearMarkSymbol(name);
}
};
for (i = 0; i < this.bufferLength; i++) {
c = buffer[i];
switch (state) {
case STATE.PARSER_UNINITIALIZED:
done(this._endUnexpected());
return;
case STATE.START:
index = 0;
state = STATE.START_BOUNDARY;
case STATE.START_BOUNDARY:
if (index === boundary.length - 2) {
if (c === HYPHEN) {
flags |= FBOUNDARY.LAST_BOUNDARY;
} else if (c !== CR) {
done(this._endUnexpected());
return;
}
index++;
break;
} else if (index - 1 === boundary.length - 2) {
if (flags & FBOUNDARY.LAST_BOUNDARY && c === HYPHEN) {
this._handleCallback('end');
state = STATE.END;
flags = 0;
} else if (!(flags & FBOUNDARY.LAST_BOUNDARY) && c === LF) {
index = 0;
this._handleCallback('partBegin');
state = STATE.HEADER_FIELD_START;
} else {
done(this._endUnexpected());
return;
}
break;
}
if (c !== boundary[index + 2]) {
index = -2;
}
if (c === boundary[index + 2]) {
index++;
}
break;
case STATE.HEADER_FIELD_START:
state = STATE.HEADER_FIELD;
setMark('headerField');
index = 0;
case STATE.HEADER_FIELD:
if (c === CR) {
clearMarkSymbol('headerField');
state = STATE.HEADERS_ALMOST_DONE;
break;
}
index++;
if (c === HYPHEN) {
break;
}
if (c === COLON) {
if (index === 1) {
// empty header field
done(this._endUnexpected());
return;
}
dataCallback('headerField', true);
state = STATE.HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
done(this._endUnexpected());
return;
}
break;
case STATE.HEADER_VALUE_START:
if (c === SPACE) {
break;
}
setMark('headerValue');
state = STATE.HEADER_VALUE;
case STATE.HEADER_VALUE:
if (c === CR) {
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;
}
state = STATE.HEADER_FIELD_START;
break;
case STATE.HEADERS_ALMOST_DONE:
if (c !== LF) {
done(this._endUnexpected());
return;
}
this._handleCallback('headersEnd');
state = STATE.PART_DATA_START;
break;
case STATE.PART_DATA_START:
state = STATE.PART_DATA;
setMark('partData');
case STATE.PART_DATA:
prevIndex = index;
if (index === 0) {
// boyer-moore derived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < this.bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundary.length) {
if (boundary[index] === c) {
if (index === 0) {
dataCallback('partData', true);
}
index++;
} else {
index = 0;
}
} else if (index === boundary.length) {
index++;
if (c === CR) {
// CR = part boundary
flags |= FBOUNDARY.PART_BOUNDARY;
} else if (c === HYPHEN) {
// HYPHEN = end boundary
flags |= FBOUNDARY.LAST_BOUNDARY;
} else {
index = 0;
}
} else if (index - 1 === boundary.length) {
if (flags & FBOUNDARY.PART_BOUNDARY) {
index = 0;
if (c === LF) {
// unset the PART_BOUNDARY flag
flags &= ~FBOUNDARY.PART_BOUNDARY;
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');
state = STATE.END;
flags = 0;
} else {
index = 0;
}
} else {
index = 0;
}
}
if (index > 0) {
// when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index - 1] = c;
} else if (prevIndex > 0) {
// if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
this._handleCallback('partData', lookbehind, 0, prevIndex);
prevIndex = 0;
setMark('partData');
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--;
}
break;
case STATE.END:
break;
default:
done(this._endUnexpected());
return;
}
}
dataCallback('headerField');
dataCallback('headerValue');
dataCallback('partData');
this.index = index;
this.state = state;
this.flags = flags;
done();
return this.bufferLength;
}
explain() {
return `state = ${MultipartParser.stateToString(this.state)}`;
}
}
// eslint-disable-next-line consistent-return
MultipartParser.stateToString = (stateNumber) => {
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const stateName in STATE) {
const number = STATE[stateName];
if (number === stateNumber) return stateName;
}
};
export default Object.assign(MultipartParser, { STATES });

View File

@ -0,0 +1,10 @@
import { PassThrough } from 'node:stream';
class OctetStreamParser extends PassThrough {
constructor(options = {}) {
super();
this.globalOptions = { ...options };
}
}
export default OctetStreamParser;

View File

@ -0,0 +1,33 @@
/* eslint-disable no-underscore-dangle */
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.bufferLength = 0;
}
_transform(buffer, encoding, callback) {
this.buffer += buffer.toString('ascii');
this.bufferLength = this.buffer.length;
callback();
}
_flush(callback) {
const fields = new URLSearchParams(this.buffer);
for (const [key, value] of fields) {
this.push({
key,
value,
});
}
this.buffer = '';
callback();
}
}
export default QuerystringParser;

View File

@ -0,0 +1,117 @@
// not used
/* eslint-disable no-underscore-dangle */
import { Transform } from 'node:stream';
import FormidableError, { maxFieldsSizeExceeded } from '../FormidableError.js';
const AMPERSAND = 38;
const EQUALS = 61;
class QuerystringParser extends Transform {
constructor(options = {}) {
super({ readableObjectMode: true });
const { maxFieldSize } = options;
this.maxFieldLength = maxFieldSize;
this.buffer = Buffer.from('');
this.fieldCount = 0;
this.sectionStart = 0;
this.key = '';
this.readingKey = true;
}
_transform(buffer, encoding, callback) {
let len = buffer.length;
if (this.buffer && this.buffer.length) {
// we have some data left over from the last write which we are in the middle of processing
len += this.buffer.length;
buffer = Buffer.concat([this.buffer, buffer], len);
}
for (let i = this.buffer.length || 0; i < len; i += 1) {
const c = buffer[i];
if (this.readingKey) {
// KEY, check for =
if (c === EQUALS) {
this.key = this.getSection(buffer, i);
this.readingKey = false;
this.sectionStart = i + 1;
} else if (c === AMPERSAND) {
// just key, no value. Prepare to read another key
this.emitField(this.getSection(buffer, i));
this.sectionStart = i + 1;
}
// VALUE, check for &
} else if (c === AMPERSAND) {
this.emitField(this.key, this.getSection(buffer, i));
this.sectionStart = i + 1;
}
if (
this.maxFieldLength &&
i - this.sectionStart === this.maxFieldLength
) {
callback(
new FormidableError(
`${
this.readingKey ? 'Key' : `Value for ${this.key}`
} longer than maxFieldLength`,
),
maxFieldsSizeExceeded,
413,
);
}
}
// Prepare the remaining key or value (from sectionStart to the end) for the next write() or for end()
len -= this.sectionStart;
if (len) {
// i.e. Unless the last character was a & or =
this.buffer = Buffer.from(this.buffer, 0, this.sectionStart);
} else this.buffer = null;
this.sectionStart = 0;
callback();
}
_flush(callback) {
// Emit the last field
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'));
}
} 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 = '';
callback();
}
getSection(buffer, i) {
if (i === this.sectionStart) return '';
return buffer.toString('ascii', this.sectionStart, i);
}
emitField(key, val) {
this.key = '';
this.readingKey = true;
this.push({ key, value: val || '' });
}
}
export default QuerystringParser;
// const q = new QuerystringParser({maxFieldSize: 100});
// (async function() {
// for await (const chunk of q) {
// console.log(chunk);
// }
// })();
// q.write("a=b&c=d")
// q.end()

View File

@ -0,0 +1,15 @@
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,
DummyParser,
MultipartParser,
OctetStreamParser,
OctetStreamParser as OctetstreamParser,
QueryStringParser,
QueryStringParser as QuerystringParser,
};

View File

@ -0,0 +1,6 @@
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 };

View File

@ -0,0 +1,39 @@
/* eslint-disable no-underscore-dangle */
import JSONParser from '../parsers/JSON.js';
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
// but this allows us to customize/test each plugin
/* istanbul ignore next */
const self = this || formidable;
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
// to test the plugin you can pass custom `this` context to it (and so `this.options`)
function init(_self, _opts) {
this.type = jsonType;
const parser = new JSONParser(this.options);
parser.on('data', (fields) => {
this.fields = fields;
});
parser.once('end', () => {
this.ended = true;
this._maybeEnd();
});
this._parser = parser;
}

View File

@ -0,0 +1,173 @@
/* 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';
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
// but this allows us to customize/test each plugin
/* istanbul ignore next */
const self = this || formidable;
// NOTE: we (currently) support both multipart/form-data and multipart/related
const multipart = /multipart/i.test(self.headers['content-type']);
if (multipart) {
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',
errors.missingMultipartBoundary,
400,
);
self._error(err);
}
}
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
// to test the plugin you can pass custom `this` context to it (and so `this.options`)
function createInitMultipart(boundary) {
return function initMultipart() {
this.type = multipartType;
const parser = new MultipartParser(this.options);
let headerField;
let headerValue;
let part;
parser.initWithBoundary(boundary);
// eslint-disable-next-line max-statements, consistent-return
parser.on('data', async ({ name, buffer, start, end }) => {
if (name === 'partBegin') {
part = new Stream();
part.readable = true;
part.headers = {};
part.name = null;
part.originalFilename = null;
part.mimetype = null;
part.transferEncoding = this.options.encoding;
part.transferBuffer = '';
headerField = '';
headerValue = '';
} else if (name === 'headerField') {
headerField += buffer.toString(this.options.encoding, start, end);
} else if (name === 'headerValue') {
headerValue += buffer.toString(this.options.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.originalFilename = this._getFileName(headerValue);
} else if (headerField === 'content-type') {
part.mimetype = 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':
case 'utf-8': {
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 bytes 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 FormidableError(
'unknown transfer-encoding',
errors.unknownTransferEncoding,
501,
),
);
}
this._parser.pause();
await this.onPart(part);
this._parser.resume();
} else if (name === 'end') {
this.ended = true;
this._maybeEnd();
}
});
this._parser = parser;
};
}

View File

@ -0,0 +1,84 @@
/* eslint-disable no-underscore-dangle */
import OctetStreamParser from '../parsers/OctetStream.js';
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
// but this allows us to customize/test each plugin
/* istanbul ignore next */
const self = this || formidable;
if (/octet-stream/i.test(self.headers['content-type'])) {
await 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
// 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 thisPart = {
originalFilename,
mimetype,
};
const newFilename = this._getNewName(thisPart);
const filepath = this._joinDirectoryName(newFilename);
const file = await this._newFile({
newFilename,
filepath,
originalFilename,
mimetype,
});
this.emit('fileBegin', originalFilename, file);
file.open();
this.openedFiles.push(file);
this._flushing += 1;
this._parser = new OctetStreamParser(this.options);
// 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);
}
});
return this;
}

View File

@ -0,0 +1,41 @@
/* eslint-disable no-underscore-dangle */
import QuerystringParser from '../parsers/Querystring.js';
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
// but this allows us to customize/test each plugin
/* istanbul ignore next */
const self = this || formidable;
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
// to test the plugin you can pass custom `this` context to it (and so `this.options`)
function init(_self, _opts) {
this.type = querystringType;
const parser = new QuerystringParser(this.options);
parser.on('data', ({ key, value }) => {
this.emit('field', key, value);
});
parser.once('end', () => {
this.ended = true;
this._maybeEnd();
});
this._parser = parser;
return this;
}