HTTP Server can now parse multipart/form-data forms; better architecture.
This commit is contained in:
parent
9a33732f6a
commit
1012fd82fd
@ -2719,6 +2719,14 @@
|
||||
<Filter
|
||||
Name="HTTPServer"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\EnvelopeParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\EnvelopeParser.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPConnection.cpp"
|
||||
>
|
||||
@ -2751,6 +2759,22 @@
|
||||
RelativePath="..\source\HTTPServer\HTTPServer.h"
|
||||
>
|
||||
</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
|
||||
|
@ -108,22 +108,13 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
|
||||
{
|
||||
case wcsRecvHeaders:
|
||||
{
|
||||
ASSERT(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)
|
||||
if (m_CurrentRequest == NULL)
|
||||
{
|
||||
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;
|
||||
m_CurrentRequest = NULL;
|
||||
@ -131,20 +122,29 @@ void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
|
||||
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 (m_IncomingHeaderData.size() > idxEnd + 4)
|
||||
if (a_Size > BytesConsumed)
|
||||
{
|
||||
m_IncomingHeaderData.erase(0, idxEnd + 4);
|
||||
DataReceived(m_IncomingHeaderData.c_str(), m_IncomingHeaderData.size());
|
||||
m_IncomingHeaderData.clear();
|
||||
DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_IncomingHeaderData.clear();
|
||||
DataReceived("", 0); // If the request has zero body length, let it be processed right-away
|
||||
}
|
||||
break;
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
|
||||
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)
|
||||
wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL)
|
||||
wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL)
|
||||
|
@ -6,19 +6,15 @@
|
||||
#include "Globals.h"
|
||||
#include "HTTPFormParser.h"
|
||||
#include "HTTPMessage.h"
|
||||
#include "MultipartParser.h"
|
||||
#include "NameValueParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString cHTTPFormParser::m_FormURLEncoded("application/x-www-form-urlencoded");
|
||||
AString cHTTPFormParser::m_MultipartFormData("multipart/form-data");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
|
||||
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) :
|
||||
m_Callbacks(a_Callbacks),
|
||||
m_IsValid(true)
|
||||
{
|
||||
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.GetContentType() == m_FormURLEncoded)
|
||||
if (a_Request.GetContentType() == "application/x-www-form-urlencoded")
|
||||
{
|
||||
m_Kind = fpkFormUrlEncoded;
|
||||
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;
|
||||
BeginMultipart(a_Request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -56,18 +53,24 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
|
||||
|
||||
void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
m_IncomingData.append(a_Data, 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:
|
||||
{
|
||||
ParseMultipart();
|
||||
ASSERT(m_MultipartParser.get() != NULL);
|
||||
m_MultipartParser->Parse(a_Data, a_Size);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -105,8 +108,8 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
|
||||
{
|
||||
const AString & ContentType = a_Request.GetContentType();
|
||||
return (
|
||||
(ContentType == m_FormURLEncoded) ||
|
||||
(ContentType.substr(0, m_MultipartFormData.length()) == m_MultipartFormData) ||
|
||||
(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)
|
||||
@ -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)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MultipartParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
@ -20,10 +22,25 @@ class cHTTPRequest;
|
||||
|
||||
|
||||
class cHTTPFormParser :
|
||||
public std::map<AString, AString>
|
||||
public std::map<AString, AString>,
|
||||
public cMultipartParser::cCallbacks
|
||||
{
|
||||
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
|
||||
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
|
||||
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()
|
||||
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;
|
||||
|
||||
/// Simple static objects to hold the various strings for comparison with request's content-type
|
||||
static AString m_FormURLEncoded;
|
||||
static AString m_MultipartFormData;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Parses m_IncomingData as multipart data (fpkMultipart kind)
|
||||
void ParseMultipart(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;
|
||||
} ;
|
||||
|
||||
|
||||
|
@ -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(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)
|
||||
{
|
||||
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())
|
||||
{
|
||||
m_Headers[a_Key] = a_Value;
|
||||
m_Headers[Key] = a_Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -37,13 +50,13 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
|
||||
}
|
||||
|
||||
// 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) :
|
||||
super(mkRequest),
|
||||
m_EnvelopeParser(*this),
|
||||
m_IsValid(true),
|
||||
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:
|
||||
size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd);
|
||||
if (Next == AString::npos)
|
||||
if (!m_IsValid)
|
||||
{
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The following lines contain headers:
|
||||
AString Key;
|
||||
const char * Data = a_IncomingData + Next;
|
||||
size_t End = a_IdxEnd - Next;
|
||||
while (End > 0)
|
||||
if (m_Method.empty())
|
||||
{
|
||||
Next = ParseHeaderField(Data, End, Key);
|
||||
if (Next == AString::npos)
|
||||
// The first line hasn't been processed yet
|
||||
int res = ParseRequestLine(a_Data, a_Size);
|
||||
if ((res < 0) || (res == a_Size))
|
||||
{
|
||||
return false;
|
||||
return res;
|
||||
}
|
||||
ASSERT(End >= Next);
|
||||
Data += Next;
|
||||
End -= Next;
|
||||
int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
|
||||
if (res2 < 0)
|
||||
{
|
||||
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")
|
||||
size_t LineStart = 0;
|
||||
while (
|
||||
(LineStart < a_IdxEnd) &&
|
||||
(LineStart < IdxEnd) &&
|
||||
(
|
||||
(a_Data[LineStart] == '\r') ||
|
||||
(a_Data[LineStart] == '\n')
|
||||
(m_IncomingHeaderData[LineStart] == '\r') ||
|
||||
(m_IncomingHeaderData[LineStart] == '\n')
|
||||
)
|
||||
)
|
||||
{
|
||||
LineStart++;
|
||||
}
|
||||
if (LineStart >= a_IdxEnd)
|
||||
if (LineStart >= IdxEnd)
|
||||
{
|
||||
return AString::npos;
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t Last = LineStart;
|
||||
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 ' ':
|
||||
{
|
||||
@ -131,124 +155,56 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_Method.assign(a_Data, Last, i - Last);
|
||||
MethodEnd = i;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
m_URL.assign(a_Data, Last, i - Last);
|
||||
URLEnd = i;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Too many spaces in the request
|
||||
return AString::npos;
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Last = i + 1;
|
||||
NumSpaces += 1;
|
||||
break;
|
||||
}
|
||||
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
|
||||
return AString::npos;
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
} // switch (a_Data[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)
|
||||
} // switch (m_IncomingHeaderData[i])
|
||||
} // 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;
|
||||
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;
|
||||
AddHeader(a_Key, a_Value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EnvelopeParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
@ -58,15 +60,18 @@ protected:
|
||||
|
||||
|
||||
class cHTTPRequest :
|
||||
public cHTTPMessage
|
||||
public cHTTPMessage,
|
||||
protected cEnvelopeParser::cCallbacks
|
||||
{
|
||||
typedef cHTTPMessage super;
|
||||
|
||||
public:
|
||||
cHTTPRequest(void);
|
||||
|
||||
/// Parses the headers information from the received data in the specified string of incoming data. Returns true if successful.
|
||||
bool ParseHeaders(const char * a_IncomingData, size_t a_idxEnd);
|
||||
/** 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); }
|
||||
@ -83,7 +88,19 @@ public:
|
||||
/// 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(); }
|
||||
|
||||
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;
|
||||
|
||||
@ -94,21 +111,13 @@ protected:
|
||||
void * m_UserData;
|
||||
|
||||
|
||||
/** Parses the RequestLine out of a_Data, up to index a_IdxEnd
|
||||
Returns the index to the next line, or npos if invalid request
|
||||
/** Parses the incoming data for the first line (RequestLine)
|
||||
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.
|
||||
Returns the index to the next line (relative to a_Data), or npos if invalid request.
|
||||
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);
|
||||
// cEnvelopeParser::cCallbacks overrides:
|
||||
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
@ -24,13 +24,14 @@
|
||||
|
||||
|
||||
class cDebugCallbacks :
|
||||
public cHTTPServer::cCallbacks
|
||||
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));
|
||||
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;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user