'use strict'; const parse = require('co-body'); const copy = require('copy-to'); const typeis = require('type-is'); /** * @param [Object] opts * - {String} jsonLimit default '1mb' * - {String} formLimit default '56kb' * - {string} encoding default 'utf-8' * - {Object} extendTypes */ module.exports = function(opts) { opts = opts || {}; const {detectJSON} = opts; const {onerror} = opts; const enableTypes = opts.enableTypes || ['json', 'form']; const enableForm = checkEnable(enableTypes, 'form'); const enableJson = checkEnable(enableTypes, 'json'); const enableText = checkEnable(enableTypes, 'text'); const enableXml = checkEnable(enableTypes, 'xml'); opts.detectJSON = undefined; opts.onerror = undefined; // eslint-disable-line unicorn/prefer-add-event-listener // force co-body return raw body opts.returnRawBody = true; // default json types const jsonTypes = [ 'application/json', 'application/json-patch+json', 'application/vnd.api+json', 'application/csp-report', 'application/scim+json' ]; // default form types const formTypes = ['application/x-www-form-urlencoded']; // default text types const textTypes = ['text/plain']; // default xml types const xmlTypes = ['text/xml', 'application/xml']; const jsonOpts = formatOptions(opts, 'json'); const formOpts = formatOptions(opts, 'form'); const textOpts = formatOptions(opts, 'text'); const xmlOpts = formatOptions(opts, 'xml'); const extendTypes = opts.extendTypes || {}; extendType(jsonTypes, extendTypes.json); extendType(formTypes, extendTypes.form); extendType(textTypes, extendTypes.text); extendType(xmlTypes, extendTypes.xml); // eslint-disable-next-line func-names return async function bodyParser(ctx, next) { if (ctx.request.body !== undefined || ctx.disableBodyParser) return await next(); // eslint-disable-line no-return-await try { const res = await parseBody(ctx); ctx.request.body = 'parsed' in res ? res.parsed : {}; if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw; } catch (err) { if (onerror) { onerror(err, ctx); } else { throw err; } } await next(); }; async function parseBody(ctx) { if ( enableJson && ((detectJSON && detectJSON(ctx)) || isTypes(ctx.request.get('content-type'), jsonTypes)) ) { return await parse.json(ctx, jsonOpts); // eslint-disable-line no-return-await } if (enableForm && ctx.request.is(formTypes)) { return await parse.form(ctx, formOpts); // eslint-disable-line no-return-await } if (enableText && ctx.request.is(textTypes)) { return (await parse.text(ctx, textOpts)) || ''; } if (enableXml && ctx.request.is(xmlTypes)) { return (await parse.text(ctx, xmlOpts)) || ''; } return {}; } }; function formatOptions(opts, type) { const res = {}; copy(opts).to(res); res.limit = opts[type + 'Limit']; return res; } function extendType(original, extend) { if (extend) { if (!Array.isArray(extend)) { extend = [extend]; } extend.forEach(function(extend) { original.push(extend); }); } } function checkEnable(types, type) { return types.includes(type); } function isTypes(contentTypeValue, types) { if (typeof contentTypeValue === 'string') { // trim extra semicolon contentTypeValue = contentTypeValue.replace(/;$/, ''); } return typeis.is(contentTypeValue, types); }