From 9ce2f7e3863c7c69284eaad6466c6b0247f0037b Mon Sep 17 00:00:00 2001 From: Adrien Destugues Date: Mon, 28 Oct 2013 17:09:18 +0100 Subject: [PATCH] Improve HTTP authentication support. The authentication state is stored (in a hash map, using the domain+path as a key) in the UrlContext class. It can then be reused for multiple requests to the same place. We also lookup stored authentications for parent directories and stop at the first we find. Authentication state is not stored on disk (unlike cookies), and there can only be one for each domain+path. --- headers/os/net/HttpRequest.h | 2 - headers/os/net/UrlContext.h | 14 +++++ src/kits/network/libnetapi/HttpRequest.cpp | 54 ++++++++++++------- src/kits/network/libnetapi/UrlContext.cpp | 61 +++++++++++++++++++++- 4 files changed, 110 insertions(+), 21 deletions(-) diff --git a/headers/os/net/HttpRequest.h b/headers/os/net/HttpRequest.h index 2c4248cac2..d18222a1b0 100644 --- a/headers/os/net/HttpRequest.h +++ b/headers/os/net/HttpRequest.h @@ -9,7 +9,6 @@ #include -#include #include #include #include @@ -93,7 +92,6 @@ private: BNetBuffer fInputBuffer; BHttpHeaders fHeaders; - BHttpAuthentication fAuthentication; // Request status diff --git a/headers/os/net/UrlContext.h b/headers/os/net/UrlContext.h index 414a371106..6a4541f073 100644 --- a/headers/os/net/UrlContext.h +++ b/headers/os/net/UrlContext.h @@ -6,22 +6,36 @@ #define _B_URL_CONTEXT_H_ +#include #include +namespace BPrivate { + template class HashMap; + class HashString; +} + + class BUrlContext { public: BUrlContext(); + ~BUrlContext(); // Context modifiers void SetCookieJar( const BNetworkCookieJar& cookieJar); + void AddAuthentication(const BUrl& url, + BHttpAuthentication* const authentication); // Context accessors BNetworkCookieJar& GetCookieJar(); + BHttpAuthentication& GetAuthentication(const BUrl& url); private: BNetworkCookieJar fCookieJar; + typedef BPrivate::HashMap BHttpAuthenticationMap; + BHttpAuthenticationMap* fAuthenticationMap; }; #endif // _B_URL_CONTEXT_H_ diff --git a/src/kits/network/libnetapi/HttpRequest.cpp b/src/kits/network/libnetapi/HttpRequest.cpp index 7b19990f85..6330a3025c 100644 --- a/src/kits/network/libnetapi/HttpRequest.cpp +++ b/src/kits/network/libnetapi/HttpRequest.cpp @@ -344,22 +344,35 @@ BHttpRequest::_ProtocolLoop() break; case B_HTTP_STATUS_CLASS_CLIENT_ERROR: - switch (fResult.StatusCode()) { - case B_HTTP_STATUS_UNAUTHORIZED: - if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) { - newRequest = false; - break; - } + if(fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) { + BHttpAuthentication* authentication + = &fContext->GetAuthentication(fUrl); + status_t status = B_OK; - newRequest = false; - if (fOptUsername.Length() > 0 - && fAuthentication.Initialize(fHeaders["WWW-Authenticate"]) - == B_OK) { - fAuthentication.SetUserName(fOptUsername); - fAuthentication.SetPassword(fOptPassword); - newRequest = true; - } - break; + if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) + { + // There is no authentication context for this + // url yet, so let's create one. + authentication = new BHttpAuthentication(); + status = authentication->Initialize( + fHeaders["WWW-Authenticate"]); + fContext->AddAuthentication(fUrl, authentication); + } + + newRequest = false; + if (fOptUsername.Length() > 0 && status == B_OK) { + // If we received an username and password, add them + // to the request. This will either change the + // credentials for an existing request, or set them + // for a new one we created just above. + // + // If this request handles HTTP redirections, it will + // also automatically retry connecting and send the + // login information. + authentication->SetUserName(fOptUsername); + authentication->SetPassword(fOptPassword); + newRequest = true; + } } break; @@ -811,11 +824,16 @@ BHttpRequest::_AddHeaders() fOutputHeaders.AddHeader("Referer", fOptReferer.String()); // Authentication - if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) { - BString request(fRequestMethod); + BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl); + if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) { + if (fOptUsername.Length() > 0) { + authentication.SetUserName(fOptUsername); + authentication.SetPassword(fOptPassword); + } + BString request(fRequestMethod); fOutputHeaders.AddHeader("Authorization", - fAuthentication.Authorization(fUrl, request)); + authentication.Authorization(fUrl, request)); } // Required headers for POST data diff --git a/src/kits/network/libnetapi/UrlContext.cpp b/src/kits/network/libnetapi/UrlContext.cpp index a8693043c2..429eb5918d 100644 --- a/src/kits/network/libnetapi/UrlContext.cpp +++ b/src/kits/network/libnetapi/UrlContext.cpp @@ -9,11 +9,35 @@ #include +#include + +#include +#include + BUrlContext::BUrlContext() : - fCookieJar() + fCookieJar(), + fAuthenticationMap(NULL) { + fAuthenticationMap = new(std::nothrow) BHttpAuthenticationMap(); + if(!fAuthenticationMap) + return; + // This is the default authentication, used when nothing else is found. + // The empty string used as a key will match all the domain strings, once + // we have removed all components. + fAuthenticationMap->Put(HashString("", 0), new BHttpAuthentication()); +} + + +BUrlContext::~BUrlContext() +{ + BHttpAuthenticationMap::Iterator iterator = + fAuthenticationMap->GetIterator(); + while(iterator.HasNext()) + delete iterator.Remove().value; + + delete fAuthenticationMap; } @@ -27,6 +51,21 @@ BUrlContext::SetCookieJar(const BNetworkCookieJar& cookieJar) } +void +BUrlContext::AddAuthentication(const BUrl& url, + BHttpAuthentication* const authentication) +{ + BString domain = url.Host(); + domain += url.Path(); + BPrivate::HashString hostHash(domain.String(), domain.Length()); + + delete fAuthenticationMap->Get(hostHash); + // Make sure we don't leak memory by overriding a previous + // authentication for the same domain. + fAuthenticationMap->Put(hostHash, authentication); +} + + // #pragma mark Context accessors @@ -35,3 +74,23 @@ BUrlContext::GetCookieJar() { return fCookieJar; } + + +BHttpAuthentication& +BUrlContext::GetAuthentication(const BUrl& url) +{ + BString domain = url.Host(); + domain += url.Path(); + + BHttpAuthentication* authentication = NULL; + + do { + authentication = fAuthenticationMap->Get( HashString(domain.String(), + domain.Length())); + + domain.Truncate(domain.FindLast('/')); + + } while(authentication == NULL); + + return *authentication; +}