Created basic cHTTPFormParser.
It can parse forms in the application/x-www-form-urlencoded encoding, used for forms without file uploads.
This commit is contained in:
parent
3b473f7a67
commit
8130e6dd54
@ -2727,6 +2727,14 @@
|
||||
RelativePath="..\source\HTTPServer\HTTPConnection.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPFormParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPFormParser.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPMessage.cpp"
|
||||
>
|
||||
|
211
source/HTTPServer/HTTPFormParser.cpp
Normal file
211
source/HTTPServer/HTTPFormParser.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
|
||||
// HTTPFormParser.cpp
|
||||
|
||||
// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP
|
||||
|
||||
#include "Globals.h"
|
||||
#include "HTTPFormParser.h"
|
||||
#include "HTTPMessage.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) :
|
||||
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 (a_Request.GetContentType() == "application/x-www-form-urlencoded")
|
||||
{
|
||||
m_Kind = fpkFormUrlEncoded;
|
||||
return;
|
||||
}
|
||||
if (a_Request.GetContentType() == "multipart/form-data")
|
||||
{
|
||||
m_Kind = fpkMultipart;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ASSERT(!"Unhandled request method");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
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()
|
||||
break;
|
||||
}
|
||||
case fpkMultipart:
|
||||
{
|
||||
ParseMultipart();
|
||||
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)
|
||||
{
|
||||
return (
|
||||
(a_Request.GetContentType() == "application/x-www-form-urlencoded") ||
|
||||
(a_Request.GetContentType() == "multipart/form-data") ||
|
||||
(
|
||||
(a_Request.GetMethod() == "GET") &&
|
||||
(a_Request.GetURL().find('?') != AString::npos)
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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();
|
||||
|
||||
/*
|
||||
size_t len = m_IncomingData.size();
|
||||
if (len == 0)
|
||||
{
|
||||
// No values in the form, consider this valid, too.
|
||||
return;
|
||||
}
|
||||
size_t len1 = len - 1;
|
||||
|
||||
for (size_t i = 0; i < len; )
|
||||
{
|
||||
char ch = m_IncomingData[i];
|
||||
AString Name;
|
||||
AString Value;
|
||||
while ((i < len1) && (ch != '=') && (ch != '&'))
|
||||
{
|
||||
if (ch == '+')
|
||||
{
|
||||
ch = ' ';
|
||||
}
|
||||
Name.push_back(ch);
|
||||
ch = m_IncomingData[++i];
|
||||
}
|
||||
if (i == len1)
|
||||
{
|
||||
Value.push_back(ch);
|
||||
}
|
||||
|
||||
if (ch == '=')
|
||||
{
|
||||
ch = m_IncomingData[++i];
|
||||
while ((i < len1) && (ch != '&'))
|
||||
{
|
||||
if (ch == '+')
|
||||
{
|
||||
ch = ' ';
|
||||
}
|
||||
Value.push_back(ch);
|
||||
ch = m_IncomingData[++i];
|
||||
}
|
||||
if (i == len1)
|
||||
{
|
||||
Value.push_back(ch);
|
||||
}
|
||||
}
|
||||
(*this)[URLDecode(Name)] = URLDecode(Value);
|
||||
if (ch == '&')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
} // for i - m_IncomingData[]
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::ParseMultipart(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
64
source/HTTPServer/HTTPFormParser.h
Normal file
64
source/HTTPServer/HTTPFormParser.h
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
// HTTPFormParser.h
|
||||
|
||||
// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cHTTPRequest;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPFormParser :
|
||||
public std::map<AString, AString>
|
||||
{
|
||||
public:
|
||||
cHTTPFormParser(cHTTPRequest & a_Request);
|
||||
|
||||
/// 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:
|
||||
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/*". Currently unsupported
|
||||
};
|
||||
|
||||
/// The kind of the parser (decided in the constructor, used in Parse()
|
||||
eKind m_Kind;
|
||||
|
||||
AString m_IncomingData;
|
||||
|
||||
bool m_IsValid;
|
||||
|
||||
|
||||
/// 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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -99,6 +99,7 @@ bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd)
|
||||
|
||||
|
||||
|
||||
|
||||
size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd)
|
||||
{
|
||||
// Ignore the initial CRLFs (HTTP spec's "should")
|
||||
|
@ -71,6 +71,12 @@ public:
|
||||
/// 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; }
|
||||
|
||||
/// 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; }
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "HTTPServer.h"
|
||||
#include "HTTPMessage.h"
|
||||
#include "HTTPConnection.h"
|
||||
#include "HTTPFormParser.h"
|
||||
|
||||
|
||||
|
||||
@ -27,18 +28,49 @@ class cDebugCallbacks :
|
||||
{
|
||||
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
|
||||
{
|
||||
// Nothing needed
|
||||
if (cHTTPFormParser::HasFormData(a_Request))
|
||||
{
|
||||
a_Request.SetUserData(new cHTTPFormParser(a_Request));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override
|
||||
{
|
||||
// TODO
|
||||
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");
|
||||
}
|
||||
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/plain");
|
||||
a_Connection.Send(Resp);
|
||||
|
Loading…
Reference in New Issue
Block a user