Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: ci

on:
- pull_request
- push

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
name:
- Node.js 10.x
- Node.js 11.x
- Node.js 12.x
- Node.js 13.x
- Node.js 14.x
- Node.js 15.x
- Node.js 16.x
- Node.js 17.x
- Node.js 18.x
- Node.js 19.x
- Node.js 20.x
- Node.js 21.x
- Node.js 22.x
- Node.js 23.x
- Node.js 24.x

include:
- name: Node.js 10.x
node-version: "10.24"
npm-i: mocha@8.4.0

- name: Node.js 11.x
node-version: "11.15"
npm-i: mocha@8.4.0

- name: Node.js 12.x
node-version: "12.22"
npm-i: mocha@9.2.2

- name: Node.js 13.x
node-version: "13.14"
npm-i: mocha@9.2.2

- name: Node.js 14.x
node-version: "14.21"
npm-i: mocha@9.2.2

- name: Node.js 15.x
node-version: "15.14"
npm-i: mocha@9.2.2

- name: Node.js 16.x
node-version: "16.20"

- name: Node.js 17.x
node-version: "17.9"

- name: Node.js 18.x
node-version: "18.18"

- name: Node.js 19.x
node-version: "19.9"

- name: Node.js 20.x
node-version: "20.9"

- name: Node.js 21.x
node-version: "21"

- name: Node.js 22.x
node-version: "22"

- name: Node.js 23.x
node-version: "23"

- name: Node.js 24.x
node-version: "24"

steps:
- uses: actions/checkout@v4

- name: Install Node.js ${{ matrix.node-version }}
shell: bash -eo pipefail -l {0}
run: |
nvm install --default ${{ matrix.node-version }}
if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then
nvm install --alias=npm 0.10
nvm use ${{ matrix.node-version }}
if [[ "$(npm -v)" == 1.1.* ]]; then
nvm exec npm npm install -g npm@1.1
ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm"
else
sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")"
fi
npm config set strict-ssl false
fi
dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH"

- name: Remove npm module(s) ${{ matrix.npm-rm }}
run: npm rm --silent --save-dev ${{ matrix.npm-rm }}
if: matrix.npm-rm != ''

- name: Install npm module(s) ${{ matrix.npm-i }}
run: npm install --save-dev ${{ matrix.npm-i }}
if: matrix.npm-i != ''

- name: Install Node.js dependencies
run: npm install

- name: List environment
id: list_env
shell: bash
run: |
echo "node@$(node -v)"
echo "npm@$(npm -v)"
npm -s ls ||:
(npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print $2 "=" $3 }' >> "$GITHUB_OUTPUT"

- name: Lint code
run: npm run lint

- name: Run tests
shell: bash
run: |
if npm -ps ls nyc | grep -q nyc; then
npm run test-ci
else
npm test
fi

- name: Collect code coverage
uses: coverallsapp/github-action@master
if: steps.list_env.outputs.nyc != ''
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: run-${{ matrix.test_number }}
parallel: true

coverage:
needs: test
runs-on: ubuntu-latest
steps:
- name: Upload code coverage
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## 2.0.0

