6#include <unordered_set>
9#include "HttpParserException.h"
12#pragma warning(disable: 26800)
15static const std::unordered_set<std::string> methods =
30 const std::unordered_map<std::string_view, std::function<void(HttpParser&, std::string_view)>> HttpParser::contentTypeParsers =
32 { HttpParser::urlEncoded, [](HttpParser& parser, std::string_view data) { parser.parseQueryParameter(data); }},
33 { HttpParser::jsonEncoded, [](HttpParser& parser, std::string_view data) { parser.jsonParser.setJSONData(data); }},
34 { HttpParser::multipartEncoded, [](HttpParser& parser, std::string_view data) { parser.parseMultipart(data); }},
37 HttpParser::ReadOnlyBuffer::ReadOnlyBuffer(std::string_view view)
39 char* data =
const_cast<char*
>(view.data());
41 setg(data, data, data + view.size());
44 std::string HttpParser::mergeChunks()
const
48 result.reserve(chunksSize);
50 std::ranges::for_each(chunks, [&result](
const std::string& value) { result += value; });
55 void HttpParser::parseQueryParameter(std::string_view rawParameters)
61 if (rawParameters.find(
"HTTP") != std::string_view::npos)
63 rawParameters.remove_suffix(httpVersion.size());
68 for (
size_t nextKeyValuePair = 0; nextKeyValuePair < decodedParameters.size(); nextKeyValuePair++)
70 if (decodedParameters[nextKeyValuePair] ==
'&')
74 queryParameters.try_emplace(move(key), move(value));
81 equal = decodedParameters[nextKeyValuePair] ==
'=';
89 equal ? value += decodedParameters[nextKeyValuePair] : key += decodedParameters[nextKeyValuePair];
92 queryParameters.try_emplace(move(key), move(value));
95 void HttpParser::parseMultipart(std::string_view data)
97 constexpr std::string_view boundaryText =
"boundary=";
99 const std::string& contentType = headers[
"Content-Type"];
100 size_t index = contentType.find(boundaryText);
102 if (index == std::string_view::npos)
104 throw std::runtime_error(std::format(
"Can't find {}", boundaryText));
107 std::string boundary = std::format(
"--{}", std::string_view(contentType.begin() + index + boundaryText.size(), contentType.end()));
108 std::boyer_moore_horspool_searcher searcher(boundary.begin(), boundary.end());
109 std::string_view::const_iterator current = std::search(data.begin(), data.end(), searcher);
113 std::string_view::const_iterator next = std::search(current + 1, data.end(), searcher);
115 if (next == data.end())
120 multiparts.emplace_back(std::string_view(current + boundary.size(), next));
122 current = std::search(next, data.end(), searcher);
126 void HttpParser::parseContentType()
128 if (
auto it = headers.find(contentTypeHeader); it != headers.end())
130 for (
const auto& [encodeType, parser] : contentTypeParsers)
132 if (it->second.find(encodeType) != std::string::npos)
134 parser(*
this, chunksSize ? this->mergeChunks() : body);
142 void HttpParser::parseChunkEncoded(std::string_view httpMessage,
bool isUTF8)
144 size_t chunksStart = httpMessage.find(crlfcrlf);
146 if (chunksStart == std::string_view::npos)
148 throw std::runtime_error(std::format(
"Can't find chunks start in {}", httpMessage));
151 chunksStart += crlfcrlf.size();
153 if (chunksStart >= httpMessage.size())
155 throw std::runtime_error(std::format(
"Wrong stat chunks format in {}", httpMessage));
158 size_t chunksEnd = httpMessage.rfind(crlfcrlf);
160 if (chunksEnd == std::string_view::npos)
162 throw std::runtime_error(std::format(
"Can't find chunks start in {}", httpMessage));
165 chunksEnd += crlfcrlf.size();
167 if (chunksEnd > httpMessage.size())
169 throw std::runtime_error(std::format(
"Wrong end chunks format in {}", httpMessage));
172 ReadOnlyBuffer buffer(std::string_view(httpMessage.data() + chunksStart, chunksEnd - chunksStart));
173 std::istringstream chunksData;
175 static_cast<std::ios&
>(chunksData).rdbuf(&buffer);
184 std::getline(chunksData, size);
188 value.resize(std::stol(size,
nullptr, 16));
195 chunksData.read(value.data(), value.size());
197 chunksData.ignore(constants::crlf.size());
199 std::string& chunk = isUTF8 ?
200 chunks.emplace_back(json::utility::toUTF8JSON(value, CP_UTF8)) :
201 chunks.emplace_back(move(value));
203 chunksSize += chunk.size();
207 HttpParser::HttpParser() :
214 HttpParser::HttpParser(
const std::string& httpMessage)
216 this->parse(httpMessage);
219 HttpParser::HttpParser(
const std::vector<char>& httpMessage)
221 this->parse(std::string_view(httpMessage.data(), httpMessage.size()));
224 void HttpParser::parse(std::string_view httpMessage)
226 if (httpMessage.empty())
235 size_t prevString = 0;
236 size_t nextString = httpMessage.find(
'\r');
238 if (nextString == std::string_view::npos)
240 throw std::runtime_error(std::format(
"Can't find next string: {}", httpMessage));
243 std::string_view firstString(httpMessage.data(), nextString);
245 if (std::string_view temp = firstString.substr(0, firstString.find(
' ')); temp.find(
"HTTP") == std::string_view::npos)
249 if (methods.find(method) == methods.end())
251 throw exceptions::HttpParserException(std::format(
"Wrong method: {}", method));
257 rawData = httpMessage;
261 ReadOnlyBuffer buffer(firstString);
262 std::istringstream data;
263 std::string responseCode;
265 static_cast<std::ios&
>(data).rdbuf(&buffer);
267 data >> httpVersion >> responseCode >> response.second;
269 response.first = std::stoi(responseCode);
271 else if (method !=
"CONNECT")
273 size_t startParameters = firstString.find(
'/');
275 if (startParameters == std::string::npos)
277 throw exceptions::HttpParserException(
"Can't find /");
282 size_t endParameters = firstString.rfind(
' ');
284 if (endParameters == std::string_view::npos)
286 throw std::runtime_error(std::format(
"Can't find end parameters in: {}", firstString));
289 size_t queryStart = firstString.find(
'?');
291 if (queryStart == std::string_view::npos)
293 throw std::runtime_error(std::format(
"Can't find query start in: {}", firstString));
296 size_t httpStartIndex = firstString.find(
"HTTP");
298 if (httpStartIndex == std::string_view::npos)
300 throw std::runtime_error(std::format(
"Can't find HTTP in: {}", firstString));
303 parameters =
web::decodeUrl(std::string_view(firstString.begin() + startParameters, firstString.begin() + endParameters));
304 httpVersion = std::string(firstString.begin() + httpStartIndex, firstString.end());
306 if (queryStart != std::string::npos)
308 this->parseQueryParameter(std::string_view(firstString.data() + queryStart + 1, firstString.data() + endParameters));
313 size_t space = firstString.find(
' ');
315 if (space == std::string_view::npos)
317 throw std::runtime_error(std::format(
"Can't find first space in {}", firstString));
320 size_t lastSpace = firstString.rfind(
' ');
322 if (lastSpace == std::string_view::npos)
324 throw std::runtime_error(std::format(
"Can't find first last space in {}", firstString));
327 size_t httpStartIndex = firstString.find(
"HTTP");
329 if (httpStartIndex == std::string_view::npos)
331 throw std::runtime_error(std::format(
"Can't find HTTP in: {}", firstString));
334 parameters = std::string(firstString.begin() + space + 1, firstString.begin() + lastSpace);
335 httpVersion = std::string(firstString.begin() + httpStartIndex, firstString.end());
340 prevString = nextString + constants::crlf.size();
341 nextString = httpMessage.find(
'\r', prevString);
343 if (prevString == nextString || nextString == std::string::npos)
348 std::string_view next(httpMessage.data() + prevString, nextString - prevString);
350 size_t colonIndex = next.find(
':');
352 if (colonIndex == std::string_view::npos)
354 throw std::runtime_error(std::format(
"Can't find ':' while parsing headers in {}", next));
357 size_t nonSpace = colonIndex + 1;
358 std::string header(next.begin(), next.begin() + colonIndex);
360 while (next.size() > nonSpace && isspace(next[nonSpace]))
365 std::string value(next.begin() + nonSpace, next.end());
367 headers.try_emplace(std::move(header), std::move(value));
370 bool isUTF8 = httpMessage.find(utf8Encoded) != std::string::npos;
372 if (
auto it = headers.find(transferEncodingHeader); it != headers.end())
374 static const std::unordered_map<std::string, void (HttpParser::*)(std::string_view httpMessage,
bool isUTF8)> transferTypeParsers =
376 { chunkEncoded, &HttpParser::parseChunkEncoded }
379 if (!transferTypeParsers.contains(it->second))
381 throw exceptions::HttpParserException(
"Not supported transfer encoding: " + it->second);
384 std::invoke(transferTypeParsers.at(it->second), *
this, httpMessage, isUTF8);
386 else if (headers.find(contentLengthHeader) != headers.end())
390 body = json::utility::toUTF8JSON(std::string_view(httpMessage.begin() + httpMessage.find(crlfcrlf) + crlfcrlf.size(), httpMessage.end()), CP_UTF8);
394 body = std::string(httpMessage.begin() + httpMessage.find(crlfcrlf) + crlfcrlf.size(), httpMessage.end());
398 this->parseContentType();
401 const std::string& HttpParser::getMethod()
const
406 double HttpParser::getHTTPVersion()
const
408 return std::stod(httpVersion.substr(5));
411 const std::string& HttpParser::getParameters()
const
416 const std::unordered_map<std::string, std::string>& HttpParser::getQueryParameters()
const
418 return queryParameters;
421 const std::pair<int, std::string>& HttpParser::getFullResponse()
const
426 int HttpParser::getResponseCode()
const
428 return response.first;
431 const std::string& HttpParser::getResponseMessage()
const
433 return response.second;
436 const HeadersMap& HttpParser::getHeaders()
const
441 const std::string& HttpParser::getBody()
const
446 const std::vector<std::string>& HttpParser::getChunks()
const
451 const json::JsonParser& HttpParser::getJson()
const
456 const std::string& HttpParser::getRawData()
const
461 const std::vector<Multipart>& HttpParser::getMultiparts()
const
466 HttpParser::operator bool()
const
471 std::ostream& operator << (std::ostream& outputStream,
const HttpParser& parser)
475 if (parser.method.size())
477 if (parser.method !=
"CONNECT")
479 result += std::format(
"{} /{} {}", parser.method, parser.parameters, parser.httpVersion);
483 result += std::format(
"{} {} {}", parser.method, parser.parameters, parser.httpVersion);
488 const auto& [code, message] = parser.getFullResponse();
490 result += std::format(
"{} {} {}", parser.httpVersion,
static_cast<int>(code), message);
493 result += constants::crlf;
495 for (
const auto& [header, value] : parser.headers)
497 result += std::format(
"{}: {}{}", header, value, constants::crlf);
500 if (parser.body.size())
502 result += std::format(
"{}{}", constants::crlf, parser.body);
504 else if (parser.chunks.size())
506 result += constants::crlf;
508 for (
const auto& chunk : parser.chunks)
510 result += std::format(
"{}{}{}", (std::ostringstream() << std::hex << chunk.size() << constants::crlf).str(), chunk, constants::crlf);
513 result += std::format(
"0{}", HttpParser::crlfcrlf);
516 if (!result.ends_with(HttpParser::crlfcrlf))
518 if (result.ends_with(constants::crlf))
520 result += constants::crlf;
524 result += HttpParser::crlfcrlf;
528 return outputStream << result;
531 std::istream& operator >> (std::istream& inputStream, HttpParser& parser)
533 std::istreambuf_iterator<char> it(inputStream);
534 std::string httpMessage(it, {});
536 if (httpMessage.size())
538 parser.parse(httpMessage);
std::string decodeUrl(std::string_view data)