1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* http-authn.c - Basic authentication handling for HTTP
4 Copyright (C) 2001 Eazel, Inc.
6 The Gnome Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 The Gnome Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public
17 License along with the Gnome Library; see the file COPYING.LIB. If not,
18 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA.
22 Michael Fleming <mfleming@eazel.com>
26 #include "http-authn.h"
28 #include "http-method.h"
29 #include <glib/ghash.h>
30 #include <glib/gmessages.h>
31 #include <glib/gstring.h>
32 #include <glib/gstrfuncs.h>
33 #include <glib/gthread.h>
34 #include <libgnomevfs/gnome-vfs-method.h>
35 #include <libgnomevfs/gnome-vfs-utils.h>
39 /* Authentication session information
41 * Value: Header line including <cr><lf>
44 static GHashTable * gl_authn_table = NULL;
45 static GMutex *gl_mutex = NULL;
48 http_authn_init (void)
50 gl_authn_table = g_hash_table_new (g_str_hash, g_str_equal);
51 gl_mutex = g_mutex_new ();
54 static void /* GHFunc */
55 hfunc_free_string (gpointer key, gpointer value, gpointer user_data)
62 http_authn_shutdown (void)
64 g_hash_table_foreach (gl_authn_table, hfunc_free_string, NULL);
65 g_hash_table_destroy (gl_authn_table);
66 gl_authn_table = NULL;
68 g_mutex_free (gl_mutex);
72 /* watch it here: # and ? may be legal in the authority field of a URI
73 * (eg: username:password)
74 * so don't feed the authority field in here
77 strip_uri_query_and_fragment (const char *in)
84 separator = strchr (ret, '#');
90 separator = strchr (ret, '?');
96 ret = g_realloc (ret, strlen (ret) + 1);
101 http_authn_get_key_string_from_uri (GnomeVFSURI *uri)
105 char *uri_string_canonical;
108 uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_USER_NAME
109 | GNOME_VFS_URI_HIDE_PASSWORD
110 | GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER);
112 uri_string_canonical = gnome_vfs_make_uri_canonical (uri_string);
114 ret = strip_uri_query_and_fragment (uri_string_canonical);
116 /* strip any trailing / */
118 if (ret [len-1] == '/') {
122 g_free (uri_string_canonical);
123 uri_string_canonical = NULL;
131 http_authn_session_get_header_for_uri (GnomeVFSURI *uri)
138 key_string = http_authn_get_key_string_from_uri (uri);
140 g_mutex_lock (gl_mutex);
143 while (NULL != strrchr (key_string, '/')) {
144 authn_header = (char *) g_hash_table_lookup (gl_authn_table, key_string);
145 if (authn_header != NULL) {
146 ret = g_strdup (authn_header);
150 /* Try the next level up in the heirarchy */
151 marker = strrchr (key_string, '/');
155 g_mutex_unlock (gl_mutex);
164 http_authn_session_add_credentials (GnomeVFSURI *uri, const char *username, const char *password)
170 char *credentials_encoded;
172 g_return_if_fail (uri != NULL);
174 key_string = http_authn_get_key_string_from_uri (uri);
176 credentials = credentials_encoded = NULL;
178 if (username != NULL) {
179 credentials = g_strdup_printf ("%s:%s", username,
182 credentials_encoded = http_util_base64 (credentials);
185 g_mutex_lock (gl_mutex);
187 if (g_hash_table_lookup_extended (gl_authn_table, key_string, (gpointer) &orig_key, (gpointer) &orig_value)) {
188 g_hash_table_remove (gl_authn_table, orig_key);
195 if (credentials_encoded != NULL) {
196 g_hash_table_insert (gl_authn_table, key_string, g_strdup_printf ("Authorization: Basic %s\r\n", credentials_encoded));
200 g_mutex_unlock (gl_mutex);
203 g_free (credentials);
204 g_free (credentials_encoded);
208 static gint /* GCompareFunc */
209 http_authn_glist_find_header (gconstpointer a, gconstpointer b)
211 if ( NULL != a && NULL != b) {
212 return g_ascii_strncasecmp ( (const char *)a, (const char *)b, strlen((const char *)b));
218 /* "quoted-string" rfc 2616 section 2.1 */
219 /* tolerant of lack of quotes (switches to space-delimited) */
221 http_authn_parse_quoted_string (const char *in, const char **p_out)
224 gboolean space_delimited;
235 space_delimited = TRUE;
237 space_delimited = FALSE;
242 unquoted = g_string_new ("");
245 ? (*in != ' ' && *in != '\t')
246 : (*in != '"' || quote_next))
248 if (!quote_next && *in == '\\') {
252 g_string_append_c (unquoted, *in);
265 g_string_free (unquoted, FALSE);
271 http_authn_parse_response_header_basic (enum AuthnHeaderType type, GList *response_headers, /* OUT */ char **p_realm)
273 const char *header_name;
279 g_return_val_if_fail (p_realm != NULL, FALSE);
285 case AuthnHeader_WWW:
286 header_name = "WWW-Authenticate:";
289 case AuthnHeader_Proxy:
290 header_name = "Proxy-Authenticate:";
293 g_return_val_if_fail (FALSE, FALSE);
296 /* Apparently, there can be more than one authentication header
297 * (see RFC 2617 section 1.2 paragraph beginning with "Note:"
299 node = g_list_find_custom (response_headers, (gpointer) header_name, http_authn_glist_find_header);
301 ; node = g_list_find_custom (g_list_next (node), (gpointer) header_name, http_authn_glist_find_header)) {
303 header = (char *)node->data;
305 /* skip through the header name */
306 marker = strchr (header, (unsigned char)':');
308 if (marker == NULL) {
314 /* skip to the auth-scheme */
315 for (; *marker != '\0'
316 && (*marker == ' ' || *marker == '\t')
319 if (0 != g_ascii_strncasecmp ("Basic", marker, strlen ("Basic"))) {
323 marker += strlen ("Basic");
325 while (*marker != '\0') {
326 for (; *marker != '\0'
327 && (*marker == ' ' || *marker == '\t' || *marker == ',')
330 if (0 == g_ascii_strncasecmp ("realm=", marker, strlen ("realm="))) {
331 marker += strlen ("realm=");
332 *p_realm = http_authn_parse_quoted_string (marker, &marker);
337 if (*p_realm == NULL) {
338 *p_realm = strdup ("");
350 http_authn_get_header_for_uri (GnomeVFSURI *uri)
353 GnomeVFSToplevelURI *toplevel_uri;
355 toplevel_uri = gnome_vfs_uri_get_toplevel (uri);
359 /* If authn info was passed in the URI, then default to that */
360 if(toplevel_uri != NULL && toplevel_uri->user_name) {
364 raw = g_strdup_printf("%s:%s", toplevel_uri->user_name,
365 toplevel_uri->password?toplevel_uri->password:"");
367 enc = http_util_base64(raw);
369 result = g_strdup_printf("Authorization: Basic %s\r\n", enc);
373 result = http_authn_session_get_header_for_uri ((GnomeVFSURI *)toplevel_uri);
379 #define VERIFY_STRING_RESULT(function, expected) \
381 char *result = function; \
382 if (!((result == NULL && expected == NULL) \
383 || (result != NULL && expected != NULL && strcmp (result, (char *)expected) == 0))) { \
384 test_failed ("%s:%s:%s: returned '%s' expected '%s'", __FILE__, __LINE__, #function, result, expected); \
388 static gboolean at_least_one_test_failed = FALSE;
391 test_failed (const char *format, ...)
396 va_start (arguments, format);
397 message = g_strdup_vprintf (format, arguments);
400 fprintf (stderr, "test failed: %s\n", message);
401 at_least_one_test_failed = TRUE;
405 test_parse_header (guint line, enum AuthnHeaderType type, const char *realm_expected, gboolean result_expected, ...)
413 va_start (arguments, result_expected);
417 for (header = va_arg (arguments, const char *)
419 ; header = va_arg (arguments, const char *)) {
421 header_list = g_list_prepend (header_list, (gpointer)header);
424 header_list = g_list_reverse (header_list);
427 result = http_authn_parse_response_header_basic (type, header_list, &realm);
429 if (! (result == result_expected
430 && ((realm == NULL && realm_expected == NULL)
431 || (realm != NULL && realm_expected != NULL
432 && 0 == strcmp (realm, realm_expected))))) {
434 test_failed ("%s:%u:http_authn_parse_response_header_basic failed, expected (%d,%s) but got (%d, %s)\n",
435 __FILE__, line, result_expected, realm_expected, result, realm);
439 /* Hook for testing code to flush credentials */
441 http_authentication_test_flush_credentials (void);
444 http_authentication_test_flush_credentials (void)
446 g_hash_table_foreach (gl_authn_table, hfunc_free_string, NULL);
447 g_hash_table_destroy (gl_authn_table);
448 gl_authn_table = g_hash_table_new (g_str_hash, g_str_equal);
452 http_authn_self_test (void)
456 at_least_one_test_failed = FALSE;
458 fprintf (stderr, "self-test: http-authn\n");
460 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar"), "/foo/bar");
461 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar?query"), "/foo/bar");
462 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar?query#fragment"), "/foo/bar");
463 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar#fragment"), "/foo/bar");
464 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("#fragment"), "");
465 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query#fragment"), "");
466 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query"), "");
467 VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query#fragment"), "");
469 uri = gnome_vfs_uri_new ("http://host/path");
470 VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host/path");
471 gnome_vfs_uri_unref (uri);
473 /* FIXME I think make_uri_canonical should remove default ports */
474 uri = gnome_vfs_uri_new ("http://host:80/path");
475 VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
476 gnome_vfs_uri_unref (uri);
478 uri = gnome_vfs_uri_new ("http://host:80/path/");
479 VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
480 gnome_vfs_uri_unref (uri);
482 uri = gnome_vfs_uri_new ("http://user:pass@host:80/path/?query#foo");
483 VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
484 gnome_vfs_uri_unref (uri);
486 VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"quoted string\"", NULL), "quoted string");
487 VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"quoted\\\"str\\\\ing\"", NULL), "quoted\"str\\ing");
488 VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("unquoted-string", NULL), "unquoted-string");
489 VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"\"", NULL), "");
490 VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("", NULL), "");
492 test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Basic realm=\"realm\"", NULL);
493 test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Digest crap=\"crap\"", "WWW-Authenticate: Basic realm=\"realm\"", NULL);
494 test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Basic realm=\"realm\"", "WWW-Authenticate: Digest crap=\"crap\"", NULL);
495 test_parse_header (__LINE__, AuthnHeader_WWW, "" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"crap\"", NULL);
496 test_parse_header (__LINE__, AuthnHeader_WWW, NULL , FALSE, "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"crap\"", NULL);
498 test_parse_header (__LINE__, AuthnHeader_Proxy, "realm" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"realm\"", NULL);
499 test_parse_header (__LINE__, AuthnHeader_Proxy, "realm" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "proxy-authenticate: basic Realm=\"realm\"", NULL);
501 uri = gnome_vfs_uri_new ("http://host/path/");
503 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
505 http_authn_session_add_credentials (uri, "myuser", "mypasswd");
507 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
508 g_strconcat ("Authorization: Basic ", http_util_base64("myuser:mypasswd"), "\r\n", NULL));
510 gnome_vfs_uri_unref (uri);
511 uri = gnome_vfs_uri_new ("http://host/path/?query#foo");
513 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
514 g_strconcat ("Authorization: Basic ", http_util_base64("myuser:mypasswd"), "\r\n", NULL));
516 http_authn_session_add_credentials (uri, "newuser", "newpasswd");
518 gnome_vfs_uri_unref (uri);
519 uri = gnome_vfs_uri_new ("http://host/path");
521 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
522 g_strconcat ("Authorization: Basic ", http_util_base64("newuser:newpasswd"), "\r\n", NULL));
524 gnome_vfs_uri_unref (uri);
525 uri = gnome_vfs_uri_new ("http://host/");
527 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
529 gnome_vfs_uri_unref (uri);
530 uri = gnome_vfs_uri_new ("http://user:passwd@host/path");
532 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
533 g_strconcat ("Authorization: Basic ", http_util_base64("user:passwd"), "\r\n", NULL));
535 gnome_vfs_uri_unref (uri);
536 uri = gnome_vfs_uri_new ("http://anotherhost/path");
538 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
540 http_authn_session_add_credentials (uri, "newuser", "newpasswd");
541 http_authn_session_add_credentials (uri, NULL, NULL);
543 VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
545 return !at_least_one_test_failed;