6#include <unordered_set>
12#pragma warning(disable: 26800)
17static const unordered_set<string> methods =
32 const unordered_map<string_view, function<void(HTTPParser&, string_view)>> HTTPParser::contentTypeParsers =
39 HTTPParser::ReadOnlyBuffer::ReadOnlyBuffer(string_view view)
41 char* data =
const_cast<char*
>(view.data());
43 setg(data, data, data + view.size());
46 string HTTPParser::mergeChunks()
const
50 result.reserve(chunksSize);
52 ranges::for_each(chunks, [&result](
const string& value) { result += value; });
57 void HTTPParser::parseQueryParameter(string_view rawParameters)
63 if (rawParameters.find(
"HTTP") != string_view::npos)
65 rawParameters.remove_suffix(httpVersion.size());
70 for (
size_t nextKeyValuePair = 0; nextKeyValuePair < decodedParameters.size(); nextKeyValuePair++)
72 if (decodedParameters[nextKeyValuePair] ==
'&')
76 queryParameters.try_emplace(move(key), move(value));
83 equal = decodedParameters[nextKeyValuePair] ==
'=';
91 equal ? value += decodedParameters[nextKeyValuePair] : key += decodedParameters[nextKeyValuePair];
94 queryParameters.try_emplace(move(key), move(value));
97 void HTTPParser::parseMultipart(string_view data)
99 constexpr string_view boundaryText =
"boundary=";
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);
109 string_view::const_iterator next = search(current + 1, data.end(), searcher);
111 if (next == data.end())
116 multiparts.emplace_back(string_view(current + boundary.size(), next));
118 current = search(next, data.end(), searcher);
122 void HTTPParser::parseContentType()
126 for (
const auto& [encodeType, parser] : contentTypeParsers)
128 if (it->second.find(encodeType) != string::npos)
130 parser(*
this, chunksSize ? this->mergeChunks() : body);
138 void HTTPParser::parseChunkEncoded(string_view HTTPMessage,
bool isUTF8)
142 ReadOnlyBuffer buffer(string_view(HTTPMessage.data() + chunksStart, chunksEnd - chunksStart));
143 istringstream chunksData;
145 static_cast<ios&
>(chunksData).rdbuf(&buffer);
154 getline(chunksData, size);
158 value.resize(stol(size,
nullptr, 16));
165 chunksData.read(value.data(), value.size());
167 chunksData.ignore(
crlf.size());
169 string& chunk = isUTF8 ?
170 chunks.emplace_back(json::utility::toUTF8JSON(value, CP_UTF8)) :
171 chunks.emplace_back(move(value));
173 chunksSize += chunk.size();
186 this->
parse(HTTPMessage);
191 this->
parse(string_view(HTTPMessage.data(), HTTPMessage.size()));
196 if (HTTPMessage.empty())
205 size_t prevString = 0;
206 size_t nextString = HTTPMessage.find(
'\r');
207 string_view firstString(HTTPMessage.data(), nextString);
209 if (string_view temp = firstString.substr(0, firstString.find(
' ')); temp.find(
"HTTP") == string_view::npos)
213 if (methods.find(method) == methods.end())
221 rawData = HTTPMessage;
225 ReadOnlyBuffer buffer(firstString);
229 static_cast<ios&
>(data).rdbuf(&buffer);
231 data >> httpVersion >> responseCode >> response.second;
233 response.first = stoi(responseCode);
235 else if (method !=
"CONNECT")
237 size_t startParameters = firstString.find(
'/');
239 if (startParameters == string::npos)
246 size_t endParameters = firstString.rfind(
' ');
247 size_t queryStart = firstString.find(
'?');
249 parameters =
web::decodeUrl(string_view(firstString.begin() + startParameters, firstString.begin() + endParameters));
251 httpVersion = string(firstString.begin() + firstString.find(
"HTTP"), firstString.end());
253 if (queryStart != string::npos)
255 this->parseQueryParameter(string_view(firstString.data() + queryStart + 1, firstString.data() + endParameters));
260 parameters = string(firstString.begin() + firstString.find(
' ') + 1, firstString.begin() + firstString.rfind(
' '));
262 httpVersion = string(firstString.begin() + firstString.find(
"HTTP"), firstString.end());
267 prevString = nextString +
crlf.size();
268 nextString = HTTPMessage.find(
'\r', prevString);
270 string_view next(HTTPMessage.data() + prevString, nextString - prevString);
272 if (prevString == nextString || nextString == string::npos)
277 size_t colonIndex = next.find(
':');
278 size_t nonSpace = colonIndex + 1;
279 string header(next.begin(), next.begin() + colonIndex);
281 while (next.size() > nonSpace && isspace(next[nonSpace]))
286 string value(next.begin() + nonSpace, next.end());
288 headers.try_emplace(move(header), move(value));
291 bool isUTF8 = HTTPMessage.find(
utf8Encoded) != string::npos;
295 static const unordered_map<string, void (
HTTPParser::*)(string_view HTTPMessage,
bool isUTF8)> transferTypeParsers =
300 if (!transferTypeParsers.contains(it->second))
305 invoke(transferTypeParsers.at(it->second), *
this, HTTPMessage, isUTF8);
311 body = json::utility::toUTF8JSON(
string(HTTPMessage.begin() + HTTPMessage.find(
crlfcrlf) +
crlfcrlf.size(), HTTPMessage.end()), CP_UTF8);
315 body = string(HTTPMessage.begin() + HTTPMessage.find(
crlfcrlf) +
crlfcrlf.size(), HTTPMessage.end());
319 this->parseContentType();
329 return stod(httpVersion.substr(5));
339 return queryParameters;
349 return response.first;
354 return response.second;
387 HTTPParser::operator bool()
const
396 if (parser.method.size())
398 if (parser.method !=
"CONNECT")
400 result += format(
"{} /{} {}", parser.method, parser.parameters, parser.httpVersion);
404 result += format(
"{} {} {}", parser.method, parser.parameters, parser.httpVersion);
411 result += format(
"{} {} {}", parser.httpVersion,
static_cast<int>(code), message);
416 for (
const auto& [header, value] : parser.headers)
421 if (parser.body.size())
425 else if (parser.chunks.size())
429 for (
const auto& chunk : parser.chunks)
449 return outputStream << result;
454 istreambuf_iterator<char> it(inputStream);
455 string httpMessage(it, {});
457 if (httpMessage.size())
459 parser.
parse(httpMessage);
const std::string & getParameters() const
static const std::string chunkEncoded
const std::string & getBody() const
void parse(std::string_view HTTPMessage)
static constexpr std::string_view multipartEncoded
static constexpr std::string_view urlEncoded
const std::string & getMethod() const
int getResponseCode() const
const std::unordered_map< std::string, std::string > & getQueryParameters() const
static constexpr std::string_view crlfcrlf
static constexpr std::string_view crlf
const HeadersMap & getHeaders() const
const std::pair< int, std::string > & getFullResponse() const
static const std::string transferEncodingHeader
static constexpr std::string_view jsonEncoded
const std::vector< std::string > & getChunks() const
const std::string & getRawData() const
static const std::string contentTypeHeader
static const std::string utf8Encoded
double getHTTPVersion() const
const std::vector< Multipart > & getMultiparts() const
const json::JSONParser & getJSON() const
const std::string & getResponseMessage() const
static const std::string contentLengthHeader
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)