[seahorse/wip/nielsdg/server-source-props: 8/10] pgp: ServerSource: Only use a URI property




commit cb24db76800aee64a2a92f114d45e7cd3742f59a
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Thu Feb 18 10:30:47 2021 +0100

    pgp: ServerSource: Only use a URI property
    
    We have a redundant property for the (unresolved) address and the URI
    that is put in by the user (which is separated by some quick-n-dirty
    function to extract the scheme and host). Since we're already using
    better URI wrappers inside subclasses (either `SoupUri` or
    `GNetworkAddress`), we can just get rid of the "key-server" property,
    which didn't amount to much really.
    
    This commits also no longer requires libsoup for resolving addresses in
    LDAP sources.

 meson.build                  |   1 -
 pgp/seahorse-hkp-source.c    |  23 +++----
 pgp/seahorse-hkp-source.h    |   5 +-
 pgp/seahorse-ldap-source.c   | 139 ++++++++++++++++-----------------------
 pgp/seahorse-ldap-source.h   |   3 +-
 pgp/seahorse-server-source.c | 151 +++++++------------------------------------
 pgp/seahorse-server-source.h |   2 +-
 7 files changed, 90 insertions(+), 234 deletions(-)
---
diff --git a/meson.build b/meson.build
index 97f3bc69..540e933f 100644
--- a/meson.build
+++ b/meson.build
@@ -113,7 +113,6 @@ conf.set('WITH_PGP', get_option('pgp-support'))
 conf.set('WITH_PKCS11', get_option('pkcs11-support'))
 conf.set('WITH_LDAP', get_option('ldap-support'))
 conf.set('WITH_HKP', get_option('hkp-support'))
-conf.set('WITH_SOUP', get_option('hkp-support'))
 conf.set('WITH_KEYSERVER', get_option('keyservers-support'))
 conf.set('WITH_SHARING', get_option('key-sharing'))
 conf.set_quoted('SSH_KEYGEN_PATH', ssh_keygen.path())
