diff options
author | Dan Winship <danw@gnome.org> | 2013-08-25 10:51:22 -0400 |
---|---|---|
committer | Dan Winship <danw@gnome.org> | 2013-08-26 11:00:42 -0400 |
commit | 4a8b1c0544b168b25cbd3b016b7aedee263515ba (patch) | |
tree | 823f0e9fbc26d311aa3fb6e16f80684407ecb86f | |
parent | 3935e102d8195ec12349e688b87e200df4f33649 (diff) | |
download | libsoup-4a8b1c0544b168b25cbd3b016b7aedee263515ba.tar.gz |
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
-rw-r--r-- | libsoup/soup-message-server-io.c | 5 | ||||
-rw-r--r-- | libsoup/soup-misc-private.h | 2 | ||||
-rw-r--r-- | libsoup/soup-server.c | 117 | ||||
-rw-r--r-- | libsoup/soup-server.h | 2 | ||||
-rw-r--r-- | libsoup/soup-session.c | 54 | ||||
-rw-r--r-- | libsoup/soup-uri.c | 43 | ||||
-rw-r--r-- | 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 814ae7e5..2647b811 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 02768706..e9c83f61 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 ff51e5bd..abda60ba 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 e1c9bbfb..0d09322e 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 10851b3e..85ef1b25 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 6e3d220b..808d2ccc 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 3cad5c52..3ff7cadd 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; } |