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.
This commit is contained in:
Adrien Destugues 2013-10-28 17:09:18 +01:00
parent b7617ddd68
commit 9ce2f7e386
4 changed files with 110 additions and 21 deletions

View File

@ -9,7 +9,6 @@
#include <deque>
#include <HttpAuthentication.h>
#include <HttpForm.h>
#include <HttpHeaders.h>
#include <HttpResult.h>
@ -93,7 +92,6 @@ private:
BNetBuffer fInputBuffer;
BHttpHeaders fHeaders;
BHttpAuthentication fAuthentication;
// Request status

View File

@ -6,22 +6,36 @@
#define _B_URL_CONTEXT_H_
#include <HttpAuthentication.h>
#include <NetworkCookieJar.h>
namespace BPrivate {
template <class key, class value> 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<BPrivate::HashString,
BHttpAuthentication*> BHttpAuthenticationMap;
BHttpAuthenticationMap* fAuthenticationMap;
};
#endif // _B_URL_CONTEXT_H_

View File

@ -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

View File

@ -9,11 +9,35 @@
#include <UrlContext.h>
#include <stdio.h>
#include <HashMap.h>
#include <HashString.h>
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;
}