parseBody.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.parseBody = void 0;
  7. const zlib_1 = __importDefault(require("zlib"));
  8. const querystring_1 = __importDefault(require("querystring"));
  9. const raw_body_1 = __importDefault(require("raw-body"));
  10. const http_errors_1 = __importDefault(require("http-errors"));
  11. const content_type_1 = __importDefault(require("content-type"));
  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 { body } = req;
  18. // If express has already parsed a body as a keyed object, use it.
  19. if (typeof body === 'object' && !(body instanceof Buffer)) {
  20. return body;
  21. }
  22. // Skip requests without content types.
  23. if (req.headers['content-type'] === undefined) {
  24. return {};
  25. }
  26. const typeInfo = content_type_1.default.parse(req);
  27. // If express has already parsed a body as a string, and the content-type
  28. // was application/graphql, parse the string body.
  29. if (typeof body === 'string' && typeInfo.type === 'application/graphql') {
  30. return { 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);
  37. // Use the correct body parser based on Content-Type header.
  38. switch (typeInfo.type) {
  39. case 'application/graphql':
  40. return { query: rawBody };
  41. case 'application/json':
  42. if (jsonObjRegex.test(rawBody)) {
  43. try {
  44. return JSON.parse(rawBody);
  45. }
  46. catch (_a) {
  47. // Do nothing
  48. }
  49. }
  50. throw http_errors_1.default(400, 'POST body sent invalid JSON.');
  51. case 'application/x-www-form-urlencoded':
  52. return querystring_1.default.parse(rawBody);
  53. }
  54. // If no Content-Type header matches, parse nothing.
  55. return {};
  56. }
  57. exports.parseBody = parseBody;
  58. /**
  59. * RegExp to match an Object-opening brace "{" as the first non-space
  60. * in a string. Allowed whitespace is defined in RFC 7159:
  61. *
  62. * ' ' Space
  63. * '\t' Horizontal tab
  64. * '\n' Line feed or New line
  65. * '\r' Carriage return
  66. */
  67. const jsonObjRegex = /^[ \t\n\r]*\{/;
  68. // Read and parse a request body.
  69. async function readBody(req, typeInfo) {
  70. var _a, _b;
  71. const charset = (_b = (_a = typeInfo.parameters.charset) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : 'utf-8';
  72. // Assert charset encoding per JSON RFC 7159 sec 8.1
  73. if (!charset.startsWith('utf-')) {
  74. throw http_errors_1.default(415, `Unsupported charset "${charset.toUpperCase()}".`);
  75. }
  76. // Get content-encoding (e.g. gzip)
  77. const contentEncoding = req.headers['content-encoding'];
  78. const encoding = typeof contentEncoding === 'string'
  79. ? contentEncoding.toLowerCase()
  80. : 'identity';
  81. const length = encoding === 'identity' ? req.headers['content-length'] : null;
  82. const limit = 100 * 1024; // 100kb
  83. const stream = decompressed(req, encoding);
  84. // Read body from stream.
  85. try {
  86. return await raw_body_1.default(stream, { encoding: charset, length, limit });
  87. }
  88. catch (rawError) {
  89. const error = http_errors_1.default(400,
  90. /* istanbul ignore next: Thrown by underlying library. */
  91. rawError instanceof Error ? rawError : String(rawError));
  92. error.message =
  93. error.type === 'encoding.unsupported'
  94. ? `Unsupported charset "${charset.toUpperCase()}".`
  95. : `Invalid body: ${error.message}.`;
  96. throw error;
  97. }
  98. }
  99. // Return a decompressed stream, given an encoding.
  100. function decompressed(req, encoding) {
  101. switch (encoding) {
  102. case 'identity':
  103. return req;
  104. case 'deflate':
  105. return req.pipe(zlib_1.default.createInflate());
  106. case 'gzip':
  107. return req.pipe(zlib_1.default.createGunzip());
  108. }
  109. throw http_errors_1.default(415, `Unsupported content-encoding "${encoding}".`);
  110. }