
// EnvelopeParser.cpp

// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME

#include "Globals.h"
#include "EnvelopeParser.h"





cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) :
	m_Callbacks(a_Callbacks),
	m_IsInHeaders(true)
{
}





size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size)
{
	if (!m_IsInHeaders)
	{
		return 0;
	}
	
	// Start searching 1 char from the end of the already received data, if available:
	size_t SearchStart = m_IncomingData.size();
	SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0;
	
	m_IncomingData.append(a_Data, a_Size);
	
	size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart);
	if (idxCRLF == AString::npos)
	{
		// Not a complete line yet, all input consumed:
		return a_Size;
	}
	
	// Parse as many lines as found:
	size_t Last = 0;
	do
	{
		if (idxCRLF == Last)
		{
			// This was the last line of the data. Finish whatever value has been cached and return:
			NotifyLast();
			m_IsInHeaders = false;
			return a_Size - (m_IncomingData.size() - idxCRLF) + 2;
		}
		if (!ParseLine(m_IncomingData.c_str() + Last, idxCRLF - Last))
		{
			// An error has occurred
			m_IsInHeaders = false;
			return AString::npos;
		}
		Last = idxCRLF + 2;
		idxCRLF = m_IncomingData.find("\r\n", idxCRLF + 2);
	} while (idxCRLF != AString::npos);
	m_IncomingData.erase(0, Last);
	
	// Parsed all lines and still expecting more
	return a_Size;
}





void cEnvelopeParser::Reset(void)
{
	m_IsInHeaders = true;
	m_IncomingData.clear();
	m_LastKey.clear();
	m_LastValue.clear();
}





void cEnvelopeParser::NotifyLast(void)
{
	if (!m_LastKey.empty())
	{
		m_Callbacks.OnHeaderLine(m_LastKey, m_LastValue);
		m_LastKey.clear();
	}
	m_LastValue.clear();
}





bool cEnvelopeParser::ParseLine(const char * a_Data, size_t a_Size)
{
	ASSERT(a_Size > 0);
	if (a_Data[0] <= ' ')
	{
		// This line is a continuation for the previous line
		if (m_LastKey.empty())
		{
			return false;
		}
		// Append, including the whitespace in a_Data[0]
		m_LastValue.append(a_Data, a_Size);
		return true;
	}
	
	// This is a line with a new key:
	NotifyLast();
	for (size_t i = 0; i < a_Size; i++)
	{
		if (a_Data[i] == ':')
		{
			m_LastKey.assign(a_Data, i);
			m_LastValue.assign(a_Data + i + 2, a_Size - i - 2);
			return true;
		}
	}  // for i - a_Data[]
	
	// No colon was found, key-less header??
	return false;
}




