// HTTPMessageParser.cpp
// Implements the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
// and reports the individual parts via callbacks
#include "Globals.h"
#include "HTTPMessageParser.h"
cHTTPMessageParser::cHTTPMessageParser(cHTTPMessageParser::cCallbacks & a_Callbacks):
m_Callbacks(a_Callbacks),
m_EnvelopeParser(*this)
{
Reset();
}
size_t cHTTPMessageParser::Parse(const char * a_Data, size_t a_Size)
{
// If parsing already finished or errorred, let the caller keep all the data:
if (m_IsFinished || m_HasHadError)
{
return 0;
}
// If still waiting for the status line, add to buffer and try parsing it:
auto inBufferSoFar = m_Buffer.size();
if (m_FirstLine.empty())
{
m_Buffer.append(a_Data, a_Size);
auto bytesConsumedFirstLine = ParseFirstLine();
ASSERT(bytesConsumedFirstLine <= inBufferSoFar + a_Size); // Haven't consumed more data than there is in the buffer
ASSERT(bytesConsumedFirstLine > inBufferSoFar); // Have consumed at least the previous buffer contents
if (m_FirstLine.empty())
{
// All data used, but not a complete status line yet.
return a_Size;
}
if (m_HasHadError)
{
return AString::npos;
}
// Status line completed, feed the rest of the buffer into the envelope parser:
auto bytesConsumedEnvelope = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
if (bytesConsumedEnvelope == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
ASSERT(bytesConsumedEnvelope <= bytesConsumedFirstLine + a_Size); // Haven't consumed more data than there was in the buffer
m_Buffer.erase(0, bytesConsumedEnvelope);
if (!m_EnvelopeParser.IsInHeaders())
{
HeadersFinished();
// Process any data still left in the buffer as message body:
auto bytesConsumedBody = ParseBody(m_Buffer.data(), m_Buffer.size());
if (bytesConsumedBody == AString::npos)
{
// Error has already been reported by ParseBody, just bail out:
return AString::npos;
}
return bytesConsumedBody + bytesConsumedEnvelope + bytesConsumedFirstLine - inBufferSoFar;
}
return a_Size;
} // if (m_FirstLine.empty())
// If still parsing headers, send them to the envelope parser:
if (m_EnvelopeParser.IsInHeaders())
{
auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size);
if (bytesConsumed == AString::npos)
{
m_HasHadError = true;
m_Callbacks.OnError("Failed to parse the envelope");
return AString::npos;
}
if (!m_EnvelopeParser.IsInHeaders())
{
HeadersFinished();
// Process any data still left as message body:
auto bytesConsumedBody = ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
if (bytesConsumedBody == AString::npos)
{
// Error has already been reported by ParseBody, just bail out:
return AString::npos;
}
}
return a_Size;
}
// Already parsing the body
return ParseBody(a_Data, a_Size);
}
void cHTTPMessageParser::Reset(void)
{
m_HasHadError = false;
m_IsFinished = false;
m_FirstLine.clear();
m_Buffer.clear();
m_EnvelopeParser.Reset();
m_TransferEncodingParser.reset();
m_TransferEncoding.clear();
m_ContentLength = 0;
}
size_t cHTTPMessageParser::ParseFirstLine(void)
{
auto idxLineEnd = m_Buffer.find("\r\n");
if (idxLineEnd == AString::npos)
{
// Not a complete line yet
return m_Buffer.size();
}
m_FirstLine = m_Buffer.substr(0, idxLineEnd);
m_Buffer.erase(0, idxLineEnd + 2);
m_Callbacks.OnFirstLine(m_FirstLine);
return idxLineEnd + 2;
}
size_t cHTTPMessageParser::ParseBody(const char * a_Data, size_t a_Size)
{
if (m_TransferEncodingParser == nullptr)
{
// We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope
OnError("No transfer encoding parser");
return AString::npos;
}
// Parse the body using the transfer encoding parser:
// (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed)
return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size);
}
void cHTTPMessageParser::HeadersFinished(void)
{
m_Callbacks.OnHeadersFinished();
m_TransferEncodingParser = cTransferEncodingParser::Create(*this, m_TransferEncoding, m_ContentLength);
if (m_TransferEncodingParser == nullptr)
{
OnError(fmt::format(FMT_STRING("Unknown transfer encoding: {}"), m_TransferEncoding));
return;
}
}
void cHTTPMessageParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{
m_Callbacks.OnHeaderLine(a_Key, a_Value);
auto Key = StrToLower(a_Key);
if (Key == "content-length")
{
if (!StringToInteger(a_Value, m_ContentLength))
{
OnError(fmt::format(FMT_STRING("Invalid content length header value: \"{}\""), a_Value));
}
return;
}
if (Key == "transfer-encoding")
{
m_TransferEncoding = a_Value;
return;
}
}
void cHTTPMessageParser::OnError(const AString & a_ErrorDescription)
{
m_HasHadError = true;
m_Callbacks.OnError(a_ErrorDescription);
}
void cHTTPMessageParser::OnBodyData(const void * a_Data, size_t a_Size)
{
m_Callbacks.OnBodyData(a_Data, a_Size);
}
void cHTTPMessageParser::OnBodyFinished(void)
{
m_IsFinished = true;
m_Callbacks.OnBodyFinished();
}