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