Moved source to src

This commit is contained in:
Alexander Harkness
2013-11-24 14:19:41 +00:00
parent 1480d6d64d
commit 675b4aa878
469 changed files with 0 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
// EnvelopeParser.cpp
// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME
#include "Globals.h"
#include "EnvelopeParser.h"
cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) :
m_Callbacks(a_Callbacks),
m_IsInHeaders(true)
{
}
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;
}
+69
View File
@@ -0,0 +1,69 @@
// 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);
} ;
+247
View File
@@ -0,0 +1,247 @@
// 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);
}
+101
View File
@@ -0,0 +1,101 @@
// 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;
+290
View File
@@ -0,0 +1,290 @@
// 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();
}
+112
View File
@@ -0,0 +1,112 @@
// 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;
} ;
+279
View File
@@ -0,0 +1,279 @@
// 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");
}
+164
View File
@@ -0,0 +1,164 @@
// 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;
} ;
+258
View File
@@ -0,0 +1,258 @@
// 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();
}
+101
View File
@@ -0,0 +1,101 @@
// HTTPServer.h
// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
#pragma once
#include "../OSSupport/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);
} ;
+256
View File
@@ -0,0 +1,256 @@
// 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);
}
+76
View File
@@ -0,0 +1,76 @@
// 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;
} ;
+412
View File
@@ -0,0 +1,412 @@
// 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;
}
+70
View File
@@ -0,0 +1,70 @@
// NameValueParser.h
// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
#pragma once
class cNameValueParser :
public std::map<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;
} ;