From 8130e6dd5439e381aae18532ede48441a4b46155 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 19:30:25 +0200 Subject: Created basic cHTTPFormParser. It can parse forms in the application/x-www-form-urlencoded encoding, used for forms without file uploads. --- source/HTTPServer/HTTPFormParser.cpp | 211 +++++++++++++++++++++++++++++++++++ source/HTTPServer/HTTPFormParser.h | 64 +++++++++++ source/HTTPServer/HTTPMessage.cpp | 1 + source/HTTPServer/HTTPMessage.h | 6 + source/HTTPServer/HTTPServer.cpp | 36 +++++- 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 source/HTTPServer/HTTPFormParser.cpp create mode 100644 source/HTTPServer/HTTPFormParser.h (limited to 'source') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp new file mode 100644 index 000000000..3412bcc94 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -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 +} + + + + diff --git a/source/HTTPServer/HTTPFormParser.h b/source/HTTPServer/HTTPFormParser.h new file mode 100644 index 000000000..72a7dfc05 --- /dev/null +++ b/source/HTTPServer/HTTPFormParser.h @@ -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 +{ +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); +} ; + + + + diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index 3ad838ac0..72c603295 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -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") diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index 7b1a27eaa..1c2514739 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -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; } diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 8494d6fce..86fd545f6 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -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("\r\n"); + for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr) + { + a_Connection.Send(Printf("\r\n", itr->first.c_str(), itr->second.c_str())); + } // for itr - FormParser[] + a_Connection.Send("
NameValue
%s
%s
"); + 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); -- cgit v1.2.3