summaryrefslogblamecommitdiffstats
path: root/src/HTTP/HTTPMessageParser.cpp
blob: de3cf6518954c28a41dc2b29b114966f680e2a3a (plain) (tree)
























































































































                                                                                                                                             
 
















                                                     
 























                                                                                                                      
                                                                                                      















                                                                                     
                                                                                                                 









































                                                                       

// 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();
}