Moved source to src
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user