[libsoup] Add SoupPasswordManager and SoupPasswordManagerGNOME



commit f81520bfd3a971f82c2b69aa9977edc1a33b20aa
Author: Dan Winship <danw gnome org>
Date:   Wed Aug 12 10:23:39 2009 -0400

    Add SoupPasswordManager and SoupPasswordManagerGNOME
    
    SoupPasswordManager (and some new SoupAuth APIs) provide an interface
    for saving passwords, and SoupPasswordManagerGNOME provides an
    implementation of that interface using gnome-keyring.

 configure.in                          |   10 ++
 libsoup/Makefile.am                   |   13 ++-
 libsoup/soup-auth-manager-ntlm.h      |    2 +
 libsoup/soup-auth-manager.h           |    2 -
 libsoup/soup-auth.c                   |  171 ++++++++++++++++++++++++
 libsoup/soup-auth.h                   |   11 ++
 libsoup/soup-gnome.h                  |    1 +
 libsoup/soup-marshal.list             |    1 +
 libsoup/soup-password-manager-gnome.c |  230 +++++++++++++++++++++++++++++++++
 libsoup/soup-password-manager-gnome.h |   32 +++++
 libsoup/soup-password-manager.c       |   97 ++++++++++++++
 libsoup/soup-password-manager.h       |   55 ++++++++
 libsoup/soup-session-async.c          |   38 ++++++
 libsoup/soup-session-sync.c           |   23 +++-
 libsoup/soup-session.c                |   13 ++-
 libsoup/soup-session.h                |    5 +-
 libsoup/soup.h                        |    1 +
 tests/get.c                           |   75 +++++++++++
 18 files changed, 772 insertions(+), 8 deletions(-)
---
diff --git a/configure.in b/configure.in
index 0d9e2f1..3318992 100644
--- a/configure.in
+++ b/configure.in
@@ -183,6 +183,16 @@ AC_ARG_WITH(gnome,
 	    :, [if test $os_win32 = yes; then with_gnome=no; else with_gnome=yes; fi])
 AC_MSG_RESULT($with_gnome)
 
+if test $with_gnome != no; then
+	PKG_CHECK_MODULES(GNOME_KEYRING, gnome-keyring-1, :,
+			  AC_MSG_ERROR(
+[Could not find gnome-keyring devel files.
+Configure with --without-gnome if you wish to build only libsoup
+without GNOME-specific features.]))
+fi
+AC_SUBST(GNOME_KEYRING_CFLAGS)
+AC_SUBST(GNOME_KEYRING_LIBS)
+
 AM_CONDITIONAL(BUILD_LIBSOUP_GNOME, test $with_gnome != no)
 
 if test $with_gnome != no; then
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index d5b8be2..a436d4e 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -14,6 +14,7 @@ INCLUDES = 				\
 	$(GCONF_CFLAGS)			\
 	$(LIBPROXY_CFLAGS)		\
 	$(SQLITE_CFLAGS)		\
+	$(GNOME_KEYRING_CFLAGS)		\
 	$(LIBGCRYPT_CFLAGS)		\
 	$(LIBGNUTLS_CFLAGS)
 
@@ -69,6 +70,7 @@ soup_headers =			\
 	soup-method.h		\
 	soup-misc.h     	\
 	soup-multipart.h     	\
+	soup-password-manager.h	\
 	soup-portability.h	\
 	soup-proxy-resolver.h	\
 	soup-proxy-uri-resolver.h \
@@ -142,6 +144,7 @@ libsoup_2_4_la_SOURCES =		\
 	soup-misc.c     		\
 	soup-multipart.c	     	\
 	soup-nossl.c     		\
+	soup-password-manager.c		\
 	soup-path-map.h     		\
 	soup-path-map.c     		\
 	soup-proxy-resolver.c		\
@@ -168,7 +171,8 @@ libsoupgnomeincludedir = $(includedir)/libsoup-gnome-2.4/libsoup
 libsoupgnomeinclude_HEADERS =	\
 	soup-cookie-jar-sqlite.h\
 	soup-gnome.h		\
-	soup-gnome-features.h
+	soup-gnome-features.h	\
+	soup-password-manager-gnome.h
 
 lib_LTLIBRARIES += libsoup-gnome-2.4.la
 
@@ -179,13 +183,16 @@ libsoup_gnome_2_4_la_LIBADD =		\
 	$(GLIB_LIBS)			\
 	$(GCONF_LIBS)			\
 	$(LIBPROXY_LIBS)		\
-	$(SQLITE_LIBS)
+	$(SQLITE_LIBS)			\
+	$(GNOME_KEYRING_LIBS)
 
 libsoup_gnome_2_4_la_SOURCES =		\
 	soup-cookie-jar-sqlite.c	\
 	soup-gnome-features.c		\
 	soup-proxy-resolver-gnome.h	\
-	soup-proxy-resolver-gnome.c
+	soup-proxy-resolver-gnome.c	\
+	soup-password-manager-gnome.h	\
+	soup-password-manager-gnome.c
 
 endif
 
