Skip to content

Commit

Permalink
Parse status and headers all at once
Browse files Browse the repository at this point in the history
  • Loading branch information
elnormous committed Feb 25, 2022
1 parent f123709 commit 882d653
Showing 1 changed file with 46 additions and 62 deletions.
108 changes: 46 additions & 62 deletions include/HTTPRequest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,14 +846,10 @@ namespace http

std::array<std::uint8_t, 4096> tempBuffer;
constexpr std::array<std::uint8_t, 2> crlf = {'\r', '\n'};
constexpr std::array<std::uint8_t, 4> headerEnd = {'\r', '\n', '\r', '\n'};
Response response;
std::vector<std::uint8_t> responseData;
enum class State
{
parsingStatusLine,
parsingHeaders,
parsingBody
} state = State::parsingStatusLine;
bool parsingBody = false;
bool contentLengthReceived = false;
std::size_t contentLength = 0;
bool chunkedResponse = false;
Expand All @@ -870,75 +866,63 @@ namespace http

responseData.insert(responseData.end(), tempBuffer.begin(), tempBuffer.begin() + size);

if (state != State::parsingBody)
for (;;)
{
// RFC 7230, 3. Message Format
const auto lineBegin = responseData.cbegin();
const auto lineEnd = std::search(responseData.cbegin(), responseData.cend(), crlf.cbegin(), crlf.cend());
if (!parsingBody)
{
// RFC 7230, 3. Message Format
// Empty line indicates the end of the header section (RFC 7230, 2.1. Client/Server Messaging)
const auto headerEndIterator = std::search(responseData.cbegin(), responseData.cend(), headerEnd.cbegin(), headerEnd.cend());
if (headerEndIterator == responseData.cend()) break;

// didn't find a newline
if (lineEnd == responseData.cend()) break;
// didn't find a newline
const std::string headerResponseData(responseData.cbegin(), headerEndIterator + 2);
responseData.erase(responseData.cbegin(), headerEndIterator + 4);

const std::string line(lineBegin, lineEnd + 2);
responseData.erase(lineBegin, lineEnd + 2);
const auto statusLineResult = parseStatusLine(headerResponseData.cbegin(), headerResponseData.cend());
auto i = statusLineResult.first;

// empty line indicates the end of the header section (RFC 7230, 2.1. Client/Server Messaging)
if (line.length() <= 2)
{
state = State::parsingBody;
break;
}
else if (state == State::parsingStatusLine) // RFC 7230, 3.1.2. Status Line
{
state = State::parsingHeaders;

const auto statusLineResult = parseStatusLine(line.cbegin(), line.cend());
const auto i = statusLineResult.first;
if (i != line.cend())
throw ResponseError{"Invalid status line"};
response.status = std::move(statusLineResult.second);

response.status = std::move(statusLineResult.second);
}
else if (state == State::parsingHeaders) // RFC 7230, 3.2. Header Fields
{
const auto headerFieldResult = parseHeaderField(line.cbegin(), line.cend());
auto i = headerFieldResult.first;
for (;;)
{
const auto headerFieldResult = parseHeaderField(i, headerResponseData.cend());
i = headerFieldResult.first;

if (i != line.cend())
throw ResponseError{"Invalid header"};
auto fieldName = std::move(headerFieldResult.second.first);

auto fieldName = std::move(headerFieldResult.second.first);
const auto toLower = [](const char c) noexcept {
return (c >= 'A' && c <= 'Z') ? c - ('A' - 'a') : c;
};

const auto toLower = [](const char c) noexcept {
return (c >= 'A' && c <= 'Z') ? c - ('A' - 'a') : c;
};
std::transform(fieldName.begin(), fieldName.end(), fieldName.begin(), toLower);

std::transform(fieldName.begin(), fieldName.end(), fieldName.begin(), toLower);
auto fieldValue = std::move(headerFieldResult.second.second);

auto fieldValue = std::move(headerFieldResult.second.second);
if (fieldName == "transfer-encoding")
{
// RFC 7230, 3.3.1. Transfer-Encoding
if (fieldValue == "chunked")
chunkedResponse = true;
else
throw ResponseError{"Unsupported transfer encoding: " + fieldValue};
}
else if (fieldName == "content-length")
{
// RFC 7230, 3.3.2. Content-Length
contentLength = std::stoul(fieldValue);
contentLengthReceived = true;
response.body.reserve(contentLength);
}

if (fieldName == "transfer-encoding")
{
// RFC 7230, 3.3.1. Transfer-Encoding
if (fieldValue == "chunked")
chunkedResponse = true;
else
throw ResponseError{"Unsupported transfer encoding: " + fieldValue};
}
else if (fieldName == "content-length")
{
// RFC 7230, 3.3.2. Content-Length
contentLength = std::stoul(fieldValue);
contentLengthReceived = true;
response.body.reserve(contentLength);
}
response.headers.push_back(std::make_pair(std::move(fieldName), std::move(fieldValue)));

response.headers.push_back(std::make_pair(std::move(fieldName), std::move(fieldValue)));
}
if (i == headerResponseData.cend())
break;
}

if (state == State::parsingBody)
parsingBody = true;
}

if (parsingBody)
{
// Content-Length must be ignored if Transfer-Encoding is received (RFC 7230, 3.2. Content-Length)
if (chunkedResponse)
Expand Down

0 comments on commit 882d653

Please sign in to comment.