diff --git a/pgp/seahorse-hkp-source.c b/pgp/seahorse-hkp-source.c
index 69239691..c053de2b 100644
--- a/pgp/seahorse-hkp-source.c
+++ b/pgp/seahorse-hkp-source.c
@@ -75,12 +75,10 @@ static SoupURI*
 get_http_server_uri (SeahorseHKPSource *self, const char *path)
 {
     g_autoptr(SoupURI) uri = NULL;
-    g_autofree gchar *server = NULL;
     g_autofree char *conf_uri = NULL;
 
-    g_object_get (self, "key-server", &server, NULL);
-    g_return_val_if_fail (server != NULL, NULL);
-    g_object_get (self, "uri", &conf_uri, NULL);
+    conf_uri = seahorse_place_get_uri (SEAHORSE_PLACE (self));
+    g_return_val_if_fail (conf_uri != NULL, NULL);
 
     if (strncasecmp (conf_uri, "hkp:", 4) == 0) {
         g_autofree char *t = g_strdup_printf ("http:%s", conf_uri + 4);
@@ -505,8 +503,8 @@ hkp_message_propagate_error (SeahorseHKPSource *self,
                              SoupMessage *message,
                              GError **error)
 {
-    gchar *server;
-    g_autofree gchar *text = NULL;
+    g_autofree char *uri = NULL;
+    g_autofree char *text = NULL;
 
     if (!SOUP_MESSAGE_IS_ERROR (message))
         return FALSE;
@@ -517,7 +515,7 @@ hkp_message_propagate_error (SeahorseHKPSource *self,
         return TRUE;
     }
 
-    g_object_get (self, "key-server", &server, NULL);
+    uri = seahorse_place_get_uri (SEAHORSE_PLACE (self));
 
     /* Make the body lower case, and no tags */
     text = g_strndup (message->response_body->data, message->response_body->length);
@@ -531,11 +529,12 @@ hkp_message_propagate_error (SeahorseHKPSource *self,
 
     if (text && strstr (text, "too many")) {
         g_set_error (error, HKP_ERROR_DOMAIN, 0,
-                     _("Search was not specific enough. Server “%s” found too many keys."), server);
+                     _("Search was not specific enough. Server “%s” found too many keys."),
+                     uri);
     } else {
         g_set_error (error, HKP_ERROR_DOMAIN, message->status_code,
                      _("Couldn’t communicate with server “%s”: %s"),
-                     server, message->reason_phrase);
+                     uri, message->reason_phrase);
     }
 
     return TRUE;
@@ -1011,13 +1010,11 @@ seahorse_hkp_source_class_init (SeahorseHKPSourceClass *klass)
  * Returns: A new HKP Key Source
  */
 SeahorseHKPSource*
-seahorse_hkp_source_new (const gchar *uri, const gchar *host)
+seahorse_hkp_source_new (const char *uri)
 {
     g_return_val_if_fail (seahorse_hkp_is_valid_uri (uri), NULL);
-    g_return_val_if_fail (host && *host, NULL);
 
-    return g_object_new (SEAHORSE_TYPE_HKP_SOURCE, "uri", uri,
-                         "key-server", host, NULL);
+    return g_object_new (SEAHORSE_TYPE_HKP_SOURCE, "uri", uri, NULL);
 }
 
 /**
diff --git a/pgp/seahorse-hkp-source.h b/pgp/seahorse-hkp-source.h
index 03fcc0d9..fbb4e01e 100644
--- a/pgp/seahorse-hkp-source.h
+++ b/pgp/seahorse-hkp-source.h
@@ -37,10 +37,9 @@ G_DECLARE_FINAL_TYPE (SeahorseHKPSource, seahorse_hkp_source,
                       SEAHORSE, HKP_SOURCE,
                       SeahorseServerSource)
 
-SeahorseHKPSource*    seahorse_hkp_source_new      (const gchar *uri,
-                                                    const gchar *host);
+SeahorseHKPSource*    seahorse_hkp_source_new      (const char *uri);
 
-gboolean              seahorse_hkp_is_valid_uri    (const gchar *uri);
+gboolean              seahorse_hkp_is_valid_uri    (const char *uri);
 
 
 #define HKP_ERROR_DOMAIN (seahorse_hkp_error_quark())
diff --git a/pgp/seahorse-ldap-source.c b/pgp/seahorse-ldap-source.c
index 415fc4ee..44e9d6f1 100644
--- a/pgp/seahorse-ldap-source.c
+++ b/pgp/seahorse-ldap-source.c
@@ -40,10 +40,6 @@
 
 #include <ldap.h>
 
-#ifdef WITH_SOUP
-#include <libsoup/soup-address.h>
-#endif
-
 #ifdef WITH_LDAP
 
 /* Amount of keys to load in a batch */
@@ -430,14 +426,14 @@ static gboolean
 seahorse_ldap_source_propagate_error (SeahorseLDAPSource *self,
                                       int rc, GError **error)
 {
-    g_autofree char *server = NULL;
+    g_autofree char *uri = NULL;
 
     if (rc == LDAP_SUCCESS)
         return FALSE;
 
-    g_object_get (self, "key-server", &server, NULL);
+    uri = seahorse_place_get_uri (SEAHORSE_PLACE (self));
     g_set_error (error, LDAP_ERROR_DOMAIN, rc, _("Couldn’t communicate with %s: %s"),
-                 server, ldap_err2string (rc));
+                 uri, ldap_err2string (rc));
 
     return TRUE;
 }
@@ -570,38 +566,43 @@ on_connect_bind_completed (LDAPMessage *result,
 }
 
 static void
-once_resolved_start_connect (SeahorseLDAPSource *self,
-                             GTask *task,
-                             const char *address)
+on_address_resolved (GObject      *src_object,
+                     GAsyncResult *res,
+                     gpointer      user_data)
 {
+    GSocketAddressEnumerator *enumer = G_SOCKET_ADDRESS_ENUMERATOR (src_object);
+    g_autoptr(GTask) task = user_data;
+    SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (g_task_get_source_object (task));
     ConnectClosure *closure = g_task_get_task_data (task);
     GCancellable *cancellable = g_task_get_cancellable (task);
-    g_autofree char *server = NULL;
-    char *text;
+    g_autofree char *uri = NULL;
+    g_autoptr(GSocketAddress) addr = NULL;
     g_autoptr(GError) error = NULL;
-    int port = LDAP_PORT;
-    g_autofree char *url = NULL;
+    g_autofree char *addr_str = NULL;
+    g_autofree char *resolved_url = NULL;
     int rc;
     struct berval cred;
     int ldap_op;
     g_autoptr(GSource) gsource = NULL;
 
-    /* Now that we've resolved our address, connect via IP */
-    g_object_get (self, "key-server", &server, NULL);
-    g_return_if_fail (server && server[0]);
-
-    if ((text = strchr (server, ':')) != NULL) {
-        *text = 0;
-        text++;
-        port = atoi (text);
-        if (port <= 0 || port >= G_MAXUINT16) {
-            g_warning ("invalid port number: %s (using default)", text);
-            port = LDAP_PORT;
-        }
+    /* Note: this is the original (unresolved) URI */
+    uri = seahorse_place_get_uri (SEAHORSE_PLACE (self));
+
+    /* Get the resolved IP */
+    addr = g_socket_address_enumerator_next_finish (enumer, res, &error);
+    if (!addr) {
+        g_task_return_new_error (task, SEAHORSE_ERROR, -1,
+                                 _("Couldn’t resolve address %s"), uri);
+        return;
     }
 
-    url = g_strdup_printf ("ldap://%s:%u";, address, port);
-    rc = ldap_initialize (&closure->ldap, url);
+    /* Now that we've resolved our address, connect via IP */
+    seahorse_progress_update (cancellable, task, _("Connecting to: %s"), uri);
+
+    /* Re-create the URL with the resolved IP */
+    addr_str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr));
+    resolved_url = g_strdup_printf ("ldap://%s";, addr_str);
+    rc = ldap_initialize (&closure->ldap, resolved_url);
 
     if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
         g_task_return_error (task, g_steal_pointer (&error));
@@ -625,36 +626,6 @@ once_resolved_start_connect (SeahorseLDAPSource *self,
     g_source_attach (gsource, g_main_context_default ());
 }
 
-#ifdef WITH_SOUP
-
-static void
-on_address_resolved_complete (SoupAddress *address,
-                              guint status,
-                              gpointer user_data)
-{
-    g_autoptr(GTask) task = user_data;
-    SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (g_task_get_source_object (task));
-    GCancellable *cancellable = g_task_get_cancellable (task);
-    g_autofree char *server = NULL;
-
-    g_object_get (self, "key-server", &server, NULL);
-    g_return_if_fail (server && server[0]);
-    seahorse_progress_update (cancellable, task, _("Connecting to: %s"), server);
-
-    /* DNS failed */
-    if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
-        g_task_return_new_error (task, SEAHORSE_ERROR, -1,
-                                 _("Couldn’t resolve address: %s"),
-                                 soup_address_get_name (address));
-        return;
-    }
-
-    /* Yay resolved */
-    once_resolved_start_connect (self, task, soup_address_get_physical (address));
-}
-
-#endif /* WITH_SOUP */
-
 static void
 seahorse_ldap_source_connect_async (SeahorseLDAPSource *source,
                                     GCancellable *cancellable,
@@ -663,11 +634,10 @@ seahorse_ldap_source_connect_async (SeahorseLDAPSource *source,
 {
     g_autoptr(GTask) task = NULL;
     ConnectClosure *closure;
-    g_autofree char *server = NULL;
-    char *pos;
-#ifdef WITH_SOUP
-    SoupAddress *address = NULL;
-#endif
+    g_autofree char *uri = NULL;
+    g_autoptr(GSocketConnectable) addr = NULL;
+    g_autoptr(GSocketAddressEnumerator) addr_enumer = NULL;
+    g_autoptr(GError) error = NULL;
 
     task = g_task_new (source, cancellable, callback, user_data);
     g_task_set_source_tag (task, seahorse_ldap_source_connect_async);
@@ -675,28 +645,28 @@ seahorse_ldap_source_connect_async (SeahorseLDAPSource *source,
     closure = g_new0 (ConnectClosure, 1);
     g_task_set_task_data (task, closure, connect_closure_free);
 
-    g_object_get (source, "key-server", &server, NULL);
-    g_return_if_fail (server && server[0]);
-    if ((pos = strchr (server, ':')) != NULL)
-        *pos = 0;
+    /* Take the URI & turn it into a GNetworkAddress, to do address resolving */
+    uri = seahorse_place_get_uri (SEAHORSE_PLACE (source));
+    g_return_if_fail (uri && uri[0]);
+
+    addr = g_network_address_parse_uri (uri, LDAP_PORT, &error);
+    if (!addr) {
+      g_task_return_new_error (task, SEAHORSE_ERROR, -1,
+                               _("Invalid URI: %s"), uri);
+      return;
+    }
 
     seahorse_progress_prep_and_begin (cancellable, task, NULL);
 
-    /* If we have libsoup, try and resolve asynchronously */
-#ifdef WITH_SOUP
-    address = soup_address_new (server, LDAP_PORT);
+    /* Now get a GSocketAddressEnumerator to do the resolving */
     seahorse_progress_update (cancellable, task,
-                              _("Resolving server address: %s"), server);
+                              _("Resolving server address: %s"), uri);
 
-    soup_address_resolve_async (address, NULL, cancellable,
-                                on_address_resolved_complete,
-                                g_steal_pointer (&task));
-    g_object_unref (address);
-#else /* !WITH_SOUP */
-
-    once_resolved_start_connect (source, task, server);
-
-#endif
+    addr_enumer = g_socket_connectable_enumerate (addr);
+    g_socket_address_enumerator_next_async (addr_enumer,
+                                            cancellable,
+                                            on_address_resolved,
+                                            g_steal_pointer (&task));
 }
 
 static LDAP *
@@ -1374,13 +1344,12 @@ seahorse_ldap_source_class_init (SeahorseLDAPSourceClass *klass)
  *
  * Returns: A new LDAP Key Source
  */
-SeahorseLDAPSource*
-seahorse_ldap_source_new (const char* uri, const char *host)
+SeahorseLDAPSource *
+seahorse_ldap_source_new (const char *uri)
 {
     g_return_val_if_fail (seahorse_ldap_is_valid_uri (uri), NULL);
-    g_return_val_if_fail (host && *host, NULL);
-    return g_object_new (SEAHORSE_TYPE_LDAP_SOURCE, "key-server", host,
-                         "uri", uri, NULL);
+
+    return g_object_new (SEAHORSE_TYPE_LDAP_SOURCE, "uri", uri, NULL);
 }
 
 /**
diff --git a/pgp/seahorse-ldap-source.h b/pgp/seahorse-ldap-source.h
index f338c67a..37996051 100644
--- a/pgp/seahorse-ldap-source.h
+++ b/pgp/seahorse-ldap-source.h
@@ -35,8 +35,7 @@ G_DECLARE_FINAL_TYPE (SeahorseLDAPSource, seahorse_ldap_source,
                       SEAHORSE, LDAP_SOURCE,
                       SeahorseServerSource)
 
-SeahorseLDAPSource*   seahorse_ldap_source_new     (const char *uri,
-                                                    const char *host);
+SeahorseLDAPSource*   seahorse_ldap_source_new     (const char *uri);
 
 gboolean              seahorse_ldap_is_valid_uri   (const char *uri);
 
diff --git a/pgp/seahorse-server-source.c b/pgp/seahorse-server-source.c
index 70201ffe..1c615a17 100644
--- a/pgp/seahorse-server-source.c
+++ b/pgp/seahorse-server-source.c
@@ -48,7 +48,6 @@ enum {
     PROP_DESCRIPTION,
     PROP_ICON,
     PROP_CATEGORY,
-    PROP_KEY_SERVER,
     PROP_URI,
     PROP_ACTIONS,
     PROP_ACTION_PREFIX,
@@ -101,15 +100,10 @@ seahorse_server_source_class_init (SeahorseServerSourceClass *klass)
        g_object_class_override_property (gobject_class, PROP_MENU_MODEL, "menu-model");
     g_object_class_override_property (gobject_class, PROP_SHOW_IF_EMPTY, "show-if-empty");
 
-    g_object_class_install_property (gobject_class, PROP_KEY_SERVER,
-            g_param_spec_string ("key-server", "Key Server",
-                                 "Key Server to search on", "",
-                                 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-
     g_object_class_install_property (gobject_class, PROP_URI,
             g_param_spec_string ("uri", "Key Server URI",
                                  "Key Server full URI", "",
-                                 G_PARAM_READWRITE));
+                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -235,16 +229,6 @@ seahorse_server_source_place_iface (SeahorsePlaceIface *iface)
     iface->get_show_if_empty = seahorse_server_source_get_show_if_empty;
 }
 
-/**
-* object: A SeahorseServerSource object
-* prop_id: The ID of the property to set
-* value: The value to set
-* pspec: ignored
-*
-* Properties that can be set:
-* PROP_KEY_SERVER, PROP_URI
-*
-**/
 static void
 seahorse_server_set_property (GObject *object, guint prop_id,
                               const GValue *value, GParamSpec *pspec)
@@ -258,11 +242,6 @@ seahorse_server_set_property (GObject *object, guint prop_id,
         seahorse_server_source_set_label (SEAHORSE_PLACE (ssrc),
                                           g_value_get_boxed (value));
         break;
-    case PROP_KEY_SERVER:
-        g_assert (priv->server == NULL);
-        priv->server = g_strdup (g_value_get_string (value));
-        g_return_if_fail (priv->server && priv->server[0]);
-        break;
     case PROP_URI:
         g_free (priv->uri);
         priv->uri = g_strdup (g_value_get_string (value));
@@ -273,16 +252,6 @@ seahorse_server_set_property (GObject *object, guint prop_id,
     }
 }
 
-/**
-* object: A #SeahorseServerSource object
-* prop_id: The id of the property
-* value: The value to get
-* pspec: ignored
-*
-* The properties that can be read are:
-* PROP_KEY_SERVER, PROP_URI
-*
-**/
 static void
 seahorse_server_get_property (GObject *obj,
                               guint prop_id,
@@ -291,16 +260,11 @@ seahorse_server_get_property (GObject *obj,
 {
        SeahorseServerSource *self = SEAHORSE_SERVER_SOURCE (obj);
        SeahorsePlace *place = SEAHORSE_PLACE (self);
-    SeahorseServerSourcePrivate *priv =
-        seahorse_server_source_get_instance_private (self);
 
        switch (prop_id) {
        case PROP_LABEL:
                g_value_take_string (value, seahorse_server_source_get_label (place));
                break;
-       case PROP_KEY_SERVER:
-               g_value_set_string (value, priv->server);
-               break;
        case PROP_DESCRIPTION:
                g_value_take_string (value, seahorse_server_source_get_description (place));
                break;
@@ -359,124 +323,53 @@ seahorse_server_source_collection_init (GcrCollectionIface *iface)
        iface->contains = seahorse_server_source_contains;
 }
 
-/* --------------------------------------------------------------------------
- * METHODS
- */
-
-/**
-* uri: the uri to parse
-* scheme: the scheme ("http") of this uri
-* host: the host part of the uri
-*
-*
-* Code adapted from GnuPG (file g10/keyserver.c)
-*
-* Returns FALSE if the separation failed
-**/
-static gboolean
-parse_keyserver_uri (char *uri, const char **scheme, const char **host)
-{
-    int assume_ldap = 0;
-
-    g_assert (uri != NULL);
-    g_assert (scheme != NULL && host != NULL);
-
-    *scheme = NULL;
-    *host = NULL;
-
-    /* Get the scheme */
-
-    *scheme = strsep(&uri, ":");
-    if (uri == NULL) {
-        /* Assume LDAP if there is no scheme */
-        assume_ldap = 1;
-        uri = (char*)*scheme;
-        *scheme = "ldap";
-    }
-
-    if (assume_ldap || (uri[0] == '/' && uri[1] == '/')) {
-        /* Two slashes means network path. */
-
-        /* Skip over the "//", if any */
-        if (!assume_ldap)
-            uri += 2;
-
-        /* Get the host */
-        *host = strsep (&uri, "/");
-        if (*host[0] == '\0')
-            return FALSE;
-    }
-
-    if (*scheme[0] == '\0')
-        return FALSE;
-
-    return TRUE;
-}
-
 /**
  * seahorse_server_source_new:
- * @server: The server uri to create an object for
+ * @uri: The server URI to create an object for
  *
- * Creates a #SeahorseServerSource object out of @server. Depending
+ * Creates a #SeahorseServerSource object out of @uri. Depending
  * on the defines at compilation time other sources are supported
  * (ldap, hkp)
  *
- * Returns: A new SeahorseServerSource or NULL
+ * Returns: A new #SeahorseServerSource or %NULL
  */
 SeahorseServerSource*
-seahorse_server_source_new (const gchar *server)
+seahorse_server_source_new (const char *uri)
 {
-    SeahorseServerSource *ssrc = NULL;
-    const gchar *scheme;
-    const gchar *host;
-    gchar *uri, *t;
-
-    g_return_val_if_fail (server && server[0], NULL);
+    g_autofree char *scheme = NULL;
 
-    uri = g_strdup (server);
+    g_return_val_if_fail (uri && *uri, NULL);
 
-    if (!parse_keyserver_uri (uri, &scheme, &host)) {
-        g_warning ("invalid uri passed: %s", server);
-
-
-    } else {
+    scheme = g_uri_parse_scheme (uri);
+    if (!scheme) {
+        g_warning ("invalid uri passed (no scheme): %s", uri);
+               return NULL;
+    }
 
 #ifdef WITH_LDAP
     /* LDAP Uris */
     if (g_ascii_strcasecmp (scheme, "ldap") == 0)
-        ssrc = SEAHORSE_SERVER_SOURCE (seahorse_ldap_source_new (server, host));
-    else
+        return SEAHORSE_SERVER_SOURCE (seahorse_ldap_source_new (uri));
 #endif /* WITH_LDAP */
 
 #ifdef WITH_HKP
     /* HKP Uris */
     if (g_ascii_strcasecmp (scheme, "hkp") == 0 ||
-        g_ascii_strcasecmp (scheme, "hkps") == 0) {
+        g_ascii_strcasecmp (scheme, "hkps") == 0)
 
-        ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
+        return SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (uri));
 
     /* HTTP Uris */
-    } else if (g_ascii_strcasecmp (scheme, "http") == 0 ||
-               g_ascii_strcasecmp (scheme, "https") == 0) {
-
-        /* If already have a port */
-        if (strchr (host, ':'))
-            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, host));
+    if (g_ascii_strcasecmp (scheme, "http") == 0 ||
+        g_ascii_strcasecmp (scheme, "https") == 0) {
 
-        /* No port make sure to use defaults */
-        else {
-            t = g_strdup_printf ("%s:%d", host, (g_ascii_strcasecmp (scheme, "http") == 0) ? 80 : 443);
-            ssrc = SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (server, t));
-            g_free (t);
-        }
-
-    } else
-#endif /* WITH_HKP */
-        g_message ("unsupported key server uri scheme: %s", scheme);
+        /* FIXME: if no port given, use port 80/443 */
+        return SEAHORSE_SERVER_SOURCE (seahorse_hkp_source_new (uri));
     }
+#endif /* WITH_HKP */
 
-    g_free (uri);
-    return ssrc;
+    g_warning ("unsupported key server uri scheme: %s", scheme);
+    return NULL;
 }
 
 void
diff --git a/pgp/seahorse-server-source.h b/pgp/seahorse-server-source.h
index bbdc4c2e..47ac232c 100644
--- a/pgp/seahorse-server-source.h
+++ b/pgp/seahorse-server-source.h
@@ -83,7 +83,7 @@ struct _SeahorseServerSourceClass {
                                                  GError **error);
 };
 
-SeahorseServerSource*  seahorse_server_source_new              (const gchar *uri);
+SeahorseServerSource*  seahorse_server_source_new              (const char *uri);
 
 void                   seahorse_server_source_search_async     (SeahorseServerSource *self,
                                                                 const gchar *match,


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