diff --git a/libsoup/soup-auth-manager-ntlm.h b/libsoup/soup-auth-manager-ntlm.h
index a048950..f0b4f57 100644
--- a/libsoup/soup-auth-manager-ntlm.h
+++ b/libsoup/soup-auth-manager-ntlm.h
@@ -27,6 +27,8 @@ typedef struct {
 
 } SoupAuthManagerNTLMClass;
 
+#define SOUP_AUTH_MANAGER_NTLM_USE_NTLM "use-ntlm"
+
 GType soup_auth_manager_ntlm_get_type (void);
 
 G_END_DECLS
diff --git a/libsoup/soup-auth-manager.h b/libsoup/soup-auth-manager.h
index 1fc44c4..b6759ec 100644
--- a/libsoup/soup-auth-manager.h
+++ b/libsoup/soup-auth-manager.h
@@ -30,8 +30,6 @@ typedef struct {
 			      SoupAuth *auth, gboolean retrying);
 } SoupAuthManagerClass;
 
-#define SOUP_AUTH_MANAGER_NTLM_USE_NTLM "use-ntlm"
-
 GType soup_auth_manager_get_type (void);
 
 void soup_auth_manager_add_type          (SoupAuthManager *manager,
diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c
index d77fd82..a3f2ae7 100644
--- a/libsoup/soup-auth.c
+++ b/libsoup/soup-auth.c
@@ -15,6 +15,7 @@
 #include "soup-auth-basic.h"
 #include "soup-auth-digest.h"
 #include "soup-headers.h"
+#include "soup-marshal.h"
 #include "soup-uri.h"
 
 /**
@@ -40,12 +41,20 @@ typedef struct {
 	gboolean proxy;
 	char *host;
 
+	GHashTable *saved_passwords;
 } SoupAuthPrivate;
 #define SOUP_AUTH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH, SoupAuthPrivate))
 
 G_DEFINE_TYPE (SoupAuth, soup_auth, G_TYPE_OBJECT)
 
 enum {
+	SAVE_PASSWORD,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
 	PROP_0,
 
 	PROP_SCHEME_NAME,
@@ -70,6 +79,8 @@ finalize (GObject *object)
 
 	g_free (auth->realm);
 	g_free (priv->host);
+	if (priv->saved_passwords)
+		g_hash_table_destroy (priv->saved_passwords);
 
 	G_OBJECT_CLASS (soup_auth_parent_class)->finalize (object);
 }
@@ -85,6 +96,30 @@ soup_auth_class_init (SoupAuthClass *auth_class)
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 
+	/**
+	 * SoupAuth::save-password:
+	 * @auth: the auth
+	 * @username: the username to save
+	 * @password: the password to save
+	 *
+	 * Emitted to request that the @username/@password pair be
+	 * saved. If the session supports password-saving, it will
+	 * connect to this signal before emitting
+	 * #SoupSession::authenticate, so that it record the password
+	 * if requested by the caller.
+	 *
+	 * Since: 2.28
+	 **/
+	signals[SAVE_PASSWORD] =
+		g_signal_new ("save-password",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      0, NULL, NULL,
+			      soup_marshal_NONE__STRING_STRING,
+			      G_TYPE_NONE, 2,
+			      G_TYPE_STRING,
+			      G_TYPE_STRING);
+
 	/* properties */
 	/**
 	 * SOUP_AUTH_SCHEME_NAME:
@@ -325,6 +360,9 @@ soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header)
  *
  * Call this on an auth to authenticate it; normally this will cause
  * the auth's message to be requeued with the new authentication info.
+ *
+ * This does not cause the password to be saved to persistent storage;
+ * see soup_auth_save_password() for that.
  **/
 void
 soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password)
@@ -506,3 +544,136 @@ soup_auth_free_protection_space (SoupAuth *auth, GSList *space)
 		g_free (s->data);
 	g_slist_free (space);
 }
