diff options
author | Mattes D <github@xoft.cz> | 2016-03-01 16:58:43 +0100 |
---|---|---|
committer | Mattes D <github@xoft.cz> | 2016-03-01 16:58:43 +0100 |
commit | 7cc8f8dd2213cade66633d196f579895385a869a (patch) | |
tree | 83219a6eaf87a2050c10662fb8caa5d1514a6af4 | |
parent | Merge pull request #3057 from tonibm19/master (diff) | |
parent | HTTP: Fixed typos and bad leftovers. (diff) | |
download | cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar.gz cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar.bz2 cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar.lz cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar.xz cuberite-7cc8f8dd2213cade66633d196f579895385a869a.tar.zst cuberite-7cc8f8dd2213cade66633d196f579895385a869a.zip |
-rw-r--r-- | .gitattributes | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/Bindings/ManualBindings.cpp | 2 | ||||
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/HTTP/CMakeLists.txt (renamed from src/HTTPServer/CMakeLists.txt) | 14 | ||||
-rw-r--r-- | src/HTTP/EnvelopeParser.cpp (renamed from src/HTTPServer/EnvelopeParser.cpp) | 6 | ||||
-rw-r--r-- | src/HTTP/EnvelopeParser.h (renamed from src/HTTPServer/EnvelopeParser.h) | 0 | ||||
-rw-r--r-- | src/HTTP/HTTPFormParser.cpp (renamed from src/HTTPServer/HTTPFormParser.cpp) | 6 | ||||
-rw-r--r-- | src/HTTP/HTTPFormParser.h (renamed from src/HTTPServer/HTTPFormParser.h) | 8 | ||||
-rw-r--r-- | src/HTTP/HTTPMessage.cpp | 159 | ||||
-rw-r--r-- | src/HTTP/HTTPMessage.h (renamed from src/HTTPServer/HTTPMessage.h) | 114 | ||||
-rw-r--r-- | src/HTTP/HTTPMessageParser.cpp | 222 | ||||
-rw-r--r-- | src/HTTP/HTTPMessageParser.h | 125 | ||||
-rw-r--r-- | src/HTTP/HTTPServer.cpp (renamed from src/HTTPServer/HTTPServer.cpp) | 115 | ||||
-rw-r--r-- | src/HTTP/HTTPServer.h (renamed from src/HTTPServer/HTTPServer.h) | 28 | ||||
-rw-r--r-- | src/HTTP/HTTPServerConnection.cpp | 240 | ||||
-rw-r--r-- | src/HTTP/HTTPServerConnection.h (renamed from src/HTTPServer/HTTPConnection.h) | 64 | ||||
-rw-r--r-- | src/HTTP/MultipartParser.cpp (renamed from src/HTTPServer/MultipartParser.cpp) | 0 | ||||
-rw-r--r-- | src/HTTP/MultipartParser.h (renamed from src/HTTPServer/MultipartParser.h) | 0 | ||||
-rw-r--r-- | src/HTTP/NameValueParser.cpp (renamed from src/HTTPServer/NameValueParser.cpp) | 0 | ||||
-rw-r--r-- | src/HTTP/NameValueParser.h (renamed from src/HTTPServer/NameValueParser.h) | 0 | ||||
-rw-r--r-- | src/HTTP/SslHTTPServerConnection.cpp (renamed from src/HTTPServer/SslHTTPConnection.cpp) | 12 | ||||
-rw-r--r-- | src/HTTP/SslHTTPServerConnection.h (renamed from src/HTTPServer/SslHTTPConnection.h) | 16 | ||||
-rw-r--r-- | src/HTTP/TransferEncodingParser.cpp | 394 | ||||
-rw-r--r-- | src/HTTP/TransferEncodingParser.h | 76 | ||||
-rw-r--r-- | src/HTTP/UrlParser.cpp (renamed from src/HTTPServer/UrlParser.cpp) | 0 | ||||
-rw-r--r-- | src/HTTP/UrlParser.h (renamed from src/HTTPServer/UrlParser.h) | 0 | ||||
-rw-r--r-- | src/HTTPServer/HTTPConnection.cpp | 278 | ||||
-rw-r--r-- | src/HTTPServer/HTTPMessage.cpp | 290 | ||||
-rw-r--r-- | src/Root.h | 2 | ||||
-rw-r--r-- | src/WebAdmin.cpp | 84 | ||||
-rw-r--r-- | src/WebAdmin.h | 56 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/HTTP/CMakeLists.txt | 60 | ||||
-rw-r--r-- | tests/HTTP/HTTPMessageParser_file.cpp | 153 | ||||
-rw-r--r-- | tests/HTTP/HTTPRequest1.data | 5 | ||||
-rw-r--r-- | tests/HTTP/HTTPRequest2.data | 3 | ||||
-rw-r--r-- | tests/HTTP/HTTPRequestParser_file.cpp | 153 | ||||
-rw-r--r-- | tests/HTTP/HTTPResponse1.data | 10 | ||||
-rw-r--r-- | tests/HTTP/HTTPResponse2.data | 15 | ||||
-rw-r--r-- | tests/HTTP/HTTPResponseParser_file.cpp | 153 |
41 files changed, 1973 insertions, 902 deletions
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..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) + @@ -263,6 +266,7 @@ if (MSVC) if (${SELF_TEST}) set_target_properties( Network + HTTP PROPERTIES FOLDER Lib ) set_target_properties( @@ -274,6 +278,7 @@ if (MSVC) creatable-exe EchoServer Google-exe + HTTPMessageParser_file-exe LoadablePieces NameLookup PROPERTIES FOLDER Tests 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/HTTPServer/CMakeLists.txt b/src/HTTP/CMakeLists.txt index a08a1fcf8..acb3ac2cd 100644 --- a/src/HTTPServer/CMakeLists.txt +++ b/src/HTTP/CMakeLists.txt @@ -6,31 +6,35 @@ include_directories ("${PROJECT_SOURCE_DIR}/../") SET (SRCS EnvelopeParser.cpp - HTTPConnection.cpp HTTPFormParser.cpp HTTPMessage.cpp + HTTPMessageParser.cpp HTTPServer.cpp + HTTPServerConnection.cpp MultipartParser.cpp NameValueParser.cpp - SslHTTPConnection.cpp + SslHTTPServerConnection.cpp + TransferEncodingParser.cpp UrlParser.cpp ) SET (HDRS EnvelopeParser.h - HTTPConnection.h HTTPFormParser.h HTTPMessage.h + HTTPMessageParser.h HTTPServer.h + HTTPServerConnection.h MultipartParser.h NameValueParser.h - SslHTTPConnection.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(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/EnvelopeParser.cpp b/src/HTTP/EnvelopeParser.cpp index 407e9dcfc..1c49b643f 100644 --- a/src/HTTPServer/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/HTTPServer/EnvelopeParser.h b/src/HTTP/EnvelopeParser.h index 2fa930539..2fa930539 100644 --- a/src/HTTPServer/EnvelopeParser.h +++ b/src/HTTP/EnvelopeParser.h diff --git a/src/HTTPServer/HTTPFormParser.cpp b/src/HTTP/HTTPFormParser.cpp index 497f84033..ea5da3c18 100644 --- a/src/HTTPServer/HTTPFormParser.cpp +++ b/src/HTTP/HTTPFormParser.cpp @@ -13,7 +13,7 @@ -cHTTPFormParser::cHTTPFormParser(cHTTPRequest & 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 cHTTPRequest & a_Request) +bool cHTTPFormParser::HasFormData(const cHTTPIncomingRequest & 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 cHTTPIncomingRequest & 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/HTTP/HTTPFormParser.h index f6cbe047f..6bf3e7d78 100644 --- a/src/HTTPServer/HTTPFormParser.h +++ b/src/HTTP/HTTPFormParser.h @@ -15,7 +15,7 @@ // fwd: -class cHTTPRequest; +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(cHTTPRequest & 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 cHTTPRequest & 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 cHTTPRequest & 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 new file mode 100644 index 000000000..cf7525b6c --- /dev/null +++ b/src/HTTP/HTTPMessage.cpp @@ -0,0 +1,159 @@ + +// 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; + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cHTTPOutgoingResponse: + +cHTTPOutgoingResponse::cHTTPOutgoingResponse(void) : + super(mkResponse) +{ +} + + + + + +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); + 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"); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// 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/HTTPServer/HTTPMessage.h b/src/HTTP/HTTPMessage.h index 72ecb67d1..9afcd5b97 100644 --- a/src/HTTPServer/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; } @@ -49,7 +49,8 @@ protected: eKind m_Kind; - cNameValueMap m_Headers; + /** Map of headers, with their keys lowercased. */ + AStringMap m_Headers; /** Type of the content; parsed by AddHeader(), set directly by SetContentLength() */ AString m_ContentType; @@ -64,22 +65,42 @@ protected: -class cHTTPRequest : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks +/** Stores outgoing response headers and serializes them to an HTTP data stream. */ +class cHTTPOutgoingResponse : + public cHTTPMessage { typedef cHTTPMessage super; public: - cHTTPRequest(void); + cHTTPOutgoingResponse(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; +} ; + - /** 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); } + + +/** 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<cUserData> 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; } @@ -87,20 +108,10 @@ public: /** 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 the path part of the URL. */ + AString GetURLPath(void) const; - /** 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 */ + /** 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 */ @@ -111,15 +122,17 @@ public: bool DoesAllowKeepAlive(void) const { return m_AllowKeepAlive; } -protected: - /** Parser for the envelope data */ - cEnvelopeParser m_EnvelopeParser; + /** Attaches any kind of data to this request, to be later retrieved by GetUserData(). */ + void SetUserData(cUserDataPtr a_UserData) { m_UserData = a_UserData; } - /** True if the data received so far is parsed successfully. When false, all further parsing is skipped */ - bool m_IsValid; + /** Returns the data attached to this request by the class client. */ + cUserDataPtr GetUserData(void) { return m_UserData; } - /** Bufferred incoming data, while parsing for the request line */ - AString m_IncomingHeaderData; + /** 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; @@ -127,9 +140,6 @@ protected: /** 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; @@ -143,34 +153,6 @@ protected: 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 -{ - 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; -} ; - - - - + /** 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/HTTPServer/HTTPServer.cpp b/src/HTTP/HTTPServer.cpp index 814a87fe4..5a5bee045 100644 --- a/src/HTTPServer/HTTPServer.cpp +++ b/src/HTTP/HTTPServer.cpp @@ -5,10 +5,10 @@ #include "Globals.h" #include "HTTPServer.h" -#include "HTTPMessage.h" -#include "HTTPConnection.h" +#include "HTTPMessageParser.h" +#include "HTTPServerConnection.h" #include "HTTPFormParser.h" -#include "SslHTTPConnection.h" +#include "SslHTTPServerConnection.h" @@ -24,102 +24,6 @@ -class cDebugCallbacks : - public cHTTPServer::cCallbacks, - protected cHTTPFormParser::cCallbacks -{ - virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override - { - UNUSED(a_Connection); - - if (cHTTPFormParser::HasFormData(a_Request)) - { - a_Request.SetUserData(new cHTTPFormParser(a_Request, *this)); - } - } - - - virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) override - { - UNUSED(a_Connection); - - cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData()); - if (FormParser != nullptr) - { - FormParser->Parse(a_Data, a_Size); - } - } - - - virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override - { - cHTTPFormParser * FormParser = reinterpret_cast<cHTTPFormParser *>(a_Request.GetUserData()); - if (FormParser != nullptr) - { - if (FormParser->Finish()) - { - cHTTPResponse Resp; - Resp.SetContentType("text/html"); - a_Connection.Send(Resp); - a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n"); - for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) - { - a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str())); - } // for itr - FormParser[] - a_Connection.Send("</table></body></html>"); - 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: @@ -268,11 +172,11 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo if (m_Cert.get() != nullptr) { - return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey); + return std::make_shared<cSslHTTPServerConnection>(*this, m_Cert, m_CertPrivKey); } else { - return std::make_shared<cHTTPConnection>(*this); + return std::make_shared<cHTTPServerConnection>(*this); } } @@ -280,7 +184,7 @@ cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_Remo -void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & 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(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, 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<const char *>(a_Data), a_Size); } -void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & 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/HTTPServer/HTTPServer.h b/src/HTTP/HTTPServer.h index 2a094b413..1de0a6ce9 100644 --- a/src/HTTPServer/HTTPServer.h +++ b/src/HTTP/HTTPServer.h @@ -21,11 +21,9 @@ // fwd: class cHTTPMessage; -class cHTTPRequest; -class cHTTPResponse; -class cHTTPConnection; - -typedef std::vector<cHTTPConnection *> cHTTPConnections; +class cHTTPRequestParser; +class cHTTPIncomingRequest; +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, 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(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; + virtual void OnRequestFinished(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & 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, 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(cHTTPConnection & a_Connection, cHTTPRequest & 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 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, cHTTPIncomingRequest & a_Request); } ; diff --git a/src/HTTP/HTTPServerConnection.cpp b/src/HTTP/HTTPServerConnection.cpp new file mode 100644 index 000000000..9edec2886 --- /dev/null +++ b/src/HTTP/HTTPServerConnection.cpp @@ -0,0 +1,240 @@ + +// 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 "HTTPMessageParser.h" +#include "HTTPServer.h" + + + + + +cHTTPServerConnection::cHTTPServerConnection(cHTTPServer & a_HTTPServer) : + m_HTTPServer(a_HTTPServer), + m_Parser(*this), + m_CurrentRequest(nullptr) +{ + // LOGD("HTTP: New connection at %p", this); +} + + + + + +cHTTPServerConnection::~cHTTPServerConnection() +{ + // LOGD("HTTP: Connection deleting: %p", this); + m_CurrentRequest.reset(); +} + + + + + +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<unsigned>(a_Response.size()))); + SendData(a_Response.data(), a_Response.size()); + m_CurrentRequest.reset(); + m_Parser.Reset(); +} + + + + + +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_CurrentRequest.reset(); + m_Parser.Reset(); +} + + + + + +void cHTTPServerConnection::Send(const cHTTPOutgoingResponse & a_Response) +{ + ASSERT(m_CurrentRequest != nullptr); + AString toSend; + a_Response.AppendToData(toSend); + SendData(toSend); +} + + + + + +void cHTTPServerConnection::Send(const void * a_Data, size_t a_Size) +{ + 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); + SendData("\r\n"); +} + + + + + +void cHTTPServerConnection::FinishResponse(void) +{ + ASSERT(m_CurrentRequest != nullptr); + SendData("0\r\n\r\n"); + m_CurrentRequest.reset(); + m_Parser.Reset(); +} + + + + + +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); + + m_Parser.Parse(a_Data, a_Size); +} + + + + + +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::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/HTTPServer/HTTPConnection.h b/src/HTTP/HTTPServerConnection.h index 414075411..4390471d0 100644 --- a/src/HTTPServer/HTTPConnection.h +++ b/src/HTTP/HTTPServerConnection.h @@ -10,6 +10,7 @@ #pragma once #include "../OSSupport/Network.h" +#include "HTTPMessageParser.h" @@ -17,39 +18,34 @@ // fwd: class cHTTPServer; -class cHTTPResponse; -class cHTTPRequest; +class cHTTPOutgoingResponse; +class cHTTPIncomingRequest; - -class cHTTPConnection : - public cTCPLink::cCallbacks +class cHTTPServerConnection : + public cTCPLink::cCallbacks, + public cHTTPMessageParser::cCallbacks { public: + /** Creates a new instance, connected to the specified HTTP server instance */ + cHTTPServerConnection(cHTTPServer & a_HTTPServer); - 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(); + // 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 */ - 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); @@ -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. */ - 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; + std::unique_ptr<cHTTPIncomingRequest> 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); @@ -116,7 +110,7 @@ protected: } } ; -typedef std::vector<cHTTPConnection *> cHTTPConnections; +typedef std::vector<cHTTPServerConnection *> cHTTPServerConnections; diff --git a/src/HTTPServer/MultipartParser.cpp b/src/HTTP/MultipartParser.cpp index 09f4fd02a..09f4fd02a 100644 --- a/src/HTTPServer/MultipartParser.cpp +++ b/src/HTTP/MultipartParser.cpp diff --git a/src/HTTPServer/MultipartParser.h b/src/HTTP/MultipartParser.h index 4f20b2bed..4f20b2bed 100644 --- a/src/HTTPServer/MultipartParser.h +++ b/src/HTTP/MultipartParser.h diff --git a/src/HTTPServer/NameValueParser.cpp b/src/HTTP/NameValueParser.cpp index f759c4d21..f759c4d21 100644 --- a/src/HTTPServer/NameValueParser.cpp +++ b/src/HTTP/NameValueParser.cpp diff --git a/src/HTTPServer/NameValueParser.h b/src/HTTP/NameValueParser.h index e205079db..e205079db 100644 --- a/src/HTTPServer/NameValueParser.h +++ b/src/HTTP/NameValueParser.h diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTP/SslHTTPServerConnection.cpp index 1cbe02312..547e6de3a 100644 --- a/src/HTTPServer/SslHTTPConnection.cpp +++ b/src/HTTP/SslHTTPServerConnection.cpp @@ -1,17 +1,17 @@ // SslHTTPConnection.cpp -// Implements the cSslHTTPConnection class representing a HTTP connection made over a SSL link +// Implements the cSslHTTPServerConnection class representing a HTTP connection made over a SSL link #include "Globals.h" -#include "SslHTTPConnection.h" +#include "SslHTTPServerConnection.h" #include "HTTPServer.h" -cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : +cSslHTTPServerConnection::cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey) : super(a_HTTPServer), m_Ssl(64000), m_Cert(a_Cert), @@ -25,7 +25,7 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce -cSslHTTPConnection::~cSslHTTPConnection() +cSslHTTPServerConnection::~cSslHTTPServerConnection() { m_Ssl.NotifyClose(); } @@ -34,7 +34,7 @@ cSslHTTPConnection::~cSslHTTPConnection() -void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) +void cSslHTTPServerConnection::OnReceivedData(const char * a_Data, size_t a_Size) { // Process the received data: const char * Data = a_Data; @@ -77,7 +77,7 @@ void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size) -void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size) +void cSslHTTPServerConnection::SendData(const void * a_Data, size_t a_Size) { const char * OutgoingData = reinterpret_cast<const char *>(a_Data); size_t pos = 0; diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTP/SslHTTPServerConnection.h index b35bd8ba0..eceb80fb7 100644 --- a/src/HTTPServer/SslHTTPConnection.h +++ b/src/HTTP/SslHTTPServerConnection.h @@ -1,7 +1,7 @@ -// SslHTTPConnection.h +// SslHTTPServerConnection.h -// Declared the cSslHTTPConnection class representing a HTTP connection made over a SSL link +// Declares the cSslHTTPServerConnection class representing a HTTP connection made over an SSL link @@ -9,24 +9,24 @@ #pragma once -#include "HTTPConnection.h" +#include "HTTPServerConnection.h" #include "PolarSSL++/BufferedSslContext.h" -class cSslHTTPConnection : - public cHTTPConnection +class cSslHTTPServerConnection : + public cHTTPServerConnection { - typedef cHTTPConnection super; + 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. */ - cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); + cSslHTTPServerConnection(cHTTPServer & a_HTTPServer, const cX509CertPtr & a_Cert, const cCryptoKeyPtr & a_PrivateKey); - ~cSslHTTPConnection(); + ~cSslHTTPServerConnection(); protected: cBufferedSslContext m_Ssl; diff --git a/src/HTTP/TransferEncodingParser.cpp b/src/HTTP/TransferEncodingParser.cpp new file mode 100644 index 000000000..a14900e9c --- /dev/null +++ b/src/HTTP/TransferEncodingParser.cpp @@ -0,0 +1,394 @@ + +// TransferEncodingParser.cpp + +// Implements the cTransferEncodingParser class and its descendants representing the parsers 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; + + /** The parser used for the last (empty) chunk's trailer data */ + 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: <hexnumber>[;<trailer>]<CR><LF> + // 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<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - '0'); + break; + } + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + { + m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(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<decltype(m_ChunkDataLengthLeft)>(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: <trailer><CR><LF> + // 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: <LF> + 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: <CR> + 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: <LF> + 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<cChunkedTEParser>(a_Callbacks); + } + if (a_TransferEncoding == "identity") + { + return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength); + } + if (a_TransferEncoding.empty()) + { + return std::make_shared<cIdentityTEParser>(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<cTransferEncodingParser> 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/HTTP/UrlParser.cpp index 05db3e413..05db3e413 100644 --- a/src/HTTPServer/UrlParser.cpp +++ b/src/HTTP/UrlParser.cpp diff --git a/src/HTTPServer/UrlParser.h b/src/HTTP/UrlParser.h index 15a63e05d..15a63e05d 100644 --- a/src/HTTPServer/UrlParser.h +++ b/src/HTTP/UrlParser.h 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<unsigned>(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<size_t>(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/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp deleted file mode 100644 index 360145a9a..000000000 --- a/src/HTTPServer/HTTPMessage.cpp +++ /dev/null @@ -1,290 +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) -{ - AString Key = StrToLower(a_Key); - cNameValueMap::iterator 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; - } - } -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// 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: - -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 (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); 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/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 <thread> diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 29e1bbccf..08e90ea13 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -12,8 +12,8 @@ #include "Server.h" #include "Root.h" -#include "HTTPServer/HTTPMessage.h" -#include "HTTPServer/HTTPConnection.h" +#include "HTTP/HTTPServerConnection.h" +#include "HTTP/HTTPFormParser.h" @@ -50,6 +50,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: cWebAdmin::cWebAdmin(void) : @@ -212,7 +246,7 @@ bool cWebAdmin::LoadLoginTemplate(void) -void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { if (!a_Request.HasAuth()) { @@ -229,12 +263,12 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque } // 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<cWebadminRequestData *>(a_Request.GetUserData()); + auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData()); if (Data == nullptr) { a_Connection.SendStatusAndReason(500, "Bad UserData"); @@ -280,7 +314,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque { 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()); @@ -339,21 +373,22 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque 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()); + a_Connection.FinishResponse(); } -void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { UNUSED(a_Request); - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType("text/html"); a_Connection.Send(Resp); a_Connection.Send(m_LoginTemplate); @@ -364,7 +399,7 @@ void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & -void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) +void cWebAdmin::HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request) { AString FileURL = a_Request.GetURL(); std::replace(FileURL.begin(), FileURL.end(), '\\', '/'); @@ -406,7 +441,7 @@ void cWebAdmin::HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & } // Send the response: - cHTTPResponse Resp; + cHTTPOutgoingResponse Resp; Resp.SetContentType(ContentType); a_Connection.Send(Resp); a_Connection.Send(Content); @@ -621,7 +656,7 @@ AString cWebAdmin::GetBaseURL(const AStringVector & a_URLSplit) -void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_ (strncmp(URL.c_str(), "/~webadmin", 10) == 0) ) { - a_Request.SetUserData(new cWebadminRequestData(a_Request)); + a_Request.SetUserData(std::make_shared<cWebadminRequestData>(a_Request)); return; } if (URL == "/") @@ -645,22 +680,22 @@ 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, cHTTPIncomingRequest & a_Request, const char * a_Data, size_t a_Size) { UNUSED(a_Connection); - cRequestData * Data = reinterpret_cast<cRequestData *>(a_Request.GetUserData()); + auto Data = std::static_pointer_cast<cWebadminRequestData>(a_Request.GetUserData()); if (Data == nullptr) { return; } - Data->OnBody(a_Data, a_Size); + Data->m_Form.Parse(a_Data, a_Size); } -void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & { HandleFileRequest(a_Connection, a_Request); } - - // Delete any request data assigned to the request: - cRequestData * Data = reinterpret_cast<cRequestData *>(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 a3ebac43c..5e48f597c 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/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(cHTTPRequest & 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(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleWebadminRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Handles requests for the root page */ - void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleRootRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & a_Request); /** Handles requests for a file */ - void HandleFileRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); + void HandleFileRequest(cHTTPServerConnection & a_Connection, cHTTPIncomingRequest & 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, 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/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..233e5c0cb --- /dev/null +++ b/tests/HTTP/CMakeLists.txt @@ -0,0 +1,60 @@ +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/HTTPMessageParser.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/HTTPMessageParser.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: + +# 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 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 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) + 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<unsigned>(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 <filename> [<buffersize>]\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<unsigned>(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<unsigned>(bufSize), static_cast<unsigned>(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<unsigned>(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<unsigned>(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 <filename> [<buffersize>]\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<unsigned>(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<unsigned>(bufSize), static_cast<unsigned>(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<unsigned>(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 new file mode 100644 index 000000000..97e0b23c3 --- /dev/null +++ b/tests/HTTP/HTTPResponse1.data @@ -0,0 +1,10 @@ +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
+
+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..7e8d127b7 --- /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<unsigned>(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 <filename> [<buffersize>]\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<unsigned>(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<unsigned>(bufSize), static_cast<unsigned>(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<unsigned>(numBytes - numConsumed)); + } + } + if (!parser.IsFinished()) + { + printf("Parser indicates an incomplete stream.\n"); + } + + // Close the input file: + if (f != stdin) + { + fclose(f); + } + + return 0; +} + + + + |