This repository has been archived by the owner on Mar 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 538
/
parseBody.js
147 lines (127 loc) · 4.26 KB
/
parseBody.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow strict
*/
import contentType from 'content-type';
import getBody from 'raw-body';
import httpError from 'http-errors';
import querystring from 'querystring';
import zlib from 'zlib';
import type { $Request } from 'express';
/**
* Provided a "Request" provided by express or connect (typically a node style
* HTTPClientRequest), Promise the body data contained.
*/
export function parseBody(req: $Request): Promise<{ [param: string]: mixed }> {
return new Promise((resolve, reject) => {
const body = req.body;
// If express has already parsed a body as a keyed object, use it.
if (typeof body === 'object' && !(body instanceof Buffer)) {
return resolve((body: any));
}
// Skip requests without content types.
if (req.headers['content-type'] === undefined) {
return resolve({});
}
const typeInfo = contentType.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 resolve(graphqlParser(body));
}
// Already parsed body we didn't recognise? Parse nothing.
if (body) {
return resolve({});
}
// Use the correct body parser based on Content-Type header.
switch (typeInfo.type) {
case 'application/graphql':
return read(req, typeInfo, graphqlParser, resolve, reject);
case 'application/json':
return read(req, typeInfo, jsonEncodedParser, resolve, reject);
case 'application/x-www-form-urlencoded':
return read(req, typeInfo, urlEncodedParser, resolve, reject);
}
// If no Content-Type header matches, parse nothing.
return resolve({});
});
}
function jsonEncodedParser(body) {
if (jsonObjRegex.test(body)) {
/* eslint-disable no-empty */
try {
return JSON.parse(body);
} catch (error) {
// Do nothing
}
/* eslint-enable no-empty */
}
throw httpError(400, 'POST body sent invalid JSON.');
}
function urlEncodedParser(body) {
return querystring.parse(body);
}
function graphqlParser(body) {
return { query: body };
}
/**
* RegExp to match an Object-opening brace "{" as the first non-space
* in a string. Allowed whitespace is defined in RFC 7159:
*
* x20 Space
* x09 Horizontal tab
* x0A Line feed or New line
* x0D Carriage return
*/
const jsonObjRegex = /^[\x20\x09\x0a\x0d]*\{/;
// Read and parse a request body.
function read(req, typeInfo, parseFn, resolve, reject) {
const charset = (typeInfo.parameters.charset || 'utf-8').toLowerCase();
// Assert charset encoding per JSON RFC 7159 sec 8.1
if (charset.slice(0, 4) !== 'utf-') {
throw httpError(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.
getBody(stream, { encoding: charset, length, limit }, (err, body) => {
if (err) {
return reject(
err.type === 'encoding.unsupported'
? httpError(415, `Unsupported charset "${charset.toUpperCase()}".`)
: httpError(400, `Invalid body: ${err.message}.`),
);
}
try {
// Decode and parse body.
return resolve(parseFn(body));
} catch (error) {
return reject(error);
}
});
}
// Return a decompressed stream, given an encoding.
function decompressed(req, encoding) {
switch (encoding) {
case 'identity':
return req;
case 'deflate':
return req.pipe(zlib.createInflate());
case 'gzip':
return req.pipe(zlib.createGunzip());
}
throw httpError(415, `Unsupported content-encoding "${encoding}".`);
}