forked from RamseyK/httpserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHTTPMessage.cpp
More file actions
executable file
·360 lines (303 loc) · 9.68 KB
/
HTTPMessage.cpp
File metadata and controls
executable file
·360 lines (303 loc) · 9.68 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
/**
ByteBuffer
HTTPMessage.cpp
Copyright 2011-2019 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"
HTTPMessage::HTTPMessage() : ByteBuffer(4096) {
this->init();
}
HTTPMessage::HTTPMessage(std::string sData) : ByteBuffer(sData.size() + 1) {
putBytes((byte*)sData.c_str(), sData.size() + 1);
this->init();
}
HTTPMessage::HTTPMessage(byte* pData, unsigned int len) : ByteBuffer(pData, len) {
this->init();
}
HTTPMessage::~HTTPMessage() {
headers->clear();
delete headers;
}
void HTTPMessage::init() {
parseErrorStr = "";
data = NULL;
dataLen = 0;
version = DEFAULT_HTTP_VERSION; // By default, all create() will indicate the version is whatever DEFAULT_HTTP_VERSION is
headers = new std::map<std::string, std::string>();
}
/**
* Put Line
* Append a line (string) to the backing ByteBuffer at the current position
*
* @param str String to put into the byte buffer
* @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((byte*)str.c_str(), str.size());
}
/**
* Put Headers
* Write all headers currently in the 'headers' map to the ByteBuffer.
* 'Header: value'
*/
void HTTPMessage::putHeaders() {
std::map<std::string, std::string>::const_iterator it;
for (it = headers->begin(); it != headers->end(); it++) {
std::string final = it->first + ": " + it->second;
putLine(final, 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 = "";
int 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 (unsigned int 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
unsigned int k = 0;
for (unsigned int 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) {
std::string ret = "";
int startPos = getReadPos();
unsigned int size = 0;
int endPos = find(delim, startPos);
// Calculate the size based on the found ending position
size = (endPos + 1) - startPos;
if ((endPos == -1) || (size <= 0))
return "";
// Grab the std::string from the byte buffer up to the delimiter
char *str = new char[size];
getBytes((byte*)str, size);
str[size - 1] = 0x00; // NULL termination
ret.assign(str);
// 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 = "", 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 = "";
unsigned int contentLen = 0;
hlenstr = getHeaderValue("Content-Length");
// No body data to read:
if (hlenstr.empty())
return true;
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 > bytesRemaining() + 1) {
/*
// If it exceeds, read only up to the number of bytes remaining
dataLen = bytesRemaining();
*/
// If it exceeds, there's a potential security issue and we can't reliably parse
std::stringstream pes;
pes << "Content-Length (" << hlenstr << ") is greater than remaining bytes (" << bytesRemaining() << ")";
parseErrorStr = pes.str();
return false;
} else {
// Otherwise, we ca probably trust Content-Length is valid and read the specificed number of bytes
dataLen = contentLen;
}
// Create a big enough buffer to store the data
unsigned int dIdx = 0, s = size();
data = new byte[dataLen];
// Grab all the bytes from the current position to the end
for (unsigned int i = getReadPos(); i < s; i++) {
data[dIdx] = get(i);
dIdx++;
}
// TODO: Handle chuncked Request/Response parsing (with footers) here
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 line) {
std::string key = "", value = "";
size_t kpos;
int i = 0;
kpos = line.find(':');
if (kpos == std::string::npos) {
std::cout << "Could not addHeader: " << line.c_str() << std::endl;
return;
}
key = line.substr(0, kpos);
value = line.substr(kpos + 1, line.size() - kpos - 1);
// Skip all leading spaces in the value
while (i < value.size() && value.at(i) == 0x20) {
i++;
}
value = value.substr(i, value.size());
// 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 key, std::string value) {
headers->insert(std::pair<std::string, std::string>(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 key, int value) {
std::stringstream sz;
sz << value;
headers->insert(std::pair<std::string, std::string>(key, sz.str()));
}
/**
* 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 key) {
char c;
std::string key_lower = "";
// 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()) {
for (int i = 0; i < key.length(); i++) {
c = key.at(i);
key_lower += 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
*
* @ret Formatted string with header name and value
*/
std::string HTTPMessage::getHeaderStr(int index) {
int i = 0;
std::string ret = "";
std::map<std::string, std::string>::const_iterator it;
for (it = headers->begin(); it != headers->end(); it++) {
if (i == index) {
ret = it->first + ": " + it->second;
break;
}
i++;
}
return ret;
}
/**
* Get Number of Headers
* Return the number of headers in the headers map
*
* @return size of the map
*/
int HTTPMessage::getNumHeaders() {
return headers->size();
}
/**
* Clear Headers
* Removes all headers from the internal map
*/
void HTTPMessage::clearHeaders() {
headers->clear();
}