index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. 'use strict';
  2. const parse = require('co-body');
  3. const copy = require('copy-to');
  4. const typeis = require('type-is');
  5. /**
  6. * @param [Object] opts
  7. * - {String} jsonLimit default '1mb'
  8. * - {String} formLimit default '56kb'
  9. * - {string} encoding default 'utf-8'
  10. * - {Object} extendTypes
  11. */
  12. module.exports = function(opts) {
  13. opts = opts || {};
  14. const {detectJSON} = opts;
  15. const {onerror} = opts;
  16. const enableTypes = opts.enableTypes || ['json', 'form'];
  17. const enableForm = checkEnable(enableTypes, 'form');
  18. const enableJson = checkEnable(enableTypes, 'json');
  19. const enableText = checkEnable(enableTypes, 'text');
  20. const enableXml = checkEnable(enableTypes, 'xml');
  21. opts.detectJSON = undefined;
  22. opts.onerror = undefined; // eslint-disable-line unicorn/prefer-add-event-listener
  23. // force co-body return raw body
  24. opts.returnRawBody = true;
  25. // default json types
  26. const jsonTypes = [
  27. 'application/json',
  28. 'application/json-patch+json',
  29. 'application/vnd.api+json',
  30. 'application/csp-report',
  31. 'application/scim+json'
  32. ];
  33. // default form types
  34. const formTypes = ['application/x-www-form-urlencoded'];
  35. // default text types
  36. const textTypes = ['text/plain'];
  37. // default xml types
  38. const xmlTypes = ['text/xml', 'application/xml'];
  39. const jsonOpts = formatOptions(opts, 'json');
  40. const formOpts = formatOptions(opts, 'form');
  41. const textOpts = formatOptions(opts, 'text');
  42. const xmlOpts = formatOptions(opts, 'xml');
  43. const extendTypes = opts.extendTypes || {};
  44. extendType(jsonTypes, extendTypes.json);
  45. extendType(formTypes, extendTypes.form);
  46. extendType(textTypes, extendTypes.text);
  47. extendType(xmlTypes, extendTypes.xml);
  48. // eslint-disable-next-line func-names
  49. return async function bodyParser(ctx, next) {
  50. if (ctx.request.body !== undefined || ctx.disableBodyParser)
  51. return await next(); // eslint-disable-line no-return-await
  52. try {
  53. const res = await parseBody(ctx);
  54. ctx.request.body = 'parsed' in res ? res.parsed : {};
  55. if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw;
  56. } catch (err) {
  57. if (onerror) {
  58. onerror(err, ctx);
  59. } else {
  60. throw err;
  61. }
  62. }
  63. await next();
  64. };
  65. async function parseBody(ctx) {
  66. if (
  67. enableJson &&
  68. ((detectJSON && detectJSON(ctx)) ||
  69. isTypes(ctx.request.get('content-type'), jsonTypes))
  70. ) {
  71. return await parse.json(ctx, jsonOpts); // eslint-disable-line no-return-await
  72. }
  73. if (enableForm && ctx.request.is(formTypes)) {
  74. return await parse.form(ctx, formOpts); // eslint-disable-line no-return-await
  75. }
  76. if (enableText && ctx.request.is(textTypes)) {
  77. return (await parse.text(ctx, textOpts)) || '';
  78. }
  79. if (enableXml && ctx.request.is(xmlTypes)) {
  80. return (await parse.text(ctx, xmlOpts)) || '';
  81. }
  82. return {};
  83. }
  84. };
  85. function formatOptions(opts, type) {
  86. const res = {};
  87. copy(opts).to(res);
  88. res.limit = opts[type + 'Limit'];
  89. return res;
  90. }
  91. function extendType(original, extend) {
  92. if (extend) {
  93. if (!Array.isArray(extend)) {
  94. extend = [extend];
  95. }
  96. extend.forEach(function(extend) {
  97. original.push(extend);
  98. });
  99. }
  100. }
  101. function checkEnable(types, type) {
  102. return types.includes(type);
  103. }
  104. function isTypes(contentTypeValue, types) {
  105. if (typeof contentTypeValue === 'string') {
  106. // trim extra semicolon
  107. contentTypeValue = contentTypeValue.replace(/;$/, '');
  108. }
  109. return typeis.is(contentTypeValue, types);
  110. }