[libsoup] SoupServer: add :http-aliases and :https-aliases properties



commit 4a8b1c0544b168b25cbd3b016b7aedee263515ba
Author: Dan Winship <danw gnome org>
Date:   Sun Aug 25 10:51:22 2013 -0400

    SoupServer: add :http-aliases and :https-aliases properties
    
    Add :http-aliases and :https-aliases properties to SoupServer, to
    allow it to handle requests like:
    
        GET daap://host:port/path HTTP/1.1
    
    https://bugzilla.gnome.org/show_bug.cgi?id=703694

 libsoup/soup-message-server-io.c |    5 +-
 libsoup/soup-misc-private.h      |    2 +
 libsoup/soup-server.c            |  117 +++++++++++++++++++++++++++++++++-
 libsoup/soup-server.h            |    2 +
 libsoup/soup-session.c           |   54 ++--------------
 libsoup/soup-uri.c               |   43 +++++++++++++
 tests/server-test.c              |  130 +++++++++++++++++++++++++++++++++++++-
 7 files changed, 298 insertions(+), 55 deletions(-)
---
diff --git a/libsoup/soup-message-server-io.c b/libsoup/soup-message-server-io.c
index 814ae7e..2647b81 100644
--- a/libsoup/soup-message-server-io.c
+++ b/libsoup/soup-message-server-io.c
@@ -98,10 +98,7 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
 
        g_free (req_path);
 