+
+/**
+ * soup_auth_get_saved_users:
+ * @auth: a #SoupAuth
+ *
+ * Gets a list of usernames for which a saved password is available.
+ * (If the session is not configured to save passwords, this will
+ * always be %NULL.)
+ *
+ * Return value: the list of usernames. You must free the list with
+ * g_slist_free(), but do not free or modify the contents.
+ *
+ * Since: 2.28
+ **/
+GSList *
+soup_auth_get_saved_users (SoupAuth *auth)
+{
+	SoupAuthPrivate *priv;
+	GSList *users;
+
+	g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+
+	priv = SOUP_AUTH_GET_PRIVATE (auth);
+	users = NULL;
+
+	if (priv->saved_passwords) {
+		GHashTableIter iter;
+		gpointer key, value;
+
+		g_hash_table_iter_init (&iter, priv->saved_passwords);
+		while (g_hash_table_iter_next (&iter, &key, &value))
+			users = g_slist_prepend (users, key);
+	}
+	return users;
+}
+
+/**
+ * soup_auth_get_saved_password:
+ * @auth: a #SoupAuth
+ * @user: a username from the list returned from
+ * soup_auth_get_saved_users().
+ *
+ * Given a username for which @auth has a saved password, this returns
+ * that password. If @auth doesn't have a passwords saved for @user, it
+ * returns %NULL.
+ *
+ * Return value: the saved password, or %NULL.
+ *
+ * Since: 2.28
+ **/
+const char *
+soup_auth_get_saved_password (SoupAuth *auth, const char *user)
+{
+	SoupAuthPrivate *priv;
+
+	g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+	g_return_val_if_fail (user != NULL, NULL);
+
+	priv = SOUP_AUTH_GET_PRIVATE (auth);
+	if (!priv->saved_passwords)
+		return NULL;
+	return g_hash_table_lookup (priv->saved_passwords, user);
+}
+
+static void
+free_password (gpointer password)
+{
+	memset (password, 0, strlen (password));
+	g_free (password);
+}
+
+static inline void
+init_saved_passwords (SoupAuthPrivate *priv)
+{
+	priv->saved_passwords = g_hash_table_new_full (
+		g_str_hash, g_str_equal, g_free, free_password);
+}
+
+/**
+ * soup_auth_has_saved_password:
+ * @auth: a #SoupAuth
+ * @username: a username
+ * @password: a password
+ *
+ * Updates @auth to be aware of an already-saved username/password
+ * combination. This method <emphasis>does not</emphasis> cause the
+ * given @username and @password to be saved; use
+ * soup_auth_save_password() for that. (soup_auth_has_saved_password()
+ * is an internal method, which is used by the code that actually
+ * saves and restores the passwords.)
+ *
+ * Since: 2.28
+ **/
+void
+soup_auth_has_saved_password (SoupAuth *auth, const char *username,
+			      const char *password)
+{
+	SoupAuthPrivate *priv;
+
+	g_return_if_fail (SOUP_IS_AUTH (auth));
+	g_return_if_fail (username != NULL);
+	g_return_if_fail (password != NULL);
+
+	priv = SOUP_AUTH_GET_PRIVATE (auth);
+
+	if (!priv->saved_passwords)
+		init_saved_passwords (priv);
+	g_hash_table_insert (priv->saved_passwords,
+			     g_strdup (username), g_strdup (password));
+}
+
+/**
+ * soup_auth_save_password:
+ * @auth: a #SoupAuth
+ * @username: the username provided by the user or client
+ * @password: the password provided by the user or client
+ *
+ * Requests that the username/password pair be saved to whatever form
+ * of persistent password storage the session supports.
+ *
+ * Since: 2.28
+ **/
+void
+soup_auth_save_password (SoupAuth *auth, const char *username,
+			 const char *password)
+{
+	g_return_if_fail (SOUP_IS_AUTH (auth));
+	g_return_if_fail (username != NULL);
+	g_return_if_fail (password != NULL);
+
+	g_signal_emit (auth, signals[SAVE_PASSWORD], 0,
+		       username, password);
+}
diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h
index 53eb16b..1259df8 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -72,6 +72,13 @@ const char *soup_auth_get_host              (SoupAuth      *auth);
 const char *soup_auth_get_realm             (SoupAuth      *auth);
 char       *soup_auth_get_info              (SoupAuth      *auth);
 
+GSList     *soup_auth_get_saved_users       (SoupAuth      *auth);
+const char *soup_auth_get_saved_password    (SoupAuth      *auth,
+					     const char    *user);
+void        soup_auth_save_password         (SoupAuth      *auth,
+					     const char    *username,
+					     const char    *password);
+
 void        soup_auth_authenticate          (SoupAuth      *auth,
 					     const char    *username,
 					     const char    *password);
