netsurf/content/authdb.c
John Mark Bell 5ce5fe084c [project @ 2006-02-19 18:26:23 by jmb]
Rewrite HTTP authentication.
Fix extraction of realm from WWW-Authenticate header.
Tidy up login dialog code.

svn path=/import/netsurf/; revision=2085
2006-02-19 18:26:23 +00:00

367 lines
8.2 KiB
C

/*
* This file is part of NetSurf, http://netsurf.sourceforge.net/
* Licensed under the GNU General Public License,
* http://www.opensource.org/licenses/gpl-license
* Copyright 2006 John M Bell <jmb202@ecs.soton.ac.uk>
*/
/** \file
* HTTP authentication database (implementation)
*
* Authentication details are stored hashed by canonical root URI
* (absoluteURI with no abs_path part - see RFC 2617) for fast lookup.
*
* A protection space is specified by the root URI and a case sensitive
* realm match. User-agents may preemptively send authentication details
* for locations within a currently known protected space (i.e:
* Given a known realm URI of scheme://authority/path/to/realm/
* the URI scheme://authority/path/to/realm/foo/ can be assumed to
* be within the protection space.)
*
* In order to deal with realms within realms, the realm details are stored
* such that the most specific URI comes first (where "most specific" is
* classed as the one with the longest abs_path segment).
*
* Realms spanning domains are stored multiple times (once per domain).
*
* Where a higher level resource is found to be within a known realm, the
* existing match is replaced with the new one (i.e:
* Given a known realm of scheme://authority/path/to/realm/ (uri1)
* and the newly-acquired knowledge that scheme://authority/path/to/ (uri2)
* lies within the same realm, the realm details for uri1 are replaced with
* those for uri2. - in most cases, this is likely to be a simple
* replacement of the realm URI)
*
* There is currently no mechanism for retaining authentication details over
* sessions.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "netsurf/content/authdb.h"
#define NDEBUG
#include "netsurf/utils/log.h"
#include "netsurf/utils/url.h"
#define HASH_SIZE 77
struct realm_details {
char *realm; /**< Realm identifier */
char *url; /**< Base URL of realm */
char *auth; /**< Authentication details */
struct realm_details *next;
struct realm_details *prev;
};
struct auth_entry {
char *root_url; /**< Canonical root URL of realms */
struct realm_details *realms; /**< List of realms on this host */
struct auth_entry *next;
};
static struct auth_entry *auth_table[HASH_SIZE];
static unsigned int authdb_hash(const char *s);
static struct realm_details *authdb_get_rd(const char *canon,
const char *url, const char *realm);
static void authdb_dump(void);
/**
* Insert an entry into the database, potentially replacing any
* existing entry.
*
* \param url Absolute URL to resource
* \param realm Authentication realm containing resource
* \param auth Authentication details in form "username:password"
* \return true on success, false on error.
*/
bool authdb_insert(const char *url, const char *realm, const char *auth)
{
char *canon, *stripped;
unsigned int hash;
struct realm_details *rd;
struct auth_entry *entry;
url_func_result ret;
assert(url && realm && auth);
LOG(("Adding '%s' - '%s'", url, realm));
ret = url_canonical_root(url, &canon);
if (ret != URL_FUNC_OK)
return false;
LOG(("'%s'", canon));
ret = url_strip_lqf(url, &stripped);
if (ret != URL_FUNC_OK) {
free(canon);
return false;
}
hash = authdb_hash(canon);
/* Look for existing entry */
for (entry = auth_table[hash]; entry; entry = entry->next)
if (strcmp(entry->root_url, canon) == 0)
break;
rd = authdb_get_rd(canon, stripped, realm);
if (rd) {
/* We have a match */
if (strlen(stripped) < strlen(rd->url)) {
/* more generic, so update URL and move to
* appropriate location in list (s.t. the invariant
* that most specific URLs come first is maintained)
*/
struct realm_details *r, *s;
char *temp = strdup(auth);
if (!temp) {
free(temp);
free(stripped);
free(canon);
return false;
}
free(rd->url);
rd->url = stripped;
free(rd->auth);
rd->auth = temp;
for (r = rd->next; r; r = s) {
s = r->next;
if (strlen(r->url) > strlen(rd->url)) {
rd->next->prev = rd->prev;
if (rd->prev)
rd->prev->next = rd->next;
else
entry->realms = r;
rd->prev = r;
rd->next = r->next;
if (r->next)
r->next->prev = rd;
r->next = rd;
}
}
}
else if (strlen(stripped) == strlen(rd->url)) {
/* exact match, so replace auth details */
char *temp = strdup(auth);
if (!temp) {
free(stripped);
free(canon);
return false;
}
free(rd->auth);
rd->auth = temp;
free(stripped);
}
/* otherwise, nothing to do */
free(canon);
return true;
}
/* no existing entry => create one */
rd = malloc(sizeof(struct realm_details));
if (!rd) {
free(stripped);
free(canon);
return false;
}
rd->realm = strdup(realm);
rd->auth = strdup(auth);
rd->url = stripped;
rd->prev = 0;
if (!rd->realm || !rd->auth || ret != URL_FUNC_OK) {
free(rd->url);
free(rd->auth);
free(rd->realm);
free(rd);
free(canon);
return false;
}
if (entry) {
/* found => add to it */
rd->next = entry->realms;
if (entry->realms)
entry->realms->prev = rd;
entry->realms = rd;
free(canon);
return true;
}
/* not found => create new */
entry = malloc(sizeof(struct auth_entry));
if (!entry) {
free(rd->url);
free(rd->auth);
free(rd->realm);
free(rd);
free(canon);
return false;
}
rd->next = 0;
entry->root_url = canon;
entry->realms = rd;
entry->next = auth_table[hash];
auth_table[hash] = entry;
return true;
}
/**
* Find realm details entry
*
* \param canon Canonical root URL
* \param url Stripped URL to resource
* \param realm Realm containing resource
* \return Realm details or NULL if not found
*/
struct realm_details *authdb_get_rd(const char *canon, const char *url,
const char *realm)
{
struct auth_entry *entry;
struct realm_details *ret;
assert(canon && url);
for (entry = auth_table[authdb_hash(canon)]; entry;
entry = entry->next)
if (strcmp(entry->root_url, canon) == 0)
break;
if (!entry)
return NULL;
for (ret = entry->realms; ret; ret = ret->next) {
if (strcmp(ret->realm, realm))
/* skip realms that don't match */
continue;
if (strlen(url) >= strlen(ret->url) &&
!strncmp(url, ret->url, strlen(ret->url)))
/* If the requested URL is of equal or greater
* specificity than the stored one, but is within
* the same realm, then use the more generic details
*/
return ret;
else if (strncmp(url, ret->url, strlen(url)) == 0) {
/* We have a more general URL in the same realm */
return ret;
}
}
return NULL;
}
/**
* Retrieve authentication details for an URL from the database
*
* \param url Absolute URL to consider
* \return authentication details, or NULL if none found.
*/
const char *authdb_get(const char *url)
{
char *canon, *stripped;
struct auth_entry *entry;
struct realm_details *rd;
url_func_result ret;
assert(url);
LOG(("Searching for '%s'", url));
authdb_dump();
ret = url_canonical_root(url, &canon);
if (ret != URL_FUNC_OK)
return NULL;
ret = url_strip_lqf(url, &stripped);
if (ret != URL_FUNC_OK) {
free(canon);
return NULL;
}
/* Find auth entry */
for (entry = auth_table[authdb_hash(canon)]; entry;
entry = entry->next)
if (strcmp(entry->root_url, canon) == 0)
break;
if (!entry) {
free(stripped);
free(canon);
return NULL;
}
LOG(("Found entry"));
/* Find realm details */
for (rd = entry->realms; rd; rd = rd->next)
if (strlen(stripped) >= strlen(rd->url) &&
!strncmp(stripped, rd->url, strlen(rd->url)))
break;
if (!rd) {
free(stripped);
free(canon);
return NULL;
}
LOG(("Found realm"));
free(stripped);
free(canon);
return rd->auth;
}
/**
* Hash function for keys.
*/
unsigned int authdb_hash(const char *s)
{
unsigned int i, z = 0, m;
if (!s)
return 0;
m = strlen(s);
for (i = 0; i != m && s[i]; i++)
z += s[i] & 0x1f; /* lower 5 bits, case insensitive */
return z % HASH_SIZE;
}
/**
* Dump contents of auth db to stderr
*/
void authdb_dump(void)
{
#ifndef NDEBUG
int i;
struct auth_entry *e;
struct realm_details *r;
for (i = 0; i != HASH_SIZE; i++) {
LOG(("%d:", i));
for (e = auth_table[i]; e; e = e->next) {
LOG(("\t%s", e->root_url));
for (r = e->realms; r; r = r->next) {
LOG(("\t\t%s - %s", r->url, r->realm));
}
}
}
#endif
}