├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | tmp/ 4 | npm-debug.log* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - "4" 3 | - "6" 4 | - "7" 5 | sudo: false 6 | language: node_js 7 | script: "npm run test:cov" 8 | after_script: "npm i -g codecov.io && cat ./coverage/lcov.info | codecov" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yoshua Wuyts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multipart-read-stream [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] [![test coverage][6]][7] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | Read a multipart stream over HTTP. Built on top of [pez][pez]. 6 | 7 | ## Usage 8 | ```js 9 | var multipart = require('multipart-read-stream') 10 | var pump = require('pump') 11 | var http = require('http') 12 | 13 | http.createServer(function (req, res) { 14 | var multipartStream = multipart(req.headers, handler) 15 | 16 | pump(req, multipartStream, function (err) { 17 | if (err) res.end('server error') 18 | res.end() 19 | }) 20 | 21 | function handler (fieldname, file, filename) { 22 | console.log('reading file ' + filename + ' from field ' + fieldname) 23 | var fileStream = fs.createWriteStream(path.join('/tmp', filename)) 24 | pump(file, fileStream) 25 | } 26 | }).listen(8080) 27 | ``` 28 | 29 | ## API 30 | ### readableStream = multipart(headers, [options], fileHandler) 31 | Create a new multipart stream handler. Takes the following arguments: 32 | - __headers:__ an object containing request headers (typically: `req.headers`) 33 | - __options:__ an object that is passed directly to [pez][pez] 34 | - __filehandler(fieldname, file, filename, encoding, mimetype):__ handle a 35 | file. Each `file` is a `readableStream` 36 | 37 | ### Events 38 | multipart-read-stream returns an instance (from `pez.Dispenser`) which 39 | emits a number of multipart specific events: 40 | 41 | #### readableStream.on('part', cb(stream)) 42 | The `part` event drives the `fileHandler` callback for the main API. 43 | The difference is it supplies a single parameter, the read stream of the 44 | file data of a multipart section. 45 | 46 | #### readableStream.on('field', cb(name, value)) 47 | A field event is emitted for partitions containing key-value data 48 | (instead of file data). 49 | 50 | #### readableStream.on('preamble', cb(str)) 51 | Multipart data *may* have a preamble section, which is typically 52 | ignored by parsers. However it's sometimes used as an area to 53 | contain hints/meta information. 54 | 55 | #### readableStream.on('epilogue', cb(str)) 56 | As with the preamble section, the epilogue section essentially 57 | has the same role (ignored, but can be used for meta data), except 58 | it will be parsed after the body rather than before. 59 | 60 | ## Installation 61 | ```sh 62 | $ npm install --save multipart-read-stream 63 | ``` 64 | 65 | ## License 66 | [MIT](https://tldrlegal.com/license/mit-license) 67 | 68 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 69 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 70 | [2]: https://img.shields.io/npm/v/multipart-read-stream.svg?style=flat-square 71 | [3]: https://npmjs.org/package/multipart-read-stream 72 | [4]: https://img.shields.io/travis/yoshuawuyts/multipart-read-stream/master.svg?style=flat-square 73 | [5]: https://travis-ci.org/yoshuawuyts/multipart-read-stream 74 | [6]: https://img.shields.io/codecov/c/github/yoshuawuyts/multipart-read-stream/master.svg?style=flat-square 75 | [7]: https://codecov.io/github/yoshuawuyts/multipart-read-stream 76 | [8]: http://img.shields.io/npm/dm/multipart-read-stream.svg?style=flat-square 77 | [9]: https://npmjs.org/package/multipart-read-stream 78 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 79 | [11]: https://github.com/feross/standard 80 | [pez]: https://github.com/hapijs/pez 81 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var content = require('content') 2 | var assert = require('assert') 3 | var xtend = require('xtend') 4 | var pez = require('pez') 5 | 6 | module.exports = downloadMultipart 7 | 8 | // Download a multipart request 9 | // (obj, [obj], fn) -> transformStream 10 | function downloadMultipart (headers, opts, handle) { 11 | if (!handle) { 12 | handle = opts 13 | opts = {} 14 | } 15 | 16 | assert.equal(typeof headers, 'object', 'multipart-stream: headers should be an object') 17 | assert.equal(typeof opts, 'object', 'multipart-stream: opts should be an object') 18 | assert.equal(typeof handle, 'function', 'multipart-stream: handle should be a function') 19 | 20 | opts = xtend(content.type(headers['content-type']), opts) 21 | var dispenser = new pez.Dispenser(opts) 22 | 23 | dispenser.on('part', function (part) { 24 | var encoding = part.headers['content-transfer-encoding'] 25 | encoding = (encoding) ? encoding.toLowerCase() : '7bit' 26 | 27 | handle(part.name, part, part.filename, encoding, part.headers['content-type']) 28 | }) 29 | 30 | return dispenser 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multipart-read-stream", 3 | "version": "3.0.1", 4 | "description": "Read a multipart stream over HTTP", 5 | "main": "index.js", 6 | "scripts": { 7 | "deps": "dependency-check . && dependency-check . --extra --no-dev", 8 | "test": "standard && npm run deps && NODE_ENV=test node test", 9 | "test:cov": "standard && npm run deps && NODE_ENV=test istanbul cover test.js" 10 | }, 11 | "repository": "yoshuawuyts/multipart-read-stream", 12 | "keywords": [ 13 | "http", 14 | "multipart", 15 | "stream" 16 | ], 17 | "license": "MIT", 18 | "dependencies": { 19 | "content": "^3.0.3", 20 | "pez": "^2.1.4", 21 | "xtend": "^4.0.1" 22 | }, 23 | "devDependencies": { 24 | "concat-stream": "^1.5.2", 25 | "dependency-check": "^2.6.0", 26 | "form-data": "^2.1.1", 27 | "get-server-port": "^1.0.0", 28 | "istanbul": "^0.4.5", 29 | "pump": "^1.0.1", 30 | "standard": "^10.0.2", 31 | "tape": "^4.6.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var getPort = require('get-server-port') 2 | var FormData = require('form-data') 3 | var concat = require('concat-stream') 4 | var http = require('http') 5 | var path = require('path') 6 | var pump = require('pump') 7 | var test = require('tape') 8 | var fs = require('fs') 9 | 10 | var multipart = require('./') 11 | 12 | var filePath = path.join(__dirname, 'README.md') 13 | 14 | test('should assert input types', function (t) { 15 | t.plan(3) 16 | t.throws(multipart.bind(null), /object/) 17 | t.throws(multipart.bind(null, {}), /function/) 18 | t.throws(multipart.bind(null, {}, 123, function () {}), /object/) 19 | }) 20 | 21 | test('should parse forms', function (t) { 22 | t.plan(7) 23 | 24 | var server = http.createServer(function (req, res) { 25 | var multipartStream = multipart(req.headers, handler) 26 | pump(req, multipartStream, function (err) { 27 | t.ifError(err, 'no error') 28 | res.end() 29 | }) 30 | 31 | function handler (field, file, filename, encoding, mimetype) { 32 | t.equal(filename, 'README.md') 33 | t.equal(field, 'upload') 34 | t.equal(encoding, '7bit') 35 | t.equal(mimetype, 'text/x-markdown') 36 | var original = fs.readFileSync(filePath, 'utf8') 37 | file.pipe(concat(function (buf) { 38 | t.equal(String(buf), original) 39 | })) 40 | } 41 | }) 42 | server.listen() 43 | var port = getPort(server) 44 | 45 | // request 46 | var form = new FormData() 47 | var opts = { 48 | protocol: 'http:', 49 | hostname: 'localhost', 50 | port: port, 51 | path: filePath, 52 | headers: form.getHeaders(), 53 | method: 'POST' 54 | } 55 | 56 | var req = http.request(opts, server.close.bind(server)) 57 | var rs = fs.createReadStream(filePath) 58 | form.append('upload', rs) 59 | pump(form, req, function (err) { 60 | t.error(err, 'client pump: no err') 61 | }) 62 | }) 63 | --------------------------------------------------------------------------------