HTTP v1.13.1
Loading...
Searching...
No Matches
HTTPParser.cpp
Go to the documentation of this file.
1#include "HTTPParser.h"
2
3#include <iterator>
4#include <format>
5#include <algorithm>
6#include <unordered_set>
7#include <functional>
8
10
11#ifndef __LINUX__
12#pragma warning(disable: 26800)
13#endif
14
15using namespace std;
16
17static const unordered_set<string> methods =
18{
19 "HEAD",
20 "PUT",
21 "POST",
22 "PATCH",
23 "OPTIONS",
24 "DELETE",
25 "CONNECT",
26 "GET",
27 "TRACE"
28};
29
30namespace web
31{
32 const unordered_map<string_view, function<void(HTTPParser&, string_view)>> HTTPParser::contentTypeParsers =
33 {
34 { HTTPParser::urlEncoded, [](HTTPParser& parser, string_view data) { parser.parseQueryParameter(data); }},
35 { HTTPParser::jsonEncoded, [](HTTPParser& parser, string_view data) { parser.jsonParser.setJSONData(data); }},
36 { HTTPParser::multipartEncoded, [](HTTPParser& parser, string_view data) { parser.parseMultipart(data); }},
37 };
38
39 HTTPParser::ReadOnlyBuffer::ReadOnlyBuffer(string_view view)
40 {
41 char* data = const_cast<char*>(view.data());
42
43 setg(data, data, data + view.size());
44 }
45
46 string HTTPParser::mergeChunks() const
47 {
48 string result;
49
50 result.reserve(chunksSize);
51
52 ranges::for_each(chunks, [&result](const string& value) { result += value; });
53
54 return result;
55 }
56
57 void HTTPParser::parseQueryParameter(string_view rawParameters)
58 {
59 string key;
60 string value;
61 bool equal = false;
62
63 if (rawParameters.find("HTTP") != string_view::npos)
64 {
65 rawParameters.remove_suffix(httpVersion.size());
66 }
67
68 string decodedParameters = web::decodeUrl(rawParameters);
69
70 for (size_t nextKeyValuePair = 0; nextKeyValuePair < decodedParameters.size(); nextKeyValuePair++)
71 {
72 if (decodedParameters[nextKeyValuePair] == '&')
73 {
74 equal = false;
75
76 queryParameters.try_emplace(move(key), move(value));
77
78 continue;
79 }
80
81 if (!equal)
82 {
83 equal = decodedParameters[nextKeyValuePair] == '=';
84
85 if (equal)
86 {
87 continue;
88 }
89 }
90
91 equal ? value += decodedParameters[nextKeyValuePair] : key += decodedParameters[nextKeyValuePair];
92 }
93
94 queryParameters.try_emplace(move(key), move(value));
95 }
96
97 void HTTPParser::parseMultipart(string_view data)
98 {
99 constexpr string_view boundaryText = "boundary=";
100
101 const string& contentType = headers["Content-Type"];
102 size_t index = contentType.find(boundaryText);
103 string boundary = format("--{}", string_view(contentType.begin() + index + boundaryText.size(), contentType.end()));
104 boyer_moore_horspool_searcher searcher(boundary.begin(), boundary.end());
105 string_view::const_iterator current = search(data.begin(), data.end(), searcher);
106
107 while (true)
108 {
109 string_view::const_iterator next = search(current + 1, data.end(), searcher);
110
111 if (next == data.end())
112 {
113 break;
114 }
115
116 multiparts.emplace_back(string_view(current + boundary.size(), next));
117
118 current = search(next, data.end(), searcher);
119 }
120 }
121
122 void HTTPParser::parseContentType()
123 {
124 if (auto it = headers.find(contentTypeHeader); it != headers.end())
125 {
126 for (const auto& [encodeType, parser] : contentTypeParsers)
127 {
128 if (it->second.find(encodeType) != string::npos)
129 {
130 parser(*this, chunksSize ? this->mergeChunks() : body);
131
132 break;
133 }
134 }
135 }
136 }
137
138 void HTTPParser::parseChunkEncoded(string_view HTTPMessage, bool isUTF8)
139 {
140 size_t chunksStart = HTTPMessage.find(crlfcrlf) + crlfcrlf.size();
141 size_t chunksEnd = HTTPMessage.rfind(crlfcrlf) + crlfcrlf.size();
142 ReadOnlyBuffer buffer(string_view(HTTPMessage.data() + chunksStart, chunksEnd - chunksStart));
143 istringstream chunksData;
144
145 static_cast<ios&>(chunksData).rdbuf(&buffer);
146
147 chunksSize = 0;
148
149 while (true)
150 {
151 string size;
152 string value;
153
154 getline(chunksData, size);
155
156 size.pop_back(); // \r symbol from \r\n
157
158 value.resize(stol(size, nullptr, 16));
159
160 if (value.empty())
161 {
162 return;
163 }
164
165 chunksData.read(value.data(), value.size());
166
167 chunksData.ignore(crlf.size());
168
169 string& chunk = isUTF8 ?
170 chunks.emplace_back(json::utility::toUTF8JSON(value, CP_UTF8)) :
171 chunks.emplace_back(move(value));
172
173 chunksSize += chunk.size();
174 }
175 }
176
178 chunksSize(0),
179 parsed(false)
180 {
181
182 }
183
184 HTTPParser::HTTPParser(const string& HTTPMessage)
185 {
186 this->parse(HTTPMessage);
187 }
188
189 HTTPParser::HTTPParser(const vector<char>& HTTPMessage)
190 {
191 this->parse(string_view(HTTPMessage.data(), HTTPMessage.size()));
192 }
193
194 void HTTPParser::parse(string_view HTTPMessage)
195 {
196 if (HTTPMessage.empty())
197 {
198 parsed = false;
199
200 return;
201 }
202
203 parsed = true;
204
205 size_t prevString = 0;
206 size_t nextString = HTTPMessage.find('\r');
207 string_view firstString(HTTPMessage.data(), nextString);
208
209 if (string_view temp = firstString.substr(0, firstString.find(' ')); temp.find("HTTP") == string_view::npos)
210 {
211 method = temp;
212
213 if (methods.find(method) == methods.end())
214 {
215 throw exceptions::HTTPParseException(format("Wrong method: {}", method));
216 }
217 }
218
219 chunksSize = 0;
220
221 rawData = HTTPMessage;
222
223 if (method.empty())
224 {
225 ReadOnlyBuffer buffer(firstString);
226 istringstream data;
227 string responseCode;
228
229 static_cast<ios&>(data).rdbuf(&buffer);
230
231 data >> httpVersion >> responseCode >> response.second;
232
233 response.first = stoi(responseCode);
234 }
235 else if (method != "CONNECT")
236 {
237 size_t startParameters = firstString.find('/');
238
239 if (startParameters == string::npos)
240 {
241 throw exceptions::HTTPParseException("Can't find /");
242 }
243
244 startParameters++;
245
246 size_t endParameters = firstString.rfind(' ');
247 size_t queryStart = firstString.find('?');
248
249 parameters = web::decodeUrl(string_view(firstString.begin() + startParameters, firstString.begin() + endParameters));
250
251 httpVersion = string(firstString.begin() + firstString.find("HTTP"), firstString.end());
252
253 if (queryStart != string::npos)
254 {
255 this->parseQueryParameter(string_view(firstString.data() + queryStart + 1, firstString.data() + endParameters));
256 }
257 }
258 else
259 {
260 parameters = string(firstString.begin() + firstString.find(' ') + 1, firstString.begin() + firstString.rfind(' '));
261
262 httpVersion = string(firstString.begin() + firstString.find("HTTP"), firstString.end());
263 }
264
265 while (true)
266 {
267 prevString = nextString + crlf.size();
268 nextString = HTTPMessage.find('\r', prevString);
269
270 string_view next(HTTPMessage.data() + prevString, nextString - prevString);
271
272 if (prevString == nextString || nextString == string::npos)
273 {
274 break;
275 }
276
277 size_t colonIndex = next.find(':');
278 size_t nonSpace = colonIndex + 1;
279 string header(next.begin(), next.begin() + colonIndex);
280
281 while (next.size() > nonSpace && isspace(next[nonSpace]))
282 {
283 nonSpace++;
284 }
285
286 string value(next.begin() + nonSpace, next.end());
287
288 headers.try_emplace(move(header), move(value));
289 }
290
291 bool isUTF8 = HTTPMessage.find(utf8Encoded) != string::npos;
292
293 if (auto it = headers.find(transferEncodingHeader); it != headers.end())
294 {
295 static const unordered_map<string, void (HTTPParser::*)(string_view HTTPMessage, bool isUTF8)> transferTypeParsers =
296 {
297 { chunkEncoded, &HTTPParser::parseChunkEncoded }
298 };
299
300 if (!transferTypeParsers.contains(it->second))
301 {
302 throw exceptions::HTTPParseException("Not supported transfer encoding: " + it->second);
303 }
304
305 invoke(transferTypeParsers.at(it->second), *this, HTTPMessage, isUTF8);
306 }
307 else if (headers.find(contentLengthHeader) != headers.end())
308 {
309 if (isUTF8)
310 {
311 body = json::utility::toUTF8JSON(string(HTTPMessage.begin() + HTTPMessage.find(crlfcrlf) + crlfcrlf.size(), HTTPMessage.end()), CP_UTF8);
312 }
313 else
314 {
315 body = string(HTTPMessage.begin() + HTTPMessage.find(crlfcrlf) + crlfcrlf.size(), HTTPMessage.end());
316 }
317 }
318
319 this->parseContentType();
320 }
321
322 const string& HTTPParser::getMethod() const
323 {
324 return method;
325 }
326
328 {
329 return stod(httpVersion.substr(5));
330 }
331
332 const string& HTTPParser::getParameters() const
333 {
334 return parameters;
335 }
336
337 const unordered_map<string, string>& HTTPParser::getQueryParameters() const
338 {
339 return queryParameters;
340 }
341
342 const pair<int, string>& HTTPParser::getFullResponse() const
343 {
344 return response;
345 }
346
348 {
349 return response.first;
350 }
351
352 const string& HTTPParser::getResponseMessage() const
353 {
354 return response.second;
355 }
356
358 {
359 return headers;
360 }
361
362 const string& HTTPParser::getBody() const
363 {
364 return body;
365 }
366
367 const vector<string>& HTTPParser::getChunks() const
368 {
369 return chunks;
370 }
371
372 const json::JSONParser& HTTPParser::getJSON() const
373 {
374 return jsonParser;
375 }
376
377 const string& HTTPParser::getRawData() const
378 {
379 return rawData;
380 }
381
382 const vector<Multipart>& HTTPParser::getMultiparts() const
383 {
384 return multiparts;
385 }
386
387 HTTPParser::operator bool() const
388 {
389 return parsed;
390 }
391
392 ostream& operator << (ostream& outputStream, const HTTPParser& parser)
393 {
394 string result;
395
396 if (parser.method.size())
397 {
398 if (parser.method != "CONNECT")
399 {
400 result += format("{} /{} {}", parser.method, parser.parameters, parser.httpVersion);
401 }
402 else
403 {
404 result += format("{} {} {}", parser.method, parser.parameters, parser.httpVersion);
405 }
406 }
407 else
408 {
409 const auto& [code, message] = parser.getFullResponse();
410
411 result += format("{} {} {}", parser.httpVersion, static_cast<int>(code), message);
412 }
413
414 result += HTTPParser::crlf;
415
416 for (const auto& [header, value] : parser.headers)
417 {
418 result += format("{}: {}{}", header, value, HTTPParser::crlf);
419 }
420
421 if (parser.body.size())
422 {
423 result += format("{}{}", HTTPParser::crlf, parser.body);
424 }
425 else if (parser.chunks.size())
426 {
427 result += HTTPParser::crlf;
428
429 for (const auto& chunk : parser.chunks)
430 {
431 result += format("{}{}{}", (ostringstream() << hex << chunk.size() << HTTPParser::crlf).str(), chunk, HTTPParser::crlf);
432 }
433
434 result += format("0{}", HTTPParser::crlfcrlf);
435 }
436
437 if (!result.ends_with(HTTPParser::crlfcrlf))
438 {
439 if (result.ends_with(HTTPParser::crlf))
440 {
441 result += HTTPParser::crlf;
442 }
443 else
444 {
445 result += HTTPParser::crlfcrlf;
446 }
447 }
448
449 return outputStream << result;
450 }
451
452 istream& operator >> (istream& inputStream, HTTPParser& parser)
453 {
454 istreambuf_iterator<char> it(inputStream);
455 string httpMessage(it, {});
456
457 if (httpMessage.size())
458 {
459 parser.parse(httpMessage);
460 }
461
462 return inputStream;
463 }
464}
HTTP parser.
Definition HTTPParser.h:14
const std::string & getParameters() const
static const std::string chunkEncoded
Definition HTTPParser.h:29
const std::string & getBody() const
void parse(std::string_view HTTPMessage)
static constexpr std::string_view multipartEncoded
Definition HTTPParser.h:36
static constexpr std::string_view urlEncoded
Definition HTTPParser.h:34
const std::string & getMethod() const
int getResponseCode() const
const std::unordered_map< std::string, std::string > & getQueryParameters() const
static constexpr std::string_view crlfcrlf
Definition HTTPParser.h:30
static constexpr std::string_view crlf
Definition HTTPParser.h:31
const HeadersMap & getHeaders() const
const std::pair< int, std::string > & getFullResponse() const
static const std::string transferEncodingHeader
Definition HTTPParser.h:27
static constexpr std::string_view jsonEncoded
Definition HTTPParser.h:35
const std::vector< std::string > & getChunks() const
const std::string & getRawData() const
static const std::string contentTypeHeader
Definition HTTPParser.h:26
static const std::string utf8Encoded
Definition HTTPParser.h:28
double getHTTPVersion() const
const std::vector< Multipart > & getMultiparts() const
const json::JSONParser & getJSON() const
const std::string & getResponseMessage() const
static const std::string contentLengthHeader
Definition HTTPParser.h:25
std::unordered_map< std::string, std::string, InsensitiveStringHash, InsensitiveStringEqual > HeadersMap
Case insensitive unordered_map.
ostream & operator<<(ostream &outputStream, const HTTPBuilder &builder)
string decodeUrl(string_view data)
istream & operator>>(istream &inputStream, HTTPParser &parser)