diff options
author | Alexander Harkness <bearbin@gmail.com> | 2013-11-24 15:19:41 +0100 |
---|---|---|
committer | Alexander Harkness <bearbin@gmail.com> | 2013-11-24 15:19:41 +0100 |
commit | 675b4aa878f16291ce33fced48a2bc7425f635ae (patch) | |
tree | 409914df27a98f65adf866da669429c4de141b6f /source/HTTPServer | |
parent | LineBlockTracer: Using the coord-based block faces. (diff) | |
download | cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.gz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.bz2 cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.lz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.xz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.zst cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.zip |
Diffstat (limited to 'source/HTTPServer')
-rw-r--r-- | source/HTTPServer/EnvelopeParser.cpp | 132 | ||||
-rw-r--r-- | source/HTTPServer/EnvelopeParser.h | 69 | ||||
-rw-r--r-- | source/HTTPServer/HTTPConnection.cpp | 247 | ||||
-rw-r--r-- | source/HTTPServer/HTTPConnection.h | 101 | ||||
-rw-r--r-- | source/HTTPServer/HTTPFormParser.cpp | 290 | ||||
-rw-r--r-- | source/HTTPServer/HTTPFormParser.h | 112 | ||||
-rw-r--r-- | source/HTTPServer/HTTPMessage.cpp | 279 | ||||
-rw-r--r-- | source/HTTPServer/HTTPMessage.h | 164 | ||||
-rw-r--r-- | source/HTTPServer/HTTPServer.cpp | 258 | ||||
-rw-r--r-- | source/HTTPServer/HTTPServer.h | 101 | ||||
-rw-r--r-- | source/HTTPServer/MultipartParser.cpp | 256 | ||||
-rw-r--r-- | source/HTTPServer/MultipartParser.h | 76 | ||||
-rw-r--r-- | source/HTTPServer/NameValueParser.cpp | 412 | ||||
-rw-r--r-- | source/HTTPServer/NameValueParser.h | 70 |
14 files changed, 0 insertions, 2567 deletions
diff --git a/source/HTTPServer/EnvelopeParser.cpp b/source/HTTPServer/EnvelopeParser.cpp deleted file mode 100644 index 8dbe05f14..000000000 --- a/source/HTTPServer/EnvelopeParser.cpp +++ /dev/null @@ -1,132 +0,0 @@ - -// EnvelopeParser.cpp - -// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME - -#include "Globals.h" -#include "EnvelopeParser.h" - - - - - -cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_IsInHeaders(true) -{ -} - - - - - -int cEnvelopeParser::Parse(const char * a_Data, int a_Size) -{ - if (!m_IsInHeaders) - { - return 0; - } - - // Start searching 1 char from the end of the already received data, if available: - size_t SearchStart = m_IncomingData.size(); - SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0; - - m_IncomingData.append(a_Data, a_Size); - - size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart); - if (idxCRLF == AString::npos) - { - // Not a complete line yet, all input consumed: - return a_Size; - } - - // Parse as many lines as found: - size_t Last = 0; - do - { - if (idxCRLF == Last) - { - // This was the last line of the data. Finish whatever value has been cached and return: - NotifyLast(); - m_IsInHeaders = false; - return a_Size - (m_IncomingData.size() - idxCRLF) + 2; - } - if (!ParseLine(m_IncomingData.c_str() + Last, idxCRLF - Last)) - { - // An error has occurred - m_IsInHeaders = false; - return -1; - } - Last = idxCRLF + 2; - idxCRLF = m_IncomingData.find("\r\n", idxCRLF + 2); - } while (idxCRLF != AString::npos); - m_IncomingData.erase(0, Last); - - // Parsed all lines and still expecting more - return a_Size; -} - - - - - -void cEnvelopeParser::Reset(void) -{ - m_IsInHeaders = true; - m_IncomingData.clear(); - m_LastKey.clear(); - m_LastValue.clear(); -} - - - - - -void cEnvelopeParser::NotifyLast(void) -{ - if (!m_LastKey.empty()) - { - m_Callbacks.OnHeaderLine(m_LastKey, m_LastValue); - m_LastKey.clear(); - } - m_LastValue.clear(); -} - - - - - -bool cEnvelopeParser::ParseLine(const char * a_Data, size_t a_Size) -{ - ASSERT(a_Size > 0); - if (a_Data[0] <= ' ') - { - // This line is a continuation for the previous line - if (m_LastKey.empty()) - { - return false; - } - // Append, including the whitespace in a_Data[0] - m_LastValue.append(a_Data, a_Size); - return true; - } - - // This is a line with a new key: - NotifyLast(); - for (size_t i = 0; i < a_Size; i++) - { - if (a_Data[i] == ':') - { - m_LastKey.assign(a_Data, i); - m_LastValue.assign(a_Data + i + 2, a_Size - i - 2); - return true; - } - } // for i - a_Data[] - - // No colon was found, key-less header?? - return false; -} - - - - diff --git a/source/HTTPServer/EnvelopeParser.h b/source/HTTPServer/EnvelopeParser.h deleted file mode 100644 index 6430fbebf..000000000 --- a/source/HTTPServer/EnvelopeParser.h +++ /dev/null @@ -1,69 +0,0 @@ - -// EnvelopeParser.h - -// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME - - - - - -#pragma once - - - - - -class cEnvelopeParser -{ -public: - class cCallbacks - { - public: - /// Called when a full header line is parsed - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0; - } ; - - - cEnvelopeParser(cCallbacks & a_Callbacks); - - /** Parses the incoming data. - Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header - */ - int Parse(const char * a_Data, int a_Size); - - /// Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream - void Reset(void); - - /// Returns true if more input is expected for the envelope header - bool IsInHeaders(void) const { return m_IsInHeaders; } - - /// Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions - void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; } - -public: - /// Callbacks to call for the various events - cCallbacks & m_Callbacks; - - /// Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data. - bool m_IsInHeaders; - - /// Buffer for the incoming data until it is parsed - AString m_IncomingData; - - /// Holds the last parsed key; used for line-wrapped values - AString m_LastKey; - - /// Holds the last parsed value; used for line-wrapped values - AString m_LastValue; - - - /// Notifies the callback of the key/value stored in m_LastKey/m_LastValue, then erases them - void NotifyLast(void); - - /// Parses one line of header data. Returns true if successful - bool ParseLine(const char * a_Data, size_t a_Size); -} ; - - - - diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp deleted file mode 100644 index 68afdfc11..000000000 --- a/source/HTTPServer/HTTPConnection.cpp +++ /dev/null @@ -1,247 +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(NULL) -{ - // LOGD("HTTP: New connection at %p", this); -} - - - - - -cHTTPConnection::~cHTTPConnection() -{ - // LOGD("HTTP: Del connection at %p", this); -} - - - - - -void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) -{ - AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()); - m_HTTPServer.NotifyConnectionWrite(*this); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPConnection::SendNeedAuth(const AString & a_Realm) -{ - AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()); - m_HTTPServer.NotifyConnectionWrite(*this); - m_State = wcsRecvHeaders; -} - - - - - -void cHTTPConnection::Send(const cHTTPResponse & a_Response) -{ - ASSERT(m_State = wcsRecvIdle); - a_Response.AppendToData(m_OutgoingData); - m_State = wcsSendingResp; - m_HTTPServer.NotifyConnectionWrite(*this); -} - - - - - -void cHTTPConnection::Send(const void * a_Data, int a_Size) -{ - ASSERT(m_State == wcsSendingResp); - AppendPrintf(m_OutgoingData, "%x\r\n", a_Size); - m_OutgoingData.append((const char *)a_Data, a_Size); - m_OutgoingData.append("\r\n"); - m_HTTPServer.NotifyConnectionWrite(*this); -} - - - - - -void cHTTPConnection::FinishResponse(void) -{ - ASSERT(m_State == wcsSendingResp); - m_OutgoingData.append("0\r\n\r\n"); - m_State = wcsRecvHeaders; - m_HTTPServer.NotifyConnectionWrite(*this); -} - - - - - -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": - m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n"); - m_HTTPServer.NotifyConnectionWrite(*this); - m_State = wcsRecvHeaders; - break; - } - - case wcsSendingResp: - { - // The response headers have been sent, we need to terminate the response body: - m_OutgoingData.append("0\r\n\r\n"); - m_State = wcsRecvHeaders; - break; - } - - default: - { - ASSERT(!"Unhandled state recovery"); - break; - } - } -} - - - - - -void cHTTPConnection::Terminate(void) -{ - if (m_CurrentRequest != NULL) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_HTTPServer.CloseConnection(*this); -} - - - - - -void cHTTPConnection::DataReceived(const char * a_Data, int a_Size) -{ - switch (m_State) - { - case wcsRecvHeaders: - { - if (m_CurrentRequest == NULL) - { - m_CurrentRequest = new cHTTPRequest; - } - - int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size); - if (BytesConsumed < 0) - { - delete m_CurrentRequest; - m_CurrentRequest = NULL; - m_State = wcsInvalid; - m_HTTPServer.CloseConnection(*this); - 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 < 0) - { - // 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) - { - DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed); - } - else - { - DataReceived("", 0); // If the request has zero body length, let it be processed right-away - } - break; - } - - case wcsRecvBody: - { - ASSERT(m_CurrentRequest != NULL); - if (m_CurrentRequestBodyRemaining > 0) - { - int BytesToConsume = std::min(m_CurrentRequestBodyRemaining, 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); - delete m_CurrentRequest; - m_CurrentRequest = NULL; - } - break; - } - - default: - { - // TODO: Should we be receiving data in this state? - break; - } - } -} - - - - - -void cHTTPConnection::GetOutgoingData(AString & a_Data) -{ - std::swap(a_Data, m_OutgoingData); -} - - - - - -void cHTTPConnection::SocketClosed(void) -{ - if (m_CurrentRequest != NULL) - { - m_HTTPServer.RequestFinished(*this, *m_CurrentRequest); - } - m_HTTPServer.CloseConnection(*this); -} - - - - - diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h deleted file mode 100644 index 14603bb70..000000000 --- a/source/HTTPServer/HTTPConnection.h +++ /dev/null @@ -1,101 +0,0 @@ - -// HTTPConnection.h - -// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server. - - - - - -#pragma once - -#include "../OSSupport/SocketThreads.h" - - - - - -// fwd: -class cHTTPServer; -class cHTTPResponse; -class cHTTPRequest; - - - - - -class cHTTPConnection : - public cSocketThreads::cCallback -{ -public: - - enum eState - { - wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if NULL) - wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) - wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL) - wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL) - wcsInvalid, ///< The request was malformed, the connection is closing - } ; - - cHTTPConnection(cHTTPServer & a_HTTPServer); - ~cHTTPConnection(); - - /// Sends HTTP status code together with a_Reason (used for HTTP errors) - void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); - - /// Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm - void SendNeedAuth(const AString & a_Realm); - - /// Sends the headers contained in a_Response - void Send(const cHTTPResponse & a_Response); - - /// Sends the data as the response (may be called multiple times) - void Send(const void * a_Data, int a_Size); - - /// Sends the data as the response (may be called multiple times) - void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); } - - /// Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive) - void FinishResponse(void); - - /// Resets the connection for a new request. Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd" - void AwaitNextRequest(void); - - /// Terminates the connection; finishes any request being currently processed - void Terminate(void); - -protected: - typedef std::map<AString, AString> cNameValueMap; - - /// The parent webserver that is to be notified of events on this connection - cHTTPServer & m_HTTPServer; - - /// All the incoming data until the entire request header is parsed - AString m_IncomingHeaderData; - - /// Status in which the request currently is - eState m_State; - - /// Data that is queued for sending, once the socket becomes writable - AString m_OutgoingData; - - /// 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 - int m_CurrentRequestBodyRemaining; - - - // cSocketThreads::cCallback overrides: - virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client - virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client - virtual void SocketClosed (void) override; // The socket has been closed for any reason -} ; - -typedef std::vector<cHTTPConnection *> cHTTPConnections; - - - - - diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp deleted file mode 100644 index 596db424e..000000000 --- a/source/HTTPServer/HTTPFormParser.cpp +++ /dev/null @@ -1,290 +0,0 @@ - -// HTTPFormParser.cpp - -// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP - -#include "Globals.h" -#include "HTTPFormParser.h" -#include "HTTPMessage.h" -#include "MultipartParser.h" -#include "NameValueParser.h" - - - - - -cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_IsValid(true) -{ - if (a_Request.GetMethod() == "GET") - { - m_Kind = fpkURL; - - // Directly parse the URL in the request: - const AString & URL = a_Request.GetURL(); - size_t idxQM = URL.find('?'); - if (idxQM != AString::npos) - { - Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1); - } - return; - } - if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) - { - if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) - { - m_Kind = fpkFormUrlEncoded; - return; - } - if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) - { - m_Kind = fpkMultipart; - BeginMultipart(a_Request); - return; - } - } - // Invalid method / content type combination, this is not a HTTP form - m_IsValid = false; -} - - - - - -cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, int a_Size, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_Kind(a_Kind), - m_IsValid(true) -{ - Parse(a_Data, a_Size); -} - - - - - -void cHTTPFormParser::Parse(const char * a_Data, int a_Size) -{ - if (!m_IsValid) - { - return; - } - - switch (m_Kind) - { - case fpkURL: - case fpkFormUrlEncoded: - { - // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish() - m_IncomingData.append(a_Data, a_Size); - break; - } - case fpkMultipart: - { - ASSERT(m_MultipartParser.get() != NULL); - m_MultipartParser->Parse(a_Data, a_Size); - break; - } - default: - { - ASSERT(!"Unhandled form kind"); - break; - } - } -} - - - - - -bool cHTTPFormParser::Finish(void) -{ - switch (m_Kind) - { - case fpkURL: - case fpkFormUrlEncoded: - { - // m_IncomingData has all the form data, parse it now: - ParseFormUrlEncoded(); - break; - } - } - return (m_IsValid && m_IncomingData.empty()); -} - - - - - -bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) -{ - const AString & ContentType = a_Request.GetContentType(); - return ( - (ContentType == "application/x-www-form-urlencoded") || - (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) || - ( - (a_Request.GetMethod() == "GET") && - (a_Request.GetURL().find('?') != AString::npos) - ) - ); - return false; -} - - - - - -void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request) -{ - ASSERT(m_MultipartParser.get() == NULL); - m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this)); -} - - - - - -void cHTTPFormParser::ParseFormUrlEncoded(void) -{ - // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish() - // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway - AStringVector Lines = StringSplit(m_IncomingData, "&"); - for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr) - { - AStringVector Components = StringSplit(*itr, "="); - switch (Components.size()) - { - default: - { - // Neither name nor value, or too many "="s, mark this as invalid form: - m_IsValid = false; - return; - } - case 1: - { - // Only name present - (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = ""; - break; - } - case 2: - { - // name=value format: - (*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' ')); - break; - } - } - } // for itr - Lines[] - m_IncomingData.clear(); -} - - - - - -void cHTTPFormParser::OnPartStart(void) -{ - m_CurrentPartFileName.clear(); - m_CurrentPartName.clear(); - m_IsCurrentPartFile = false; - m_FileHasBeenAnnounced = false; -} - - - - - -void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value) -{ - if (NoCaseCompare(a_Key, "Content-Disposition") == 0) - { - size_t len = a_Value.size(); - size_t ParamsStart = AString::npos; - for (size_t i = 0; i < len; ++i) - { - if (a_Value[i] > ' ') - { - if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0) - { - // Content disposition is not "form-data", mark the whole form invalid - m_IsValid = false; - return; - } - ParamsStart = a_Value.find(';', i + 9); - break; - } - } - if (ParamsStart == AString::npos) - { - // There is data missing in the Content-Disposition field, mark the whole form invalid: - m_IsValid = false; - return; - } - - // Parse the field name and optional filename from this header: - cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart); - Parser.Finish(); - m_CurrentPartName = Parser["name"]; - if (!Parser.IsValid() || m_CurrentPartName.empty()) - { - // The required parameter "name" is missing, mark the whole form invalid: - m_IsValid = false; - return; - } - m_CurrentPartFileName = Parser["filename"]; - } -} - - - - - -void cHTTPFormParser::OnPartData(const char * a_Data, int a_Size) -{ - if (m_CurrentPartName.empty()) - { - // Prologue, epilogue or invalid part - return; - } - if (m_CurrentPartFileName.empty()) - { - // This is a variable, store it in the map - iterator itr = find(m_CurrentPartName); - if (itr == end()) - { - (*this)[m_CurrentPartName] = AString(a_Data, a_Size); - } - else - { - itr->second.append(a_Data, a_Size); - } - } - else - { - // This is a file, pass it on through the callbacks - if (!m_FileHasBeenAnnounced) - { - m_Callbacks.OnFileStart(*this, m_CurrentPartFileName); - m_FileHasBeenAnnounced = true; - } - m_Callbacks.OnFileData(*this, a_Data, a_Size); - } -} - - - - - -void cHTTPFormParser::OnPartEnd(void) -{ - if (m_FileHasBeenAnnounced) - { - m_Callbacks.OnFileEnd(*this); - } - m_CurrentPartName.clear(); - m_CurrentPartFileName.clear(); -} - - - - diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h deleted file mode 100644 index a554ca5a4..000000000 --- a/source/HTTPServer/HTTPFormParser.h +++ /dev/null @@ -1,112 +0,0 @@ - -// HTTPFormParser.h - -// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP - - - - -#pragma once - -#include "MultipartParser.h" - - - - - -// fwd: -class cHTTPRequest; - - - - - -class cHTTPFormParser : - public std::map<AString, AString>, - public cMultipartParser::cCallbacks -{ -public: - enum eKind - { - fpkURL, ///< The form has been transmitted as parameters to a GET request - fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded" - fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data" - } ; - - class cCallbacks - { - public: - /// Called when a new file part is encountered in the form data - virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0; - - /// Called when more file data has come for the current file in the form data - virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) = 0; - - /// Called when the current file part has ended in the form data - virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0; - } ; - - - /// Creates a parser that is tied to a request and notifies of various events using a callback mechanism - cHTTPFormParser(cHTTPRequest & 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, int a_Size, cCallbacks & a_Callbacks); - - /// Adds more data into the parser, as the request body is received - void Parse(const char * a_Data, int a_Size); - - /** Notifies that there's no more data incoming and the parser should finish its parsing. - Returns true if parsing successful - */ - bool Finish(void); - - /// Returns true if the headers suggest the request has form data parseable by this class - static bool HasFormData(const cHTTPRequest & a_Request); - -protected: - - /// The callbacks to call for incoming file data - cCallbacks & m_Callbacks; - - /// The kind of the parser (decided in the constructor, used in Parse() - eKind m_Kind; - - /// Buffer for the incoming data until it's parsed - AString m_IncomingData; - - /// True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false. - bool m_IsValid; - - /// The parser for the multipart data, if used - std::auto_ptr<cMultipartParser> m_MultipartParser; - - /// Name of the currently parsed part in multipart data - AString m_CurrentPartName; - - /// True if the currently parsed part in multipart data is a file - bool m_IsCurrentPartFile; - - /// Filename of the current parsed part in multipart data (for file uploads) - AString m_CurrentPartFileName; - - /// Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd - bool m_FileHasBeenAnnounced; - - - /// Sets up the object for parsing a fpkMultipart request - void BeginMultipart(const cHTTPRequest & a_Request); - - /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds) - void ParseFormUrlEncoded(void); - - // cMultipartParser::cCallbacks overrides: - virtual void OnPartStart (void) override; - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override; - virtual void OnPartData (const char * a_Data, int a_Size) override; - virtual void OnPartEnd (void) override; -} ; - - - - diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp deleted file mode 100644 index ab23866e6..000000000 --- a/source/HTTPServer/HTTPMessage.cpp +++ /dev/null @@ -1,279 +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(-1) -{ -} - - - - - -void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) -{ - AString Key = a_Key; - StrToLower(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") - { - m_ContentLength = atoi(m_Headers[Key].c_str()); - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cHTTPRequest: - -cHTTPRequest::cHTTPRequest(void) : - super(mkRequest), - m_EnvelopeParser(*this), - m_IsValid(true), - m_UserData(NULL), - m_HasAuth(false) -{ -} - - - - - -int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size) -{ - if (!m_IsValid) - { - return -1; - } - - if (m_Method.empty()) - { - // The first line hasn't been processed yet - int res = ParseRequestLine(a_Data, a_Size); - if ((res < 0) || (res == a_Size)) - { - return res; - } - int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); - if (res2 < 0) - { - m_IsValid = false; - return res2; - } - return res2 + res; - } - - if (m_EnvelopeParser.IsInHeaders()) - { - int res = m_EnvelopeParser.Parse(a_Data, a_Size); - if (res < 0) - { - 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; - } -} - - - - - -int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size) -{ - m_IncomingHeaderData.append(a_Data, a_Size); - size_t IdxEnd = m_IncomingHeaderData.size(); - - // Ignore the initial CRLFs (HTTP spec's "should") - size_t LineStart = 0; - while ( - (LineStart < IdxEnd) && - ( - (m_IncomingHeaderData[LineStart] == '\r') || - (m_IncomingHeaderData[LineStart] == '\n') - ) - ) - { - LineStart++; - } - if (LineStart >= IdxEnd) - { - m_IsValid = false; - return -1; - } - - 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 -1; - } - } - 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 -1; - } - // Check that there's HTTP/version at the end - if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0) - { - m_IsValid = false; - return -1; - } - m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); - m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); - return i + 1; - } - } // switch (m_IncomingHeaderData[i]) - } // for i - m_IncomingHeaderData[] - - // CRLF hasn't been encountered yet, consider all data consumed - return a_Size; -} - - - - - -void 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; - } - } - 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/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h deleted file mode 100644 index f5284c535..000000000 --- a/source/HTTPServer/HTTPMessage.h +++ /dev/null @@ -1,164 +0,0 @@ - -// HTTPMessage.h - -// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes - - - - - -#pragma once - -#include "EnvelopeParser.h" - - - - - -class cHTTPMessage -{ -public: - enum - { - HTTP_OK = 200, - HTTP_BAD_REQUEST = 400, - } ; - - enum eKind - { - mkRequest, - mkResponse, - } ; - - cHTTPMessage(eKind a_Kind); - - /// Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length - void AddHeader(const AString & a_Key, const AString & a_Value); - - void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; } - void SetContentLength(int a_ContentLength) { m_ContentLength = a_ContentLength; } - - const AString & GetContentType (void) const { return m_ContentType; } - int GetContentLength(void) const { return m_ContentLength; } - -protected: - typedef std::map<AString, AString> cNameValueMap; - - eKind m_Kind; - - cNameValueMap m_Headers; - - /// Type of the content; parsed by AddHeader(), set directly by SetContentLength() - AString m_ContentType; - - /// Length of the content that is to be received. -1 when the object is created, parsed by AddHeader() or set directly by SetContentLength() - int m_ContentLength; -} ; - - - - - -class cHTTPRequest : - public cHTTPMessage, - protected cEnvelopeParser::cCallbacks -{ - typedef cHTTPMessage super; - -public: - cHTTPRequest(void); - - /** Parses the request line and then headers from the received data. - Returns the number of bytes consumed or a negative number for error - */ - int ParseHeaders(const char * a_Data, int a_Size); - - /// Returns true if the request did contain a Content-Length header - bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); } - - /// Returns the method used in the request - const AString & GetMethod(void) const { return m_Method; } - - /// Returns the URL used in the request - const AString & GetURL(void) const { return m_URL; } - - /// Returns the URL used in the request, without any parameters - AString GetBareURL(void) const; - - /// Sets the UserData pointer that is stored within this request. The request doesn't touch this data (doesn't delete it)! - void SetUserData(void * a_UserData) { m_UserData = a_UserData; } - - /// Retrieves the UserData pointer that has been stored within this request. - void * GetUserData(void) const { return m_UserData; } - - /// Returns true if more data is expected for the request headers - bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } - - /// Returns true if the request did present auth data that was understood by the parser - bool HasAuth(void) const { return m_HasAuth; } - - /// Returns the username that the request presented. Only valid if HasAuth() is true - const AString & GetAuthUsername(void) const { return m_AuthUsername; } - - /// Returns the password that the request presented. Only valid if HasAuth() is true - const AString & GetAuthPassword(void) const { return m_AuthPassword; } - -protected: - /// Parser for the envelope data - cEnvelopeParser m_EnvelopeParser; - - /// True if the data received so far is parsed successfully. When false, all further parsing is skipped - bool m_IsValid; - - /// Bufferred incoming data, while parsing for the request line - AString m_IncomingHeaderData; - - /// Method of the request (GET / PUT / POST / ...) - AString m_Method; - - /// Full URL of the request - AString m_URL; - - /// Data that the HTTPServer callbacks are allowed to store. - void * m_UserData; - - /// Set to true if the request contains auth data that was understood by the parser - bool m_HasAuth; - - /// The username used for auth - AString m_AuthUsername; - - /// The password used for auth - AString m_AuthPassword; - - - /** Parses the incoming data for the first line (RequestLine) - Returns the number of bytes consumed, or -1 for an error - */ - int ParseRequestLine(const char * a_Data, int 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; -} ; - - - - diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp deleted file mode 100644 index f6f5b0f8b..000000000 --- a/source/HTTPServer/HTTPServer.cpp +++ /dev/null @@ -1,258 +0,0 @@ - -// HTTPServer.cpp - -// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing - -#include "Globals.h" -#include "HTTPServer.h" -#include "HTTPMessage.h" -#include "HTTPConnection.h" -#include "HTTPFormParser.h" - - - - - -// Disable MSVC warnings: -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4355) // 'this' : used in base member initializer list -#endif - - - - - -class cDebugCallbacks : - public cHTTPServer::cCallbacks, - protected cHTTPFormParser::cCallbacks -{ - virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override - { - 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, int a_Size) override - { - cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData()); - if (FormParser != NULL) - { - FormParser->Parse(a_Data, a_Size); - } - } - - - virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override - { - cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData()); - if (FormParser != NULL) - { - 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("MCServer 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, int a_Size) override - { - // TODO - } - - - virtual void OnFileEnd(cHTTPFormParser & a_Parser) override - { - // TODO - } - -} g_DebugCallbacks; - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cHTTPServer: - -cHTTPServer::cHTTPServer(void) : - m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"), - m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"), - m_Callbacks(NULL) -{ -} - - - - - -cHTTPServer::~cHTTPServer() -{ - Stop(); -} - - - - - -bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6) -{ - bool HasAnyPort; - HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4); - HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort; - if (!HasAnyPort) - { - return false; - } - - return true; -} - - - - - -bool cHTTPServer::Start(cCallbacks & a_Callbacks) -{ - m_Callbacks = &a_Callbacks; - if (!m_ListenThreadIPv4.Start()) - { - return false; - } - if (!m_ListenThreadIPv6.Start()) - { - m_ListenThreadIPv4.Stop(); - return false; - } - return true; -} - - - - - -void cHTTPServer::Stop(void) -{ - m_ListenThreadIPv4.Stop(); - m_ListenThreadIPv6.Stop(); - - // Drop all current connections: - cCSLock Lock(m_CSConnections); - while (!m_Connections.empty()) - { - m_Connections.front()->Terminate(); - } // for itr - m_Connections[] -} - - - - - -void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket) -{ - cHTTPConnection * Connection = new cHTTPConnection(*this); - m_SocketThreads.AddClient(a_Socket, Connection); - cCSLock Lock(m_CSConnections); - m_Connections.push_back(Connection); -} - - - - - -void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection) -{ - m_SocketThreads.RemoveClient(&a_Connection); - cCSLock Lock(m_CSConnections); - for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr) - { - if (*itr == &a_Connection) - { - m_Connections.erase(itr); - break; - } - } - delete &a_Connection; -} - - - - - -void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection) -{ - m_SocketThreads.NotifyWrite(&a_Connection); -} - - - - - -void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) -{ - m_Callbacks->OnRequestBegun(a_Connection, a_Request); -} - - - - - -void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) -{ - m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size); -} - - - - - -void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) -{ - m_Callbacks->OnRequestFinished(a_Connection, a_Request); - a_Connection.AwaitNextRequest(); -} - - - - diff --git a/source/HTTPServer/HTTPServer.h b/source/HTTPServer/HTTPServer.h deleted file mode 100644 index fea2a9029..000000000 --- a/source/HTTPServer/HTTPServer.h +++ /dev/null @@ -1,101 +0,0 @@ - -// HTTPServer.h - -// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing - - - - - -#pragma once - -#include "../OSSupport/ListenThread.h" -#include "../OSSupport/SocketThreads.h" -#include "../../iniFile/iniFile.h" - - - - - -// fwd: -class cHTTPMessage; -class cHTTPRequest; -class cHTTPResponse; -class cHTTPConnection; - -typedef std::vector<cHTTPConnection *> cHTTPConnections; - - - - - - -class cHTTPServer : - public cListenThread::cCallback -{ -public: - class cCallbacks - { - public: - /** Called when a new request arrives over a connection and its headers have been parsed. - The request body needn't have arrived yet. - */ - virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0; - - /// Called when another part of request body has arrived. - virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int 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; - } ; - - cHTTPServer(void); - ~cHTTPServer(); - - /// Initializes the server on the specified ports - bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6); - - /// Starts the server and assigns the callbacks to use for incoming requests - bool Start(cCallbacks & a_Callbacks); - - /// Stops the server, drops all current connections - void Stop(void); - -protected: - friend class cHTTPConnection; - - cListenThread m_ListenThreadIPv4; - cListenThread m_ListenThreadIPv6; - - cSocketThreads m_SocketThreads; - - cCriticalSection m_CSConnections; - cHTTPConnections m_Connections; ///< All the connections that are currently being serviced - - /// The callbacks to call for various events - cCallbacks * m_Callbacks; - - - // cListenThread::cCallback overrides: - virtual void OnConnectionAccepted(cSocket & a_Socket) override; - - /// Called by cHTTPConnection to close the connection (presumably due to an error) - void CloseConnection(cHTTPConnection & a_Connection); - - /// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection - void NotifyConnectionWrite(cHTTPConnection & a_Connection); - - /// Called by cHTTPConnection when it finishes parsing the request header - void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request); - - /// Called by cHTTPConenction when it receives more data for the request body - void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int 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); -} ; - - - - - diff --git a/source/HTTPServer/MultipartParser.cpp b/source/HTTPServer/MultipartParser.cpp deleted file mode 100644 index b49f6ec07..000000000 --- a/source/HTTPServer/MultipartParser.cpp +++ /dev/null @@ -1,256 +0,0 @@ - -// MultipartParser.cpp - -// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts - -#include "Globals.h" -#include "MultipartParser.h" -#include "NameValueParser.h" - - - - - -// Disable MSVC warnings: -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4355) // 'this' : used in base member initializer list -#endif - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// self-test: - -#if 0 - -class cMultipartParserTest : - public cMultipartParser::cCallbacks -{ -public: - cMultipartParserTest(void) - { - cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this); - const char Data[] = -"ThisIsIgnoredPrologue\r\n\ ---MyBoundaryString\r\n\ -\r\n\ -Body with confusing strings\r\n\ ---NotABoundary\r\n\ ---MyBoundaryStringWithPostfix\r\n\ ---\r\n\ ---MyBoundaryString\r\n\ -content-disposition: inline\r\n\ -\r\n\ -This is body\r\n\ ---MyBoundaryString\r\n\ -\r\n\ -Headerless body with trailing CRLF\r\n\ -\r\n\ ---MyBoundaryString--\r\n\ -ThisIsIgnoredEpilogue"; - printf("Multipart parsing test commencing.\n"); - Parser.Parse(Data, sizeof(Data) - 1); - // DEBUG: Check if the onscreen output corresponds with the data above - printf("Multipart parsing test finished\n"); - } - - virtual void OnPartStart(void) override - { - printf("Starting a new part\n"); - } - - - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override - { - printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str()); - } - - - virtual void OnPartData(const char * a_Data, int a_Size) override - { - printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data); - } - - - virtual void OnPartEnd(void) override - { - printf("Part end\n"); - } -} g_Test; - -#endif - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cMultipartParser: - - -cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) : - m_Callbacks(a_Callbacks), - m_IsValid(true), - m_EnvelopeParser(*this), - m_HasHadData(false) -{ - static AString s_Multipart = "multipart/"; - - // Check that the content type is multipart: - AString ContentType(a_ContentType); - if (strncmp(ContentType.c_str(), "multipart/", 10) != 0) - { - m_IsValid = false; - return; - } - size_t idxSC = ContentType.find(';', 10); - if (idxSC == AString::npos) - { - m_IsValid = false; - return; - } - - // Find the multipart boundary: - ContentType.erase(0, idxSC + 1); - cNameValueParser CTParser(ContentType.c_str(), ContentType.size()); - CTParser.Finish(); - if (!CTParser.IsValid()) - { - m_IsValid = false; - return; - } - m_Boundary = CTParser["boundary"]; - m_IsValid = !m_Boundary.empty(); - if (!m_IsValid) - { - return; - } - - // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body - m_EnvelopeParser.SetIsInHeaders(false); - - // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught - m_IncomingData.assign("\r\n"); - - /* - m_Boundary = AString("\r\n--") + m_Boundary - m_BoundaryEnd = m_Boundary + "--\r\n"; - m_Boundary = m_Boundary + "\r\n"; - */ -} - - - - - -void cMultipartParser::Parse(const char * a_Data, int a_Size) -{ - // Skip parsing if invalid - if (!m_IsValid) - { - return; - } - - // Append to buffer, then parse it: - m_IncomingData.append(a_Data, a_Size); - while (true) - { - if (m_EnvelopeParser.IsInHeaders()) - { - int BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size()); - if (BytesConsumed < 0) - { - m_IsValid = false; - return; - } - if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders()) - { - // All the incoming data has been consumed and still waiting for more - return; - } - m_IncomingData.erase(0, BytesConsumed); - } - - // Search for boundary / boundary end: - size_t idxBoundary = m_IncomingData.find("\r\n--"); - if (idxBoundary == AString::npos) - { - // Boundary string start not present, present as much data to the part callback as possible - if (m_IncomingData.size() > m_Boundary.size() + 8) - { - size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; - m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); - m_IncomingData.erase(0, BytesToReport); - } - return; - } - if (idxBoundary > 0) - { - m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary); - m_IncomingData.erase(0, idxBoundary); - } - idxBoundary = 4; - size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary); - if (LineEnd == AString::npos) - { - // Not a complete line yet, present as much data to the part callback as possible - if (m_IncomingData.size() > m_Boundary.size() + 8) - { - size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8; - m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport); - m_IncomingData.erase(0, BytesToReport); - } - return; - } - if ( - (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary - (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end - ) - { - // Got a line, but it's not a boundary, report it as data: - m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd); - m_IncomingData.erase(0, LineEnd); - continue; - } - - if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0) - { - // Boundary or BoundaryEnd found: - m_Callbacks.OnPartEnd(); - size_t idxSlash = idxBoundary + m_Boundary.size(); - if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-')) - { - // This was the last part - m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4); - m_IncomingData.clear(); - return; - } - m_Callbacks.OnPartStart(); - m_IncomingData.erase(0, LineEnd + 2); - - // Keep parsing for the headers that may have come with this data: - m_EnvelopeParser.Reset(); - continue; - } - - // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines - m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd); - m_IncomingData.erase(0, LineEnd); - } // while (true) -} - - - - - -void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value) -{ - m_Callbacks.OnPartHeader(a_Key, a_Value); -} - - - - diff --git a/source/HTTPServer/MultipartParser.h b/source/HTTPServer/MultipartParser.h deleted file mode 100644 index d853929ed..000000000 --- a/source/HTTPServer/MultipartParser.h +++ /dev/null @@ -1,76 +0,0 @@ - -// MultipartParser.h - -// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts - - - - - -#pragma once - -#include "EnvelopeParser.h" - - - - - -class cMultipartParser : - protected cEnvelopeParser::cCallbacks -{ -public: - class cCallbacks - { - public: - /// Called when a new part starts - virtual void OnPartStart(void) = 0; - - /// Called when a complete header line is received for a part - virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0; - - /// Called when body for a part is received - virtual void OnPartData(const char * a_Data, int a_Size) = 0; - - /// Called when the current part ends - virtual void OnPartEnd(void) = 0; - } ; - - /// Creates the parser, expects to find the boundary in a_ContentType - cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks); - - /// Parses more incoming data - void Parse(const char * a_Data, int a_Size); - -protected: - /// The callbacks to call for various parsing events - cCallbacks & m_Callbacks; - - /// True if the data parsed so far is valid; if false, further parsing is skipped - bool m_IsValid; - - /// Parser for each part's envelope - cEnvelopeParser m_EnvelopeParser; - - /// Buffer for the incoming data until it is parsed - AString m_IncomingData; - - /// The boundary, excluding both the initial "--" and the terminating CRLF - AString m_Boundary; - - /// Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting. - bool m_HasHadData; - - - /// Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size - void ParseLine(const char * a_Data, int a_Size); - - /// Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size - void ParseHeaderLine(const char * a_Data, int a_Size); - - // cEnvelopeParser overrides: - virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override; -} ; - - - - diff --git a/source/HTTPServer/NameValueParser.cpp b/source/HTTPServer/NameValueParser.cpp deleted file mode 100644 index a27f07d19..000000000 --- a/source/HTTPServer/NameValueParser.cpp +++ /dev/null @@ -1,412 +0,0 @@ - -// NameValueParser.cpp - -// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap - -#include "Globals.h" -#include "NameValueParser.h" - - - - - - -// DEBUG: Self-test - -#if 0 - -class cNameValueParserTest -{ -public: - cNameValueParserTest(void) - { - const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\""; - - // Now try parsing char-by-char, to debug transitions across datachunk boundaries: - cNameValueParser Parser2; - for (int i = 0; i < sizeof(Data) - 1; i++) - { - Parser2.Parse(Data + i, 1); - } - Parser2.Finish(); - - // Parse as a single chunk of data: - cNameValueParser Parser(Data, sizeof(Data) - 1); - - // Use the debugger to inspect the Parser variable - - // Check that the two parsers have the same content: - for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) - { - ASSERT(Parser2[itr->first] == itr->second); - } // for itr - Parser[] - - // Try parsing in 2-char chunks: - cNameValueParser Parser3; - for (int i = 0; i < sizeof(Data) - 2; i += 2) - { - Parser3.Parse(Data + i, 2); - } - if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char - { - Parser3.Parse(Data + sizeof(Data) - 2, 1); - } - Parser3.Finish(); - - // Check that the third parser has the same content: - for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr) - { - ASSERT(Parser3[itr->first] == itr->second); - } // for itr - Parser[] - - printf("cNameValueParserTest done"); - } -} g_Test; - -#endif - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cNameValueParser: - -cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) : - m_State(psKeySpace), - m_AllowsKeyOnly(a_AllowsKeyOnly) -{ -} - - - - - -cNameValueParser::cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly) : - m_State(psKeySpace), - m_AllowsKeyOnly(a_AllowsKeyOnly) -{ - Parse(a_Data, a_Size); -} - - - - - -void cNameValueParser::Parse(const char * a_Data, int a_Size) -{ - ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong! - - if ((m_State == psInvalid) || (m_State == psFinished)) - { - return; - } - int Last = 0; - for (int i = 0; i < a_Size;) - { - switch (m_State) - { - case psKeySpace: - { - // Skip whitespace until a non-whitespace is found, then start the key: - while ((i < a_Size) && (a_Data[i] <= ' ')) - { - i++; - } - if ((i < a_Size) && (a_Data[i] > ' ')) - { - m_State = psKey; - Last = i; - } - break; - } - - case psKey: - { - // Read the key until whitespace or an equal sign: - while (i < a_Size) - { - if (a_Data[i] == '=') - { - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - m_State = psEqual; - break; - } - else if (a_Data[i] <= ' ') - { - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - m_State = psEqualSpace; - break; - } - else if (a_Data[i] == ';') - { - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - m_CurrentKey.append(a_Data + Last, i - Last); - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if ((a_Data[i] == '\"') || (a_Data[i] == '\'')) - { - m_State = psInvalid; - return; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - // Still the key, ran out of data to parse, store the part of the key parsed so far: - m_CurrentKey.append(a_Data + Last, a_Size - Last); - return; - } - break; - } - - case psEqualSpace: - { - // The space before the expected equal sign; the current key is already assigned - while (i < a_Size) - { - if (a_Data[i] == '=') - { - m_State = psEqual; - i++; - Last = i; - break; - } - else if (a_Data[i] == ';') - { - // Key-only - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if (a_Data[i] > ' ') - { - m_State = psInvalid; - return; - } - i++; - } // while (i < a_Size) - break; - } // case psEqualSpace - - case psEqual: - { - // just parsed the equal-sign - while (i < a_Size) - { - if (a_Data[i] == ';') - { - if (!m_AllowsKeyOnly) - { - m_State = psInvalid; - return; - } - i++; - Last = i; - (*this)[m_CurrentKey] = ""; - m_CurrentKey.clear(); - m_State = psKeySpace; - break; - } - else if (a_Data[i] == '\"') - { - i++; - Last = i; - m_State = psValueInDQuotes; - break; - } - else if (a_Data[i] == '\'') - { - i++; - Last = i; - m_State = psValueInSQuotes; - break; - } - else - { - m_CurrentValue.push_back(a_Data[i]); - i++; - Last = i; - m_State = psValueRaw; - break; - } - i++; - } // while (i < a_Size) - break; - } // case psEqual - - case psValueInDQuotes: - { - while (i < a_Size) - { - if (a_Data[i] == '\"') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psAfterValue; - i++; - Last = i; - break; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueInDQuotes - - case psValueInSQuotes: - { - while (i < a_Size) - { - if (a_Data[i] == '\'') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psAfterValue; - i++; - Last = i; - break; - } - i++; - } // while (i < a_Size) - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueInSQuotes - - case psValueRaw: - { - while (i < a_Size) - { - if (a_Data[i] == ';') - { - m_CurrentValue.append(a_Data + Last, i - Last); - (*this)[m_CurrentKey] = m_CurrentValue; - m_CurrentKey.clear(); - m_CurrentValue.clear(); - m_State = psKeySpace; - i++; - Last = i; - break; - } - i++; - } - if (i == a_Size) - { - m_CurrentValue.append(a_Data + Last, a_Size - Last); - } - break; - } // case psValueRaw - - case psAfterValue: - { - // Between the closing DQuote or SQuote and the terminating semicolon - while (i < a_Size) - { - if (a_Data[i] == ';') - { - m_State = psKeySpace; - i++; - Last = i; - break; - } - else if (a_Data[i] < ' ') - { - i++; - continue; - } - m_State = psInvalid; - return; - } // while (i < a_Size) - break; - } - } // switch (m_State) - } // for i - a_Data[] -} - - - - - -bool cNameValueParser::Finish(void) -{ - switch (m_State) - { - case psInvalid: - { - return false; - } - case psFinished: - { - return true; - } - case psKey: - case psEqualSpace: - case psEqual: - { - if ((m_AllowsKeyOnly) && !m_CurrentKey.empty()) - { - (*this)[m_CurrentKey] = ""; - m_State = psFinished; - return true; - } - m_State = psInvalid; - return false; - } - case psValueRaw: - { - (*this)[m_CurrentKey] = m_CurrentValue; - m_State = psFinished; - return true; - } - case psValueInDQuotes: - case psValueInSQuotes: - { - // Missing the terminating quotes, this is an error - m_State = psInvalid; - return false; - } - case psKeySpace: - case psAfterValue: - { - m_State = psFinished; - return true; - } - } - ASSERT(!"Unhandled parser state!"); - return false; -} - - - - diff --git a/source/HTTPServer/NameValueParser.h b/source/HTTPServer/NameValueParser.h deleted file mode 100644 index 07dc0b942..000000000 --- a/source/HTTPServer/NameValueParser.h +++ /dev/null @@ -1,70 +0,0 @@ - -// NameValueParser.h - -// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap - - - - - -#pragma once - - - - - -class cNameValueParser : - public std::map<AString, AString> -{ -public: - /// Creates an empty parser - cNameValueParser(bool a_AllowsKeyOnly = true); - - /// Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later - cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly = true); - - /// Parses the data given - void Parse(const char * a_Data, int a_Size); - - /// Notifies the parser that no more data will be coming. Returns true if the parser state is valid - bool Finish(void); - - /// Returns true if the data parsed so far was valid - bool IsValid(void) const { return (m_State != psInvalid); } - - /// Returns true if the parser expects no more data - bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); } - -protected: - enum eState - { - psKeySpace, ///< Parsing the space in front of the next key - psKey, ///< Currently adding more chars to the key in m_CurrentKey - psEqualSpace, ///< Space after m_CurrentKey - psEqual, ///< Just parsed the = sign after a name - psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign - psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign - psValueRaw, ///< Just parsed a raw value without a quote - psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end - psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped - psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data - } ; - - /// The current state of the parser - eState m_State; - - /// If true, the parser will accept keys without an equal sign and the value - bool m_AllowsKeyOnly; - - /// Buffer for the current Key - AString m_CurrentKey; - - /// Buffer for the current Value; - AString m_CurrentValue; - - -} ; - - - - |