From 2dbc54a14824329faf53851441c6585fd451f0a2 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 25 Dec 2015 19:42:50 +0100 Subject: Renamed the HTTP classes to indicate they're for server. --- src/HTTPServer/CMakeLists.txt | 10 +- src/HTTPServer/HTTPConnection.cpp | 278 ----------------------------- src/HTTPServer/HTTPConnection.h | 124 ------------- src/HTTPServer/HTTPServer.cpp | 20 +-- src/HTTPServer/HTTPServer.h | 24 ++- src/HTTPServer/HTTPServerConnection.cpp | 278 +++++++++++++++++++++++++++++ src/HTTPServer/HTTPServerConnection.h | 124 +++++++++++++ src/HTTPServer/SslHTTPConnection.cpp | 115 ------------ src/HTTPServer/SslHTTPConnection.h | 47 ----- src/HTTPServer/SslHTTPServerConnection.cpp | 115 ++++++++++++ src/HTTPServer/SslHTTPServerConnection.h | 47 +++++ src/WebAdmin.cpp | 14 +- src/WebAdmin.h | 12 +- 13 files changed, 603 insertions(+), 605 deletions(-) delete mode 100644 src/HTTPServer/HTTPConnection.cpp delete mode 100644 src/HTTPServer/HTTPConnection.h create mode 100644 src/HTTPServer/HTTPServerConnection.cpp create mode 100644 src/HTTPServer/HTTPServerConnection.h delete mode 100644 src/HTTPServer/SslHTTPConnection.cpp delete mode 100644 src/HTTPServer/SslHTTPConnection.h create mode 100644 src/HTTPServer/SslHTTPServerConnection.cpp create mode 100644 src/HTTPServer/SslHTTPServerConnection.h diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTPServer/CMakeLists.txt index a08a1fcf8..e8026f761 100644 --- a/src/HTTPServer/CMakeLists.txt +++ b/src/HTTPServer/CMakeLists.txt @@ -6,31 +6,31 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS EnvelopeParser.cpp - HTTPConnection.cpp HTTPFormParser.cpp HTTPMessage.cpp HTTPServer.cpp + HTTPServerConnection.cpp MultipartParser.cpp NameValueParser.cpp - SslHTTPConnection.cpp + SslHTTPServerConnection.cpp UrlParser.cpp ) SET (HDRS EnvelopeParser.h - HTTPConnection.h HTTPFormParser.h HTTPMessage.h HTTPServer.h + HTTPServerConnection.h MultipartParser.h NameValueParser.h - SslHTTPConnection.h + SslHTTPServerConnection.h UrlParser.h ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set_source_files_properties(HTTPServer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=global-constructors ") - set_source_files_properties(HTTPConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum") + set_source_files_properties(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum") set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare") endif() diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp deleted file mode 100644 index 0db154139..000000000 --- a/src/HTTPServer/HTTPConnection.cpp +++ /dev/null @@ -1,278 +0,0 @@ - -// HTTPConnection.cpp - -// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server. - -#include "Globals.h" -#include "HTTPConnection.h" -#include "HTTPMessage.h" -#include "HTTPServer.h" - - - - - -cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : - m_HTTPServer(a_HTTPServer), - m_State(wcsRecvHeaders), - m_CurrentRequest(nullptr), - m_CurrentRequestBodyRemaining(0) -{ - // LOGD("HTTP: New connection at %p", this); -} - - - - - -cHTTPConnection::~cHTTPConnection() -{ - // LOGD("HTTP: Connection deleting: %p", this); - delete m_CurrentRequest; - m_CurrentRequest = nullptr; -} - - - - - -void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) -{ - SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); - SendData(Printf("Content-Length: %u\r\n\r\n", static_cast(a_Response.size()))); - SendData(a_Response.data(), a_Response.size()); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPConnection::SendNeedAuth(const AString & a_Realm) -{ - SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPConnection::Send(const cHTTPResponse & a_Response) -{ - ASSERT(m_State == wcsRecvIdle); - AString toSend; - a_Response.AppendToData(toSend); - m_State = wcsSendingResp; - SendData(toSend); -} - - - - - -void cHTTPConnection::Send(const void * a_Data, size_t a_Size) -{ - ASSERT(m_State == wcsSendingResp); - // We're sending in Chunked transfer encoding - SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); - SendData(a_Data, a_Size); - SendData("\r\n"); -} - - - - - -void cHTTPConnection::FinishResponse(void) -{ - ASSERT(m_State == wcsSendingResp); - SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPConnection::AwaitNextRequest(void) -{ - switch (m_State) - { - case wcsRecvHeaders: - { - // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth()) - break; - } - - case wcsRecvIdle: - { - // The client is waiting for a response, send an "Internal server error": - SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - case wcsSendingResp: - { - // The response headers have been sent, we need to terminate the response body: - SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - default: - { - ASSERT(!"Unhandled state recovery"); - break; - } - } -} - - - - - -void cHTTPConnection::Terminate(void) -{ - if (m_CurrentRequest != nullptr) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_Link.reset(); -} - - - - - -void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link) -{ - ASSERT(m_Link == nullptr); - m_Link = a_Link; -} - - - - - -void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) -{ - ASSERT(m_Link != nullptr); - - switch (m_State) - { - case wcsRecvHeaders: - { - if (m_CurrentRequest == nullptr) - { - m_CurrentRequest = new cHTTPRequest; - } - - size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); - if (BytesConsumed == AString::npos) - { - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - if (m_CurrentRequest->IsInHeaders()) - { - // The request headers are not yet complete - return; - } - - // The request has finished parsing its headers successfully, notify of it: - m_State = wcsRecvBody; - m_HTTPServer.NewRequest(*this, *m_CurrentRequest); - m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); - if (m_CurrentRequestBodyRemaining == AString::npos) - { - // The body length was not specified in the request, assume zero - m_CurrentRequestBodyRemaining = 0; - } - - // Process the rest of the incoming data into the request body: - if (a_Size > BytesConsumed) - { - cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); - return; - } - else - { - cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away - return; - } - } - - case wcsRecvBody: - { - ASSERT(m_CurrentRequest != nullptr); - if (m_CurrentRequestBodyRemaining > 0) - { - size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast(a_Size)); - m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); - m_CurrentRequestBodyRemaining -= BytesToConsume; - } - if (m_CurrentRequestBodyRemaining == 0) - { - m_State = wcsRecvIdle; - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - if (!m_CurrentRequest->DoesAllowKeepAlive()) - { - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - } - break; - } - - default: - { - // TODO: Should we be receiving data in this state? - break; - } - } -} - - - - - -void cHTTPConnection::OnRemoteClosed(void) -{ - if (m_CurrentRequest != nullptr) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_Link.reset(); -} - - - - - - -void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) -{ - OnRemoteClosed(); -} - - - - -void cHTTPConnection::SendData(const void * a_Data, size_t a_Size) -{ - m_Link->Send(a_Data, a_Size); -} - - - - diff --git a/src/HTTPServer/HTTPConnection.h b/src/HTTPServer/HTTPConnection.h deleted file mode 100644 index 414075411..000000000 --- a/src/HTTPServer/HTTPConnection.h +++ /dev/null @@ -1,124 +0,0 @@ - -// HTTPConnection.h - -// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. - - - - - -#pragma once - -#include "../OSSupport/Network.h" - - - - - -// fwd: -class cHTTPServer; -class cHTTPResponse; -class cHTTPRequest; - - - - - -class cHTTPConnection : - public cTCPLink::cCallbacks -{ -public: - - enum eState - { - wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr) - wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) - wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr) - wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr) - wcsInvalid, ///< The request was malformed, the connection is closing - } ; - - cHTTPConnection(cHTTPServer & a_HTTPServer); - virtual ~cHTTPConnection(); - - /** Sends HTTP status code together with a_Reason (used for HTTP errors). - Sends the a_Reason as the body as well, so that browsers display it. */ - void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); - - /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ - void SendNeedAuth(const AString & a_Realm); - - /** Sends the headers contained in a_Response */ - void Send(const cHTTPResponse & a_Response); - - /** Sends the data as the response (may be called multiple times) */ - void Send(const void * a_Data, size_t a_Size); - - /** Sends the data as the response (may be called multiple times) */ - void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } - - /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */ - void FinishResponse(void); - - /** Resets the internal connection state for a new request. - Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */ - void AwaitNextRequest(void); - - /** Terminates the connection; finishes any request being currently processed */ - void Terminate(void); - -protected: - typedef std::map cNameValueMap; - - /** The parent webserver that is to be notified of events on this connection */ - cHTTPServer & m_HTTPServer; - - /** All the incoming data until the entire request header is parsed */ - AString m_IncomingHeaderData; - - /** Status in which the request currently is */ - eState m_State; - - /** The request being currently received - Valid only between having parsed the headers and finishing receiving the body. */ - cHTTPRequest * m_CurrentRequest; - - /** Number of bytes that remain to read for the complete body of the message to be received. - Valid only in wcsRecvBody */ - size_t m_CurrentRequestBodyRemaining; - - /** The network link attached to this connection. */ - cTCPLinkPtr m_Link; - - - // cTCPLink::cCallbacks overrides: - /** The link instance has been created, remember it. */ - virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; - - /** Data is received from the client. */ - virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; - - /** The socket has been closed for any reason. */ - virtual void OnRemoteClosed(void) override; - - /** An error has occurred on the socket. */ - virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; - - // Overridable: - /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ - virtual void SendData(const void * a_Data, size_t a_Size); - - /** Sends the raw data over the link. - Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */ - void SendData(const AString & a_Data) - { - SendData(a_Data.data(), a_Data.size()); - } -} ; - -typedef std::vector cHTTPConnections; - - - - - diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp index 814a87fe4..1cdf29bca 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTPServer/HTTPServer.cpp @@ -6,9 +6,9 @@ #include "Globals.h" #include "HTTPServer.h" #include "HTTPMessage.h" -#include "HTTPConnection.h" +#include "HTTPServerConnection.h" #include "HTTPFormParser.h" -#include "SslHTTPConnection.h" +#include "SslHTTPServerConnection.h" @@ -28,7 +28,7 @@ class cDebugCallbacks : public cHTTPServer::cCallbacks, protected cHTTPFormParser::cCallbacks { - virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override { UNUSED(a_Connection); @@ -39,7 +39,7 @@ class cDebugCallbacks : } - virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override { UNUSED(a_Connection); @@ -51,7 +51,7 @@ class cDebugCallbacks : } - virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override { cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); if (FormParser != nullptr) @@ -268,11 +268,11 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo if (m_Cert.get() != nullptr) { - return std::make_shared(*this, m_Cert, m_CertPrivKey); + return std::make_shared(*this, m_Cert, m_CertPrivKey); } else { - return std::make_shared(*this); + return std::make_shared(*this); } } @@ -280,7 +280,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo -void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { m_Callbacks->OnRequestBegun(a_Connection, a_Request); } @@ -289,7 +289,7 @@ void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Re -void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) +void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) { m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); } @@ -298,7 +298,7 @@ void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_R -void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { m_Callbacks->OnRequestFinished(a_Connection, a_Request); a_Connection.AwaitNextRequest(); diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h index 2a094b413..2fd4cdcfc 100644 --- a/src/HTTPServer/HTTPServer.h +++ b/src/HTTPServer/HTTPServer.h @@ -23,9 +23,7 @@ class cHTTPMessage; class cHTTPRequest; class cHTTPResponse; -class cHTTPConnection; - -typedef std::vector cHTTPConnections; +class cHTTPServerConnection; @@ -42,14 +40,14 @@ public: /** Called when a new request arrives over a connection and all its headers have been parsed. The request body needn't have arrived yet. */ - virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) = 0; /** Called when another part of request body has arrived. May be called multiple times for a single request. */ - virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0; + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0; /** Called when the request body has been fully received in previous calls to OnRequestBody() */ - virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) = 0; } ; cHTTPServer(void); @@ -65,8 +63,8 @@ public: void Stop(void); protected: - friend class cHTTPConnection; - friend class cSslHTTPConnection; + friend class cHTTPServerConnection; + friend class cSslHTTPServerConnection; friend class cHTTPServerListenCallbacks; /** The cNetwork API handle for the listening socket. */ @@ -86,15 +84,15 @@ protected: Returns the connection instance to be used as the cTCPLink callbacks. */ cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); - /** Called by cHTTPConnection when it finishes parsing the request header */ - void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + /** Called by cHTTPServerConnection when it finishes parsing the request header */ + void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); /** Called by cHTTPConenction when it receives more data for the request body. May be called multiple times for a single request. */ - void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size); + void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size); - /** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */ - void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ + void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); } ; diff --git a/src/HTTPServer/HTTPServerConnection.cpp b/src/HTTPServer/HTTPServerConnection.cpp new file mode 100644 index 000000000..3c4acc2d4 --- /dev/null +++ b/src/HTTPServer/HTTPServerConnection.cpp @@ -0,0 +1,278 @@ + +// HTTPConnection.cpp + +// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server. + +#include "Globals.h" +#include "HTTPServerConnection.h" +#include "HTTPMessage.h" +#include "HTTPServer.h" + + + + + +cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : + m_HTTPServer(a_HTTPServer), + m_State(wcsRecvHeaders), + m_CurrentRequest(nullptr), + m_CurrentRequestBodyRemaining(0) +{ + // LOGD("HTTP: New connection at %p", this); +} + + + + + +cHTTPServerConnection::~cHTTPServerConnection() +{ + // LOGD("HTTP: Connection deleting: %p", this); + delete m_CurrentRequest; + m_CurrentRequest = nullptr; +} + + + + + +void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) +{ + SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); + SendData(Printf("Content-Length: %u\r\n\r\n", static_cast(a_Response.size()))); + SendData(a_Response.data(), a_Response.size()); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) +{ + SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) +{ + ASSERT(m_State == wcsRecvIdle); + AString toSend; + a_Response.AppendToData(toSend); + m_State = wcsSendingResp; + SendData(toSend); +} + + + + + +void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) +{ + ASSERT(m_State == wcsSendingResp); + // We're sending in Chunked transfer encoding + SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); + SendData(a_Data, a_Size); + SendData("\r\n"); +} + + + + + +void cHTTPServerConnection::FinishResponse(void) +{ + ASSERT(m_State == wcsSendingResp); + SendData("0\r\n\r\n"); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::AwaitNextRequest(void) +{ + switch (m_State) + { + case wcsRecvHeaders: + { + // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth()) + break; + } + + case wcsRecvIdle: + { + // The client is waiting for a response, send an "Internal server error": + SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); + m_State = wcsRecvHeaders; + break; + } + + case wcsSendingResp: + { + // The response headers have been sent, we need to terminate the response body: + SendData("0\r\n\r\n"); + m_State = wcsRecvHeaders; + break; + } + + default: + { + ASSERT(!"Unhandled state recovery"); + break; + } + } +} + + + + + +void cHTTPServerConnection::Terminate(void) +{ + if (m_CurrentRequest != nullptr) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_Link.reset(); +} + + + + + +void cHTTPServerConnection::OnLinkCreated(cTCPLinkPtr a_Link) +{ + ASSERT(m_Link == nullptr); + m_Link = a_Link; +} + + + + + +void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) +{ + ASSERT(m_Link != nullptr); + + switch (m_State) + { + case wcsRecvHeaders: + { + if (m_CurrentRequest == nullptr) + { + m_CurrentRequest = new cHTTPRequest; + } + + size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); + if (BytesConsumed == AString::npos) + { + delete m_CurrentRequest; + m_CurrentRequest = nullptr; + m_State = wcsInvalid; + m_Link->Close(); + m_Link.reset(); + return; + } + if (m_CurrentRequest->IsInHeaders()) + { + // The request headers are not yet complete + return; + } + + // The request has finished parsing its headers successfully, notify of it: + m_State = wcsRecvBody; + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); + m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); + if (m_CurrentRequestBodyRemaining == AString::npos) + { + // The body length was not specified in the request, assume zero + m_CurrentRequestBodyRemaining = 0; + } + + // Process the rest of the incoming data into the request body: + if (a_Size > BytesConsumed) + { + cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); + return; + } + else + { + cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away + return; + } + } + + case wcsRecvBody: + { + ASSERT(m_CurrentRequest != nullptr); + if (m_CurrentRequestBodyRemaining > 0) + { + size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast(a_Size)); + m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); + m_CurrentRequestBodyRemaining -= BytesToConsume; + } + if (m_CurrentRequestBodyRemaining == 0) + { + m_State = wcsRecvIdle; + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + if (!m_CurrentRequest->DoesAllowKeepAlive()) + { + m_State = wcsInvalid; + m_Link->Close(); + m_Link.reset(); + return; + } + delete m_CurrentRequest; + m_CurrentRequest = nullptr; + } + break; + } + + default: + { + // TODO: Should we be receiving data in this state? + break; + } + } +} + + + + + +void cHTTPServerConnection::OnRemoteClosed(void) +{ + if (m_CurrentRequest != nullptr) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_Link.reset(); +} + + + + + + +void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + OnRemoteClosed(); +} + + + + +void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) +{ + m_Link->Send(a_Data, a_Size); +} + + + + diff --git a/src/HTTPServer/HTTPServerConnection.h b/src/HTTPServer/HTTPServerConnection.h new file mode 100644 index 000000000..d63cc7395 --- /dev/null +++ b/src/HTTPServer/HTTPServerConnection.h @@ -0,0 +1,124 @@ + +// HTTPConnection.h + +// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. + + + + + +#pragma once + +#include "../OSSupport/Network.h" + + + + + +// fwd: +class cHTTPServer; +class cHTTPResponse; +class cHTTPRequest; + + + + + +class cHTTPServerConnection : + public cTCPLink::cCallbacks +{ +public: + + enum eState + { + wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr) + wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) + wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr) + wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr) + wcsInvalid, ///< The request was malformed, the connection is closing + } ; + + cHTTPServerConnection(cHTTPServer & a_HTTPServer); + virtual ~cHTTPServerConnection(); + + /** Sends HTTP status code together with a_Reason (used for HTTP errors). + Sends the a_Reason as the body as well, so that browsers display it. */ + void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + + /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ + void SendNeedAuth(const AString & a_Realm); + + /** Sends the headers contained in a_Response */ + void Send(const cHTTPResponse & a_Response); + + /** Sends the data as the response (may be called multiple times) */ + void Send(const void * a_Data, size_t a_Size); + + /** Sends the data as the response (may be called multiple times) */ + void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } + + /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */ + void FinishResponse(void); + + /** Resets the internal connection state for a new request. + Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */ + void AwaitNextRequest(void); + + /** Terminates the connection; finishes any request being currently processed */ + void Terminate(void); + +protected: + typedef std::map cNameValueMap; + + /** The parent webserver that is to be notified of events on this connection */ + cHTTPServer & m_HTTPServer; + + /** All the incoming data until the entire request header is parsed */ + AString m_IncomingHeaderData; + + /** Status in which the request currently is */ + eState m_State; + + /** The request being currently received + Valid only between having parsed the headers and finishing receiving the body. */ + cHTTPRequest * m_CurrentRequest; + + /** Number of bytes that remain to read for the complete body of the message to be received. + Valid only in wcsRecvBody */ + size_t m_CurrentRequestBodyRemaining; + + /** The network link attached to this connection. */ + cTCPLinkPtr m_Link; + + + // cTCPLink::cCallbacks overrides: + /** The link instance has been created, remember it. */ + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; + + /** Data is received from the client. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; + + /** The socket has been closed for any reason. */ + virtual void OnRemoteClosed(void) override; + + /** An error has occurred on the socket. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + + // Overridable: + /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ + virtual void SendData(const void * a_Data, size_t a_Size); + + /** Sends the raw data over the link. + Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */ + void SendData(const AString & a_Data) + { + SendData(a_Data.data(), a_Data.size()); + } +} ; + +typedef std::vector cHTTPServerConnections; + + + + + diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp deleted file mode 100644 index 1cbe02312..000000000 --- a/src/HTTPServer/SslHTTPConnection.cpp +++ /dev/null @@ -1,115 +0,0 @@ - -// SslHTTPConnection.cpp - -// Implements the cSslHTTPConnection class representing a HTTP connection made over a SSL link - -#include "Globals.h" -#include "SslHTTPConnection.h" -#include "HTTPServer.h" - - - - - -cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : - super(a_HTTPServer), - m_Ssl(64000), - m_Cert(a_Cert), - m_PrivateKey(a_PrivateKey) -{ - m_Ssl.Initialize(false); - m_Ssl.SetOwnCert(a_Cert, a_PrivateKey); -} - - - - - -cSslHTTPConnection::~cSslHTTPConnection() -{ - m_Ssl.NotifyClose(); -} - - - - - -void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) -{ - // Process the received data: - const char * Data = a_Data; - size_t Size = a_Size; - for (;;) - { - // Try to write as many bytes into Ssl's "incoming" buffer as possible: - size_t BytesWritten = 0; - if (Size > 0) - { - BytesWritten = m_Ssl.WriteIncoming(Data, Size); - Data += BytesWritten; - Size -= BytesWritten; - } - - // Try to read as many bytes from SSL's decryption as possible: - char Buffer[32000]; - int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); - if (NumRead > 0) - { - super::OnReceivedData(Buffer, static_cast(NumRead)); - // The link may have closed while processing the data, bail out: - return; - } - else if (NumRead == POLARSSL_ERR_NET_WANT_READ) - { - // SSL requires us to send data to peer first, do so by "sending" empty data: - SendData(nullptr, 0); - } - - // If both failed, bail out: - if ((BytesWritten == 0) && (NumRead <= 0)) - { - return; - } - } -} - - - - - -void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size) -{ - const char * OutgoingData = reinterpret_cast(a_Data); - size_t pos = 0; - for (;;) - { - // Write as many bytes from our buffer to SSL's encryption as possible: - int NumWritten = 0; - if (pos < a_Size) - { - NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos); - if (NumWritten > 0) - { - pos += static_cast(NumWritten); - } - } - - // Read as many bytes from SSL's "outgoing" buffer as possible: - char Buffer[32000]; - size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); - if (NumBytes > 0) - { - m_Link->Send(Buffer, NumBytes); - } - - // If both failed, bail out: - if ((NumWritten <= 0) && (NumBytes == 0)) - { - return; - } - } -} - - - - diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h deleted file mode 100644 index b35bd8ba0..000000000 --- a/src/HTTPServer/SslHTTPConnection.h +++ /dev/null @@ -1,47 +0,0 @@ - -// SslHTTPConnection.h - -// Declared the cSslHTTPConnection class representing a HTTP connection made over a SSL link - - - - - -#pragma once - -#include "HTTPConnection.h" -#include "PolarSSL++/BufferedSslContext.h" - - - - - -class cSslHTTPConnection : - public cHTTPConnection -{ - typedef cHTTPConnection super; - -public: - /** Creates a new connection on the specified server. - Sends the specified cert as the server certificate, uses the private key for decryption. */ - cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); - - ~cSslHTTPConnection(); - -protected: - cBufferedSslContext m_Ssl; - - /** The certificate to send to the client */ - cX509CertPtr m_Cert; - - /** The private key used for the certificate */ - cCryptoKeyPtr m_PrivateKey; - - // cHTTPConnection overrides: - virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client - virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client -} ; - - - - diff --git a/src/HTTPServer/SslHTTPServerConnection.cpp b/src/HTTPServer/SslHTTPServerConnection.cpp new file mode 100644 index 000000000..547e6de3a --- /dev/null +++ b/src/HTTPServer/SslHTTPServerConnection.cpp @@ -0,0 +1,115 @@ + +// SslHTTPConnection.cpp + +// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link + +#include "Globals.h" +#include "SslHTTPServerConnection.h" +#include "HTTPServer.h" + + + + + +cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : + super(a_HTTPServer), + m_Ssl(64000), + m_Cert(a_Cert), + m_PrivateKey(a_PrivateKey) +{ + m_Ssl.Initialize(false); + m_Ssl.SetOwnCert(a_Cert, a_PrivateKey); +} + + + + + +cSslHTTPServerConnection::~cSslHTTPServerConnection() +{ + m_Ssl.NotifyClose(); +} + + + + + +void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) +{ + // Process the received data: + const char * Data = a_Data; + size_t Size = a_Size; + for (;;) + { + // Try to write as many bytes into Ssl's "incoming" buffer as possible: + size_t BytesWritten = 0; + if (Size > 0) + { + BytesWritten = m_Ssl.WriteIncoming(Data, Size); + Data += BytesWritten; + Size -= BytesWritten; + } + + // Try to read as many bytes from SSL's decryption as possible: + char Buffer[32000]; + int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); + if (NumRead > 0) + { + super::OnReceivedData(Buffer, static_cast(NumRead)); + // The link may have closed while processing the data, bail out: + return; + } + else if (NumRead == POLARSSL_ERR_NET_WANT_READ) + { + // SSL requires us to send data to peer first, do so by "sending" empty data: + SendData(nullptr, 0); + } + + // If both failed, bail out: + if ((BytesWritten == 0) && (NumRead <= 0)) + { + return; + } + } +} + + + + + +void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) +{ + const char * OutgoingData = reinterpret_cast(a_Data); + size_t pos = 0; + for (;;) + { + // Write as many bytes from our buffer to SSL's encryption as possible: + int NumWritten = 0; + if (pos < a_Size) + { + NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos); + if (NumWritten > 0) + { + pos += static_cast(NumWritten); + } + } + + // Read as many bytes from SSL's "outgoing" buffer as possible: + char Buffer[32000]; + size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); + if (NumBytes > 0) + { + m_Link->Send(Buffer, NumBytes); + } + + // If both failed, bail out: + if ((NumWritten <= 0) && (NumBytes == 0)) + { + return; + } + } +} + + + + diff --git a/src/HTTPServer/SslHTTPServerConnection.h b/src/HTTPServer/SslHTTPServerConnection.h new file mode 100644 index 000000000..6032a2bd0 --- /dev/null +++ b/src/HTTPServer/SslHTTPServerConnection.h @@ -0,0 +1,47 @@ + +// SslHTTPServerConnection.h + +// Declared the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link + + + + + +#pragma once + +#include "HTTPServerConnection.h" +#include "PolarSSL++/BufferedSslContext.h" + + + + + +class cSslHTTPServerConnection : + public cHTTPServerConnection +{ + typedef cHTTPServerConnection super; + +public: + /** Creates a new connection on the specified server. + Sends the specified cert as the server certificate, uses the private key for decryption. */ + cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); + + ~cSslHTTPServerConnection(); + +protected: + cBufferedSslContext m_Ssl; + + /** The certificate to send to the client */ + cX509CertPtr m_Cert; + + /** The private key used for the certificate */ + cCryptoKeyPtr m_PrivateKey; + + // cHTTPConnection overrides: + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client + virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client +} ; + + + + diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 29e1bbccf..73ee234d3 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -13,7 +13,7 @@ #include "Root.h" #include "HTTPServer/HTTPMessage.h" -#include "HTTPServer/HTTPConnection.h" +#include "HTTPServer/HTTPServerConnection.h" @@ -212,7 +212,7 @@ bool cWebAdmin::LoadLoginTemplate(void) -void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { if (!a_Request.HasAuth()) { @@ -349,7 +349,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque -void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { UNUSED(a_Request); @@ -364,7 +364,7 @@ void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & -void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { AString FileURL = a_Request.GetURL(); std::replace(FileURL.begin(), FileURL.end(), '\\', '/'); @@ -621,7 +621,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit) -void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { UNUSED(a_Connection); const AString & URL = a_Request.GetURL(); @@ -645,7 +645,7 @@ void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_ -void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) +void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) { UNUSED(a_Connection); cRequestData * Data = reinterpret_cast(a_Request.GetUserData()); @@ -660,7 +660,7 @@ void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_R -void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) { const AString & URL = a_Request.GetURL(); if ( diff --git a/src/WebAdmin.h b/src/WebAdmin.h index a3ebac43c..e5f8fc615 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -236,18 +236,18 @@ protected: cHTTPServer m_HTTPServer; /** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */ - void HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); /** Handles requests for the root page */ - void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); /** Handles requests for a file */ - void HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); // cHTTPServer::cCallbacks overrides: - virtual void OnRequestBegun (cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override; - virtual void OnRequestBody (cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override; - virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override; + virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override; + virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override; } ; // tolua_export -- cgit v1.2.3 From b92346e3cc517fe2d9f01720110e51cf3ea22c44 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 25 Dec 2015 19:54:04 +0100 Subject: Moved cHTTPRequest to a separate file, renamed to cHTTPRequestParser. --- src/HTTPServer/CMakeLists.txt | 2 + src/HTTPServer/HTTPFormParser.cpp | 8 +- src/HTTPServer/HTTPFormParser.h | 8 +- src/HTTPServer/HTTPMessage.cpp | 194 +------------------------------- src/HTTPServer/HTTPMessage.h | 95 +--------------- src/HTTPServer/HTTPRequestParser.cpp | 192 +++++++++++++++++++++++++++++++ src/HTTPServer/HTTPRequestParser.h | 109 ++++++++++++++++++ src/HTTPServer/HTTPServer.cpp | 14 +-- src/HTTPServer/HTTPServer.h | 14 +-- src/HTTPServer/HTTPServerConnection.cpp | 4 +- src/HTTPServer/HTTPServerConnection.h | 4 +- src/WebAdmin.cpp | 14 +-- src/WebAdmin.h | 14 +-- 13 files changed, 347 insertions(+), 325 deletions(-) create mode 100644 src/HTTPServer/HTTPRequestParser.cpp create mode 100644 src/HTTPServer/HTTPRequestParser.h diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTPServer/CMakeLists.txt index e8026f761..719c3949e 100644 --- a/src/HTTPServer/CMakeLists.txt +++ b/src/HTTPServer/CMakeLists.txt @@ -8,6 +8,7 @@ SET (SRCS EnvelopeParser.cpp HTTPFormParser.cpp HTTPMessage.cpp + HTTPRequestParser.cpp HTTPServer.cpp HTTPServerConnection.cpp MultipartParser.cpp @@ -20,6 +21,7 @@ SET (HDRS EnvelopeParser.h HTTPFormParser.h HTTPMessage.h + HTTPRequestParser.h HTTPServer.h HTTPServerConnection.h MultipartParser.h diff --git a/src/HTTPServer/HTTPFormParser.cpp b/src/HTTPServer/HTTPFormParser.cpp index 497f84033..ddf87f540 100644 --- a/src/HTTPServer/HTTPFormParser.cpp +++ b/src/HTTPServer/HTTPFormParser.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPFormParser.h" -#include "HTTPMessage.h" +#include "HTTPRequestParser.h" #include "MultipartParser.h" #include "NameValueParser.h" @@ -13,7 +13,7 @@ -cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) : +cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) : m_Callbacks(a_Callbacks), m_IsValid(true), m_IsCurrentPartFile(false), @@ -121,7 +121,7 @@ bool cHTTPFormParser::Finish(void) -bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) +bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) { const AString & ContentType = a_Request.GetContentType(); return ( @@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) -void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request) +void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request) { ASSERT(m_MultipartParser.get() == nullptr); m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); diff --git a/src/HTTPServer/HTTPFormParser.h b/src/HTTPServer/HTTPFormParser.h index f6cbe047f..fde6df696 100644 --- a/src/HTTPServer/HTTPFormParser.h +++ b/src/HTTPServer/HTTPFormParser.h @@ -15,7 +15,7 @@ // fwd: -class cHTTPRequest; +class cHTTPRequestParser; @@ -51,7 +51,7 @@ public: /** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */ - cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks); + cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks); /** Creates a parser with the specified content type that reads data from a string */ cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks); @@ -64,7 +64,7 @@ public: bool Finish(void); /** Returns true if the headers suggest the request has form data parseable by this class */ - static bool HasFormData(const cHTTPRequest & a_Request); + static bool HasFormData(const cHTTPRequestParser & a_Request); protected: @@ -97,7 +97,7 @@ protected: /** Sets up the object for parsing a fpkMultipart request */ - void BeginMultipart(const cHTTPRequest & a_Request); + void BeginMultipart(const cHTTPRequestParser & a_Request); /** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */ void ParseFormUrlEncoded(void); diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp index 360145a9a..ca63397dd 100644 --- a/src/HTTPServer/HTTPMessage.cpp +++ b/src/HTTPServer/HTTPMessage.cpp @@ -35,8 +35,8 @@ cHTTPMessage::cHTTPMessage(eKind a_Kind) : void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) { - AString Key = StrToLower(a_Key); - cNameValueMap::iterator itr = m_Headers.find(Key); + auto Key = StrToLower(a_Key); + auto itr = m_Headers.find(Key); if (itr == m_Headers.end()) { m_Headers[Key] = a_Value; @@ -66,194 +66,6 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) -//////////////////////////////////////////////////////////////////////////////// -// cHTTPRequest: - -cHTTPRequest::cHTTPRequest(void) : - super(mkRequest), - m_EnvelopeParser(*this), - m_IsValid(true), - m_UserData(nullptr), - m_HasAuth(false), - m_AllowKeepAlive(false) -{ -} - - - - - -size_t cHTTPRequest::ParseHeaders(const char * a_Data, size_t a_Size) -{ - if (!m_IsValid) - { - return AString::npos; - } - - if (m_Method.empty()) - { - // The first line hasn't been processed yet - size_t res = ParseRequestLine(a_Data, a_Size); - ASSERT((res == AString::npos) || (res <= a_Size)); - if ((res == AString::npos) || (res == a_Size)) - { - return res; - } - size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); - ASSERT((res2 == AString::npos) || (res2 <= a_Size - res)); - if (res2 == AString::npos) - { - m_IsValid = false; - return res2; - } - return res2 + res; - } - - if (m_EnvelopeParser.IsInHeaders()) - { - size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); - ASSERT((res == AString::npos) || (res <= a_Size)); - if (res == AString::npos) - { - m_IsValid = false; - } - return res; - } - return 0; -} - - - - - -AString cHTTPRequest::GetBareURL(void) const -{ - size_t idxQM = m_URL.find('?'); - if (idxQM != AString::npos) - { - return m_URL.substr(0, idxQM); - } - else - { - return m_URL; - } -} - - - - - -size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_Size) -{ - auto inBufferSoFar = m_IncomingHeaderData.size(); - m_IncomingHeaderData.append(a_Data, a_Size); - auto IdxEnd = m_IncomingHeaderData.size(); - - // Ignore the initial CRLFs (HTTP spec's "should") - size_t LineStart = 0; - while ( - (LineStart < IdxEnd) && - ( - (m_IncomingHeaderData[LineStart] == '\r') || - (m_IncomingHeaderData[LineStart] == '\n') - ) - ) - { - LineStart++; - } - if (LineStart >= IdxEnd) - { - m_IsValid = false; - return AString::npos; - } - - int NumSpaces = 0; - size_t MethodEnd = 0; - size_t URLEnd = 0; - for (size_t i = LineStart; i < IdxEnd; i++) - { - switch (m_IncomingHeaderData[i]) - { - case ' ': - { - switch (NumSpaces) - { - case 0: - { - MethodEnd = i; - break; - } - case 1: - { - URLEnd = i; - break; - } - default: - { - // Too many spaces in the request - m_IsValid = false; - return AString::npos; - } - } - NumSpaces += 1; - break; - } - case '\n': - { - if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) - { - // LF too early, without a CR, without two preceeding spaces or too soon after the second space - m_IsValid = false; - return AString::npos; - } - // Check that there's HTTP / version at the end - if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) - { - m_IsValid = false; - return AString::npos; - } - m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); - m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); - return i + 1 - inBufferSoFar; - } - } // switch (m_IncomingHeaderData[i]) - } // for i - m_IncomingHeaderData[] - - // CRLF hasn't been encountered yet, consider all data consumed - return a_Size; -} - - - - - -void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - if ( - (NoCaseCompare(a_Key, "Authorization") == 0) && - (strncmp(a_Value.c_str(), "Basic ", 6) == 0) - ) - { - AString UserPass = Base64Decode(a_Value.substr(6)); - size_t idxCol = UserPass.find(':'); - if (idxCol != AString::npos) - { - m_AuthUsername = UserPass.substr(0, idxCol); - m_AuthPassword = UserPass.substr(idxCol + 1); - m_HasAuth = true; - } - } - if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) - { - m_AllowKeepAlive = true; - } - AddHeader(a_Key, a_Value); -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cHTTPResponse: @@ -271,7 +83,7 @@ void cHTTPResponse::AppendToData(AString & a_DataStream) const a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); a_DataStream.append(m_ContentType); a_DataStream.append("\r\n"); - for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr) + for (auto itr = m_Headers.cbegin(), end = m_Headers.cend(); itr != end; ++itr) { if ((itr->first == "Content-Type") || (itr->first == "Content-Length")) { diff --git a/src/HTTPServer/HTTPMessage.h b/src/HTTPServer/HTTPMessage.h index 72ecb67d1..4af5e471b 100644 --- a/src/HTTPServer/HTTPMessage.h +++ b/src/HTTPServer/HTTPMessage.h @@ -49,7 +49,7 @@ protected: eKind m_Kind; - cNameValueMap m_Headers; + AStringMap m_Headers; /** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */ AString m_ContentType; @@ -64,99 +64,6 @@ protected: -class cHTTPRequest : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks -{ - typedef cHTTPMessage super; - -public: - cHTTPRequest(void); - - /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or AString::npos number for error - */ - size_t ParseHeaders(const char * a_Data, size_t a_Size); - - /** Returns true if the request did contain a Content-Length header */ - bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); } - - /** Returns the method used in the request */ - const AString & GetMethod(void) const { return m_Method; } - - /** Returns the URL used in the request */ - const AString & GetURL(void) const { return m_URL; } - - /** Returns the URL used in the request, without any parameters */ - AString GetBareURL(void) const; - - /** Sets the UserData pointer that is stored within this request. - The request doesn't touch this data (doesn't delete it)! */ - void SetUserData(void * a_UserData) { m_UserData = a_UserData; } - - /** Retrieves the UserData pointer that has been stored within this request. */ - void * GetUserData(void) const { return m_UserData; } - - /** Returns true if more data is expected for the request headers */ - bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } - - /** Returns true if the request did present auth data that was understood by the parser */ - bool HasAuth(void) const { return m_HasAuth; } - - /** Returns the username that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthUsername(void) const { return m_AuthUsername; } - - /** Returns the password that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthPassword(void) const { return m_AuthPassword; } - - bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } - -protected: - /** Parser for the envelope data */ - cEnvelopeParser m_EnvelopeParser; - - /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ - bool m_IsValid; - - /** Bufferred incoming data, while parsing for the request line */ - AString m_IncomingHeaderData; - - /** Method of the request (GET / PUT / POST / ...) */ - AString m_Method; - - /** Full URL of the request */ - AString m_URL; - - /** Data that the HTTPServer callbacks are allowed to store. */ - void * m_UserData; - - /** Set to true if the request contains auth data that was understood by the parser */ - bool m_HasAuth; - - /** The username used for auth */ - AString m_AuthUsername; - - /** The password used for auth */ - AString m_AuthPassword; - - /** Set to true if the request indicated that it supports keepalives. - If false, the server will close the connection once the request is finished */ - bool m_AllowKeepAlive; - - - /** Parses the incoming data for the first line (RequestLine) - Returns the number of bytes consumed, or AString::npos for an error - */ - size_t ParseRequestLine(const char * a_Data, size_t a_Size); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; -} ; - - - - - class cHTTPResponse : public cHTTPMessage { diff --git a/src/HTTPServer/HTTPRequestParser.cpp b/src/HTTPServer/HTTPRequestParser.cpp new file mode 100644 index 000000000..9c60c6053 --- /dev/null +++ b/src/HTTPServer/HTTPRequestParser.cpp @@ -0,0 +1,192 @@ + +// HTTPRequestParser.cpp + +// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests + +#include "Globals.h" +#include "HTTPRequestParser.h" + + + + + +cHTTPRequestParser::cHTTPRequestParser(void) : + super(mkRequest), + m_EnvelopeParser(*this), + m_IsValid(true), + m_UserData(nullptr), + m_HasAuth(false), + m_AllowKeepAlive(false) +{ +} + + + + + +size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) +{ + if (!m_IsValid) + { + return AString::npos; + } + + if (m_Method.empty()) + { + // The first line hasn't been processed yet + size_t res = ParseRequestLine(a_Data, a_Size); + if ((res == AString::npos) || (res == a_Size)) + { + return res; + } + size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); + if (res2 == AString::npos) + { + m_IsValid = false; + return res2; + } + return res2 + res; + } + + if (m_EnvelopeParser.IsInHeaders()) + { + size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); + if (res == AString::npos) + { + m_IsValid = false; + } + return res; + } + return 0; +} + + + + + +AString cHTTPRequestParser::GetBareURL(void) const +{ + size_t idxQM = m_URL.find('?'); + if (idxQM != AString::npos) + { + return m_URL.substr(0, idxQM); + } + else + { + return m_URL; + } +} + + + + + +size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) +{ + m_IncomingHeaderData.append(a_Data, a_Size); + size_t IdxEnd = m_IncomingHeaderData.size(); + + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < IdxEnd) && + ( + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= IdxEnd) + { + m_IsValid = false; + return AString::npos; + } + + int NumSpaces = 0; + size_t MethodEnd = 0; + size_t URLEnd = 0; + for (size_t i = LineStart; i < IdxEnd; i++) + { + switch (m_IncomingHeaderData[i]) + { + case ' ': + { + switch (NumSpaces) + { + case 0: + { + MethodEnd = i; + break; + } + case 1: + { + URLEnd = i; + break; + } + default: + { + // Too many spaces in the request + m_IsValid = false; + return AString::npos; + } + } + NumSpaces += 1; + break; + } + case '\n': + { + if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) + { + // LF too early, without a CR, without two preceeding spaces or too soon after the second space + m_IsValid = false; + return AString::npos; + } + // Check that there's HTTP / version at the end + if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) + { + m_IsValid = false; + return AString::npos; + } + m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); + m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); + return i + 1; + } + } // switch (m_IncomingHeaderData[i]) + } // for i - m_IncomingHeaderData[] + + // CRLF hasn't been encountered yet, consider all data consumed + return a_Size; +} + + + + + +void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } + if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) + { + m_AllowKeepAlive = true; + } + AddHeader(a_Key, a_Value); +} + + + + diff --git a/src/HTTPServer/HTTPRequestParser.h b/src/HTTPServer/HTTPRequestParser.h new file mode 100644 index 000000000..f3d3add91 --- /dev/null +++ b/src/HTTPServer/HTTPRequestParser.h @@ -0,0 +1,109 @@ + +// HTTPRequestParser.h + +// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests + + + + +#pragma once + +#include "HTTPMessage.h" +#include "EnvelopeParser.h" + + + + + +class cHTTPRequestParser : + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks +{ + typedef cHTTPMessage super; + +public: + cHTTPRequestParser(void); + + /** Parses the request line and then headers from the received data. + Returns the number of bytes consumed or AString::npos number for error + */ + size_t ParseHeaders(const char * a_Data, size_t a_Size); + + /** Returns true if the request did contain a Content-Length header */ + bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); } + + /** Returns the method used in the request */ + const AString & GetMethod(void) const { return m_Method; } + + /** Returns the URL used in the request */ + const AString & GetURL(void) const { return m_URL; } + + /** Returns the URL used in the request, without any parameters */ + AString GetBareURL(void) const; + + /** Sets the UserData pointer that is stored within this request. + The request doesn't touch this data (doesn't delete it)! */ + void SetUserData(void * a_UserData) { m_UserData = a_UserData; } + + /** Retrieves the UserData pointer that has been stored within this request. */ + void * GetUserData(void) const { return m_UserData; } + + /** Returns true if more data is expected for the request headers */ + bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } + + /** Returns true if the request did present auth data that was understood by the parser */ + bool HasAuth(void) const { return m_HasAuth; } + + /** Returns the username that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /** Returns the password that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + + bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } + +protected: + /** Parser for the envelope data */ + cEnvelopeParser m_EnvelopeParser; + + /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ + bool m_IsValid; + + /** Bufferred incoming data, while parsing for the request line */ + AString m_IncomingHeaderData; + + /** Method of the request (GET / PUT / POST / ...) */ + AString m_Method; + + /** Full URL of the request */ + AString m_URL; + + /** Data that the HTTPServer callbacks are allowed to store. */ + void * m_UserData; + + /** Set to true if the request contains auth data that was understood by the parser */ + bool m_HasAuth; + + /** The username used for auth */ + AString m_AuthUsername; + + /** The password used for auth */ + AString m_AuthPassword; + + /** Set to true if the request indicated that it supports keepalives. + If false, the server will close the connection once the request is finished */ + bool m_AllowKeepAlive; + + + /** Parses the incoming data for the first line (RequestLine) + Returns the number of bytes consumed, or AString::npos for an error + */ + size_t ParseRequestLine(const char * a_Data, size_t a_Size); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; +} ; + + + + diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp index 1cdf29bca..741f5f1c5 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTPServer/HTTPServer.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPServer.h" -#include "HTTPMessage.h" +#include "HTTPRequestParser.h" #include "HTTPServerConnection.h" #include "HTTPFormParser.h" #include "SslHTTPServerConnection.h" @@ -28,7 +28,7 @@ class cDebugCallbacks : public cHTTPServer::cCallbacks, protected cHTTPFormParser::cCallbacks { - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override { UNUSED(a_Connection); @@ -39,7 +39,7 @@ class cDebugCallbacks : } - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override { UNUSED(a_Connection); @@ -51,7 +51,7 @@ class cDebugCallbacks : } - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override { cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); if (FormParser != nullptr) @@ -280,7 +280,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo -void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { m_Callbacks->OnRequestBegun(a_Connection, a_Request); } @@ -289,7 +289,7 @@ void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequest -void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) +void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) { m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); } @@ -298,7 +298,7 @@ void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest -void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { m_Callbacks->OnRequestFinished(a_Connection, a_Request); a_Connection.AwaitNextRequest(); diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h index 2fd4cdcfc..15202a50e 100644 --- a/src/HTTPServer/HTTPServer.h +++ b/src/HTTPServer/HTTPServer.h @@ -21,7 +21,7 @@ // fwd: class cHTTPMessage; -class cHTTPRequest; +class cHTTPRequestParser; class cHTTPResponse; class cHTTPServerConnection; @@ -40,14 +40,14 @@ public: /** Called when a new request arrives over a connection and all its headers have been parsed. The request body needn't have arrived yet. */ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) = 0; + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; /** Called when another part of request body has arrived. May be called multiple times for a single request. */ - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0; + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0; /** Called when the request body has been fully received in previous calls to OnRequestBody() */ - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) = 0; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; } ; cHTTPServer(void); @@ -85,14 +85,14 @@ protected: cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); /** Called by cHTTPServerConnection when it finishes parsing the request header */ - void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); + void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); /** Called by cHTTPConenction when it receives more data for the request body. May be called multiple times for a single request. */ - void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size); + void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size); /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ - void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); + void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); } ; diff --git a/src/HTTPServer/HTTPServerConnection.cpp b/src/HTTPServer/HTTPServerConnection.cpp index 3c4acc2d4..0ee9f3ce7 100644 --- a/src/HTTPServer/HTTPServerConnection.cpp +++ b/src/HTTPServer/HTTPServerConnection.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPServerConnection.h" -#include "HTTPMessage.h" +#include "HTTPRequestParser.h" #include "HTTPServer.h" @@ -166,7 +166,7 @@ void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) { if (m_CurrentRequest == nullptr) { - m_CurrentRequest = new cHTTPRequest; + m_CurrentRequest = new cHTTPRequestParser; } size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); diff --git a/src/HTTPServer/HTTPServerConnection.h b/src/HTTPServer/HTTPServerConnection.h index d63cc7395..af2abb73f 100644 --- a/src/HTTPServer/HTTPServerConnection.h +++ b/src/HTTPServer/HTTPServerConnection.h @@ -18,7 +18,7 @@ // fwd: class cHTTPServer; class cHTTPResponse; -class cHTTPRequest; +class cHTTPRequestParser; @@ -81,7 +81,7 @@ protected: /** The request being currently received Valid only between having parsed the headers and finishing receiving the body. */ - cHTTPRequest * m_CurrentRequest; + cHTTPRequestParser * m_CurrentRequest; /** Number of bytes that remain to read for the complete body of the message to be received. Valid only in wcsRecvBody */ diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 73ee234d3..f63e8b3d9 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -12,7 +12,7 @@ #include "Server.h" #include "Root.h" -#include "HTTPServer/HTTPMessage.h" +#include "HTTPServer/HTTPRequestParser.h" #include "HTTPServer/HTTPServerConnection.h" @@ -212,7 +212,7 @@ bool cWebAdmin::LoadLoginTemplate(void) -void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { if (!a_Request.HasAuth()) { @@ -349,7 +349,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT -void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { UNUSED(a_Request); @@ -364,7 +364,7 @@ void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPReq -void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { AString FileURL = a_Request.GetURL(); std::replace(FileURL.begin(), FileURL.end(), '\\', '/'); @@ -621,7 +621,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit) -void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { UNUSED(a_Connection); const AString & URL = a_Request.GetURL(); @@ -645,7 +645,7 @@ void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPReques -void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) +void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) { UNUSED(a_Connection); cRequestData * Data = reinterpret_cast(a_Request.GetUserData()); @@ -660,7 +660,7 @@ void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequest -void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) { const AString & URL = a_Request.GetURL(); if ( diff --git a/src/WebAdmin.h b/src/WebAdmin.h index e5f8fc615..70d772f1e 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -190,7 +190,7 @@ protected: cHTTPFormParser m_Form; - cWebadminRequestData(cHTTPRequest & a_Request) : + cWebadminRequestData(cHTTPRequestParser & a_Request) : m_Form(a_Request, *this) { } @@ -236,18 +236,18 @@ protected: cHTTPServer m_HTTPServer; /** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */ - void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); + void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); /** Handles requests for the root page */ - void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); + void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); /** Handles requests for a file */ - void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request); + void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); // cHTTPServer::cCallbacks overrides: - virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override; - virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override; - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequest & a_Request) override; + virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override; + virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override; } ; // tolua_export -- cgit v1.2.3 From fce68dc8f39dfceab3e80513390bae8fa936a6b9 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 1 Jan 2016 16:42:22 +0100 Subject: Added HTTPResponseParser. --- src/HTTPServer/CMakeLists.txt | 4 + src/HTTPServer/HTTPMessage.h | 1 + src/HTTPServer/HTTPResponseParser.cpp | 177 ++++++++++++++++++++++++++++++ src/HTTPServer/HTTPResponseParser.h | 118 ++++++++++++++++++++ src/HTTPServer/TransferEncodingParser.cpp | 132 ++++++++++++++++++++++ src/HTTPServer/TransferEncodingParser.h | 76 +++++++++++++ 6 files changed, 508 insertions(+) create mode 100644 src/HTTPServer/HTTPResponseParser.cpp create mode 100644 src/HTTPServer/HTTPResponseParser.h create mode 100644 src/HTTPServer/TransferEncodingParser.cpp create mode 100644 src/HTTPServer/TransferEncodingParser.h diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTPServer/CMakeLists.txt index 719c3949e..4597f6eda 100644 --- a/src/HTTPServer/CMakeLists.txt +++ b/src/HTTPServer/CMakeLists.txt @@ -9,11 +9,13 @@ SET (SRCS HTTPFormParser.cpp HTTPMessage.cpp HTTPRequestParser.cpp + HTTPResponseParser.cpp HTTPServer.cpp HTTPServerConnection.cpp MultipartParser.cpp NameValueParser.cpp SslHTTPServerConnection.cpp + TransferEncodingParser.cpp UrlParser.cpp ) @@ -22,11 +24,13 @@ SET (HDRS HTTPFormParser.h HTTPMessage.h HTTPRequestParser.h + HTTPResponseParser.h HTTPServer.h HTTPServerConnection.h MultipartParser.h NameValueParser.h SslHTTPServerConnection.h + TransferEncodingParser.h UrlParser.h ) diff --git a/src/HTTPServer/HTTPMessage.h b/src/HTTPServer/HTTPMessage.h index 4af5e471b..50b1f9e19 100644 --- a/src/HTTPServer/HTTPMessage.h +++ b/src/HTTPServer/HTTPMessage.h @@ -49,6 +49,7 @@ protected: eKind m_Kind; + /** Map of headers, with their keys lowercased. */ AStringMap m_Headers; /** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */ diff --git a/src/HTTPServer/HTTPResponseParser.cpp b/src/HTTPServer/HTTPResponseParser.cpp new file mode 100644 index 000000000..b3ce9dab6 --- /dev/null +++ b/src/HTTPServer/HTTPResponseParser.cpp @@ -0,0 +1,177 @@ + +// HTTPResponseParser.cpp + +// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses + +#include "Globals.h" +#include "HTTPResponseParser.h" + + + + + +cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks): + Super(mkResponse), + m_Callbacks(a_Callbacks), + m_IsInHeaders(true), + m_IsFinished(false), + m_EnvelopeParser(*this) +{ +} + + + + + +size_t cHTTPResponseParser::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 a_Size; + } + + // If still waiting for the status line, add to buffer and try parsing it: + if (m_StatusLine.empty()) + { + m_Buffer.append(a_Data, a_Size); + if (!ParseStatusLine()) + { + // All data used, but not a complete status line yet. + return 0; + } + if (m_HasHadError) + { + return AString::npos; + } + // Status line completed, feed the rest of the buffer into the envelope parser: + auto bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size()); + if (bytesConsumed == AString::npos) + { + m_HasHadError = true; + m_Callbacks.OnError("Failed to parse the envelope"); + return AString::npos; + } + m_Buffer.erase(0, bytesConsumed); + if (!m_Buffer.empty()) + { + // Headers finished and there's still data left in the buffer, process it as message body: + m_IsInHeaders = false; + return ParseBody(m_Buffer.data(), m_Buffer.size()); + } + return 0; + } // if (m_StatusLine.empty()) + + // If still parsing headers, send them to the envelope parser: + if (m_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 (bytesConsumed < a_Size) + { + // Headers finished and there's still data left in the buffer, process it as message body: + HeadersFinished(); + return ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); + } + return 0; + } + + // Already parsing the body + return ParseBody(a_Data, a_Size); +} + + + + + +bool cHTTPResponseParser::ParseStatusLine(void) +{ + auto idxLineEnd = m_Buffer.find("\r\n"); + if (idxLineEnd == AString::npos) + { + // Not a complete line yet + return false; + } + m_StatusLine = m_Buffer.substr(0, idxLineEnd); + m_Buffer.erase(0, idxLineEnd + 2); + m_Callbacks.OnStatusLine(m_StatusLine); + return true; +} + + + + +size_t cHTTPResponseParser::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 + return AString::npos; + } + + // Parse the body using the transfer encoding parser: + return m_TransferEncodingParser->Parse(a_Data, a_Size); +} + + + + + +void cHTTPResponseParser::HeadersFinished(void) +{ + m_IsInHeaders = false; + m_Callbacks.OnHeadersFinished(); + + auto transferEncoding = m_Headers.find("transfer-encoding"); + if (transferEncoding == m_Headers.end()) + { + m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength); + } +} + + + + + +void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + AddHeader(a_Key, a_Value); + m_Callbacks.OnHeaderLine(a_Key, a_Value); +} + + + + + +void cHTTPResponseParser::OnError(const AString & a_ErrorDescription) +{ + m_HasHadError = true; + m_Callbacks.OnError(a_ErrorDescription); +} + + + + + +void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size) +{ + m_Callbacks.OnBodyData(a_Data, a_Size); +} + + + + + +void cHTTPResponseParser::OnBodyFinished(void) +{ + m_Callbacks.OnBodyFinished(); +} + + + + diff --git a/src/HTTPServer/HTTPResponseParser.h b/src/HTTPServer/HTTPResponseParser.h new file mode 100644 index 000000000..19652ccef --- /dev/null +++ b/src/HTTPServer/HTTPResponseParser.h @@ -0,0 +1,118 @@ + +// HTTPResponseParser.h + +// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses + + + + +#pragma once + +#include "HTTPMessage.h" +#include "TransferEncodingParser.h" + + + + + +class cHTTPResponseParser: + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks, + protected cTransferEncodingParser::cCallbacks +{ + typedef cHTTPMessage Super; + +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called when the status line is fully parsed. */ + virtual void OnStatusLine(const AString & a_StatusLine) = 0; + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + cHTTPResponseParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes from the end of a_Data that is already not part of this response. + Returns AString::npos on an error. */ + size_t Parse(const char * a_Data, size_t a_Size); + + /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). + Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ + void Finish(void); + + /** Returns true if the entire response has been already parsed. */ + bool IsFinished(void) const { return m_IsFinished; } + + +protected: + + /** The callbacks used for reporting. */ + cCallbacks & m_Callbacks; + + /** Set to true if an error has been encountered by the parser. */ + bool m_HasHadError; + + /** True if the parser is still parsing the status or headers. */ + bool m_IsInHeaders; + + /** True if the response has been fully parsed. */ + bool m_IsFinished; + + /** The complete status line of the response. Empty if not parsed yet. */ + AString m_StatusLine; + + /** Buffer for the incoming data until the status line is parsed. */ + AString m_Buffer; + + /** Parser for the envelope data (headers) */ + cEnvelopeParser m_EnvelopeParser; + + /** The specific parser for the transfer encoding used by this response. */ + cTransferEncodingParserPtr m_TransferEncodingParser; + + + /** Parses the status line out of the m_Buffer. + Removes the status line from m_Buffer, if appropriate. + Returns true if the entire status line has been parsed. */ + bool ParseStatusLine(void); + + /** Parses the message body. + Processes transfer encoding and calls the callbacks for body data. + Returns the number of bytes from the end of a_Data that is already not part of this response. + Returns AString::npos on error. */ + size_t ParseBody(const char * a_Data, size_t a_Size); + + /** Called internally when the headers-parsing has just finished. */ + void HeadersFinished(void); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; + + // cTransferEncodingParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override; + virtual void OnBodyData(const void * a_Data, size_t a_Size) override; + virtual void OnBodyFinished(void) override; +}; + + + + diff --git a/src/HTTPServer/TransferEncodingParser.cpp b/src/HTTPServer/TransferEncodingParser.cpp new file mode 100644 index 000000000..8b703fd42 --- /dev/null +++ b/src/HTTPServer/TransferEncodingParser.cpp @@ -0,0 +1,132 @@ + +// TransferEncodingParser.cpp + +// Implements the cTransferEncodingParser class and its descendants representing the parser for the various transfer encodings (chunked etc.) + +#include "Globals.h" +#include "TransferEncodingParser.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cChunkedTEParser: + +class cChunkedTEParser: + public cTransferEncodingParser +{ + typedef cTransferEncodingParser Super; + +public: + cChunkedTEParser(cCallbacks & a_Callbacks): + Super(a_Callbacks), + m_IsFinished(false) + { + } + + +protected: + + /** True if the datastream has finished (zero-length chunk received). */ + bool m_IsFinished; + + + // cTransferEncodingParser overrides: + virtual size_t Parse(const char * a_Data, size_t a_Size) override + { + // TODO + m_Callbacks.OnError("cChunkedTEParser not implemented yet"); + return AString::npos; + } + + virtual void Finish(void) override + { + if (!m_IsFinished) + { + m_Callbacks.OnError("ChunkedTransferEncoding: Finish signal received before the data stream ended"); + } + m_IsFinished = true; + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cIdentityTEParser: + +class cIdentityTEParser: + public cTransferEncodingParser +{ + typedef cTransferEncodingParser Super; + +public: + cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength): + Super(a_Callbacks), + m_BytesLeft(a_ContentLength) + { + } + + +protected: + /** How many bytes of content are left before the message ends. */ + size_t m_BytesLeft; + + // cTransferEncodingParser overrides: + virtual size_t Parse(const char * a_Data, size_t a_Size) override + { + auto size = std::min(a_Size, m_BytesLeft); + if (size > 0) + { + m_Callbacks.OnBodyData(a_Data, size); + } + m_BytesLeft -= size; + if (m_BytesLeft == 0) + { + m_Callbacks.OnBodyFinished(); + } + return a_Size - size; + } + + virtual void Finish(void) override + { + if (m_BytesLeft > 0) + { + m_Callbacks.OnError("IdentityTransferEncoding: body was truncated"); + } + else + { + // BodyFinished has already been called, just bail out + } + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTransferEncodingParser: + +cTransferEncodingParserPtr cTransferEncodingParser::Create( + cCallbacks & a_Callbacks, + const AString & a_TransferEncoding, + size_t a_ContentLength +) +{ + if (a_TransferEncoding == "chunked") + { + return std::make_shared(a_Callbacks); + } + if (a_TransferEncoding.empty()) + { + return std::make_shared(a_Callbacks, a_ContentLength); + } + return nullptr; +} + + + + diff --git a/src/HTTPServer/TransferEncodingParser.h b/src/HTTPServer/TransferEncodingParser.h new file mode 100644 index 000000000..ce3d01df7 --- /dev/null +++ b/src/HTTPServer/TransferEncodingParser.h @@ -0,0 +1,76 @@ + +// TransferEncodingParser.h + +// Declares the cTransferEncodingParser class representing the parser for the various transfer encodings (chunked etc.) + +#pragma once + + + + + +// fwd: +class cTransferEncodingParser; +typedef SharedPtr cTransferEncodingParserPtr; + + + + + +/** Used as both the interface that all the parsers share and the (static) factory creating such parsers. */ +class cTransferEncodingParser +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + + // Force a virtual destructor in all descendants + virtual ~cTransferEncodingParser() {} + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes from the end of a_Data that is already not part of this message (if the parser can detect it). + Returns AString::npos on an error. */ + virtual size_t Parse(const char * a_Data, size_t a_Size) = 0; + + /** To be called when the stream is terminated from the source (connection closed). + Flushes any buffers and calls appropriate callbacks. */ + virtual void Finish(void) = 0; + + /** Creates a new parser for the specified encoding. + If the encoding is not known, returns a nullptr. + a_ContentLength is the length of the content, received in a Content-Length header. + It is used for the Identity encoding, it is ignored for the Chunked encoding. */ + static cTransferEncodingParserPtr Create( + cCallbacks & a_Callbacks, + const AString & a_TransferEncoding, + size_t a_ContentLength + ); + +protected: + /** The callbacks used to report progress. */ + cCallbacks & m_Callbacks; + + + cTransferEncodingParser(cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + + + + -- cgit v1.2.3 From fea556ca1b8aeec975f5276d5d829ee6275841d9 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 3 Jan 2016 15:59:55 +0100 Subject: Renamed HTTPServer folder to HTTP. It contains client code as well. --- src/Bindings/ManualBindings.cpp | 2 +- src/CMakeLists.txt | 2 +- src/HTTP/CMakeLists.txt | 45 ++++ src/HTTP/EnvelopeParser.cpp | 132 +++++++++ src/HTTP/EnvelopeParser.h | 73 +++++ src/HTTP/HTTPFormParser.cpp | 293 ++++++++++++++++++++ src/HTTP/HTTPFormParser.h | 114 ++++++++ src/HTTP/HTTPMessage.cpp | 102 +++++++ src/HTTP/HTTPMessage.h | 84 ++++++ src/HTTP/HTTPRequestParser.cpp | 192 ++++++++++++++ src/HTTP/HTTPRequestParser.h | 109 ++++++++ src/HTTP/HTTPResponseParser.cpp | 183 +++++++++++++ src/HTTP/HTTPResponseParser.h | 118 +++++++++ src/HTTP/HTTPServer.cpp | 309 +++++++++++++++++++++ src/HTTP/HTTPServer.h | 101 +++++++ src/HTTP/HTTPServerConnection.cpp | 278 +++++++++++++++++++ src/HTTP/HTTPServerConnection.h | 124 +++++++++ src/HTTP/MultipartParser.cpp | 254 ++++++++++++++++++ src/HTTP/MultipartParser.h | 79 ++++++ src/HTTP/NameValueParser.cpp | 413 +++++++++++++++++++++++++++++ src/HTTP/NameValueParser.h | 70 +++++ src/HTTP/SslHTTPServerConnection.cpp | 115 ++++++++ src/HTTP/SslHTTPServerConnection.h | 47 ++++ src/HTTP/TransferEncodingParser.cpp | 393 +++++++++++++++++++++++++++ src/HTTP/TransferEncodingParser.h | 76 ++++++ src/HTTP/UrlParser.cpp | 200 ++++++++++++++ src/HTTP/UrlParser.h | 58 ++++ src/HTTPServer/CMakeLists.txt | 45 ---- src/HTTPServer/EnvelopeParser.cpp | 132 --------- src/HTTPServer/EnvelopeParser.h | 73 ----- src/HTTPServer/HTTPFormParser.cpp | 293 -------------------- src/HTTPServer/HTTPFormParser.h | 114 -------- src/HTTPServer/HTTPMessage.cpp | 102 ------- src/HTTPServer/HTTPMessage.h | 84 ------ src/HTTPServer/HTTPRequestParser.cpp | 192 -------------- src/HTTPServer/HTTPRequestParser.h | 109 -------- src/HTTPServer/HTTPResponseParser.cpp | 177 ------------- src/HTTPServer/HTTPResponseParser.h | 118 --------- src/HTTPServer/HTTPServer.cpp | 309 --------------------- src/HTTPServer/HTTPServer.h | 101 ------- src/HTTPServer/HTTPServerConnection.cpp | 278 ------------------- src/HTTPServer/HTTPServerConnection.h | 124 --------- src/HTTPServer/MultipartParser.cpp | 254 ------------------ src/HTTPServer/MultipartParser.h | 79 ------ src/HTTPServer/NameValueParser.cpp | 413 ----------------------------- src/HTTPServer/NameValueParser.h | 70 ----- src/HTTPServer/SslHTTPServerConnection.cpp | 115 -------- src/HTTPServer/SslHTTPServerConnection.h | 47 ---- src/HTTPServer/TransferEncodingParser.cpp | 132 --------- src/HTTPServer/TransferEncodingParser.h | 76 ------ src/HTTPServer/UrlParser.cpp | 200 -------------- src/HTTPServer/UrlParser.h | 58 ---- src/Root.h | 2 +- src/WebAdmin.cpp | 4 +- src/WebAdmin.h | 4 +- 55 files changed, 3969 insertions(+), 3702 deletions(-) create mode 100644 src/HTTP/CMakeLists.txt create mode 100644 src/HTTP/EnvelopeParser.cpp create mode 100644 src/HTTP/EnvelopeParser.h create mode 100644 src/HTTP/HTTPFormParser.cpp create mode 100644 src/HTTP/HTTPFormParser.h create mode 100644 src/HTTP/HTTPMessage.cpp create mode 100644 src/HTTP/HTTPMessage.h create mode 100644 src/HTTP/HTTPRequestParser.cpp create mode 100644 src/HTTP/HTTPRequestParser.h create mode 100644 src/HTTP/HTTPResponseParser.cpp create mode 100644 src/HTTP/HTTPResponseParser.h create mode 100644 src/HTTP/HTTPServer.cpp create mode 100644 src/HTTP/HTTPServer.h create mode 100644 src/HTTP/HTTPServerConnection.cpp create mode 100644 src/HTTP/HTTPServerConnection.h create mode 100644 src/HTTP/MultipartParser.cpp create mode 100644 src/HTTP/MultipartParser.h create mode 100644 src/HTTP/NameValueParser.cpp create mode 100644 src/HTTP/NameValueParser.h create mode 100644 src/HTTP/SslHTTPServerConnection.cpp create mode 100644 src/HTTP/SslHTTPServerConnection.h create mode 100644 src/HTTP/TransferEncodingParser.cpp create mode 100644 src/HTTP/TransferEncodingParser.h create mode 100644 src/HTTP/UrlParser.cpp create mode 100644 src/HTTP/UrlParser.h delete mode 100644 src/HTTPServer/CMakeLists.txt delete mode 100644 src/HTTPServer/EnvelopeParser.cpp delete mode 100644 src/HTTPServer/EnvelopeParser.h delete mode 100644 src/HTTPServer/HTTPFormParser.cpp delete mode 100644 src/HTTPServer/HTTPFormParser.h delete mode 100644 src/HTTPServer/HTTPMessage.cpp delete mode 100644 src/HTTPServer/HTTPMessage.h delete mode 100644 src/HTTPServer/HTTPRequestParser.cpp delete mode 100644 src/HTTPServer/HTTPRequestParser.h delete mode 100644 src/HTTPServer/HTTPResponseParser.cpp delete mode 100644 src/HTTPServer/HTTPResponseParser.h delete mode 100644 src/HTTPServer/HTTPServer.cpp delete mode 100644 src/HTTPServer/HTTPServer.h delete mode 100644 src/HTTPServer/HTTPServerConnection.cpp delete mode 100644 src/HTTPServer/HTTPServerConnection.h delete mode 100644 src/HTTPServer/MultipartParser.cpp delete mode 100644 src/HTTPServer/MultipartParser.h delete mode 100644 src/HTTPServer/NameValueParser.cpp delete mode 100644 src/HTTPServer/NameValueParser.h delete mode 100644 src/HTTPServer/SslHTTPServerConnection.cpp delete mode 100644 src/HTTPServer/SslHTTPServerConnection.h delete mode 100644 src/HTTPServer/TransferEncodingParser.cpp delete mode 100644 src/HTTPServer/TransferEncodingParser.h delete mode 100644 src/HTTPServer/UrlParser.cpp delete mode 100644 src/HTTPServer/UrlParser.h diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 110f22f1c..523244ed2 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -36,7 +36,7 @@ #include "../StringCompression.h" #include "../CommandOutput.h" #include "../BuildInfo.h" -#include "../HTTPServer/UrlParser.h" +#include "../HTTP/UrlParser.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3d9e10aa5..5c57be1c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,7 @@ include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/polarssl/include include_directories (SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/../lib/libevent/include") set(FOLDERS - OSSupport HTTPServer Items Blocks Protocol Generating PolarSSL++ Bindings + OSSupport HTTP Items Blocks Protocol Generating PolarSSL++ Bindings WorldStorage Mobs Entities Simulator Simulator/IncrementalRedstoneSimulator BlockEntities UI Noise ) diff --git a/src/HTTP/CMakeLists.txt b/src/HTTP/CMakeLists.txt new file mode 100644 index 000000000..4597f6eda --- /dev/null +++ b/src/HTTP/CMakeLists.txt @@ -0,0 +1,45 @@ + +cmake_minimum_required (VERSION 2.6) +project (Cuberite) + +include_directories ("${PROJECT_SOURCE_DIR}/../") + +SET (SRCS + EnvelopeParser.cpp + HTTPFormParser.cpp + HTTPMessage.cpp + HTTPRequestParser.cpp + HTTPResponseParser.cpp + HTTPServer.cpp + HTTPServerConnection.cpp + MultipartParser.cpp + NameValueParser.cpp + SslHTTPServerConnection.cpp + TransferEncodingParser.cpp + UrlParser.cpp +) + +SET (HDRS + EnvelopeParser.h + HTTPFormParser.h + HTTPMessage.h + HTTPRequestParser.h + HTTPResponseParser.h + HTTPServer.h + HTTPServerConnection.h + MultipartParser.h + NameValueParser.h + SslHTTPServerConnection.h + TransferEncodingParser.h + UrlParser.h +) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set_source_files_properties(HTTPServer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=global-constructors ") + set_source_files_properties(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum") + set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare") +endif() + +if(NOT MSVC) + add_library(HTTPServer ${SRCS} ${HDRS}) +endif() diff --git a/src/HTTP/EnvelopeParser.cpp b/src/HTTP/EnvelopeParser.cpp new file mode 100644 index 000000000..407e9dcfc --- /dev/null +++ b/src/HTTP/EnvelopeParser.cpp @@ -0,0 +1,132 @@ + +// 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; +} + + + + diff --git a/src/HTTP/EnvelopeParser.h b/src/HTTP/EnvelopeParser.h new file mode 100644 index 000000000..2fa930539 --- /dev/null +++ b/src/HTTP/EnvelopeParser.h @@ -0,0 +1,73 @@ + +// EnvelopeParser.h + +// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME + + + + + +#pragma once + + + + + +class cEnvelopeParser +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when a full header line is parsed */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + } ; + + + cEnvelopeParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data. + Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header. + Returns AString::npos on error + */ + size_t Parse(const char * a_Data, size_t a_Size); + + /** Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream */ + void Reset(void); + + /** Returns true if more input is expected for the envelope header */ + bool IsInHeaders(void) const { return m_IsInHeaders; } + + /** Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions */ + void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; } + +public: + /** Callbacks to call for the various events */ + cCallbacks & m_Callbacks; + + /** Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data. */ + bool m_IsInHeaders; + + /** Buffer for the incoming data until it is parsed */ + AString m_IncomingData; + + /** Holds the last parsed key; used for line-wrapped values */ + AString m_LastKey; + + /** Holds the last parsed value; used for line-wrapped values */ + AString m_LastValue; + + + /** Notifies the callback of the key / value stored in m_LastKey / m_LastValue, then erases them */ + void NotifyLast(void); + + /** Parses one line of header data. Returns true if successful */ + bool ParseLine(const char * a_Data, size_t a_Size); +} ; + + + + diff --git a/src/HTTP/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp new file mode 100644 index 000000000..ddf87f540 --- /dev/null +++ b/src/HTTP/HTTPFormParser.cpp @@ -0,0 +1,293 @@ + +// HTTPFormParser.cpp + +// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP + +#include "Globals.h" +#include "HTTPFormParser.h" +#include "HTTPRequestParser.h" +#include "MultipartParser.h" +#include "NameValueParser.h" + + + + + +cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_IsValid(true), + m_IsCurrentPartFile(false), + m_FileHasBeenAnnounced(false) +{ + if (a_Request.GetMethod() == "GET") + { + m_Kind = fpkURL; + + // Directly parse the URL in the request: + const AString & URL = a_Request.GetURL(); + size_t idxQM = URL.find('?'); + if (idxQM != AString::npos) + { + Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1); + } + return; + } + if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) + { + if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) + { + m_Kind = fpkFormUrlEncoded; + return; + } + if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) + { + m_Kind = fpkMultipart; + BeginMultipart(a_Request); + return; + } + } + // Invalid method / content type combination, this is not a HTTP form + m_IsValid = false; +} + + + + + +cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_Kind(a_Kind), + m_IsValid(true), + m_IsCurrentPartFile(false), + m_FileHasBeenAnnounced(false) +{ + Parse(a_Data, a_Size); +} + + + + + +void cHTTPFormParser::Parse(const char * a_Data, size_t a_Size) +{ + if (!m_IsValid) + { + return; + } + + switch (m_Kind) + { + case fpkURL: + case fpkFormUrlEncoded: + { + // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish() + m_IncomingData.append(a_Data, a_Size); + break; + } + case fpkMultipart: + { + ASSERT(m_MultipartParser.get() != nullptr); + m_MultipartParser->Parse(a_Data, a_Size); + break; + } + } +} + + + + + +bool cHTTPFormParser::Finish(void) +{ + switch (m_Kind) + { + case fpkURL: + case fpkFormUrlEncoded: + { + // m_IncomingData has all the form data, parse it now: + ParseFormUrlEncoded(); + break; + } + case fpkMultipart: + { + // Nothing needed for other formats + break; + } + } + return (m_IsValid && m_IncomingData.empty()); +} + + + + + +bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) +{ + const AString & ContentType = a_Request.GetContentType(); + return ( + (ContentType == "application/x-www-form-urlencoded") || + (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) || + ( + (a_Request.GetMethod() == "GET") && + (a_Request.GetURL().find('?') != AString::npos) + ) + ); +} + + + + + +void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request) +{ + ASSERT(m_MultipartParser.get() == nullptr); + m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); +} + + + + + +void cHTTPFormParser::ParseFormUrlEncoded(void) +{ + // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish() + // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway + AStringVector Lines = StringSplit(m_IncomingData, "&"); + for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr) + { + AStringVector Components = StringSplit(*itr, "="); + switch (Components.size()) + { + default: + { + // Neither name nor value, or too many "="s, mark this as invalid form: + m_IsValid = false; + return; + } + case 1: + { + // Only name present + (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = ""; + break; + } + case 2: + { + // name=value format: + (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' ')); + break; + } + } + } // for itr - Lines[] + m_IncomingData.clear(); +} + + + + + +void cHTTPFormParser::OnPartStart(void) +{ + m_CurrentPartFileName.clear(); + m_CurrentPartName.clear(); + m_IsCurrentPartFile = false; + m_FileHasBeenAnnounced = false; +} + + + + + +void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value) +{ + if (NoCaseCompare(a_Key, "Content-Disposition") == 0) + { + size_t len = a_Value.size(); + size_t ParamsStart = AString::npos; + for (size_t i = 0; i < len; ++i) + { + if (a_Value[i] > ' ') + { + if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0) + { + // Content disposition is not "form-data", mark the whole form invalid + m_IsValid = false; + return; + } + ParamsStart = a_Value.find(';', i + 9); + break; + } + } + if (ParamsStart == AString::npos) + { + // There is data missing in the Content-Disposition field, mark the whole form invalid: + m_IsValid = false; + return; + } + + // Parse the field name and optional filename from this header: + cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart); + Parser.Finish(); + m_CurrentPartName = Parser["name"]; + if (!Parser.IsValid() || m_CurrentPartName.empty()) + { + // The required parameter "name" is missing, mark the whole form invalid: + m_IsValid = false; + return; + } + m_CurrentPartFileName = Parser["filename"]; + } +} + + + + + +void cHTTPFormParser::OnPartData(const char * a_Data, size_t a_Size) +{ + if (m_CurrentPartName.empty()) + { + // Prologue, epilogue or invalid part + return; + } + if (m_CurrentPartFileName.empty()) + { + // This is a variable, store it in the map + iterator itr = find(m_CurrentPartName); + if (itr == end()) + { + (*this)[m_CurrentPartName] = AString(a_Data, a_Size); + } + else + { + itr->second.append(a_Data, a_Size); + } + } + else + { + // This is a file, pass it on through the callbacks + if (!m_FileHasBeenAnnounced) + { + m_Callbacks.OnFileStart(*this, m_CurrentPartFileName); + m_FileHasBeenAnnounced = true; + } + m_Callbacks.OnFileData(*this, a_Data, a_Size); + } +} + + + + + +void cHTTPFormParser::OnPartEnd(void) +{ + if (m_FileHasBeenAnnounced) + { + m_Callbacks.OnFileEnd(*this); + } + m_CurrentPartName.clear(); + m_CurrentPartFileName.clear(); +} + + + + diff --git a/src/HTTP/HTTPFormParser.h b/src/HTTP/HTTPFormParser.h new file mode 100644 index 000000000..fde6df696 --- /dev/null +++ b/src/HTTP/HTTPFormParser.h @@ -0,0 +1,114 @@ + +// HTTPFormParser.h + +// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP + + + + +#pragma once + +#include "MultipartParser.h" + + + + + +// fwd: +class cHTTPRequestParser; + + + + + +class cHTTPFormParser : + public std::map, + public cMultipartParser::cCallbacks +{ +public: + enum eKind + { + fpkURL, ///< The form has been transmitted as parameters to a GET request + fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded" + fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data" + } ; + + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when a new file part is encountered in the form data */ + virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0; + + /** Called when more file data has come for the current file in the form data */ + virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) = 0; + + /** Called when the current file part has ended in the form data */ + virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0; + } ; + + + /** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */ + cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks); + + /** Creates a parser with the specified content type that reads data from a string */ + cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks); + + /** Adds more data into the parser, as the request body is received */ + void Parse(const char * a_Data, size_t a_Size); + + /** Notifies that there's no more data incoming and the parser should finish its parsing. + Returns true if parsing successful. */ + bool Finish(void); + + /** Returns true if the headers suggest the request has form data parseable by this class */ + static bool HasFormData(const cHTTPRequestParser & a_Request); + +protected: + + /** The callbacks to call for incoming file data */ + cCallbacks & m_Callbacks; + + /** The kind of the parser (decided in the constructor, used in Parse() */ + eKind m_Kind; + + /** Buffer for the incoming data until it's parsed */ + AString m_IncomingData; + + /** True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false. */ + bool m_IsValid; + + /** The parser for the multipart data, if used */ + std::unique_ptr m_MultipartParser; + + /** Name of the currently parsed part in multipart data */ + AString m_CurrentPartName; + + /** True if the currently parsed part in multipart data is a file */ + bool m_IsCurrentPartFile; + + /** Filename of the current parsed part in multipart data (for file uploads) */ + AString m_CurrentPartFileName; + + /** Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd */ + bool m_FileHasBeenAnnounced; + + + /** Sets up the object for parsing a fpkMultipart request */ + void BeginMultipart(const cHTTPRequestParser & a_Request); + + /** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */ + void ParseFormUrlEncoded(void); + + // cMultipartParser::cCallbacks overrides: + virtual void OnPartStart (void) override; + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override; + virtual void OnPartData (const char * a_Data, size_t a_Size) override; + virtual void OnPartEnd (void) override; +} ; + + + + diff --git a/src/HTTP/HTTPMessage.cpp b/src/HTTP/HTTPMessage.cpp new file mode 100644 index 000000000..ca63397dd --- /dev/null +++ b/src/HTTP/HTTPMessage.cpp @@ -0,0 +1,102 @@ + +// HTTPMessage.cpp + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + +#include "Globals.h" +#include "HTTPMessage.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPMessage: + +cHTTPMessage::cHTTPMessage(eKind a_Kind) : + m_Kind(a_Kind), + m_ContentLength(AString::npos) +{ +} + + + + + +void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) +{ + auto Key = StrToLower(a_Key); + auto itr = m_Headers.find(Key); + if (itr == m_Headers.end()) + { + m_Headers[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); + } + + // Special processing for well-known headers: + if (Key == "content-type") + { + m_ContentType = m_Headers[Key]; + } + else if (Key == "content-length") + { + if (!StringToInteger(m_Headers[Key], m_ContentLength)) + { + m_ContentLength = 0; + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPResponse: + +cHTTPResponse::cHTTPResponse(void) : + super(mkResponse) +{ +} + + + + + +void cHTTPResponse::AppendToData(AString & a_DataStream) const +{ + a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); + a_DataStream.append(m_ContentType); + a_DataStream.append("\r\n"); + for (auto itr = m_Headers.cbegin(), end = m_Headers.cend(); itr != end; ++itr) + { + if ((itr->first == "Content-Type") || (itr->first == "Content-Length")) + { + continue; + } + a_DataStream.append(itr->first); + a_DataStream.append(": "); + a_DataStream.append(itr->second); + a_DataStream.append("\r\n"); + } // for itr - m_Headers[] + a_DataStream.append("\r\n"); +} + + + + diff --git a/src/HTTP/HTTPMessage.h b/src/HTTP/HTTPMessage.h new file mode 100644 index 000000000..50b1f9e19 --- /dev/null +++ b/src/HTTP/HTTPMessage.h @@ -0,0 +1,84 @@ + +// HTTPMessage.h + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + + + + + +#pragma once + +#include "EnvelopeParser.h" + + + + + +class cHTTPMessage +{ +public: + enum eStatus + { + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + } ; + + enum eKind + { + mkRequest, + mkResponse, + } ; + + cHTTPMessage(eKind a_Kind); + + // Force a virtual destructor in all descendants + virtual ~cHTTPMessage() {} + + /** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */ + void AddHeader(const AString & a_Key, const AString & a_Value); + + void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; } + void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; } + + const AString & GetContentType (void) const { return m_ContentType; } + size_t GetContentLength(void) const { return m_ContentLength; } + +protected: + typedef std::map cNameValueMap; + + eKind m_Kind; + + /** Map of headers, with their keys lowercased. */ + AStringMap m_Headers; + + /** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */ + AString m_ContentType; + + /** Length of the content that is to be received. + AString::npos when the object is created. + Parsed by AddHeader() or set directly by SetContentLength() */ + size_t m_ContentLength; +} ; + + + + + +class cHTTPResponse : + public cHTTPMessage +{ + typedef cHTTPMessage super; + +public: + cHTTPResponse(void); + + /** Appends the response to the specified datastream - response line and headers. + The body will be sent later directly through cConnection::Send() + */ + void AppendToData(AString & a_DataStream) const; +} ; + + + + diff --git a/src/HTTP/HTTPRequestParser.cpp b/src/HTTP/HTTPRequestParser.cpp new file mode 100644 index 000000000..9c60c6053 --- /dev/null +++ b/src/HTTP/HTTPRequestParser.cpp @@ -0,0 +1,192 @@ + +// HTTPRequestParser.cpp + +// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests + +#include "Globals.h" +#include "HTTPRequestParser.h" + + + + + +cHTTPRequestParser::cHTTPRequestParser(void) : + super(mkRequest), + m_EnvelopeParser(*this), + m_IsValid(true), + m_UserData(nullptr), + m_HasAuth(false), + m_AllowKeepAlive(false) +{ +} + + + + + +size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) +{ + if (!m_IsValid) + { + return AString::npos; + } + + if (m_Method.empty()) + { + // The first line hasn't been processed yet + size_t res = ParseRequestLine(a_Data, a_Size); + if ((res == AString::npos) || (res == a_Size)) + { + return res; + } + size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); + if (res2 == AString::npos) + { + m_IsValid = false; + return res2; + } + return res2 + res; + } + + if (m_EnvelopeParser.IsInHeaders()) + { + size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); + if (res == AString::npos) + { + m_IsValid = false; + } + return res; + } + return 0; +} + + + + + +AString cHTTPRequestParser::GetBareURL(void) const +{ + size_t idxQM = m_URL.find('?'); + if (idxQM != AString::npos) + { + return m_URL.substr(0, idxQM); + } + else + { + return m_URL; + } +} + + + + + +size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) +{ + m_IncomingHeaderData.append(a_Data, a_Size); + size_t IdxEnd = m_IncomingHeaderData.size(); + + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < IdxEnd) && + ( + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= IdxEnd) + { + m_IsValid = false; + return AString::npos; + } + + int NumSpaces = 0; + size_t MethodEnd = 0; + size_t URLEnd = 0; + for (size_t i = LineStart; i < IdxEnd; i++) + { + switch (m_IncomingHeaderData[i]) + { + case ' ': + { + switch (NumSpaces) + { + case 0: + { + MethodEnd = i; + break; + } + case 1: + { + URLEnd = i; + break; + } + default: + { + // Too many spaces in the request + m_IsValid = false; + return AString::npos; + } + } + NumSpaces += 1; + break; + } + case '\n': + { + if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) + { + // LF too early, without a CR, without two preceeding spaces or too soon after the second space + m_IsValid = false; + return AString::npos; + } + // Check that there's HTTP / version at the end + if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) + { + m_IsValid = false; + return AString::npos; + } + m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); + m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); + return i + 1; + } + } // switch (m_IncomingHeaderData[i]) + } // for i - m_IncomingHeaderData[] + + // CRLF hasn't been encountered yet, consider all data consumed + return a_Size; +} + + + + + +void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } + if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) + { + m_AllowKeepAlive = true; + } + AddHeader(a_Key, a_Value); +} + + + + diff --git a/src/HTTP/HTTPRequestParser.h b/src/HTTP/HTTPRequestParser.h new file mode 100644 index 000000000..f3d3add91 --- /dev/null +++ b/src/HTTP/HTTPRequestParser.h @@ -0,0 +1,109 @@ + +// HTTPRequestParser.h + +// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests + + + + +#pragma once + +#include "HTTPMessage.h" +#include "EnvelopeParser.h" + + + + + +class cHTTPRequestParser : + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks +{ + typedef cHTTPMessage super; + +public: + cHTTPRequestParser(void); + + /** Parses the request line and then headers from the received data. + Returns the number of bytes consumed or AString::npos number for error + */ + size_t ParseHeaders(const char * a_Data, size_t a_Size); + + /** Returns true if the request did contain a Content-Length header */ + bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); } + + /** Returns the method used in the request */ + const AString & GetMethod(void) const { return m_Method; } + + /** Returns the URL used in the request */ + const AString & GetURL(void) const { return m_URL; } + + /** Returns the URL used in the request, without any parameters */ + AString GetBareURL(void) const; + + /** Sets the UserData pointer that is stored within this request. + The request doesn't touch this data (doesn't delete it)! */ + void SetUserData(void * a_UserData) { m_UserData = a_UserData; } + + /** Retrieves the UserData pointer that has been stored within this request. */ + void * GetUserData(void) const { return m_UserData; } + + /** Returns true if more data is expected for the request headers */ + bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } + + /** Returns true if the request did present auth data that was understood by the parser */ + bool HasAuth(void) const { return m_HasAuth; } + + /** Returns the username that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /** Returns the password that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + + bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } + +protected: + /** Parser for the envelope data */ + cEnvelopeParser m_EnvelopeParser; + + /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ + bool m_IsValid; + + /** Bufferred incoming data, while parsing for the request line */ + AString m_IncomingHeaderData; + + /** Method of the request (GET / PUT / POST / ...) */ + AString m_Method; + + /** Full URL of the request */ + AString m_URL; + + /** Data that the HTTPServer callbacks are allowed to store. */ + void * m_UserData; + + /** Set to true if the request contains auth data that was understood by the parser */ + bool m_HasAuth; + + /** The username used for auth */ + AString m_AuthUsername; + + /** The password used for auth */ + AString m_AuthPassword; + + /** Set to true if the request indicated that it supports keepalives. + If false, the server will close the connection once the request is finished */ + bool m_AllowKeepAlive; + + + /** Parses the incoming data for the first line (RequestLine) + Returns the number of bytes consumed, or AString::npos for an error + */ + size_t ParseRequestLine(const char * a_Data, size_t a_Size); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; +} ; + + + + diff --git a/src/HTTP/HTTPResponseParser.cpp b/src/HTTP/HTTPResponseParser.cpp new file mode 100644 index 000000000..9411208e2 --- /dev/null +++ b/src/HTTP/HTTPResponseParser.cpp @@ -0,0 +1,183 @@ + +// HTTPResponseParser.cpp + +// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses + +#include "Globals.h" +#include "HTTPResponseParser.h" + + + + + +cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks): + Super(mkResponse), + m_Callbacks(a_Callbacks), + m_HasHadError(false), + m_IsInHeaders(true), + m_IsFinished(false), + m_EnvelopeParser(*this) +{ +} + + + + + +size_t cHTTPResponseParser::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 a_Size; + } + + // If still waiting for the status line, add to buffer and try parsing it: + if (m_StatusLine.empty()) + { + m_Buffer.append(a_Data, a_Size); + if (!ParseStatusLine()) + { + // All data used, but not a complete status line yet. + return 0; + } + if (m_HasHadError) + { + return AString::npos; + } + // Status line completed, feed the rest of the buffer into the envelope parser: + auto bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size()); + if (bytesConsumed == AString::npos) + { + m_HasHadError = true; + m_Callbacks.OnError("Failed to parse the envelope"); + return AString::npos; + } + m_Buffer.erase(0, bytesConsumed); + if (!m_Buffer.empty()) + { + // Headers finished and there's still data left in the buffer, process it as message body: + HeadersFinished(); + return ParseBody(m_Buffer.data(), m_Buffer.size()); + } + return 0; + } // if (m_StatusLine.empty()) + + // If still parsing headers, send them to the envelope parser: + if (m_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 (bytesConsumed < a_Size) + { + // Headers finished and there's still data left in the buffer, process it as message body: + HeadersFinished(); + return ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); + } + return 0; + } + + // Already parsing the body + return ParseBody(a_Data, a_Size); +} + + + + + +bool cHTTPResponseParser::ParseStatusLine(void) +{ + auto idxLineEnd = m_Buffer.find("\r\n"); + if (idxLineEnd == AString::npos) + { + // Not a complete line yet + return false; + } + m_StatusLine = m_Buffer.substr(0, idxLineEnd); + m_Buffer.erase(0, idxLineEnd + 2); + m_Callbacks.OnStatusLine(m_StatusLine); + return true; +} + + + + +size_t cHTTPResponseParser::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 + return AString::npos; + } + + // Parse the body using the transfer encoding parser: + return m_TransferEncodingParser->Parse(a_Data, a_Size); +} + + + + + +void cHTTPResponseParser::HeadersFinished(void) +{ + m_IsInHeaders = false; + m_Callbacks.OnHeadersFinished(); + + auto transferEncoding = m_Headers.find("transfer-encoding"); + if (transferEncoding == m_Headers.end()) + { + m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength); + } + else + { + m_TransferEncodingParser = cTransferEncodingParser::Create(*this, transferEncoding->second, m_ContentLength); + } +} + + + + + +void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + AddHeader(a_Key, a_Value); + m_Callbacks.OnHeaderLine(a_Key, a_Value); +} + + + + + +void cHTTPResponseParser::OnError(const AString & a_ErrorDescription) +{ + m_HasHadError = true; + m_Callbacks.OnError(a_ErrorDescription); +} + + + + + +void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size) +{ + m_Callbacks.OnBodyData(a_Data, a_Size); +} + + + + + +void cHTTPResponseParser::OnBodyFinished(void) +{ + m_IsFinished = true; + m_Callbacks.OnBodyFinished(); +} + + + + diff --git a/src/HTTP/HTTPResponseParser.h b/src/HTTP/HTTPResponseParser.h new file mode 100644 index 000000000..19652ccef --- /dev/null +++ b/src/HTTP/HTTPResponseParser.h @@ -0,0 +1,118 @@ + +// HTTPResponseParser.h + +// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses + + + + +#pragma once + +#include "HTTPMessage.h" +#include "TransferEncodingParser.h" + + + + + +class cHTTPResponseParser: + public cHTTPMessage, + protected cEnvelopeParser::cCallbacks, + protected cTransferEncodingParser::cCallbacks +{ + typedef cHTTPMessage Super; + +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called when the status line is fully parsed. */ + virtual void OnStatusLine(const AString & a_StatusLine) = 0; + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + cHTTPResponseParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes from the end of a_Data that is already not part of this response. + Returns AString::npos on an error. */ + size_t Parse(const char * a_Data, size_t a_Size); + + /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). + Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ + void Finish(void); + + /** Returns true if the entire response has been already parsed. */ + bool IsFinished(void) const { return m_IsFinished; } + + +protected: + + /** The callbacks used for reporting. */ + cCallbacks & m_Callbacks; + + /** Set to true if an error has been encountered by the parser. */ + bool m_HasHadError; + + /** True if the parser is still parsing the status or headers. */ + bool m_IsInHeaders; + + /** True if the response has been fully parsed. */ + bool m_IsFinished; + + /** The complete status line of the response. Empty if not parsed yet. */ + AString m_StatusLine; + + /** Buffer for the incoming data until the status line is parsed. */ + AString m_Buffer; + + /** Parser for the envelope data (headers) */ + cEnvelopeParser m_EnvelopeParser; + + /** The specific parser for the transfer encoding used by this response. */ + cTransferEncodingParserPtr m_TransferEncodingParser; + + + /** Parses the status line out of the m_Buffer. + Removes the status line from m_Buffer, if appropriate. + Returns true if the entire status line has been parsed. */ + bool ParseStatusLine(void); + + /** Parses the message body. + Processes transfer encoding and calls the callbacks for body data. + Returns the number of bytes from the end of a_Data that is already not part of this response. + Returns AString::npos on error. */ + size_t ParseBody(const char * a_Data, size_t a_Size); + + /** Called internally when the headers-parsing has just finished. */ + void HeadersFinished(void); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; + + // cTransferEncodingParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override; + virtual void OnBodyData(const void * a_Data, size_t a_Size) override; + virtual void OnBodyFinished(void) override; +}; + + + + diff --git a/src/HTTP/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp new file mode 100644 index 000000000..741f5f1c5 --- /dev/null +++ b/src/HTTP/HTTPServer.cpp @@ -0,0 +1,309 @@ + +// HTTPServer.cpp + +// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing + +#include "Globals.h" +#include "HTTPServer.h" +#include "HTTPRequestParser.h" +#include "HTTPServerConnection.h" +#include "HTTPFormParser.h" +#include "SslHTTPServerConnection.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +class cDebugCallbacks : + public cHTTPServer::cCallbacks, + protected cHTTPFormParser::cCallbacks +{ + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override + { + UNUSED(a_Connection); + + if (cHTTPFormParser::HasFormData(a_Request)) + { + a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); + } + } + + + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override + { + UNUSED(a_Connection); + + cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); + if (FormParser != nullptr) + { + FormParser->Parse(a_Data, a_Size); + } + } + + + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override + { + cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); + if (FormParser != nullptr) + { + if (FormParser->Finish()) + { + cHTTPResponse Resp; + Resp.SetContentType("text/html"); + a_Connection.Send(Resp); + a_Connection.Send("\r\n"); + for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) + { + a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); + } // for itr - FormParser[] + a_Connection.Send("
NameValue
%s
%s
"); + return; + } + + // Parsing failed: + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Form parsing failed"); + return; + } + + // Test the auth failure and success: + if (a_Request.GetURL() == "/auth") + { + if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b")) + { + a_Connection.SendNeedAuth("Cuberite WebAdmin"); + return; + } + } + + cHTTPResponse Resp; + Resp.SetContentType("text/plain"); + a_Connection.Send(Resp); + a_Connection.Send("Hello, world"); + } + + + virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override + { + // TODO + } + + + virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override + { + // TODO + } + + + virtual void OnFileEnd(cHTTPFormParser & a_Parser) override + { + // TODO + } + +}; + +static cDebugCallbacks g_DebugCallbacks; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPServerListenCallbacks: + +class cHTTPServerListenCallbacks: + public cNetwork::cListenCallbacks +{ +public: + cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port): + m_HTTPServer(a_HTTPServer), + m_Port(a_Port) + { + } + +protected: + /** The HTTP server instance that we're attached to. */ + cHTTPServer & m_HTTPServer; + + /** The port for which this instance is responsible. */ + UInt16 m_Port; + + // cNetwork::cListenCallbacks overrides: + virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override + { + return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort); + } + virtual void OnAccepted(cTCPLink & a_Link) override {} + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override + { + LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str()); + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPServer: + +cHTTPServer::cHTTPServer(void) : + m_Callbacks(nullptr) +{ +} + + + + + +cHTTPServer::~cHTTPServer() +{ + Stop(); +} + + + + + +bool cHTTPServer::Initialize(void) +{ + // Read the HTTPS cert + key: + AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt"); + AString KeyFile = cFile::ReadWholeFile("webadmin/httpskey.pem"); + if (!CertFile.empty() && !KeyFile.empty()) + { + m_Cert.reset(new cX509Cert); + int res = m_Cert->Parse(CertFile.data(), CertFile.size()); + if (res == 0) + { + m_CertPrivKey.reset(new cCryptoKey); + int res2 = m_CertPrivKey->ParsePrivate(KeyFile.data(), KeyFile.size(), ""); + if (res2 != 0) + { + // Reading the private key failed, reset the cert: + LOGWARNING("WebServer: Cannot read HTTPS certificate private key: -0x%x", -res2); + m_Cert.reset(); + } + } + else + { + LOGWARNING("WebServer: Cannot read HTTPS certificate: -0x%x", -res); + } + } + + // Notify the admin about the HTTPS / HTTP status + if (m_Cert.get() == nullptr) + { + LOGWARNING("WebServer: The server is running in unsecured HTTP mode."); + LOGINFO("Put a valid HTTPS certificate in file 'webadmin/httpscert.crt' and its corresponding private key to 'webadmin/httpskey.pem' (without any password) to enable HTTPS support"); + } + else + { + LOGINFO("WebServer: The server is running in secure HTTPS mode."); + } + return true; +} + + + + + +bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports) +{ + m_Callbacks = &a_Callbacks; + + // Open up requested ports: + for (auto port : a_Ports) + { + UInt16 PortNum; + if (!StringToInteger(port, PortNum)) + { + LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str()); + continue; + } + auto Handle = cNetwork::Listen(PortNum, std::make_shared(*this, PortNum)); + if (Handle->IsListening()) + { + m_ServerHandles.push_back(Handle); + } + } // for port - a_Ports[] + + // Report success if at least one port opened successfully: + return !m_ServerHandles.empty(); +} + + + + + +void cHTTPServer::Stop(void) +{ + for (auto handle : m_ServerHandles) + { + handle->Close(); + } + m_ServerHandles.clear(); +} + + + + + +cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) +{ + UNUSED(a_RemoteIPAddress); + UNUSED(a_RemotePort); + + if (m_Cert.get() != nullptr) + { + return std::make_shared(*this, m_Cert, m_CertPrivKey); + } + else + { + return std::make_shared(*this); + } +} + + + + + +void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +{ + m_Callbacks->OnRequestBegun(a_Connection, a_Request); +} + + + + + +void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) +{ + m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); +} + + + + + +void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +{ + m_Callbacks->OnRequestFinished(a_Connection, a_Request); + a_Connection.AwaitNextRequest(); +} + + + + diff --git a/src/HTTP/HTTPServer.h b/src/HTTP/HTTPServer.h new file mode 100644 index 000000000..15202a50e --- /dev/null +++ b/src/HTTP/HTTPServer.h @@ -0,0 +1,101 @@ + +// HTTPServer.h + +// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing + + + + + +#pragma once + +#include "../OSSupport/Network.h" +#include "../IniFile.h" +#include "PolarSSL++/RsaPrivateKey.h" +#include "PolarSSL++/CryptoKey.h" +#include "PolarSSL++/X509Cert.h" + + + + + +// fwd: +class cHTTPMessage; +class cHTTPRequestParser; +class cHTTPResponse; +class cHTTPServerConnection; + + + + + + +class cHTTPServer +{ +public: + class cCallbacks + { + public: + virtual ~cCallbacks() {} + + /** Called when a new request arrives over a connection and all its headers have been parsed. + The request body needn't have arrived yet. */ + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; + + /** Called when another part of request body has arrived. + May be called multiple times for a single request. */ + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0; + + /** Called when the request body has been fully received in previous calls to OnRequestBody() */ + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; + } ; + + cHTTPServer(void); + virtual ~cHTTPServer(); + + /** Initializes the server - reads the cert files etc. */ + bool Initialize(void); + + /** Starts the server and assigns the callbacks to use for incoming requests */ + bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports); + + /** Stops the server, drops all current connections */ + void Stop(void); + +protected: + friend class cHTTPServerConnection; + friend class cSslHTTPServerConnection; + friend class cHTTPServerListenCallbacks; + + /** The cNetwork API handle for the listening socket. */ + cServerHandlePtrs m_ServerHandles; + + /** The callbacks to call for various events */ + cCallbacks * m_Callbacks; + + /** The server certificate to use for the SSL connections */ + cX509CertPtr m_Cert; + + /** The private key for m_Cert. */ + cCryptoKeyPtr m_CertPrivKey; + + + /** Called by cHTTPServerListenCallbacks when there's a new incoming connection. + Returns the connection instance to be used as the cTCPLink callbacks. */ + cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); + + /** Called by cHTTPServerConnection when it finishes parsing the request header */ + void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + + /** Called by cHTTPConenction when it receives more data for the request body. + May be called multiple times for a single request. */ + void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size); + + /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ + void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); +} ; + + + + + diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp new file mode 100644 index 000000000..0ee9f3ce7 --- /dev/null +++ b/src/HTTP/HTTPServerConnection.cpp @@ -0,0 +1,278 @@ + +// HTTPConnection.cpp + +// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server. + +#include "Globals.h" +#include "HTTPServerConnection.h" +#include "HTTPRequestParser.h" +#include "HTTPServer.h" + + + + + +cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : + m_HTTPServer(a_HTTPServer), + m_State(wcsRecvHeaders), + m_CurrentRequest(nullptr), + m_CurrentRequestBodyRemaining(0) +{ + // LOGD("HTTP: New connection at %p", this); +} + + + + + +cHTTPServerConnection::~cHTTPServerConnection() +{ + // LOGD("HTTP: Connection deleting: %p", this); + delete m_CurrentRequest; + m_CurrentRequest = nullptr; +} + + + + + +void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) +{ + SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); + SendData(Printf("Content-Length: %u\r\n\r\n", static_cast(a_Response.size()))); + SendData(a_Response.data(), a_Response.size()); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) +{ + SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) +{ + ASSERT(m_State == wcsRecvIdle); + AString toSend; + a_Response.AppendToData(toSend); + m_State = wcsSendingResp; + SendData(toSend); +} + + + + + +void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) +{ + ASSERT(m_State == wcsSendingResp); + // We're sending in Chunked transfer encoding + SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); + SendData(a_Data, a_Size); + SendData("\r\n"); +} + + + + + +void cHTTPServerConnection::FinishResponse(void) +{ + ASSERT(m_State == wcsSendingResp); + SendData("0\r\n\r\n"); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPServerConnection::AwaitNextRequest(void) +{ + switch (m_State) + { + case wcsRecvHeaders: + { + // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth()) + break; + } + + case wcsRecvIdle: + { + // The client is waiting for a response, send an "Internal server error": + SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); + m_State = wcsRecvHeaders; + break; + } + + case wcsSendingResp: + { + // The response headers have been sent, we need to terminate the response body: + SendData("0\r\n\r\n"); + m_State = wcsRecvHeaders; + break; + } + + default: + { + ASSERT(!"Unhandled state recovery"); + break; + } + } +} + + + + + +void cHTTPServerConnection::Terminate(void) +{ + if (m_CurrentRequest != nullptr) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_Link.reset(); +} + + + + + +void cHTTPServerConnection::OnLinkCreated(cTCPLinkPtr a_Link) +{ + ASSERT(m_Link == nullptr); + m_Link = a_Link; +} + + + + + +void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) +{ + ASSERT(m_Link != nullptr); + + switch (m_State) + { + case wcsRecvHeaders: + { + if (m_CurrentRequest == nullptr) + { + m_CurrentRequest = new cHTTPRequestParser; + } + + size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); + if (BytesConsumed == AString::npos) + { + delete m_CurrentRequest; + m_CurrentRequest = nullptr; + m_State = wcsInvalid; + m_Link->Close(); + m_Link.reset(); + return; + } + if (m_CurrentRequest->IsInHeaders()) + { + // The request headers are not yet complete + return; + } + + // The request has finished parsing its headers successfully, notify of it: + m_State = wcsRecvBody; + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); + m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); + if (m_CurrentRequestBodyRemaining == AString::npos) + { + // The body length was not specified in the request, assume zero + m_CurrentRequestBodyRemaining = 0; + } + + // Process the rest of the incoming data into the request body: + if (a_Size > BytesConsumed) + { + cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); + return; + } + else + { + cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away + return; + } + } + + case wcsRecvBody: + { + ASSERT(m_CurrentRequest != nullptr); + if (m_CurrentRequestBodyRemaining > 0) + { + size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast(a_Size)); + m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); + m_CurrentRequestBodyRemaining -= BytesToConsume; + } + if (m_CurrentRequestBodyRemaining == 0) + { + m_State = wcsRecvIdle; + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + if (!m_CurrentRequest->DoesAllowKeepAlive()) + { + m_State = wcsInvalid; + m_Link->Close(); + m_Link.reset(); + return; + } + delete m_CurrentRequest; + m_CurrentRequest = nullptr; + } + break; + } + + default: + { + // TODO: Should we be receiving data in this state? + break; + } + } +} + + + + + +void cHTTPServerConnection::OnRemoteClosed(void) +{ + if (m_CurrentRequest != nullptr) + { + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + } + m_Link.reset(); +} + + + + + + +void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) +{ + OnRemoteClosed(); +} + + + + +void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) +{ + m_Link->Send(a_Data, a_Size); +} + + + + diff --git a/src/HTTP/HTTPServerConnection.h b/src/HTTP/HTTPServerConnection.h new file mode 100644 index 000000000..af2abb73f --- /dev/null +++ b/src/HTTP/HTTPServerConnection.h @@ -0,0 +1,124 @@ + +// HTTPConnection.h + +// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. + + + + + +#pragma once + +#include "../OSSupport/Network.h" + + + + + +// fwd: +class cHTTPServer; +class cHTTPResponse; +class cHTTPRequestParser; + + + + + +class cHTTPServerConnection : + public cTCPLink::cCallbacks +{ +public: + + enum eState + { + wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr) + wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) + wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr) + wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr) + wcsInvalid, ///< The request was malformed, the connection is closing + } ; + + cHTTPServerConnection(cHTTPServer & a_HTTPServer); + virtual ~cHTTPServerConnection(); + + /** Sends HTTP status code together with a_Reason (used for HTTP errors). + Sends the a_Reason as the body as well, so that browsers display it. */ + void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + + /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ + void SendNeedAuth(const AString & a_Realm); + + /** Sends the headers contained in a_Response */ + void Send(const cHTTPResponse & a_Response); + + /** Sends the data as the response (may be called multiple times) */ + void Send(const void * a_Data, size_t a_Size); + + /** Sends the data as the response (may be called multiple times) */ + void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } + + /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */ + void FinishResponse(void); + + /** Resets the internal connection state for a new request. + Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */ + void AwaitNextRequest(void); + + /** Terminates the connection; finishes any request being currently processed */ + void Terminate(void); + +protected: + typedef std::map cNameValueMap; + + /** The parent webserver that is to be notified of events on this connection */ + cHTTPServer & m_HTTPServer; + + /** All the incoming data until the entire request header is parsed */ + AString m_IncomingHeaderData; + + /** Status in which the request currently is */ + eState m_State; + + /** The request being currently received + Valid only between having parsed the headers and finishing receiving the body. */ + cHTTPRequestParser * m_CurrentRequest; + + /** Number of bytes that remain to read for the complete body of the message to be received. + Valid only in wcsRecvBody */ + size_t m_CurrentRequestBodyRemaining; + + /** The network link attached to this connection. */ + cTCPLinkPtr m_Link; + + + // cTCPLink::cCallbacks overrides: + /** The link instance has been created, remember it. */ + virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; + + /** Data is received from the client. */ + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; + + /** The socket has been closed for any reason. */ + virtual void OnRemoteClosed(void) override; + + /** An error has occurred on the socket. */ + virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + + // Overridable: + /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ + virtual void SendData(const void * a_Data, size_t a_Size); + + /** Sends the raw data over the link. + Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */ + void SendData(const AString & a_Data) + { + SendData(a_Data.data(), a_Data.size()); + } +} ; + +typedef std::vector cHTTPServerConnections; + + + + + diff --git a/src/HTTP/MultipartParser.cpp b/src/HTTP/MultipartParser.cpp new file mode 100644 index 000000000..09f4fd02a --- /dev/null +++ b/src/HTTP/MultipartParser.cpp @@ -0,0 +1,254 @@ + +// MultipartParser.cpp + +// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts + +#include "Globals.h" +#include "MultipartParser.h" +#include "NameValueParser.h" + + + + + +// Disable MSVC warnings: +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4355) // 'this' : used in base member initializer list +#endif + + + + + +//////////////////////////////////////////////////////////////////////////////// +// self-test: + +#if 0 + +class cMultipartParserTest : + public cMultipartParser::cCallbacks +{ +public: + cMultipartParserTest(void) + { + cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this); + const char Data[] = +"ThisIsIgnoredPrologue\r\n\ +--MyBoundaryString\r\n\ +\r\n\ +Body with confusing strings\r\n\ +--NotABoundary\r\n\ +--MyBoundaryStringWithPostfix\r\n\ +--\r\n\ +--MyBoundaryString\r\n\ +content-disposition: inline\r\n\ +\r\n\ +This is body\r\n\ +--MyBoundaryString\r\n\ +\r\n\ +Headerless body with trailing CRLF\r\n\ +\r\n\ +--MyBoundaryString--\r\n\ +ThisIsIgnoredEpilogue"; + printf("Multipart parsing test commencing.\n"); + Parser.Parse(Data, sizeof(Data) - 1); + // DEBUG: Check if the onscreen output corresponds with the data above + printf("Multipart parsing test finished\n"); + } + + virtual void OnPartStart(void) override + { + printf("Starting a new part\n"); + } + + + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override + { + printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str()); + } + + + virtual void OnPartData(const char * a_Data, int a_Size) override + { + printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data); + } + + + virtual void OnPartEnd(void) override + { + printf("Part end\n"); + } +} g_Test; + +#endif + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cMultipartParser: + + +cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) : + m_Callbacks(a_Callbacks), + m_IsValid(true), + m_EnvelopeParser(*this), + m_HasHadData(false) +{ + // Check that the content type is multipart: + AString ContentType(a_ContentType); + if (strncmp(ContentType.c_str(), "multipart/", 10) != 0) + { + m_IsValid = false; + return; + } + size_t idxSC = ContentType.find(';', 10); + if (idxSC == AString::npos) + { + m_IsValid = false; + return; + } + + // Find the multipart boundary: + ContentType.erase(0, idxSC + 1); + cNameValueParser CTParser(ContentType.c_str(), ContentType.size()); + CTParser.Finish(); + if (!CTParser.IsValid()) + { + m_IsValid = false; + return; + } + m_Boundary = CTParser["boundary"]; + m_IsValid = !m_Boundary.empty(); + if (!m_IsValid) + { + return; + } + + // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body + m_EnvelopeParser.SetIsInHeaders(false); + + // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught + m_IncomingData.assign("\r\n"); + + /* + m_Boundary = AString("\r\n--") + m_Boundary + m_BoundaryEnd = m_Boundary + "--\r\n"; + m_Boundary = m_Boundary + "\r\n"; + */ +} + + + + + +void cMultipartParser::Parse(const char * a_Data, size_t a_Size) +{ + // Skip parsing if invalid + if (!m_IsValid) + { + return; + } + + // Append to buffer, then parse it: + m_IncomingData.append(a_Data, a_Size); + for (;;) + { + if (m_EnvelopeParser.IsInHeaders()) + { + size_t BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size()); + if (BytesConsumed == AString::npos) + { + m_IsValid = false; + return; + } + if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders()) + { + // All the incoming data has been consumed and still waiting for more + return; + } + m_IncomingData.erase(0, BytesConsumed); + } + + // Search for boundary / boundary end: + size_t idxBoundary = m_IncomingData.find("\r\n--"); + if (idxBoundary == AString::npos) + { + // Boundary string start not present, present as much data to the part callback as possible + if (m_IncomingData.size() > m_Boundary.size() + 8) + { + size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; + m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); + m_IncomingData.erase(0, BytesToReport); + } + return; + } + if (idxBoundary > 0) + { + m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary); + m_IncomingData.erase(0, idxBoundary); + } + idxBoundary = 4; + size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary); + if (LineEnd == AString::npos) + { + // Not a complete line yet, present as much data to the part callback as possible + if (m_IncomingData.size() > m_Boundary.size() + 8) + { + size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; + m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); + m_IncomingData.erase(0, BytesToReport); + } + return; + } + if ( + (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary + (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end + ) + { + // Got a line, but it's not a boundary, report it as data: + m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd); + m_IncomingData.erase(0, LineEnd); + continue; + } + + if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0) + { + // Boundary or BoundaryEnd found: + m_Callbacks.OnPartEnd(); + size_t idxSlash = idxBoundary + m_Boundary.size(); + if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-')) + { + // This was the last part + m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4); + m_IncomingData.clear(); + return; + } + m_Callbacks.OnPartStart(); + m_IncomingData.erase(0, LineEnd + 2); + + // Keep parsing for the headers that may have come with this data: + m_EnvelopeParser.Reset(); + continue; + } + + // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines + m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd); + m_IncomingData.erase(0, LineEnd); + } // while (true) +} + + + + + +void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + m_Callbacks.OnPartHeader(a_Key, a_Value); +} + + + + diff --git a/src/HTTP/MultipartParser.h b/src/HTTP/MultipartParser.h new file mode 100644 index 000000000..4f20b2bed --- /dev/null +++ b/src/HTTP/MultipartParser.h @@ -0,0 +1,79 @@ + +// MultipartParser.h + +// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts + + + + + +#pragma once + +#include "EnvelopeParser.h" + + + + + +class cMultipartParser : + protected cEnvelopeParser::cCallbacks +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when a new part starts */ + virtual void OnPartStart(void) = 0; + + /** Called when a complete header line is received for a part */ + virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0; + + /** Called when body for a part is received */ + virtual void OnPartData(const char * a_Data, size_t a_Size) = 0; + + /** Called when the current part ends */ + virtual void OnPartEnd(void) = 0; + } ; + + /** Creates the parser, expects to find the boundary in a_ContentType */ + cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks); + + /** Parses more incoming data */ + void Parse(const char * a_Data, size_t a_Size); + +protected: + /** The callbacks to call for various parsing events */ + cCallbacks & m_Callbacks; + + /** True if the data parsed so far is valid; if false, further parsing is skipped */ + bool m_IsValid; + + /** Parser for each part's envelope */ + cEnvelopeParser m_EnvelopeParser; + + /** Buffer for the incoming data until it is parsed */ + AString m_IncomingData; + + /** The boundary, excluding both the initial "--" and the terminating CRLF */ + AString m_Boundary; + + /** Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting. */ + bool m_HasHadData; + + + /** Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size */ + void ParseLine(const char * a_Data, size_t a_Size); + + /** Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size */ + void ParseHeaderLine(const char * a_Data, size_t a_Size); + + // cEnvelopeParser overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; +} ; + + + + diff --git a/src/HTTP/NameValueParser.cpp b/src/HTTP/NameValueParser.cpp new file mode 100644 index 000000000..f759c4d21 --- /dev/null +++ b/src/HTTP/NameValueParser.cpp @@ -0,0 +1,413 @@ + +// NameValueParser.cpp + +// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap + +#include "Globals.h" +#include "NameValueParser.h" + + + + + + +// DEBUG: Self-test + +#if 0 + +class cNameValueParserTest +{ +public: + cNameValueParserTest(void) + { + const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\""; + + // Now try parsing char-by-char, to debug transitions across datachunk boundaries: + cNameValueParser Parser2; + for (size_t i = 0; i < sizeof(Data) - 1; i++) + { + Parser2.Parse(Data + i, 1); + } + Parser2.Finish(); + + // Parse as a single chunk of data: + cNameValueParser Parser(Data, sizeof(Data) - 1); + + // Use the debugger to inspect the Parser variable + + // Check that the two parsers have the same content: + for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) + { + ASSERT(Parser2[itr->first] == itr->second); + } // for itr - Parser[] + + // Try parsing in 2-char chunks: + cNameValueParser Parser3; + for (int i = 0; i < sizeof(Data) - 2; i += 2) + { + Parser3.Parse(Data + i, 2); + } + if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char + { + Parser3.Parse(Data + sizeof(Data) - 2, 1); + } + Parser3.Finish(); + + // Check that the third parser has the same content: + for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) + { + ASSERT(Parser3[itr->first] == itr->second); + } // for itr - Parser[] + + printf("cNameValueParserTest done"); + } +} g_Test; + +#endif + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cNameValueParser: + +cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) : + m_State(psKeySpace), + m_AllowsKeyOnly(a_AllowsKeyOnly) +{ +} + + + + + +cNameValueParser::cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly) : + m_State(psKeySpace), + m_AllowsKeyOnly(a_AllowsKeyOnly) +{ + Parse(a_Data, a_Size); +} + + + + + +void cNameValueParser::Parse(const char * a_Data, size_t a_Size) +{ + ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong! + + size_t Last = 0; + for (size_t i = 0; i < a_Size;) + { + switch (m_State) + { + case psInvalid: + case psFinished: + { + return; + } + + case psKeySpace: + { + // Skip whitespace until a non-whitespace is found, then start the key: + while ((i < a_Size) && (a_Data[i] <= ' ')) + { + i++; + } + if ((i < a_Size) && (a_Data[i] > ' ')) + { + m_State = psKey; + Last = i; + } + break; + } + + case psKey: + { + // Read the key until whitespace or an equal sign: + while (i < a_Size) + { + if (a_Data[i] == '=') + { + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + m_State = psEqual; + break; + } + else if (a_Data[i] <= ' ') + { + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + m_State = psEqualSpace; + break; + } + else if (a_Data[i] == ';') + { + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + m_CurrentKey.append(a_Data + Last, i - Last); + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if ((a_Data[i] == '\"') || (a_Data[i] == '\'')) + { + m_State = psInvalid; + return; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + // Still the key, ran out of data to parse, store the part of the key parsed so far: + m_CurrentKey.append(a_Data + Last, a_Size - Last); + return; + } + break; + } + + case psEqualSpace: + { + // The space before the expected equal sign; the current key is already assigned + while (i < a_Size) + { + if (a_Data[i] == '=') + { + m_State = psEqual; + i++; + Last = i; + break; + } + else if (a_Data[i] == ';') + { + // Key-only + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if (a_Data[i] > ' ') + { + m_State = psInvalid; + return; + } + i++; + } // while (i < a_Size) + break; + } // case psEqualSpace + + case psEqual: + { + // just parsed the equal-sign + while (i < a_Size) + { + if (a_Data[i] == ';') + { + if (!m_AllowsKeyOnly) + { + m_State = psInvalid; + return; + } + i++; + Last = i; + (*this)[m_CurrentKey] = ""; + m_CurrentKey.clear(); + m_State = psKeySpace; + break; + } + else if (a_Data[i] == '\"') + { + i++; + Last = i; + m_State = psValueInDQuotes; + break; + } + else if (a_Data[i] == '\'') + { + i++; + Last = i; + m_State = psValueInSQuotes; + break; + } + else + { + m_CurrentValue.push_back(a_Data[i]); + i++; + Last = i; + m_State = psValueRaw; + break; + } + } // while (i < a_Size) + break; + } // case psEqual + + case psValueInDQuotes: + { + while (i < a_Size) + { + if (a_Data[i] == '\"') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psAfterValue; + i++; + Last = i; + break; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueInDQuotes + + case psValueInSQuotes: + { + while (i < a_Size) + { + if (a_Data[i] == '\'') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psAfterValue; + i++; + Last = i; + break; + } + i++; + } // while (i < a_Size) + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueInSQuotes + + case psValueRaw: + { + while (i < a_Size) + { + if (a_Data[i] == ';') + { + m_CurrentValue.append(a_Data + Last, i - Last); + (*this)[m_CurrentKey] = m_CurrentValue; + m_CurrentKey.clear(); + m_CurrentValue.clear(); + m_State = psKeySpace; + i++; + Last = i; + break; + } + i++; + } + if (i == a_Size) + { + m_CurrentValue.append(a_Data + Last, a_Size - Last); + } + break; + } // case psValueRaw + + case psAfterValue: + { + // Between the closing DQuote or SQuote and the terminating semicolon + while (i < a_Size) + { + if (a_Data[i] == ';') + { + m_State = psKeySpace; + i++; + Last = i; + break; + } + else if (a_Data[i] < ' ') + { + i++; + continue; + } + m_State = psInvalid; + return; + } // while (i < a_Size) + break; + } + } // switch (m_State) + } // for i - a_Data[] +} + + + + + +bool cNameValueParser::Finish(void) +{ + switch (m_State) + { + case psInvalid: + { + return false; + } + case psFinished: + { + return true; + } + case psKey: + case psEqualSpace: + case psEqual: + { + if ((m_AllowsKeyOnly) && !m_CurrentKey.empty()) + { + (*this)[m_CurrentKey] = ""; + m_State = psFinished; + return true; + } + m_State = psInvalid; + return false; + } + case psValueRaw: + { + (*this)[m_CurrentKey] = m_CurrentValue; + m_State = psFinished; + return true; + } + case psValueInDQuotes: + case psValueInSQuotes: + { + // Missing the terminating quotes, this is an error + m_State = psInvalid; + return false; + } + case psKeySpace: + case psAfterValue: + { + m_State = psFinished; + return true; + } + } + ASSERT(!"Unhandled parser state!"); + return false; +} + + + + diff --git a/src/HTTP/NameValueParser.h b/src/HTTP/NameValueParser.h new file mode 100644 index 000000000..e205079db --- /dev/null +++ b/src/HTTP/NameValueParser.h @@ -0,0 +1,70 @@ + +// NameValueParser.h + +// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap + + + + + +#pragma once + + + + + +class cNameValueParser : + public std::map +{ +public: + /** Creates an empty parser */ + cNameValueParser(bool a_AllowsKeyOnly = true); + + /** Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later */ + cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly = true); + + /** Parses the data given */ + void Parse(const char * a_Data, size_t a_Size); + + /** Notifies the parser that no more data will be coming. Returns true if the parser state is valid */ + bool Finish(void); + + /** Returns true if the data parsed so far was valid */ + bool IsValid(void) const { return (m_State != psInvalid); } + + /** Returns true if the parser expects no more data */ + bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); } + +protected: + enum eState + { + psKeySpace, ///< Parsing the space in front of the next key + psKey, ///< Currently adding more chars to the key in m_CurrentKey + psEqualSpace, ///< Space after m_CurrentKey + psEqual, ///< Just parsed the = sign after a name + psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign + psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign + psValueRaw, ///< Just parsed a raw value without a quote + psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end + psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped + psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data + } ; + + /** The current state of the parser */ + eState m_State; + + /** If true, the parser will accept keys without an equal sign and the value */ + bool m_AllowsKeyOnly; + + /** Buffer for the current Key */ + AString m_CurrentKey; + + /** Buffer for the current Value; */ + AString m_CurrentValue; + + +} ; + + + + diff --git a/src/HTTP/SslHTTPServerConnection.cpp b/src/HTTP/SslHTTPServerConnection.cpp new file mode 100644 index 000000000..547e6de3a --- /dev/null +++ b/src/HTTP/SslHTTPServerConnection.cpp @@ -0,0 +1,115 @@ + +// SslHTTPConnection.cpp + +// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link + +#include "Globals.h" +#include "SslHTTPServerConnection.h" +#include "HTTPServer.h" + + + + + +cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : + super(a_HTTPServer), + m_Ssl(64000), + m_Cert(a_Cert), + m_PrivateKey(a_PrivateKey) +{ + m_Ssl.Initialize(false); + m_Ssl.SetOwnCert(a_Cert, a_PrivateKey); +} + + + + + +cSslHTTPServerConnection::~cSslHTTPServerConnection() +{ + m_Ssl.NotifyClose(); +} + + + + + +void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) +{ + // Process the received data: + const char * Data = a_Data; + size_t Size = a_Size; + for (;;) + { + // Try to write as many bytes into Ssl's "incoming" buffer as possible: + size_t BytesWritten = 0; + if (Size > 0) + { + BytesWritten = m_Ssl.WriteIncoming(Data, Size); + Data += BytesWritten; + Size -= BytesWritten; + } + + // Try to read as many bytes from SSL's decryption as possible: + char Buffer[32000]; + int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); + if (NumRead > 0) + { + super::OnReceivedData(Buffer, static_cast(NumRead)); + // The link may have closed while processing the data, bail out: + return; + } + else if (NumRead == POLARSSL_ERR_NET_WANT_READ) + { + // SSL requires us to send data to peer first, do so by "sending" empty data: + SendData(nullptr, 0); + } + + // If both failed, bail out: + if ((BytesWritten == 0) && (NumRead <= 0)) + { + return; + } + } +} + + + + + +void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) +{ + const char * OutgoingData = reinterpret_cast(a_Data); + size_t pos = 0; + for (;;) + { + // Write as many bytes from our buffer to SSL's encryption as possible: + int NumWritten = 0; + if (pos < a_Size) + { + NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos); + if (NumWritten > 0) + { + pos += static_cast(NumWritten); + } + } + + // Read as many bytes from SSL's "outgoing" buffer as possible: + char Buffer[32000]; + size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); + if (NumBytes > 0) + { + m_Link->Send(Buffer, NumBytes); + } + + // If both failed, bail out: + if ((NumWritten <= 0) && (NumBytes == 0)) + { + return; + } + } +} + + + + diff --git a/src/HTTP/SslHTTPServerConnection.h b/src/HTTP/SslHTTPServerConnection.h new file mode 100644 index 000000000..6032a2bd0 --- /dev/null +++ b/src/HTTP/SslHTTPServerConnection.h @@ -0,0 +1,47 @@ + +// SslHTTPServerConnection.h + +// Declared the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link + + + + + +#pragma once + +#include "HTTPServerConnection.h" +#include "PolarSSL++/BufferedSslContext.h" + + + + + +class cSslHTTPServerConnection : + public cHTTPServerConnection +{ + typedef cHTTPServerConnection super; + +public: + /** Creates a new connection on the specified server. + Sends the specified cert as the server certificate, uses the private key for decryption. */ + cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); + + ~cSslHTTPServerConnection(); + +protected: + cBufferedSslContext m_Ssl; + + /** The certificate to send to the client */ + cX509CertPtr m_Cert; + + /** The private key used for the certificate */ + cCryptoKeyPtr m_PrivateKey; + + // cHTTPConnection overrides: + virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client + virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client +} ; + + + + diff --git a/src/HTTP/TransferEncodingParser.cpp b/src/HTTP/TransferEncodingParser.cpp new file mode 100644 index 000000000..d95b3d08e --- /dev/null +++ b/src/HTTP/TransferEncodingParser.cpp @@ -0,0 +1,393 @@ + +// TransferEncodingParser.cpp + +// Implements the cTransferEncodingParser class and its descendants representing the parser for the various transfer encodings (chunked etc.) + +#include "Globals.h" +#include "TransferEncodingParser.h" +#include "EnvelopeParser.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cChunkedTEParser: + +class cChunkedTEParser: + public cTransferEncodingParser, + public cEnvelopeParser::cCallbacks +{ + typedef cTransferEncodingParser Super; + +public: + cChunkedTEParser(Super::cCallbacks & a_Callbacks): + Super(a_Callbacks), + m_State(psChunkLength), + m_ChunkDataLengthLeft(0), + m_TrailerParser(*this) + { + } + + +protected: + enum eState + { + psChunkLength, ///< Parsing the chunk length hex number + psChunkLengthTrailer, ///< Any trailer (chunk extension) specified after the chunk length + psChunkLengthLF, ///< The LF character after the CR character terminating the chunk length + psChunkData, ///< Relaying chunk data + psChunkDataCR, ///< Skipping the extra CR character after chunk data + psChunkDataLF, ///< Skipping the extra LF character after chunk data + psTrailer, ///< Received an empty chunk, parsing the trailer (through the envelope parser) + psFinished, ///< The parser has finished parsing, either successfully or with an error + }; + + /** The current state of the parser (parsing chunk length / chunk data). */ + eState m_State; + + /** Number of bytes that still belong to the chunk currently being parsed. + When in psChunkLength, the value is the currently parsed length digits. */ + size_t m_ChunkDataLengthLeft; + + cEnvelopeParser m_TrailerParser; + + + /** Calls the OnError callback and sets parser state to finished. */ + void Error(const AString & a_ErrorMsg) + { + m_State = psFinished; + m_Callbacks.OnError(a_ErrorMsg); + } + + + /** Parses the incoming data, the current state is psChunkLength. + Stops parsing when either the chunk length has been read, or there is no more data in the input. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseChunkLength(const char * a_Data, size_t a_Size) + { + // Expected input: [;] + // Only the hexnumber is parsed into m_ChunkDataLengthLeft, the rest is postponed into psChunkLengthTrailer or psChunkLengthLF + for (size_t i = 0; i < a_Size; i++) + { + switch (a_Data[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast(a_Data[i] - '0'); + break; + } + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + { + m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast(a_Data[i] - 'a' + 10); + break; + } + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + { + m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast(a_Data[i] - 'A' + 10); + break; + } + case '\r': + { + m_State = psChunkLengthLF; + return i + 1; + } + case ';': + { + m_State = psChunkLengthTrailer; + return i + 1; + } + default: + { + Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i])); + return AString::npos; + } + } // switch (a_Data[i]) + } // for i - a_Data[] + return a_Size; + } + + + /** Parses the incoming data, the current state is psChunkLengthTrailer. + Stops parsing when either the chunk length trailer has been read, or there is no more data in the input. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseChunkLengthTrailer(const char * a_Data, size_t a_Size) + { + // Expected input: + // The LF itself is not parsed, it is instead postponed into psChunkLengthLF + for (size_t i = 0; i < a_Size; i++) + { + switch (a_Data[i]) + { + case '\r': + { + m_State = psChunkLengthLF; + return i; + } + default: + { + if (a_Data[i] < 32) + { + // Only printable characters are allowed in the trailer + Error(Printf("Invalid character in chunk length line: 0x%x", a_Data[i])); + return AString::npos; + } + } + } // switch (a_Data[i]) + } // for i - a_Data[] + return a_Size; + } + + + /** Parses the incoming data, the current state is psChunkLengthLF. + Only the LF character is expected, if found, moves to psChunkData, otherwise issues an error. + If the chunk length that just finished reading is equal to 0, signals the end of stream (via psTrailer). + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseChunkLengthLF(const char * a_Data, size_t a_Size) + { + // Expected input: + if (a_Size == 0) + { + return 0; + } + if (a_Data[0] == '\n') + { + if (m_ChunkDataLengthLeft == 0) + { + m_State = psTrailer; + } + else + { + m_State = psChunkData; + } + return 1; + } + Error(Printf("Invalid character past chunk length's CR: 0x%x", a_Data[0])); + return AString::npos; + } + + + /** Consumes as much chunk data from the input as possible. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error() handler). */ + size_t ParseChunkData(const char * a_Data, size_t a_Size) + { + ASSERT(m_ChunkDataLengthLeft > 0); + auto bytes = std::min(a_Size, m_ChunkDataLengthLeft); + m_ChunkDataLengthLeft -= bytes; + m_Callbacks.OnBodyData(a_Data, bytes); + if (m_ChunkDataLengthLeft == 0) + { + m_State = psChunkDataCR; + } + return bytes; + } + + + /** Parses the incoming data, the current state is psChunkDataCR. + Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseChunkDataCR(const char * a_Data, size_t a_Size) + { + // Expected input: + if (a_Size == 0) + { + return 0; + } + if (a_Data[0] == '\r') + { + m_State = psChunkDataLF; + return 1; + } + Error(Printf("Invalid character past chunk data: 0x%x", a_Data[0])); + return AString::npos; + } + + + + + /** Parses the incoming data, the current state is psChunkDataCR. + Only the CR character is expected, if found, moves to psChunkDataLF, otherwise issues an error. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseChunkDataLF(const char * a_Data, size_t a_Size) + { + // Expected input: + if (a_Size == 0) + { + return 0; + } + if (a_Data[0] == '\n') + { + m_State = psChunkLength; + return 1; + } + Error(Printf("Invalid character past chunk data's CR: 0x%x", a_Data[0])); + return AString::npos; + } + + + /** Parses the incoming data, the current state is psChunkDataCR. + The trailer is normally a set of "Header: Value" lines, terminated by an empty line. Use the m_TrailerParser for that. + Returns the number of bytes consumed from the input, or AString::npos on error (calls the Error handler). */ + size_t ParseTrailer(const char * a_Data, size_t a_Size) + { + auto res = m_TrailerParser.Parse(a_Data, a_Size); + if (res == AString::npos) + { + Error("Error while parsing the trailer"); + } + if ((res < a_Size) || !m_TrailerParser.IsInHeaders()) + { + m_Callbacks.OnBodyFinished(); + m_State = psFinished; + } + return res; + } + + + // cTransferEncodingParser overrides: + virtual size_t Parse(const char * a_Data, size_t a_Size) override + { + while ((a_Size > 0) && (m_State != psFinished)) + { + size_t consumed = 0; + switch (m_State) + { + case psChunkLength: consumed = ParseChunkLength (a_Data, a_Size); break; + case psChunkLengthTrailer: consumed = ParseChunkLengthTrailer(a_Data, a_Size); break; + case psChunkLengthLF: consumed = ParseChunkLengthLF (a_Data, a_Size); break; + case psChunkData: consumed = ParseChunkData (a_Data, a_Size); break; + case psChunkDataCR: consumed = ParseChunkDataCR (a_Data, a_Size); break; + case psChunkDataLF: consumed = ParseChunkDataLF (a_Data, a_Size); break; + case psTrailer: consumed = ParseTrailer (a_Data, a_Size); break; + case psFinished: consumed = 0; break; // Not supposed to happen, but Clang complains without it + } + if (consumed == AString::npos) + { + return AString::npos; + } + a_Data += consumed; + a_Size -= consumed; + } + return a_Size; + } + + virtual void Finish(void) override + { + if (m_State != psFinished) + { + Error(Printf("ChunkedTransferEncoding: Finish signal received before the data stream ended (state: %d)", m_State)); + } + m_State = psFinished; + } + + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override + { + // Ignored + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cIdentityTEParser: + +class cIdentityTEParser: + public cTransferEncodingParser +{ + typedef cTransferEncodingParser Super; + +public: + cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength): + Super(a_Callbacks), + m_BytesLeft(a_ContentLength) + { + } + + +protected: + /** How many bytes of content are left before the message ends. */ + size_t m_BytesLeft; + + // cTransferEncodingParser overrides: + virtual size_t Parse(const char * a_Data, size_t a_Size) override + { + auto size = std::min(a_Size, m_BytesLeft); + if (size > 0) + { + m_Callbacks.OnBodyData(a_Data, size); + } + m_BytesLeft -= size; + if (m_BytesLeft == 0) + { + m_Callbacks.OnBodyFinished(); + } + return a_Size - size; + } + + virtual void Finish(void) override + { + if (m_BytesLeft > 0) + { + m_Callbacks.OnError("IdentityTransferEncoding: body was truncated"); + } + else + { + // BodyFinished has already been called, just bail out + } + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cTransferEncodingParser: + +cTransferEncodingParserPtr cTransferEncodingParser::Create( + cCallbacks & a_Callbacks, + const AString & a_TransferEncoding, + size_t a_ContentLength +) +{ + if (a_TransferEncoding == "chunked") + { + return std::make_shared(a_Callbacks); + } + if (a_TransferEncoding == "identity") + { + return std::make_shared(a_Callbacks, a_ContentLength); + } + if (a_TransferEncoding.empty()) + { + return std::make_shared(a_Callbacks, a_ContentLength); + } + return nullptr; +} + + + + diff --git a/src/HTTP/TransferEncodingParser.h b/src/HTTP/TransferEncodingParser.h new file mode 100644 index 000000000..ce3d01df7 --- /dev/null +++ b/src/HTTP/TransferEncodingParser.h @@ -0,0 +1,76 @@ + +// TransferEncodingParser.h + +// Declares the cTransferEncodingParser class representing the parser for the various transfer encodings (chunked etc.) + +#pragma once + + + + + +// fwd: +class cTransferEncodingParser; +typedef SharedPtr cTransferEncodingParserPtr; + + + + + +/** Used as both the interface that all the parsers share and the (static) factory creating such parsers. */ +class cTransferEncodingParser +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + + // Force a virtual destructor in all descendants + virtual ~cTransferEncodingParser() {} + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes from the end of a_Data that is already not part of this message (if the parser can detect it). + Returns AString::npos on an error. */ + virtual size_t Parse(const char * a_Data, size_t a_Size) = 0; + + /** To be called when the stream is terminated from the source (connection closed). + Flushes any buffers and calls appropriate callbacks. */ + virtual void Finish(void) = 0; + + /** Creates a new parser for the specified encoding. + If the encoding is not known, returns a nullptr. + a_ContentLength is the length of the content, received in a Content-Length header. + It is used for the Identity encoding, it is ignored for the Chunked encoding. */ + static cTransferEncodingParserPtr Create( + cCallbacks & a_Callbacks, + const AString & a_TransferEncoding, + size_t a_ContentLength + ); + +protected: + /** The callbacks used to report progress. */ + cCallbacks & m_Callbacks; + + + cTransferEncodingParser(cCallbacks & a_Callbacks): + m_Callbacks(a_Callbacks) + { + } +}; + + + + diff --git a/src/HTTP/UrlParser.cpp b/src/HTTP/UrlParser.cpp new file mode 100644 index 000000000..05db3e413 --- /dev/null +++ b/src/HTTP/UrlParser.cpp @@ -0,0 +1,200 @@ + +// UrlParser.cpp + +// Implements the cUrlParser class that parses string URL into individual parts + +#include "Globals.h" +#include "UrlParser.h" + + + + + +UInt16 cUrlParser::GetDefaultPort(const AString & a_Scheme) +{ + if (a_Scheme == "http") + { + return 80; + } + else if (a_Scheme == "https") + { + return 443; + } + else if (a_Scheme == "ftp") + { + return 21; + } + else if (a_Scheme == "mailto") + { + return 25; + } + return 0; +} + + + + + +std::pair cUrlParser::ParseAuthorityPart( + const AString & a_AuthorityPart, + AString & a_Username, + AString & a_Password, + AString & a_Host, + UInt16 & a_Port +) +{ + /* + a_AuthorityPart format: + [user:password@]host[:port] + host can be an IPv4, hostname, or an IPv6 enclosed in brackets + Assume only the password can contain an additional at-sign + */ + + // Split the authority on the last at-sign, if present: + auto idxLastAtSign = a_AuthorityPart.find_last_of('@'); + auto credPart = (idxLastAtSign == AString::npos) ? AString() : a_AuthorityPart.substr(0, idxLastAtSign); + auto srvrPart = (idxLastAtSign == AString::npos) ? a_AuthorityPart : a_AuthorityPart.substr(idxLastAtSign + 1); + + // User credentials are completely optional: + auto idxCredColon = credPart.find(':'); + a_Username = credPart.substr(0, idxCredColon); + a_Password = (idxCredColon == AString::npos) ? AString() : credPart.substr(idxCredColon + 1); + + // Host can be a hostname, IPv4 or [IPv6]. If in brackets, search for the closing bracket first + if (srvrPart.empty()) + { + // No host information at all. Bail out with success + a_Host.clear(); + return std::make_pair(true, AString()); + } + if (srvrPart[0] == '[') + { + // [IPv6] host, search for the closing bracket + auto idxClosingBracket = srvrPart.find(']'); + if (idxClosingBracket == AString::npos) + { + return std::make_pair(false, "Invalid IPv6-like address, missing closing bracket"); + } + a_Host = srvrPart.substr(0, idxClosingBracket); + auto portPart = srvrPart.substr(idxClosingBracket + 1); + if (portPart.empty()) + { + // No port was specified, return success + return std::make_pair(true, AString()); + } + if (portPart[0] != ':') + { + return std::make_pair(false, "Invalid port format after IPv6 address, mising colon"); + } + if (!StringToInteger(portPart.substr(2), a_Port)) + { + return std::make_pair(false, "Failed to parse port number after IPv6 address"); + } + return std::make_pair(true, AString()); + } + + // Not an [IPv6] address, split on the last colon: + auto idxLastColon = srvrPart.find_last_of(':'); + a_Host = srvrPart.substr(0, idxLastColon); + if (idxLastColon == AString::npos) + { + // No port was specified, return success + return std::make_pair(true, AString()); + } + auto portPart = srvrPart.substr(idxLastColon + 1); + if (!StringToInteger(portPart, a_Port)) + { + return std::make_pair(false, "Failed to parse port number after hostname"); + } + return std::make_pair(true, AString()); +} + + + + + +std::pair cUrlParser::Parse( + const AString & a_Url, + AString & a_Scheme, + AString & a_Username, + AString & a_Password, + AString & a_Host, + UInt16 & a_Port, + AString & a_Path, + AString & a_Query, + AString & a_Fragment +) +{ + // Find the scheme - the text before the first colon: + auto idxColon = a_Url.find(':'); + if (idxColon == AString::npos) + { + return std::make_pair(false, "Cannot parse the Scheme part of the URL"); + } + a_Scheme = StrToLower(a_Url.substr(0, idxColon)); + a_Port = GetDefaultPort(a_Scheme); + if (a_Port == 0) + { + return std::make_pair(false, Printf("Unknown URL scheme: \"%s\"", a_Scheme.c_str())); + } + + // If the next two chars are a double-slash, skip them: + auto authStart = idxColon + 1; + if (a_Url.substr(authStart, 2) == "//") + { + authStart += 2; + } + + // The Authority part follows the Scheme, until the first slash: + auto idxFirstSlash = a_Url.find('/', authStart + 1); + if (idxFirstSlash == AString::npos) + { + // No slash, the whole end of the Url is the authority part + idxFirstSlash = a_Url.size(); + } + + // Parse the Authority part into individual components: + auto res = ParseAuthorityPart( + a_Url.substr(authStart, idxFirstSlash - authStart), + a_Username, a_Password, + a_Host, a_Port + ); + if (!res.first) + { + return res; + } + + // Parse the rest into a path, query and fragment: + a_Path.clear(); + a_Query.clear(); + a_Fragment.clear(); + if (idxFirstSlash == a_Url.size()) + { + // No additional data, bail out with success + return std::make_pair(true, AString()); + } + auto idxPathEnd = a_Url.find_first_of("?#", idxFirstSlash + 1); + if (idxPathEnd == AString::npos) + { + a_Path = a_Url.substr(idxFirstSlash); + return std::make_pair(true, AString()); + } + a_Path = a_Url.substr(idxFirstSlash, idxPathEnd - idxFirstSlash); + auto idxHash = a_Url.find('#', idxPathEnd); + if (idxHash == AString::npos) + { + a_Query = a_Url.substr(idxPathEnd + 1); + return std::make_pair(true, AString()); + } + if (idxHash > idxPathEnd) + { + a_Query = a_Url.substr(idxPathEnd + 1, idxHash - idxPathEnd - 1); + } + a_Fragment = a_Url.substr(idxHash + 1); + return std::make_pair(true, AString()); +} + + + + + diff --git a/src/HTTP/UrlParser.h b/src/HTTP/UrlParser.h new file mode 100644 index 000000000..15a63e05d --- /dev/null +++ b/src/HTTP/UrlParser.h @@ -0,0 +1,58 @@ + +// UrlParser.h + +// Declares the cUrlParser class that parses string URL into individual parts + + + + + +#pragma once + + + + + +class cUrlParser +{ +public: + /** Returns true if the specified scheme (http, ftp, mailto, ...) is recognized by the URL parser. + Is case sensitive, known schemes are always lowercase. */ + static bool IsKnownScheme(const AString & a_Scheme) { return (GetDefaultPort(a_Scheme) > 0); } + + /** Returns the default port used by the specified scheme / protocol. + If the scheme is not known, 0 is returned. */ + static UInt16 GetDefaultPort(const AString & a_Scheme); + + /** Parses the given Authority part of an URL into individual components. + Returns true on success, + returns false and error message on failure. */ + static std::pair ParseAuthorityPart( + const AString & a_AuthorityPart, + AString & a_Username, + AString & a_Password, + AString & a_Host, + UInt16 & a_Port + ); + + /** Parses the given URL into individual components. + Returns true on success, + returns false and error message on failure. + Fails if the scheme (protocol) is not known. + If port is missing, the default port for the specific scheme is applied. */ + static std::pair Parse( + const AString & a_Url, + AString & a_Scheme, + AString & a_Username, + AString & a_Password, + AString & a_Host, + UInt16 & a_Port, + AString & a_Path, + AString & a_Query, + AString & a_Fragment + ); +}; + + + + diff --git a/src/HTTPServer/CMakeLists.txt b/src/HTTPServer/CMakeLists.txt deleted file mode 100644 index 4597f6eda..000000000 --- a/src/HTTPServer/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ - -cmake_minimum_required (VERSION 2.6) -project (Cuberite) - -include_directories ("${PROJECT_SOURCE_DIR}/../") - -SET (SRCS - EnvelopeParser.cpp - HTTPFormParser.cpp - HTTPMessage.cpp - HTTPRequestParser.cpp - HTTPResponseParser.cpp - HTTPServer.cpp - HTTPServerConnection.cpp - MultipartParser.cpp - NameValueParser.cpp - SslHTTPServerConnection.cpp - TransferEncodingParser.cpp - UrlParser.cpp -) - -SET (HDRS - EnvelopeParser.h - HTTPFormParser.h - HTTPMessage.h - HTTPRequestParser.h - HTTPResponseParser.h - HTTPServer.h - HTTPServerConnection.h - MultipartParser.h - NameValueParser.h - SslHTTPServerConnection.h - TransferEncodingParser.h - UrlParser.h -) - -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(HTTPServer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=global-constructors ") - set_source_files_properties(HTTPServerConnection.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum") - set_source_files_properties(HTTPMessage.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=tautological-compare") -endif() - -if(NOT MSVC) - add_library(HTTPServer ${SRCS} ${HDRS}) -endif() diff --git a/src/HTTPServer/EnvelopeParser.cpp b/src/HTTPServer/EnvelopeParser.cpp deleted file mode 100644 index 407e9dcfc..000000000 --- a/src/HTTPServer/EnvelopeParser.cpp +++ /dev/null @@ -1,132 +0,0 @@ - -// 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; -} - - - - diff --git a/src/HTTPServer/EnvelopeParser.h b/src/HTTPServer/EnvelopeParser.h deleted file mode 100644 index 2fa930539..000000000 --- a/src/HTTPServer/EnvelopeParser.h +++ /dev/null @@ -1,73 +0,0 @@ - -// EnvelopeParser.h - -// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME - - - - - -#pragma once - - - - - -class cEnvelopeParser -{ -public: - class cCallbacks - { - public: - // Force a virtual destructor in descendants: - virtual ~cCallbacks() {} - - /** Called when a full header line is parsed */ - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; - } ; - - - cEnvelopeParser(cCallbacks & a_Callbacks); - - /** Parses the incoming data. - Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header. - Returns AString::npos on error - */ - size_t Parse(const char * a_Data, size_t a_Size); - - /** Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream */ - void Reset(void); - - /** Returns true if more input is expected for the envelope header */ - bool IsInHeaders(void) const { return m_IsInHeaders; } - - /** Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions */ - void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; } - -public: - /** Callbacks to call for the various events */ - cCallbacks & m_Callbacks; - - /** Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data. */ - bool m_IsInHeaders; - - /** Buffer for the incoming data until it is parsed */ - AString m_IncomingData; - - /** Holds the last parsed key; used for line-wrapped values */ - AString m_LastKey; - - /** Holds the last parsed value; used for line-wrapped values */ - AString m_LastValue; - - - /** Notifies the callback of the key / value stored in m_LastKey / m_LastValue, then erases them */ - void NotifyLast(void); - - /** Parses one line of header data. Returns true if successful */ - bool ParseLine(const char * a_Data, size_t a_Size); -} ; - - - - diff --git a/src/HTTPServer/HTTPFormParser.cpp b/src/HTTPServer/HTTPFormParser.cpp deleted file mode 100644 index ddf87f540..000000000 --- a/src/HTTPServer/HTTPFormParser.cpp +++ /dev/null @@ -1,293 +0,0 @@ - -// HTTPFormParser.cpp - -// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP - -#include "Globals.h" -#include "HTTPFormParser.h" -#include "HTTPRequestParser.h" -#include "MultipartParser.h" -#include "NameValueParser.h" - - - - - -cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_IsValid(true), - m_IsCurrentPartFile(false), - m_FileHasBeenAnnounced(false) -{ - if (a_Request.GetMethod() == "GET") - { - m_Kind = fpkURL; - - // Directly parse the URL in the request: - const AString & URL = a_Request.GetURL(); - size_t idxQM = URL.find('?'); - if (idxQM != AString::npos) - { - Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1); - } - return; - } - if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) - { - if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) - { - m_Kind = fpkFormUrlEncoded; - return; - } - if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) - { - m_Kind = fpkMultipart; - BeginMultipart(a_Request); - return; - } - } - // Invalid method / content type combination, this is not a HTTP form - m_IsValid = false; -} - - - - - -cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_Kind(a_Kind), - m_IsValid(true), - m_IsCurrentPartFile(false), - m_FileHasBeenAnnounced(false) -{ - Parse(a_Data, a_Size); -} - - - - - -void cHTTPFormParser::Parse(const char * a_Data, size_t a_Size) -{ - if (!m_IsValid) - { - return; - } - - switch (m_Kind) - { - case fpkURL: - case fpkFormUrlEncoded: - { - // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish() - m_IncomingData.append(a_Data, a_Size); - break; - } - case fpkMultipart: - { - ASSERT(m_MultipartParser.get() != nullptr); - m_MultipartParser->Parse(a_Data, a_Size); - break; - } - } -} - - - - - -bool cHTTPFormParser::Finish(void) -{ - switch (m_Kind) - { - case fpkURL: - case fpkFormUrlEncoded: - { - // m_IncomingData has all the form data, parse it now: - ParseFormUrlEncoded(); - break; - } - case fpkMultipart: - { - // Nothing needed for other formats - break; - } - } - return (m_IsValid && m_IncomingData.empty()); -} - - - - - -bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) -{ - const AString & ContentType = a_Request.GetContentType(); - return ( - (ContentType == "application/x-www-form-urlencoded") || - (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) || - ( - (a_Request.GetMethod() == "GET") && - (a_Request.GetURL().find('?') != AString::npos) - ) - ); -} - - - - - -void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request) -{ - ASSERT(m_MultipartParser.get() == nullptr); - m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); -} - - - - - -void cHTTPFormParser::ParseFormUrlEncoded(void) -{ - // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish() - // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway - AStringVector Lines = StringSplit(m_IncomingData, "&"); - for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr) - { - AStringVector Components = StringSplit(*itr, "="); - switch (Components.size()) - { - default: - { - // Neither name nor value, or too many "="s, mark this as invalid form: - m_IsValid = false; - return; - } - case 1: - { - // Only name present - (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = ""; - break; - } - case 2: - { - // name=value format: - (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' ')); - break; - } - } - } // for itr - Lines[] - m_IncomingData.clear(); -} - - - - - -void cHTTPFormParser::OnPartStart(void) -{ - m_CurrentPartFileName.clear(); - m_CurrentPartName.clear(); - m_IsCurrentPartFile = false; - m_FileHasBeenAnnounced = false; -} - - - - - -void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value) -{ - if (NoCaseCompare(a_Key, "Content-Disposition") == 0) - { - size_t len = a_Value.size(); - size_t ParamsStart = AString::npos; - for (size_t i = 0; i < len; ++i) - { - if (a_Value[i] > ' ') - { - if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0) - { - // Content disposition is not "form-data", mark the whole form invalid - m_IsValid = false; - return; - } - ParamsStart = a_Value.find(';', i + 9); - break; - } - } - if (ParamsStart == AString::npos) - { - // There is data missing in the Content-Disposition field, mark the whole form invalid: - m_IsValid = false; - return; - } - - // Parse the field name and optional filename from this header: - cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart); - Parser.Finish(); - m_CurrentPartName = Parser["name"]; - if (!Parser.IsValid() || m_CurrentPartName.empty()) - { - // The required parameter "name" is missing, mark the whole form invalid: - m_IsValid = false; - return; - } - m_CurrentPartFileName = Parser["filename"]; - } -} - - - - - -void cHTTPFormParser::OnPartData(const char * a_Data, size_t a_Size) -{ - if (m_CurrentPartName.empty()) - { - // Prologue, epilogue or invalid part - return; - } - if (m_CurrentPartFileName.empty()) - { - // This is a variable, store it in the map - iterator itr = find(m_CurrentPartName); - if (itr == end()) - { - (*this)[m_CurrentPartName] = AString(a_Data, a_Size); - } - else - { - itr->second.append(a_Data, a_Size); - } - } - else - { - // This is a file, pass it on through the callbacks - if (!m_FileHasBeenAnnounced) - { - m_Callbacks.OnFileStart(*this, m_CurrentPartFileName); - m_FileHasBeenAnnounced = true; - } - m_Callbacks.OnFileData(*this, a_Data, a_Size); - } -} - - - - - -void cHTTPFormParser::OnPartEnd(void) -{ - if (m_FileHasBeenAnnounced) - { - m_Callbacks.OnFileEnd(*this); - } - m_CurrentPartName.clear(); - m_CurrentPartFileName.clear(); -} - - - - diff --git a/src/HTTPServer/HTTPFormParser.h b/src/HTTPServer/HTTPFormParser.h deleted file mode 100644 index fde6df696..000000000 --- a/src/HTTPServer/HTTPFormParser.h +++ /dev/null @@ -1,114 +0,0 @@ - -// HTTPFormParser.h - -// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP - - - - -#pragma once - -#include "MultipartParser.h" - - - - - -// fwd: -class cHTTPRequestParser; - - - - - -class cHTTPFormParser : - public std::map, - public cMultipartParser::cCallbacks -{ -public: - enum eKind - { - fpkURL, ///< The form has been transmitted as parameters to a GET request - fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded" - fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data" - } ; - - class cCallbacks - { - public: - // Force a virtual destructor in descendants: - virtual ~cCallbacks() {} - - /** Called when a new file part is encountered in the form data */ - virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0; - - /** Called when more file data has come for the current file in the form data */ - virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) = 0; - - /** Called when the current file part has ended in the form data */ - virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0; - } ; - - - /** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */ - cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks); - - /** Creates a parser with the specified content type that reads data from a string */ - cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks); - - /** Adds more data into the parser, as the request body is received */ - void Parse(const char * a_Data, size_t a_Size); - - /** Notifies that there's no more data incoming and the parser should finish its parsing. - Returns true if parsing successful. */ - bool Finish(void); - - /** Returns true if the headers suggest the request has form data parseable by this class */ - static bool HasFormData(const cHTTPRequestParser & a_Request); - -protected: - - /** The callbacks to call for incoming file data */ - cCallbacks & m_Callbacks; - - /** The kind of the parser (decided in the constructor, used in Parse() */ - eKind m_Kind; - - /** Buffer for the incoming data until it's parsed */ - AString m_IncomingData; - - /** True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false. */ - bool m_IsValid; - - /** The parser for the multipart data, if used */ - std::unique_ptr m_MultipartParser; - - /** Name of the currently parsed part in multipart data */ - AString m_CurrentPartName; - - /** True if the currently parsed part in multipart data is a file */ - bool m_IsCurrentPartFile; - - /** Filename of the current parsed part in multipart data (for file uploads) */ - AString m_CurrentPartFileName; - - /** Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd */ - bool m_FileHasBeenAnnounced; - - - /** Sets up the object for parsing a fpkMultipart request */ - void BeginMultipart(const cHTTPRequestParser & a_Request); - - /** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */ - void ParseFormUrlEncoded(void); - - // cMultipartParser::cCallbacks overrides: - virtual void OnPartStart (void) override; - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override; - virtual void OnPartData (const char * a_Data, size_t a_Size) override; - virtual void OnPartEnd (void) override; -} ; - - - - diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp deleted file mode 100644 index ca63397dd..000000000 --- a/src/HTTPServer/HTTPMessage.cpp +++ /dev/null @@ -1,102 +0,0 @@ - -// HTTPMessage.cpp - -// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes - -#include "Globals.h" -#include "HTTPMessage.h" - - - - - -// Disable MSVC warnings: -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4355) // 'this' : used in base member initializer list -#endif - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHTTPMessage: - -cHTTPMessage::cHTTPMessage(eKind a_Kind) : - m_Kind(a_Kind), - m_ContentLength(AString::npos) -{ -} - - - - - -void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) -{ - auto Key = StrToLower(a_Key); - auto itr = m_Headers.find(Key); - if (itr == m_Headers.end()) - { - m_Headers[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); - } - - // Special processing for well-known headers: - if (Key == "content-type") - { - m_ContentType = m_Headers[Key]; - } - else if (Key == "content-length") - { - if (!StringToInteger(m_Headers[Key], m_ContentLength)) - { - m_ContentLength = 0; - } - } -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHTTPResponse: - -cHTTPResponse::cHTTPResponse(void) : - super(mkResponse) -{ -} - - - - - -void cHTTPResponse::AppendToData(AString & a_DataStream) const -{ - a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); - a_DataStream.append(m_ContentType); - a_DataStream.append("\r\n"); - for (auto itr = m_Headers.cbegin(), end = m_Headers.cend(); itr != end; ++itr) - { - if ((itr->first == "Content-Type") || (itr->first == "Content-Length")) - { - continue; - } - a_DataStream.append(itr->first); - a_DataStream.append(": "); - a_DataStream.append(itr->second); - a_DataStream.append("\r\n"); - } // for itr - m_Headers[] - a_DataStream.append("\r\n"); -} - - - - diff --git a/src/HTTPServer/HTTPMessage.h b/src/HTTPServer/HTTPMessage.h deleted file mode 100644 index 50b1f9e19..000000000 --- a/src/HTTPServer/HTTPMessage.h +++ /dev/null @@ -1,84 +0,0 @@ - -// HTTPMessage.h - -// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes - - - - - -#pragma once - -#include "EnvelopeParser.h" - - - - - -class cHTTPMessage -{ -public: - enum eStatus - { - HTTP_OK = 200, - HTTP_BAD_REQUEST = 400, - } ; - - enum eKind - { - mkRequest, - mkResponse, - } ; - - cHTTPMessage(eKind a_Kind); - - // Force a virtual destructor in all descendants - virtual ~cHTTPMessage() {} - - /** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */ - void AddHeader(const AString & a_Key, const AString & a_Value); - - void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; } - void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; } - - const AString & GetContentType (void) const { return m_ContentType; } - size_t GetContentLength(void) const { return m_ContentLength; } - -protected: - typedef std::map cNameValueMap; - - eKind m_Kind; - - /** Map of headers, with their keys lowercased. */ - AStringMap m_Headers; - - /** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */ - AString m_ContentType; - - /** Length of the content that is to be received. - AString::npos when the object is created. - Parsed by AddHeader() or set directly by SetContentLength() */ - size_t m_ContentLength; -} ; - - - - - -class cHTTPResponse : - public cHTTPMessage -{ - typedef cHTTPMessage super; - -public: - cHTTPResponse(void); - - /** Appends the response to the specified datastream - response line and headers. - The body will be sent later directly through cConnection::Send() - */ - void AppendToData(AString & a_DataStream) const; -} ; - - - - diff --git a/src/HTTPServer/HTTPRequestParser.cpp b/src/HTTPServer/HTTPRequestParser.cpp deleted file mode 100644 index 9c60c6053..000000000 --- a/src/HTTPServer/HTTPRequestParser.cpp +++ /dev/null @@ -1,192 +0,0 @@ - -// HTTPRequestParser.cpp - -// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests - -#include "Globals.h" -#include "HTTPRequestParser.h" - - - - - -cHTTPRequestParser::cHTTPRequestParser(void) : - super(mkRequest), - m_EnvelopeParser(*this), - m_IsValid(true), - m_UserData(nullptr), - m_HasAuth(false), - m_AllowKeepAlive(false) -{ -} - - - - - -size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) -{ - if (!m_IsValid) - { - return AString::npos; - } - - if (m_Method.empty()) - { - // The first line hasn't been processed yet - size_t res = ParseRequestLine(a_Data, a_Size); - if ((res == AString::npos) || (res == a_Size)) - { - return res; - } - size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); - if (res2 == AString::npos) - { - m_IsValid = false; - return res2; - } - return res2 + res; - } - - if (m_EnvelopeParser.IsInHeaders()) - { - size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); - if (res == AString::npos) - { - m_IsValid = false; - } - return res; - } - return 0; -} - - - - - -AString cHTTPRequestParser::GetBareURL(void) const -{ - size_t idxQM = m_URL.find('?'); - if (idxQM != AString::npos) - { - return m_URL.substr(0, idxQM); - } - else - { - return m_URL; - } -} - - - - - -size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) -{ - m_IncomingHeaderData.append(a_Data, a_Size); - size_t IdxEnd = m_IncomingHeaderData.size(); - - // Ignore the initial CRLFs (HTTP spec's "should") - size_t LineStart = 0; - while ( - (LineStart < IdxEnd) && - ( - (m_IncomingHeaderData[LineStart] == '\r') || - (m_IncomingHeaderData[LineStart] == '\n') - ) - ) - { - LineStart++; - } - if (LineStart >= IdxEnd) - { - m_IsValid = false; - return AString::npos; - } - - int NumSpaces = 0; - size_t MethodEnd = 0; - size_t URLEnd = 0; - for (size_t i = LineStart; i < IdxEnd; i++) - { - switch (m_IncomingHeaderData[i]) - { - case ' ': - { - switch (NumSpaces) - { - case 0: - { - MethodEnd = i; - break; - } - case 1: - { - URLEnd = i; - break; - } - default: - { - // Too many spaces in the request - m_IsValid = false; - return AString::npos; - } - } - NumSpaces += 1; - break; - } - case '\n': - { - if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) - { - // LF too early, without a CR, without two preceeding spaces or too soon after the second space - m_IsValid = false; - return AString::npos; - } - // Check that there's HTTP / version at the end - if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) - { - m_IsValid = false; - return AString::npos; - } - m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); - m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); - return i + 1; - } - } // switch (m_IncomingHeaderData[i]) - } // for i - m_IncomingHeaderData[] - - // CRLF hasn't been encountered yet, consider all data consumed - return a_Size; -} - - - - - -void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - if ( - (NoCaseCompare(a_Key, "Authorization") == 0) && - (strncmp(a_Value.c_str(), "Basic ", 6) == 0) - ) - { - AString UserPass = Base64Decode(a_Value.substr(6)); - size_t idxCol = UserPass.find(':'); - if (idxCol != AString::npos) - { - m_AuthUsername = UserPass.substr(0, idxCol); - m_AuthPassword = UserPass.substr(idxCol + 1); - m_HasAuth = true; - } - } - if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) - { - m_AllowKeepAlive = true; - } - AddHeader(a_Key, a_Value); -} - - - - diff --git a/src/HTTPServer/HTTPRequestParser.h b/src/HTTPServer/HTTPRequestParser.h deleted file mode 100644 index f3d3add91..000000000 --- a/src/HTTPServer/HTTPRequestParser.h +++ /dev/null @@ -1,109 +0,0 @@ - -// HTTPRequestParser.h - -// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests - - - - -#pragma once - -#include "HTTPMessage.h" -#include "EnvelopeParser.h" - - - - - -class cHTTPRequestParser : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks -{ - typedef cHTTPMessage super; - -public: - cHTTPRequestParser(void); - - /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or AString::npos number for error - */ - size_t ParseHeaders(const char * a_Data, size_t a_Size); - - /** Returns true if the request did contain a Content-Length header */ - bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); } - - /** Returns the method used in the request */ - const AString & GetMethod(void) const { return m_Method; } - - /** Returns the URL used in the request */ - const AString & GetURL(void) const { return m_URL; } - - /** Returns the URL used in the request, without any parameters */ - AString GetBareURL(void) const; - - /** Sets the UserData pointer that is stored within this request. - The request doesn't touch this data (doesn't delete it)! */ - void SetUserData(void * a_UserData) { m_UserData = a_UserData; } - - /** Retrieves the UserData pointer that has been stored within this request. */ - void * GetUserData(void) const { return m_UserData; } - - /** Returns true if more data is expected for the request headers */ - bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } - - /** Returns true if the request did present auth data that was understood by the parser */ - bool HasAuth(void) const { return m_HasAuth; } - - /** Returns the username that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthUsername(void) const { return m_AuthUsername; } - - /** Returns the password that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthPassword(void) const { return m_AuthPassword; } - - bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } - -protected: - /** Parser for the envelope data */ - cEnvelopeParser m_EnvelopeParser; - - /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ - bool m_IsValid; - - /** Bufferred incoming data, while parsing for the request line */ - AString m_IncomingHeaderData; - - /** Method of the request (GET / PUT / POST / ...) */ - AString m_Method; - - /** Full URL of the request */ - AString m_URL; - - /** Data that the HTTPServer callbacks are allowed to store. */ - void * m_UserData; - - /** Set to true if the request contains auth data that was understood by the parser */ - bool m_HasAuth; - - /** The username used for auth */ - AString m_AuthUsername; - - /** The password used for auth */ - AString m_AuthPassword; - - /** Set to true if the request indicated that it supports keepalives. - If false, the server will close the connection once the request is finished */ - bool m_AllowKeepAlive; - - - /** Parses the incoming data for the first line (RequestLine) - Returns the number of bytes consumed, or AString::npos for an error - */ - size_t ParseRequestLine(const char * a_Data, size_t a_Size); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; -} ; - - - - diff --git a/src/HTTPServer/HTTPResponseParser.cpp b/src/HTTPServer/HTTPResponseParser.cpp deleted file mode 100644 index b3ce9dab6..000000000 --- a/src/HTTPServer/HTTPResponseParser.cpp +++ /dev/null @@ -1,177 +0,0 @@ - -// HTTPResponseParser.cpp - -// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses - -#include "Globals.h" -#include "HTTPResponseParser.h" - - - - - -cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks): - Super(mkResponse), - m_Callbacks(a_Callbacks), - m_IsInHeaders(true), - m_IsFinished(false), - m_EnvelopeParser(*this) -{ -} - - - - - -size_t cHTTPResponseParser::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 a_Size; - } - - // If still waiting for the status line, add to buffer and try parsing it: - if (m_StatusLine.empty()) - { - m_Buffer.append(a_Data, a_Size); - if (!ParseStatusLine()) - { - // All data used, but not a complete status line yet. - return 0; - } - if (m_HasHadError) - { - return AString::npos; - } - // Status line completed, feed the rest of the buffer into the envelope parser: - auto bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size()); - if (bytesConsumed == AString::npos) - { - m_HasHadError = true; - m_Callbacks.OnError("Failed to parse the envelope"); - return AString::npos; - } - m_Buffer.erase(0, bytesConsumed); - if (!m_Buffer.empty()) - { - // Headers finished and there's still data left in the buffer, process it as message body: - m_IsInHeaders = false; - return ParseBody(m_Buffer.data(), m_Buffer.size()); - } - return 0; - } // if (m_StatusLine.empty()) - - // If still parsing headers, send them to the envelope parser: - if (m_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 (bytesConsumed < a_Size) - { - // Headers finished and there's still data left in the buffer, process it as message body: - HeadersFinished(); - return ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); - } - return 0; - } - - // Already parsing the body - return ParseBody(a_Data, a_Size); -} - - - - - -bool cHTTPResponseParser::ParseStatusLine(void) -{ - auto idxLineEnd = m_Buffer.find("\r\n"); - if (idxLineEnd == AString::npos) - { - // Not a complete line yet - return false; - } - m_StatusLine = m_Buffer.substr(0, idxLineEnd); - m_Buffer.erase(0, idxLineEnd + 2); - m_Callbacks.OnStatusLine(m_StatusLine); - return true; -} - - - - -size_t cHTTPResponseParser::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 - return AString::npos; - } - - // Parse the body using the transfer encoding parser: - return m_TransferEncodingParser->Parse(a_Data, a_Size); -} - - - - - -void cHTTPResponseParser::HeadersFinished(void) -{ - m_IsInHeaders = false; - m_Callbacks.OnHeadersFinished(); - - auto transferEncoding = m_Headers.find("transfer-encoding"); - if (transferEncoding == m_Headers.end()) - { - m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength); - } -} - - - - - -void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - AddHeader(a_Key, a_Value); - m_Callbacks.OnHeaderLine(a_Key, a_Value); -} - - - - - -void cHTTPResponseParser::OnError(const AString & a_ErrorDescription) -{ - m_HasHadError = true; - m_Callbacks.OnError(a_ErrorDescription); -} - - - - - -void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size) -{ - m_Callbacks.OnBodyData(a_Data, a_Size); -} - - - - - -void cHTTPResponseParser::OnBodyFinished(void) -{ - m_Callbacks.OnBodyFinished(); -} - - - - diff --git a/src/HTTPServer/HTTPResponseParser.h b/src/HTTPServer/HTTPResponseParser.h deleted file mode 100644 index 19652ccef..000000000 --- a/src/HTTPServer/HTTPResponseParser.h +++ /dev/null @@ -1,118 +0,0 @@ - -// HTTPResponseParser.h - -// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses - - - - -#pragma once - -#include "HTTPMessage.h" -#include "TransferEncodingParser.h" - - - - - -class cHTTPResponseParser: - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks, - protected cTransferEncodingParser::cCallbacks -{ - typedef cHTTPMessage Super; - -public: - class cCallbacks - { - public: - // Force a virtual destructor in descendants: - virtual ~cCallbacks() {} - - /** Called when an error has occured while parsing. */ - virtual void OnError(const AString & a_ErrorDescription) = 0; - - /** Called when the status line is fully parsed. */ - virtual void OnStatusLine(const AString & a_StatusLine) = 0; - - /** Called when a single header line is parsed. */ - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; - - /** Called when all the headers have been parsed. */ - virtual void OnHeadersFinished(void) = 0; - - /** Called for each chunk of the incoming body data. */ - virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; - - /** Called when the entire body has been reported by OnBodyData(). */ - virtual void OnBodyFinished(void) = 0; - }; - - cHTTPResponseParser(cCallbacks & a_Callbacks); - - /** Parses the incoming data and calls the appropriate callbacks. - Returns the number of bytes from the end of a_Data that is already not part of this response. - Returns AString::npos on an error. */ - size_t Parse(const char * a_Data, size_t a_Size); - - /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). - Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ - void Finish(void); - - /** Returns true if the entire response has been already parsed. */ - bool IsFinished(void) const { return m_IsFinished; } - - -protected: - - /** The callbacks used for reporting. */ - cCallbacks & m_Callbacks; - - /** Set to true if an error has been encountered by the parser. */ - bool m_HasHadError; - - /** True if the parser is still parsing the status or headers. */ - bool m_IsInHeaders; - - /** True if the response has been fully parsed. */ - bool m_IsFinished; - - /** The complete status line of the response. Empty if not parsed yet. */ - AString m_StatusLine; - - /** Buffer for the incoming data until the status line is parsed. */ - AString m_Buffer; - - /** Parser for the envelope data (headers) */ - cEnvelopeParser m_EnvelopeParser; - - /** The specific parser for the transfer encoding used by this response. */ - cTransferEncodingParserPtr m_TransferEncodingParser; - - - /** Parses the status line out of the m_Buffer. - Removes the status line from m_Buffer, if appropriate. - Returns true if the entire status line has been parsed. */ - bool ParseStatusLine(void); - - /** Parses the message body. - Processes transfer encoding and calls the callbacks for body data. - Returns the number of bytes from the end of a_Data that is already not part of this response. - Returns AString::npos on error. */ - size_t ParseBody(const char * a_Data, size_t a_Size); - - /** Called internally when the headers-parsing has just finished. */ - void HeadersFinished(void); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; - - // cTransferEncodingParser::cCallbacks overrides: - virtual void OnError(const AString & a_ErrorDescription) override; - virtual void OnBodyData(const void * a_Data, size_t a_Size) override; - virtual void OnBodyFinished(void) override; -}; - - - - diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp deleted file mode 100644 index 741f5f1c5..000000000 --- a/src/HTTPServer/HTTPServer.cpp +++ /dev/null @@ -1,309 +0,0 @@ - -// HTTPServer.cpp - -// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing - -#include "Globals.h" -#include "HTTPServer.h" -#include "HTTPRequestParser.h" -#include "HTTPServerConnection.h" -#include "HTTPFormParser.h" -#include "SslHTTPServerConnection.h" - - - - - -// Disable MSVC warnings: -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4355) // 'this' : used in base member initializer list -#endif - - - - - -class cDebugCallbacks : - public cHTTPServer::cCallbacks, - protected cHTTPFormParser::cCallbacks -{ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - UNUSED(a_Connection); - - if (cHTTPFormParser::HasFormData(a_Request)) - { - a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); - } - } - - - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override - { - UNUSED(a_Connection); - - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - FormParser->Parse(a_Data, a_Size); - } - } - - - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - if (FormParser->Finish()) - { - cHTTPResponse Resp; - Resp.SetContentType("text/html"); - a_Connection.Send(Resp); - a_Connection.Send("\r\n"); - for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) - { - a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); - } // for itr - FormParser[] - a_Connection.Send("
NameValue
%s
%s
"); - return; - } - - // Parsing failed: - cHTTPResponse Resp; - Resp.SetContentType("text/plain"); - a_Connection.Send(Resp); - a_Connection.Send("Form parsing failed"); - return; - } - - // Test the auth failure and success: - if (a_Request.GetURL() == "/auth") - { - if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b")) - { - a_Connection.SendNeedAuth("Cuberite WebAdmin"); - return; - } - } - - cHTTPResponse Resp; - Resp.SetContentType("text/plain"); - a_Connection.Send(Resp); - a_Connection.Send("Hello, world"); - } - - - virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override - { - // TODO - } - - - virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override - { - // TODO - } - - - virtual void OnFileEnd(cHTTPFormParser & a_Parser) override - { - // TODO - } - -}; - -static cDebugCallbacks g_DebugCallbacks; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHTTPServerListenCallbacks: - -class cHTTPServerListenCallbacks: - public cNetwork::cListenCallbacks -{ -public: - cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port): - m_HTTPServer(a_HTTPServer), - m_Port(a_Port) - { - } - -protected: - /** The HTTP server instance that we're attached to. */ - cHTTPServer & m_HTTPServer; - - /** The port for which this instance is responsible. */ - UInt16 m_Port; - - // cNetwork::cListenCallbacks overrides: - virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override - { - return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort); - } - virtual void OnAccepted(cTCPLink & a_Link) override {} - virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override - { - LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str()); - } -}; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cHTTPServer: - -cHTTPServer::cHTTPServer(void) : - m_Callbacks(nullptr) -{ -} - - - - - -cHTTPServer::~cHTTPServer() -{ - Stop(); -} - - - - - -bool cHTTPServer::Initialize(void) -{ - // Read the HTTPS cert + key: - AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt"); - AString KeyFile = cFile::ReadWholeFile("webadmin/httpskey.pem"); - if (!CertFile.empty() && !KeyFile.empty()) - { - m_Cert.reset(new cX509Cert); - int res = m_Cert->Parse(CertFile.data(), CertFile.size()); - if (res == 0) - { - m_CertPrivKey.reset(new cCryptoKey); - int res2 = m_CertPrivKey->ParsePrivate(KeyFile.data(), KeyFile.size(), ""); - if (res2 != 0) - { - // Reading the private key failed, reset the cert: - LOGWARNING("WebServer: Cannot read HTTPS certificate private key: -0x%x", -res2); - m_Cert.reset(); - } - } - else - { - LOGWARNING("WebServer: Cannot read HTTPS certificate: -0x%x", -res); - } - } - - // Notify the admin about the HTTPS / HTTP status - if (m_Cert.get() == nullptr) - { - LOGWARNING("WebServer: The server is running in unsecured HTTP mode."); - LOGINFO("Put a valid HTTPS certificate in file 'webadmin/httpscert.crt' and its corresponding private key to 'webadmin/httpskey.pem' (without any password) to enable HTTPS support"); - } - else - { - LOGINFO("WebServer: The server is running in secure HTTPS mode."); - } - return true; -} - - - - - -bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports) -{ - m_Callbacks = &a_Callbacks; - - // Open up requested ports: - for (auto port : a_Ports) - { - UInt16 PortNum; - if (!StringToInteger(port, PortNum)) - { - LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str()); - continue; - } - auto Handle = cNetwork::Listen(PortNum, std::make_shared(*this, PortNum)); - if (Handle->IsListening()) - { - m_ServerHandles.push_back(Handle); - } - } // for port - a_Ports[] - - // Report success if at least one port opened successfully: - return !m_ServerHandles.empty(); -} - - - - - -void cHTTPServer::Stop(void) -{ - for (auto handle : m_ServerHandles) - { - handle->Close(); - } - m_ServerHandles.clear(); -} - - - - - -cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) -{ - UNUSED(a_RemoteIPAddress); - UNUSED(a_RemotePort); - - if (m_Cert.get() != nullptr) - { - return std::make_shared(*this, m_Cert, m_CertPrivKey); - } - else - { - return std::make_shared(*this); - } -} - - - - - -void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) -{ - m_Callbacks->OnRequestBegun(a_Connection, a_Request); -} - - - - - -void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) -{ - m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); -} - - - - - -void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) -{ - m_Callbacks->OnRequestFinished(a_Connection, a_Request); - a_Connection.AwaitNextRequest(); -} - - - - diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h deleted file mode 100644 index 15202a50e..000000000 --- a/src/HTTPServer/HTTPServer.h +++ /dev/null @@ -1,101 +0,0 @@ - -// HTTPServer.h - -// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing - - - - - -#pragma once - -#include "../OSSupport/Network.h" -#include "../IniFile.h" -#include "PolarSSL++/RsaPrivateKey.h" -#include "PolarSSL++/CryptoKey.h" -#include "PolarSSL++/X509Cert.h" - - - - - -// fwd: -class cHTTPMessage; -class cHTTPRequestParser; -class cHTTPResponse; -class cHTTPServerConnection; - - - - - - -class cHTTPServer -{ -public: - class cCallbacks - { - public: - virtual ~cCallbacks() {} - - /** Called when a new request arrives over a connection and all its headers have been parsed. - The request body needn't have arrived yet. */ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; - - /** Called when another part of request body has arrived. - May be called multiple times for a single request. */ - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0; - - /** Called when the request body has been fully received in previous calls to OnRequestBody() */ - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; - } ; - - cHTTPServer(void); - virtual ~cHTTPServer(); - - /** Initializes the server - reads the cert files etc. */ - bool Initialize(void); - - /** Starts the server and assigns the callbacks to use for incoming requests */ - bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports); - - /** Stops the server, drops all current connections */ - void Stop(void); - -protected: - friend class cHTTPServerConnection; - friend class cSslHTTPServerConnection; - friend class cHTTPServerListenCallbacks; - - /** The cNetwork API handle for the listening socket. */ - cServerHandlePtrs m_ServerHandles; - - /** The callbacks to call for various events */ - cCallbacks * m_Callbacks; - - /** The server certificate to use for the SSL connections */ - cX509CertPtr m_Cert; - - /** The private key for m_Cert. */ - cCryptoKeyPtr m_CertPrivKey; - - - /** Called by cHTTPServerListenCallbacks when there's a new incoming connection. - Returns the connection instance to be used as the cTCPLink callbacks. */ - cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); - - /** Called by cHTTPServerConnection when it finishes parsing the request header */ - void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); - - /** Called by cHTTPConenction when it receives more data for the request body. - May be called multiple times for a single request. */ - void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size); - - /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ - void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); -} ; - - - - - diff --git a/src/HTTPServer/HTTPServerConnection.cpp b/src/HTTPServer/HTTPServerConnection.cpp deleted file mode 100644 index 0ee9f3ce7..000000000 --- a/src/HTTPServer/HTTPServerConnection.cpp +++ /dev/null @@ -1,278 +0,0 @@ - -// HTTPConnection.cpp - -// Implements the cHTTPServerConnection class representing a single persistent connection in the HTTP server. - -#include "Globals.h" -#include "HTTPServerConnection.h" -#include "HTTPRequestParser.h" -#include "HTTPServer.h" - - - - - -cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : - m_HTTPServer(a_HTTPServer), - m_State(wcsRecvHeaders), - m_CurrentRequest(nullptr), - m_CurrentRequestBodyRemaining(0) -{ - // LOGD("HTTP: New connection at %p", this); -} - - - - - -cHTTPServerConnection::~cHTTPServerConnection() -{ - // LOGD("HTTP: Connection deleting: %p", this); - delete m_CurrentRequest; - m_CurrentRequest = nullptr; -} - - - - - -void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) -{ - SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); - SendData(Printf("Content-Length: %u\r\n\r\n", static_cast(a_Response.size()))); - SendData(a_Response.data(), a_Response.size()); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) -{ - SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) -{ - ASSERT(m_State == wcsRecvIdle); - AString toSend; - a_Response.AppendToData(toSend); - m_State = wcsSendingResp; - SendData(toSend); -} - - - - - -void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) -{ - ASSERT(m_State == wcsSendingResp); - // We're sending in Chunked transfer encoding - SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); - SendData(a_Data, a_Size); - SendData("\r\n"); -} - - - - - -void cHTTPServerConnection::FinishResponse(void) -{ - ASSERT(m_State == wcsSendingResp); - SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPServerConnection::AwaitNextRequest(void) -{ - switch (m_State) - { - case wcsRecvHeaders: - { - // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth()) - break; - } - - case wcsRecvIdle: - { - // The client is waiting for a response, send an "Internal server error": - SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - case wcsSendingResp: - { - // The response headers have been sent, we need to terminate the response body: - SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - default: - { - ASSERT(!"Unhandled state recovery"); - break; - } - } -} - - - - - -void cHTTPServerConnection::Terminate(void) -{ - if (m_CurrentRequest != nullptr) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_Link.reset(); -} - - - - - -void cHTTPServerConnection::OnLinkCreated(cTCPLinkPtr a_Link) -{ - ASSERT(m_Link == nullptr); - m_Link = a_Link; -} - - - - - -void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) -{ - ASSERT(m_Link != nullptr); - - switch (m_State) - { - case wcsRecvHeaders: - { - if (m_CurrentRequest == nullptr) - { - m_CurrentRequest = new cHTTPRequestParser; - } - - size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); - if (BytesConsumed == AString::npos) - { - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - if (m_CurrentRequest->IsInHeaders()) - { - // The request headers are not yet complete - return; - } - - // The request has finished parsing its headers successfully, notify of it: - m_State = wcsRecvBody; - m_HTTPServer.NewRequest(*this, *m_CurrentRequest); - m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); - if (m_CurrentRequestBodyRemaining == AString::npos) - { - // The body length was not specified in the request, assume zero - m_CurrentRequestBodyRemaining = 0; - } - - // Process the rest of the incoming data into the request body: - if (a_Size > BytesConsumed) - { - cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); - return; - } - else - { - cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away - return; - } - } - - case wcsRecvBody: - { - ASSERT(m_CurrentRequest != nullptr); - if (m_CurrentRequestBodyRemaining > 0) - { - size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast(a_Size)); - m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); - m_CurrentRequestBodyRemaining -= BytesToConsume; - } - if (m_CurrentRequestBodyRemaining == 0) - { - m_State = wcsRecvIdle; - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - if (!m_CurrentRequest->DoesAllowKeepAlive()) - { - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - } - break; - } - - default: - { - // TODO: Should we be receiving data in this state? - break; - } - } -} - - - - - -void cHTTPServerConnection::OnRemoteClosed(void) -{ - if (m_CurrentRequest != nullptr) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_Link.reset(); -} - - - - - - -void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) -{ - OnRemoteClosed(); -} - - - - -void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) -{ - m_Link->Send(a_Data, a_Size); -} - - - - diff --git a/src/HTTPServer/HTTPServerConnection.h b/src/HTTPServer/HTTPServerConnection.h deleted file mode 100644 index af2abb73f..000000000 --- a/src/HTTPServer/HTTPServerConnection.h +++ /dev/null @@ -1,124 +0,0 @@ - -// HTTPConnection.h - -// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. - - - - - -#pragma once - -#include "../OSSupport/Network.h" - - - - - -// fwd: -class cHTTPServer; -class cHTTPResponse; -class cHTTPRequestParser; - - - - - -class cHTTPServerConnection : - public cTCPLink::cCallbacks -{ -public: - - enum eState - { - wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr) - wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) - wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr) - wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr) - wcsInvalid, ///< The request was malformed, the connection is closing - } ; - - cHTTPServerConnection(cHTTPServer & a_HTTPServer); - virtual ~cHTTPServerConnection(); - - /** Sends HTTP status code together with a_Reason (used for HTTP errors). - Sends the a_Reason as the body as well, so that browsers display it. */ - void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); - - /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ - void SendNeedAuth(const AString & a_Realm); - - /** Sends the headers contained in a_Response */ - void Send(const cHTTPResponse & a_Response); - - /** Sends the data as the response (may be called multiple times) */ - void Send(const void * a_Data, size_t a_Size); - - /** Sends the data as the response (may be called multiple times) */ - void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } - - /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */ - void FinishResponse(void); - - /** Resets the internal connection state for a new request. - Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */ - void AwaitNextRequest(void); - - /** Terminates the connection; finishes any request being currently processed */ - void Terminate(void); - -protected: - typedef std::map cNameValueMap; - - /** The parent webserver that is to be notified of events on this connection */ - cHTTPServer & m_HTTPServer; - - /** All the incoming data until the entire request header is parsed */ - AString m_IncomingHeaderData; - - /** Status in which the request currently is */ - eState m_State; - - /** The request being currently received - Valid only between having parsed the headers and finishing receiving the body. */ - cHTTPRequestParser * m_CurrentRequest; - - /** Number of bytes that remain to read for the complete body of the message to be received. - Valid only in wcsRecvBody */ - size_t m_CurrentRequestBodyRemaining; - - /** The network link attached to this connection. */ - cTCPLinkPtr m_Link; - - - // cTCPLink::cCallbacks overrides: - /** The link instance has been created, remember it. */ - virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; - - /** Data is received from the client. */ - virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; - - /** The socket has been closed for any reason. */ - virtual void OnRemoteClosed(void) override; - - /** An error has occurred on the socket. */ - virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; - - // Overridable: - /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ - virtual void SendData(const void * a_Data, size_t a_Size); - - /** Sends the raw data over the link. - Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */ - void SendData(const AString & a_Data) - { - SendData(a_Data.data(), a_Data.size()); - } -} ; - -typedef std::vector cHTTPServerConnections; - - - - - diff --git a/src/HTTPServer/MultipartParser.cpp b/src/HTTPServer/MultipartParser.cpp deleted file mode 100644 index 09f4fd02a..000000000 --- a/src/HTTPServer/MultipartParser.cpp +++ /dev/null @@ -1,254 +0,0 @@ - -// MultipartParser.cpp - -// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts - -#include "Globals.h" -#include "MultipartParser.h" -#include "NameValueParser.h" - - - - - -// Disable MSVC warnings: -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4355) // 'this' : used in base member initializer list -#endif - - - - - -//////////////////////////////////////////////////////////////////////////////// -// self-test: - -#if 0 - -class cMultipartParserTest : - public cMultipartParser::cCallbacks -{ -public: - cMultipartParserTest(void) - { - cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this); - const char Data[] = -"ThisIsIgnoredPrologue\r\n\ ---MyBoundaryString\r\n\ -\r\n\ -Body with confusing strings\r\n\ ---NotABoundary\r\n\ ---MyBoundaryStringWithPostfix\r\n\ ---\r\n\ ---MyBoundaryString\r\n\ -content-disposition: inline\r\n\ -\r\n\ -This is body\r\n\ ---MyBoundaryString\r\n\ -\r\n\ -Headerless body with trailing CRLF\r\n\ -\r\n\ ---MyBoundaryString--\r\n\ -ThisIsIgnoredEpilogue"; - printf("Multipart parsing test commencing.\n"); - Parser.Parse(Data, sizeof(Data) - 1); - // DEBUG: Check if the onscreen output corresponds with the data above - printf("Multipart parsing test finished\n"); - } - - virtual void OnPartStart(void) override - { - printf("Starting a new part\n"); - } - - - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override - { - printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str()); - } - - - virtual void OnPartData(const char * a_Data, int a_Size) override - { - printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data); - } - - - virtual void OnPartEnd(void) override - { - printf("Part end\n"); - } -} g_Test; - -#endif - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cMultipartParser: - - -cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_IsValid(true), - m_EnvelopeParser(*this), - m_HasHadData(false) -{ - // Check that the content type is multipart: - AString ContentType(a_ContentType); - if (strncmp(ContentType.c_str(), "multipart/", 10) != 0) - { - m_IsValid = false; - return; - } - size_t idxSC = ContentType.find(';', 10); - if (idxSC == AString::npos) - { - m_IsValid = false; - return; - } - - // Find the multipart boundary: - ContentType.erase(0, idxSC + 1); - cNameValueParser CTParser(ContentType.c_str(), ContentType.size()); - CTParser.Finish(); - if (!CTParser.IsValid()) - { - m_IsValid = false; - return; - } - m_Boundary = CTParser["boundary"]; - m_IsValid = !m_Boundary.empty(); - if (!m_IsValid) - { - return; - } - - // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body - m_EnvelopeParser.SetIsInHeaders(false); - - // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught - m_IncomingData.assign("\r\n"); - - /* - m_Boundary = AString("\r\n--") + m_Boundary - m_BoundaryEnd = m_Boundary + "--\r\n"; - m_Boundary = m_Boundary + "\r\n"; - */ -} - - - - - -void cMultipartParser::Parse(const char * a_Data, size_t a_Size) -{ - // Skip parsing if invalid - if (!m_IsValid) - { - return; - } - - // Append to buffer, then parse it: - m_IncomingData.append(a_Data, a_Size); - for (;;) - { - if (m_EnvelopeParser.IsInHeaders()) - { - size_t BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size()); - if (BytesConsumed == AString::npos) - { - m_IsValid = false; - return; - } - if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders()) - { - // All the incoming data has been consumed and still waiting for more - return; - } - m_IncomingData.erase(0, BytesConsumed); - } - - // Search for boundary / boundary end: - size_t idxBoundary = m_IncomingData.find("\r\n--"); - if (idxBoundary == AString::npos) - { - // Boundary string start not present, present as much data to the part callback as possible - if (m_IncomingData.size() > m_Boundary.size() + 8) - { - size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; - m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); - m_IncomingData.erase(0, BytesToReport); - } - return; - } - if (idxBoundary > 0) - { - m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary); - m_IncomingData.erase(0, idxBoundary); - } - idxBoundary = 4; - size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary); - if (LineEnd == AString::npos) - { - // Not a complete line yet, present as much data to the part callback as possible - if (m_IncomingData.size() > m_Boundary.size() + 8) - { - size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; - m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); - m_IncomingData.erase(0, BytesToReport); - } - return; - } - if ( - (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary - (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end - ) - { - // Got a line, but it's not a boundary, report it as data: - m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd); - m_IncomingData.erase(0, LineEnd); - continue; - } - - if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0) - { - // Boundary or BoundaryEnd found: - m_Callbacks.OnPartEnd(); - size_t idxSlash = idxBoundary + m_Boundary.size(); - if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-')) - { - // This was the last part - m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4); - m_IncomingData.clear(); - return; - } - m_Callbacks.OnPartStart(); - m_IncomingData.erase(0, LineEnd + 2); - - // Keep parsing for the headers that may have come with this data: - m_EnvelopeParser.Reset(); - continue; - } - - // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines - m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd); - m_IncomingData.erase(0, LineEnd); - } // while (true) -} - - - - - -void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - m_Callbacks.OnPartHeader(a_Key, a_Value); -} - - - - diff --git a/src/HTTPServer/MultipartParser.h b/src/HTTPServer/MultipartParser.h deleted file mode 100644 index 4f20b2bed..000000000 --- a/src/HTTPServer/MultipartParser.h +++ /dev/null @@ -1,79 +0,0 @@ - -// MultipartParser.h - -// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts - - - - - -#pragma once - -#include "EnvelopeParser.h" - - - - - -class cMultipartParser : - protected cEnvelopeParser::cCallbacks -{ -public: - class cCallbacks - { - public: - // Force a virtual destructor in descendants: - virtual ~cCallbacks() {} - - /** Called when a new part starts */ - virtual void OnPartStart(void) = 0; - - /** Called when a complete header line is received for a part */ - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0; - - /** Called when body for a part is received */ - virtual void OnPartData(const char * a_Data, size_t a_Size) = 0; - - /** Called when the current part ends */ - virtual void OnPartEnd(void) = 0; - } ; - - /** Creates the parser, expects to find the boundary in a_ContentType */ - cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks); - - /** Parses more incoming data */ - void Parse(const char * a_Data, size_t a_Size); - -protected: - /** The callbacks to call for various parsing events */ - cCallbacks & m_Callbacks; - - /** True if the data parsed so far is valid; if false, further parsing is skipped */ - bool m_IsValid; - - /** Parser for each part's envelope */ - cEnvelopeParser m_EnvelopeParser; - - /** Buffer for the incoming data until it is parsed */ - AString m_IncomingData; - - /** The boundary, excluding both the initial "--" and the terminating CRLF */ - AString m_Boundary; - - /** Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting. */ - bool m_HasHadData; - - - /** Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size */ - void ParseLine(const char * a_Data, size_t a_Size); - - /** Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size */ - void ParseHeaderLine(const char * a_Data, size_t a_Size); - - // cEnvelopeParser overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; -} ; - - - - diff --git a/src/HTTPServer/NameValueParser.cpp b/src/HTTPServer/NameValueParser.cpp deleted file mode 100644 index f759c4d21..000000000 --- a/src/HTTPServer/NameValueParser.cpp +++ /dev/null @@ -1,413 +0,0 @@ - -// NameValueParser.cpp - -// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap - -#include "Globals.h" -#include "NameValueParser.h" - - - - - - -// DEBUG: Self-test - -#if 0 - -class cNameValueParserTest -{ -public: - cNameValueParserTest(void) - { - const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\""; - - // Now try parsing char-by-char, to debug transitions across datachunk boundaries: - cNameValueParser Parser2; - for (size_t i = 0; i < sizeof(Data) - 1; i++) - { - Parser2.Parse(Data + i, 1); - } - Parser2.Finish(); - - // Parse as a single chunk of data: - cNameValueParser Parser(Data, sizeof(Data) - 1); - - // Use the debugger to inspect the Parser variable - - // Check that the two parsers have the same content: - for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) - { - ASSERT(Parser2[itr->first] == itr->second); - } // for itr - Parser[] - - // Try parsing in 2-char chunks: - cNameValueParser Parser3; - for (int i = 0; i < sizeof(Data) - 2; i += 2) - { - Parser3.Parse(Data + i, 2); - } - if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char - { - Parser3.Parse(Data + sizeof(Data) - 2, 1); - } - Parser3.Finish(); - - // Check that the third parser has the same content: - for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) - { - ASSERT(Parser3[itr->first] == itr->second); - } // for itr - Parser[] - - printf("cNameValueParserTest done"); - } -} g_Test; - -#endif - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cNameValueParser: - -cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) : - m_State(psKeySpace), - m_AllowsKeyOnly(a_AllowsKeyOnly) -{ -} - - - - - -cNameValueParser::cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly) : - m_State(psKeySpace), - m_AllowsKeyOnly(a_AllowsKeyOnly) -{ - Parse(a_Data, a_Size); -} - - - - - -void cNameValueParser::Parse(const char * a_Data, size_t a_Size) -{ - ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong! - - size_t Last = 0; - for (size_t i = 0; i < a_Size;) - { - switch (m_State) - { - case psInvalid: - case psFinished: - { - return; - } - - case psKeySpace: - { - // Skip whitespace until a non-whitespace is found, then start the key: - while ((i < a_Size) && (a_Data[i] <= ' ')) - { - i++; - } - if ((i < a_Size) && (a_Data[i] > ' ')) - { - m_State = psKey; - Last = i; - } - break; - } - - case psKey: - { - // Read the key until whitespace or an equal sign: - while (i < a_Size) - { - if (a_Data[i] == '=') - { - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - m_State = psEqual; - break; - } - else if (a_Data[i] <= ' ') - { - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - m_State = psEqualSpace; - break; - } - else if (a_Data[i] == ';') - { - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if ((a_Data[i] == '\"') || (a_Data[i] == '\'')) - { - m_State = psInvalid; - return; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - // Still the key, ran out of data to parse, store the part of the key parsed so far: - m_CurrentKey.append(a_Data + Last, a_Size - Last); - return; - } - break; - } - - case psEqualSpace: - { - // The space before the expected equal sign; the current key is already assigned - while (i < a_Size) - { - if (a_Data[i] == '=') - { - m_State = psEqual; - i++; - Last = i; - break; - } - else if (a_Data[i] == ';') - { - // Key-only - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if (a_Data[i] > ' ') - { - m_State = psInvalid; - return; - } - i++; - } // while (i < a_Size) - break; - } // case psEqualSpace - - case psEqual: - { - // just parsed the equal-sign - while (i < a_Size) - { - if (a_Data[i] == ';') - { - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if (a_Data[i] == '\"') - { - i++; - Last = i; - m_State = psValueInDQuotes; - break; - } - else if (a_Data[i] == '\'') - { - i++; - Last = i; - m_State = psValueInSQuotes; - break; - } - else - { - m_CurrentValue.push_back(a_Data[i]); - i++; - Last = i; - m_State = psValueRaw; - break; - } - } // while (i < a_Size) - break; - } // case psEqual - - case psValueInDQuotes: - { - while (i < a_Size) - { - if (a_Data[i] == '\"') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psAfterValue; - i++; - Last = i; - break; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueInDQuotes - - case psValueInSQuotes: - { - while (i < a_Size) - { - if (a_Data[i] == '\'') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psAfterValue; - i++; - Last = i; - break; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueInSQuotes - - case psValueRaw: - { - while (i < a_Size) - { - if (a_Data[i] == ';') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psKeySpace; - i++; - Last = i; - break; - } - i++; - } - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueRaw - - case psAfterValue: - { - // Between the closing DQuote or SQuote and the terminating semicolon - while (i < a_Size) - { - if (a_Data[i] == ';') - { - m_State = psKeySpace; - i++; - Last = i; - break; - } - else if (a_Data[i] < ' ') - { - i++; - continue; - } - m_State = psInvalid; - return; - } // while (i < a_Size) - break; - } - } // switch (m_State) - } // for i - a_Data[] -} - - - - - -bool cNameValueParser::Finish(void) -{ - switch (m_State) - { - case psInvalid: - { - return false; - } - case psFinished: - { - return true; - } - case psKey: - case psEqualSpace: - case psEqual: - { - if ((m_AllowsKeyOnly) && !m_CurrentKey.empty()) - { - (*this)[m_CurrentKey] = ""; - m_State = psFinished; - return true; - } - m_State = psInvalid; - return false; - } - case psValueRaw: - { - (*this)[m_CurrentKey] = m_CurrentValue; - m_State = psFinished; - return true; - } - case psValueInDQuotes: - case psValueInSQuotes: - { - // Missing the terminating quotes, this is an error - m_State = psInvalid; - return false; - } - case psKeySpace: - case psAfterValue: - { - m_State = psFinished; - return true; - } - } - ASSERT(!"Unhandled parser state!"); - return false; -} - - - - diff --git a/src/HTTPServer/NameValueParser.h b/src/HTTPServer/NameValueParser.h deleted file mode 100644 index e205079db..000000000 --- a/src/HTTPServer/NameValueParser.h +++ /dev/null @@ -1,70 +0,0 @@ - -// NameValueParser.h - -// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap - - - - - -#pragma once - - - - - -class cNameValueParser : - public std::map -{ -public: - /** Creates an empty parser */ - cNameValueParser(bool a_AllowsKeyOnly = true); - - /** Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later */ - cNameValueParser(const char * a_Data, size_t a_Size, bool a_AllowsKeyOnly = true); - - /** Parses the data given */ - void Parse(const char * a_Data, size_t a_Size); - - /** Notifies the parser that no more data will be coming. Returns true if the parser state is valid */ - bool Finish(void); - - /** Returns true if the data parsed so far was valid */ - bool IsValid(void) const { return (m_State != psInvalid); } - - /** Returns true if the parser expects no more data */ - bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); } - -protected: - enum eState - { - psKeySpace, ///< Parsing the space in front of the next key - psKey, ///< Currently adding more chars to the key in m_CurrentKey - psEqualSpace, ///< Space after m_CurrentKey - psEqual, ///< Just parsed the = sign after a name - psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign - psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign - psValueRaw, ///< Just parsed a raw value without a quote - psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end - psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped - psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data - } ; - - /** The current state of the parser */ - eState m_State; - - /** If true, the parser will accept keys without an equal sign and the value */ - bool m_AllowsKeyOnly; - - /** Buffer for the current Key */ - AString m_CurrentKey; - - /** Buffer for the current Value; */ - AString m_CurrentValue; - - -} ; - - - - diff --git a/src/HTTPServer/SslHTTPServerConnection.cpp b/src/HTTPServer/SslHTTPServerConnection.cpp deleted file mode 100644 index 547e6de3a..000000000 --- a/src/HTTPServer/SslHTTPServerConnection.cpp +++ /dev/null @@ -1,115 +0,0 @@ - -// SslHTTPConnection.cpp - -// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link - -#include "Globals.h" -#include "SslHTTPServerConnection.h" -#include "HTTPServer.h" - - - - - -cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : - super(a_HTTPServer), - m_Ssl(64000), - m_Cert(a_Cert), - m_PrivateKey(a_PrivateKey) -{ - m_Ssl.Initialize(false); - m_Ssl.SetOwnCert(a_Cert, a_PrivateKey); -} - - - - - -cSslHTTPServerConnection::~cSslHTTPServerConnection() -{ - m_Ssl.NotifyClose(); -} - - - - - -void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) -{ - // Process the received data: - const char * Data = a_Data; - size_t Size = a_Size; - for (;;) - { - // Try to write as many bytes into Ssl's "incoming" buffer as possible: - size_t BytesWritten = 0; - if (Size > 0) - { - BytesWritten = m_Ssl.WriteIncoming(Data, Size); - Data += BytesWritten; - Size -= BytesWritten; - } - - // Try to read as many bytes from SSL's decryption as possible: - char Buffer[32000]; - int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer)); - if (NumRead > 0) - { - super::OnReceivedData(Buffer, static_cast(NumRead)); - // The link may have closed while processing the data, bail out: - return; - } - else if (NumRead == POLARSSL_ERR_NET_WANT_READ) - { - // SSL requires us to send data to peer first, do so by "sending" empty data: - SendData(nullptr, 0); - } - - // If both failed, bail out: - if ((BytesWritten == 0) && (NumRead <= 0)) - { - return; - } - } -} - - - - - -void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) -{ - const char * OutgoingData = reinterpret_cast(a_Data); - size_t pos = 0; - for (;;) - { - // Write as many bytes from our buffer to SSL's encryption as possible: - int NumWritten = 0; - if (pos < a_Size) - { - NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos); - if (NumWritten > 0) - { - pos += static_cast(NumWritten); - } - } - - // Read as many bytes from SSL's "outgoing" buffer as possible: - char Buffer[32000]; - size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer)); - if (NumBytes > 0) - { - m_Link->Send(Buffer, NumBytes); - } - - // If both failed, bail out: - if ((NumWritten <= 0) && (NumBytes == 0)) - { - return; - } - } -} - - - - diff --git a/src/HTTPServer/SslHTTPServerConnection.h b/src/HTTPServer/SslHTTPServerConnection.h deleted file mode 100644 index 6032a2bd0..000000000 --- a/src/HTTPServer/SslHTTPServerConnection.h +++ /dev/null @@ -1,47 +0,0 @@ - -// SslHTTPServerConnection.h - -// Declared the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link - - - - - -#pragma once - -#include "HTTPServerConnection.h" -#include "PolarSSL++/BufferedSslContext.h" - - - - - -class cSslHTTPServerConnection : - public cHTTPServerConnection -{ - typedef cHTTPServerConnection super; - -public: - /** Creates a new connection on the specified server. - Sends the specified cert as the server certificate, uses the private key for decryption. */ - cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); - - ~cSslHTTPServerConnection(); - -protected: - cBufferedSslContext m_Ssl; - - /** The certificate to send to the client */ - cX509CertPtr m_Cert; - - /** The private key used for the certificate */ - cCryptoKeyPtr m_PrivateKey; - - // cHTTPConnection overrides: - virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client - virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client -} ; - - - - diff --git a/src/HTTPServer/TransferEncodingParser.cpp b/src/HTTPServer/TransferEncodingParser.cpp deleted file mode 100644 index 8b703fd42..000000000 --- a/src/HTTPServer/TransferEncodingParser.cpp +++ /dev/null @@ -1,132 +0,0 @@ - -// TransferEncodingParser.cpp - -// Implements the cTransferEncodingParser class and its descendants representing the parser for the various transfer encodings (chunked etc.) - -#include "Globals.h" -#include "TransferEncodingParser.h" - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cChunkedTEParser: - -class cChunkedTEParser: - public cTransferEncodingParser -{ - typedef cTransferEncodingParser Super; - -public: - cChunkedTEParser(cCallbacks & a_Callbacks): - Super(a_Callbacks), - m_IsFinished(false) - { - } - - -protected: - - /** True if the datastream has finished (zero-length chunk received). */ - bool m_IsFinished; - - - // cTransferEncodingParser overrides: - virtual size_t Parse(const char * a_Data, size_t a_Size) override - { - // TODO - m_Callbacks.OnError("cChunkedTEParser not implemented yet"); - return AString::npos; - } - - virtual void Finish(void) override - { - if (!m_IsFinished) - { - m_Callbacks.OnError("ChunkedTransferEncoding: Finish signal received before the data stream ended"); - } - m_IsFinished = true; - } -}; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cIdentityTEParser: - -class cIdentityTEParser: - public cTransferEncodingParser -{ - typedef cTransferEncodingParser Super; - -public: - cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength): - Super(a_Callbacks), - m_BytesLeft(a_ContentLength) - { - } - - -protected: - /** How many bytes of content are left before the message ends. */ - size_t m_BytesLeft; - - // cTransferEncodingParser overrides: - virtual size_t Parse(const char * a_Data, size_t a_Size) override - { - auto size = std::min(a_Size, m_BytesLeft); - if (size > 0) - { - m_Callbacks.OnBodyData(a_Data, size); - } - m_BytesLeft -= size; - if (m_BytesLeft == 0) - { - m_Callbacks.OnBodyFinished(); - } - return a_Size - size; - } - - virtual void Finish(void) override - { - if (m_BytesLeft > 0) - { - m_Callbacks.OnError("IdentityTransferEncoding: body was truncated"); - } - else - { - // BodyFinished has already been called, just bail out - } - } -}; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cTransferEncodingParser: - -cTransferEncodingParserPtr cTransferEncodingParser::Create( - cCallbacks & a_Callbacks, - const AString & a_TransferEncoding, - size_t a_ContentLength -) -{ - if (a_TransferEncoding == "chunked") - { - return std::make_shared(a_Callbacks); - } - if (a_TransferEncoding.empty()) - { - return std::make_shared(a_Callbacks, a_ContentLength); - } - return nullptr; -} - - - - diff --git a/src/HTTPServer/TransferEncodingParser.h b/src/HTTPServer/TransferEncodingParser.h deleted file mode 100644 index ce3d01df7..000000000 --- a/src/HTTPServer/TransferEncodingParser.h +++ /dev/null @@ -1,76 +0,0 @@ - -// TransferEncodingParser.h - -// Declares the cTransferEncodingParser class representing the parser for the various transfer encodings (chunked etc.) - -#pragma once - - - - - -// fwd: -class cTransferEncodingParser; -typedef SharedPtr cTransferEncodingParserPtr; - - - - - -/** Used as both the interface that all the parsers share and the (static) factory creating such parsers. */ -class cTransferEncodingParser -{ -public: - class cCallbacks - { - public: - // Force a virtual destructor in descendants - virtual ~cCallbacks() {} - - /** Called when an error has occured while parsing. */ - virtual void OnError(const AString & a_ErrorDescription) = 0; - - /** Called for each chunk of the incoming body data. */ - virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; - - /** Called when the entire body has been reported by OnBodyData(). */ - virtual void OnBodyFinished(void) = 0; - }; - - - // Force a virtual destructor in all descendants - virtual ~cTransferEncodingParser() {} - - /** Parses the incoming data and calls the appropriate callbacks. - Returns the number of bytes from the end of a_Data that is already not part of this message (if the parser can detect it). - Returns AString::npos on an error. */ - virtual size_t Parse(const char * a_Data, size_t a_Size) = 0; - - /** To be called when the stream is terminated from the source (connection closed). - Flushes any buffers and calls appropriate callbacks. */ - virtual void Finish(void) = 0; - - /** Creates a new parser for the specified encoding. - If the encoding is not known, returns a nullptr. - a_ContentLength is the length of the content, received in a Content-Length header. - It is used for the Identity encoding, it is ignored for the Chunked encoding. */ - static cTransferEncodingParserPtr Create( - cCallbacks & a_Callbacks, - const AString & a_TransferEncoding, - size_t a_ContentLength - ); - -protected: - /** The callbacks used to report progress. */ - cCallbacks & m_Callbacks; - - - cTransferEncodingParser(cCallbacks & a_Callbacks): - m_Callbacks(a_Callbacks) - { - } -}; - - - - diff --git a/src/HTTPServer/UrlParser.cpp b/src/HTTPServer/UrlParser.cpp deleted file mode 100644 index 05db3e413..000000000 --- a/src/HTTPServer/UrlParser.cpp +++ /dev/null @@ -1,200 +0,0 @@ - -// UrlParser.cpp - -// Implements the cUrlParser class that parses string URL into individual parts - -#include "Globals.h" -#include "UrlParser.h" - - - - - -UInt16 cUrlParser::GetDefaultPort(const AString & a_Scheme) -{ - if (a_Scheme == "http") - { - return 80; - } - else if (a_Scheme == "https") - { - return 443; - } - else if (a_Scheme == "ftp") - { - return 21; - } - else if (a_Scheme == "mailto") - { - return 25; - } - return 0; -} - - - - - -std::pair cUrlParser::ParseAuthorityPart( - const AString & a_AuthorityPart, - AString & a_Username, - AString & a_Password, - AString & a_Host, - UInt16 & a_Port -) -{ - /* - a_AuthorityPart format: - [user:password@]host[:port] - host can be an IPv4, hostname, or an IPv6 enclosed in brackets - Assume only the password can contain an additional at-sign - */ - - // Split the authority on the last at-sign, if present: - auto idxLastAtSign = a_AuthorityPart.find_last_of('@'); - auto credPart = (idxLastAtSign == AString::npos) ? AString() : a_AuthorityPart.substr(0, idxLastAtSign); - auto srvrPart = (idxLastAtSign == AString::npos) ? a_AuthorityPart : a_AuthorityPart.substr(idxLastAtSign + 1); - - // User credentials are completely optional: - auto idxCredColon = credPart.find(':'); - a_Username = credPart.substr(0, idxCredColon); - a_Password = (idxCredColon == AString::npos) ? AString() : credPart.substr(idxCredColon + 1); - - // Host can be a hostname, IPv4 or [IPv6]. If in brackets, search for the closing bracket first - if (srvrPart.empty()) - { - // No host information at all. Bail out with success - a_Host.clear(); - return std::make_pair(true, AString()); - } - if (srvrPart[0] == '[') - { - // [IPv6] host, search for the closing bracket - auto idxClosingBracket = srvrPart.find(']'); - if (idxClosingBracket == AString::npos) - { - return std::make_pair(false, "Invalid IPv6-like address, missing closing bracket"); - } - a_Host = srvrPart.substr(0, idxClosingBracket); - auto portPart = srvrPart.substr(idxClosingBracket + 1); - if (portPart.empty()) - { - // No port was specified, return success - return std::make_pair(true, AString()); - } - if (portPart[0] != ':') - { - return std::make_pair(false, "Invalid port format after IPv6 address, mising colon"); - } - if (!StringToInteger(portPart.substr(2), a_Port)) - { - return std::make_pair(false, "Failed to parse port number after IPv6 address"); - } - return std::make_pair(true, AString()); - } - - // Not an [IPv6] address, split on the last colon: - auto idxLastColon = srvrPart.find_last_of(':'); - a_Host = srvrPart.substr(0, idxLastColon); - if (idxLastColon == AString::npos) - { - // No port was specified, return success - return std::make_pair(true, AString()); - } - auto portPart = srvrPart.substr(idxLastColon + 1); - if (!StringToInteger(portPart, a_Port)) - { - return std::make_pair(false, "Failed to parse port number after hostname"); - } - return std::make_pair(true, AString()); -} - - - - - -std::pair cUrlParser::Parse( - const AString & a_Url, - AString & a_Scheme, - AString & a_Username, - AString & a_Password, - AString & a_Host, - UInt16 & a_Port, - AString & a_Path, - AString & a_Query, - AString & a_Fragment -) -{ - // Find the scheme - the text before the first colon: - auto idxColon = a_Url.find(':'); - if (idxColon == AString::npos) - { - return std::make_pair(false, "Cannot parse the Scheme part of the URL"); - } - a_Scheme = StrToLower(a_Url.substr(0, idxColon)); - a_Port = GetDefaultPort(a_Scheme); - if (a_Port == 0) - { - return std::make_pair(false, Printf("Unknown URL scheme: \"%s\"", a_Scheme.c_str())); - } - - // If the next two chars are a double-slash, skip them: - auto authStart = idxColon + 1; - if (a_Url.substr(authStart, 2) == "//") - { - authStart += 2; - } - - // The Authority part follows the Scheme, until the first slash: - auto idxFirstSlash = a_Url.find('/', authStart + 1); - if (idxFirstSlash == AString::npos) - { - // No slash, the whole end of the Url is the authority part - idxFirstSlash = a_Url.size(); - } - - // Parse the Authority part into individual components: - auto res = ParseAuthorityPart( - a_Url.substr(authStart, idxFirstSlash - authStart), - a_Username, a_Password, - a_Host, a_Port - ); - if (!res.first) - { - return res; - } - - // Parse the rest into a path, query and fragment: - a_Path.clear(); - a_Query.clear(); - a_Fragment.clear(); - if (idxFirstSlash == a_Url.size()) - { - // No additional data, bail out with success - return std::make_pair(true, AString()); - } - auto idxPathEnd = a_Url.find_first_of("?#", idxFirstSlash + 1); - if (idxPathEnd == AString::npos) - { - a_Path = a_Url.substr(idxFirstSlash); - return std::make_pair(true, AString()); - } - a_Path = a_Url.substr(idxFirstSlash, idxPathEnd - idxFirstSlash); - auto idxHash = a_Url.find('#', idxPathEnd); - if (idxHash == AString::npos) - { - a_Query = a_Url.substr(idxPathEnd + 1); - return std::make_pair(true, AString()); - } - if (idxHash > idxPathEnd) - { - a_Query = a_Url.substr(idxPathEnd + 1, idxHash - idxPathEnd - 1); - } - a_Fragment = a_Url.substr(idxHash + 1); - return std::make_pair(true, AString()); -} - - - - - diff --git a/src/HTTPServer/UrlParser.h b/src/HTTPServer/UrlParser.h deleted file mode 100644 index 15a63e05d..000000000 --- a/src/HTTPServer/UrlParser.h +++ /dev/null @@ -1,58 +0,0 @@ - -// UrlParser.h - -// Declares the cUrlParser class that parses string URL into individual parts - - - - - -#pragma once - - - - - -class cUrlParser -{ -public: - /** Returns true if the specified scheme (http, ftp, mailto, ...) is recognized by the URL parser. - Is case sensitive, known schemes are always lowercase. */ - static bool IsKnownScheme(const AString & a_Scheme) { return (GetDefaultPort(a_Scheme) > 0); } - - /** Returns the default port used by the specified scheme / protocol. - If the scheme is not known, 0 is returned. */ - static UInt16 GetDefaultPort(const AString & a_Scheme); - - /** Parses the given Authority part of an URL into individual components. - Returns true on success, - returns false and error message on failure. */ - static std::pair ParseAuthorityPart( - const AString & a_AuthorityPart, - AString & a_Username, - AString & a_Password, - AString & a_Host, - UInt16 & a_Port - ); - - /** Parses the given URL into individual components. - Returns true on success, - returns false and error message on failure. - Fails if the scheme (protocol) is not known. - If port is missing, the default port for the specific scheme is applied. */ - static std::pair Parse( - const AString & a_Url, - AString & a_Scheme, - AString & a_Username, - AString & a_Password, - AString & a_Host, - UInt16 & a_Port, - AString & a_Path, - AString & a_Query, - AString & a_Fragment - ); -}; - - - - diff --git a/src/Root.h b/src/Root.h index 24c8216d9..a828dc3fa 100644 --- a/src/Root.h +++ b/src/Root.h @@ -3,7 +3,7 @@ #include "Protocol/Authenticator.h" #include "Protocol/MojangAPI.h" -#include "HTTPServer/HTTPServer.h" +#include "HTTP/HTTPServer.h" #include "Defines.h" #include "RankManager.h" #include diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index f63e8b3d9..85ee981c7 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -12,8 +12,8 @@ #include "Server.h" #include "Root.h" -#include "HTTPServer/HTTPRequestParser.h" -#include "HTTPServer/HTTPServerConnection.h" +#include "HTTP/HTTPRequestParser.h" +#include "HTTP/HTTPServerConnection.h" diff --git a/src/WebAdmin.h b/src/WebAdmin.h index 70d772f1e..29acb2664 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -7,8 +7,8 @@ #include "Bindings/LuaState.h" #include "IniFile.h" -#include "HTTPServer/HTTPServer.h" -#include "HTTPServer/HTTPFormParser.h" +#include "HTTP/HTTPServer.h" +#include "HTTP/HTTPFormParser.h" -- cgit v1.2.3 From 1d05fc95aeb67876452c9e25d67b18fc18d1765d Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 3 Jan 2016 16:07:53 +0100 Subject: Added unit test for parsing HTTP request. --- .gitattributes | 4 + CMakeLists.txt | 2 + tests/CMakeLists.txt | 1 + tests/HTTP/CMakeLists.txt | 46 ++++++++++ tests/HTTP/HTTPResponse1.data | 9 ++ tests/HTTP/HTTPResponse2.data | 15 ++++ tests/HTTP/HTTPResponseParser_file.cpp | 153 +++++++++++++++++++++++++++++++++ 7 files changed, 230 insertions(+) create mode 100644 .gitattributes create mode 100644 tests/HTTP/CMakeLists.txt create mode 100644 tests/HTTP/HTTPResponse1.data create mode 100644 tests/HTTP/HTTPResponse2.data create mode 100644 tests/HTTP/HTTPResponseParser_file.cpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6470a6393 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Set the *.data files to be checked out as binary files. +# Used for the HTTP test data files, they need to have the CRLF line endings +# even on Linux, because they're HTTP protocol dumps. +*.data binary diff --git a/CMakeLists.txt b/CMakeLists.txt index 3234e93d7..f84a81062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,6 +263,7 @@ if (MSVC) if (${SELF_TEST}) set_target_properties( Network + HTTP PROPERTIES FOLDER Lib ) set_target_properties( @@ -274,6 +275,7 @@ if (MSVC) creatable-exe EchoServer Google-exe + HTTPResponseParser_file-exe LoadablePieces NameLookup PROPERTIES FOLDER Tests diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bdab4bc58..63eb8ae3a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,5 +9,6 @@ endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(ChunkData) +add_subdirectory(HTTP) add_subdirectory(Network) add_subdirectory(LoadablePieces) diff --git a/tests/HTTP/CMakeLists.txt b/tests/HTTP/CMakeLists.txt new file mode 100644 index 000000000..f6989d831 --- /dev/null +++ b/tests/HTTP/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required (VERSION 2.6) + +enable_testing() + +include_directories(${CMAKE_SOURCE_DIR}/src/) +include_directories(${CMAKE_SOURCE_DIR}/lib/libevent/include) + +add_definitions(-DTEST_GLOBALS=1) + +# Create a single HTTP library that contains all the HTTP code: +set (HTTP_SRCS + ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.cpp + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.cpp + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPResponseParser.cpp + ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.cpp + ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp +) + +set (HTTP_HDRS + ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.h + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.h + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPResponseParser.h + ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.h + ${CMAKE_SOURCE_DIR}/src/StringUtils.h +) + +add_library(HTTP + ${HTTP_SRCS} + ${HTTP_HDRS} +) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_flags_cxx("-Wno-error=conversion -Wno-error=old-style-cast") +endif() + + + + + +# Define individual tests: + +# HTTPResponseParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called: +add_executable(HTTPResponseParser_file-exe HTTPResponseParser_file.cpp) +target_link_libraries(HTTPResponseParser_file-exe HTTP) +add_test(NAME HTTPResponseParser_file-test1 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data) +add_test(NAME HTTPResponseParser_file-test2 COMMAND HTTPResponseParser_file-exe HTTPResponse2.data) diff --git a/tests/HTTP/HTTPResponse1.data b/tests/HTTP/HTTPResponse1.data new file mode 100644 index 000000000..b97e58afd --- /dev/null +++ b/tests/HTTP/HTTPResponse1.data @@ -0,0 +1,9 @@ +HTTP/1.0 200 OK +Note: This is a test of a regular response with Content-Length set + (identity transfer encoding) +Note2: The above header also tests multi-line header lines +Header1: Value1 +Header2: Value2 +Content-Length: 3 + +bla diff --git a/tests/HTTP/HTTPResponse2.data b/tests/HTTP/HTTPResponse2.data new file mode 100644 index 000000000..d708c4758 --- /dev/null +++ b/tests/HTTP/HTTPResponse2.data @@ -0,0 +1,15 @@ +HTTP/1.0 200 OK +Note: This is a Chunked transfer encoding test +Header2: Value2 +Transfer-Encoding: chunked + +4 +Wiki +5 +pedia +e + in + +chunks. +0 + diff --git a/tests/HTTP/HTTPResponseParser_file.cpp b/tests/HTTP/HTTPResponseParser_file.cpp new file mode 100644 index 000000000..48ff928bc --- /dev/null +++ b/tests/HTTP/HTTPResponseParser_file.cpp @@ -0,0 +1,153 @@ + +// HTTPResponseParser_file.cpp + +// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks + +#include "Globals.h" +#include "HTTP/HTTPResponseParser.h" + + + + + +/** Maximum size of the input buffer, through which the file is read */ +static const size_t MAX_BUF = 4096; + + + + + +class cCallbacks: + public cHTTPResponseParser::cCallbacks +{ + typedef cHTTPResponseParser::cCallbacks Super; +public: + cCallbacks(void) + { + printf("cCallbacks created\n"); + } + + // cHTTPResponseParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override + { + printf("Error: \"%s\"\n", a_ErrorDescription.c_str()); + } + + /** Called when the status line is fully parsed. */ + virtual void OnStatusLine(const AString & a_StatusLine) override + { + printf("Status line: \"%s\"\n", a_StatusLine.c_str()); + } + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override + { + printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str()); + } + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) override + { + printf("Headers finished\n"); + } + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) override + { + AString hexDump; + CreateHexDump(hexDump, a_Data, a_Size, 16); + printf("Body data: %u bytes\n%s", static_cast(a_Size), hexDump.c_str()); + } + + virtual void OnBodyFinished(void) override + { + printf("Body finished\n"); + } +}; + + + + + +int main(int argc, char * argv[]) +{ + printf("HTTPResponseParser_file beginning\n"); + + // Open the input file: + if (argc <= 1) + { + printf("Usage: %s []\n", argv[0]); + return 1; + } + FILE * f; + if (strcmp(argv[1], "-") == 0) + { + f = stdin; + } + else + { + f = fopen(argv[1], "rb"); + if (f == nullptr) + { + printf("Cannot open file \"%s\". Aborting.\n", argv[1]); + return 2; + } + } + + // If a third param is present, use it as the buffer size + size_t bufSize = MAX_BUF; + if (argc >= 3) + { + if (!StringToInteger(argv[2], bufSize) || (bufSize == 0)) + { + bufSize = MAX_BUF; + printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast(bufSize)); + } + if (bufSize > MAX_BUF) + { + bufSize = MAX_BUF; + printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast(bufSize), static_cast(bufSize)); + } + } + + // Feed the file contents into the parser: + cCallbacks callbacks; + cHTTPResponseParser parser(callbacks); + while (!feof(f)) + { + char buf[MAX_BUF]; + auto numBytes = fread(buf, 1, bufSize, f); + if (numBytes == 0) + { + printf("Read 0 bytes from file (EOF?), terminating\n"); + break; + } + auto numLeft = parser.Parse(buf, numBytes); + if (numLeft == AString::npos) + { + printf("Parser indicates there was an error, terminating parsing.\n"); + break; + } + ASSERT(numLeft <= numBytes); + if (numLeft > 0) + { + printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast(numLeft)); + } + } + if (!parser.IsFinished()) + { + printf("Parser indicates an incomplete stream.\n"); + } + + // Close the input file: + if (f != stdin) + { + fclose(f); + } + + return 0; +} + + + + -- cgit v1.2.3 From 52c5ce6598afb919b5f6846ffb6b5ad52dcd6a31 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 20 Jan 2016 09:45:16 +0100 Subject: Fixed HTTP parsing when in insecure mode. Parsing would ignore the size of data already buffered, resulting in bad_alloc exception. Fixes #2898. --- src/HTTP/HTTPRequestParser.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/HTTP/HTTPRequestParser.cpp b/src/HTTP/HTTPRequestParser.cpp index 9c60c6053..bab463832 100644 --- a/src/HTTP/HTTPRequestParser.cpp +++ b/src/HTTP/HTTPRequestParser.cpp @@ -35,11 +35,13 @@ size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) { // The first line hasn't been processed yet size_t res = ParseRequestLine(a_Data, a_Size); + ASSERT((res == AString::npos) || (res <= a_Size)); if ((res == AString::npos) || (res == a_Size)) { return res; } size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); + ASSERT((res2 == AString::npos) || (res2 <= a_Size - res)); if (res2 == AString::npos) { m_IsValid = false; @@ -51,6 +53,7 @@ size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) if (m_EnvelopeParser.IsInHeaders()) { size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); + ASSERT((res == AString::npos) || (res <= a_Size)); if (res == AString::npos) { m_IsValid = false; @@ -83,8 +86,9 @@ AString cHTTPRequestParser::GetBareURL(void) const size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) { + auto inBufferSoFar = m_IncomingHeaderData.size(); m_IncomingHeaderData.append(a_Data, a_Size); - size_t IdxEnd = m_IncomingHeaderData.size(); + auto IdxEnd = m_IncomingHeaderData.size(); // Ignore the initial CRLFs (HTTP spec's "should") size_t LineStart = 0; @@ -151,7 +155,7 @@ size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) } m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); - return i + 1; + return i + 1 - inBufferSoFar; } } // switch (m_IncomingHeaderData[i]) } // for i - m_IncomingHeaderData[] -- cgit v1.2.3 From 12d95ab0474dffa443bf82834177db7e10110fae Mon Sep 17 00:00:00 2001 From: Mattes D Date: Thu, 4 Feb 2016 17:44:10 +0100 Subject: HTTP: Fixed response parser, unified API. --- src/HTTP/EnvelopeParser.cpp | 6 +++--- src/HTTP/HTTPRequestParser.h | 4 ++-- src/HTTP/HTTPResponseParser.cpp | 22 +++++++++++++++------- src/HTTP/HTTPResponseParser.h | 6 ++---- tests/HTTP/CMakeLists.txt | 9 ++++++++- tests/HTTP/HTTPResponseParser_file.cpp | 10 +++++----- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/HTTP/EnvelopeParser.cpp b/src/HTTP/EnvelopeParser.cpp index 407e9dcfc..1c49b643f 100644 --- a/src/HTTP/EnvelopeParser.cpp +++ b/src/HTTP/EnvelopeParser.cpp @@ -28,12 +28,12 @@ size_t cEnvelopeParser::Parse(const char * a_Data, size_t a_Size) } // 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; + auto 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); + size_t idxCRLF = m_IncomingData.find("\r\n", searchStart); if (idxCRLF == AString::npos) { // Not a complete line yet, all input consumed: diff --git a/src/HTTP/HTTPRequestParser.h b/src/HTTP/HTTPRequestParser.h index f3d3add91..1b06d7b8b 100644 --- a/src/HTTP/HTTPRequestParser.h +++ b/src/HTTP/HTTPRequestParser.h @@ -25,8 +25,8 @@ public: cHTTPRequestParser(void); /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or AString::npos number for error - */ + Returns the number of bytes consumed or AString::npos number for error. + Once it has fully parsed all the headers, doesn't consume any more data. */ size_t ParseHeaders(const char * a_Data, size_t a_Size); /** Returns true if the request did contain a Content-Length header */ diff --git a/src/HTTP/HTTPResponseParser.cpp b/src/HTTP/HTTPResponseParser.cpp index 9411208e2..5469666b6 100644 --- a/src/HTTP/HTTPResponseParser.cpp +++ b/src/HTTP/HTTPResponseParser.cpp @@ -29,17 +29,18 @@ size_t cHTTPResponseParser::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 a_Size; + return 0; } // If still waiting for the status line, add to buffer and try parsing it: + auto inBufferSoFar = m_Buffer.size(); if (m_StatusLine.empty()) { m_Buffer.append(a_Data, a_Size); if (!ParseStatusLine()) { // All data used, but not a complete status line yet. - return 0; + return a_Size; } if (m_HasHadError) { @@ -53,14 +54,20 @@ size_t cHTTPResponseParser::Parse(const char * a_Data, size_t a_Size) m_Callbacks.OnError("Failed to parse the envelope"); return AString::npos; } + ASSERT(bytesConsumed < inBufferSoFar + a_Size); m_Buffer.erase(0, bytesConsumed); if (!m_Buffer.empty()) { // Headers finished and there's still data left in the buffer, process it as message body: HeadersFinished(); - return ParseBody(m_Buffer.data(), m_Buffer.size()); + auto res = ParseBody(m_Buffer.data(), m_Buffer.size()); + if (res == AString::npos) + { + return AString::npos; + } + return res + bytesConsumed - inBufferSoFar; } - return 0; + return a_Size; } // if (m_StatusLine.empty()) // If still parsing headers, send them to the envelope parser: @@ -77,9 +84,9 @@ size_t cHTTPResponseParser::Parse(const char * a_Data, size_t a_Size) { // Headers finished and there's still data left in the buffer, process it as message body: HeadersFinished(); - return ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); + return bytesConsumed + ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); } - return 0; + return a_Size; } // Already parsing the body @@ -116,7 +123,8 @@ size_t cHTTPResponseParser::ParseBody(const char * a_Data, size_t a_Size) } // Parse the body using the transfer encoding parser: - return m_TransferEncodingParser->Parse(a_Data, a_Size); + // (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); } diff --git a/src/HTTP/HTTPResponseParser.h b/src/HTTP/HTTPResponseParser.h index 19652ccef..1d867ecc5 100644 --- a/src/HTTP/HTTPResponseParser.h +++ b/src/HTTP/HTTPResponseParser.h @@ -51,8 +51,7 @@ public: cHTTPResponseParser(cCallbacks & a_Callbacks); /** Parses the incoming data and calls the appropriate callbacks. - Returns the number of bytes from the end of a_Data that is already not part of this response. - Returns AString::npos on an error. */ + Returns the number of bytes consumed or AString::npos number for error. */ size_t Parse(const char * a_Data, size_t a_Size); /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). @@ -97,8 +96,7 @@ protected: /** Parses the message body. Processes transfer encoding and calls the callbacks for body data. - Returns the number of bytes from the end of a_Data that is already not part of this response. - Returns AString::npos on error. */ + Returns the number of bytes consumed or AString::npos number for error. */ size_t ParseBody(const char * a_Data, size_t a_Size); /** Called internally when the headers-parsing has just finished. */ diff --git a/tests/HTTP/CMakeLists.txt b/tests/HTTP/CMakeLists.txt index f6989d831..c11c601d9 100644 --- a/tests/HTTP/CMakeLists.txt +++ b/tests/HTTP/CMakeLists.txt @@ -42,5 +42,12 @@ endif() # HTTPResponseParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called: add_executable(HTTPResponseParser_file-exe HTTPResponseParser_file.cpp) target_link_libraries(HTTPResponseParser_file-exe HTTP) -add_test(NAME HTTPResponseParser_file-test1 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data) + +# Test parsing the file in 2-byte chunks (should go from response line parsing through headers parsing to body parsing, each within a different step): +add_test(NAME HTTPResponseParser_file-test1-2 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data 2) + +# Test parsing the file in 128-byte chunks (should parse response line and part of headers in one step, the rest in another step): +add_test(NAME HTTPResponseParser_file-test1-128 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data 128) + +# Test parsing a chunked-encoding content: add_test(NAME HTTPResponseParser_file-test2 COMMAND HTTPResponseParser_file-exe HTTPResponse2.data) diff --git a/tests/HTTP/HTTPResponseParser_file.cpp b/tests/HTTP/HTTPResponseParser_file.cpp index 48ff928bc..7e8d127b7 100644 --- a/tests/HTTP/HTTPResponseParser_file.cpp +++ b/tests/HTTP/HTTPResponseParser_file.cpp @@ -122,16 +122,16 @@ int main(int argc, char * argv[]) printf("Read 0 bytes from file (EOF?), terminating\n"); break; } - auto numLeft = parser.Parse(buf, numBytes); - if (numLeft == AString::npos) + auto numConsumed = parser.Parse(buf, numBytes); + if (numConsumed == AString::npos) { printf("Parser indicates there was an error, terminating parsing.\n"); break; } - ASSERT(numLeft <= numBytes); - if (numLeft > 0) + ASSERT(numConsumed <= numBytes); + if (numConsumed < numBytes) { - printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast(numLeft)); + printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast(numBytes - numConsumed)); } } if (!parser.IsFinished()) -- cgit v1.2.3 From 52d18b4559cbaca949f722aa6901a6eb5f505f02 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 20 Feb 2016 11:50:52 +0100 Subject: WebAdmin uses the new HTTP parser framework. --- CMakeLists.txt | 5 +- src/HTTP/CMakeLists.txt | 6 +- src/HTTP/HTTPFormParser.cpp | 8 +- src/HTTP/HTTPFormParser.h | 8 +- src/HTTP/HTTPMessage.cpp | 57 +++++++++ src/HTTP/HTTPMessage.h | 76 +++++++++++- src/HTTP/HTTPMessageParser.cpp | 222 +++++++++++++++++++++++++++++++++ src/HTTP/HTTPMessageParser.h | 125 +++++++++++++++++++ src/HTTP/HTTPRequestParser.cpp | 196 ----------------------------- src/HTTP/HTTPRequestParser.h | 109 ----------------- src/HTTP/HTTPResponseParser.cpp | 191 ----------------------------- src/HTTP/HTTPResponseParser.h | 116 ------------------ src/HTTP/HTTPServer.cpp | 107 +--------------- src/HTTP/HTTPServer.h | 13 +- src/HTTP/HTTPServerConnection.cpp | 224 ++++++++++++++-------------------- src/HTTP/HTTPServerConnection.h | 54 ++++---- src/WebAdmin.cpp | 74 +++++++---- src/WebAdmin.h | 54 ++------ tests/HTTP/CMakeLists.txt | 29 +++-- tests/HTTP/HTTPMessageParser_file.cpp | 153 +++++++++++++++++++++++ tests/HTTP/HTTPRequest1.data | 5 + tests/HTTP/HTTPRequest2.data | 3 + tests/HTTP/HTTPRequestParser_file.cpp | 153 +++++++++++++++++++++++ tests/HTTP/HTTPResponse1.data | 1 + 24 files changed, 1009 insertions(+), 980 deletions(-) create mode 100644 src/HTTP/HTTPMessageParser.cpp create mode 100644 src/HTTP/HTTPMessageParser.h delete mode 100644 src/HTTP/HTTPRequestParser.cpp delete mode 100644 src/HTTP/HTTPRequestParser.h delete mode 100644 src/HTTP/HTTPResponseParser.cpp delete mode 100644 src/HTTP/HTTPResponseParser.h create mode 100644 tests/HTTP/HTTPMessageParser_file.cpp create mode 100644 tests/HTTP/HTTPRequest1.data create mode 100644 tests/HTTP/HTTPRequest2.data create mode 100644 tests/HTTP/HTTPRequestParser_file.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f84a81062..2b5abb362 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,9 @@ if(${SELF_TEST}) add_definitions(-DSELF_TEST) endif() +# Build all dependent libraries as static: +SET(CMAKE_BUILD_STATIC_LIBRARIES ON) + @@ -275,7 +278,7 @@ if (MSVC) creatable-exe EchoServer Google-exe - HTTPResponseParser_file-exe + HTTPMessageParser_file-exe LoadablePieces NameLookup PROPERTIES FOLDER Tests diff --git a/src/HTTP/CMakeLists.txt b/src/HTTP/CMakeLists.txt index 4597f6eda..acb3ac2cd 100644 --- a/src/HTTP/CMakeLists.txt +++ b/src/HTTP/CMakeLists.txt @@ -8,8 +8,7 @@ SET (SRCS EnvelopeParser.cpp HTTPFormParser.cpp HTTPMessage.cpp - HTTPRequestParser.cpp - HTTPResponseParser.cpp + HTTPMessageParser.cpp HTTPServer.cpp HTTPServerConnection.cpp MultipartParser.cpp @@ -23,8 +22,7 @@ SET (HDRS EnvelopeParser.h HTTPFormParser.h HTTPMessage.h - HTTPRequestParser.h - HTTPResponseParser.h + HTTPMessageParser.h HTTPServer.h HTTPServerConnection.h MultipartParser.h diff --git a/src/HTTP/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp index ddf87f540..ea5da3c18 100644 --- a/src/HTTP/HTTPFormParser.cpp +++ b/src/HTTP/HTTPFormParser.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPFormParser.h" -#include "HTTPRequestParser.h" +#include "HTTPMessage.h" #include "MultipartParser.h" #include "NameValueParser.h" @@ -13,7 +13,7 @@ -cHTTPFormParser::cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks) : +cHTTPFormParser::cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks) : m_Callbacks(a_Callbacks), m_IsValid(true), m_IsCurrentPartFile(false), @@ -121,7 +121,7 @@ bool cHTTPFormParser::Finish(void) -bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) +bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & a_Request) { const AString & ContentType = a_Request.GetContentType(); return ( @@ -138,7 +138,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequestParser & a_Request) -void cHTTPFormParser::BeginMultipart(const cHTTPRequestParser & a_Request) +void cHTTPFormParser::BeginMultipart(const cHTTPIncomingRequest & a_Request) { ASSERT(m_MultipartParser.get() == nullptr); m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); diff --git a/src/HTTP/HTTPFormParser.h b/src/HTTP/HTTPFormParser.h index fde6df696..6bf3e7d78 100644 --- a/src/HTTP/HTTPFormParser.h +++ b/src/HTTP/HTTPFormParser.h @@ -15,7 +15,7 @@ // fwd: -class cHTTPRequestParser; +class cHTTPIncomingRequest; @@ -51,7 +51,7 @@ public: /** Creates a parser that is tied to a request and notifies of various events using a callback mechanism */ - cHTTPFormParser(cHTTPRequestParser & a_Request, cCallbacks & a_Callbacks); + cHTTPFormParser(const cHTTPIncomingRequest & a_Request, cCallbacks & a_Callbacks); /** Creates a parser with the specified content type that reads data from a string */ cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks); @@ -64,7 +64,7 @@ public: bool Finish(void); /** Returns true if the headers suggest the request has form data parseable by this class */ - static bool HasFormData(const cHTTPRequestParser & a_Request); + static bool HasFormData(const cHTTPIncomingRequest & a_Request); protected: @@ -97,7 +97,7 @@ protected: /** Sets up the object for parsing a fpkMultipart request */ - void BeginMultipart(const cHTTPRequestParser & a_Request); + void BeginMultipart(const cHTTPIncomingRequest & a_Request); /** Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) */ void ParseFormUrlEncoded(void); diff --git a/src/HTTP/HTTPMessage.cpp b/src/HTTP/HTTPMessage.cpp index ca63397dd..5a7b86315 100644 --- a/src/HTTP/HTTPMessage.cpp +++ b/src/HTTP/HTTPMessage.cpp @@ -100,3 +100,60 @@ void cHTTPResponse::AppendToData(AString & a_DataStream) const + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPIncomingRequest: + +cHTTPIncomingRequest::cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL): + Super(mkRequest), + m_Method(a_Method), + m_URL(a_URL) +{ +} + + + + + +AString cHTTPIncomingRequest::GetURLPath(void) const +{ + auto idxQuestionMark = m_URL.find('?'); + if (idxQuestionMark == AString::npos) + { + return m_URL; + } + else + { + return m_URL.substr(0, idxQuestionMark); + } +} + + + + + +void cHTTPIncomingRequest::AddHeader(const AString & a_Key, const AString & a_Value) +{ + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } + if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) + { + m_AllowKeepAlive = true; + } +} + + + + diff --git a/src/HTTP/HTTPMessage.h b/src/HTTP/HTTPMessage.h index 50b1f9e19..683734b67 100644 --- a/src/HTTP/HTTPMessage.h +++ b/src/HTTP/HTTPMessage.h @@ -36,7 +36,7 @@ public: virtual ~cHTTPMessage() {} /** Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length */ - void AddHeader(const AString & a_Key, const AString & a_Value); + virtual void AddHeader(const AString & a_Key, const AString & a_Value); void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; } void SetContentLength(size_t a_ContentLength) { m_ContentLength = a_ContentLength; } @@ -82,3 +82,77 @@ public: + +/** Provides storage for an incoming HTTP request. */ +class cHTTPIncomingRequest: + public cHTTPMessage +{ + typedef cHTTPMessage Super; +public: + /** Base class for anything that can be used as the UserData for the request. */ + class cUserData + { + public: + // Force a virtual destructor in descendants: + virtual ~cUserData() {} + }; + typedef SharedPtr cUserDataPtr; + + + /** Creates a new instance of the class, containing the method and URL provided by the client. */ + cHTTPIncomingRequest(const AString & a_Method, const AString & a_URL); + + /** Returns the method used in the request */ + const AString & GetMethod(void) const { return m_Method; } + + /** Returns the URL used in the request */ + const AString & GetURL(void) const { return m_URL; } + + /** Returns the path part of the URL. */ + AString GetURLPath(void) const; + + /** Returns true if the request has had the Auth header present. */ + bool HasAuth(void) const { return m_HasAuth; } + + /** Returns the username that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /** Returns the password that the request presented. Only valid if HasAuth() is true */ + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + + bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } + + /** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */ + void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; } + + /** Returns the data attached to this request by the class client. */ + cUserDataPtr GetUserData(void) { return m_UserData; } + + /** Adds the specified header into the internal list of headers. + Overrides the parent to add recognizing additional headers: auth and keepalive. */ + virtual void AddHeader(const AString & a_Key, const AString & a_Value) override; + +protected: + + /** Method of the request (GET / PUT / POST / ...) */ + AString m_Method; + + /** Full URL of the request */ + AString m_URL; + + /** Set to true if the request contains auth data that was understood by the parser */ + bool m_HasAuth; + + /** The username used for auth */ + AString m_AuthUsername; + + /** The password used for auth */ + AString m_AuthPassword; + + /** Set to true if the request indicated that it supports keepalives. + If false, the server will close the connection once the request is finished */ + bool m_AllowKeepAlive; + + /** Any data attached to the request by the class client. */ + cUserDataPtr m_UserData; +}; diff --git a/src/HTTP/HTTPMessageParser.cpp b/src/HTTP/HTTPMessageParser.cpp new file mode 100644 index 000000000..10d3d4ad9 --- /dev/null +++ b/src/HTTP/HTTPMessageParser.cpp @@ -0,0 +1,222 @@ + +// 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(Printf("Unknown transfer encoding: %s", m_TransferEncoding.c_str())); + 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(Printf("Invalid content length header value: \"%s\"", a_Value.c_str())); + } + 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(); +} + + + + diff --git a/src/HTTP/HTTPMessageParser.h b/src/HTTP/HTTPMessageParser.h new file mode 100644 index 000000000..f07de0492 --- /dev/null +++ b/src/HTTP/HTTPMessageParser.h @@ -0,0 +1,125 @@ + +// HTTPMessageParser.h + +// Declares the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser, +// and reports the individual parts via callbacks + + + + + +#pragma once + +#include "EnvelopeParser.h" +#include "TransferEncodingParser.h" + + + + + +class cHTTPMessageParser: + protected cEnvelopeParser::cCallbacks, + protected cTransferEncodingParser::cCallbacks +{ +public: + class cCallbacks + { + public: + // Force a virtual destructor in descendants: + virtual ~cCallbacks() {} + + /** Called when an error has occured while parsing. */ + virtual void OnError(const AString & a_ErrorDescription) = 0; + + /** Called when the first line (request / status) is fully parsed. */ + virtual void OnFirstLine(const AString & a_FirstLine) = 0; + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) = 0; + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; + + /** Called when the entire body has been reported by OnBodyData(). */ + virtual void OnBodyFinished(void) = 0; + }; + + /** Creates a new parser instance that will use the specified callbacks for reporting. */ + cHTTPMessageParser(cCallbacks & a_Callbacks); + + /** Parses the incoming data and calls the appropriate callbacks. + Returns the number of bytes consumed or AString::npos number for error. */ + size_t Parse(const char * a_Data, size_t a_Size); + + /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). + Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ + void Finish(void); + + /** Returns true if the entire response has been already parsed. */ + bool IsFinished(void) const { return m_IsFinished; } + + /** Resets the parser to the initial state, so that a new request can be parsed. */ + void Reset(void); + + +protected: + + /** The callbacks used for reporting. */ + cCallbacks & m_Callbacks; + + /** Set to true if an error has been encountered by the parser. */ + bool m_HasHadError; + + /** True if the response has been fully parsed. */ + bool m_IsFinished; + + /** The complete first line of the response. Empty if not parsed yet. */ + AString m_FirstLine; + + /** Buffer for the incoming data until the status line is parsed. */ + AString m_Buffer; + + /** Parser for the envelope data (headers) */ + cEnvelopeParser m_EnvelopeParser; + + /** The specific parser for the transfer encoding used by this response. */ + cTransferEncodingParserPtr m_TransferEncodingParser; + + /** The transfer encoding to be used by the parser. + Filled while parsing headers, used when headers are finished. */ + AString m_TransferEncoding; + + /** The content length, parsed from the headers, if available. + Unused for chunked encoding. + Filled while parsing headers, used when headers are finished. */ + size_t m_ContentLength; + + + /** Parses the first line out of m_Buffer. + Removes the first line from m_Buffer, if appropriate. + Returns the number of bytes consumed out of m_Buffer, or AString::npos number for error. */ + size_t ParseFirstLine(void); + + /** Parses the message body. + Processes transfer encoding and calls the callbacks for body data. + Returns the number of bytes consumed or AString::npos number for error. */ + size_t ParseBody(const char * a_Data, size_t a_Size); + + /** Called internally when the headers-parsing has just finished. */ + void HeadersFinished(void); + + // cEnvelopeParser::cCallbacks overrides: + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; + + // cTransferEncodingParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override; + virtual void OnBodyData(const void * a_Data, size_t a_Size) override; + virtual void OnBodyFinished(void) override; +}; + + + + diff --git a/src/HTTP/HTTPRequestParser.cpp b/src/HTTP/HTTPRequestParser.cpp deleted file mode 100644 index bab463832..000000000 --- a/src/HTTP/HTTPRequestParser.cpp +++ /dev/null @@ -1,196 +0,0 @@ - -// HTTPRequestParser.cpp - -// Implements the cHTTPRequestParser class representing the parser for incoming HTTP requests - -#include "Globals.h" -#include "HTTPRequestParser.h" - - - - - -cHTTPRequestParser::cHTTPRequestParser(void) : - super(mkRequest), - m_EnvelopeParser(*this), - m_IsValid(true), - m_UserData(nullptr), - m_HasAuth(false), - m_AllowKeepAlive(false) -{ -} - - - - - -size_t cHTTPRequestParser::ParseHeaders(const char * a_Data, size_t a_Size) -{ - if (!m_IsValid) - { - return AString::npos; - } - - if (m_Method.empty()) - { - // The first line hasn't been processed yet - size_t res = ParseRequestLine(a_Data, a_Size); - ASSERT((res == AString::npos) || (res <= a_Size)); - if ((res == AString::npos) || (res == a_Size)) - { - return res; - } - size_t res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); - ASSERT((res2 == AString::npos) || (res2 <= a_Size - res)); - if (res2 == AString::npos) - { - m_IsValid = false; - return res2; - } - return res2 + res; - } - - if (m_EnvelopeParser.IsInHeaders()) - { - size_t res = m_EnvelopeParser.Parse(a_Data, a_Size); - ASSERT((res == AString::npos) || (res <= a_Size)); - if (res == AString::npos) - { - m_IsValid = false; - } - return res; - } - return 0; -} - - - - - -AString cHTTPRequestParser::GetBareURL(void) const -{ - size_t idxQM = m_URL.find('?'); - if (idxQM != AString::npos) - { - return m_URL.substr(0, idxQM); - } - else - { - return m_URL; - } -} - - - - - -size_t cHTTPRequestParser::ParseRequestLine(const char * a_Data, size_t a_Size) -{ - auto inBufferSoFar = m_IncomingHeaderData.size(); - m_IncomingHeaderData.append(a_Data, a_Size); - auto IdxEnd = m_IncomingHeaderData.size(); - - // Ignore the initial CRLFs (HTTP spec's "should") - size_t LineStart = 0; - while ( - (LineStart < IdxEnd) && - ( - (m_IncomingHeaderData[LineStart] == '\r') || - (m_IncomingHeaderData[LineStart] == '\n') - ) - ) - { - LineStart++; - } - if (LineStart >= IdxEnd) - { - m_IsValid = false; - return AString::npos; - } - - int NumSpaces = 0; - size_t MethodEnd = 0; - size_t URLEnd = 0; - for (size_t i = LineStart; i < IdxEnd; i++) - { - switch (m_IncomingHeaderData[i]) - { - case ' ': - { - switch (NumSpaces) - { - case 0: - { - MethodEnd = i; - break; - } - case 1: - { - URLEnd = i; - break; - } - default: - { - // Too many spaces in the request - m_IsValid = false; - return AString::npos; - } - } - NumSpaces += 1; - break; - } - case '\n': - { - if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7)) - { - // LF too early, without a CR, without two preceeding spaces or too soon after the second space - m_IsValid = false; - return AString::npos; - } - // Check that there's HTTP / version at the end - if (strncmp(m_IncomingHeaderData.c_str() + URLEnd + 1, "HTTP/1.", 7) != 0) - { - m_IsValid = false; - return AString::npos; - } - m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); - m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); - return i + 1 - inBufferSoFar; - } - } // switch (m_IncomingHeaderData[i]) - } // for i - m_IncomingHeaderData[] - - // CRLF hasn't been encountered yet, consider all data consumed - return a_Size; -} - - - - - -void cHTTPRequestParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - if ( - (NoCaseCompare(a_Key, "Authorization") == 0) && - (strncmp(a_Value.c_str(), "Basic ", 6) == 0) - ) - { - AString UserPass = Base64Decode(a_Value.substr(6)); - size_t idxCol = UserPass.find(':'); - if (idxCol != AString::npos) - { - m_AuthUsername = UserPass.substr(0, idxCol); - m_AuthPassword = UserPass.substr(idxCol + 1); - m_HasAuth = true; - } - } - if ((a_Key == "Connection") && (NoCaseCompare(a_Value, "keep-alive") == 0)) - { - m_AllowKeepAlive = true; - } - AddHeader(a_Key, a_Value); -} - - - - diff --git a/src/HTTP/HTTPRequestParser.h b/src/HTTP/HTTPRequestParser.h deleted file mode 100644 index 1b06d7b8b..000000000 --- a/src/HTTP/HTTPRequestParser.h +++ /dev/null @@ -1,109 +0,0 @@ - -// HTTPRequestParser.h - -// Declares the cHTTPRequestParser class representing the parser for incoming HTTP requests - - - - -#pragma once - -#include "HTTPMessage.h" -#include "EnvelopeParser.h" - - - - - -class cHTTPRequestParser : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks -{ - typedef cHTTPMessage super; - -public: - cHTTPRequestParser(void); - - /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or AString::npos number for error. - Once it has fully parsed all the headers, doesn't consume any more data. */ - size_t ParseHeaders(const char * a_Data, size_t a_Size); - - /** Returns true if the request did contain a Content-Length header */ - bool HasReceivedContentLength(void) const { return (m_ContentLength != AString::npos); } - - /** Returns the method used in the request */ - const AString & GetMethod(void) const { return m_Method; } - - /** Returns the URL used in the request */ - const AString & GetURL(void) const { return m_URL; } - - /** Returns the URL used in the request, without any parameters */ - AString GetBareURL(void) const; - - /** Sets the UserData pointer that is stored within this request. - The request doesn't touch this data (doesn't delete it)! */ - void SetUserData(void * a_UserData) { m_UserData = a_UserData; } - - /** Retrieves the UserData pointer that has been stored within this request. */ - void * GetUserData(void) const { return m_UserData; } - - /** Returns true if more data is expected for the request headers */ - bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } - - /** Returns true if the request did present auth data that was understood by the parser */ - bool HasAuth(void) const { return m_HasAuth; } - - /** Returns the username that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthUsername(void) const { return m_AuthUsername; } - - /** Returns the password that the request presented. Only valid if HasAuth() is true */ - const AString & GetAuthPassword(void) const { return m_AuthPassword; } - - bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } - -protected: - /** Parser for the envelope data */ - cEnvelopeParser m_EnvelopeParser; - - /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ - bool m_IsValid; - - /** Bufferred incoming data, while parsing for the request line */ - AString m_IncomingHeaderData; - - /** Method of the request (GET / PUT / POST / ...) */ - AString m_Method; - - /** Full URL of the request */ - AString m_URL; - - /** Data that the HTTPServer callbacks are allowed to store. */ - void * m_UserData; - - /** Set to true if the request contains auth data that was understood by the parser */ - bool m_HasAuth; - - /** The username used for auth */ - AString m_AuthUsername; - - /** The password used for auth */ - AString m_AuthPassword; - - /** Set to true if the request indicated that it supports keepalives. - If false, the server will close the connection once the request is finished */ - bool m_AllowKeepAlive; - - - /** Parses the incoming data for the first line (RequestLine) - Returns the number of bytes consumed, or AString::npos for an error - */ - size_t ParseRequestLine(const char * a_Data, size_t a_Size); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; -} ; - - - - diff --git a/src/HTTP/HTTPResponseParser.cpp b/src/HTTP/HTTPResponseParser.cpp deleted file mode 100644 index 5469666b6..000000000 --- a/src/HTTP/HTTPResponseParser.cpp +++ /dev/null @@ -1,191 +0,0 @@ - -// HTTPResponseParser.cpp - -// Implements the cHTTPResponseParser class representing the parser for incoming HTTP responses - -#include "Globals.h" -#include "HTTPResponseParser.h" - - - - - -cHTTPResponseParser::cHTTPResponseParser(cHTTPResponseParser::cCallbacks & a_Callbacks): - Super(mkResponse), - m_Callbacks(a_Callbacks), - m_HasHadError(false), - m_IsInHeaders(true), - m_IsFinished(false), - m_EnvelopeParser(*this) -{ -} - - - - - -size_t cHTTPResponseParser::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_StatusLine.empty()) - { - m_Buffer.append(a_Data, a_Size); - if (!ParseStatusLine()) - { - // 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 bytesConsumed = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size()); - if (bytesConsumed == AString::npos) - { - m_HasHadError = true; - m_Callbacks.OnError("Failed to parse the envelope"); - return AString::npos; - } - ASSERT(bytesConsumed < inBufferSoFar + a_Size); - m_Buffer.erase(0, bytesConsumed); - if (!m_Buffer.empty()) - { - // Headers finished and there's still data left in the buffer, process it as message body: - HeadersFinished(); - auto res = ParseBody(m_Buffer.data(), m_Buffer.size()); - if (res == AString::npos) - { - return AString::npos; - } - return res + bytesConsumed - inBufferSoFar; - } - return a_Size; - } // if (m_StatusLine.empty()) - - // If still parsing headers, send them to the envelope parser: - if (m_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 (bytesConsumed < a_Size) - { - // Headers finished and there's still data left in the buffer, process it as message body: - HeadersFinished(); - return bytesConsumed + ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed); - } - return a_Size; - } - - // Already parsing the body - return ParseBody(a_Data, a_Size); -} - - - - - -bool cHTTPResponseParser::ParseStatusLine(void) -{ - auto idxLineEnd = m_Buffer.find("\r\n"); - if (idxLineEnd == AString::npos) - { - // Not a complete line yet - return false; - } - m_StatusLine = m_Buffer.substr(0, idxLineEnd); - m_Buffer.erase(0, idxLineEnd + 2); - m_Callbacks.OnStatusLine(m_StatusLine); - return true; -} - - - - -size_t cHTTPResponseParser::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 - 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 cHTTPResponseParser::HeadersFinished(void) -{ - m_IsInHeaders = false; - m_Callbacks.OnHeadersFinished(); - - auto transferEncoding = m_Headers.find("transfer-encoding"); - if (transferEncoding == m_Headers.end()) - { - m_TransferEncodingParser = cTransferEncodingParser::Create(*this, "identity", m_ContentLength); - } - else - { - m_TransferEncodingParser = cTransferEncodingParser::Create(*this, transferEncoding->second, m_ContentLength); - } -} - - - - - -void cHTTPResponseParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - AddHeader(a_Key, a_Value); - m_Callbacks.OnHeaderLine(a_Key, a_Value); -} - - - - - -void cHTTPResponseParser::OnError(const AString & a_ErrorDescription) -{ - m_HasHadError = true; - m_Callbacks.OnError(a_ErrorDescription); -} - - - - - -void cHTTPResponseParser::OnBodyData(const void * a_Data, size_t a_Size) -{ - m_Callbacks.OnBodyData(a_Data, a_Size); -} - - - - - -void cHTTPResponseParser::OnBodyFinished(void) -{ - m_IsFinished = true; - m_Callbacks.OnBodyFinished(); -} - - - - diff --git a/src/HTTP/HTTPResponseParser.h b/src/HTTP/HTTPResponseParser.h deleted file mode 100644 index 1d867ecc5..000000000 --- a/src/HTTP/HTTPResponseParser.h +++ /dev/null @@ -1,116 +0,0 @@ - -// HTTPResponseParser.h - -// Declares the cHTTPResponseParser class representing the parser for incoming HTTP responses - - - - -#pragma once - -#include "HTTPMessage.h" -#include "TransferEncodingParser.h" - - - - - -class cHTTPResponseParser: - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks, - protected cTransferEncodingParser::cCallbacks -{ - typedef cHTTPMessage Super; - -public: - class cCallbacks - { - public: - // Force a virtual destructor in descendants: - virtual ~cCallbacks() {} - - /** Called when an error has occured while parsing. */ - virtual void OnError(const AString & a_ErrorDescription) = 0; - - /** Called when the status line is fully parsed. */ - virtual void OnStatusLine(const AString & a_StatusLine) = 0; - - /** Called when a single header line is parsed. */ - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; - - /** Called when all the headers have been parsed. */ - virtual void OnHeadersFinished(void) = 0; - - /** Called for each chunk of the incoming body data. */ - virtual void OnBodyData(const void * a_Data, size_t a_Size) = 0; - - /** Called when the entire body has been reported by OnBodyData(). */ - virtual void OnBodyFinished(void) = 0; - }; - - cHTTPResponseParser(cCallbacks & a_Callbacks); - - /** Parses the incoming data and calls the appropriate callbacks. - Returns the number of bytes consumed or AString::npos number for error. */ - size_t Parse(const char * a_Data, size_t a_Size); - - /** Called when the server indicates no more data will be sent (HTTP 1.0 socket closed). - Finishes all parsing and calls apropriate callbacks (error if incomplete response). */ - void Finish(void); - - /** Returns true if the entire response has been already parsed. */ - bool IsFinished(void) const { return m_IsFinished; } - - -protected: - - /** The callbacks used for reporting. */ - cCallbacks & m_Callbacks; - - /** Set to true if an error has been encountered by the parser. */ - bool m_HasHadError; - - /** True if the parser is still parsing the status or headers. */ - bool m_IsInHeaders; - - /** True if the response has been fully parsed. */ - bool m_IsFinished; - - /** The complete status line of the response. Empty if not parsed yet. */ - AString m_StatusLine; - - /** Buffer for the incoming data until the status line is parsed. */ - AString m_Buffer; - - /** Parser for the envelope data (headers) */ - cEnvelopeParser m_EnvelopeParser; - - /** The specific parser for the transfer encoding used by this response. */ - cTransferEncodingParserPtr m_TransferEncodingParser; - - - /** Parses the status line out of the m_Buffer. - Removes the status line from m_Buffer, if appropriate. - Returns true if the entire status line has been parsed. */ - bool ParseStatusLine(void); - - /** Parses the message body. - Processes transfer encoding and calls the callbacks for body data. - Returns the number of bytes consumed or AString::npos number for error. */ - size_t ParseBody(const char * a_Data, size_t a_Size); - - /** Called internally when the headers-parsing has just finished. */ - void HeadersFinished(void); - - // cEnvelopeParser::cCallbacks overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; - - // cTransferEncodingParser::cCallbacks overrides: - virtual void OnError(const AString & a_ErrorDescription) override; - virtual void OnBodyData(const void * a_Data, size_t a_Size) override; - virtual void OnBodyFinished(void) override; -}; - - - - diff --git a/src/HTTP/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp index 741f5f1c5..5a5bee045 100644 --- a/src/HTTP/HTTPServer.cpp +++ b/src/HTTP/HTTPServer.cpp @@ -5,7 +5,7 @@ #include "Globals.h" #include "HTTPServer.h" -#include "HTTPRequestParser.h" +#include "HTTPMessageParser.h" #include "HTTPServerConnection.h" #include "HTTPFormParser.h" #include "SslHTTPServerConnection.h" @@ -24,102 +24,6 @@ -class cDebugCallbacks : - public cHTTPServer::cCallbacks, - protected cHTTPFormParser::cCallbacks -{ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - UNUSED(a_Connection); - - if (cHTTPFormParser::HasFormData(a_Request)) - { - a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); - } - } - - - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override - { - UNUSED(a_Connection); - - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - FormParser->Parse(a_Data, a_Size); - } - } - - - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override - { - cHTTPFormParser * FormParser = reinterpret_cast(a_Request.GetUserData()); - if (FormParser != nullptr) - { - if (FormParser->Finish()) - { - cHTTPResponse Resp; - Resp.SetContentType("text/html"); - a_Connection.Send(Resp); - a_Connection.Send("\r\n"); - for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) - { - a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); - } // for itr - FormParser[] - a_Connection.Send("
NameValue
%s
%s
"); - return; - } - - // Parsing failed: - cHTTPResponse Resp; - Resp.SetContentType("text/plain"); - a_Connection.Send(Resp); - a_Connection.Send("Form parsing failed"); - return; - } - - // Test the auth failure and success: - if (a_Request.GetURL() == "/auth") - { - if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b")) - { - a_Connection.SendNeedAuth("Cuberite WebAdmin"); - return; - } - } - - cHTTPResponse Resp; - Resp.SetContentType("text/plain"); - a_Connection.Send(Resp); - a_Connection.Send("Hello, world"); - } - - - virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override - { - // TODO - } - - - virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, size_t a_Size) override - { - // TODO - } - - - virtual void OnFileEnd(cHTTPFormParser & a_Parser) override - { - // TODO - } - -}; - -static cDebugCallbacks g_DebugCallbacks; - - - - - //////////////////////////////////////////////////////////////////////////////// // cHTTPServerListenCallbacks: @@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo -void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { m_Callbacks->OnRequestBegun(a_Connection, a_Request); } @@ -289,19 +193,18 @@ void cHTTPServer::NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestP -void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) +void cHTTPServer::RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size) { - m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); + m_Callbacks->OnRequestBody(a_Connection, a_Request, reinterpret_cast(a_Data), a_Size); } -void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cHTTPServer::RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { m_Callbacks->OnRequestFinished(a_Connection, a_Request); - a_Connection.AwaitNextRequest(); } diff --git a/src/HTTP/HTTPServer.h b/src/HTTP/HTTPServer.h index 15202a50e..d06e13cac 100644 --- a/src/HTTP/HTTPServer.h +++ b/src/HTTP/HTTPServer.h @@ -22,6 +22,7 @@ // fwd: class cHTTPMessage; class cHTTPRequestParser; +class cHTTPIncomingRequest; class cHTTPResponse; class cHTTPServerConnection; @@ -40,14 +41,14 @@ public: /** Called when a new request arrives over a connection and all its headers have been parsed. The request body needn't have arrived yet. */ - virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; + virtual void OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0; /** Called when another part of request body has arrived. May be called multiple times for a single request. */ - virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) = 0; + virtual void OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) = 0; /** Called when the request body has been fully received in previous calls to OnRequestBody() */ - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) = 0; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) = 0; } ; cHTTPServer(void); @@ -85,14 +86,14 @@ protected: cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort); /** Called by cHTTPServerConnection when it finishes parsing the request header */ - void NewRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void NewRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Called by cHTTPConenction when it receives more data for the request body. May be called multiple times for a single request. */ - void RequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size); + void RequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const void * a_Data, size_t a_Size); /** Called by cHTTPServerConnection when it detects that the request has finished (all of its body has been received) */ - void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void RequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); } ; diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp index 0ee9f3ce7..0439c8b93 100644 --- a/src/HTTP/HTTPServerConnection.cpp +++ b/src/HTTP/HTTPServerConnection.cpp @@ -5,7 +5,8 @@ #include "Globals.h" #include "HTTPServerConnection.h" -#include "HTTPRequestParser.h" +#include "HTTPMessage.h" +#include "HTTPMessageParser.h" #include "HTTPServer.h" @@ -14,9 +15,8 @@ cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : m_HTTPServer(a_HTTPServer), - m_State(wcsRecvHeaders), - m_CurrentRequest(nullptr), - m_CurrentRequestBodyRemaining(0) + m_Parser(*this), + m_CurrentRequest(nullptr) { // LOGD("HTTP: New connection at %p", this); } @@ -28,8 +28,7 @@ cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : cHTTPServerConnection::~cHTTPServerConnection() { // LOGD("HTTP: Connection deleting: %p", this); - delete m_CurrentRequest; - m_CurrentRequest = nullptr; + m_CurrentRequest.reset(); } @@ -41,7 +40,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); SendData(Printf("Content-Length: %u\r\n\r\n", static_cast(a_Response.size()))); SendData(a_Response.data(), a_Response.size()); - m_State = wcsRecvHeaders; + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -51,7 +51,8 @@ void cHTTPServerConnection::SendStatusAndReason(int a_StatusCode, const AString void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) { SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str())); - m_State = wcsRecvHeaders; + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -60,10 +61,9 @@ void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) { - ASSERT(m_State == wcsRecvIdle); + ASSERT(m_CurrentRequest != nullptr); AString toSend; a_Response.AppendToData(toSend); - m_State = wcsSendingResp; SendData(toSend); } @@ -73,7 +73,7 @@ void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) { - ASSERT(m_State == wcsSendingResp); + ASSERT(m_CurrentRequest != nullptr); // We're sending in Chunked transfer encoding SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size)); SendData(a_Data, a_Size); @@ -86,47 +86,10 @@ void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) void cHTTPServerConnection::FinishResponse(void) { - ASSERT(m_State == wcsSendingResp); + ASSERT(m_CurrentRequest != nullptr); SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPServerConnection::AwaitNextRequest(void) -{ - switch (m_State) - { - case wcsRecvHeaders: - { - // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth()) - break; - } - - case wcsRecvIdle: - { - // The client is waiting for a response, send an "Internal server error": - SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - case wcsSendingResp: - { - // The response headers have been sent, we need to terminate the response body: - SendData("0\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - default: - { - ASSERT(!"Unhandled state recovery"); - break; - } - } + m_CurrentRequest.reset(); + m_Parser.Reset(); } @@ -160,86 +123,7 @@ void cHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) { ASSERT(m_Link != nullptr); - switch (m_State) - { - case wcsRecvHeaders: - { - if (m_CurrentRequest == nullptr) - { - m_CurrentRequest = new cHTTPRequestParser; - } - - size_t BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); - if (BytesConsumed == AString::npos) - { - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - if (m_CurrentRequest->IsInHeaders()) - { - // The request headers are not yet complete - return; - } - - // The request has finished parsing its headers successfully, notify of it: - m_State = wcsRecvBody; - m_HTTPServer.NewRequest(*this, *m_CurrentRequest); - m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); - if (m_CurrentRequestBodyRemaining == AString::npos) - { - // The body length was not specified in the request, assume zero - m_CurrentRequestBodyRemaining = 0; - } - - // Process the rest of the incoming data into the request body: - if (a_Size > BytesConsumed) - { - cHTTPServerConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed); - return; - } - else - { - cHTTPServerConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away - return; - } - } - - case wcsRecvBody: - { - ASSERT(m_CurrentRequest != nullptr); - if (m_CurrentRequestBodyRemaining > 0) - { - size_t BytesToConsume = std::min(m_CurrentRequestBodyRemaining, static_cast(a_Size)); - m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume); - m_CurrentRequestBodyRemaining -= BytesToConsume; - } - if (m_CurrentRequestBodyRemaining == 0) - { - m_State = wcsRecvIdle; - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - if (!m_CurrentRequest->DoesAllowKeepAlive()) - { - m_State = wcsInvalid; - m_Link->Close(); - m_Link.reset(); - return; - } - delete m_CurrentRequest; - m_CurrentRequest = nullptr; - } - break; - } - - default: - { - // TODO: Should we be receiving data in this state? - break; - } - } + m_Parser.Parse(a_Data, a_Size); } @@ -268,6 +152,84 @@ void cHTTPServerConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg) + +void cHTTPServerConnection::OnError(const AString & a_ErrorDescription) +{ + OnRemoteClosed(); +} + + + + + +void cHTTPServerConnection::OnFirstLine(const AString & a_FirstLine) +{ + // Create a new request object for this request: + auto split = StringSplit(a_FirstLine, " "); + if (split.size() < 2) + { + // Invalid request line. We need at least the Method and URL + OnRemoteClosed(); + return; + } + m_CurrentRequest.reset(new cHTTPIncomingRequest(split[0], split[1])); +} + + + + + +void cHTTPServerConnection::OnHeaderLine(const AString & a_Key, const AString & a_Value) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_CurrentRequest->AddHeader(a_Key, a_Value); +} + + + + + +void cHTTPServerConnection::OnHeadersFinished(void) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_HTTPServer.NewRequest(*this, *m_CurrentRequest); +} + + + + + +void cHTTPServerConnection::OnBodyData(const void * a_Data, size_t a_Size) +{ + if (m_CurrentRequest == nullptr) + { + return; + } + m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, a_Size); +} + + + + + +void cHTTPServerConnection::OnBodyFinished(void) +{ + // Process the request and reset: + m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); + m_CurrentRequest.reset(); + m_Parser.Reset(); +} + + + + + void cHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) { m_Link->Send(a_Data, a_Size); diff --git a/src/HTTP/HTTPServerConnection.h b/src/HTTP/HTTPServerConnection.h index af2abb73f..62d3b7182 100644 --- a/src/HTTP/HTTPServerConnection.h +++ b/src/HTTP/HTTPServerConnection.h @@ -10,6 +10,7 @@ #pragma once #include "../OSSupport/Network.h" +#include "HTTPMessageParser.h" @@ -18,34 +19,29 @@ // fwd: class cHTTPServer; class cHTTPResponse; -class cHTTPRequestParser; - +class cHTTPIncomingRequest; class cHTTPServerConnection : - public cTCPLink::cCallbacks + public cTCPLink::cCallbacks, + public cHTTPMessageParser::cCallbacks { public: - - enum eState - { - wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if nullptr) - wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) - wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == nullptr) - wcsSendingResp, ///< Sending response body (m_CurrentRequest == nullptr) - wcsInvalid, ///< The request was malformed, the connection is closing - } ; - + /** Creates a new instance, connected to the specified HTTP server instance */ cHTTPServerConnection(cHTTPServer & a_HTTPServer); + + // Force a virtual destructor in all descendants virtual ~cHTTPServerConnection(); /** Sends HTTP status code together with a_Reason (used for HTTP errors). - Sends the a_Reason as the body as well, so that browsers display it. */ + Sends the a_Reason as the body as well, so that browsers display it. + Clears the current request (since it's finished by this call). */ void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); - /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ + /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm. + Clears the current request (since it's finished by this call). */ void SendNeedAuth(const AString & a_Realm); /** Sends the headers contained in a_Response */ @@ -57,13 +53,10 @@ public: /** Sends the data as the response (may be called multiple times) */ void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } - /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) */ + /** Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive). + Clears the current request (since it's finished by this call). */ void FinishResponse(void); - /** Resets the internal connection state for a new request. - Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" */ - void AwaitNextRequest(void); - /** Terminates the connection; finishes any request being currently processed */ void Terminate(void); @@ -73,19 +66,12 @@ protected: /** The parent webserver that is to be notified of events on this connection */ cHTTPServer & m_HTTPServer; - /** All the incoming data until the entire request header is parsed */ - AString m_IncomingHeaderData; - - /** Status in which the request currently is */ - eState m_State; + /** The parser responsible for reading the requests. */ + cHTTPMessageParser m_Parser; /** The request being currently received Valid only between having parsed the headers and finishing receiving the body. */ - cHTTPRequestParser * m_CurrentRequest; - - /** Number of bytes that remain to read for the complete body of the message to be received. - Valid only in wcsRecvBody */ - size_t m_CurrentRequestBodyRemaining; + std::unique_ptr m_CurrentRequest; /** The network link attached to this connection. */ cTCPLinkPtr m_Link; @@ -104,6 +90,14 @@ protected: /** An error has occurred on the socket. */ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override; + // cHTTPMessageParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override; + virtual void OnFirstLine(const AString & a_FirstLine) override; + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; + virtual void OnHeadersFinished(void) override; + virtual void OnBodyData(const void * a_Data, size_t a_Size) override; + virtual void OnBodyFinished(void) override; + // Overridable: /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */ virtual void SendData(const void * a_Data, size_t a_Size); diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 85ee981c7..95a1bcdbd 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -12,8 +12,8 @@ #include "Server.h" #include "Root.h" -#include "HTTP/HTTPRequestParser.h" #include "HTTP/HTTPServerConnection.h" +#include "HTTP/HTTPFormParser.h" @@ -49,6 +49,40 @@ public: +//////////////////////////////////////////////////////////////////////////////// +// cWebadminRequestData + +/** The form parser callbacks for requests in the "/webadmin" and "/~webadmin" paths */ +class cWebadminRequestData : + public cHTTPFormParser::cCallbacks, + public cHTTPIncomingRequest::cUserData +{ +public: + cHTTPFormParser m_Form; + + + cWebadminRequestData(const cHTTPIncomingRequest & a_Request): + m_Form(a_Request, *this) + { + } + + // cHTTPFormParser::cCallbacks overrides. Files are ignored: + virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override + { + UNUSED(a_FileName); + } + virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override + { + UNUSED(a_Data); + UNUSED(a_Size); + } + virtual void OnFileEnd(cHTTPFormParser &) override {} +} ; + + + + + //////////////////////////////////////////////////////////////////////////////// // cWebAdmin: @@ -212,7 +246,7 @@ bool cWebAdmin::LoadLoginTemplate(void) -void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { if (!a_Request.HasAuth()) { @@ -229,12 +263,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT } // Check if the contents should be wrapped in the template: - AString BareURL = a_Request.GetBareURL(); + auto BareURL = a_Request.GetURLPath(); ASSERT(BareURL.length() > 0); bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~')); // Retrieve the request data: - cWebadminRequestData * Data = reinterpret_cast(a_Request.GetUserData()); + auto Data = std::static_pointer_cast(a_Request.GetUserData()); if (Data == nullptr) { a_Connection.SendStatusAndReason(500, "Bad UserData"); @@ -343,13 +377,14 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT Resp.SetContentType("text/html"); a_Connection.Send(Resp); a_Connection.Send(Template.c_str(), Template.length()); + a_Connection.FinishResponse(); } -void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { UNUSED(a_Request); @@ -364,7 +399,7 @@ void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPReq -void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { AString FileURL = a_Request.GetURL(); std::replace(FileURL.begin(), FileURL.end(), '\\', '/'); @@ -621,7 +656,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit) -void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { UNUSED(a_Connection); const AString & URL = a_Request.GetURL(); @@ -630,7 +665,7 @@ void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPReques (strncmp(URL.c_str(), "/~webadmin", 10) == 0) ) { - a_Request.SetUserData(new cWebadminRequestData(a_Request)); + a_Request.SetUserData(std::make_shared(a_Request)); return; } if (URL == "/") @@ -645,22 +680,22 @@ void cWebAdmin::OnRequestBegun(cHTTPServerConnection & a_Connection, cHTTPReques -void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) +void cWebAdmin::OnRequestBody(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) { UNUSED(a_Connection); - cRequestData * Data = reinterpret_cast(a_Request.GetUserData()); + auto Data = std::static_pointer_cast(a_Request.GetUserData()); if (Data == nullptr) { return; } - Data->OnBody(a_Data, a_Size); + Data->m_Form.Parse(a_Data, a_Size); } -void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) +void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { const AString & URL = a_Request.GetURL(); if ( @@ -679,24 +714,9 @@ void cWebAdmin::OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPReq { HandleFileRequest(a_Connection, a_Request); } - - // Delete any request data assigned to the request: - cRequestData * Data = reinterpret_cast(a_Request.GetUserData()); - delete Data; - Data = nullptr; } -//////////////////////////////////////////////////////////////////////////////// -// cWebAdmin::cWebadminRequestData - -void cWebAdmin::cWebadminRequestData::OnBody(const char * a_Data, size_t a_Size) -{ - m_Form.Parse(a_Data, a_Size); -} - - - diff --git a/src/WebAdmin.h b/src/WebAdmin.h index 29acb2664..5e48f597c 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -8,7 +8,7 @@ #include "Bindings/LuaState.h" #include "IniFile.h" #include "HTTP/HTTPServer.h" -#include "HTTP/HTTPFormParser.h" +#include "HTTP/HTTPMessage.h" @@ -171,46 +171,6 @@ public: static AString GetContentTypeFromFileExt(const AString & a_FileExtension); protected: - /** Common base class for request body data handlers */ - class cRequestData - { - public: - virtual ~cRequestData() {} // Force a virtual destructor in all descendants - - /** Called when a new chunk of body data is received */ - virtual void OnBody(const char * a_Data, size_t a_Size) = 0; - } ; - - /** The body handler for requests in the "/webadmin" and "/~webadmin" paths */ - class cWebadminRequestData : - public cRequestData, - public cHTTPFormParser::cCallbacks - { - public: - cHTTPFormParser m_Form; - - - cWebadminRequestData(cHTTPRequestParser & a_Request) : - m_Form(a_Request, *this) - { - } - - // cRequestData overrides: - virtual void OnBody(const char * a_Data, size_t a_Size) override; - - // cHTTPFormParser::cCallbacks overrides. Files are ignored: - virtual void OnFileStart(cHTTPFormParser &, const AString & a_FileName) override - { - UNUSED(a_FileName); - } - virtual void OnFileData(cHTTPFormParser &, const char * a_Data, size_t a_Size) override - { - UNUSED(a_Data); - UNUSED(a_Size); - } - virtual void OnFileEnd(cHTTPFormParser &) override {} - } ; - /** Set to true if Init() succeeds and the webadmin isn't to be disabled */ bool m_IsInitialized; @@ -236,18 +196,18 @@ protected: cHTTPServer m_HTTPServer; /** Handles requests coming to the "/webadmin" or "/~webadmin" URLs */ - void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Handles requests for the root page */ - void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Handles requests for a file */ - void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request); + void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); // cHTTPServer::cCallbacks overrides: - virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override; - virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request, const char * a_Data, size_t a_Size) override; - virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPRequestParser & a_Request) override; + virtual void OnRequestBegun (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override; + virtual void OnRequestBody (cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) override; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) override; } ; // tolua_export diff --git a/tests/HTTP/CMakeLists.txt b/tests/HTTP/CMakeLists.txt index c11c601d9..233e5c0cb 100644 --- a/tests/HTTP/CMakeLists.txt +++ b/tests/HTTP/CMakeLists.txt @@ -11,7 +11,7 @@ add_definitions(-DTEST_GLOBALS=1) set (HTTP_SRCS ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.cpp ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.cpp - ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPResponseParser.cpp + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.cpp ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp ) @@ -19,7 +19,7 @@ set (HTTP_SRCS set (HTTP_HDRS ${CMAKE_SOURCE_DIR}/src/HTTP/EnvelopeParser.h ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessage.h - ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPResponseParser.h + ${CMAKE_SOURCE_DIR}/src/HTTP/HTTPMessageParser.h ${CMAKE_SOURCE_DIR}/src/HTTP/TransferEncodingParser.h ${CMAKE_SOURCE_DIR}/src/StringUtils.h ) @@ -39,15 +39,22 @@ endif() # Define individual tests: -# HTTPResponseParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called: -add_executable(HTTPResponseParser_file-exe HTTPResponseParser_file.cpp) -target_link_libraries(HTTPResponseParser_file-exe HTTP) +# HTTPMessageParser_file: Feed file contents into a cHTTPResponseParser and print the callbacks as they're called: +add_executable(HTTPMessageParser_file-exe HTTPMessageParser_file.cpp) +target_link_libraries(HTTPMessageParser_file-exe HTTP) -# Test parsing the file in 2-byte chunks (should go from response line parsing through headers parsing to body parsing, each within a different step): -add_test(NAME HTTPResponseParser_file-test1-2 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data 2) +# Test parsing the response file in 2-byte chunks (should go from response line parsing through headers parsing to body parsing, each within a different step): +add_test(NAME HTTPMessageParser_file-test1-2 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 2) -# Test parsing the file in 128-byte chunks (should parse response line and part of headers in one step, the rest in another step): -add_test(NAME HTTPResponseParser_file-test1-128 COMMAND HTTPResponseParser_file-exe HTTPResponse1.data 128) +# Test parsing the response file in 128-byte chunks (should parse response line and part of headers in one step, the rest in another step): +add_test(NAME HTTPMessageParser_file-test1-128 COMMAND HTTPMessageParser_file-exe HTTPResponse1.data 128) + +# Test parsing a chunked-encoding response: +add_test(NAME HTTPMessageParser_file-test2 COMMAND HTTPMessageParser_file-exe HTTPResponse2.data) + +# Test parsing the request file in 2-byte chunks (should go from request line parsing through headers parsing to body parsing, each within a different step): +add_test(NAME HTTPMessageParser_file-test3-2 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 2) + +# Test parsing the request file in 512-byte chunks (should process everything in a single call): +add_test(NAME HTTPMessageParser_file-test4-512 COMMAND HTTPMessageParser_file-exe HTTPRequest1.data 512) -# Test parsing a chunked-encoding content: -add_test(NAME HTTPResponseParser_file-test2 COMMAND HTTPResponseParser_file-exe HTTPResponse2.data) diff --git a/tests/HTTP/HTTPMessageParser_file.cpp b/tests/HTTP/HTTPMessageParser_file.cpp new file mode 100644 index 000000000..cd6dfa605 --- /dev/null +++ b/tests/HTTP/HTTPMessageParser_file.cpp @@ -0,0 +1,153 @@ + +// HTTPMessageParser_file.cpp + +// Implements a test that feeds file contents into a cHTTPMessageParser instance and prints all callbacks + +#include "Globals.h" +#include "HTTP/HTTPMessageParser.h" + + + + + +/** Maximum size of the input buffer, through which the file is read */ +static const size_t MAX_BUF = 4096; + + + + + +class cCallbacks: + public cHTTPMessageParser::cCallbacks +{ + typedef cHTTPMessageParser::cCallbacks Super; +public: + cCallbacks(void) + { + printf("cCallbacks created\n"); + } + + // cHTTPResponseParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override + { + printf("Error: \"%s\"\n", a_ErrorDescription.c_str()); + } + + /** Called when the first line (request / status) is fully parsed. */ + virtual void OnFirstLine(const AString & a_FirstLine) override + { + printf("First line: \"%s\"\n", a_FirstLine.c_str()); + } + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override + { + printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str()); + } + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) override + { + printf("Headers finished\n"); + } + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) override + { + AString hexDump; + CreateHexDump(hexDump, a_Data, a_Size, 16); + printf("Body data: %u bytes\n%s", static_cast(a_Size), hexDump.c_str()); + } + + virtual void OnBodyFinished(void) override + { + printf("Body finished\n"); + } +}; + + + + + +int main(int argc, char * argv[]) +{ + printf("HTTPMessageParser_file beginning\n"); + + // Open the input file: + if (argc <= 1) + { + printf("Usage: %s []\n", argv[0]); + return 1; + } + FILE * f; + if (strcmp(argv[1], "-") == 0) + { + f = stdin; + } + else + { + f = fopen(argv[1], "rb"); + if (f == nullptr) + { + printf("Cannot open file \"%s\". Aborting.\n", argv[1]); + return 2; + } + } + + // If a third param is present, use it as the buffer size + size_t bufSize = MAX_BUF; + if (argc >= 3) + { + if (!StringToInteger(argv[2], bufSize) || (bufSize == 0)) + { + bufSize = MAX_BUF; + printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast(bufSize)); + } + if (bufSize > MAX_BUF) + { + bufSize = MAX_BUF; + printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast(bufSize), static_cast(bufSize)); + } + } + + // Feed the file contents into the parser: + cCallbacks callbacks; + cHTTPMessageParser parser(callbacks); + while (true) + { + char buf[MAX_BUF]; + auto numBytes = fread(buf, 1, bufSize, f); + if (numBytes == 0) + { + printf("Read 0 bytes from file (EOF?), terminating\n"); + break; + } + auto numConsumed = parser.Parse(buf, numBytes); + if (numConsumed == AString::npos) + { + printf("Parser indicates there was an error, terminating parsing.\n"); + break; + } + ASSERT(numConsumed <= numBytes); + if (numConsumed < numBytes) + { + printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast(numBytes - numConsumed)); + } + } + if (!parser.IsFinished()) + { + printf("Parser indicates an incomplete stream.\n"); + } + + // Close the input file: + if (f != stdin) + { + fclose(f); + } + + return 0; +} + + + + diff --git a/tests/HTTP/HTTPRequest1.data b/tests/HTTP/HTTPRequest1.data new file mode 100644 index 000000000..cac43c06c --- /dev/null +++ b/tests/HTTP/HTTPRequest1.data @@ -0,0 +1,5 @@ +GET /some/url HTTP/1.1 +Note: This is a test of a regular request +Content-Length: 3 + +bla \ No newline at end of file diff --git a/tests/HTTP/HTTPRequest2.data b/tests/HTTP/HTTPRequest2.data new file mode 100644 index 000000000..f7dbede4f --- /dev/null +++ b/tests/HTTP/HTTPRequest2.data @@ -0,0 +1,3 @@ +GET /some/url HTTP/1.1 +Note: This is a test of a regular body-less request + diff --git a/tests/HTTP/HTTPRequestParser_file.cpp b/tests/HTTP/HTTPRequestParser_file.cpp new file mode 100644 index 000000000..98f11e8b1 --- /dev/null +++ b/tests/HTTP/HTTPRequestParser_file.cpp @@ -0,0 +1,153 @@ + +// HTTPResponseParser_file.cpp + +// Implements a test that feeds file contents into a cHTTPResponseParser instance and prints all callbacks + +#include "Globals.h" +#include "HTTP/HTTPRequestParser.h" + + + + + +/** Maximum size of the input buffer, through which the file is read */ +static const size_t MAX_BUF = 4096; + + + + + +class cCallbacks: + public cHTTPRequestParser::cCallbacks +{ + typedef cHTTPResponseParser::cCallbacks Super; +public: + cCallbacks(void) + { + printf("cCallbacks created\n"); + } + + // cHTTPResponseParser::cCallbacks overrides: + virtual void OnError(const AString & a_ErrorDescription) override + { + printf("Error: \"%s\"\n", a_ErrorDescription.c_str()); + } + + /** Called when the status line is fully parsed. */ + virtual void OnStatusLine(const AString & a_StatusLine) override + { + printf("Status line: \"%s\"\n", a_StatusLine.c_str()); + } + + /** Called when a single header line is parsed. */ + virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override + { + printf("Header line: \"%s\": \"%s\"\n", a_Key.c_str(), a_Value.c_str()); + } + + /** Called when all the headers have been parsed. */ + virtual void OnHeadersFinished(void) override + { + printf("Headers finished\n"); + } + + /** Called for each chunk of the incoming body data. */ + virtual void OnBodyData(const void * a_Data, size_t a_Size) override + { + AString hexDump; + CreateHexDump(hexDump, a_Data, a_Size, 16); + printf("Body data: %u bytes\n%s", static_cast(a_Size), hexDump.c_str()); + } + + virtual void OnBodyFinished(void) override + { + printf("Body finished\n"); + } +}; + + + + + +int main(int argc, char * argv[]) +{ + printf("HTTPResponseParser_file beginning\n"); + + // Open the input file: + if (argc <= 1) + { + printf("Usage: %s []\n", argv[0]); + return 1; + } + FILE * f; + if (strcmp(argv[1], "-") == 0) + { + f = stdin; + } + else + { + f = fopen(argv[1], "rb"); + if (f == nullptr) + { + printf("Cannot open file \"%s\". Aborting.\n", argv[1]); + return 2; + } + } + + // If a third param is present, use it as the buffer size + size_t bufSize = MAX_BUF; + if (argc >= 3) + { + if (!StringToInteger(argv[2], bufSize) || (bufSize == 0)) + { + bufSize = MAX_BUF; + printf("\"%s\" is not a valid buffer size, using the default of %u instead.\n", argv[2], static_cast(bufSize)); + } + if (bufSize > MAX_BUF) + { + bufSize = MAX_BUF; + printf("\"%s\" is too large, maximum buffer size is %u. Using the size %u instead.\n", argv[2], static_cast(bufSize), static_cast(bufSize)); + } + } + + // Feed the file contents into the parser: + cCallbacks callbacks; + cHTTPResponseParser parser(callbacks); + while (!feof(f)) + { + char buf[MAX_BUF]; + auto numBytes = fread(buf, 1, bufSize, f); + if (numBytes == 0) + { + printf("Read 0 bytes from file (EOF?), terminating\n"); + break; + } + auto numConsumed = parser.Parse(buf, numBytes); + if (numConsumed == AString::npos) + { + printf("Parser indicates there was an error, terminating parsing.\n"); + break; + } + ASSERT(numConsumed <= numBytes); + if (numConsumed < numBytes) + { + printf("Parser indicates stream end, but there's more data (at least %u bytes) in the file.\n", static_cast(numBytes - numConsumed)); + } + } + if (!parser.IsFinished()) + { + printf("Parser indicates an incomplete stream.\n"); + } + + // Close the input file: + if (f != stdin) + { + fclose(f); + } + + return 0; +} + + + + diff --git a/tests/HTTP/HTTPResponse1.data b/tests/HTTP/HTTPResponse1.data index b97e58afd..97e0b23c3 100644 --- a/tests/HTTP/HTTPResponse1.data +++ b/tests/HTTP/HTTPResponse1.data @@ -2,6 +2,7 @@ HTTP/1.0 200 OK Note: This is a test of a regular response with Content-Length set (identity transfer encoding) Note2: The above header also tests multi-line header lines +Note3: The data is 2 bytes longer than the actual request, parser should indicate 2 extra bytes at the end Header1: Value1 Header2: Value2 Content-Length: 3 -- cgit v1.2.3 From 71a1fa81f00cf897d91e8f76cc7e646dd61cc2ff Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 20 Feb 2016 12:33:27 +0100 Subject: Renamed HTTPResponse to HTTPOutgoingResponse. --- src/HTTP/HTTPMessage.cpp | 4 ++-- src/HTTP/HTTPMessage.h | 8 ++++---- src/HTTP/HTTPServer.h | 1 - src/HTTP/HTTPServerConnection.cpp | 2 +- src/HTTP/HTTPServerConnection.h | 4 ++-- src/WebAdmin.cpp | 8 ++++---- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/HTTP/HTTPMessage.cpp b/src/HTTP/HTTPMessage.cpp index 5a7b86315..a535108e3 100644 --- a/src/HTTP/HTTPMessage.cpp +++ b/src/HTTP/HTTPMessage.cpp @@ -69,7 +69,7 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) //////////////////////////////////////////////////////////////////////////////// // cHTTPResponse: -cHTTPResponse::cHTTPResponse(void) : +cHTTPOutgoingResponse::cHTTPOutgoingResponse(void) : super(mkResponse) { } @@ -78,7 +78,7 @@ cHTTPResponse::cHTTPResponse(void) : -void cHTTPResponse::AppendToData(AString & a_DataStream) const +void cHTTPOutgoingResponse::AppendToData(AString & a_DataStream) const { a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); a_DataStream.append(m_ContentType); diff --git a/src/HTTP/HTTPMessage.h b/src/HTTP/HTTPMessage.h index 683734b67..9afcd5b97 100644 --- a/src/HTTP/HTTPMessage.h +++ b/src/HTTP/HTTPMessage.h @@ -65,17 +65,17 @@ protected: -class cHTTPResponse : +/** Stores outgoing response headers and serializes them to an HTTP data stream. */ +class cHTTPOutgoingResponse : public cHTTPMessage { typedef cHTTPMessage super; public: - cHTTPResponse(void); + cHTTPOutgoingResponse(void); /** Appends the response to the specified datastream - response line and headers. - The body will be sent later directly through cConnection::Send() - */ + The body will be sent later directly through cConnection::Send() */ void AppendToData(AString & a_DataStream) const; } ; diff --git a/src/HTTP/HTTPServer.h b/src/HTTP/HTTPServer.h index d06e13cac..1de0a6ce9 100644 --- a/src/HTTP/HTTPServer.h +++ b/src/HTTP/HTTPServer.h @@ -23,7 +23,6 @@ class cHTTPMessage; class cHTTPRequestParser; class cHTTPIncomingRequest; -class cHTTPResponse; class cHTTPServerConnection; diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp index 0439c8b93..9edec2886 100644 --- a/src/HTTP/HTTPServerConnection.cpp +++ b/src/HTTP/HTTPServerConnection.cpp @@ -59,7 +59,7 @@ void cHTTPServerConnection::SendNeedAuth(const AString & a_Realm) -void cHTTPServerConnection::Send(const cHTTPResponse & a_Response) +void cHTTPServerConnection::Send(const cHTTPOutgoingResponse & a_Response) { ASSERT(m_CurrentRequest != nullptr); AString toSend; diff --git a/src/HTTP/HTTPServerConnection.h b/src/HTTP/HTTPServerConnection.h index 62d3b7182..4390471d0 100644 --- a/src/HTTP/HTTPServerConnection.h +++ b/src/HTTP/HTTPServerConnection.h @@ -18,7 +18,7 @@ // fwd: class cHTTPServer; -class cHTTPResponse; +class cHTTPOutgoingResponse; class cHTTPIncomingRequest; @@ -45,7 +45,7 @@ public: void SendNeedAuth(const AString & a_Realm); /** Sends the headers contained in a_Response */ - void Send(const cHTTPResponse & a_Response); + void Send(const cHTTPOutgoingResponse & a_Response); /** Sends the data as the response (may be called multiple times) */ void Send(const void * a_Data, size_t a_Size); diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 95a1bcdbd..08e90ea13 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -314,7 +314,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT { if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template)) { - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType("text/html"); a_Connection.Send(Resp); a_Connection.Send(Template.c_str(), Template.length()); @@ -373,7 +373,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTT Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount()); ReplaceString(Template, "{NUMCHUNKS}", NumChunks); - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType("text/html"); a_Connection.Send(Resp); a_Connection.Send(Template.c_str(), Template.length()); @@ -388,7 +388,7 @@ void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPInc { UNUSED(a_Request); - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType("text/html"); a_Connection.Send(Resp); a_Connection.Send(m_LoginTemplate); @@ -441,7 +441,7 @@ void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPInc } // Send the response: - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType(ContentType); a_Connection.Send(Resp); a_Connection.Send(Content); -- cgit v1.2.3 From 6ff389f6d48fd66c77286ac384031863ccf2a768 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 21 Feb 2016 11:34:16 +0100 Subject: HTTP: Fixed typos and bad leftovers. --- src/HTTP/HTTPMessage.cpp | 2 +- src/HTTP/SslHTTPServerConnection.h | 2 +- src/HTTP/TransferEncodingParser.cpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/HTTP/HTTPMessage.cpp b/src/HTTP/HTTPMessage.cpp index a535108e3..cf7525b6c 100644 --- a/src/HTTP/HTTPMessage.cpp +++ b/src/HTTP/HTTPMessage.cpp @@ -67,7 +67,7 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) //////////////////////////////////////////////////////////////////////////////// -// cHTTPResponse: +// cHTTPOutgoingResponse: cHTTPOutgoingResponse::cHTTPOutgoingResponse(void) : super(mkResponse) diff --git a/src/HTTP/SslHTTPServerConnection.h b/src/HTTP/SslHTTPServerConnection.h index 6032a2bd0..eceb80fb7 100644 --- a/src/HTTP/SslHTTPServerConnection.h +++ b/src/HTTP/SslHTTPServerConnection.h @@ -1,7 +1,7 @@ // SslHTTPServerConnection.h -// Declared the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link +// Declares the cSslHTTPServerConnection class representing a HTTP connection made over an SSL link diff --git a/src/HTTP/TransferEncodingParser.cpp b/src/HTTP/TransferEncodingParser.cpp index d95b3d08e..a14900e9c 100644 --- a/src/HTTP/TransferEncodingParser.cpp +++ b/src/HTTP/TransferEncodingParser.cpp @@ -1,7 +1,7 @@ // TransferEncodingParser.cpp -// Implements the cTransferEncodingParser class and its descendants representing the parser for the various transfer encodings (chunked etc.) +// Implements the cTransferEncodingParser class and its descendants representing the parsers for the various transfer encodings (chunked etc.) #include "Globals.h" #include "TransferEncodingParser.h" @@ -50,6 +50,7 @@ protected: When in psChunkLength, the value is the currently parsed length digits. */ size_t m_ChunkDataLengthLeft; + /** The parser used for the last (empty) chunk's trailer data */ cEnvelopeParser m_TrailerParser; -- cgit v1.2.3