-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathRequest.cpp
307 lines (260 loc) · 9.72 KB
/
Request.cpp
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#include "Request.h"
using namespace OTF;
// Find the pointers of substrings within the HTTP request and turns them into null-terminated C strings.
Request::Request(char *str, size_t length, bool cloudRequest) {
this->cloudRequest = cloudRequest;
size_t index = 0;
// Parse the HTTP method.
REQ_DEBUG(F("Parsing HTTP method\n"));
for (; index < length; index++) {
if (str[index] == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
requestType = INVALID;
return;
} else if (str[index] == ' ') {
// Null terminate the HTTP method.
str[index] = '\0';
// Move to the first character in the path.
index++;
break;
}
}
if (index >= length) {
// Error if the HTTP method extends to the end of the request.
requestType = INVALID;
return;
}
// Map the string to an enum value.
if (strcmp("GET", &str[0]) == 0) {
this->httpMethod = HTTP_GET;
} else if (strcmp("POST", &str[0]) == 0) {
this->httpMethod = HTTP_POST;
} else if (strcmp("PUT", &str[0]) == 0) {
this->httpMethod = HTTP_PUT;
} else if (strcmp("DELETE", &str[0]) == 0) {
this->httpMethod = HTTP_DELETE;
} else if (strcmp("OPTIONS", &str[0]) == 0) {
this->httpMethod = HTTP_OPTIONS;
} else if (strcmp("PATCH", &str[0]) == 0) {
this->httpMethod = HTTP_PATCH;
} else {
REQ_DEBUG(F("Could not match HTTP method\n"));
// Error if the method isn't a standard method.
requestType = INVALID;
return;
}
char character = str[index];
// TODO handle cases where target isn't always a root path? (https://tools.ietf.org/html/rfc7230#section-5.3.1)
REQ_DEBUG(F("Parsing the request target.\n"));
// Parse the target.
this->path = &str[index];
// Skip over the path.
while (true) {
if (++index >= length) {
// Error if the target extends to the end of the request.
requestType = INVALID;
return;
}
character = str[index];
if (character == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
requestType = INVALID;
return;
} else if (character == '?' || character == '#' || character == ' ') {
// Null terminate the path.
str[index] = '\0';
break;
}
}
if (character == '?') {
// Parse the query.
character = parseQuery(str, length, ++index);
// Exit if an error occurred while parsing the query.
if (character == '\0') {
requestType = INVALID;
return;
}
}
if (character == '#') {
// Skip over the fragment.
while (index < length && (character = str[++index]) != ' ') {
if (character == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
requestType = INVALID;
return;
}
}
}
REQ_DEBUG(F("Finished parsing request target.\n"));
// Move to the first character in the HTTP version.
index++;
// Find the HTTP version.
this->httpVersion = &str[index];
for (; index < length; index++) {
if (str[index] == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
requestType = INVALID;
return;
} else if (str[index] == '\r') {
// Replace the carriage return with a null terminator.
str[index] = '\0';
// Move past the carriage return and assumed line feed.
index += 2;
break;
}
}
// Parse headers until "\r\n\r\n" is encountered (indicates the end of headers) or the end of the string is reached (caused by invalid requests).
while (index < length && str[index] != '\r') {
if (!parseHeader(str, length, index, headers)) {
// Reject the request if an error occurs while parsing headers.
requestType = INVALID;
return;
}
}
// Move past the 2nd consecutive carriage return and assumed line feed.
index += 2;
if (index == length) {
/* If the cursor is exactly 1 character after the end of the request, it means there is no body. Point the `body` pointer
* to the last character in the request (since a value of NULL would indicate that the request was invalid), but set
* `bodyLength` to 0 so it never gets read.
*/
body = &str[index - 1];
requestType = NORMAL;
} else if (index > length) {
// If the cursor is more than 1 character after the end of the request, it means the request was somehow illegally formatted.
requestType = INVALID;
return;
} else {
body = &str[index];
requestType = NORMAL;
}
bodyLength = length - index;
}
char Request::parseQuery(char *str, size_t length, size_t &index) {
REQ_DEBUG(F("Starting to parse query.\n"));
while (index < length) {
char *key = &str[index];
char *value = nullptr;
for (; index < length; index++) {
char character = str[index];
if (character == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
return '\0';
}
// If the end of the parameter has been reached, store it in the map.
if (character == ' ' || character == '#' || character == '&') {
// Null terminate the parameter.
str[index] = '\0';
// Handle parameters without a value.
if (value == nullptr) {
// Replace the null pointer with a pointer to an empty string to differentiate between empty parameters and unspecified parameters.
value = &str[index];
}
decodeQueryParameter(value);
REQ_DEBUG((char *) F("Found query parameter '%s' with value '%s'.\n"), key, value);
queryParams.add(key, value);
if (character == ' ' || character == '#') {
// If the end of the query has been reached, return.
return character;
} else {
// Move to the start of the next parameter key.
index++;
break;
}
} else if (character == '=') {
// Make sure that the character is separating the key and the value (it isn't part of the value).
if (value == nullptr) {
// Null terminate the key.
str[index] = '\0';
value = &str[index + 1];
}
}
}
}
// Error if the query extends to the end of the request.
return '\0';
}
bool Request::parseHeader(char *str, size_t length, size_t &index, LinkedMap<char *> &headers) {
char *lineStart = &str[index];
char *colon = nullptr;
char *value = nullptr;
for (; index < length; index++) {
if (str[index] == '\0') {
// Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks.
return false;
} else if (str[index] == ':' && colon == nullptr) {
colon = &str[index];
str[index] = '\0';
} else if (str[index] == '\r') {
if (colon == nullptr) {
// Error if there is an illegal header line that doesn't contain a colon.
return false;
}
// Terminate the header value.
str[index] = '\0';
// Handle headers without a value.
if (value == nullptr) {
// Replace the null pointer with a pointer to an empty string to differentiate between empty header and unspecified headers.
value = &str[index];
}
// Trim trailing whitespace from the header value (https://tools.ietf.org/html/rfc7230#section-3.2.4).
for (size_t i = index; i > 0; i--) {
// Replace whitespace with null terminators so the value string will terminate at the first space after it.
if (isspace(str[index])) {
str[index] = '\0';
} else {
break;
}
}
REQ_DEBUG((char *) F("Found header '%s' with value '%s'.\n"), lineStart, value);
// TODO handle duplicate header fields by concatenating them with a comma.
headers.add(lineStart, value);
// Move past the carriage return and assumed line feed.
index += 2;
break;
} else if (colon != nullptr && value == nullptr && !isspace(str[index])) {
// Mark the location of the header value after skipping whitespace.
value = &str[index];
} else if (value == nullptr) {
// Make the header name lowercase.
str[index] = tolower(str[index]);
}
}
return true;
}
void Request::decodeQueryParameter(char *value) {
unsigned int offset = 0;
unsigned int index = 0;
while (value[index + offset] != '\0') {
REQ_DEBUG((char *) F("Index is %d and offset is %d\n"), index, offset);
char character = value[index + offset];
if (character == '+') {
character = ' ';
} else if (character == '%') {
char highDigit = value[index + ++offset];
char lowDigit = value[index + ++offset];
if (highDigit == '\0' || lowDigit == '\0') {
// Abort decoding because the query string is illegally formatted.
return;
}
char hex[3] = {highDigit, lowDigit, '\0'};
character = strtol(hex, nullptr, 16);
}
value[index] = character;
index++;
}
value[index] = '\0';
}
char *Request::getPath() const { return path; }
#if defined(ARDUINO)
char *Request::getQueryParameter(const __FlashStringHelper *key) const { return queryParams.find(key); }
#endif
char *Request::getQueryParameter(const char *key) const { return queryParams.find(key); }
char *Request::getHeader(const char *key) const { return headers.find(key); }
#if defined(ARDUINO)
char *Request::getHeader(const __FlashStringHelper *key) const { return headers.find(key); }
#endif
char *Request::getBody() const { return body; }
size_t Request::getBodyLength() const { return bodyLength; }
RequestType Request::getType() const { return requestType; }
bool Request::isCloudRequest() const { return cloudRequest; }