"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.parseBody = parseBody;

var _zlib = _interopRequireDefault(require("zlib"));

var _querystring = _interopRequireDefault(require("querystring"));

var _rawBody = _interopRequireDefault(require("raw-body"));

var _httpErrors = _interopRequireDefault(require("http-errors"));

var _contentType = _interopRequireDefault(require("content-type"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Provided a "Request" provided by express or connect (typically a node style
 * HTTPClientRequest), Promise the body data contained.
 */
async function parseBody(req) {
  const {
    body
  } = req; // If express has already parsed a body as a keyed object, use it.

  if (typeof body === 'object' && !(body instanceof Buffer)) {
    return body;
  } // Skip requests without content types.


  if (req.headers['content-type'] === undefined) {
    return {};
  }

  const typeInfo = _contentType.default.parse(req); // If express has already parsed a body as a string, and the content-type
  // was application/graphql, parse the string body.


  if (typeof body === 'string' && typeInfo.type === 'application/graphql') {
    return {
      query: body
    };
  } // Already parsed body we didn't recognise? Parse nothing.


  if (body != null) {
    return {};
  }

  const rawBody = await readBody(req, typeInfo); // Use the correct body parser based on Content-Type header.

  switch (typeInfo.type) {
    case 'application/graphql':
      return {
        query: rawBody
      };

    case 'application/json':
      if (jsonObjRegex.test(rawBody)) {
        try {
          return JSON.parse(rawBody);
        } catch (error) {// Do nothing
        }
      }

      throw (0, _httpErrors.default)(400, 'POST body sent invalid JSON.');

    case 'application/x-www-form-urlencoded':
      return _querystring.default.parse(rawBody);
  } // If no Content-Type header matches, parse nothing.


  return {};
}
/**
 * RegExp to match an Object-opening brace "{" as the first non-space
 * in a string. Allowed whitespace is defined in RFC 7159:
 *
 *     ' '   Space
 *     '\t'  Horizontal tab
 *     '\n'  Line feed or New line
 *     '\r'  Carriage return
 */


const jsonObjRegex = /^[ \t\n\r]*\{/; // Read and parse a request body.

async function readBody(req, // TODO: Import the appropriate TS type and use it here instead
typeInfo) {
  var _typeInfo$parameters$, _typeInfo$parameters$2;

  // flowlint-next-line unnecessary-optional-chain:off
  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

  if (charset.slice(0, 4) !== 'utf-') {
    throw (0, _httpErrors.default)(415, `Unsupported charset "${charset.toUpperCase()}".`);
  } // Get content-encoding (e.g. gzip)


  const contentEncoding = req.headers['content-encoding'];
  const encoding = typeof contentEncoding === 'string' ? contentEncoding.toLowerCase() : 'identity';
  const length = encoding === 'identity' ? req.headers['content-length'] : null;
  const limit = 100 * 1024; // 100kb

  const stream = decompressed(req, encoding); // Read body from stream.

  try {
    return await (0, _rawBody.default)(stream, {
      encoding: charset,
      length,
      limit
    });
  } catch (err) {
    throw err.type === 'encoding.unsupported' ? (0, _httpErrors.default)(415, `Unsupported charset "${charset.toUpperCase()}".`) : (0, _httpErrors.default)(400, `Invalid body: ${err.message}.`);
  }
} // Return a decompressed stream, given an encoding.


function decompressed(req, encoding) {
  switch (encoding) {
    case 'identity':
      return req;

    case 'deflate':
      return req.pipe(_zlib.default.createInflate());

    case 'gzip':
      return req.pipe(_zlib.default.createGunzip());
  }

  throw (0, _httpErrors.default)(415, `Unsupported content-encoding "${encoding}".`);
}
