-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathHTTPMessage.cpp
More file actions
executable file
·363 lines (304 loc) · 10.7 KB
/
HTTPMessage.cpp
File metadata and controls
executable file
·363 lines (304 loc) · 10.7 KB
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/**
ByteBuffer
HTTPMessage.cpp
Copyright 2011-2025 Ramsey Kant
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "HTTPMessage.h"
#include <algorithm>
#include <string>
#include <format>
#include <memory>
#include <print>
#include <cctype> // to std::tolower
HTTPMessage::HTTPMessage() : ByteBuffer(4096) {
}
HTTPMessage::HTTPMessage(std::string const& sData) : ByteBuffer(sData.size() + 1) {
putBytes((const uint8_t* const)sData.c_str(), sData.size() + 1);
}
HTTPMessage::HTTPMessage(const uint8_t* pData, uint32_t len) : ByteBuffer(pData, len) {
}
/**
* Put Line
* Append a line (string) to the backing ByteBuffer at the current position
*
* @param str String to put into the ByteBuffer
* @param crlf_end If true (default), end the line with a \r\n
*/
void HTTPMessage::putLine(std::string str, bool crlf_end) {
// Terminate with crlf if flag set
if (crlf_end)
str += "\r\n";
// Put the entire contents of str into the buffer
putBytes((const uint8_t* const)str.c_str(), str.size());
}
/**
* Put Headers
* Write all headers currently in the 'headers' map to the ByteBuffer.
* 'Header: value'
*/
void HTTPMessage::putHeaders() {
for (auto const &[key, value] : headers) {
putLine(std::format("{}: {}", key, value), true);
}
// End with a blank line
putLine();
}
/**
* Get Line
* Retrive the entire contents of a line: string from current position until CR or LF, whichever comes first, then increment the read position
* until it's past the last CR or LF in the line
*
* @return Contents of the line in a string (without CR or LF)
*/
std::string HTTPMessage::getLine() {
std::string ret = "";
int32_t startPos = getReadPos();
bool newLineReached = false;
char c = 0;
// Append characters to the return std::string until we hit the end of the buffer, a CR (13) or LF (10)
for (uint32_t i = startPos; i < size(); i++) {
// If the next byte is a \r or \n, we've reached the end of the line and should break out of the loop
c = peek();
if ((c == 13) || (c == 10)) {
newLineReached = true;
break;
}
// Otherwise, append the next character to the std::string
ret += getChar();
}
// If a line termination was never reached, discard the result and conclude there are no more lines to parse
if (!newLineReached) {
setReadPos(startPos); // Reset the position to before the last line read that we are now discarding
ret = "";
return ret;
}
// Increment the read position until the end of a CR or LF chain, so the read position will then point to the next line
// Also, only read a maximum of 2 characters so as to not skip a blank line that is only \r\n
uint32_t k = 0;
for (uint32_t i = getReadPos(); i < size(); i++) {
if (k++ >= 2)
break;
c = getChar();
if ((c != 13) && (c != 10)) {
// Set the Read position back one because the retrived character wasn't a LF or CR
setReadPos(getReadPos() - 1);
break;
}
}
return ret;
}
/**
* getStrElement
* Get a token from the current buffer, stopping at the delimiter. Returns the token as a string
*
* @param delim The delimiter to stop at when retriving the element. By default, it's a space
* @return Token found in the buffer. Empty if delimiter wasn't reached
*/
std::string HTTPMessage::getStrElement(char delim) {
int32_t startPos = getReadPos();
if (startPos < 0)
return "";
int32_t endPos = find(delim, startPos);
if (endPos < 0)
return "";
if (startPos > endPos)
return "";
// Calculate the size based on the found ending position
uint32_t size = (endPos + 1) - startPos;
if (size <= 0)
return "";
// Grab the std::string from the ByteBuffer up to the delimiter
auto str = std::make_unique<char[]>(size);
getBytes((uint8_t*)str.get(), size);
str[size - 1] = 0x00; // NULL termination
std::string ret = str.get();
// Increment the read position PAST the delimiter
setReadPos(endPos + 1);
return ret;
}
/**
* Parse Headers
* When an HTTP message (request & response) has reached the point where headers are present, this method
* should be called to parse and populate the internal map of headers.
* Parse headers will move the read position past the blank line that signals the end of the headers
*/
void HTTPMessage::parseHeaders() {
std::string hline = "";
std::string app = "";
// Get the first header
hline = getLine();
// Keep pulling headers until a blank line has been reached (signaling the end of headers)
while (hline.size() > 0) {
// Case where values are on multiple lines ending with a comma
app = hline;
while (app[app.size() - 1] == ',') {
app = getLine();
hline += app;
}
addHeader(hline);
hline = getLine();
}
}
/**
* Parse Body
* Parses everything after the headers section of an HTTP message. Handles chuncked responses/requests
*
* @return True if successful. False on error, parseErrorStr is set with a reason
*/
bool HTTPMessage::parseBody() {
// Content-Length should exist (size of the Body data) if there is body data
std::string hlenstr = "";
hlenstr = getHeaderValue("Content-Length");
// No body data to read:
if (hlenstr.empty())
return true;
uint32_t remainingLen = bytesRemaining();
uint32_t contentLen = atoi(hlenstr.c_str());
// contentLen should NOT exceed the remaining number of bytes in the buffer
// Add 1 to bytesRemaining so it includes the byte at the current read position
if (contentLen > remainingLen + 1) {
// If it exceeds, there's a potential security issue and we can't reliably parse
parseErrorStr = std::format("Content-Length ({}) is greater than remaining bytes ({})", hlenstr, remainingLen);
return false;
} else if (remainingLen > contentLen) {
parseErrorStr = std::format("ByteBuffer remaining size to read ({}) is greater than provided Content-Length {}", remainingLen, contentLen);
return false;
} else if (contentLen == 0) {
// Nothing to read, which is fine
return true;
} else {
// Otherwise, we can probably trust Content-Length is valid and read the specificed number of bytes
this->dataLen = contentLen;
}
// Create a big enough buffer to store the data
this->data = new uint8_t[this->dataLen];
// Grab all the bytes from the current position to the end
uint32_t dIdx = 0;
for (uint32_t i = getReadPos(); i < remainingLen; i++) {
this->data[dIdx] = get(i);
dIdx++;
}
// We could handle chunked Request/Response parsing (with footers) here, but, we won't.
return true;
}
/**
* Add Header to the Map from string
* Takes a formatted header string "Header: value", parse it, and put it into the std::map as a key,value pair.
*
* @param string containing formatted header: value
*/
void HTTPMessage::addHeader(std::string const& line) {
size_t kpos = line.find(':');
if (kpos == std::string::npos) {
std::print("Could not addHeader: {}\n", line);
return;
}
// We're choosing to reject HTTP Header keys longer than 32 characters
if (kpos > 32)
return;
std::string key = line.substr(0, kpos);
if (key.empty())
return;
int32_t value_len = line.size() - kpos - 1;
if (value_len <= 0)
return;
// We're choosing to reject HTTP header values longer than 4kb
if (value_len > 4096)
return;
std::string value = line.substr(kpos + 1, value_len);
// Skip all leading spaces in the value
int32_t i = 0;
while (i < value.size() && value.at(i) == 0x20) {
i++;
}
value = value.substr(i, value.size());
if (value.empty())
return;
// Add header to the map
addHeader(key, value);
}
/**
* Add header key-value std::pair to the map
*
* @param key String representation of the Header Key
* @param value String representation of the Header value
*/
void HTTPMessage::addHeader(std::string const& key, std::string const& value) {
headers.try_emplace(key, value);
}
/**
* Add header key-value std::pair to the map (Integer value)
* Integer value is converted to a string
*
* @param key String representation of the Header Key
* @param value Integer representation of the Header value
*/
void HTTPMessage::addHeader(std::string const& key, int32_t value) {
headers.try_emplace(key, std::format("{}", value));
}
/**
* Get Header Value
* Given a header name (key), return the value associated with it in the headers map
*
* @param key Key to identify the header
*/
std::string HTTPMessage::getHeaderValue(std::string const& key) const {
// Lookup in map
auto it = headers.find(key);
// Key wasn't found, try an all lowercase variant as some clients won't always use proper capitalization
if (it == headers.end()) {
auto key_lower = std::string(key);
std::ranges::transform(key_lower.begin(), key_lower.end(), key_lower.begin(),
[](unsigned char c){ return std::tolower(c); });
// Still not found, return empty string to indicate the Header value doesnt exist
it = headers.find(key_lower);
if (it == headers.end())
return "";
}
// Otherwise, return the value
return it->second;
}
/**
* Get Header String
* Get the full formatted header string "Header: value" from the headers map at position index
*
* @param index Position in the headers map to retrieve a formatted header string
* @ret Formatted string with header name and value
*/
std::string HTTPMessage::getHeaderStr(int32_t index) const {
int32_t i = 0;
std::string ret = "";
for (auto const &[key, value] : headers) {
if (i == index) {
ret = std::format("{}: {}", key, value);
break;
}
i++;
}
return ret;
}
/**
* Get Number of Headers
* Return the number of headers in the headers map
*
* @return size of the map
*/
uint32_t HTTPMessage::getNumHeaders() const {
return headers.size();
}
/**
* Clear Headers
* Removes all headers from the internal map
*/
void HTTPMessage::clearHeaders() {
headers.clear();
}