summaryrefslogtreecommitdiffstats
path: root/source/HTTPServer
diff options
context:
space:
mode:
authorAlexander Harkness <bearbin@gmail.com>2013-11-24 15:19:41 +0100
committerAlexander Harkness <bearbin@gmail.com>2013-11-24 15:19:41 +0100
commit675b4aa878f16291ce33fced48a2bc7425f635ae (patch)
tree409914df27a98f65adf866da669429c4de141b6f /source/HTTPServer
parentLineBlockTracer: Using the coord-based block faces. (diff)
downloadcuberite-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.cpp132
-rw-r--r--source/HTTPServer/EnvelopeParser.h69
-rw-r--r--source/HTTPServer/HTTPConnection.cpp247
-rw-r--r--source/HTTPServer/HTTPConnection.h101
-rw-r--r--source/HTTPServer/HTTPFormParser.cpp290
-rw-r--r--source/HTTPServer/HTTPFormParser.h112
-rw-r--r--source/HTTPServer/HTTPMessage.cpp279
-rw-r--r--source/HTTPServer/HTTPMessage.h164
-rw-r--r--source/HTTPServer/HTTPServer.cpp258
-rw-r--r--source/HTTPServer/HTTPServer.h101
-rw-r--r--source/HTTPServer/MultipartParser.cpp256
-rw-r--r--source/HTTPServer/MultipartParser.h76
-rw-r--r--source/HTTPServer/NameValueParser.cpp412
-rw-r--r--source/HTTPServer/NameValueParser.h70
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;
-
-
-} ;
-
-
-
-