parseBody.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.parseBody = parseBody;
  6. var _zlib = _interopRequireDefault(require("zlib"));
  7. var _querystring = _interopRequireDefault(require("querystring"));
  8. var _rawBody = _interopRequireDefault(require("raw-body"));
  9. var _httpErrors = _interopRequireDefault(require("http-errors"));
  10. var _contentType = _interopRequireDefault(require("content-type"));
  11. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  12. /**
  13. * Provided a "Request" provided by express or connect (typically a node style
  14. * HTTPClientRequest), Promise the body data contained.
  15. */
  16. async function parseBody(req) {
  17. const {
  18. body
  19. } = req; // If express has already parsed a body as a keyed object, use it.
  20. if (typeof body === 'object' && !(body instanceof Buffer)) {
  21. return body;
  22. } // Skip requests without content types.
  23. if (req.headers['content-type'] === undefined) {
  24. return {};
  25. }
  26. const typeInfo = _contentType.default.parse(req); // If express has already parsed a body as a string, and the content-type
  27. // was application/graphql, parse the string body.
  28. if (typeof body === 'string' && typeInfo.type === 'application/graphql') {
  29. return {
  30. query: body
  31. };
  32. } // Already parsed body we didn't recognise? Parse nothing.
  33. if (body != null) {
  34. return {};
  35. }
  36. const rawBody = await readBody(req, typeInfo); // Use the correct body parser based on Content-Type header.
  37. switch (typeInfo.type) {
  38. case 'application/graphql':
  39. return {
  40. query: rawBody
  41. };
  42. case 'application/json':
  43. if (jsonObjRegex.test(rawBody)) {
  44. try {
  45. return JSON.parse(rawBody);
  46. } catch (error) {// Do nothing
  47. }
  48. }
  49. throw (0, _httpErrors.default)(400, 'POST body sent invalid JSON.');
  50. case 'application/x-www-form-urlencoded':
  51. return _querystring.default.parse(rawBody);
  52. } // If no Content-Type header matches, parse nothing.
  53. return {};
  54. }
  55. /**
  56. * RegExp to match an Object-opening brace "{" as the first non-space
  57. * in a string. Allowed whitespace is defined in RFC 7159:
  58. *
  59. * ' ' Space
  60. * '\t' Horizontal tab
  61. * '\n' Line feed or New line
  62. * '\r' Carriage return
  63. */
  64. const jsonObjRegex = /^[ \t\n\r]*\{/; // Read and parse a request body.
  65. async function readBody(req, // TODO: Import the appropriate TS type and use it here instead
  66. typeInfo) {
  67. var _typeInfo$parameters$, _typeInfo$parameters$2;
  68. // flowlint-next-line unnecessary-optional-chain:off
  69. const charset = (_typeInfo$parameters$ = (_typeInfo$parameters$2 = typeInfo.parameters.charset) === null || _typeInfo$parameters$2 === void 0 ? void 0 : _typeInfo$parameters$2.toLowerCase()) !== null && _typeInfo$parameters$ !== void 0 ? _typeInfo$parameters$ : 'utf-8'; // Assert charset encoding per JSON RFC 7159 sec 8.1
  70. if (charset.slice(0, 4) !== 'utf-') {
  71. throw (0, _httpErrors.default)(415, `Unsupported charset "${charset.toUpperCase()}".`);
  72. } // Get content-encoding (e.g. gzip)
  73. const contentEncoding = req.headers['content-encoding'];
  74. const encoding = typeof contentEncoding === 'string' ? contentEncoding.toLowerCase() : 'identity';
  75. const length = encoding === 'identity' ? req.headers['content-length'] : null;
  76. const limit = 100 * 1024; // 100kb
  77. const stream = decompressed(req, encoding); // Read body from stream.
  78. try {
  79. return await (0, _rawBody.default)(stream, {
  80. encoding: charset,
  81. length,
  82. limit
  83. });
  84. } catch (err) {
  85. throw err.type === 'encoding.unsupported' ? (0, _httpErrors.default)(415, `Unsupported charset "${charset.toUpperCase()}".`) : (0, _httpErrors.default)(400, `Invalid body: ${err.message}.`);
  86. }
  87. } // Return a decompressed stream, given an encoding.
  88. function decompressed(req, encoding) {
  89. switch (encoding) {
  90. case 'identity':
  91. return req;
  92. case 'deflate':
  93. return req.pipe(_zlib.default.createInflate());
  94. case 'gzip':
  95. return req.pipe(_zlib.default.createGunzip());
  96. }
  97. throw (0, _httpErrors.default)(415, `Unsupported content-encoding "${encoding}".`);
  98. }