summaryrefslogblamecommitdiffstats
path: root/source/WebServer.cpp
blob: 8f3fa26aea614f343f3d65d5a1549be43d6ef1e3 (plain) (tree)




















































































































































































































































































































































                                                                                                                                           

// WebServer.cpp

// Implements the cWebServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing

#include "Globals.h"
#include "WebServer.h"





// Disable MSVC warnings:
#if defined(_MSC_VER)
	#pragma warning(push)
	#pragma warning(disable:4355)  // 'this' : used in base member initializer list
#endif





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWebRequest:

cWebRequest::cWebRequest(cWebServer & a_WebServer) :
	m_WebServer(a_WebServer),
	m_IsReceivingHeaders(true)
{
}




void cWebRequest::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
{
	AppendPrintf(m_OutgoingData, "%d %s\r\n\r\n", a_StatusCode, a_Response.c_str());
}





void cWebRequest::ParseHeader(size_t a_IdxEnd)
{
	size_t Next = ParseRequestLine(a_IdxEnd);
	if (Next == AString::npos)
	{
		SendStatusAndReason(HTTP_BAD_REQUEST, "Bad request");
		return;
	}
	
	AString Key;
	while (Next < a_IdxEnd)
	{
		Next = ParseHeaderField(Next, a_IdxEnd, Key);
		if (Next == AString::npos)
		{
			SendStatusAndReason(HTTP_BAD_REQUEST, "Bad request");
			return;
		}
	}
	
	m_WebServer.RequestReady(this);
}




size_t cWebRequest::ParseRequestLine(size_t a_IdxEnd)
{	
	// Ignore the initial CRLFs (HTTP spec's "should")
	size_t LineStart = 0;
	while (
		(LineStart < a_IdxEnd) &&
		(
			(m_IncomingHeaderData[LineStart] == '\r') ||
			(m_IncomingHeaderData[LineStart] == '\n')
		)
	)
	{
		LineStart++;
	}
	if (LineStart >= a_IdxEnd)
	{
		return AString::npos;
	}
	
	// Get the Request-Line
	size_t LineEnd = m_IncomingHeaderData.find("\r\n", LineStart);
	if (LineEnd == AString::npos)
	{
		return AString::npos;
	}
	AString RequestLine = m_IncomingHeaderData.substr(LineStart, LineEnd - LineStart);
	
	// Find the method:
	size_t Space = RequestLine.find(" ", LineStart);
	if (Space == AString::npos)
	{
		return AString::npos;
	}
	m_Method = RequestLine.substr(0, Space);
	
	// Find the URL:
	size_t Space2 = RequestLine.find(" ", Space + 1);
	if (Space2 == AString::npos)
	{
		return AString::npos;
	}
	m_URL = RequestLine.substr(Space, Space2 - Space);
	
	// Check that there's HTTP/version at the end
	if (strncmp(RequestLine.c_str() + Space2 + 1, "HTTP/1.", 7) != 0)
	{
		return AString::npos;
	}
	
	return LineEnd + 2;
}





size_t cWebRequest::ParseHeaderField(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key)
{
	if (a_IdxStart >= a_IdxEnd)
	{
		return a_IdxEnd;
	}
	if (m_IncomingHeaderData[a_IdxStart] <= ' ')
	{
		return ParseHeaderFieldContinuation(a_IdxStart + 1, a_IdxEnd, a_Key);
	}
	size_t ValueIdx = 0;
	AString Key;
	for (size_t i = a_IdxStart; i < a_IdxEnd; i++)
	{
		switch (m_IncomingHeaderData[i])
		{
			case '\n':
			{
				if ((ValueIdx == 0) || (i < ValueIdx - 2) || (i < a_IdxStart + 1) || (m_IncomingHeaderData[i - 1] != '\r'))
				{
					// Invalid header field - no colon or no CR before LF
					return AString::npos;
				}
				AString Value = m_IncomingHeaderData.substr(ValueIdx + 1, i - ValueIdx - 2);
				AddHeader(Key, Value);
				a_Key = Key;
				return i + 1;
			}
			case ':':
			{
				if (ValueIdx == 0)
				{
					Key = m_IncomingHeaderData.substr(a_IdxStart, i - a_IdxStart);
					ValueIdx = i;
				}
				break;
			}
			case ' ':
			case '\t':
			{
				if (ValueIdx == i - 1)
				{
					// Value has started in this char, but it is whitespace, so move the start one char further
					ValueIdx = i;
				}
			}
		}  // switch (char)
	}  // for i - m_IncomingHeaderData[]
	// No header found, return the end-of-data index:
	return a_IdxEnd;
}





