From 11e0c73ffd23a506c68ae351641a7ca74085ca81 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 24 Sep 2013 20:52:37 +0200 Subject: Implemented basic HTTP message header parsing. --- source/WebServer.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 source/WebServer.cpp (limited to 'source/WebServer.cpp') diff --git a/source/WebServer.cpp b/source/WebServer.cpp new file mode 100644 index 000000000..8f3fa26ae --- /dev/null +++ b/source/WebServer.cpp @@ -0,0 +1,341 @@ + +// 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"); +} + + + + -- cgit v1.2.3