UNPKG

koa2-better-body

Version:

Full-featured [koa][] body parser! Support parsing text, buffer, json, json patch, json api, csp-report, multipart, form and urlencoded bodies. Works for koa@1, koa@2 and will work for koa@3.

278 lines (250 loc) 7.49 kB
'use strict' /** * Module dependencies */ var utils = require('lazy-cache')(require) /** * Temporarily re-assign `require` to trick browserify and * webpack into reconizing lazy dependencies. * * This tiny bit of ugliness has the huge dual advantage of * only loading modules that are actually called at some * point in the lifecycle of the application, whilst also * allowing browserify and webpack to find modules that * are depended on but never actually called. */ var fn = require require = utils // eslint-disable-line no-undef, no-native-reassign, no-global-assign /** * Lazily required module dependencies */ require('extend-shallow', 'extend') require('formidable') require('koa-body-parsers', 'bodyParsers') require('querystring') /** * Restore `require` */ require = fn // eslint-disable-line no-undef, no-native-reassign, no-global-assign /** * > Patch querystring logic. By default uses node's * built-in `querystring` module, otherwise that is * provided from `opts.querystring`. Works well with * the `qs` module. You can pass it like that * * **Example** * * ```js * app.use(body({ * querystring: require('qs') * })) * ``` * * @param {String} `str` querystring * @param {Object} `opts` loaded options * @return {Object} parsed querystring object * @api private */ utils.parseQs = function parseQs (str, opts) { return opts.querystring ? opts.querystring.parse(str, opts) : utils.querystring.parse(str, opts.delimiter, '=', opts) } /** * > Default options that will be loaded. Pass `options` to overwrite them. * * @param {Object} `options` * @return {Object} * @api private */ utils.defaultOptions = function defaultOptions (options) { options = typeof options === 'object' ? options : {} var types = utils.defaultTypes(options.extendTypes) options = utils.extend({ fields: false, files: false, multipart: true, textLimit: false, formLimit: false, jsonLimit: false, jsonStrict: true, detectJSON: false, bufferLimit: false, buffer: false, strict: true, // query string `parse` options delimiter: '&', decodeURIComponent: utils.querystring.unescape, maxKeys: 1000 }, options) options.delimiter = options.sep || options.delimiter options.formLimit = options.formLimit || options.urlencodedLimit options.extendTypes = types options.onerror = options.onЕrror || options.onerror options.onerror = typeof options.onerror === 'function' ? options.onerror : false options.delimiter = typeof options.delimiter === 'string' ? options.delimiter : '&' if (typeof options.handler !== 'function') { options.handler = function * noopHandler () {} } if (typeof options.detectJSON !== 'function') { options.detectJSON = function detectJSON () { return false } } return options } /** * > Only extend/overwrite default accept types. * * @param {Object} `types` * @return {Object} * @api private */ utils.defaultTypes = function defaultTypes (types) { types = typeof types === 'object' ? types : {} return utils.extend({ multipart: [ 'multipart/form-data' ], text: [ 'text/*' ], form: [ 'application/x-www-form-urlencoded' ], json: [ 'application/json', 'application/json-patch+json', 'application/vnd.api+json', 'application/csp-report' ], buffer: [ 'text/*' ] }, types) } /** * > Is "valid" request method, according to IETF Draft. * * @see https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-19#section-6.1 * @param {String} `method` koa request method * @return {Boolean} * @api private */ utils.isValid = function isValid (method) { return ['GET', 'HEAD', 'DELETE'].indexOf(method.toUpperCase()) === -1 } /** * > Add `koa-body-parsers` to the koa context. In addition * also adds the formidable as multipart parser. * * @param {Object} `ctx` koa context * @param {Object} `opts` default options * @return {Object} `ctx` koa context * @api private */ utils.setParsers = function setParsers (ctx, opts) { ctx.app.querystring = opts.querystring || opts.qs || // alias ctx.app && ctx.app.querystring || ctx.app && ctx.app.qs || // alias ctx.querystring || ctx.qs // alias utils.bodyParsers(ctx) ctx.request.multipart = utils.multipart.bind(ctx) return ctx } /** * > Formidable wrapper as multipart parser to make * thunk that later can be yielded. Also allows you to pass * formidable.IncomingForm instance to `options.IncomingForm`. * * @param {Object} `options` passed or default plugin options * @param {Object} `ctx` koa context * @return {Function} thunk * @api private */ utils.multipart = function multipart (options) { var ctx = this return new Promise(function(resolve,reject){ var buff = '' var fields = {} var files = [] var form = options.IncomingForm instanceof utils.formidable.IncomingForm ? options.IncomingForm : new utils.formidable.IncomingForm(options) form.on('error', reject) form.on('aborted', reject) form.on('file', function (name, value) { files.push(value) fields[name] = fields[name] || [] fields[name].push(value) }) form.on('field', function (name, value) { buff += name + '=' + value + options.delimiter }) form.on('end', function () { fields = buff && buff.length ? utils.extend({}, utils.parseQs(buff.slice(0, -1), options), fields) : fields resolve({ fields: fields, files: files }) }) form.parse(ctx.req) }) } /** * > Parse a different type of request bodies. By default accepts * and can parse JSON, JSON-API, JSON-Patch, text, form, urlencoded * and buffer bodies. * * @param {Object} `ctx` koa context * @param {Object} `options` plugin options * @param {Function} `next` next middleware * @api private */ utils.parseBody = async function parseBody (ctx, options, next) { /* eslint complexity: [2, 12] */ var fields = typeof options.fields === 'string' ? options.fields : 'fields' var files = typeof options.files === 'string' ? options.files : 'files' var custom = options.extendTypes.custom if (custom && custom.length && ctx.request.is(custom)) { await options.handler.call(ctx, ctx, options, next) return next } if (options.detectJSON(ctx) || ctx.request.is(options.extendTypes.json)) { ctx.app.jsonStrict = typeof options.jsonStrict === 'boolean' ? options.jsonStrict : true ctx.request[fields] = await ctx.request.json(options.jsonLimit) return next } if (ctx.request.is(options.extendTypes.form || options.extendTypes.urlencoded)) { var res = await ctx.request.urlencoded(options.formLimit) ctx.request[fields] = res return next } if (options.buffer && ctx.request.is(options.extendTypes.buffer)) { ctx.request.body = await ctx.request.buffer(options.bufferLimit) return next } if (ctx.request.is(options.extendTypes.text)) { var limit = options.textLimit var body = await ctx.request.text(limit) ctx.request.body = body return next } if (options.multipart && ctx.request.is(options.extendTypes.multipart)) { var result = await ctx.request.multipart(options) ctx.request[fields] = result.fields ctx.request[files] = result.files return next } } /** * Expose `utils` modules */ module.exports = utils