From db3d83b38dd61b90466a0721fa9104e742f3fb8b Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 4 Oct 2013 20:28:30 +0200 Subject: [PATCH] Added Basic auth support to cHTTPRequest. --- source/HTTPServer/HTTPConnection.cpp | 18 ++++++++ source/HTTPServer/HTTPConnection.h | 3 ++ source/HTTPServer/HTTPMessage.cpp | 17 ++++++- source/HTTPServer/HTTPMessage.h | 18 ++++++++ source/HTTPServer/HTTPServer.cpp | 10 ++++ source/StringUtils.cpp | 68 ++++++++++++++++++++++++++++ source/StringUtils.h | 3 ++ 7 files changed, 136 insertions(+), 1 deletion(-) diff --git a/source/HTTPServer/HTTPConnection.cpp b/source/HTTPServer/HTTPConnection.cpp index 61f4b3a31..e3a6be494 100644 --- a/source/HTTPServer/HTTPConnection.cpp +++ b/source/HTTPServer/HTTPConnection.cpp @@ -26,6 +26,18 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re { AppendPrintf(m_OutgoingData, "%d %s\r\n\r\n", a_StatusCode, a_Response.c_str()); m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; +} + + + + + +void cHTTPConnection::SendNeedAuth(const AString & a_Realm) +{ + AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\n\r\n", a_Realm.c_str()); + m_HTTPServer.NotifyConnectionWrite(*this); + m_State = wcsRecvHeaders; } @@ -73,6 +85,12 @@ void cHTTPConnection::AwaitNextRequest(void) { switch (m_State) { + case wcsRecvHeaders: + { + // Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth() ) + break; + } + case wcsRecvIdle: { // The client is waiting for a response, send an "Internal server error": diff --git a/source/HTTPServer/HTTPConnection.h b/source/HTTPServer/HTTPConnection.h index 9e05d342b..941a29000 100644 --- a/source/HTTPServer/HTTPConnection.h +++ b/source/HTTPServer/HTTPConnection.h @@ -43,6 +43,9 @@ public: /// Sends HTTP status code together with a_Reason (used for HTTP errors) void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); + /// Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm + void SendNeedAuth(const AString & a_Realm); + /// Sends the headers contained in a_Response void Send(const cHTTPResponse & a_Response); 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); } diff --git a/source/HTTPServer/HTTPMessage.h b/source/HTTPServer/HTTPMessage.h index ef8e12ca4..151eb468f 100644 --- a/source/HTTPServer/HTTPMessage.h +++ b/source/HTTPServer/HTTPMessage.h @@ -91,6 +91,15 @@ public: /// Returns true if more data is expected for the request headers bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); } + /// Returns true if the request did present auth data that was understood by the parser + bool HasAuth(void) const { return m_HasAuth; } + + /// Returns the username that the request presented. Only valid if HasAuth() is true + const AString & GetAuthUsername(void) const { return m_AuthUsername; } + + /// Returns the password that the request presented. Only valid if HasAuth() is true + const AString & GetAuthPassword(void) const { return m_AuthPassword; } + protected: /// Parser for the envelope data cEnvelopeParser m_EnvelopeParser; @@ -110,6 +119,15 @@ protected: /// Data that the HTTPServer callbacks are allowed to store. void * m_UserData; + /// Set to true if the request contains auth data that was understood by the parser + bool m_HasAuth; + + /// The username used for auth + AString m_AuthUsername; + + /// The password used for auth + AString m_AuthPassword; + /** Parses the incoming data for the first line (RequestLine) Returns the number of bytes consumed, or -1 for an error diff --git a/source/HTTPServer/HTTPServer.cpp b/source/HTTPServer/HTTPServer.cpp index 4102d1047..43bb751c4 100644 --- a/source/HTTPServer/HTTPServer.cpp +++ b/source/HTTPServer/HTTPServer.cpp @@ -73,6 +73,16 @@ class cDebugCallbacks : return; } + // Test the auth failure and success: + if (a_Request.GetURL() == "/auth") + { + if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b")) + { + a_Connection.SendNeedAuth("MCServer WebAdmin"); + return; + } + } + cHTTPResponse Resp; Resp.SetContentType("text/plain"); a_Connection.Send(Resp); diff --git a/source/StringUtils.cpp b/source/StringUtils.cpp index c62bb3acb..530eda086 100644 --- a/source/StringUtils.cpp +++ b/source/StringUtils.cpp @@ -745,3 +745,71 @@ AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_ + +/// Converts one Hex character in a Base64 encoding into the data value +static inline int UnBase64(char c) +{ + if (c >='A' && c <= 'Z') + { + return c - 'A'; + } + if (c >='a' && c <= 'z') + { + return c - 'a' + 26; + } + if (c >= '0' && c <= '9') + { + return c - '0' + 52; + } + if (c == '+') + { + return 62; + } + if (c == '/') + { + return 63; + } + if (c == '=') + { + return -1; + } + return -2; +} + + + + + +AString Base64Decode(const AString & a_Base64String) +{ + AString res; + size_t i, len = a_Base64String.size(); + int o, c; + res.resize((len * 4) / 3 + 5, 0); // Approximate the upper bound on the result length + for (o = 0, i = 0; i < len; i++) + { + c = UnBase64(a_Base64String[i]); + if (c >= 0) + { + switch (o & 7) + { + case 0: res[o >> 3] |= (c << 2); break; + case 6: res[o >> 3] |= (c >> 4); res[(o >> 3) + 1] |= (c << 4); break; + case 4: res[o >> 3] |= (c >> 2); res[(o >> 3) + 1] |= (c << 6); break; + case 2: res[o >> 3] |= c; break; + } + o += 6; + } + if (c == -1) + { + // Error while decoding, invalid input. Return as much as we've decoded: + res.resize(o >> 3); + return ERROR_SUCCESS; + } + } + res.resize(o >> 3); + return res;} + + + + diff --git a/source/StringUtils.h b/source/StringUtils.h index e35e58c9f..ec9ba96ce 100644 --- a/source/StringUtils.h +++ b/source/StringUtils.h @@ -81,6 +81,9 @@ extern AString URLDecode(const AString & a_String); // Cannot export to Lua aut /// Replaces all occurrences of char a_From inside a_String with char a_To. extern AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_To); // Needn't export to Lua, since Lua doesn't have chars anyway +/// Decodes a Base64-encoded string into the raw data +extern AString Base64Decode(const AString & a_Base64String); + // If you have any other string helper functions, declare them here