@@ -85,6 +92,10 @@ GSList     *soup_auth_get_protection_space  (SoupAuth      *auth,
 void        soup_auth_free_protection_space (SoupAuth      *auth,
 					     GSList        *space);
 
+void        soup_auth_has_saved_password    (SoupAuth      *auth,
+					     const char    *username,
+					     const char    *password);
+
 G_END_DECLS
 
 #endif /* SOUP_AUTH_H */
diff --git a/libsoup/soup-gnome.h b/libsoup/soup-gnome.h
index a2134a4..6703cf0 100644
--- a/libsoup/soup-gnome.h
+++ b/libsoup/soup-gnome.h
@@ -10,5 +10,6 @@
 
 #include <libsoup/soup-cookie-jar-sqlite.h>
 #include <libsoup/soup-gnome-features.h>
+#include <libsoup/soup-password-manager-gnome.h>
 
 #endif /* SOUP_GNOME_H */
diff --git a/libsoup/soup-marshal.list b/libsoup/soup-marshal.list
index d0c53ef..7714813 100644
--- a/libsoup/soup-marshal.list
+++ b/libsoup/soup-marshal.list
@@ -7,3 +7,4 @@ NONE:OBJECT,POINTER
 NONE:BOXED,BOXED
 NONE:OBJECT,OBJECT,BOOLEAN
 NONE:STRING,BOXED
+NONE:STRING,STRING
diff --git a/libsoup/soup-password-manager-gnome.c b/libsoup/soup-password-manager-gnome.c
new file mode 100644
index 0000000..9b56eae
--- /dev/null
+++ b/libsoup/soup-password-manager-gnome.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-password-manager-gnome.c: GNOME-keyring-based password manager
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-password-manager-gnome.h"
+#include "soup-auth.h"
+#include "soup-session-feature.h"
+#include "soup-uri.h"
+
+#include <gnome-keyring.h>
+
+static void soup_password_manager_gnome_interface_init (SoupPasswordManagerInterface *password_manager_interface);
+
+G_DEFINE_TYPE_EXTENDED (SoupPasswordManagerGNOME, soup_password_manager_gnome, G_TYPE_OBJECT, 0,
+			G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, NULL)
+			G_IMPLEMENT_INTERFACE (SOUP_TYPE_PASSWORD_MANAGER, soup_password_manager_gnome_interface_init))
+
+static void get_passwords_async (SoupPasswordManager  *password_manager,
+				 SoupMessage          *msg,
+				 SoupAuth             *auth,
+				 gboolean              retrying,
+				 GMainContext         *async_context,
+				 GCancellable         *cancellable,
+				 SoupPasswordManagerCallback callback,
+				 gpointer              user_data);
+static void get_passwords_sync  (SoupPasswordManager  *password_manager,
+				 SoupMessage          *msg,
+				 SoupAuth             *auth,
+				 GCancellable         *cancellable);
+
+static void
+soup_password_manager_gnome_init (SoupPasswordManagerGNOME *manager_gnome)
+{
+}
+
+static void
+soup_password_manager_gnome_class_init (SoupPasswordManagerGNOMEClass *gnome_class)
+{
+}
+
+static void
+soup_password_manager_gnome_interface_init (SoupPasswordManagerInterface *password_manager_interface)
+{
+	password_manager_interface->get_passwords_async = get_passwords_async;
+	password_manager_interface->get_passwords_sync = get_passwords_sync;
+}
+
+SoupPasswordManager *
+soup_password_manager_gnome_new (void)
+{
+	return g_object_new (SOUP_TYPE_PASSWORD_MANAGER_GNOME, NULL);
+}
+
+
+static void
+save_password_callback (GnomeKeyringResult result, guint32 val, gpointer data)
+{
+}
+
+static void
+async_save_password (SoupAuth *auth, const char *username,
+		     const char *password, gpointer user_data)
+{
+	SoupURI *uri = user_data;
+
+	gnome_keyring_set_network_password (
+		NULL, /* use default keyring */
+		username,
+		soup_auth_get_realm (auth),
+		uri->host,
+		NULL,
+		uri->scheme,
+		soup_auth_get_scheme_name (auth),
+		uri->port,
+		password,
+		save_password_callback, NULL, NULL);
+}
+
+static void
+sync_save_password (SoupAuth *auth, const char *username,
+		    const char *password, gpointer user_data)
+{
+	SoupURI *uri = user_data;
+	guint32 item_id;
+
+	gnome_keyring_set_network_password_sync (
+		NULL, /* use default keyring */
+		username,
+		soup_auth_get_realm (auth),
+		uri->host,
+		NULL,
+		uri->scheme,
+		soup_auth_get_scheme_name (auth),
+		uri->port,
+		password,
+		&item_id);
+}
+
+static void
+update_auth_for_passwords (SoupAuth *auth, SoupMessage *msg,
+			   GList *passwords, gboolean async)
+{
+	GnomeKeyringNetworkPasswordData *pdata;
+	SoupURI *uri;
+
+	while (passwords) {
+		pdata = passwords->data;
+		soup_auth_has_saved_password (auth, pdata->user,
+					      pdata->password);
+		passwords = passwords->next;
+	}
+
+	uri = soup_uri_copy (soup_message_get_uri (msg));
+	g_signal_connect (auth, "save_password",
+			  G_CALLBACK (async ? async_save_password : sync_save_password),
+			  uri);
+	g_object_set_data_full (G_OBJECT (auth),
+				"SoupPasswordManagerGNOME-save_password-uri",
+				uri, (GDestroyNotify)soup_uri_free);
+}
+
+typedef struct {
+	SoupPasswordManager *password_manager;
+	SoupMessage *msg;
+	SoupAuth *auth;
+	gboolean retrying;
+
+	SoupPasswordManagerCallback callback;
+	gpointer user_data;
+
+	gpointer request;
+} SoupPasswordManagerGNOMEAuthData;
+
+static void
+find_password_callback (GnomeKeyringResult result, GList *list,
+			gpointer user_data)
+{
+	SoupPasswordManagerGNOMEAuthData *auth_data = user_data;
+
+	/* FIXME: check result? */
+
+	update_auth_for_passwords (auth_data->auth, auth_data->msg, list, TRUE);
+	auth_data->callback (auth_data->password_manager,
+			     auth_data->msg, auth_data->auth,
+			     auth_data->retrying, auth_data->user_data);
+
+	/* gnome-keyring will call free_auth_data to clean up for us. */
+}
+
+static void
+free_auth_data (gpointer data)
+{
+	SoupPasswordManagerGNOMEAuthData *auth_data = data;
+
+	g_object_unref (auth_data->auth);
+	g_object_unref (auth_data->msg);
+	g_slice_free (SoupPasswordManagerGNOMEAuthData, auth_data);
+}
+
+static void
+get_passwords_async (SoupPasswordManager  *password_manager,
+		     SoupMessage          *msg,
+		     SoupAuth             *auth,
+		     gboolean              retrying,
+		     GMainContext         *async_context,
+		     GCancellable         *cancellable,
+		     SoupPasswordManagerCallback callback,
+		     gpointer              user_data)
+{
+	SoupPasswordManagerGNOMEAuthData *auth_data;
+	SoupURI *uri = soup_message_get_uri (msg);
+
+	auth_data = g_slice_new (SoupPasswordManagerGNOMEAuthData);
+	auth_data->password_manager = password_manager;
+	auth_data->msg = g_object_ref (msg);
+	auth_data->auth = g_object_ref (auth);
+	auth_data->retrying = retrying;
+
+	/* FIXME: async_context, cancellable */
+
+	auth_data->callback = callback;
+	auth_data->user_data = user_data;
+
+	/* FIXME: should we be specifying protocol and port here, or
+	 * leaving them NULL/0 and filtering results in the callback?
+	 * We don't want to send https passwords to http, but the
+	 * reverse might be OK (if that's how other clients tend to
+	 * behave).
+	 */
+	auth_data->request = gnome_keyring_find_network_password (
+		NULL,                             /* user -- accept any */
+		soup_auth_get_realm (auth),       /* domain */
+		uri->host,                        /* server */
+		NULL,                             /* object -- unused */
+		uri->scheme,                      /* protocol */
+		soup_auth_get_scheme_name (auth), /* authtype */
+		uri->port,                        /* port */
+		find_password_callback, auth_data, free_auth_data);
+}
+
+static void
+get_passwords_sync (SoupPasswordManager  *password_manager,
+		    SoupMessage          *msg,
+		    SoupAuth             *auth,
+		    GCancellable         *cancellable)
+{
+	SoupURI *uri = soup_message_get_uri (msg);
+	GList *results = NULL;
+
+	/* FIXME: cancellable */
+
+	gnome_keyring_find_network_password_sync (
+		NULL,                             /* user -- accept any */
+		soup_auth_get_realm (auth),       /* domain */
+		uri->host,                        /* server */
+		NULL,                             /* object -- unused */
+		uri->scheme,                      /* protocol */
+		soup_auth_get_scheme_name (auth), /* authtype */
+		uri->port,                        /* port */
+		&results);
+
+	update_auth_for_passwords (auth, msg, results, FALSE);
+}
diff --git a/libsoup/soup-password-manager-gnome.h b/libsoup/soup-password-manager-gnome.h
new file mode 100644
index 0000000..ad6d362
--- /dev/null
+++ b/libsoup/soup-password-manager-gnome.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_PASSWORD_MANAGER_GNOME_H
+#define SOUP_PASSWORD_MANAGER_GNOME_H 1
+
+#include <libsoup/soup-password-manager.h>
+
+#define SOUP_TYPE_PASSWORD_MANAGER_GNOME            (soup_password_manager_gnome_get_type ())
+#define SOUP_PASSWORD_MANAGER_GNOME(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PASSWORD_MANAGER_GNOME, SoupPasswordManagerGNOME))
+#define SOUP_PASSWORD_MANAGER_GNOME_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PASSWORD_MANAGER_GNOME, SoupPasswordManagerGNOMEClass))
+#define SOUP_IS_PASSWORD_MANAGER_GNOME(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_PASSWORD_MANAGER_GNOME))
+#define SOUP_IS_PASSWORD_MANAGER_GNOME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_PASSWORD_MANAGER_GNOME))
+#define SOUP_PASSWORD_MANAGER_GNOME_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_PASSWORD_MANAGER_GNOME, SoupPasswordManagerGNOMEClass))
+
+typedef struct {
+	GObject parent;
+
+} SoupPasswordManagerGNOME;
+
+typedef struct {
+	GObjectClass parent_class;
+
+} SoupPasswordManagerGNOMEClass;
+
+GType soup_password_manager_gnome_get_type (void);
+
+SoupPasswordManager *soup_password_manager_gnome_new (void);
+
+#endif /* SOUP_PASSWORD_MANAGER_GNOME_H */
diff --git a/libsoup/soup-password-manager.c b/libsoup/soup-password-manager.c
new file mode 100644
index 0000000..5654dc3
--- /dev/null
+++ b/libsoup/soup-password-manager.c
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-password-manager.c: HTTP auth password manager interface
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-password-manager.h"
+#include "soup-session-feature.h"
+
+GType
+soup_password_manager_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      GType g_define_type_id =
+        g_type_register_static_simple (G_TYPE_INTERFACE,
+                                       g_intern_static_string ("SoupPasswordManager"),
+                                       sizeof (SoupPasswordManagerInterface),
+                                       (GClassInitFunc)NULL,
+                                       0,
+                                       (GInstanceInitFunc)NULL,
+                                       (GTypeFlags) 0);
+      g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_OBJECT);
+      g_type_interface_add_prerequisite (g_define_type_id, SOUP_TYPE_SESSION_FEATURE);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+  return g_define_type_id__volatile;
+}
+
+/**
+ * soup_password_manager_get_passwords_async:
+ * @password_manager: the #SoupPasswordManager
+ * @msg: the #SoupMessage being authenticated
+ * @auth: the #SoupAuth being authenticated
+ * @retrying: whether or not this is a re-attempt to authenticate
+ * @async_context: the #GMainContext to invoke @callback in
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: callback to invoke after fetching passwords
+ * @user_data: data for @callback
+ *
+ * Asynchronously attempts to look up saved passwords for @auth/@msg
+ * and then calls @callback after updating @auth with the information.
+ * Also registers @auth with @password_manager so that if the caller
+ * calls soup_auth_save_password() on it, the password will be saved.
+ *
+ * #SoupPasswordManager does not actually use the @retrying flag itself;
+ * it just passes its value on to @callback.
+ * 
+ * If @cancellable is cancelled, @callback will still be invoked.
+ *
+ * Since: 2.28
+ **/
+void
+soup_password_manager_get_passwords_async (SoupPasswordManager  *password_manager,
+					   SoupMessage          *msg,
+					   SoupAuth             *auth,
+					   gboolean              retrying,
+					   GMainContext         *async_context,
+					   GCancellable         *cancellable,
+					   SoupPasswordManagerCallback callback,
+					   gpointer              user_data)
+{
+	SOUP_PASSWORD_MANAGER_GET_CLASS (password_manager)->
+		get_passwords_async (password_manager, msg, auth, retrying,
+				     async_context, cancellable,
+				     callback, user_data);
+}
+
+/**
+ * soup_password_manager_get_passwords_sync:
+ * @password_manager: the #SoupPasswordManager
+ * @msg: the #SoupMessage being authenticated
+ * @auth: the #SoupAuth being authenticated
+ * @cancellable: a #GCancellable, or %NULL
+ *
+ * Synchronously attempts to look up saved passwords for @auth/@msg
+ * and updates @auth with the information. Also registers @auth with
+ * @password_manager so that if the caller calls
+ * soup_auth_save_password() on it, the password will be saved.
+ *
+ * Since: 2.28
+ **/
+void
+soup_password_manager_get_passwords_sync (SoupPasswordManager  *password_manager,
+					  SoupMessage          *msg,
+					  SoupAuth             *auth,
+					  GCancellable         *cancellable)
+{
+	SOUP_PASSWORD_MANAGER_GET_CLASS (password_manager)->
+		get_passwords_sync (password_manager, msg, auth, cancellable);
+}
diff --git a/libsoup/soup-password-manager.h b/libsoup/soup-password-manager.h
new file mode 100644
index 0000000..2cd0270
--- /dev/null
+++ b/libsoup/soup-password-manager.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_PASSWORD_MANAGER_H
+#define SOUP_PASSWORD_MANAGER_H 1
+
+#include <libsoup/soup-types.h>
+#include <gio/gio.h>
+
+#define SOUP_TYPE_PASSWORD_MANAGER            (soup_password_manager_get_type ())
+#define SOUP_PASSWORD_MANAGER(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PASSWORD_MANAGER, SoupPasswordManager))
+#define SOUP_PASSWORD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PASSWORD_MANAGER, SoupPasswordManagerInterface))
+#define SOUP_IS_PASSWORD_MANAGER(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_PASSWORD_MANAGER))
+#define SOUP_IS_PASSWORD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_PASSWORD_MANAGER))
+#define SOUP_PASSWORD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SOUP_TYPE_PASSWORD_MANAGER, SoupPasswordManagerInterface))
+
+typedef struct _SoupPasswordManager SoupPasswordManager;
+
+typedef void (*SoupPasswordManagerCallback) (SoupPasswordManager *,
+					     SoupMessage *, SoupAuth *,
+					     gboolean retrying,
+					     gpointer user_data);
+
+typedef struct {
+	GTypeInterface base;
+
+	/* virtual methods */
+	void (*get_passwords_async) (SoupPasswordManager *, SoupMessage *,
+				     SoupAuth *, gboolean,
+				     GMainContext *, GCancellable *,
+				     SoupPasswordManagerCallback, gpointer);
+	void (*get_passwords_sync)  (SoupPasswordManager *, SoupMessage *,
+				     SoupAuth *, GCancellable *);
+
+} SoupPasswordManagerInterface;
+
+GType soup_password_manager_get_type (void);
+
+void  soup_password_manager_get_passwords_async (SoupPasswordManager  *password_manager,
+						 SoupMessage          *msg,
+						 SoupAuth             *auth,
+						 gboolean              retrying,
+						 GMainContext         *async_context,
+						 GCancellable         *cancellable,
+						 SoupPasswordManagerCallback callback,
+						 gpointer              user_data);
+
+void  soup_password_manager_get_passwords_sync  (SoupPasswordManager  *password_manager,
+						 SoupMessage          *msg,
+						 SoupAuth             *auth,
+						 GCancellable         *cancellable);
+
+#endif /* SOUP_PASSWORD_MANAGER_H */
diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c
index 81fda17..6045481 100644
--- a/libsoup/soup-session-async.c
+++ b/libsoup/soup-session-async.c
@@ -15,6 +15,7 @@
 #include "soup-address.h"
 #include "soup-message-private.h"
 #include "soup-misc.h"
