1
0

HTTP Server can now parse multipart/form-data forms; better architecture.

This commit is contained in:
madmaxoft 2013-10-04 13:07:57 +02:00
parent 9a33732f6a
commit 1012fd82fd
8 changed files with 342 additions and 185 deletions

View File

@ -2719,6 +2719,14 @@
<Filter <Filter
Name="HTTPServer" Name="HTTPServer"
> >
<File
RelativePath="..\source\HTTPServer\EnvelopeParser.cpp"
>
</File>
<File
RelativePath="..\source\HTTPServer\EnvelopeParser.h"
>
</File>
<File <File
RelativePath="..\source\HTTPServer\HTTPConnection.cpp" RelativePath="..\source\HTTPServer\HTTPConnection.cpp"
> >
@ -2751,6 +2759,22 @@
RelativePath="..\source\HTTPServer\HTTPServer.h" RelativePath="..\source\HTTPServer\HTTPServer.h"
> >
</File> </File>
<File
RelativePath="..\source\HTTPServer\MultipartParser.cpp"
>
</File>
<File
RelativePath="..\source\HTTPServer\MultipartParser.h"
>
</File>
<File
RelativePath="..\source\HTTPServer\NameValueParser.cpp"
>
</File>
<File
RelativePath="..\source\HTTPServer\NameValueParser.h"
>
</File>
</Filter> </Filter>
</Filter> </Filter>
<Filter <Filter

View File

@ -108,22 +108,13 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
{ {
case wcsRecvHeaders: case wcsRecvHeaders:
{ {
ASSERT(m_CurrentRequest == NULL); if (m_CurrentRequest == NULL)
// Start searching 3 chars from the end of the already received data, if available:
size_t SearchStart = m_IncomingHeaderData.size();
SearchStart = (SearchStart > 3) ? SearchStart - 3 : 0;
m_IncomingHeaderData.append(a_Data, a_Size);
// Parse the header, if it is complete:
size_t idxEnd = m_IncomingHeaderData.find("\r\n\r\n", SearchStart);
if (idxEnd == AString::npos)
{ {
return; m_CurrentRequest = new cHTTPRequest;
} }
m_CurrentRequest = new cHTTPRequest;
if (!m_CurrentRequest->ParseHeaders(m_IncomingHeaderData.c_str(), idxEnd + 2)) int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
if (BytesConsumed < 0)
{ {
delete m_CurrentRequest; delete m_CurrentRequest;
m_CurrentRequest = NULL; m_CurrentRequest = NULL;
@ -131,20 +122,29 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
m_HTTPServer.CloseConnection(*this); m_HTTPServer.CloseConnection(*this);
return; 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_State = wcsRecvBody;
m_HTTPServer.NewRequest(*this, *m_CurrentRequest); m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength(); 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: // Process the rest of the incoming data into the request body:
if (m_IncomingHeaderData.size() > idxEnd + 4) if (a_Size > BytesConsumed)
{ {
m_IncomingHeaderData.erase(0, idxEnd + 4); DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
DataReceived(m_IncomingHeaderData.c_str(), m_IncomingHeaderData.size());
m_IncomingHeaderData.clear();
} }
else else
{ {
m_IncomingHeaderData.clear();
DataReceived("", 0); // If the request has zero body length, let it be processed right-away DataReceived("", 0); // If the request has zero body length, let it be processed right-away
} }
break; break;

View File

@ -31,7 +31,7 @@ public:
enum eState enum eState
{ {
wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest == NULL) wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if NULL)
wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid) wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid)
wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL) wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL)
wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL) wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL)

View File

