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
|
<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
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 true;
|
return res;
|
||||||
|
}
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user