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 +++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 source/HTTPServer/HTTPFormParser.cpp (limited to 'source/HTTPServer/HTTPFormParser.cpp') 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 +} + + + + -- cgit v1.2.3 From b883a0b514f91e62dd0be4924e609b1bb0b53f4c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 20:06:35 +0200 Subject: Fixed recognition of multipart-form-data forms. --- source/HTTPServer/HTTPFormParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'source/HTTPServer/HTTPFormParser.cpp') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 3412bcc94..6f6dc02b2 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -34,7 +34,7 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : m_Kind = fpkFormUrlEncoded; return; } - if (a_Request.GetContentType() == "multipart/form-data") + if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) { m_Kind = fpkMultipart; return; @@ -98,7 +98,7 @@ bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) { return ( (a_Request.GetContentType() == "application/x-www-form-urlencoded") || - (a_Request.GetContentType() == "multipart/form-data") || + (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) || ( (a_Request.GetMethod() == "GET") && (a_Request.GetURL().find('?') != AString::npos) -- cgit v1.2.3 From bb0fb0aa3055d797e58bc17d515e55a447c9e6a3 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 28 Sep 2013 23:02:16 +0200 Subject: Improved the HTTPFormParser code. No change to the functionality. --- source/HTTPServer/HTTPFormParser.cpp | 69 +++++++----------------------------- 1 file changed, 12 insertions(+), 57 deletions(-) (limited to 'source/HTTPServer/HTTPFormParser.cpp') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 6f6dc02b2..631424391 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -11,6 +11,13 @@ +AString cHTTPFormParser::m_FormURLEncoded("application/x-www-form-urlencoded"); +AString cHTTPFormParser::m_MultipartFormData("multipart/form-data"); + + + + + cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : m_IsValid(true) { @@ -29,12 +36,12 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request) : } if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) { - if (a_Request.GetContentType() == "application/x-www-form-urlencoded") + if (a_Request.GetContentType() == m_FormURLEncoded) { m_Kind = fpkFormUrlEncoded; return; } - if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) + if (a_Request.GetContentType().substr(0, m_MultipartFormData.length()) == m_MultipartFormData) { m_Kind = fpkMultipart; return; @@ -96,9 +103,10 @@ bool cHTTPFormParser::Finish(void) bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request) { + const AString & ContentType = a_Request.GetContentType(); return ( - (a_Request.GetContentType() == "application/x-www-form-urlencoded") || - (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0) || + (ContentType == m_FormURLEncoded) || + (ContentType.substr(0, m_MultipartFormData.length()) == m_MultipartFormData) || ( (a_Request.GetMethod() == "GET") && (a_Request.GetURL().find('?') != AString::npos) @@ -142,59 +150,6 @@ void cHTTPFormParser::ParseFormUrlEncoded(void) } } // 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[] - */ } -- cgit v1.2.3 From 1012fd82fda9e9bc75d2308a3c68cb3b3738bf1b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 13:07:57 +0200 Subject: HTTP Server can now parse multipart/form-data forms; better architecture. --- source/HTTPServer/HTTPFormParser.cpp | 143 +++++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 16 deletions(-) (limited to 'source/HTTPServer/HTTPFormParser.cpp') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 631424391..85a789f7d 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -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(); } -- cgit v1.2.3 From b5c90d7b20fede4e643e96417684c7c009d063cb Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sat, 5 Oct 2013 23:08:16 +0200 Subject: WebAdmin uses the new HTTP functionality. This is a partial implementation of #183. --- source/HTTPServer/HTTPFormParser.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'source/HTTPServer/HTTPFormParser.cpp') diff --git a/source/HTTPServer/HTTPFormParser.cpp b/source/HTTPServer/HTTPFormParser.cpp index 85a789f7d..7db7b4e6d 100644 --- a/source/HTTPServer/HTTPFormParser.cpp +++ b/source/HTTPServer/HTTPFormParser.cpp @@ -32,7 +32,7 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callba } if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT")) { - if (a_Request.GetContentType() == "application/x-www-form-urlencoded") + if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0) { m_Kind = fpkFormUrlEncoded; return; @@ -44,7 +44,8 @@ cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callba return; } } - ASSERT(!"Unhandled request method"); + // Invalid method / content type combination, this is not a HTTP form + m_IsValid = false; } -- cgit v1.2.3