size_t cWebRequest::ParseHeaderFieldContinuation(size_t a_IdxStart, size_t a_IdxEnd, AString & a_Key)
{
	size_t Start = a_IdxStart;
	for (size_t i = a_IdxStart; i < a_IdxEnd; i++)
	{
		if ((m_IncomingHeaderData[i] > ' ') && (Start == a_IdxStart))
		{
			Start = i;
		}
		else if (m_IncomingHeaderData[i] == '\n')
		{
			if ((i < a_IdxStart + 1) || (m_IncomingHeaderData[i - 1] != '\r'))
			{
				// There wasn't a CR before this LF
				return AString::npos;
			}
			AString Value = m_IncomingHeaderData.substr(Start, i - Start - 1);
			AddHeader(a_Key, Value);
			return i + 1;
		}
	}
	// LF not found, how? We found it at the header end (CRLFCRLF)
	ASSERT(!"LF not found, wtf?");
	return AString::npos;
}





void cWebRequest::AddHeader(const AString & a_Key, const AString & a_Value)
{
	cNameValueMap::iterator itr = m_Headers.find(a_Key);
	if (itr == m_Headers.end())
	{
		m_Headers[a_Key] = a_Value;
	}
	else
	{
		// The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
		itr->second.append(", ");
		itr->second.append(a_Value);
	}
}





void cWebRequest::DataReceived(const char * a_Data, int a_Size)
{
	if (m_IsReceivingHeaders)
	{
		// Start searching 3 chars from the end of the already received data, if available:
		size_t SearchStart = m_IncomingHeaderData.size();
		SearchStart = (SearchStart > 3) ? SearchStart - 3 : 0;
		
		m_IncomingHeaderData.append(a_Data, a_Size);
		
		// Parse the header, if it is complete:
		size_t idxEnd = m_IncomingHeaderData.find("\r\n\r\n", SearchStart);
		if (idxEnd != AString::npos)
		{
			ParseHeader(idxEnd + 2);
			m_IsReceivingHeaders = false;
		}
	}
	else
	{
		// TODO: Receive the body, and the next request (If HTTP/1.1 keepalive
	}
}





void cWebRequest::GetOutgoingData(AString & a_Data)
{
	std::swap(a_Data, m_OutgoingData);
}





void cWebRequest::SocketClosed(void)
{
	// TODO: m_WebServer.RequestFinished(this);
}





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWebServer:

cWebServer::cWebServer(void) :
	m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"),
	m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"),
	m_SocketThreads()
{
}





bool cWebServer::Initialize(cIniFile & a_IniFile)
{
	if (!a_IniFile.GetValueSetB("WebAdmin", "Enabled", false))
	{
		// The WebAdmin is disabled
		return true;
	}
	bool HasAnyPort;
	HasAnyPort = m_ListenThreadIPv4.Initialize(a_IniFile.GetValueSet("WebAdmin", "Port", "8081"));
	HasAnyPort = m_ListenThreadIPv6.Initialize(a_IniFile.GetValueSet("WebAdmin", "PortsIPv6", "8082, 3300")) || HasAnyPort;
	if (!HasAnyPort)
	{
		LOG("WebAdmin is disabled");
		return false;
	}
	if (!m_ListenThreadIPv4.Start())
	{
		return false;
	}
	if (!m_ListenThreadIPv6.Start())
	{
		m_ListenThreadIPv4.Stop();
		return false;
	}
	return true;
}





void cWebServer::OnConnectionAccepted(cSocket & a_Socket)
{
	cWebRequest * Request = new cWebRequest(*this);
	m_SocketThreads.AddClient(a_Socket, Request);
	cCSLock Lock(m_CSRequests);
	m_Requests.push_back(Request);
}





void cWebServer::RequestReady(cWebRequest * a_Request)
{
	a_Request->SendStatusAndReason(cWebRequest::HTTP_OK, "Hello");
}