+#include "soup-password-manager.h"
 #include "soup-proxy-uri-resolver.h"
 #include "soup-uri.h"
 
@@ -34,6 +35,9 @@ static void  queue_message   (SoupSession *session, SoupMessage *req,
 			      SoupSessionCallback callback, gpointer user_data);
 static guint send_message    (SoupSession *session, SoupMessage *req);
 
+static void  auth_required   (SoupSession *session, SoupMessage *msg,
+			      SoupAuth *auth, gboolean retrying);
+
 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
 
 typedef struct {
@@ -69,6 +73,7 @@ soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class)
 	/* virtual method override */
 	session_class->queue_message = queue_message;
 	session_class->send_message = send_message;
+	session_class->auth_required = auth_required;
 
 	object_class->finalize = finalize;
 }
@@ -444,3 +449,36 @@ send_message (SoupSession *session, SoupMessage *req)
 
 	return req->status_code;
 }
+
+static void
+got_passwords (SoupPasswordManager *password_manager, SoupMessage *msg,
+	       SoupAuth *auth, gboolean retrying, gpointer session)
+{
+	soup_session_unpause_message (session, msg);
+	SOUP_SESSION_CLASS (soup_session_async_parent_class)->
+		auth_required (session, msg, auth, retrying);
+	g_object_unref (auth);
+}
+
+static void
+auth_required (SoupSession *session, SoupMessage *msg,
+	       SoupAuth *auth, gboolean retrying)
+{
+	SoupSessionFeature *password_manager;
+
+	password_manager = soup_session_get_feature_for_message (
+		session, SOUP_TYPE_PASSWORD_MANAGER, msg);
+	if (password_manager) {
+		soup_session_pause_message (session, msg);
+		g_object_ref (auth);
+		soup_password_manager_get_passwords_async (
+			SOUP_PASSWORD_MANAGER (password_manager),
+			msg, auth, retrying,
+			soup_session_get_async_context (session),
+			NULL, /* FIXME cancellable */
+			got_passwords, session);
+	} else {
+		SOUP_SESSION_CLASS (soup_session_async_parent_class)->
+			auth_required (session, msg, auth, retrying);
+	}
+}
diff --git a/libsoup/soup-session-sync.c b/libsoup/soup-session-sync.c
index fb19f21..662c029 100644
--- a/libsoup/soup-session-sync.c
+++ b/libsoup/soup-session-sync.c
@@ -14,8 +14,9 @@
 #include "soup-session-private.h"
 #include "soup-address.h"
 #include "soup-message-private.h"
