From f4efcb90808603bbfce5a149f5490bd6fceb880f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 18:14:26 +0200 Subject: Rewritten HTTPServer to split into cHTTPConnection, cHTTPRequest and cHTTPResponse classes. --- source/HTTPServer/HTTPMessage.cpp | 285 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 source/HTTPServer/HTTPMessage.cpp (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp new file mode 100644 index 000000000..b784cb941 --- /dev/null +++ b/source/HTTPServer/HTTPMessage.cpp @@ -0,0 +1,285 @@ + +// HTTPMessage.cpp + +// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes + +#include "Globals.h" +#include "HTTPMessage.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPMessage: + +cHTTPMessage::cHTTPMessage(eKind a_Kind) : + m_Kind(a_Kind) +{ +} + + + + + +void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) +{ + cNameValueMap::iterator itr = m_Headers.find(a_Key); + if (itr == m_Headers.end()) + { + m_Headers[a_Key] = a_Value; + } + else + { + // The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2) + itr->second.append(", "); + itr->second.append(a_Value); + } + + // Special processing for well-known headers: + if (a_Key == "Content-Type") + { + m_ContentType = m_Headers["Content-Type"]; + } + else if (a_Key == "Content-Length") + { + m_ContentLength = atoi(m_Headers["Content-Length"].c_str()); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPRequest: + +cHTTPRequest::cHTTPRequest(void) : + super(mkRequest) +{ +} + + + + + +bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) +{ + // The first line contains the method and the URL: + size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd); + if (Next == AString::npos) + { + return false; + } + + // The following lines contain headers: + AString Key; + const char * Data = a_IncomingData + Next; + size_t End = a_IdxEnd - Next; + while (End > 0) + { + Next = ParseHeaderField(Data, End, Key); + if (Next == AString::npos) + { + return false; + } + ASSERT(End >= Next); + Data += Next; + End -= Next; + } + + return HasReceivedContentLength(); +} + + + + +size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) +{ + // Ignore the initial CRLFs (HTTP spec's "should") + size_t LineStart = 0; + while ( + (LineStart < a_IdxEnd) && + ( + (a_Data[LineStart] == '\r') || + (a_Data[LineStart] == '\n') + ) + ) + { + LineStart++; + } + if (LineStart >= a_IdxEnd) + { + return AString::npos; + } + + size_t Last = LineStart; + int NumSpaces = 0; + for (size_t i = LineStart; i < a_IdxEnd; i++) + { + switch (a_Data[i]) + { + case ' ': + { + switch (NumSpaces) + { + case 0: + { + m_Method.assign(a_Data, Last, i - Last - 1); + break; + } + case 1: + { + m_URL.assign(a_Data, Last, i - Last - 1); + break; + } + default: + { + // Too many spaces in the request + return AString::npos; + } + } + Last = i + 1; + NumSpaces += 1; + break; + } + case '\n': + { + if ((i == 0) || (a_Data[i] != '\r') || (NumSpaces != 2) || (i < Last + 7)) + { + // LF too early, without a CR, without two preceeding spaces or too soon after the second space + return AString::npos; + } + // Check that there's HTTP/version at the end + if (strncmp(a_Data + Last, "HTTP/1.", 7) != 0) + { + return AString::npos; + } + return i; + } + } // switch (a_Data[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[] + // No header found, return the end-of-data index: + return a_IdxEnd; +} + + + + + +size_t cHTTPRequest::ParseHeaderFieldContinuation(const char * a_Data, size_t a_IdxEnd, AString & a_Key) +{ + size_t Start = 0; + 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; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHTTPResponse: + +cHTTPResponse::cHTTPResponse(void) : + super(mkResponse) +{ +} + + + + + +void cHTTPResponse::AppendToData(AString & a_DataStream) const +{ + a_DataStream.append("200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); + a_DataStream.append(m_ContentType); + a_DataStream.append("\r\n"); + for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr) + { + if ((itr->first == "Content-Type") || (itr->first == "Content-Length")) + { + continue; + } + a_DataStream.append(itr->first); + a_DataStream.append(": "); + a_DataStream.append(itr->second); + a_DataStream.append("\r\n"); + } // for itr - m_Headers[] + a_DataStream.append("\r\n"); +} + + + + -- cgit v1.2.3 From 0c3fd5e77d681c25757efaab6acb305d0b5630c1 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 20:33:18 +0200 Subject: Fixed parsing and implemented write nofitication. The web connection finally works with a browser. --- source/HTTPServer/HTTPMessage.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index b784cb941..b2e21c712 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -88,7 +88,11 @@ bool cHTTPRequest::ParseHeaders(const char * a_IncomingData, size_t a_IdxEnd) End -= Next; } - return HasReceivedContentLength(); + if (!HasReceivedContentLength()) + { + SetContentLength(0); + } + return true; } @@ -125,12 +129,12 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { case 0: { - m_Method.assign(a_Data, Last, i - Last - 1); + m_Method.assign(a_Data, Last, i - Last); break; } case 1: { - m_URL.assign(a_Data, Last, i - Last - 1); + m_URL.assign(a_Data, Last, i - Last); break; } default: @@ -145,7 +149,7 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) } case '\n': { - if ((i == 0) || (a_Data[i] != '\r') || (NumSpaces != 2) || (i < Last + 7)) + if ((i == 0) || (a_Data[i - 1] != '\r') || (NumSpaces != 2) || (i < Last + 7)) { // LF too early, without a CR, without two preceeding spaces or too soon after the second space return AString::npos; @@ -155,7 +159,7 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { return AString::npos; } - return i; + return i + 1; } } // switch (a_Data[i]) } // for i - a_Data[] @@ -263,7 +267,7 @@ cHTTPResponse::cHTTPResponse(void) : void cHTTPResponse::AppendToData(AString & a_DataStream) const { - a_DataStream.append("200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); + a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: "); a_DataStream.append(m_ContentType); a_DataStream.append("\r\n"); for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr) -- cgit v1.2.3 From c22ea7efff5d611d8293eff895b2ff1b234aa5a6 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Sep 2013 21:38:54 +0200 Subject: Added UserData to cHTTPRequest. Callbacks may store one pointer of per-request data in the cHTTPRequest object. The object doesn't touch this data (doesn't own it). --- source/HTTPServer/HTTPMessage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index b2e21c712..3ad838ac0 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -55,7 +55,8 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) // cHTTPRequest: cHTTPRequest::cHTTPRequest(void) : - super(mkRequest) + super(mkRequest), + m_UserData(NULL) { } -- cgit v1.2.3 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/HTTPMessage.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'source/HTTPServer/HTTPMessage.cpp') 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") -- 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/HTTPMessage.cpp | 198 +++++++++++++++----------------------- 1 file changed, 77 insertions(+), 121 deletions(-) (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index 72c603295..ed5c87e84 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -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(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) { - 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()) { - m_Headers[a_Key] = a_Value; + m_Headers[Key] = a_Value; } else { @@ -37,13 +50,13 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value) } // 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) : super(mkRequest), + m_EnvelopeParser(*this), + m_IsValid(true), 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: - size_t Next = ParseRequestLine(a_IncomingData, a_IdxEnd); - if (Next == AString::npos) + if (!m_IsValid) { - return false; + return -1; } - // The following lines contain headers: - AString Key; - const char * Data = a_IncomingData + Next; - size_t End = a_IdxEnd - Next; - while (End > 0) + if (m_Method.empty()) { - Next = ParseHeaderField(Data, End, Key); - if (Next == AString::npos) + // The first line hasn't been processed yet + int res = ParseRequestLine(a_Data, a_Size); + if ((res < 0) || (res == a_Size)) + { + return res; + } + int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res); + if (res2 < 0) { - return false; + m_IsValid = false; + return res2; } - ASSERT(End >= Next); - Data += Next; - End -= Next; + 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 res; } - return true; + 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") size_t LineStart = 0; while ( - (LineStart < a_IdxEnd) && + (LineStart < IdxEnd) && ( - (a_Data[LineStart] == '\r') || - (a_Data[LineStart] == '\n') + (m_IncomingHeaderData[LineStart] == '\r') || + (m_IncomingHeaderData[LineStart] == '\n') ) ) { LineStart++; } - if (LineStart >= a_IdxEnd) + if (LineStart >= IdxEnd) { - return AString::npos; + m_IsValid = false; + return -1; } - size_t Last = LineStart; 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 ' ': { @@ -131,124 +155,56 @@ size_t cHTTPRequest::ParseRequestLine(const char * a_Data, size_t a_IdxEnd) { case 0: { - m_Method.assign(a_Data, Last, i - Last); + MethodEnd = i; break; } case 1: { - m_URL.assign(a_Data, Last, i - Last); + URLEnd = i; break; } default: { // Too many spaces in the request - return AString::npos; + m_IsValid = false; + return -1; } } - Last = i + 1; NumSpaces += 1; break; } 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 - return AString::npos; + m_IsValid = false; + return -1; } // Check that there's HTTP/version at the end - if (strncmp(a_Data + Last, "HTTP/1.", 7) != 0) - { - return AString::npos; - } - return i + 1; - } - } // switch (a_Data[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')) + if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0) { - // Invalid header field - no colon or no CR before LF - return AString::npos; + m_IsValid = false; + return -1; } - AString Value(a_Data, ValueIdx + 1, i - ValueIdx - 2); - AddHeader(Key, Value); - a_Key = Key; + m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart); + m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1); 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) + } // switch (m_IncomingHeaderData[i]) } // 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; - 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; + AddHeader(a_Key, a_Value); } -- cgit v1.2.3 From db3d83b38dd61b90466a0721fa9104e742f3fb8b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 20:28:30 +0200 Subject: Added Basic auth support to cHTTPRequest. --- source/HTTPServer/HTTPMessage.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index ed5c87e84..6cf9464a3 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -71,7 +71,8 @@ cHTTPRequest::cHTTPRequest(void) : super(mkRequest), m_EnvelopeParser(*this), m_IsValid(true), - m_UserData(NULL) + m_UserData(NULL), + m_HasAuth(false) { } @@ -204,6 +205,20 @@ int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size) void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value) { + if ( + (NoCaseCompare(a_Key, "Authorization") == 0) && + (strncmp(a_Value.c_str(), "Basic ", 6) == 0) + ) + { + AString UserPass = Base64Decode(a_Value.substr(6)); + size_t idxCol = UserPass.find(':'); + if (idxCol != AString::npos) + { + m_AuthUsername = UserPass.substr(0, idxCol); + m_AuthPassword = UserPass.substr(idxCol + 1); + m_HasAuth = true; + } + } AddHeader(a_Key, a_Value); } -- 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/HTTPMessage.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'source/HTTPServer/HTTPMessage.cpp') diff --git a/source/HTTPServer/HTTPMessage.cpp b/source/HTTPServer/HTTPMessage.cpp index 6cf9464a3..ab23866e6 100644 --- a/source/HTTPServer/HTTPMessage.cpp +++ b/source/HTTPServer/HTTPMessage.cpp @@ -120,6 +120,23 @@ int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size) +AString cHTTPRequest::GetBareURL(void) const +{ + size_t idxQM = m_URL.find('?'); + if (idxQM != AString::npos) + { + return m_URL.substr(0, idxQM); + } + else + { + return m_URL; + } +} + + + + + int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size) { m_IncomingHeaderData.append(a_Data, a_Size); -- cgit v1.2.3