'use strict' const Busboy = require('busboy') const BlackHoleStream = require('black-hole-stream') const Result = require('./result') const getDescriptor = Object.getOwnPropertyDescriptor const isArray = Array.isArray module.exports = (request, options) => { const res = new Result() // - what used to be a `chan` will now be a thenable // - each time the thenable.then() is called, a Promise is returned. when the // next value is ready, the promise is resolved with it OR if the value is an // error, the promise is rejected and the thenable is marked done. // - if a done thenable is awaited, a Promise resolved with // an empty value is returned. // koa special sauce request = request.req || request options = options || {} options.headers = request.headers // options.checkField hook `function(name, val, fieldnameTruncated, valTruncated)` // options.checkFile hook `function(fieldname, fileStream, filename, encoding, mimetype)` const checkField = options.checkField const checkFile = options.checkFile let lastError const busboy = new Busboy(options) request.on('close', cleanup) busboy .on('field', onField) .on('file', onFile) .on('close', cleanup) .on('error', onEnd) .on('finish', onEnd) busboy.on('partsLimit', () => { const err = new Error('Reach parts limit') err.code = 'Request_parts_limit' err.status = 413 onError(err) }) busboy.on('filesLimit', () => { const err = new Error('Reach files limit') err.code = 'Request_files_limit' err.status = 413 onError(err) }) busboy.on('fieldsLimit', () => { const err = new Error('Reach fields limit') err.code = 'Request_fields_limit' err.status = 413 onError(err) }) request.pipe(busboy) if (options.autoFields) { var field = res.field = {} // object lookup var fields = res.fields = [] // list lookup } return res function onField (name, val, fieldnameTruncated, valTruncated) { if (checkField) { const err = checkField(name, val, fieldnameTruncated, valTruncated) if (err) { return onError(err) } } const args = [name, val, fieldnameTruncated, valTruncated] if (options.autoFields) { fields.push(args) // don't overwrite prototypes if (getDescriptor(Object.prototype, name)) return const prev = field[name] if (prev == null) { field[name] = val return } if (isArray(prev)) { prev.push(val) return } field[name] = [prev, val] } else { res.add(args) } } function onFile (fieldname, file, filename, encoding, mimetype) { if (checkFile) { const err = checkFile(fieldname, file, filename, encoding, mimetype) if (err) { // make sure request stream's data has been read const blackHoleStream = new BlackHoleStream() file.pipe(blackHoleStream) return onError(err) } } file.fieldname = fieldname file.filename = filename file.transferEncoding = file.encoding = encoding file.mimeType = file.mime = mimetype res.add(file) } function onError (err) { lastError = err } function onEnd (err) { cleanup() res.close(err || lastError) } function cleanup () { request.removeListener('close', cleanup) busboy.removeListener('field', onField) busboy.removeListener('file', onFile) busboy.removeListener('close', cleanup) busboy.removeListener('error', onEnd) busboy.removeListener('partsLimit', onEnd) busboy.removeListener('filesLimit', onEnd) busboy.removeListener('fieldsLimit', onEnd) busboy.removeListener('finish', onEnd) } }