-#include "soup-proxy-uri-resolver.h"
 #include "soup-misc.h"
+#include "soup-password-manager.h"
+#include "soup-proxy-uri-resolver.h"
 #include "soup-uri.h"
 
 /**
@@ -54,6 +55,8 @@ static void  queue_message  (SoupSession *session, SoupMessage *msg,
 static guint send_message   (SoupSession *session, SoupMessage *msg);
 static void  cancel_message (SoupSession *session, SoupMessage *msg,
 			     guint status_code);
+static void  auth_required  (SoupSession *session, SoupMessage *msg,
+			     SoupAuth *auth, gboolean retrying);
 
 G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION)
 
@@ -89,6 +92,7 @@ soup_session_sync_class_init (SoupSessionSyncClass *session_sync_class)
 	session_class->queue_message = queue_message;
 	session_class->send_message = send_message;
 	session_class->cancel_message = cancel_message;
+	session_class->auth_required = auth_required;
 	object_class->finalize = finalize;
 }
 
@@ -323,3 +327,20 @@ cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
 	g_cond_broadcast (priv->cond);
 }
 
+static void
+auth_required (SoupSession *session, SoupMessage *msg,
+	       SoupAuth *auth, gboolean retrying)
+{
+	SoupSessionFeature *password_manager;
+
+	password_manager = soup_session_get_feature_for_message (
+		session, SOUP_TYPE_PASSWORD_MANAGER, msg);
+	if (password_manager) {
+		soup_password_manager_get_passwords_sync (
+			SOUP_PASSWORD_MANAGER (password_manager),
+			msg, auth, NULL); /* FIXME cancellable */
+	}
+
+	SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
+		auth_required (session, msg, auth, retrying);
+}
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index cdb2bf8..d4eb1e5 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -102,6 +102,8 @@ static void queue_message   (SoupSession *session, SoupMessage *msg,
 static void requeue_message (SoupSession *session, SoupMessage *msg);
 static void cancel_message  (SoupSession *session, SoupMessage *msg,
 			     guint status_code);
+static void auth_required   (SoupSession *session, SoupMessage *msg,
+			     SoupAuth *auth, gboolean retrying);
 
 static void auth_manager_authenticate (SoupAuthManager *manager,
 				       SoupMessage *msg, SoupAuth *auth,
@@ -235,6 +237,7 @@ soup_session_class_init (SoupSessionClass *session_class)
 	session_class->queue_message = queue_message;
 	session_class->requeue_message = requeue_message;
 	session_class->cancel_message = cancel_message;
+	session_class->auth_required = auth_required;
 
 	/* virtual method override */
 	object_class->dispose = dispose;
@@ -841,11 +844,19 @@ free_host (SoupSessionHost *host)
 }	
 
 static void
+auth_required (SoupSession *session, SoupMessage *msg,
+	       SoupAuth *auth, gboolean retrying)
+{
+	g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
+}
+
+static void
 auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
 			   SoupAuth *auth, gboolean retrying,
 			   gpointer session)
 {
-	g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
+	SOUP_SESSION_GET_CLASS (session)->auth_required (
+		session, msg, auth, retrying);
 }
 
 #define SOUP_METHOD_IS_SAFE(method) (method == SOUP_METHOD_GET || \
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index 2eb86b5..598ee45 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -46,8 +46,11 @@ typedef struct {
 	void  (*cancel_message)  (SoupSession *session, SoupMessage *msg,
 				  guint status_code);
 
+	void  (*auth_required)   (SoupSession *session, SoupMessage *msg,
+				  SoupAuth *auth, gboolean retrying);
+
+
 	/* Padding for future expansion */
-	void (*_libsoup_reserved1) (void);
 	void (*_libsoup_reserved2) (void);
 	void (*_libsoup_reserved3) (void);
 	void (*_libsoup_reserved4) (void);
diff --git a/libsoup/soup.h b/libsoup/soup.h
index c761e00..f3b0b3d 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -28,6 +28,7 @@ extern "C" {
 #include <libsoup/soup-method.h>
 #include <libsoup/soup-misc.h>
 #include <libsoup/soup-multipart.h>
+#include <libsoup/soup-password-manager.h>
 #include <libsoup/soup-proxy-resolver.h>
 #include <libsoup/soup-proxy-uri-resolver.h>
 #include <libsoup/soup-server.h>
diff --git a/tests/get.c b/tests/get.c
index b0e5c57..9bb00a5 100644
--- a/tests/get.c
+++ b/tests/get.c
@@ -14,6 +14,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <termios.h>
 #include <unistd.h>
 
 #ifdef HAVE_GNOME
@@ -74,6 +75,75 @@ get_url (const char *url)
 }
 
 static void
+authenticate (SoupSession *session, SoupMessage *msg,
+	      SoupAuth *auth, gpointer user_data)
+{
+	char *uri;
+	GSList *saved_users;
+	struct termios t;
+	int old_lflag;
+	char user[80], pwbuf[80];
+	const char *password;
+
+	if (tcgetattr (STDIN_FILENO, &t) != 0)
+		return;
+
+	uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
+	fprintf (stderr, "Authentication required for %s:\n", uri);
+	g_free (uri);
+	fprintf (stderr, "  Realm: %s, Auth type: %s\n",
+		soup_auth_get_realm (auth), soup_auth_get_scheme_name (auth));
+
+	saved_users = soup_auth_get_saved_users (auth);
+	if (saved_users) {
+		GSList *u;
+
+		fprintf (stderr, "  Passwords saved for: ");
+		for (u = saved_users; u; u = u->next) {
+			if (u != saved_users)
+				fprintf (stderr, ", ");
+			fprintf (stderr, "%s", (char *)u->data);
+		}
+		fprintf (stderr, "\n");
+	}
+	g_slist_free (saved_users);
+
+	fprintf (stderr, "  username: ");
+	fflush (stderr);
+
+	if (!fgets (user, sizeof (user), stdin) || user[0] == '\n')
+		return;
+	*strchr (user, '\n') = '\0';
+
+	password = soup_auth_get_saved_password (auth, user);
+	if (!password) {
+		fprintf (stderr, "  password: ");
+		fflush (stderr);
+
+		old_lflag = t.c_lflag;
+		t.c_lflag = (t.c_lflag | ICANON | ECHONL) & ~ECHO;
+		tcsetattr (STDIN_FILENO, TCSANOW, &t);
+
+		/* For some reason, fgets can return EINTR on
+		 * Linux if ECHO is false...
+		 */
+		do
+			password = fgets (pwbuf, sizeof (pwbuf), stdin);
+		while (password == NULL && errno == EINTR);
+
+		t.c_lflag = old_lflag;
+		tcsetattr (STDIN_FILENO, TCSANOW, &t);
+
+		if (!password || pwbuf[0] == '\n')
+			return;
+		*strchr (pwbuf, '\n') = '\0';
+	}
+
+	soup_auth_authenticate (auth, user, password);
+	soup_auth_save_password (auth, user, password);
+}
+
+static void
 usage (void)
 {
 	fprintf (stderr, "Usage: get [-c CAfile] [-p proxy URL] [-h] [-d] URL\n");
@@ -90,6 +160,7 @@ main (int argc, char **argv)
 
 	g_thread_init (NULL);
 	g_type_init ();
+	g_set_application_name ("get");
 
 	method = SOUP_METHOD_GET;
 
@@ -144,6 +215,7 @@ main (int argc, char **argv)
 			SOUP_SESSION_SSL_CA_FILE, cafile,
 #ifdef HAVE_GNOME
 			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
+			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PASSWORD_MANAGER_GNOME,
 #endif
 			SOUP_SESSION_USER_AGENT, "get ",
 			NULL);
@@ -152,10 +224,13 @@ main (int argc, char **argv)
 			SOUP_SESSION_SSL_CA_FILE, cafile,
 #ifdef HAVE_GNOME
 			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
+			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PASSWORD_MANAGER_GNOME,
 #endif
 			SOUP_SESSION_USER_AGENT, "get ",
 			NULL);
 	}
+	g_signal_connect (session, "authenticate",
+			  G_CALLBACK (authenticate), NULL);
 
 	/* Need to do this after creating the session, since adding
 	 * SOUP_TYPE_GNOME_FEATURE_2_26 will add a proxy resolver, thereby



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]