formidable
Advanced tools
Comparing version 2.0.0-canary.20200504.1 to 2.0.0-canary.20210330
@@ -0,19 +1,24 @@ | ||
# Changelog | ||
### Unreleased (`canary` & `dev` dist-tags) | ||
* Test only on Node.js >= v10. Support only Node LTS and latest ([#515](https://github.com/node-formidable/node-formidable/pull/515)) | ||
* stop using deprecated features ([#516](https://github.com/node-formidable/node-formidable/pull/516), [#472](https://github.com/node-formidable/node-formidable/issues/472), [#406](https://github.com/node-formidable/node-formidable/issues/406)) | ||
* throw error during data parsing ([#513](https://github.com/node-formidable/node-formidable/pull/513)) | ||
* Array support for fields and files ([#380](https://github.com/node-formidable/node-formidable/pull/380), [#340](https://github.com/node-formidable/node-formidable/pull/340), [#367](https://github.com/node-formidable/node-formidable/pull/367), [#33](https://github.com/node-formidable/node-formidable/issues/33), [#498](https://github.com/node-formidable/node-formidable/issues/498), [#280](https://github.com/node-formidable/node-formidable/issues/280), [#483](https://github.com/node-formidable/node-formidable/issues/483)) | ||
* feat: add options.filter ([#716](https://github.com/node-formidable/formidable/pull/716)) | ||
* feat: add code and httpCode to most errors ([#686](https://github.com/node-formidable/formidable/pull/686)) | ||
* rename: option.hash into option.hashAlgorithm ([#689](https://github.com/node-formidable/formidable/pull/689)) | ||
* rename: file.path into file.filepath ([#689](https://github.com/node-formidable/formidable/pull/689)) | ||
* rename: file.type into file.mimetype ([#689](https://github.com/node-formidable/formidable/pull/689)) | ||
* refactor: split file.name into file.newFilename and file.originalFilename ([#689](https://github.com/node-formidable/formidable/pull/689)) | ||
* feat: prevent directory traversal attacks by default ([#689](https://github.com/node-formidable/formidable/pull/689)) | ||
* meta: stop including test files in npm ([7003c](https://github.com/node-formidable/formidable/commit/7003cd6133f90c384081accb51743688d5e1f4be)) | ||
* fix: handle invalid filenames ([d0a34](https://github.com/node-formidable/formidable/commit/d0a3484b048b8c177e62d66aecb03f5928f7a857)) | ||
* feat: add fileWriteStreamHandler option | ||
* feat: add allowEmptyFiles and minFileSize options | ||
* feat: Array support for fields and files ([#380](https://github.com/node-formidable/node-formidable/pull/380), [#340](https://github.com/node-formidable/node-formidable/pull/340), [#367](https://github.com/node-formidable/node-formidable/pull/367), [#33](https://github.com/node-formidable/node-formidable/issues/33), [#498](https://github.com/node-formidable/node-formidable/issues/498), [#280](https://github.com/node-formidable/node-formidable/issues/280), [#483](https://github.com/node-formidable/node-formidable/issues/483)) | ||
* possible partial fix of [#386](https://github.com/node-formidable/node-formidable/pull/386) with #380 (need tests and better implementation) | ||
* use hasOwnProperty in check against files/fields ([#522](https://github.com/node-formidable/node-formidable/pull/522)) | ||
* do not promote `IncomingForm` and add `exports.default` ([#529](https://github.com/node-formidable/node-formidable/pull/529)) | ||
* Improve examples and tests ([#523](https://github.com/node-formidable/node-formidable/pull/523)) | ||
* First step of Code quality improvements ([#525](https://github.com/node-formidable/node-formidable/pull/525)) | ||
* refactor: use hasOwnProperty in check against files/fields ([#522](https://github.com/node-formidable/node-formidable/pull/522)) | ||
* meta: do not promote `IncomingForm` and add `exports.default` ([#529](https://github.com/node-formidable/node-formidable/pull/529)) | ||
* meta: Improve examples and tests ([#523](https://github.com/node-formidable/node-formidable/pull/523)) | ||
* refactor: First step of Code quality improvements ([#525](https://github.com/node-formidable/node-formidable/pull/525)) | ||
* chore(funding): remove patreon & add npm funding field ([#525](https://github.com/node-formidable/node-formidable/pull/532) | ||
* feat: use Modern Streams API ([#531](https://github.com/node-formidable/node-formidable/pull/531)) | ||
* fix: remove gently hijack and tests ([#539](https://github.com/node-formidable/node-formidable/pull/539)) | ||
* docs: Clarify supported hash algorithms ([#537](https://github.com/node-formidable/node-formidable/pull/537)) | ||
* feat: better tests, add Airbnb + Prettier ([#542](https://github.com/node-formidable/node-formidable/pull/542)) | ||
* fix(incomingForm): better detection of fields vs files | ||
* fix: resolves [#128](https://github.com/node-formidable/node-formidable/pull/128) | ||
* fix: urlencoded parsing to emit end [#543](https://github.com/node-formidable/node-formidable/pull/543), introduced in [#531](https://github.com/node-formidable/node-formidable/pull/531) | ||
@@ -24,8 +29,6 @@ * fix(tests): include multipart and qs parser unit tests, part of [#415](https://github.com/node-formidable/node-formidable/issues/415) | ||
* feat: introduce Plugins API, fix silent failing tests ([#545](https://github.com/node-formidable/node-formidable/pull/545), [#391](https://github.com/node-formidable/node-formidable/pull/391), [#407](https://github.com/node-formidable/node-formidable/pull/407), [#386](https://github.com/node-formidable/node-formidable/pull/386), [#374](https://github.com/node-formidable/node-formidable/pull/374), [#521](https://github.com/node-formidable/node-formidable/pull/521), [#267](https://github.com/node-formidable/node-formidable/pull/267)) | ||
* respect form hash option on incoming octect/stream requests ([#407](https://github.com/node-formidable/node-formidable/pull/407)) | ||
* fix: exposing file writable stream errors ([#520](https://github.com/node-formidable/node-formidable/pull/520), [#316](https://github.com/node-formidable/node-formidable/pull/316), [#469](https://github.com/node-formidable/node-formidable/pull/469), [#470](https://github.com/node-formidable/node-formidable/pull/470)) | ||
* feat: custom file (re)naming, thru options.filename ([#591](https://github.com/node-formidable/node-formidable/pull/591), [#84](https://github.com/node-formidable/node-formidable/issues/84), [#86](https://github.com/node-formidable/node-formidable/issues/86), [#94](https://github.com/node-formidable/node-formidable/issues/94), [#154](https://github.com/node-formidable/node-formidable/issues/154), [#158](https://github.com/node-formidable/node-formidable/issues/158), [#488](https://github.com/node-formidable/node-formidable/issues/488), [#595](https://github.com/node-formidable/node-formidable/issues/595)) | ||
* fix: make opts.filename from #591 work with opts.keepExtensions ([#597](https://github.com/node-formidable/node-formidable/pull/597)) | ||
* fix: better handling of nested arrays when options.multiples ([#621](https://github.com/node-formidable/node-formidable/pull/621)) | ||
* fix: a regression causing cyrillic to fail ([#624](https://github.com/node-formidable/node-formidable/pull/624), [#623](https://github.com/node-formidable/node-formidable/issues/623)) | ||
@@ -32,0 +35,0 @@ ### v1.2.1 (2018-03-20) |
{ | ||
"name": "formidable", | ||
"version": "2.0.0-canary.20200504.1", | ||
"version": "2.0.0-canary.20210330", | ||
"license": "MIT", | ||
@@ -12,3 +12,5 @@ "description": "A node.js module for parsing form data, especially file uploads.", | ||
"src", | ||
"test" | ||
"CHANGELOG.md", | ||
"LICENSE", | ||
"README.md" | ||
], | ||
@@ -29,5 +31,5 @@ "publishConfig": { | ||
"pretest": "del-cli ./test/tmp && make-dir ./test/tmp", | ||
"test": "node test/run.js", | ||
"test": "jest --coverage", | ||
"pretest:ci": "yarn run pretest", | ||
"test:ci": "nyc node test/run.js", | ||
"test:ci": "nyc jest --coverage", | ||
"test:jest": "jest --coverage" | ||
@@ -39,3 +41,3 @@ }, | ||
"once": "1.4.0", | ||
"qs": "^6.9.3" | ||
"qs": "6.9.3" | ||
}, | ||
@@ -56,3 +58,3 @@ "devDependencies": { | ||
"koa": "2.11.0", | ||
"lint-staged": "10.1.7", | ||
"lint-staged": "10.2.7", | ||
"make-dir-cli": "2.0.0", | ||
@@ -63,6 +65,7 @@ "nyc": "15.0.1", | ||
"request": "2.88.2", | ||
"supertest": "4.0.2", | ||
"urun": "0.0.8", | ||
"utest": "0.0.8" | ||
"supertest": "4.0.2" | ||
}, | ||
"jest": { | ||
"verbose": true | ||
}, | ||
"husky": { | ||
@@ -92,3 +95,13 @@ "hooks": { | ||
] | ||
} | ||
}, | ||
"keywords": [ | ||
"multipart", | ||
"form", | ||
"data", | ||
"querystring", | ||
"www", | ||
"json", | ||
"ulpoad", | ||
"file" | ||
] | ||
} |
164
README.md
@@ -67,3 +67,4 @@ <p align="center"> | ||
- [Fast (~900-2500 mb/sec)](#benchmarks) & streaming multipart parser | ||
- Automatically writing file uploads to disk (soon optionally) | ||
- Automatically writing file uploads to disk (optional, see | ||
[`options.fileWriteStreamHandler`](#options)) | ||
- [Plugins API](#useplugin-plugin) - allowing custom parsers and plugins | ||
@@ -116,3 +117,8 @@ - Low memory footprint | ||
form.parse(req, (err, fields, files) => { | ||
res.writeHead(200, { 'content-type': 'application/json' }); | ||
if (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)); | ||
@@ -125,3 +131,3 @@ }); | ||
// show a file upload form | ||
res.writeHead(200, { 'content-type': 'text/html' }); | ||
res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
res.end(` | ||
@@ -211,3 +217,3 @@ <h2>With Node.js <code>"http"</code> module</h2> | ||
// not very elegant, but that's for now if you don't want touse `koa-better-body` | ||
// not very elegant, but that's for now if you don't want to use `koa-better-body` | ||
// or other middlewares. | ||
@@ -317,4 +323,4 @@ await new Promise((resolve, reject) => { | ||
See it's defaults in [src/Formidable.js](./src/Formidable.js#L14-L22) (the | ||
`DEFAULT_OPTIONS` constant). | ||
See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js) | ||
(the `DEFAULT_OPTIONS` constant). | ||
@@ -324,16 +330,29 @@ - `options.encoding` **{string}** - default `'utf-8'`; sets encoding for | ||
- `options.uploadDir` **{string}** - default `os.tmpdir()`; the directory for | ||
placing file uploads in. You can move them later by using `fs.rename()` | ||
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 `true`; allow upload empty | ||
files | ||
- `options.minFileSize` **{number}** - default `1` (1byte); the minium size of | ||
uploaded file. | ||
- `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb); | ||
limit the size of uploaded file. | ||
- `options.maxFields` **{number}** - default `1000`; limit the number of fields | ||
that the Querystring parser will decode, set 0 for unlimited | ||
- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set 0 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.hash` **{boolean}** - default `false`; include checksums calculated | ||
- `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.multiples` **{boolean}** - default `false`; when you call the | ||
@@ -344,3 +363,11 @@ `.parse` method, the `files` argument (of the callback) will contain arrays of | ||
fields that have names ending with '[]'. | ||
- `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. | ||
#### `options.filename` **{function}** function (name, ext, part, form) -> string | ||
_**Note:** If this size of combined fields, or size of some file is exceeded, an | ||
@@ -359,2 +386,16 @@ `'error'` event is fired._ | ||
#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean | ||
**Note:** use an outside variable to cancel all uploads upon the first error | ||
```js | ||
const options = { | ||
filter: function ({name, originalFilename, mimetype}) { | ||
// keep only images | ||
return mimetype && mimetype.includes("image"); | ||
} | ||
}; | ||
``` | ||
### .parse(request, callback) | ||
@@ -381,2 +422,37 @@ | ||
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 | ||
@@ -393,8 +469,8 @@ `data` listener, so you can do whatever you choose there, based on whether its | ||
form.on('fileBegin', (filename, file) => { | ||
form.emit('data', { name: 'fileBegin', filename, value: file }); | ||
form.on('fileBegin', (formname, file) => { | ||
form.emit('data', { name: 'fileBegin', formname, value: file }); | ||
}); | ||
form.on('file', (filename, file) => { | ||
form.emit('data', { name: 'file', key: filename, value: file }); | ||
form.on('file', (formname, file) => { | ||
form.emit('data', { name: 'file', formname, value: file }); | ||
}); | ||
@@ -411,3 +487,3 @@ | ||
// If you want to customize whatever you want... | ||
form.on('data', ({ name, key, value, buffer, start, end, ...more }) => { | ||
form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => { | ||
if (name === 'partBegin') { | ||
@@ -430,6 +506,6 @@ } | ||
if (name === 'file') { | ||
console.log('file:', key, value); | ||
console.log('file:', formname, value); | ||
} | ||
if (name === 'fileBegin') { | ||
console.log('fileBegin:', key, value); | ||
console.log('fileBegin:', formname, value); | ||
} | ||
@@ -489,3 +565,3 @@ }); | ||
const form = new Formidable({ | ||
hash: 'sha1', | ||
hashAlgorithm: 'sha1', | ||
enabledPlugins: ['octetstream', 'querystring', 'json'], | ||
@@ -527,5 +603,5 @@ }); | ||
// let formidable handle only non-file parts | ||
if (part.filename === '' || !part.mime) { | ||
if (part.originalFilename === '' || !part.mimetype) { | ||
// used internally, please do not override! | ||
form.handlePart(part); | ||
form._handlePart(part); | ||
} | ||
@@ -546,16 +622,20 @@ }; | ||
// case you are unhappy with the way formidable generates a temporary path for your files. | ||
file.path: string; | ||
file.filepath: string; | ||
// The name this file had according to the uploading client. | ||
file.name: string | null; | ||
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.type: string | null; | ||
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.lastModifiedDate: Date | null; | ||
file.mtime: Date | null; | ||
// If `options.hash` calculation was set, you can read the hex digest out of this var. | ||
file.hash: string | 'sha1' | 'md5' | 'sha256' | 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; | ||
} | ||
@@ -596,3 +676,10 @@ ``` | ||
```js | ||
form.on('fileBegin', (name, file) => {}); | ||
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 pathnme as per options.uploadDir and options.filename | ||
// file.filepath = CUSTOM_PATH // to change the final path | ||
}); | ||
``` | ||
@@ -606,3 +693,7 @@ | ||
```js | ||
form.on('file', (name, file) => {}); | ||
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 | ||
}); | ||
``` | ||
@@ -616,2 +707,4 @@ | ||
May have `error.httpCode` and `error.code` attached. | ||
```js | ||
@@ -653,4 +746,4 @@ form.on('error', (err) => {}); | ||
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.md). Pull requests are welcome! | ||
a bug or add a new feature, please check our [Contributing | ||
Guide][contributing-url]. Pull requests are welcome! | ||
@@ -694,2 +787,9 @@ Thanks goes to these wonderful people | ||
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 | ||
@@ -733,4 +833,4 @@ | ||
[contributing-url]: https://github.com/node-formidable/formidable/blob/master/CONTRIBUTING.md | ||
[code_of_conduct-url]: https://github.com/node-formidable/formidable/blob/master/CODE_OF_CONDUCT.md | ||
[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 | ||
@@ -737,0 +837,0 @@ [open-issue-url]: https://github.com/node-formidable/formidable/issues/new |
@@ -7,3 +7,2 @@ /* eslint-disable class-methods-use-this */ | ||
const os = require('os'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
@@ -22,14 +21,25 @@ const hexoid = require('hexoid'); | ||
maxFileSize: 200 * 1024 * 1024, | ||
minFileSize: 1, | ||
allowEmptyFiles: true, | ||
keepExtensions: false, | ||
encoding: 'utf-8', | ||
hash: false, | ||
hashAlgorithm: false, | ||
uploadDir: os.tmpdir(), | ||
multiples: false, | ||
enabledPlugins: ['octetstream', 'querystring', 'multipart', 'json'], | ||
fileWriteStreamHandler: null, | ||
defaultInvalidName: 'invalid-name', | ||
filter: function () { | ||
return true; | ||
}, | ||
}; | ||
const File = require('./File'); | ||
const PersistentFile = require('./PersistentFile'); | ||
const VolatileFile = require('./VolatileFile'); | ||
const DummyParser = require('./parsers/Dummy'); | ||
const MultipartParser = require('./parsers/Multipart'); | ||
const errors = require('./FormidableError.js'); | ||
const { FormidableError } = errors; | ||
function hasOwnProp(obj, key) { | ||
@@ -45,3 +55,5 @@ return Object.prototype.hasOwnProperty.call(obj, key); | ||
const dir = this.options.uploadDir || this.options.uploaddir || os.tmpdir(); | ||
const dir = path.resolve( | ||
this.options.uploadDir || this.options.uploaddir || os.tmpdir(), | ||
); | ||
@@ -63,14 +75,4 @@ this.uploaddir = dir; | ||
const hasRename = typeof this.options.filename === 'function'; | ||
this._setUpRename(); | ||
if (this.options.keepExtensions === true && hasRename) { | ||
this._rename = (part) => { | ||
const resultFilepath = this.options.filename.call(this, part, this); | ||
return this._uploadPath(part, resultFilepath); | ||
}; | ||
} else { | ||
this._rename = (part) => this._uploadPath(part); | ||
} | ||
this._flushing = 0; | ||
@@ -82,9 +84,10 @@ this._fieldsSize = 0; | ||
const enabledPlugins = [] | ||
this.options.enabledPlugins = [] | ||
.concat(this.options.enabledPlugins) | ||
.filter(Boolean); | ||
if (enabledPlugins.length === 0) { | ||
throw new Error( | ||
if (this.options.enabledPlugins.length === 0) { | ||
throw new FormidableError( | ||
'expect at least 1 enabled builtin plugin, see options.enabledPlugins', | ||
errors.missingPlugin, | ||
); | ||
@@ -98,2 +101,4 @@ } | ||
}); | ||
this._setUpMaxFields(); | ||
} | ||
@@ -103,3 +108,6 @@ | ||
if (typeof plugin !== 'function') { | ||
throw new Error('.use: expect `plugin` to be a function'); | ||
throw new FormidableError( | ||
'.use: expect `plugin` to be a function', | ||
errors.pluginFunction, | ||
); | ||
} | ||
@@ -146,7 +154,12 @@ this._plugins.push(plugin.bind(this)); | ||
const files = {}; | ||
this.on('field', (name, value) => { | ||
if (this.options.multiples) { | ||
let mObj = { [name] : value }; | ||
mockFields = mockFields + '&' + qs.stringify(mObj); | ||
if ( | ||
this.options.multiples && | ||
(this.type === 'multipart' || this.type === 'urlencoded') | ||
) { | ||
const mObj = { [name]: value }; | ||
mockFields = mockFields | ||
? `${mockFields}&${qs.stringify(mObj)}` | ||
: `${qs.stringify(mObj)}`; | ||
} else { | ||
@@ -192,3 +205,3 @@ fields[name] = value; | ||
this.emit('aborted'); | ||
this._error(new Error('Request aborted')); | ||
this._error(new FormidableError('Request aborted', errors.aborted)); | ||
}) | ||
@@ -221,3 +234,9 @@ .on('data', (buffer) => { | ||
if (!this._parser) { | ||
this._error(new Error('not parser found')); | ||
this._error( | ||
new FormidableError( | ||
'no parser found', | ||
errors.noParser, | ||
415, // Unsupported Media Type | ||
), | ||
); | ||
return; | ||
@@ -236,3 +255,5 @@ } | ||
if (!this._parser) { | ||
this._error(new Error('uninitialized parser')); | ||
this._error( | ||
new FormidableError('uninitialized parser', errors.uninitializedParser), | ||
); | ||
return null; | ||
@@ -265,8 +286,13 @@ } | ||
_handlePart(part) { | ||
if (part.filename && typeof part.filename !== 'string') { | ||
this._error(new Error(`the part.filename should be string when exists`)); | ||
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.filename. | ||
// This MUST check exactly for undefined. You can not change it to !part.originalFilename. | ||
@@ -279,7 +305,9 @@ // todo: uncomment when switch tests to Jest | ||
// and such thing because code style | ||
// ? NOTE(@tunnckocore): or even better, if there is no mime, then it's for sure a field | ||
// ? NOTE(@tunnckocore): filename is an empty string when a field? | ||
if (!part.mime) { | ||
// ? 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(this.options.encoding); | ||
const decoder = new StringDecoder( | ||
part.transferEncoding || this.options.encoding, | ||
); | ||
@@ -290,4 +318,6 @@ part.on('data', (buffer) => { | ||
this._error( | ||
new Error( | ||
new FormidableError( | ||
`options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`, | ||
errors.maxFieldsSizeExceeded, | ||
413, // Payload Too Large | ||
), | ||
@@ -306,9 +336,15 @@ ); | ||
if (!this.options.filter(part)) { | ||
return; | ||
} | ||
this._flushing += 1; | ||
const file = new File({ | ||
path: this._rename(part), | ||
name: part.filename, | ||
type: part.mime, | ||
hash: this.options.hash, | ||
const newFilename = this._getNewName(part); | ||
const filepath = this._joinDirectoryName(newFilename); | ||
const file = this._newFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename: part.originalFilename, | ||
mimetype: part.mimetype, | ||
}); | ||
@@ -325,6 +361,18 @@ file.on('error', (err) => { | ||
this._fileSize += buffer.length; | ||
if (this._fileSize < this.options.minFileSize) { | ||
this._error( | ||
new FormidableError( | ||
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${this._fileSize} bytes of file data`, | ||
errors.smallerThanMinFileSize, | ||
400, | ||
), | ||
); | ||
return; | ||
} | ||
if (this._fileSize > this.options.maxFileSize) { | ||
this._error( | ||
new Error( | ||
new FormidableError( | ||
`options.maxFileSize (${this.options.maxFileSize} bytes) exceeded, received ${this._fileSize} bytes of file data`, | ||
errors.biggerThanMaxFileSize, | ||
413, | ||
), | ||
@@ -344,2 +392,13 @@ ); | ||
part.on('end', () => { | ||
if (!this.options.allowEmptyFiles && this._fileSize === 0) { | ||
this._error( | ||
new FormidableError( | ||
`options.allowEmptyFiles is false, file size should be greather than 0`, | ||
errors.noEmptyFiles, | ||
400, | ||
), | ||
); | ||
return; | ||
} | ||
file.end(() => { | ||
@@ -361,3 +420,9 @@ this._flushing -= 1; | ||
if (!this.headers['content-type']) { | ||
this._error(new Error('bad content-type header, no content-type')); | ||
this._error( | ||
new FormidableError( | ||
'bad content-type header, no content-type', | ||
errors.missingContentType, | ||
400, | ||
), | ||
); | ||
return; | ||
@@ -380,4 +445,6 @@ } | ||
// there is no other better way, except a handle through options | ||
const error = new Error( | ||
const error = new FormidableError( | ||
`plugin on index ${idx} failed with: ${err.message}`, | ||
errors.pluginFailed, | ||
500, | ||
); | ||
@@ -421,4 +488,3 @@ error.idx = idx; | ||
this.openedFiles.forEach((file) => { | ||
file._writeStream.destroy(); | ||
setTimeout(fs.unlink, 0, file.path, () => {}); | ||
file.destroy(); | ||
}); | ||
@@ -445,2 +511,21 @@ } | ||
_newFile({ filepath, originalFilename, mimetype, newFilename }) { | ||
return this.options.fileWriteStreamHandler | ||
? new VolatileFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
createFileWriteStream: this.options.fileWriteStreamHandler, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}) | ||
: new PersistentFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}); | ||
} | ||
_getFileName(headerValue) { | ||
@@ -454,12 +539,16 @@ // matches either a quoted-string or a token (RFC 2616 section 19.5.1) | ||
const match = m[2] || m[3] || ''; | ||
let filename = match.substr(match.lastIndexOf('\\') + 1); | ||
filename = filename.replace(/%22/g, '"'); | ||
filename = filename.replace(/&#([\d]{4});/g, (_, code) => | ||
let originalFilename = match.substr(match.lastIndexOf('\\') + 1); | ||
originalFilename = originalFilename.replace(/%22/g, '"'); | ||
originalFilename = originalFilename.replace(/&#([\d]{4});/g, (_, code) => | ||
String.fromCharCode(code), | ||
); | ||
return filename; | ||
return originalFilename; | ||
} | ||
_getExtension(str) { | ||
if (!str) { | ||
return ''; | ||
} | ||
const basename = path.basename(str); | ||
@@ -477,13 +566,62 @@ const firstDot = basename.indexOf('.'); | ||
_uploadPath(part, fp) { | ||
const name = fp || `${this.uploadDir}${path.sep}${toHexoId()}`; | ||
if (part && this.options.keepExtensions) { | ||
const filename = typeof part === 'string' ? part : part.filename; | ||
return `${name}${this._getExtension(filename)}`; | ||
_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 name; | ||
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 !== 0) { | ||
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, | ||
), | ||
); | ||
} | ||
}); | ||
} | ||
} | ||
_maybeEnd() { | ||
@@ -490,0 +628,0 @@ // console.log('ended', this.ended); |
'use strict'; | ||
const File = require('./File'); | ||
const PersistentFile = require('./PersistentFile'); | ||
const VolatileFile = require('./VolatileFile'); | ||
const Formidable = require('./Formidable'); | ||
const FormidableError = require('./FormidableError'); | ||
@@ -14,3 +16,6 @@ const plugins = require('./plugins/index'); | ||
module.exports = Object.assign(formidable, { | ||
File, | ||
errors: FormidableError, | ||
File: PersistentFile, | ||
PersistentFile, | ||
VolatileFile, | ||
Formidable, | ||
@@ -17,0 +22,0 @@ formidable, |
@@ -0,0 +0,0 @@ /* eslint-disable no-underscore-dangle */ |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ /* eslint-disable no-underscore-dangle */ |
@@ -9,3 +9,6 @@ /* eslint-disable no-fallthrough */ | ||
const { Transform } = require('stream'); | ||
const errors = require('../FormidableError.js'); | ||
const { FormidableError } = errors; | ||
let s = 0; | ||
@@ -63,3 +66,3 @@ const STATE = { | ||
_final(done) { | ||
_flush(done) { | ||
if ( | ||
@@ -74,4 +77,6 @@ (this.state === STATE.HEADER_FIELD_START && this.index === 0) || | ||
done( | ||
new Error( | ||
new FormidableError( | ||
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`, | ||
errors.malformedMultipart, | ||
400, | ||
), | ||
@@ -78,0 +83,0 @@ ); |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -14,3 +14,2 @@ /* eslint-disable no-underscore-dangle */ | ||
this.globalOptions = { ...options }; | ||
this.maxKeys = this.globalOptions.maxFields; | ||
this.buffer = ''; | ||
@@ -27,5 +26,3 @@ this.bufferLength = 0; | ||
_flush(callback) { | ||
const fields = querystring.parse(this.buffer, '&', '=', { | ||
maxKeys: this.maxKeys, | ||
}); | ||
const fields = querystring.parse(this.buffer, '&', '='); | ||
// eslint-disable-next-line no-restricted-syntax, guard-for-in | ||
@@ -32,0 +29,0 @@ for (const key in fields) { |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ /* eslint-disable no-underscore-dangle */ |
@@ -7,3 +7,6 @@ /* eslint-disable no-underscore-dangle */ | ||
const MultipartParser = require('../parsers/Multipart'); | ||
const errors = require('../FormidableError.js'); | ||
const { FormidableError } = errors; | ||
// the `options` is also available through the `options` / `formidable.options` | ||
@@ -28,3 +31,7 @@ module.exports = function plugin(formidable, options) { | ||
} else { | ||
const err = new Error('bad content-type header, no multipart boundary'); | ||
const err = new FormidableError( | ||
'bad content-type header, no multipart boundary', | ||
errors.missingMultipartBoundary, | ||
400, | ||
); | ||
self._error(err); | ||
@@ -56,6 +63,6 @@ } | ||
part.name = null; | ||
part.filename = null; | ||
part.mime = null; | ||
part.originalFilename = null; | ||
part.mimetype = null; | ||
part.transferEncoding = 'binary'; | ||
part.transferEncoding = this.options.encoding; | ||
part.transferBuffer = ''; | ||
@@ -83,5 +90,5 @@ | ||
part.filename = this._getFileName(headerValue); | ||
part.originalFilename = this._getFileName(headerValue); | ||
} else if (headerField === 'content-type') { | ||
part.mime = headerValue; | ||
part.mimetype = headerValue; | ||
} else if (headerField === 'content-transfer-encoding') { | ||
@@ -97,3 +104,4 @@ part.transferEncoding = headerValue.toLowerCase(); | ||
case '7bit': | ||
case '8bit': { | ||
case '8bit': | ||
case 'utf-8': { | ||
const dataPropagation = (ctx) => { | ||
@@ -152,3 +160,9 @@ if (ctx.name === 'partData') { | ||
default: | ||
return this._error(new Error('unknown transfer-encoding')); | ||
return this._error( | ||
new FormidableError( | ||
'unknown transfer-encoding', | ||
errors.unknownTransferEncoding, | ||
501, | ||
), | ||
); | ||
} | ||
@@ -155,0 +169,0 @@ |
@@ -5,3 +5,2 @@ /* eslint-disable no-underscore-dangle */ | ||
const File = require('../File'); | ||
const OctetStreamParser = require('../parsers/OctetStream'); | ||
@@ -29,13 +28,19 @@ | ||
this.type = 'octet-stream'; | ||
const filename = this.headers['x-file-name']; | ||
const mime = this.headers['content-type']; | ||
const originalFilename = this.headers['x-file-name']; | ||
const mimetype = this.headers['content-type']; | ||
const file = new File({ | ||
path: this._uploadPath(filename), | ||
name: filename, | ||
type: mime, | ||
hash: this.options.hash, | ||
const thisPart = { | ||
originalFilename, | ||
mimetype, | ||
}; | ||
const newFilename = this._getNewName(thisPart); | ||
const filepath = this._joinDirectoryName(newFilename); | ||
const file = this._newFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
}); | ||
this.emit('fileBegin', filename, file); | ||
this.emit('fileBegin', originalFilename, file); | ||
file.open(); | ||
@@ -42,0 +47,0 @@ this.openedFiles.push(file); |
@@ -0,0 +0,0 @@ /* eslint-disable no-underscore-dangle */ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
20
829
3
1
97625
21
1547
+ Addedqs@6.9.3(transitive)
- Removedcall-bind@1.0.7(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removedes-define-property@1.0.0(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.4(transitive)
- Removedgopd@1.0.1(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-proto@1.0.3(transitive)
- Removedhas-symbols@1.0.3(transitive)
- Removedhasown@2.0.2(transitive)
- Removedobject-inspect@1.13.2(transitive)
- Removedqs@6.13.0(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedside-channel@1.0.6(transitive)
Updatedqs@6.9.3