Skip to content

Commit

Permalink
fix: catch file stream error (#31)
Browse files Browse the repository at this point in the history
closes #49

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
  • Loading branch information
haoxins and fengmk2 committed May 6, 2023
1 parent 9a01eb6 commit 448fc4b
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 94 deletions.
50 changes: 11 additions & 39 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,18 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
schedule:
- cron: '0 2 * * *'

jobs:
build:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
node-version: [14, 16]
os: [ubuntu-latest, windows-latest, macos-latest]
branches: [ master ]

steps:
- name: Checkout Git Source
uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Install Dependencies
run: npm i -g npminstall@5 && npminstall
pull_request:
branches: [ master ]

- name: Continuous Integration
run: npm run ci
workflow_dispatch: {}

- name: Code Coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
jobs:
Job:
name: Node.js
uses: artusjs/github-actions/.github/workflows/node-test.yml@master
with:
os: 'ubuntu-latest, macos-latest, windows-latest'
version: '14, 16, 18, 20'
67 changes: 31 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# co busboy

[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Node.js CI](https://github.com/cojs/busboy/actions/workflows/nodejs.yml/badge.svg)](https://github.com/cojs/busboy/actions/workflows/nodejs.yml)
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![npm download][download-image]][download-url]

[npm-image]: https://img.shields.io/npm/v/co-busboy.svg?style=flat-square
[npm-url]: https://npmjs.org/package/co-busboy
[travis-image]: https://img.shields.io/travis/cojs/busboy.svg?style=flat-square
[travis-url]: https://travis-ci.org/cojs/busboy
[codecov-image]: https://codecov.io/github/cojs/busboy/coverage.svg?branch=master
[codecov-url]: https://codecov.io/github/cojs/busboy?branch=master
[david-image]: https://img.shields.io/david/cojs/busboy.svg?style=flat-square
[david-url]: https://david-dm.org/cojs/busboy
[download-image]: https://img.shields.io/npm/dm/co-busboy.svg?style=flat-square
[download-url]: https://npmjs.org/package/co-busboy

Expand All @@ -22,15 +17,15 @@
## Example

```js
var parse = require('co-busboy')
const parse = require('co-busboy')

app.use(function* (next) {
app.use(async (next) => {
// the body isn't multipart, so busboy can't parse it
if (!this.request.is('multipart/*')) return yield next
if (!this.request.is('multipart/*')) return await next()

var parts = parse(this)
var part
while (part = yield parts()) {
const parts = parse(this)
let part
while (part = await parts()) {
if (part.length) {
// arrays are busboy fields
console.log('key: ' + part[0])
Expand All @@ -52,14 +47,14 @@ set the `autoFields: true` option.
Now all the parts will be streams and a field object and array will automatically be populated.

```js
var parse = require('co-busboy')
const parse = require('co-busboy')

app.use(function* (next) {
var parts = parse(this, {
app.use(async (next) => {
const parts = parse(this, {
autoFields: true
})
var part
while (part = yield parts()) {
let part
while (part = await parts()) {
// it's a stream
part.pipe(fs.createWriteStream('some file.txt'))
}
Expand All @@ -77,21 +72,21 @@ Use `options.checkField` hook `function(name, val, fieldnameTruncated, valTrunca
can handle fields check.

```js
var parse = require('co-busboy')
const parse = require('co-busboy')

app.use(function* (next) {
var ctx = this
var parts = parse(this, {
checkField: function (name, value) {
app.use(async (next) => {
const ctx = this
const parts = parse(this, {
checkField: (name, value) => {
if (name === '_csrf' && !checkCSRF(ctx, value)) {
var err = new Error('invalid csrf token')
err.status = 400
return err
}
}
})
var part
while (part = yield parts()) {
let part
while (part = await parts()) {
// ...
}
})
Expand All @@ -103,23 +98,23 @@ Use `options.checkFile` hook `function(fieldname, file, filename, encoding, mime
can handle filename check.

```js
var parse = require('co-busboy')
var path = require('path')
const parse = require('co-busboy')
const path = require('path')

app.use(function* (next) {
var ctx = this
var parts = parse(this, {
app.use(async (next) => {
const ctx = this
const parts = parse(this, {
// only allow upload `.jpg` files
checkFile: function (fieldname, file, filename) {
checkFile: (fieldname, file, filename) => {
if (path.extname(filename) !== '.jpg') {
var err = new Error('invalid jpg image')
err.status = 400
return err
}
}
})
var part
while (part = yield parts()) {
let part
while (part = await parts()) {
// ...
}
})
Expand All @@ -130,8 +125,8 @@ app.use(function* (next) {
### parts = parse(stream, [options])

```js
var parse = require('co-busboy')
var parts = parse(stream, {
const parse = require('co-busboy')
const parts = parse(stream, {
autoFields: true
})
```
Expand All @@ -141,9 +136,9 @@ The only additional option is `autoFields`.

**Note**: If busboy events `partsLimit`, `filesLimit`, `fieldsLimit` is emitted, will throw an error.

### part = yield parts()
### part = await parts()

Yield the next part.
Await the next part.
If `autoFields: true`, this will always be a file stream.
Otherwise, it will be a [field](https://github.com/mscdex/busboy#busboy-special-events) as an array.

Expand Down
12 changes: 12 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ module.exports = function (request, options) {
}

function onFile(fieldname, file, info) {
function onFileError(err) {
lastError = err
}
function onFileCleanup() {
file.removeListener('error', onFileError)
file.removeListener('end', onFileCleanup)
file.removeListener('close', onFileCleanup)
}
file.on('error', onFileError)
file.on('end', onFileCleanup)
file.on('close', onFileCleanup)

var filename = info.filename
var encoding = info.encoding
var mimetype = info.mimeType
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"scripts": {
"test": "mocha **/*.test.js",
"lint": "echo 'ignore'",
"ci": "c8 npm test"
},
"files": [
Expand Down
105 changes: 86 additions & 19 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,22 @@ const zlib = require('zlib');
var busboy = require('..')

describe('Co Busboy', function () {
it('should work without autofields', function () {
return co(function*(){
var parts = busboy(request())
var part
var fields = 0
var streams = 0
while (part = yield parts) {
if (part.length) {
assert.equal(part.length, 4)
fields++
} else {
streams++
part.resume()
}
it('should work without autofields', async () => {
const parts = busboy(request())
let part
let fields = 0
let streams = 0
while (part = await parts()) {
if (part.length) {
assert.equal(part.length, 4)
fields++
} else {
streams++
part.resume()
}
assert.equal(fields, 6)
assert.equal(streams, 3)
})
}
assert.equal(fields, 6)
assert.equal(streams, 3)
})

it('should work without autofields on gziped content', function () {
Expand Down Expand Up @@ -262,7 +260,7 @@ describe('Co Busboy', function () {
describe('checkFile()', function() {
var logfile = path.join(__dirname, 'test.log')
before(function() {
fs.writeFileSync(logfile, new Buffer(1024 * 1024 * 10))
fs.writeFileSync(logfile, Buffer.alloc(1024 * 1024 * 10))
})

after(function() {
Expand Down Expand Up @@ -496,6 +494,48 @@ describe('Co Busboy', function () {
})
})
})

describe('invalid multipart', function() {
it('should handle error: Unexpected end of form', function() {
return co(function*(){
var parts = busboy(invalidRequest());
var part;
try {
while (part = yield parts) {
if (!part.length) {
part.resume()
}
}

throw new Error('should not run this')
} catch (err) {
assert.equal(err.message, 'Unexpected end of form')
}
})
})

it('should handle error: Unexpected end of form with checkFile', function() {
return co(function*(){
var parts = busboy(invalidRequest(), {
checkFile: function () {
return new Error('invalid filename extension')
}
});
var part;
try {
while (part = yield parts) {
if (!part.length) {
part.resume()
}
}

throw new Error('should not run this')
} catch (err) {
assert.equal(err.message, 'Unexpected end of form')
}
})
})
})
})

function wait(ms) {
Expand Down Expand Up @@ -559,13 +599,40 @@ function request() {
return stream
}


function gziped() {
// using `gzip` as demo, zlib support `deflate` as well
var stream = request()
const oldHeaders = stream.headers
stream = stream.pipe(zlib.createGzip())
stream.headers = oldHeaders
stream.headers['content-encoding'] = 'gzip'

return stream
}

function invalidRequest() {
// https://github.com/mscdex/busboy/blob/master/test/test-types-multipart.js

var stream = new Stream.PassThrough()

stream.headers = {
'content-type': 'multipart/form-data; boundary=---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k'
}

stream.end([
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'-----------------------------invalid',
'Content-Disposition: form-data; name="upload_file_2"; filename="hack.exe"',
'Content-Type: application/octet-stream',
'',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'-----------------------------invalid--'
].join('\r\n'))

return stream
}

0 comments on commit 448fc4b

Please sign in to comment.