- **Breaking change: The minimum supported Node version is now 10.16.0**
- Fix [CVE-2025-47935](https://www.cve.org/CVERecord?id=CVE-2025-47935) ([GHSA-44fp-w29j-9vj5](https://github.com/expressjs/multer/security/advisories/GHSA-44fp-w29j-9vj5))
- Fix [CVE-2025-47944](https://www.cve.org/CVERecord?id=CVE-2025-47944) ([GHSA-4pg4-qvpc-4q3h](https://github.com/expressjs/multer/security/advisories/GHSA-4pg4-qvpc-4q3h))

## 1.4.5-lts.2

- Fix out-of-band error event from busboy (#1177)

## 1.4.5-lts.1

- No changes

## 1.4.4-lts.1

- Bugfix: Bump busboy to fix CVE-2022-24434 (#1097)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Multer [![Build Status](https://travis-ci.org/expressjs/multer.svg?branch=master)](https://travis-ci.org/expressjs/multer) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
# Multer [![Build Status](https://badgen.net/github/checks/expressjs/multer/master?label=ci)](https://github.com/expressjs/multer/actions/workflows/ci.yml) [![Test Coverage](https://badgen.net/coveralls/c/github/expressjs/multer/master)](https://coveralls.io/r/expressjs/multer?branch=master) [![NPM version](https://badge.fury.io/js/multer.svg)](https://badge.fury.io/js/multer) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)

Multer is a node.js middleware for handling `multipart/form-data`, which is primarily used for uploading files. It is written
on top of [busboy](https://github.com/mscdex/busboy) for maximum efficiency.
Expand Down
22 changes: 19 additions & 3 deletions lib/make-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ var MulterError = require('./multer-error')
var FileAppender = require('./file-appender')
var removeUploadedFiles = require('./remove-uploaded-files')

function drainStream (stream) {
stream.on('readable', () => {
while (stream.read() !== null) {}
})
}

function makeMiddleware (setup) {
return function multerMiddleware (req, res, next) {
if (!is(req, ['multipart'])) return next()
Expand All @@ -22,6 +28,10 @@ function makeMiddleware (setup) {

req.body = Object.create(null)

req.on('error', function (err) {
abortWithError(err)
})

var busboy

try {
Expand All @@ -41,7 +51,11 @@ function makeMiddleware (setup) {
if (isDone) return
isDone = true
req.unpipe(busboy)
busboy.removeAllListeners()
drainStream(req)
req.resume()
setImmediate(() => {
busboy.removeAllListeners()
})
next(err)
}

Expand Down Expand Up @@ -87,8 +101,10 @@ function makeMiddleware (setup) {

// handle files
busboy.on('file', function (fieldname, fileStream, { filename, encoding, mimeType }) {
// don't attach to the files object, if there is no file
if (!filename) return fileStream.resume()
// filename is not required (https://tools.ietf.org/html/rfc1867) but if
// filename not present busboy only treats as file if content type is
// application/octet-stream
if (!filename) filename = 'undefined'

// Work around bug in Busboy (https://github.com/mscdex/busboy/issues/6)
if (limits && Object.prototype.hasOwnProperty.call(limits, 'fieldNameSize')) {
Expand Down
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "multer",
"description": "Middleware for handling `multipart/form-data`.",
"version": "1.4.5-lts.1",
"version": "2.0.0",
"contributors": [
"Hage Yaapa <captain@hacksparrow.com> (http://www.hacksparrow.com)",
"Jaret Pfluger <https://github.com/jpfluger>",
Expand Down Expand Up @@ -32,13 +32,13 @@
"express": "^4.13.1",
"form-data": "^1.0.0-rc1",
"fs-temp": "^1.1.2",
"mocha": "^3.5.3",
"mocha": "^11.3.0",
"rimraf": "^2.4.1",
"standard": "^14.3.3",
"testdata-w3c-json-form": "^1.0.0"
},
"engines": {
"node": ">= 6.0.0"
"node": ">= 10.16.0"
},
"files": [
"LICENSE",
Expand All @@ -47,6 +47,10 @@
"lib/"
],
"scripts": {
"test": "standard && mocha"
"lint": "standard",
"lint:fix": "standard --fix",
"test": "mocha --reporter spec --exit --check-leaks test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}
91 changes: 91 additions & 0 deletions test/express-integration.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-env mocha */

var assert = require('assert')
var http = require('http')

var multer = require('../')
var util = require('./_util')
Expand Down Expand Up @@ -105,4 +106,94 @@ describe('Express Integration', function () {
done()
})
})

it('should not crash on malformed request', function (done) {
var upload = multer()

app.post('/upload', upload.single('file'), function (req, res) {
res.status(500).end('Request should not be processed')
})

app.use(function (err, req, res, next) {
assert.strictEqual(err.message, 'Unexpected end of form')
res.status(200).end('Correct error')
})

var boundary = 'AaB03x'
var body = [
'--' + boundary,
'Content-Disposition: form-data; name="file"; filename="test.txt"',
'Content-Type: text/plain',
'',
'test without end boundary'
].join('\r\n')
var options = {
hostname: 'localhost',
port,
path: '/upload',
method: 'POST',
headers: {
'content-type': 'multipart/form-data; boundary=' + boundary,
'content-length': body.length
}
}

var req = http.request(options, (res) => {
assert.strictEqual(res.statusCode, 200)
done()
})

req.on('error', (err) => {
done(err)
})

req.write(body)
req.end()
})

it('should not crash on malformed request that causes two errors to be emitted by busboy', function (done) {
var upload = multer()

app.post('/upload2', upload.single('file'), function (req, res) {
res.status(500).end('Request should not be processed')
})

app.use(function (err, req, res, next) {
assert.strictEqual(err.message, 'Malformed part header')
res.status(200).end('Correct error')
})

var boundary = 'AaB03x'
// this payload causes two errors to be emitted by busboy: `Malformed part header` and `Unexpected end of form`
var body = [
'--' + boundary,
'Content-Disposition: form-data; name="file"; filename="test.txt"',
'Content-Type: text/plain',
'',
'--' + boundary + '--',
''
].join('\r\n')
var options = {
hostname: 'localhost',
port,
path: '/upload2',
method: 'POST',
headers: {
'content-type': 'multipart/form-data; boundary=' + boundary,
'content-length': body.length
}
}

var req = http.request(options, (res) => {
assert.strictEqual(res.statusCode, 200)
done()
})

req.on('error', (err) => {
done(err)
})

req.write(body)
req.end()
})
})
11 changes: 11 additions & 0 deletions test/files/no-filename.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--99999
Content-Disposition: form-data; name="textField"
Content-Type: text/plain; charset=ISO-8859-1

foo
--99999
Content-Disposition: form-data; name="fileField"
Content-Type: application/octet-stream

foo
--99999--
Loading
Loading