-       if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
-               /* certainly not "a valid host on the server" (RFC2616 5.2.3)
-                * SOUP_URI_VALID_FOR_HTTP also guards against uri == NULL
-                */
+       if (!uri || !uri->host) {
                if (uri)
                        soup_uri_free (uri);
                return SOUP_STATUS_BAD_REQUEST;
diff --git a/libsoup/soup-misc-private.h b/libsoup/soup-misc-private.h
index 0276870..e9c83f6 100644
--- a/libsoup/soup-misc-private.h
+++ b/libsoup/soup-misc-private.h
@@ -13,6 +13,8 @@
 char *uri_decoded_copy (const char *str, int length, int *decoded_length);
 char *soup_uri_to_string_internal (SoupURI *uri, gboolean just_path_and_query,
                                   gboolean force_port);
+gboolean soup_uri_is_http (SoupURI *uri, char **aliases);
+gboolean soup_uri_is_https (SoupURI *uri, char **aliases);
 
 guint soup_socket_handshake_sync  (SoupSocket         *sock,
                                   GCancellable       *cancellable);
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index ff51e5b..abda60b 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -14,6 +14,7 @@
 #include "soup-server.h"
 #include "soup.h"
 #include "soup-message-private.h"
+#include "soup-misc-private.h"
 #include "soup-path-map.h" 
 
 /**
@@ -105,6 +106,8 @@ typedef struct {
        GSList            *auth_domains;
 
        GMainContext      *async_context;
+
+       char             **http_aliases, **https_aliases;
 } SoupServerPrivate;
 #define SOUP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SERVER, SoupServerPrivate))
 
@@ -121,6 +124,8 @@ enum {
        PROP_ASYNC_CONTEXT,
        PROP_RAW_PATHS,
        PROP_SERVER_HEADER,
+       PROP_HTTP_ALIASES,
+       PROP_HTTPS_ALIASES,
 
        LAST_PROP
 };
@@ -141,6 +146,10 @@ soup_server_init (SoupServer *server)
        SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
 
        priv->handlers = soup_path_map_new ((GDestroyNotify)free_handler);
+
+       priv->http_aliases = g_new (char *, 2);
+       priv->http_aliases[0] = (char *)g_intern_string ("*");
+       priv->http_aliases[1] = NULL;
 }
 
 static void
@@ -191,6 +200,9 @@ soup_server_finalize (GObject *object)
        g_clear_pointer (&priv->loop, g_main_loop_unref);
        g_clear_pointer (&priv->async_context, g_main_context_unref);
 
+       g_free (priv->http_aliases);
+       g_free (priv->https_aliases);
+
        G_OBJECT_CLASS (soup_server_parent_class)->finalize (object);
 }
 
@@ -250,6 +262,29 @@ soup_server_constructor (GType                  type,
        return server;
 }
 
+/* priv->http_aliases and priv->https_aliases are stored as arrays of
+ * *interned* strings, so we can't just use g_strdupv() to set them.
+ */
+static void
+set_aliases (char ***variable, char **value)
+{
+       int len, i;
+
+       if (*variable)
+               g_free (*variable);
+
+       if (!value) {
+               *variable = NULL;
+               return;
+       }
+
+       len = g_strv_length (value);
+       *variable = g_new (char *, len + 1);
+       for (i = 0; i < len; i++)
+               (*variable)[i] = (char *)g_intern_string (value[i]);
+       (*variable)[i] = NULL;
+}
+
 static void
 soup_server_set_property (GObject *object, guint prop_id,
                          const GValue *value, GParamSpec *pspec)
@@ -304,6 +339,12 @@ soup_server_set_property (GObject *object, guint prop_id,
                } else
                        priv->server_header = g_strdup (header);
                break;
+       case PROP_HTTP_ALIASES:
+               set_aliases (&priv->http_aliases, g_value_get_boxed (value));
+               break;
+       case PROP_HTTPS_ALIASES:
+               set_aliases (&priv->https_aliases, g_value_get_boxed (value));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -341,6 +382,12 @@ soup_server_get_property (GObject *object, guint prop_id,
        case PROP_SERVER_HEADER:
                g_value_set_string (value, priv->server_header);
                break;
+       case PROP_HTTP_ALIASES:
+               g_value_set_boxed (value, priv->http_aliases);
+               break;
+       case PROP_HTTPS_ALIASES:
+               g_value_set_boxed (value, priv->https_aliases);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -628,6 +675,68 @@ soup_server_class_init (SoupServerClass *server_class)
                                     "Server header",
                                     NULL,
                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+       /**
+        * SoupServer:http-aliases:
+        *
+        * A %NULL-terminated array of URI schemes that should be
+        * considered to be aliases for "http". Eg, if this included
+        * <literal>"dav"</literal>, than a URI of
+        * <literal>dav://example.com/path</literal> would be treated
+        * identically to <literal>http://example.com/path</literal>.
+        * In particular, this is needed in cases where a client
+        * sends requests with absolute URIs, where those URIs do
+        * not use "http:".
+        *
+        * The default value is an array containing the single element
+        * <literal>"*"</literal>, a special value which means that
+        * any scheme except "https" is considered to be an alias for
+        * "http".
+        *
+        * See also #SoupServer:https-aliases.
+        *
+        * Since: 2.44
+        */
+       /**
+        * SOUP_SERVERI_HTTP_ALIASES:
+        *
+        * Alias for the #SoupServer:http-aliases property, qv.
+        *
+        * Since: 2.44
+        */
+       g_object_class_install_property (
+               object_class, PROP_HTTP_ALIASES,
+               g_param_spec_boxed (SOUP_SERVER_HTTP_ALIASES,
+                                   "http aliases",
+                                   "URI schemes that are considered aliases for 'http'",
+                                   G_TYPE_STRV,
+                                   G_PARAM_READWRITE));
+       /**
+        * SoupServer:https-aliases:
+        *
+        * A comma-delimited list of URI schemes that should be
+        * considered to be aliases for "https". See
+        * #SoupServer:http-aliases for more information.
+        *
+        * The default value is %NULL, meaning that no URI schemes
+        * are considered aliases for "https".
+        *
+        * Since: 2.44
+        */
+       /**
+        * SOUP_SERVER_HTTPS_ALIASES:
+        *
+        * Alias for the #SoupServer:https-aliases property, qv.
+        *
+        * Since: 2.44
+        **/
+       g_object_class_install_property (
+               object_class, PROP_HTTPS_ALIASES,
+               g_param_spec_boxed (SOUP_SERVER_HTTPS_ALIASES,
+                                   "https aliases",
+                                   "URI schemes that are considered aliases for 'https'",
+                                   G_TYPE_STRV,
+                                   G_PARAM_READWRITE));
 }
 
 /**
@@ -816,10 +925,16 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
        gboolean rejected = FALSE;
        char *auth_user;
 
+       uri = soup_message_get_uri (msg);
+       if ((soup_server_is_https (server) && !soup_uri_is_https (uri, priv->https_aliases)) ||
+           (!soup_server_is_https (server) && !soup_uri_is_http (uri, priv->http_aliases))) {
+               soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+               return;
+       }
+
        if (!priv->raw_paths) {
                char *decoded_path;
 
-               uri = soup_message_get_uri (msg);
                decoded_path = soup_uri_decode (uri->path);
 
                if (strstr (decoded_path, "/../") ||
diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h
index e1c9bbf..0d09322 100644
--- a/libsoup/soup-server.h
+++ b/libsoup/soup-server.h
@@ -64,6 +64,8 @@ typedef void (*SoupServerCallback) (SoupServer        *server,
 #define SOUP_SERVER_ASYNC_CONTEXT   "async-context"
 #define SOUP_SERVER_RAW_PATHS       "raw-paths"
 #define SOUP_SERVER_SERVER_HEADER   "server-header"
+#define SOUP_SERVER_HTTP_ALIASES    "http-aliases"
+#define SOUP_SERVER_HTTPS_ALIASES   "https-aliases"
 
 SoupServer        *soup_server_new            (const char            *optname1,
                                               ...) G_GNUC_NULL_TERMINATED;
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 10851b3..85ef1b2 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -831,49 +831,6 @@ soup_session_new_with_options (const char *optname1,
        return session;
 }
 
-static gboolean
-uri_is_http (SoupSessionPrivate *priv, SoupURI *uri)
-{
-       int i;
-
-       if (uri->scheme == SOUP_URI_SCHEME_HTTP)
-               return TRUE;
-       else if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
-               return FALSE;
-       else if (!priv->http_aliases)
-               return FALSE;
-
-       for (i = 0; priv->http_aliases[i]; i++) {
-               if (uri->scheme == priv->http_aliases[i])
-                       return TRUE;
-       }
-
-       if (!priv->http_aliases[1] && !strcmp (priv->http_aliases[0], "*"))
-               return TRUE;
-       else
-               return FALSE;
-}
-
-static gboolean
-uri_is_https (SoupSessionPrivate *priv, SoupURI *uri)
-{
-       int i;
-
-       if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
-               return TRUE;
-       else if (uri->scheme == SOUP_URI_SCHEME_HTTP)
-               return FALSE;
-       else if (!priv->https_aliases)
-               return FALSE;
-
-       for (i = 0; priv->https_aliases[i]; i++) {
-               if (uri->scheme == priv->https_aliases[i])
-                       return TRUE;
-       }
-
-       return FALSE;
-}
-
 /**
  * soup_session_get_async_context:
  * @session: a #SoupSession
@@ -945,7 +902,7 @@ soup_session_host_new (SoupSession *session, SoupURI *uri)
            host->uri->scheme != SOUP_URI_SCHEME_HTTPS) {
                SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
 
-               if (uri_is_https (priv, host->uri))
+               if (soup_uri_is_https (host->uri, priv->https_aliases))
                        host->uri->scheme = SOUP_URI_SCHEME_HTTPS;
                else
                        host->uri->scheme = SOUP_URI_SCHEME_HTTP;
@@ -969,7 +926,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri)
        SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
        SoupSessionHost *host;
 
-       if (uri_is_https (priv, uri))
+       if (soup_uri_is_https (uri, priv->https_aliases))
                host = g_hash_table_lookup (priv->https_hosts, uri);
        else
                host = g_hash_table_lookup (priv->http_hosts, uri);
@@ -978,7 +935,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri)
 
        host = soup_session_host_new (session, uri);
 
-       if (uri_is_https (priv, uri))
+       if (soup_uri_is_https (uri, priv->https_aliases))
                g_hash_table_insert (priv->https_hosts, host->uri, host);
        else
                g_hash_table_insert (priv->http_hosts, host->uri, host);
@@ -1080,7 +1037,8 @@ soup_session_would_redirect (SoupSession *session, SoupMessage *msg)
        if (!new_uri)
                return FALSE;
        if (!new_uri->host || !*new_uri->host ||
-           (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri))) {
+           (!soup_uri_is_http (new_uri, priv->http_aliases) &&
+            !soup_uri_is_https (new_uri, priv->https_aliases))) {
                soup_uri_free (new_uri);
                return FALSE;
        }
@@ -1691,7 +1649,7 @@ get_connection_for_host (SoupSession *session,
                SOUP_TYPE_CONNECTION,
                SOUP_CONNECTION_REMOTE_URI, host->uri,
                SOUP_CONNECTION_PROXY_RESOLVER, priv->proxy_resolver,
-               SOUP_CONNECTION_SSL, uri_is_https (priv, soup_message_get_uri (item->msg)),
+               SOUP_CONNECTION_SSL, soup_uri_is_https (soup_message_get_uri (item->msg), 
priv->https_aliases),
                SOUP_CONNECTION_SSL_CREDENTIALS, priv->tlsdb,
                SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict && (priv->tlsdb != NULL || SOUP_IS_PLAIN_SESSION 
(session)),
                SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c
index 6e3d220..808d2cc 100644
--- a/libsoup/soup-uri.c
+++ b/libsoup/soup-uri.c
@@ -1291,4 +1291,47 @@ soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
        return g_ascii_strcasecmp (one->host, two->host) == 0;
 }
 
+gboolean
+soup_uri_is_http (SoupURI *uri, char **aliases)
+{
+       int i;
+
+       if (uri->scheme == SOUP_URI_SCHEME_HTTP)
+               return TRUE;
+       else if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
+               return FALSE;
+       else if (!aliases)
+               return FALSE;
+
+       for (i = 0; aliases[i]; i++) {
+               if (uri->scheme == aliases[i])
+                       return TRUE;
+       }
+
+       if (!aliases[1] && !strcmp (aliases[0], "*"))
+               return TRUE;
+       else
+               return FALSE;
+}
+
+gboolean
+soup_uri_is_https (SoupURI *uri, char **aliases)
+{
+       int i;
+
+       if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
+               return TRUE;
+       else if (uri->scheme == SOUP_URI_SCHEME_HTTP)
+               return FALSE;
+       else if (!aliases)
+               return FALSE;
+
+       for (i = 0; aliases[i]; i++) {
+               if (uri->scheme == aliases[i])
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
 G_DEFINE_BOXED_TYPE (SoupURI, soup_uri, soup_uri_copy, soup_uri_free)
diff --git a/tests/server-test.c b/tests/server-test.c
index 3cad5c5..3ff7cad 100644
--- a/tests/server-test.c
+++ b/tests/server-test.c
@@ -5,8 +5,8 @@
 
 #include "test-utils.h"
 
-SoupServer *server;
-SoupURI *base_uri;
+SoupServer *server, *ssl_server;
+SoupURI *base_uri, *ssl_base_uri;
 
 static void
 server_callback (SoupServer *server, SoupMessage *msg,
@@ -120,6 +120,109 @@ do_star_test (void)
 }
 
 static void
+do_one_server_aliases_test (SoupURI    *uri,
+                           const char *alias,
+                           gboolean    succeed)
+{
+       GSocketClient *client;
+       GSocketConnectable *addr;
+       GSocketConnection *conn;
+       GInputStream *in;
+       GOutputStream *out;
+       GError *error = NULL;
+       GString *req;
+       static char buf[1024];
+
+       debug_printf (1, "  %s via %s\n", alias, uri->scheme);
+
+       /* There's no way to make libsoup's client side send an absolute
+        * URI (to a non-proxy server), so we have to fake this.
+        */
+
+       client = g_socket_client_new ();
+       if (uri->scheme == SOUP_URI_SCHEME_HTTPS) {
+               g_socket_client_set_tls (client, TRUE);
+               g_socket_client_set_tls_validation_flags (client, 0);
+       }
+       addr = g_network_address_new (uri->host, uri->port);
+
+       conn = g_socket_client_connect (client, addr, NULL, &error);
+       g_object_unref (addr);
+       g_object_unref (client);
+       if (!conn) {
+               debug_printf (1, "    error connecting to server: %s\n",
+                             error->message);
+               g_error_free (error);
+               errors++;
+               return;
+       }
+
+       in = g_io_stream_get_input_stream (G_IO_STREAM (conn));
+       out = g_io_stream_get_output_stream (G_IO_STREAM (conn));
+
+       req = g_string_new (NULL);
+       g_string_append_printf (req, "GET %s://%s:%d HTTP/1.1\r\n",
+                               alias, uri->host, uri->port);
+       g_string_append_printf (req, "Host: %s:%d\r\n",
+                               uri->host, uri->port);
+       g_string_append (req, "Connection: close\r\n\r\n");
+
+       if (!g_output_stream_write_all (out, req->str, req->len, NULL, NULL, &error)) {
+               debug_printf (1, "    error sending request: %s\n",
+                             error->message);
+               g_error_free (error);
+               errors++;
+               g_object_unref (conn);
+               g_string_free (req, TRUE);
+               return;
+       }
+       g_string_free (req, TRUE);
+
+       if (!g_input_stream_read_all (in, buf, sizeof (buf), NULL, NULL, &error)) {
+               debug_printf (1, "    error reading response: %s\n",
+                             error->message);
+               g_error_free (error);
+               errors++;
+               g_object_unref (conn);
+               return;
+       }
+
+       if ((succeed && !g_str_has_prefix (buf, "HTTP/1.1 200 ")) ||
+           (!succeed && !g_str_has_prefix (buf, "HTTP/1.1 400 "))) {
+               debug_printf (1, "    unexpected response: %.*s\n",
+                             (int) strcspn (buf, "\r\n"), buf);
+               errors++;
+       }
+
+       g_io_stream_close (G_IO_STREAM (conn), NULL, NULL);
+       g_object_unref (conn);
+}
+
+static void
+do_server_aliases_test (void)
+{
+       char *http_good[] = { "http", "dav", NULL };
+       char *http_bad[] = { "https", "davs", "fred", NULL };
+       char *https_good[] = { "https", "davs", NULL };
+       char *https_bad[] = { "http", "dav", "fred", NULL };
+       int i;
+
+       debug_printf (1, "\nserver aliases test\n");
+
+       for (i = 0; http_good[i]; i++)
+               do_one_server_aliases_test (base_uri, http_good[i], TRUE);
+       for (i = 0; http_bad[i]; i++)
+               do_one_server_aliases_test (base_uri, http_bad[i], FALSE);
+
+       if (tls_available) {
+               for (i = 0; https_good[i]; i++)
+                       do_one_server_aliases_test (ssl_base_uri, https_good[i], TRUE);
+               for (i = 0; https_bad[i]; i++)
+                       do_one_server_aliases_test (ssl_base_uri, https_bad[i], FALSE);
+       }
+}
+
+static void
 do_dot_dot_test (void)
 {
        SoupSession *session;
@@ -232,6 +335,9 @@ do_ipv6_test (void)
 int
 main (int argc, char **argv)
 {
+       char *http_aliases[] = { "dav", NULL };
+       char *https_aliases[] = { "davs", NULL };
+
        test_init (argc, argv, NULL);
 
        server = soup_test_server_new (TRUE);
@@ -239,13 +345,33 @@ main (int argc, char **argv)
        base_uri = soup_uri_new ("http://127.0.0.1/";);
        soup_uri_set_port (base_uri, soup_server_get_port (server));
 
+       g_object_set (G_OBJECT (server),
+                     SOUP_SERVER_HTTP_ALIASES, http_aliases,
+                     NULL);
+
+       if (tls_available) {
+               ssl_server = soup_test_server_new_ssl (TRUE);
+               soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL);
+               ssl_base_uri = soup_uri_new ("https://127.0.0.1/";);
+               soup_uri_set_port (ssl_base_uri, soup_server_get_port (ssl_server));
+               g_object_set (G_OBJECT (ssl_server),
+                             SOUP_SERVER_HTTPS_ALIASES, https_aliases,
+                             NULL);
+       }
+
        do_star_test ();
+       do_server_aliases_test ();
        do_dot_dot_test ();
        do_ipv6_test ();
 
        soup_uri_free (base_uri);
        soup_test_server_quit_unref (server);
 
+       if (tls_available) {
+               soup_uri_free (ssl_base_uri);
+               soup_test_server_quit_unref (ssl_server);
+       }
+
        test_cleanup ();
        return errors != 0;
 }


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