formidable
Advanced tools
Comparing version 2.1.2 to 3.5.1
{ | ||
"name": "formidable", | ||
"version": "2.1.2", | ||
"version": "3.5.1", | ||
"license": "MIT", | ||
@@ -9,12 +9,45 @@ "description": "A node.js module for parsing form data, especially file uploads.", | ||
"repository": "node-formidable/formidable", | ||
"main": "./src/index.js", | ||
"type": "module", | ||
"main": "./dist/index.cjs", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"default": "./src/index.js" | ||
}, | ||
"require": { | ||
"default": "./dist/index.cjs" | ||
}, | ||
"default": "./dist/index.cjs" | ||
}, | ||
"./src/helpers/*.js": { | ||
"import": { | ||
"default": "./src/helpers/*.js" | ||
}, | ||
"require": { | ||
"default": "./dist/helpers/*.cjs" | ||
} | ||
}, | ||
"./src/parsers/*.js": { | ||
"import": { | ||
"default": "./src/parsers/*.js" | ||
}, | ||
"require": { | ||
"default": "./dist/index.cjs" | ||
} | ||
} | ||
}, | ||
"files": [ | ||
"src" | ||
"src", | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public", | ||
"tag": "v2-latest" | ||
"tag": "latest" | ||
}, | ||
"scripts": { | ||
"build-package": "rollup --config ./tool/rollup.config.js", | ||
"prepublishOnly": "npm run build-package", | ||
"bench": "node benchmark", | ||
"bench2prep": "node benchmark/server.js", | ||
"bench2": "bombardier --body-file=\"./README.md\" --method=POST --duration=10s --connections=100 http://localhost:3000/api/upload", | ||
"fmt": "yarn run fmt:prepare '**/*'", | ||
@@ -28,6 +61,8 @@ "fmt:prepare": "prettier --write", | ||
"pretest": "del-cli ./test/tmp && make-dir ./test/tmp", | ||
"test": "jest --coverage", | ||
"test-specific": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/standalone/keep-alive-error.test.js", | ||
"test": "npm run test-jest && npm run test-node", | ||
"test-jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=test/ --coverage", | ||
"test-node": "node --test test-node/", | ||
"pretest:ci": "yarn run pretest", | ||
"test:ci": "nyc jest --coverage", | ||
"test:jest": "jest --coverage" | ||
"test:ci": "node --experimental-vm-modules node_modules/.bin/nyc jest --testPathPattern=test/ --coverage && node --experimental-vm-modules node_modules/.bin/nyc node --test test-node/" | ||
}, | ||
@@ -37,4 +72,3 @@ "dependencies": { | ||
"hexoid": "^1.0.0", | ||
"once": "^1.4.0", | ||
"qs": "^6.11.0" | ||
"once": "^1.4.0" | ||
}, | ||
@@ -44,2 +78,5 @@ "devDependencies": { | ||
"@commitlint/config-conventional": "8.3.4", | ||
"@rollup/plugin-commonjs": "^25.0.2", | ||
"@rollup/plugin-node-resolve": "^15.1.0", | ||
"@sindresorhus/slugify": "^2.1.0", | ||
"@tunnckocore/prettier-config": "1.3.8", | ||
@@ -53,12 +90,13 @@ "del-cli": "3.0.0", | ||
"express": "4.17.1", | ||
"formdata-polyfill": "^4.0.10", | ||
"husky": "4.2.5", | ||
"jest": "25.4.0", | ||
"jest": "27.2.4", | ||
"koa": "2.11.0", | ||
"lint-staged": "10.2.7", | ||
"make-dir-cli": "2.0.0", | ||
"nyc": "15.0.1", | ||
"nyc": "15.1.0", | ||
"prettier": "2.0.5", | ||
"prettier-plugin-pkgjson": "0.2.8", | ||
"request": "2.88.2", | ||
"supertest": "4.0.2" | ||
"rollup": "^3.25.3", | ||
"supertest": "6.1.6" | ||
}, | ||
@@ -68,12 +106,2 @@ "jest": { | ||
}, | ||
"keywords": [ | ||
"multipart", | ||
"form", | ||
"data", | ||
"querystring", | ||
"www", | ||
"json", | ||
"ulpoad", | ||
"file" | ||
], | ||
"husky": { | ||
@@ -103,3 +131,14 @@ "hooks": { | ||
] | ||
} | ||
}, | ||
"packageManager": "yarn@1.22.17", | ||
"keywords": [ | ||
"multipart", | ||
"form", | ||
"data", | ||
"querystring", | ||
"www", | ||
"json", | ||
"ulpoad", | ||
"file" | ||
] | ||
} |
180
README.md
@@ -23,3 +23,3 @@ <p align="center"> | ||
[![Minimum Required Nodejs][nodejs-img]][npmv-url] | ||
[![Tidelift Subcsription][tidelift-img]][tidelift-url] | ||
[![Tidelift Subscription][tidelift-img]][tidelift-url] | ||
[![Buy me a Kofi][kofi-img]][kofi-url] | ||
@@ -72,2 +72,4 @@ [![Renovate App Status][renovateapp-img]][renovateapp-url] | ||
This package is a dual ESM/commonjs package. | ||
This project requires `Node.js >= 10.13`. Install it using | ||
@@ -80,13 +82,12 @@ [yarn](https://yarnpkg.com) or [npm](https://npmjs.com).<br /> _We highly | ||
```sh | ||
``` | ||
# v2 | ||
npm install formidable | ||
npm install formidable@latest | ||
npm install formidable@v2 | ||
# or v3 | ||
# v3 | ||
npm install formidable | ||
npm install formidable@v3 | ||
``` | ||
_**Note:** In near future v3 will be published on the `latest` NPM dist-tag. Future not ready releases will continue to be published on `canary` dist-tag._ | ||
_**Note:** Future not ready releases will be published on `*-next` dist-tags for the corresponding version._ | ||
@@ -104,20 +105,25 @@ | ||
```js | ||
const http = require('http'); | ||
const formidable = require('formidable'); | ||
import http from 'node:http'; | ||
import formidable, {errors as formidableErrors} from 'formidable'; | ||
const server = http.createServer((req, res) => { | ||
const server = http.createServer(async (req, res) => { | ||
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { | ||
// parse a file upload | ||
const form = formidable({ multiples: true }); | ||
const form = formidable({}); | ||
let fields; | ||
let files; | ||
try { | ||
[fields, files] = await form.parse(req); | ||
} catch (err) { | ||
// example to check for a very specific error | ||
if (err.code === formidableErrors.maxFieldsExceeded) { | ||
form.parse(req, (err, fields, files) => { | ||
if (err) { | ||
} | ||
console.error(err); | ||
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' }); | ||
res.end(String(err)); | ||
return; | ||
} | ||
res.writeHead(200, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ fields, files }, null, 2)); | ||
}); | ||
} | ||
res.writeHead(200, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ fields, files }, null, 2)); | ||
return; | ||
@@ -153,4 +159,4 @@ } | ||
```js | ||
const express = require('express'); | ||
const formidable = require('formidable'); | ||
import express from 'express'; | ||
import formidable from 'formidable'; | ||
@@ -171,3 +177,3 @@ const app = express(); | ||
app.post('/api/upload', (req, res, next) => { | ||
const form = formidable({ multiples: true }); | ||
const form = formidable({}); | ||
@@ -201,4 +207,4 @@ form.parse(req, (err, fields, files) => { | ||
```js | ||
const Koa = require('koa'); | ||
const formidable = require('formidable'); | ||
import Koa from 'Koa'; | ||
import formidable from 'formidable'; | ||
@@ -213,3 +219,3 @@ const app = new Koa(); | ||
if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') { | ||
const form = formidable({ multiples: true }); | ||
const form = formidable({}); | ||
@@ -303,16 +309,4 @@ // not very elegant, but that's for now if you don't want to use `koa-better-body` | ||
```js | ||
const formidable = require('formidable'); | ||
import formidable from 'formidable'; | ||
const form = formidable(options); | ||
// or | ||
const { formidable } = require('formidable'); | ||
const form = formidable(options); | ||
// or | ||
const { IncomingForm } = require('formidable'); | ||
const form = new IncomingForm(options); | ||
// or | ||
const { Formidable } = require('formidable'); | ||
const form = new Formidable(options); | ||
``` | ||
@@ -331,9 +325,13 @@ | ||
extensions of the original files or not | ||
- `options.allowEmptyFiles` **{boolean}** - default `true`; allow upload empty | ||
- `options.allowEmptyFiles` **{boolean}** - default `false`; allow upload empty | ||
files | ||
- `options.minFileSize` **{number}** - default `1` (1byte); the minium size of | ||
uploaded file. | ||
- `options.maxFiles` **{number}** - default `Infinity`; | ||
limit the amount of uploaded files, set Infinity for unlimited | ||
- `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb); | ||
limit the size of uploaded file. | ||
- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set 0 for unlimited | ||
limit the size of each uploaded file. | ||
- `options.maxTotalFileSize` **{number}** - default `options.maxFileSize`; | ||
limit the size of the batch of uploaded files. | ||
- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set Infinity for unlimited | ||
- `options.maxFieldsSize` **{number}** - default `20 * 1024 * 1024` (20mb); | ||
@@ -356,7 +354,2 @@ limit the amount of memory all fields together (except files) can allocate in | ||
behavior of writing the file in the host machine file system is lost. | ||
- `options.multiples` **{boolean}** - default `false`; when you call the | ||
`.parse` method, the `files` argument (of the callback) will contain arrays of | ||
files for inputs which submit multiple files using the HTML5 `multiple` | ||
attribute. Also, the `fields` argument will contain arrays of values for | ||
fields that have names ending with '[]'. | ||
- `options.filename` **{function}** - default `undefined` Use it to control | ||
@@ -366,7 +359,15 @@ newFilename. Must return a string. Will be joined with options.uploadDir. | ||
- `options.filter` **{function}** - default function that always returns true. | ||
Use it to filter files before they are uploaded. Must return a boolean. | ||
Use it to filter files before they are uploaded. Must return a boolean. Will not make the form.parse error | ||
- `options.createDirsFromUploads` **{boolean}** - default false. If true, makes direct folder uploads possible. Use `<input type="file" name="folders" webkitdirectory directory multiple>` to create a form to upload folders. Has to be used with the options `options.uploadDir` and `options.filename` where `options.filename` has to return a string with the character `/` for folders to be created. The base will be `options.uploadDir`. | ||
#### `options.filename` **{function}** function (name, ext, part, form) -> string | ||
where part can be decomposed as | ||
```js | ||
const { originalFilename, mimetype} = part; | ||
``` | ||
_**Note:** If this size of combined fields, or size of some file is exceeded, an | ||
@@ -387,3 +388,3 @@ `'error'` event is fired._ | ||
**Note:** use an outside variable to cancel all uploads upon the first error | ||
Behaves like Array.filter: Returning false will simply ignore the file and go to the next. | ||
@@ -399,13 +400,29 @@ ```js | ||
**Note:** use an outside variable to cancel all uploads upon the first error | ||
### .parse(request, callback) | ||
**Note:** use form.emit('error') to make form.parse error | ||
Parses an incoming Node.js `request` containing form data. If `callback` is | ||
provided, all fields and files are collected and passed to the callback. | ||
```js | ||
let cancelUploads = false;// create variable at the same scope as form | ||
const options = { | ||
filter: function ({name, originalFilename, mimetype}) { | ||
// keep only images | ||
const valid = mimetype && mimetype.includes("image"); | ||
if (!valid) { | ||
form.emit('error', new formidableErrors.default('invalid type', 0, 400)); // optional make form.parse error | ||
cancelUploads = true; //variable to make filter return false after the first problem | ||
} | ||
return valid && !cancelUploads; | ||
} | ||
}; | ||
``` | ||
### .parse(request, ?callback) | ||
Parses an incoming Node.js `request` containing form data. If `callback` is not provided a promise is returned. | ||
```js | ||
const formidable = require('formidable'); | ||
const form = formidable({ uploadDir: __dirname }); | ||
const form = formidable({ multiples: true, uploadDir: __dirname }); | ||
form.parse(req, (err, fields, files) => { | ||
@@ -415,2 +432,5 @@ console.log('fields:', fields); | ||
}); | ||
// with Promise | ||
const [fields, files] = await form.parse(req); | ||
``` | ||
@@ -515,3 +535,3 @@ | ||
A method that allows you to extend the Formidable library. By default we include | ||
4 plugins, which esentially are adapters to plug the different built-in parsers. | ||
4 plugins, which essentially are adapters to plug the different built-in parsers. | ||
@@ -534,4 +554,2 @@ **The plugins added by this method are always enabled.** | ||
```js | ||
const formidable = require('formidable'); | ||
const form = formidable({ keepExtensions: true }); | ||
@@ -561,7 +579,6 @@ | ||
```js | ||
const { Formidable } = require('formidable'); | ||
const form = new Formidable({ | ||
import formidable, {octetstream, querystring, json} from "formidable"; | ||
const form = formidable({ | ||
hashAlgorithm: 'sha1', | ||
enabledPlugins: ['octetstream', 'querystring', 'json'], | ||
enabledPlugins: [octetstream, querystring, json], | ||
}); | ||
@@ -652,3 +669,3 @@ ``` | ||
Emitted after each incoming chunk of data that has been parsed. Can be used to | ||
roll your own progress bar. | ||
roll your own progress bar. **Warning** Use this only for server side progress bar. On the client side better use `XMLHttpRequest` with `xhr.upload.onprogress =` | ||
@@ -679,3 +696,3 @@ ```js | ||
// file.newFilename generated hexoid or what options.filename returned | ||
// file.filepath default pathnme as per options.uploadDir and options.filename | ||
// file.filepath default pathname as per options.uploadDir and options.filename | ||
// file.filepath = CUSTOM_PATH // to change the final path | ||
@@ -730,2 +747,43 @@ }); | ||
### Helpers | ||
#### firstValues | ||
Gets first values of fields, like pre 3.0.0 without multiples pass in a list of optional exceptions where arrays of strings is still wanted (`<select multiple>` for example) | ||
```js | ||
import { firstValues } from 'formidable/src/helpers/firstValues.js'; | ||
// ... | ||
form.parse(request, async (error, fieldsMultiple, files) => { | ||
if (error) { | ||
//... | ||
} | ||
const exceptions = ['thisshouldbeanarray']; | ||
const fieldsSingle = firstValues(form, fieldsMultiple, exceptions); | ||
// ... | ||
``` | ||
#### readBooleans | ||
Html form input type="checkbox" only send the value "on" if checked, | ||
convert it to booleans for each input that is expected to be sent as a checkbox, only use after firstValues or similar was called. | ||
```js | ||
import { firstValues } from 'formidable/src/helpers/firstValues.js'; | ||
import { readBooleans } from 'formidable/src/helpers/readBooleans.js'; | ||
// ... | ||
form.parse(request, async (error, fieldsMultiple, files) => { | ||
if (error) { | ||
//... | ||
} | ||
const fieldsSingle = firstValues(form, fieldsMultiple); | ||
const expectedBooleans = ['checkbox1', 'wantsNewsLetter', 'hasACar']; | ||
const fieldsWithBooleans = readBooleans(fieldsSingle, expectedBooleans); | ||
// ... | ||
``` | ||
## Changelog | ||
@@ -732,0 +790,0 @@ |
/* eslint-disable class-methods-use-this */ | ||
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import os from 'node:os'; | ||
import path from 'node:path'; | ||
import fsPromises from 'node:fs/promises'; | ||
import { EventEmitter } from 'node:events'; | ||
import { StringDecoder } from 'node:string_decoder'; | ||
import hexoid from 'hexoid'; | ||
import once from 'once'; | ||
import dezalgo from 'dezalgo'; | ||
import { octetstream, querystring, multipart, json } from './plugins/index.js'; | ||
import PersistentFile from './PersistentFile.js'; | ||
import VolatileFile from './VolatileFile.js'; | ||
import DummyParser from './parsers/Dummy.js'; | ||
import MultipartParser from './parsers/Multipart.js'; | ||
import * as errors from './FormidableError.js'; | ||
import FormidableError from './FormidableError.js'; | ||
const os = require('os'); | ||
const path = require('path'); | ||
const hexoid = require('hexoid'); | ||
const once = require('once'); | ||
const dezalgo = require('dezalgo'); | ||
const { EventEmitter } = require('events'); | ||
const { StringDecoder } = require('string_decoder'); | ||
const qs = require('qs'); | ||
const toHexoId = hexoid(25); | ||
@@ -19,5 +24,8 @@ const DEFAULT_OPTIONS = { | ||
maxFieldsSize: 20 * 1024 * 1024, | ||
maxFiles: Infinity, | ||
maxFileSize: 200 * 1024 * 1024, | ||
maxTotalFileSize: undefined, | ||
minFileSize: 1, | ||
allowEmptyFiles: true, | ||
allowEmptyFiles: false, | ||
createDirsFromUploads: false, | ||
keepExtensions: false, | ||
@@ -27,19 +35,11 @@ encoding: 'utf-8', | ||
uploadDir: os.tmpdir(), | ||
multiples: false, | ||
enabledPlugins: ['octetstream', 'querystring', 'multipart', 'json'], | ||
enabledPlugins: [octetstream, querystring, multipart, json], | ||
fileWriteStreamHandler: null, | ||
defaultInvalidName: 'invalid-name', | ||
filter: function () { | ||
filter(_part) { | ||
return true; | ||
}, | ||
filename: undefined, | ||
}; | ||
const PersistentFile = require('./PersistentFile'); | ||
const VolatileFile = require('./VolatileFile'); | ||
const DummyParser = require('./parsers/Dummy'); | ||
const MultipartParser = require('./parsers/Multipart'); | ||
const errors = require('./FormidableError.js'); | ||
const { FormidableError } = errors; | ||
function hasOwnProp(obj, key) { | ||
@@ -49,2 +49,38 @@ return Object.prototype.hasOwnProperty.call(obj, key); | ||
const decorateForceSequential = function (promiseCreator) { | ||
/* forces a function that returns a promise to be sequential | ||
useful for fs for example */ | ||
let lastPromise = Promise.resolve(); | ||
return async function (...x) { | ||
const promiseWeAreWaitingFor = lastPromise; | ||
let currentPromise; | ||
let callback; | ||
// we need to change lastPromise before await anything, | ||
// otherwise 2 calls might wait the same thing | ||
lastPromise = new Promise(function (resolve) { | ||
callback = resolve; | ||
}); | ||
await promiseWeAreWaitingFor; | ||
currentPromise = promiseCreator(...x); | ||
currentPromise.then(callback).catch(callback); | ||
return currentPromise; | ||
}; | ||
}; | ||
const createNecessaryDirectoriesAsync = decorateForceSequential(function (filePath) { | ||
const directoryname = path.dirname(filePath); | ||
return fsPromises.mkdir(directoryname, { recursive: true }); | ||
}); | ||
const invalidExtensionChar = (c) => { | ||
const code = c.charCodeAt(0); | ||
return !( | ||
code === 46 || // . | ||
(code >= 48 && code <= 57) || | ||
(code >= 65 && code <= 90) || | ||
(code >= 97 && code <= 122) | ||
); | ||
}; | ||
class IncomingForm extends EventEmitter { | ||
@@ -55,2 +91,5 @@ constructor(options = {}) { | ||
this.options = { ...DEFAULT_OPTIONS, ...options }; | ||
if (!this.options.maxTotalFileSize) { | ||
this.options.maxTotalFileSize = this.options.maxFileSize | ||
} | ||
@@ -72,2 +111,3 @@ const dir = path.resolve( | ||
'_parser', | ||
'req', | ||
].forEach((key) => { | ||
@@ -81,3 +121,3 @@ this[key] = null; | ||
this._fieldsSize = 0; | ||
this._fileSize = 0; | ||
this._totalFileSize = 0; | ||
this._plugins = []; | ||
@@ -97,9 +137,10 @@ this.openedFiles = []; | ||
this.options.enabledPlugins.forEach((pluginName) => { | ||
const plgName = pluginName.toLowerCase(); | ||
// eslint-disable-next-line import/no-dynamic-require, global-require | ||
this.use(require(path.join(__dirname, 'plugins', `${plgName}.js`))); | ||
this.options.enabledPlugins.forEach((plugin) => { | ||
this.use(plugin); | ||
}); | ||
this._setUpMaxFields(); | ||
this._setUpMaxFiles(); | ||
this.ended = undefined; | ||
this.type = undefined; | ||
} | ||
@@ -118,80 +159,83 @@ | ||
parse(req, cb) { | ||
this.pause = () => { | ||
try { | ||
req.pause(); | ||
} catch (err) { | ||
// the stream was destroyed | ||
if (!this.ended) { | ||
// before it was completed, crash & burn | ||
this._error(err); | ||
} | ||
return false; | ||
pause () { | ||
try { | ||
this.req.pause(); | ||
} catch (err) { | ||
// the stream was destroyed | ||
if (!this.ended) { | ||
// before it was completed, crash & burn | ||
this._error(err); | ||
} | ||
return true; | ||
}; | ||
return false; | ||
} | ||
return true; | ||
} | ||
this.resume = () => { | ||
try { | ||
req.resume(); | ||
} catch (err) { | ||
// the stream was destroyed | ||
if (!this.ended) { | ||
// before it was completed, crash & burn | ||
this._error(err); | ||
} | ||
return false; | ||
resume () { | ||
try { | ||
this.req.resume(); | ||
} catch (err) { | ||
// the stream was destroyed | ||
if (!this.ended) { | ||
// before it was completed, crash & burn | ||
this._error(err); | ||
} | ||
return false; | ||
} | ||
return true; | ||
}; | ||
return true; | ||
} | ||
// returns a promise if no callback is provided | ||
async parse(req, cb) { | ||
this.req = req; | ||
let promise; | ||
// Setup callback first, so we don't miss anything from data events emitted immediately. | ||
if (cb) { | ||
const callback = once(dezalgo(cb)); | ||
const fields = {}; | ||
let mockFields = ''; | ||
const files = {}; | ||
this.on('field', (name, value) => { | ||
if ( | ||
this.options.multiples && | ||
(this.type === 'multipart' || this.type === 'urlencoded') | ||
) { | ||
const mObj = { [name]: value }; | ||
mockFields = mockFields | ||
? `${mockFields}&${qs.stringify(mObj)}` | ||
: `${qs.stringify(mObj)}`; | ||
} else { | ||
fields[name] = value; | ||
} | ||
if (!cb) { | ||
let resolveRef; | ||
let rejectRef; | ||
promise = new Promise((resolve, reject) => { | ||
resolveRef = resolve; | ||
rejectRef = reject; | ||
}); | ||
this.on('file', (name, file) => { | ||
// TODO: too much nesting | ||
if (this.options.multiples) { | ||
if (hasOwnProp(files, name)) { | ||
if (!Array.isArray(files[name])) { | ||
files[name] = [files[name]]; | ||
} | ||
files[name].push(file); | ||
} else { | ||
files[name] = file; | ||
} | ||
cb = (err, fields, files) => { | ||
if (err) { | ||
rejectRef(err); | ||
} else { | ||
files[name] = file; | ||
resolveRef([fields, files]); | ||
} | ||
}); | ||
this.on('error', (err) => { | ||
callback(err, fields, files); | ||
}); | ||
this.on('end', () => { | ||
if (this.options.multiples) { | ||
Object.assign(fields, qs.parse(mockFields)); | ||
} | ||
callback(null, fields, files); | ||
}); | ||
} | ||
} | ||
const callback = once(dezalgo(cb)); | ||
this.fields = {}; | ||
const files = {}; | ||
this.on('field', (name, value) => { | ||
if (this.type === 'multipart' || this.type === 'urlencoded') { | ||
if (!hasOwnProp(this.fields, name)) { | ||
this.fields[name] = [value]; | ||
} else { | ||
this.fields[name].push(value); | ||
} | ||
} else { | ||
this.fields[name] = value; | ||
} | ||
}); | ||
this.on('file', (name, file) => { | ||
if (!hasOwnProp(files, name)) { | ||
files[name] = [file]; | ||
} else { | ||
files[name].push(file); | ||
} | ||
}); | ||
this.on('error', (err) => { | ||
callback(err, this.fields, files); | ||
}); | ||
this.on('end', () => { | ||
callback(null, this.fields, files); | ||
}); | ||
// Parse headers and setup the parser, ready to start listening for data. | ||
this.writeHeaders(req.headers); | ||
await this.writeHeaders(req.headers); | ||
@@ -221,12 +265,13 @@ // Start listening for data. | ||
} | ||
this._maybeEnd(); | ||
}); | ||
if (promise) { | ||
return promise; | ||
} | ||
return this; | ||
} | ||
writeHeaders(headers) { | ||
async writeHeaders(headers) { | ||
this.headers = headers; | ||
this._parseContentLength(); | ||
this._parseContentType(); | ||
await this._parseContentType(); | ||
@@ -268,18 +313,8 @@ if (!this._parser) { | ||
pause() { | ||
// this does nothing, unless overwritten in IncomingForm.parse | ||
return false; | ||
} | ||
resume() { | ||
// this does nothing, unless overwritten in IncomingForm.parse | ||
return false; | ||
} | ||
onPart(part) { | ||
// this method can be overwritten by the user | ||
this._handlePart(part); | ||
return this._handlePart(part); | ||
} | ||
_handlePart(part) { | ||
async _handlePart(part) { | ||
if (part.originalFilename && typeof part.originalFilename !== 'string') { | ||
@@ -338,5 +373,6 @@ this._error( | ||
let fileSize = 0; | ||
const newFilename = this._getNewName(part); | ||
const filepath = this._joinDirectoryName(newFilename); | ||
const file = this._newFile({ | ||
const file = await this._newFile({ | ||
newFilename, | ||
@@ -356,18 +392,10 @@ filepath, | ||
part.on('data', (buffer) => { | ||
this._fileSize += buffer.length; | ||
if (this._fileSize < this.options.minFileSize) { | ||
this._totalFileSize += buffer.length; | ||
fileSize += buffer.length; | ||
if (this._totalFileSize > this.options.maxTotalFileSize) { | ||
this._error( | ||
new FormidableError( | ||
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${this._fileSize} bytes of file data`, | ||
errors.smallerThanMinFileSize, | ||
400, | ||
), | ||
); | ||
return; | ||
} | ||
if (this._fileSize > this.options.maxFileSize) { | ||
this._error( | ||
new FormidableError( | ||
`options.maxFileSize (${this.options.maxFileSize} bytes) exceeded, received ${this._fileSize} bytes of file data`, | ||
errors.biggerThanMaxFileSize, | ||
`options.maxTotalFileSize (${this.options.maxTotalFileSize} bytes) exceeded, received ${this._totalFileSize} bytes of file data`, | ||
errors.biggerThanTotalMaxFileSize, | ||
413, | ||
@@ -388,6 +416,6 @@ ), | ||
part.on('end', () => { | ||
if (!this.options.allowEmptyFiles && this._fileSize === 0) { | ||
if (!this.options.allowEmptyFiles && fileSize === 0) { | ||
this._error( | ||
new FormidableError( | ||
`options.allowEmptyFiles is false, file size should be greather than 0`, | ||
`options.allowEmptyFiles is false, file size should be greater than 0`, | ||
errors.noEmptyFiles, | ||
@@ -399,2 +427,22 @@ 400, | ||
} | ||
if (fileSize < this.options.minFileSize) { | ||
this._error( | ||
new FormidableError( | ||
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${fileSize} bytes of file data`, | ||
errors.smallerThanMinFileSize, | ||
400, | ||
), | ||
); | ||
return; | ||
} | ||
if (fileSize > this.options.maxFileSize) { | ||
this._error( | ||
new FormidableError( | ||
`options.maxFileSize (${this.options.maxFileSize} bytes), received ${fileSize} bytes of file data`, | ||
errors.biggerThanMaxFileSize, | ||
413, | ||
), | ||
); | ||
return; | ||
} | ||
@@ -410,3 +458,3 @@ file.end(() => { | ||
// eslint-disable-next-line max-statements | ||
_parseContentType() { | ||
async _parseContentType() { | ||
if (this.bytesExpected === 0) { | ||
@@ -428,13 +476,10 @@ this._parser = new DummyParser(this, this.options); | ||
const results = []; | ||
const _dummyParser = new DummyParser(this, this.options); | ||
// eslint-disable-next-line no-plusplus | ||
for (let idx = 0; idx < this._plugins.length; idx++) { | ||
const plugin = this._plugins[idx]; | ||
new DummyParser(this, this.options); | ||
const results = []; | ||
await Promise.all(this._plugins.map(async (plugin, idx) => { | ||
let pluginReturn = null; | ||
try { | ||
pluginReturn = plugin(this, this.options) || this; | ||
pluginReturn = await plugin(this, this.options) || this; | ||
} catch (err) { | ||
@@ -451,3 +496,2 @@ // directly throw from the `form.parse` method; | ||
} | ||
Object.assign(this, pluginReturn); | ||
@@ -457,22 +501,7 @@ | ||
this.emit('plugin', idx, pluginReturn); | ||
results.push(pluginReturn); | ||
} | ||
})); | ||
this.emit('pluginsResults', results); | ||
// NOTE: probably not needed, because we check options.enabledPlugins in the constructor | ||
// if (results.length === 0 /* && results.length !== this._plugins.length */) { | ||
// this._error( | ||
// new Error( | ||
// `bad content-type header, unknown content-type: ${this.headers['content-type']}`, | ||
// ), | ||
// ); | ||
// } | ||
} | ||
_error(err, eventName = 'error') { | ||
// if (!err && this.error) { | ||
// this.emit('error', this.error); | ||
// return; | ||
// } | ||
if (this.error || this.ended) { | ||
@@ -482,10 +511,9 @@ return; | ||
this.req = null; | ||
this.error = err; | ||
this.emit(eventName, err); | ||
if (Array.isArray(this.openedFiles)) { | ||
this.openedFiles.forEach((file) => { | ||
file.destroy(); | ||
}); | ||
} | ||
this.openedFiles.forEach((file) => { | ||
file.destroy(); | ||
}); | ||
} | ||
@@ -510,19 +538,31 @@ | ||
_newFile({ filepath, originalFilename, mimetype, newFilename }) { | ||
return this.options.fileWriteStreamHandler | ||
? new VolatileFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
createFileWriteStream: this.options.fileWriteStreamHandler, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}) | ||
: new PersistentFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}); | ||
async _newFile({ filepath, originalFilename, mimetype, newFilename }) { | ||
if (this.options.fileWriteStreamHandler) { | ||
return new VolatileFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
createFileWriteStream: this.options.fileWriteStreamHandler, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}); | ||
} | ||
if (this.options.createDirsFromUploads) { | ||
try { | ||
await createNecessaryDirectoriesAsync(filepath); | ||
} catch (errorCreatingDir) { | ||
this._error(new FormidableError( | ||
`cannot create directory`, | ||
errors.cannotCreateDir, | ||
409, | ||
)); | ||
} | ||
} | ||
return new PersistentFile({ | ||
newFilename, | ||
filepath, | ||
originalFilename, | ||
mimetype, | ||
hashAlgorithm: this.options.hashAlgorithm, | ||
}); | ||
} | ||
@@ -547,2 +587,5 @@ | ||
// able to get composed extension with multiple dots | ||
// "a.b.c" -> ".b.c" | ||
// as opposed to path.extname -> ".c" | ||
_getExtension(str) { | ||
@@ -556,13 +599,21 @@ if (!str) { | ||
const lastDot = basename.lastIndexOf('.'); | ||
const extname = path.extname(basename).replace(/(\.[a-z0-9]+).*/i, '$1'); | ||
let rawExtname = path.extname(basename); | ||
if (firstDot === lastDot) { | ||
return extname; | ||
if (firstDot !== lastDot) { | ||
rawExtname = basename.slice(firstDot); | ||
} | ||
return basename.slice(firstDot, lastDot) + extname; | ||
let filtered; | ||
const firstInvalidIndex = Array.from(rawExtname).findIndex(invalidExtensionChar); | ||
if (firstInvalidIndex === -1) { | ||
filtered = rawExtname; | ||
} else { | ||
filtered = rawExtname.substring(0, firstInvalidIndex); | ||
} | ||
if (filtered === '.') { | ||
return ''; | ||
} | ||
return filtered; | ||
} | ||
_joinDirectoryName(name) { | ||
@@ -599,8 +650,9 @@ const newPath = path.join(this.uploadDir, name); | ||
if (part && this.options.keepExtensions) { | ||
const originalFilename = typeof part === 'string' ? part : part.originalFilename; | ||
const originalFilename = | ||
typeof part === 'string' ? part : part.originalFilename; | ||
return `${name}${this._getExtension(originalFilename)}`; | ||
} | ||
return name; | ||
} | ||
}; | ||
} | ||
@@ -610,3 +662,3 @@ } | ||
_setUpMaxFields() { | ||
if (this.options.maxFields !== 0) { | ||
if (this.options.maxFields !== Infinity) { | ||
let fieldsCount = 0; | ||
@@ -628,10 +680,25 @@ this.on('field', () => { | ||
_setUpMaxFiles() { | ||
if (this.options.maxFiles !== Infinity) { | ||
let fileCount = 0; | ||
this.on('fileBegin', () => { | ||
fileCount += 1; | ||
if (fileCount > this.options.maxFiles) { | ||
this._error( | ||
new FormidableError( | ||
`options.maxFiles (${this.options.maxFiles}) exceeded`, | ||
errors.maxFilesExceeded, | ||
413, | ||
), | ||
); | ||
} | ||
}); | ||
} | ||
} | ||
_maybeEnd() { | ||
// console.log('ended', this.ended); | ||
// console.log('_flushing', this._flushing); | ||
// console.log('error', this.error); | ||
if (!this.ended || this._flushing || this.error) { | ||
return; | ||
} | ||
this.req = null; | ||
this.emit('end'); | ||
@@ -641,3 +708,3 @@ } | ||
IncomingForm.DEFAULT_OPTIONS = DEFAULT_OPTIONS; | ||
module.exports = IncomingForm; | ||
export default IncomingForm; | ||
export { DEFAULT_OPTIONS }; |
@@ -1,3 +0,1 @@ | ||
/* eslint-disable no-plusplus */ | ||
const missingPlugin = 1000; | ||
@@ -12,3 +10,3 @@ const pluginFunction = 1001; | ||
const smallerThanMinFileSize = 1008; | ||
const biggerThanMaxFileSize = 1009; | ||
const biggerThanTotalMaxFileSize = 1009; | ||
const noEmptyFiles = 1010; | ||
@@ -19,2 +17,6 @@ const missingContentType = 1011; | ||
const unknownTransferEncoding = 1014; | ||
const maxFilesExceeded = 1015; | ||
const biggerThanMaxFileSize = 1016; | ||
const pluginFailed = 1017; | ||
const cannotCreateDir = 1018; | ||
@@ -29,3 +31,3 @@ const FormidableError = class extends Error { | ||
module.exports = { | ||
export { | ||
missingPlugin, | ||
@@ -39,2 +41,3 @@ pluginFunction, | ||
maxFieldsExceeded, | ||
maxFilesExceeded, | ||
smallerThanMinFileSize, | ||
@@ -47,4 +50,7 @@ biggerThanMaxFileSize, | ||
unknownTransferEncoding, | ||
biggerThanTotalMaxFileSize, | ||
pluginFailed, | ||
cannotCreateDir, | ||
}; | ||
FormidableError, | ||
}; | ||
export default FormidableError; |
@@ -1,10 +0,6 @@ | ||
'use strict'; | ||
import PersistentFile from './PersistentFile.js'; | ||
import VolatileFile from './VolatileFile.js'; | ||
import Formidable, { DEFAULT_OPTIONS } from './Formidable.js'; | ||
const PersistentFile = require('./PersistentFile'); | ||
const VolatileFile = require('./VolatileFile'); | ||
const Formidable = require('./Formidable'); | ||
const FormidableError = require('./FormidableError'); | ||
const plugins = require('./plugins/index'); | ||
const parsers = require('./parsers/index'); | ||
@@ -14,26 +10,24 @@ // make it available without requiring the `new` keyword | ||
const formidable = (...args) => new Formidable(...args); | ||
const {enabledPlugins} = DEFAULT_OPTIONS; | ||
module.exports = Object.assign(formidable, { | ||
errors: FormidableError, | ||
File: PersistentFile, | ||
export default formidable; | ||
export { | ||
PersistentFile as File, | ||
PersistentFile, | ||
VolatileFile, | ||
Formidable, | ||
// alias | ||
Formidable as IncomingForm, | ||
// as named | ||
formidable, | ||
// alias | ||
IncomingForm: Formidable, | ||
// parsers | ||
...parsers, | ||
parsers, | ||
// misc | ||
defaultOptions: Formidable.DEFAULT_OPTIONS, | ||
enabledPlugins: Formidable.DEFAULT_OPTIONS.enabledPlugins, | ||
DEFAULT_OPTIONS as defaultOptions, | ||
enabledPlugins, | ||
}; | ||
// plugins | ||
plugins: { | ||
...plugins, | ||
}, | ||
}); | ||
export * from './parsers/index.js'; | ||
export * from './plugins/index.js'; | ||
export * as errors from './FormidableError.js'; |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { Transform } from 'node:stream'; | ||
const { Transform } = require('stream'); | ||
class DummyParser extends Transform { | ||
@@ -21,2 +19,2 @@ constructor(incomingForm, options = {}) { | ||
module.exports = DummyParser; | ||
export default DummyParser; |
@@ -1,10 +0,8 @@ | ||
'use strict'; | ||
import JSONParser from './JSON.js'; | ||
import DummyParser from './Dummy.js'; | ||
import MultipartParser from './Multipart.js'; | ||
import OctetStreamParser from './OctetStream.js'; | ||
import QueryStringParser from './Querystring.js'; | ||
const JSONParser = require('./JSON'); | ||
const DummyParser = require('./Dummy'); | ||
const MultipartParser = require('./Multipart'); | ||
const OctetStreamParser = require('./OctetStream'); | ||
const QueryStringParser = require('./Querystring'); | ||
Object.assign(exports, { | ||
export { | ||
JSONParser, | ||
@@ -14,5 +12,5 @@ DummyParser, | ||
OctetStreamParser, | ||
OctetstreamParser: OctetStreamParser, | ||
OctetStreamParser as OctetstreamParser, | ||
QueryStringParser, | ||
QuerystringParser: QueryStringParser, | ||
}); | ||
QueryStringParser as QuerystringParser, | ||
}; |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { Transform } from 'node:stream'; | ||
const { Transform } = require('stream'); | ||
class JSONParser extends Transform { | ||
@@ -22,6 +20,3 @@ constructor(options = {}) { | ||
const fields = JSON.parse(this.chunks.join('')); | ||
Object.keys(fields).forEach((key) => { | ||
const value = fields[key]; | ||
this.push({ key, value }); | ||
}); | ||
this.push(fields); | ||
} catch (e) { | ||
@@ -36,2 +31,2 @@ callback(e); | ||
module.exports = JSONParser; | ||
export default JSONParser; |
@@ -6,9 +6,6 @@ /* eslint-disable no-fallthrough */ | ||
'use strict'; | ||
import { Transform } from 'node:stream'; | ||
import * as errors from '../FormidableError.js'; | ||
import FormidableError from '../FormidableError.js'; | ||
const { Transform } = require('stream'); | ||
const errors = require('../FormidableError.js'); | ||
const { FormidableError } = errors; | ||
let s = 0; | ||
@@ -46,6 +43,6 @@ const STATE = { | ||
exports.STATES = {}; | ||
export const STATES = {}; | ||
Object.keys(STATE).forEach((stateName) => { | ||
exports.STATES[stateName] = STATE[stateName]; | ||
STATES[stateName] = STATE[stateName]; | ||
}); | ||
@@ -67,2 +64,10 @@ | ||
_endUnexpected() { | ||
return new FormidableError( | ||
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`, | ||
errors.malformedMultipart, | ||
400, | ||
); | ||
} | ||
_flush(done) { | ||
@@ -77,9 +82,5 @@ if ( | ||
} else if (this.state !== STATE.END) { | ||
done( | ||
new FormidableError( | ||
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`, | ||
errors.malformedMultipart, | ||
400, | ||
), | ||
); | ||
done(this._endUnexpected()); | ||
} else { | ||
done(); | ||
} | ||
@@ -146,3 +147,4 @@ } | ||
case STATE.PARSER_UNINITIALIZED: | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
case STATE.START: | ||
@@ -156,3 +158,4 @@ index = 0; | ||
} else if (c !== CR) { | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -171,3 +174,4 @@ index++; | ||
} else { | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -203,3 +207,4 @@ break; | ||
// empty header field | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -213,3 +218,4 @@ dataCallback('headerField', true); | ||
if (cl < A || cl > Z) { | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -233,3 +239,4 @@ break; | ||
if (c !== LF) { | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -240,3 +247,4 @@ state = STATE.HEADER_FIELD_START; | ||
if (c !== LF) { | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -254,3 +262,3 @@ | ||
if (index === 0) { | ||
// boyer-moore derrived algorithm to safely skip non-boundary data | ||
// boyer-moore derived algorithm to safely skip non-boundary data | ||
i += boundaryEnd; | ||
@@ -329,3 +337,4 @@ while (i < this.bufferLength && !(buffer[i] in boundaryChars)) { | ||
default: | ||
return i; | ||
done(this._endUnexpected()); | ||
return; | ||
} | ||
@@ -360,2 +369,2 @@ } | ||
module.exports = Object.assign(MultipartParser, { STATES: exports.STATES }); | ||
export default Object.assign(MultipartParser, { STATES }); |
@@ -1,5 +0,3 @@ | ||
'use strict'; | ||
import { PassThrough } from 'node:stream'; | ||
const { PassThrough } = require('stream'); | ||
class OctetStreamParser extends PassThrough { | ||
@@ -12,2 +10,2 @@ constructor(options = {}) { | ||
module.exports = OctetStreamParser; | ||
export default OctetStreamParser; |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { Transform } from 'node:stream'; | ||
const { Transform } = require('stream'); | ||
const querystring = require('querystring'); | ||
// This is a buffering parser, not quite as nice as the multipart one. | ||
// If I find time I'll rewrite this to be fully streaming as well | ||
// This is a buffering parser, have a look at StreamingQuerystring.js for a streaming parser | ||
class QuerystringParser extends Transform { | ||
@@ -25,8 +21,7 @@ constructor(options = {}) { | ||
_flush(callback) { | ||
const fields = querystring.parse(this.buffer, '&', '='); | ||
// eslint-disable-next-line no-restricted-syntax, guard-for-in | ||
for (const key in fields) { | ||
const fields = new URLSearchParams(this.buffer); | ||
for (const [key, value] of fields) { | ||
this.push({ | ||
key, | ||
value: fields[key], | ||
value, | ||
}); | ||
@@ -39,2 +34,2 @@ } | ||
module.exports = QuerystringParser; | ||
export default QuerystringParser; |
// not used | ||
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { Transform } from 'node:stream'; | ||
import FormidableError, { maxFieldsSizeExceeded } from '../FormidableError.js'; | ||
const { Transform } = require('stream'); | ||
const errors = require('../FormidableError.js'); | ||
const { FormidableError } = errors; | ||
const AMPERSAND = 38; | ||
@@ -64,3 +60,3 @@ const EQUALS = 61; | ||
), | ||
errors.maxFieldsSizeExceeded, | ||
maxFieldsSizeExceeded, | ||
413, | ||
@@ -86,5 +82,5 @@ ); | ||
// we only have a key if there's something in the buffer. We definitely have no value | ||
if (this.buffer && this.buffer.length){ | ||
this.emitField(this.buffer.toString('ascii')); | ||
} | ||
if (this.buffer && this.buffer.length) { | ||
this.emitField(this.buffer.toString('ascii')); | ||
} | ||
} else { | ||
@@ -114,3 +110,3 @@ // We have a key, we may or may not have a value | ||
module.exports = QuerystringParser; | ||
export default QuerystringParser; | ||
@@ -117,0 +113,0 @@ // const q = new QuerystringParser({maxFieldSize: 100}); |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import fs from 'node:fs'; | ||
import crypto from 'node:crypto'; | ||
import { EventEmitter } from 'node:events'; | ||
const fs = require('fs'); | ||
const crypto = require('crypto'); | ||
const { EventEmitter } = require('events'); | ||
class PersistentFile extends EventEmitter { | ||
@@ -27,3 +25,3 @@ constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm }) { | ||
open() { | ||
this._writeStream = new fs.WriteStream(this.filepath); | ||
this._writeStream = fs.createWriteStream(this.filepath); | ||
this._writeStream.on('error', (err) => { | ||
@@ -84,6 +82,9 @@ this.emit('error', err); | ||
this._writeStream.destroy(); | ||
fs.unlink(this.filepath, () => {}); | ||
const filepath = this.filepath; | ||
setTimeout(function () { | ||
fs.unlink(filepath, () => {}); | ||
}, 1) | ||
} | ||
} | ||
module.exports = PersistentFile; | ||
export default PersistentFile; |
@@ -1,13 +0,6 @@ | ||
'use strict'; | ||
import octetstream from './octetstream.js'; | ||
import querystring from './querystring.js'; | ||
import multipart from './multipart.js'; | ||
import json from './json.js'; | ||
const octetstream = require('./octetstream'); | ||
const querystring = require('./querystring'); | ||
const multipart = require('./multipart'); | ||
const json = require('./json'); | ||
Object.assign(exports, { | ||
octetstream, | ||
querystring, | ||
multipart, | ||
json, | ||
}); | ||
export { octetstream, querystring, multipart, json }; |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import JSONParser from '../parsers/JSON.js'; | ||
const JSONParser = require('../parsers/JSON'); | ||
export const jsonType = 'json'; | ||
// the `options` is also available through the `this.options` / `formidable.options` | ||
module.exports = function plugin(formidable, options) { | ||
export default function plugin(formidable, options) { | ||
// the `this` context is always formidable, as the first argument of a plugin | ||
@@ -18,2 +17,4 @@ // but this allows us to customize/test each plugin | ||
} | ||
return self; | ||
}; | ||
@@ -25,8 +26,8 @@ | ||
function init(_self, _opts) { | ||
this.type = 'json'; | ||
this.type = jsonType; | ||
const parser = new JSONParser(this.options); | ||
parser.on('data', ({ key, value }) => { | ||
this.emit('field', key, value); | ||
parser.on('data', (fields) => { | ||
this.fields = fields; | ||
}); | ||
@@ -33,0 +34,0 @@ |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { Stream } from 'node:stream'; | ||
import MultipartParser from '../parsers/Multipart.js'; | ||
import * as errors from '../FormidableError.js'; | ||
import FormidableError from '../FormidableError.js'; | ||
const { Stream } = require('stream'); | ||
const MultipartParser = require('../parsers/Multipart'); | ||
const errors = require('../FormidableError.js'); | ||
const { FormidableError } = errors; | ||
export const multipartType = 'multipart'; | ||
// the `options` is also available through the `options` / `formidable.options` | ||
module.exports = function plugin(formidable, options) { | ||
export default function plugin(formidable, options) { | ||
// the `this` context is always formidable, as the first argument of a plugin | ||
@@ -38,3 +36,4 @@ // but this allows us to customize/test each plugin | ||
} | ||
}; | ||
return self; | ||
} | ||
@@ -46,3 +45,3 @@ // Note that it's a good practice (but it's up to you) to use the `this.options` instead | ||
return function initMultipart() { | ||
this.type = 'multipart'; | ||
this.type = multipartType; | ||
@@ -57,3 +56,3 @@ const parser = new MultipartParser(this.options); | ||
// eslint-disable-next-line max-statements, consistent-return | ||
parser.on('data', ({ name, buffer, start, end }) => { | ||
parser.on('data', async ({ name, buffer, start, end }) => { | ||
if (name === 'partBegin') { | ||
@@ -131,3 +130,3 @@ part = new Stream(); | ||
encoding. So we should always work with a number of bytes that | ||
can be divided by 4, it will result in a number of buytes that | ||
can be divided by 4, it will result in a number of bytes that | ||
can be divided vy 3. | ||
@@ -167,4 +166,5 @@ */ | ||
} | ||
this.onPart(part); | ||
this._parser.pause(); | ||
await this.onPart(part); | ||
this._parser.resume(); | ||
} else if (name === 'end') { | ||
@@ -171,0 +171,0 @@ this.ended = true; |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import OctetStreamParser from '../parsers/OctetStream.js'; | ||
const OctetStreamParser = require('../parsers/OctetStream'); | ||
export const octetStreamType = 'octet-stream'; | ||
// the `options` is also available through the `options` / `formidable.options` | ||
module.exports = function plugin(formidable, options) { | ||
export default async function plugin(formidable, options) { | ||
// the `this` context is always formidable, as the first argument of a plugin | ||
@@ -16,7 +15,6 @@ // but this allows us to customize/test each plugin | ||
if (/octet-stream/i.test(self.headers['content-type'])) { | ||
init.call(self, self, options); | ||
await init.call(self, self, options); | ||
} | ||
return self; | ||
}; | ||
} | ||
@@ -26,4 +24,4 @@ // Note that it's a good practice (but it's up to you) to use the `this.options` instead | ||
// to test the plugin you can pass custom `this` context to it (and so `this.options`) | ||
function init(_self, _opts) { | ||
this.type = 'octet-stream'; | ||
async function init(_self, _opts) { | ||
this.type = octetStreamType; | ||
const originalFilename = this.headers['x-file-name']; | ||
@@ -38,3 +36,3 @@ const mimetype = this.headers['content-type']; | ||
const filepath = this._joinDirectoryName(newFilename); | ||
const file = this._newFile({ | ||
const file = await this._newFile({ | ||
newFilename, | ||
@@ -41,0 +39,0 @@ filepath, |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
const QuerystringParser = require('../parsers/Querystring'); | ||
import QuerystringParser from '../parsers/Querystring.js'; | ||
export const querystringType = 'urlencoded'; | ||
// the `options` is also available through the `this.options` / `formidable.options` | ||
module.exports = function plugin(formidable, options) { | ||
export default function plugin(formidable, options) { | ||
// the `this` context is always formidable, as the first argument of a plugin | ||
@@ -18,3 +18,2 @@ // but this allows us to customize/test each plugin | ||
} | ||
return self; | ||
@@ -27,3 +26,3 @@ }; | ||
function init(_self, _opts) { | ||
this.type = 'urlencoded'; | ||
this.type = querystringType; | ||
@@ -30,0 +29,0 @@ const parser = new QuerystringParser(this.options); |
/* eslint-disable no-underscore-dangle */ | ||
'use strict'; | ||
import { createHash } from 'node:crypto'; | ||
import { EventEmitter } from 'node:events'; | ||
const crypto = require('crypto'); | ||
const { EventEmitter } = require('events'); | ||
class VolatileFile extends EventEmitter { | ||
@@ -19,3 +17,3 @@ constructor({ filepath, newFilename, originalFilename, mimetype, hashAlgorithm, createFileWriteStream }) { | ||
if (typeof this.hashAlgorithm === 'string') { | ||
this.hash = crypto.createHash(this.hashAlgorithm); | ||
this.hash = createHash(this.hashAlgorithm); | ||
} else { | ||
@@ -83,2 +81,2 @@ this.hash = null; | ||
module.exports = VolatileFile; | ||
export default VolatileFile; |
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
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
165386
3
30
3971
885
1
Yes
24
- Removedqs@^6.11.0
- 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)