@ -6,19 +6,15 @@
#include "Globals.h" #include "Globals.h"
#include "HTTPFormParser.h" #include "HTTPFormParser.h"
#include "HTTPMessage.h" #include "HTTPMessage.h"
#include "MultipartParser.h"
#include "NameValueParser.h"
AString cHTTPFormParser::m_FormURLEncoded("application/x-www-form-urlencoded"); cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) :
AString cHTTPFormParser::m_MultipartFormData("multipart/form-data"); m_Callbacks(a_Callbacks),
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
m_IsValid(true) m_IsValid(true)
{ {
if (a_Request.GetMethod() == "GET") if (a_Request.GetMethod() == "GET")
@ -36,14 +32,15 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
} }
if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT"))
{ {
if (a_Request.GetContentType() == m_FormURLEncoded) if (a_Request.GetContentType() == "application/x-www-form-urlencoded")
{ {
m_Kind = fpkFormUrlEncoded; m_Kind = fpkFormUrlEncoded;
return; return;
} }
if (a_Request.GetContentType().substr(0, m_MultipartFormData.length()) == m_MultipartFormData) if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0)
{ {
m_Kind = fpkMultipart; m_Kind = fpkMultipart;
BeginMultipart(a_Request);
return; return;
} }
} }
@ -56,18 +53,24 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
void cHTTPFormParser::Parse(const char * a_Data, int a_Size) void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
{ {
m_IncomingData.append(a_Data, a_Size); if (!m_IsValid)
{
return;
}
switch (m_Kind) switch (m_Kind)
{ {
case fpkURL: case fpkURL:
case fpkFormUrlEncoded: case fpkFormUrlEncoded:
{ {
// This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish() // 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; break;
} }
case fpkMultipart: case fpkMultipart:
{ {
ParseMultipart(); ASSERT(m_MultipartParser.get() != NULL);
m_MultipartParser->Parse(a_Data, a_Size);
break; break;
} }
default: default:
@ -105,8 +108,8 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
{ {
const AString & ContentType = a_Request.GetContentType(); const AString & ContentType = a_Request.GetContentType();
return ( return (
(ContentType == m_FormURLEncoded) || (ContentType == "application/x-www-form-urlencoded") ||
(ContentType.substr(0, m_MultipartFormData.length()) == m_MultipartFormData) || (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) ||
( (
(a_Request.GetMethod() == "GET") && (a_Request.GetMethod() == "GET") &&
(a_Request.GetURL().find('?') != AString::npos) (a_Request.GetURL().find('?') != AString::npos)
@ -119,6 +122,16 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
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) void cHTTPFormParser::ParseFormUrlEncoded(void)
{ {
// Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish() // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish()
@ -156,9 +169,107 @@ void cHTTPFormParser::ParseFormUrlEncoded(void)
void cHTTPFormParser::ParseMultipart(void) void cHTTPFormParser::OnPartStart(void)
{ {
// TODO 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();
} }

View File

@ -8,6 +8,8 @@
#pragma once #pragma once
#include "MultipartParser.h"
@ -20,10 +22,25 @@ class cHTTPRequest;
class cHTTPFormParser : class cHTTPFormParser :
public std::map<AString, AString> public std::map<AString, AString>,
public cMultipartParser::cCallbacks
{ {
public: public:
cHTTPFormParser(cHTTPRequest & a_Request); 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;
} ;
cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks);
/// Adds more data into the parser, as the request body is received /// Adds more data into the parser, as the request body is received
void Parse(const char * a_Data, int a_Size); void Parse(const char * a_Data, int a_Size);
@ -41,26 +58,48 @@ protected:
{ {
fpkURL, ///< The form has been transmitted as parameters to a GET request 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" 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/*". Currently unsupported fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
}; };
/// The callbacks to call for incoming file data
cCallbacks & m_Callbacks;
/// The kind of the parser (decided in the constructor, used in Parse() /// The kind of the parser (decided in the constructor, used in Parse()
eKind m_Kind; eKind m_Kind;
/// Buffer for the incoming data until it's parsed
AString m_IncomingData; 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; bool m_IsValid;
/// Simple static objects to hold the various strings for comparison with request's content-type /// The parser for the multipart data, if used
static AString m_FormURLEncoded; std::auto_ptr<cMultipartParser> m_MultipartParser;
static AString m_MultipartFormData;
/// 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) /// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds)
void ParseFormUrlEncoded(void); void ParseFormUrlEncoded(void);
/// Parses m_IncomingData as multipart data (fpkMultipart kind) // cMultipartParser::cCallbacks overrides:
void ParseMultipart(void); 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;
} ; } ;

View File

@ -10,11 +10,22 @@
// 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::cHTTPMessage(eKind a_Kind) : cHTTPMessage::cHTTPMessage(eKind a_Kind) :
m_Kind(a_Kind) m_Kind(a_Kind),
m_ContentLength(-1)
{ {
} }
@ -24,10 +35,12 @@ cHTTPMessage::cHTTPMessage(eKind a_Kind) :
void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
{ {
cNameValueMap::iterator itr = m_Headers.find(a_Key); AString Key = a_Key;
StrToLower(Key);
cNameValueMap::iterator itr = m_Headers.find(Key);
if (itr == m_Headers.end()) if (itr == m_Headers.end())
{ {
m_Headers[a_Key] = a_Value; m_Headers[Key] = a_Value;
} }
else else
{ {
@ -37,13 +50,13 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
} }
// Special processing for well-known headers: // Special processing for well-known headers:
if (a_Key == "Content-Type") if (Key == "content-type")
{ {
m_ContentType = m_Headers["Content-Type"]; m_ContentType = m_Headers[Key];
} }
else if (a_Key == "Content-Length") else if (Key == "content-length")
{ {
m_ContentLength = atoi(m_Headers["Content-Length"].c_str()); m_ContentLength = atoi(m_Headers[Key].c_str());
} }
} }
@ -56,6 +69,8 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
cHTTPRequest::cHTTPRequest(void) : cHTTPRequest::cHTTPRequest(void) :
super(mkRequest), super(mkRequest),
m_EnvelopeParser(*this),
m_IsValid(true),
m_UserData(NULL) m_UserData(NULL)
{ {
} }
@ -64,66 +79,75 @@ cHTTPRequest::cHTTPRequest(void) :
bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size)
{ {
// The first line contains the method and the URL: if (!m_IsValid)
size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd);
if (Next == AString::npos)
{ {
return false; return -1;
} }
// The following lines contain headers: if (m_Method.empty())
AString Key;
const char * Data = a_IncomingData + Next;
size_t End = a_IdxEnd - Next;
while (End > 0)
{ {
Next = ParseHeaderField(Data, End, Key); // The first line hasn't been processed yet
if (Next == AString::npos) int res = ParseRequestLine(a_Data, a_Size);
if ((res < 0) || (res == a_Size))
{ {
return false; return res;
} }
ASSERT(End >= Next); int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
Data += Next; if (res2 < 0)
End -= Next; {
m_IsValid = false;
return res2;
}
return res2 + res;
} }
if (!HasReceivedContentLength()) if (m_EnvelopeParser.IsInHeaders())
{ {
SetContentLength(0); int res = m_EnvelopeParser.Parse(a_Data, a_Size);
if (res < 0)
{
m_IsValid = false;
}
return res;
} }
return true; return 0;
} }
size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) 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") // Ignore the initial CRLFs (HTTP spec's "should")
size_t LineStart = 0; size_t LineStart = 0;
while ( while (
(LineStart < a_IdxEnd) && (LineStart < IdxEnd) &&
( (
(a_Data[LineStart] == '\r') || (m_IncomingHeaderData[LineStart] == '\r') ||
(a_Data[LineStart] == '\n') (m_IncomingHeaderData[LineStart] == '\n')
) )
) )
{ {
LineStart++; LineStart++;
} }
if (LineStart >= a_IdxEnd) if (LineStart >= IdxEnd)
{ {
return AString::npos; m_IsValid = false;
return -1;
} }
size_t Last = LineStart;
int NumSpaces = 0; int NumSpaces = 0;
for (size_t i = LineStart; i < a_IdxEnd; i++) size_t MethodEnd = 0;
size_t URLEnd = 0;
for (size_t i = LineStart; i < IdxEnd; i++)
{ {
switch (a_Data[i]) switch (m_IncomingHeaderData[i])
{ {
case ' ': case ' ':
{ {
@ -131,124 +155,56 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd)
{ {
case 0: case 0:
{ {
m_Method.assign(a_Data, Last, i - Last); MethodEnd = i;
break; break;
} }
case 1: case 1:
{ {
m_URL.assign(a_Data, Last, i - Last); URLEnd = i;
break; break;
} }
default: default:
{ {
// Too many spaces in the request // Too many spaces in the request
return AString::npos; m_IsValid = false;
return -1;
} }
} }
Last = i + 1;
NumSpaces += 1; NumSpaces += 1;
break; break;
} }
case '\n': case '\n':
{ {
if ((i == 0) || (a_Data[i - 1] != '\r') || (NumSpaces != 2) || (i < Last + 7)) 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 // LF too early, without a CR, without two preceeding spaces or too soon after the second space
return AString::npos; m_IsValid = false;
return -1;
} }
// Check that there's HTTP/version at the end // Check that there's HTTP/version at the end
if (strncmp(a_Data + Last, "HTTP/1.", 7) != 0) if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0)
{ {
return AString::npos; 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; return i + 1;
} }
} // switch (a_Data[i]) } // switch (m_IncomingHeaderData[i])
} // for i - a_Data[]
return AString::npos;
}
size_t cHTTPRequest::ParseHeaderField(const char * a_Data, size_t a_IdxEnd, AString & a_Key)
{
if (*a_Data <= ' ')
{
size_t res = ParseHeaderFieldContinuation(a_Data + 1, a_IdxEnd - 1, a_Key);
return (res == AString::npos) ? res : (res + 1);
}
size_t ValueIdx = 0;
AString Key;
for (size_t i = 0; i < a_IdxEnd; i++)
{
switch (a_Data[i])
{
case '\n':
{
if ((ValueIdx == 0) || (i < ValueIdx - 2) || (i == 0) || (a_Data[i - 1] != '\r'))
{
// Invalid header field - no colon or no CR before LF
return AString::npos;
}
AString Value(a_Data, ValueIdx + 1, i - ValueIdx - 2);
AddHeader(Key, Value);
a_Key = Key;
return i + 1;
}
case ':':
{
if (ValueIdx == 0)
{
Key.assign(a_Data, 0, i);
ValueIdx = i;
}
break;
}
case ' ':
case '\t':
{
if (ValueIdx == i - 1)
{
// Value has started in this char, but it is whitespace, so move the start one char further
ValueIdx = i;
}
}
} // switch (char)
} // for i - m_IncomingHeaderData[] } // for i - m_IncomingHeaderData[]
// No header found, return the end-of-data index:
return a_IdxEnd; // CRLF hasn't been encountered yet, consider all data consumed
return a_Size;
} }
size_t cHTTPRequest::ParseHeaderFieldContinuation(const char * a_Data, size_t a_IdxEnd, AString & a_Key) void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value)
{ {
size_t Start = 0; AddHeader(a_Key, a_Value);
for (size_t i = 0; i < a_IdxEnd; i++)
{
if ((a_Data[i] > ' ') && (Start == 0))
{
Start = i;
}
else if (a_Data[i] == '\n')
{
if ((i == 0) || (a_Data[i - 1] != '\r'))
{
// There wasn't a CR before this LF
return AString::npos;
}
AString Value(a_Data, 0, i - Start - 1);
AddHeader(a_Key, Value);
return i + 1;
}
}
// LF not found, how? We found it at the header end (CRLFCRLF)
ASSERT(!"LF not found, wtf?");
return AString::npos;
} }

View File

@ -9,6 +9,8 @@
#pragma once #pragma once
#include "EnvelopeParser.h"
@ -58,15 +60,18 @@ protected:
class cHTTPRequest : class cHTTPRequest :
public cHTTPMessage public cHTTPMessage,
protected cEnvelopeParser::cCallbacks
{ {
typedef cHTTPMessage super; typedef cHTTPMessage super;
public: public:
cHTTPRequest(void); cHTTPRequest(void);
/// Parses the headers information from the received data in the specified string of incoming data. Returns true if successful. /** Parses the request line and then headers from the received data.
bool ParseHeaders(const char * a_IncomingData, size_t a_idxEnd); 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 /// Returns true if the request did contain a Content-Length header
bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); } bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); }
@ -83,7 +88,19 @@ public:
/// Retrieves the UserData pointer that has been stored within this request. /// Retrieves the UserData pointer that has been stored within this request.
void * GetUserData(void) const { return m_UserData; } 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(); }
protected: 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 / ...) /// Method of the request (GET / PUT / POST / ...)
AString m_Method; AString m_Method;
@ -94,21 +111,13 @@ protected:
void * m_UserData; void * m_UserData;
/** Parses the RequestLine out of a_Data, up to index a_IdxEnd /** Parses the incoming data for the first line (RequestLine)
Returns the index to the next line, or npos if invalid request Returns the number of bytes consumed, or -1 for an error
*/ */
size_t ParseRequestLine(const char * a_Data, size_t a_IdxEnd); int ParseRequestLine(const char * a_Data, int a_Size);
/** Parses one header field out of a_Data, up to offset a_IdxEnd. // cEnvelopeParser::cCallbacks overrides:
Returns the index to the next line (relative to a_Data), or npos if invalid request. virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
a_Key is set to the key that was parsed (used for multi-line headers)
*/
size_t ParseHeaderField(const char * a_Data, size_t a_IdxEnd, AString & a_Key);
/** Parses one header field that is known to be a continuation of previous header.
Returns the index to the next line, or npos if invalid request.
*/
size_t ParseHeaderFieldContinuation(const char * a_Data, size_t a_IdxEnd, AString & a_Key);
} ; } ;

View File

@ -24,13 +24,14 @@
class cDebugCallbacks : class cDebugCallbacks :
public cHTTPServer::cCallbacks public cHTTPServer::cCallbacks,
protected cHTTPFormParser::cCallbacks
{ {
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
{ {
if (cHTTPFormParser::HasFormData(a_Request)) if (cHTTPFormParser::HasFormData(a_Request))
{ {
a_Request.SetUserData(new cHTTPFormParser(a_Request)); a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
} }
} }
@ -79,6 +80,23 @@ class cDebugCallbacks :
} }
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; } g_DebugCallbacks;