diff options
author | Xan Lopez <xan@gnome.org> | 2009-07-23 16:26:51 +0300 |
---|---|---|
committer | Xan Lopez <xan@gnome.org> | 2009-07-23 16:26:51 +0300 |
commit | 698d9017fb7feab33ed4a36689bb3b86e2b7da80 (patch) | |
tree | a98b0ef7007ba86a7ac9763d640b12d2f6ceb2ec | |
parent | 5e49d5438a07fdaed7cd58365c9c79548fc55805 (diff) | |
parent | fd5b785fbaad81f99c6c8b8bcb52028990e19120 (diff) | |
download | libsoup-698d9017fb7feab33ed4a36689bb3b86e2b7da80.tar.gz |
Merge branch 'master' into cache
Conflicts:
libsoup/Makefile.am
libsoup/soup-session-private.h
libsoup/soup-session.c
libsoup/soup.h
54 files changed, 2861 insertions, 1081 deletions
@@ -69,6 +69,7 @@ tests/redirect-test tests/server-auth-test tests/simple-httpd tests/simple-proxy +tests/sniffing-test tests/ssl-test tests/streaming-test tests/timeout-test diff --git a/HACKING b/HACKING new file mode 100644 index 00000000..51269e51 --- /dev/null +++ b/HACKING @@ -0,0 +1,66 @@ +CODE STYLE +---------- + +Please use the style used by the rest of the code. Among other things, +this means: + + * Tabs, not spaces, for indentation + + * Put spaces: + * around binary operators + * between if/while/for/switch and "(" + * between function name and "(" + * between ")" and "{" + * after "," + + * if/for/while bodies: + + * Single-line bodies should (a) be on their own line, and (b) + not have braces around them + + * Multi-line bodies should have braces around them, even if + the body is only a single statement and the braces are not + syntactically necessary. + + * Eg: + + for (i = 0; i < len; i++) { + if (find (i, something)) + break; + else { + function_with_big_name (i, something, + something_else); + } + } + + * C89, not C99. (In particular, don't declare variables in the + middle of blocks.) + + * Do not use gint, gchar, glong, and gshort. (Other g-types, such + as gpointer and the unsigned types are fine.) + +CORRECTNESS +----------- + + * libsoup builds with lots of -W options by default, and should + not print any warnings while compiling (unless they're caused by + #included files from other projects, eg, proxy.h). You can use + "make > /dev/null" to do a full compile showing only the + warnings/errors, to make sure your patch does not introduce any + more. + + * There are a number of regression tests in the tests/ directory. + Running "make check" will run all of them (or at least, all of + the ones that it can run based on what software you have + installed. Eg, some tests require apache to be installed.) You + should run "make check" before submitting a patch that could + potentially change libsoup's behavior. ("make check" will warn + you if it was not able to run all of the tests. If you are + making extensive changes, or changing very low-level functions, + you may want to install all of the optional pieces so you can + run all of the regression tests.) + + * libsoup ought to build correctly from outside its source tree, + so if you make large changes to the Makefiles, try a "make + distcheck" to verify that an out-of-source-tree build still + works. @@ -1,3 +1,86 @@ +Changes in libsoup from 2.27.2 to 2.27.4: + + * Added SoupContentSniffer and the "content-sniffed" signal on + SoupMessage, to do Content-Type sniffing per the HTML5 / + draft-abarth-mime-sniff algorithm. [#572589, Gustavo Noronha + Silva] + + * Updated the earlier SoupSession timeout fixes ([#574414], + [#578928]) so that async connect() also times out [#588177, + Mark Nauwelaerts] and SSL works on Windows again [#587910, + Fridrich Strba]. + + * Fixed the behavior on a 301 response to a POST to match + real-world usage rather than what the spec says. (We were + doing the right thing on 302 and 303, but had missed 301.) + [#586692] + + * Changed configure so that if GNUTLS isn't found then it + errors out, rather than silently building an SSL-less + libsoup. Configure with --disable-ssl if you actually don't + want SSL. [#584955] + +Changes in libsoup from 2.27.1 to 2.27.2: + + * Replaced SoupProxyResolver with SoupProxyURIResolver, which + is a bit simpler, works with non-HTTP URIs (and so could be + used by gvfsd-ftp) and supports proxy auth correctly. + [#580051] + + * Fixed SoupSession to not try to resolve http server + hostnames when it's just going to pass the hostname off to a + proxy server anyway. This fixes things on hosts that use a + proxy for everything and have no working DNS config + [#577532] and also makes WebKitGTK behave more like other + browsers in terms of per-host connection limits (we now + limit connections based on hostname rather than on IP + address). + + We also no longer set the AI_CANONNAME flag when calling + getaddrinfo(), which saves us a little bit of unnecessary + network traffic. [Pointed out by Christophe Gillette on the + mailing list.] + + * libsoup now always uses SSL 3.0 (not TLS 1.0 or 1.1) for + https URIs, to work around problems with older servers that + don't implement the (apparently quite confusing) TLS/SSL + compatibility rules correctly. Makes a bunch of + previously-inaccessible sites now accessible in WebKitGTK + (notably PayPal) [#581342]. Will eventually be revisited, to + first try TLS 1.1 and fall back if that fails. + + * Fixed Digest auth to (recent) Apple CalDAV servers. + [#583091] + + * Changed the way the SoupSession "authenticate" signal works + a bit. We now never emit "authenticate" before sending a + request, even if we know for sure that it's going to fail, + because this makes the semantics of the authenticate handler + too complicated (and because we'll only get into this + situation if a previous call to the authenticate handler + failed anyway). Fixes problems in WebKitGTK when you cancel + a password dialog, and then later try to load the page + again. [#583462, mostly figured out by Gustavo Noronha + Silva]. + + * Fixed a bug in the CRLF-vs-LF patch (#571283) that caused + libsoup to fail to parse the response headers (returning + SOUP_STATUS_MALFORMED) if a CR LF got split across two + read()s. [#582002] + + * Allow using PUT in soup_form_request_for_data(), to work + with certain broken web APIs. [#581860, Ross Burton]. Also, + fixed a problem with empty POST bodies that made some parts + of gmail not work in WebKitGTK. + + * Applied some minor bugfixes to configure.in and autogen.sh + [#583911, #583942]. Fixed configure.in to not use gcc + warning options that the installed version of gcc doesn't + recognize [#578851]. + + * Added G_GNUC_NULL_TERMINATED and G_GNUC_PRINTF to a few + methods that should have had them. [#581754, Ross Burton] + Changes in libsoup from 2.26.1 to 2.27.1: * SOUP_SESSION_TIMEOUT now works properly with @@ -27,7 +110,7 @@ Changes in libsoup from 2.26.1 to 2.27.1: * Fix a crash when cancelling a message from a "restarted" handler, and updated a regression test to notice the - underlying cause. [#380193] + underlying cause. [#580193] * Completing the API updates for #576760 from 2.26.1, soup_message_headers_get() is now marked deprecated in favor @@ -5,7 +5,7 @@ srcdir=`dirname $0` test -z "$srcdir" && srcdir=. PKG_NAME="libsoup" -REQUIRED_AUTOMAKE_VERSION=1.6 +REQUIRED_AUTOMAKE_VERSION=1.9 (test -f $srcdir/configure.in \ && test -f $srcdir/libsoup.doap \ diff --git a/configure.in b/configure.in index 3ed1a4b1..60616052 100644 --- a/configure.in +++ b/configure.in @@ -3,7 +3,7 @@ dnl *** Initialize automake and set version *** dnl ******************************************* AC_PREREQ(2.53) -AC_INIT(libsoup, 2.27.1) +AC_INIT(libsoup, 2.27.4) AC_CONFIG_SRCDIR(libsoup-2.4.pc.in) AM_INIT_AUTOMAKE([foreign]) @@ -143,22 +143,23 @@ dnl ********************************** dnl Allow autogening even without AM_PATH_LIBGCRYPT available m4_ifdef([AM_PATH_LIBGCRYPT],,[m4_define(AM_PATH_LIBGCRYPT,)]) AC_ARG_ENABLE(ssl, - AS_HELP_STRING([--enable-ssl], [Turn on Secure Sockets Layer support (default=yes)]),, + AS_HELP_STRING([--disable-ssl], [Disable SSL/TLS support (not recommended)]),, enable_ssl=auto) +have_ssl=no if test "$enable_ssl" != "no"; then PKG_CHECK_MODULES(LIBGNUTLS, gnutls, [AM_PATH_LIBGCRYPT([], have_ssl=yes, have_ssl=no)], have_ssl=no) - if test "$have_ssl" = "yes"; then - AC_DEFINE(HAVE_SSL, 1, [Defined if you have SSL support]) - SSL_REQUIREMENT="gnutls" +fi +if test "$have_ssl" = "yes"; then + AC_DEFINE(HAVE_SSL, 1, [Defined if you have SSL support]) + SSL_REQUIREMENT="gnutls" +else + if test "$enable_ssl" = "no"; then + AC_MSG_WARN(Disabling SSL support); else - if test "$enable_ssl" = "auto"; then - AC_MSG_WARN(Disabling SSL support); - enable_ssl=no; - else - AC_MSG_ERROR(Could not configure SSL support); - fi + AC_MSG_ERROR([Could not configure SSL support. +Pass "--disable-ssl" if you really want to build without SSL support]); fi fi @@ -241,8 +242,20 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then -Wall -Wstrict-prototypes -Wmissing-declarations \ -Wmissing-prototypes -Wnested-externs -Wpointer-arith \ -Wdeclaration-after-statement -Wformat=2 -Winit-self \ - -Wmissing-include-dirs -Wundef -Waggregate-return \ - -Wmissing-format-attribute" + -Waggregate-return -Wmissing-format-attribute" + + for option in -Wmissing-include-dirs -Wundef; do + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $option" + AC_MSG_CHECKING([whether gcc understands $option]) + AC_TRY_COMPILE([], [], + has_option=yes, + has_option=no,) + AC_MSG_RESULT($has_option) + if test $has_option = no; then + CFLAGS="$SAVE_CFLAGS" + fi + done fi if test "$os_win32" != yes; then @@ -317,7 +330,7 @@ if test "$APACHE_HTTPD" != "no" -a -n "$APACHE_MODULE_DIR" -a -n "$APACHE_SSL_MO fi else have_apache=0 - if test "$APACHE_HTTPD" == "no" -o -z "$APACHE_MODULE_DIR"; then + if test "$APACHE_HTTPD" = "no" -o -z "$APACHE_MODULE_DIR"; then MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES apache" else MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES mod_ssl" diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index ef05b729..ad9a7359 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -56,6 +56,7 @@ soup_headers = \ soup-auth-domain-basic.h \ soup-auth-domain-digest.h \ soup-cache.h \ + soup-content-sniffer.h \ soup-cookie.h \ soup-cookie-jar.h \ soup-cookie-jar-text.h \ @@ -71,6 +72,7 @@ soup_headers = \ soup-multipart.h \ soup-portability.h \ soup-proxy-resolver.h \ + soup-proxy-uri-resolver.h \ soup-server.h \ soup-session.h \ soup-session-async.h \ @@ -120,6 +122,7 @@ libsoup_2_4_la_SOURCES = \ soup-cache.c \ soup-connection.h \ soup-connection.c \ + soup-content-sniffer.c \ soup-cookie.c \ soup-cookie-jar.c \ soup-cookie-jar-text.c \ @@ -148,6 +151,7 @@ libsoup_2_4_la_SOURCES = \ soup-proxy-resolver.c \ soup-proxy-resolver-static.h \ soup-proxy-resolver-static.c \ + soup-proxy-uri-resolver.c \ soup-server.c \ soup-session.c \ soup-session-async.c \ diff --git a/libsoup/soup-auth-digest.c b/libsoup/soup-auth-digest.c index 665bac76..4fb3f815 100644 --- a/libsoup/soup-auth-digest.c +++ b/libsoup/soup-auth-digest.c @@ -421,7 +421,7 @@ get_authorization (SoupAuth *auth, SoupMessage *msg) { SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); char response[33], *token; - char *url; + char *url, *algorithm; GString *out; SoupURI *uri; @@ -444,6 +444,10 @@ get_authorization (SoupAuth *auth, SoupMessage *msg) g_string_append (out, ", "); soup_header_g_string_append_param (out, "uri", url); g_string_append (out, ", "); + algorithm = soup_auth_digest_get_algorithm (priv->algorithm); + soup_header_g_string_append_param (out, "algorithm", algorithm); + g_free (algorithm); + g_string_append (out, ", "); soup_header_g_string_append_param (out, "response", response); if (priv->opaque) { diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c index 92ffae88..5d1d1bcd 100644 --- a/libsoup/soup-auth-manager.c +++ b/libsoup/soup-auth-manager.c @@ -16,9 +16,11 @@ #include "soup-headers.h" #include "soup-marshal.h" #include "soup-message-private.h" +#include "soup-message-queue.h" #include "soup-path-map.h" #include "soup-session.h" #include "soup-session-feature.h" +#include "soup-session-private.h" #include "soup-uri.h" static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); @@ -53,7 +55,7 @@ typedef struct { #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate)) typedef struct { - SoupAddress *addr; + SoupURI *uri; SoupPathMap *auth_realms; /* path -> scheme:realm */ GHashTable *auths; /* scheme:realm -> SoupAuth */ } SoupAuthHost; @@ -64,8 +66,8 @@ soup_auth_manager_init (SoupAuthManager *manager) SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); priv->auth_types = g_ptr_array_new (); - priv->auth_hosts = g_hash_table_new (soup_address_hash_by_name, - soup_address_equal_by_name); + priv->auth_hosts = g_hash_table_new (soup_uri_host_hash, + soup_uri_host_equal); } static gboolean @@ -78,7 +80,7 @@ foreach_free_host (gpointer key, gpointer value, gpointer data) if (host->auths) g_hash_table_destroy (host->auths); - g_object_unref (host->addr); + soup_uri_free (host->uri); g_slice_free (SoupAuthHost, host); return TRUE; @@ -318,15 +320,15 @@ static SoupAuthHost * get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg) { SoupAuthHost *host; - SoupAddress *addr = soup_message_get_address (msg); + SoupURI *uri = soup_message_get_uri (msg); - host = g_hash_table_lookup (priv->auth_hosts, addr); + host = g_hash_table_lookup (priv->auth_hosts, uri); if (host) return host; host = g_slice_new0 (SoupAuthHost); - host->addr = g_object_ref (addr); - g_hash_table_insert (priv->auth_hosts, host->addr, host); + host->uri = soup_uri_copy_host (uri); + g_hash_table_insert (priv->auth_hosts, host->uri, host); return host; } @@ -354,7 +356,7 @@ lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) static gboolean authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, SoupMessage *msg, gboolean prior_auth_failed, - gboolean proxy) + gboolean proxy, gboolean can_interact) { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupURI *uri; @@ -363,24 +365,32 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, return TRUE; if (proxy) { - g_object_get (G_OBJECT (priv->session), - SOUP_SESSION_PROXY_URI, &uri, - NULL); - /* FIXME: temporary workaround for proxy auth brokenness */ + SoupMessageQueue *queue; + SoupMessageQueueItem *item; + + queue = soup_session_get_queue (priv->session); + item = soup_message_queue_lookup (queue, msg); + if (item) { + uri = soup_connection_get_proxy_uri (item->conn); + soup_message_queue_item_unref (item); + } else + uri = NULL; + if (!uri) return FALSE; } else - uri = soup_uri_copy (soup_message_get_uri (msg)); + uri = soup_message_get_uri (msg); if (uri->password && !prior_auth_failed) { soup_auth_authenticate (auth, uri->user, uri->password); - soup_uri_free (uri); return TRUE; } - soup_uri_free (uri); - soup_auth_manager_emit_authenticate (manager, msg, auth, - prior_auth_failed); + if (can_interact) { + soup_auth_manager_emit_authenticate (manager, msg, auth, + prior_auth_failed); + } + return soup_auth_is_authenticated (auth); } @@ -449,7 +459,7 @@ update_auth (SoupMessage *msg, gpointer manager) /* If we need to authenticate, try to do it. */ authenticate_auth (manager, auth, msg, - prior_auth_failed, FALSE); + prior_auth_failed, FALSE, TRUE); } static void @@ -484,7 +494,7 @@ update_proxy_auth (SoupMessage *msg, gpointer manager) /* If we need to authenticate, try to do it. */ authenticate_auth (manager, priv->proxy_auth, msg, - prior_auth_failed, TRUE); + prior_auth_failed, TRUE, TRUE); } static void @@ -525,12 +535,12 @@ request_started (SoupSessionFeature *feature, SoupSession *session, SoupAuth *auth; auth = lookup_auth (priv, msg); - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE)) + if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) auth = NULL; soup_message_set_auth (msg, auth); auth = priv->proxy_auth; - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE)) + if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE)) auth = NULL; soup_message_set_proxy_auth (msg, auth); } diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c index 99ec6c22..0d731c00 100644 --- a/libsoup/soup-connection.c +++ b/libsoup/soup-connection.c @@ -27,31 +27,18 @@ #include "soup-ssl.h" #include "soup-uri.h" -typedef enum { - SOUP_CONNECTION_MODE_DIRECT, - SOUP_CONNECTION_MODE_PROXY, - SOUP_CONNECTION_MODE_TUNNEL -} SoupConnectionMode; - typedef struct { SoupSocket *socket; - /* proxy_addr is the address of the proxy server we are - * connected to, if any. server_addr is the address of the - * origin server. conn_addr is the uri of the host we are - * actually directly connected to, which will be proxy_addr if - * there's a proxy and server_addr if not. - */ - SoupAddress *proxy_addr, *server_addr, *conn_addr; + SoupAddress *remote_addr, *tunnel_addr; + SoupURI *proxy_uri; gpointer ssl_creds; - SoupConnectionMode mode; - GMainContext *async_context; SoupMessage *cur_req; + SoupConnectionState state; time_t last_used; - gboolean connected, in_use; guint io_timeout, idle_timeout; GSource *idle_timeout_src; } SoupConnectionPrivate; @@ -60,9 +47,7 @@ typedef struct { G_DEFINE_TYPE (SoupConnection, soup_connection, G_TYPE_OBJECT) enum { - CONNECT_RESULT, DISCONNECTED, - REQUEST_STARTED, LAST_SIGNAL }; @@ -71,8 +56,9 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, - PROP_SERVER_ADDRESS, - PROP_PROXY_ADDRESS, + PROP_REMOTE_ADDRESS, + PROP_TUNNEL_ADDRESS, + PROP_PROXY_URI, PROP_SSL_CREDS, PROP_ASYNC_CONTEXT, PROP_TIMEOUT, @@ -87,7 +73,6 @@ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void stop_idle_timer (SoupConnectionPrivate *priv); -static void send_request (SoupConnection *conn, SoupMessage *req); static void clear_current_request (SoupConnection *conn); static void @@ -101,10 +86,10 @@ finalize (GObject *object) { SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object); - if (priv->proxy_addr) - g_object_unref (priv->proxy_addr); - if (priv->server_addr) - g_object_unref (priv->server_addr); + if (priv->remote_addr) + g_object_unref (priv->remote_addr); + if (priv->tunnel_addr) + g_object_unref (priv->tunnel_addr); if (priv->async_context) g_main_context_unref (priv->async_context); @@ -135,9 +120,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) g_type_class_add_private (connection_class, sizeof (SoupConnectionPrivate)); - /* virtual method definition */ - connection_class->send_request = send_request; - /* virtual method override */ object_class->dispose = dispose; object_class->finalize = finalize; @@ -145,16 +127,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) object_class->get_property = get_property; /* signals */ - - signals[CONNECT_RESULT] = - g_signal_new ("connect_result", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupConnectionClass, connect_result), - NULL, NULL, - soup_marshal_NONE__INT, - G_TYPE_NONE, 1, - G_TYPE_INT); signals[DISCONNECTED] = g_signal_new ("disconnected", G_OBJECT_CLASS_TYPE (object_class), @@ -163,32 +135,30 @@ soup_connection_class_init (SoupConnectionClass *connection_class) NULL, NULL, soup_marshal_NONE__NONE, G_TYPE_NONE, 0); - signals[REQUEST_STARTED] = - g_signal_new ("request-started", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupConnectionClass, request_started), - NULL, NULL, - soup_marshal_NONE__OBJECT, - G_TYPE_NONE, 1, - SOUP_TYPE_MESSAGE); /* properties */ g_object_class_install_property ( - object_class, PROP_SERVER_ADDRESS, - g_param_spec_object (SOUP_CONNECTION_SERVER_ADDRESS, - "Server address", - "The address of the HTTP origin server for this connection", + object_class, PROP_REMOTE_ADDRESS, + g_param_spec_object (SOUP_CONNECTION_REMOTE_ADDRESS, + "Remote address", + "The address of the HTTP or proxy server", SOUP_TYPE_ADDRESS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( - object_class, PROP_PROXY_ADDRESS, - g_param_spec_object (SOUP_CONNECTION_PROXY_ADDRESS, - "Proxy address", - "The address of the HTTP Proxy to use for this connection", + object_class, PROP_TUNNEL_ADDRESS, + g_param_spec_object (SOUP_CONNECTION_TUNNEL_ADDRESS, + "Tunnel address", + "The address of the HTTPS server this tunnel connects to", SOUP_TYPE_ADDRESS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( + object_class, PROP_PROXY_URI, + g_param_spec_boxed (SOUP_CONNECTION_PROXY_URI, + "Proxy URI", + "URI of the HTTP proxy this connection connects to", + SOUP_TYPE_URI, + G_PARAM_READWRITE)); + g_object_class_install_property ( object_class, PROP_SSL_CREDS, g_param_spec_pointer (SOUP_CONNECTION_SSL_CREDENTIALS, "SSL credentials", @@ -217,32 +187,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) } -/** - * soup_connection_new: - * @propname1: name of first property to set - * @...: value of @propname1, followed by additional property/value pairs - * - * Creates an HTTP connection. There are three possibilities: - * - * If you set %SOUP_CONNECTION_SERVER_ADDRESS and not - * %SOUP_CONNECTION_PROXY_ADDRESS, this will be a direct connection to - * the indicated origin server. - * - * If you set %SOUP_CONNECTION_PROXY_ADDRESS and not - * %SOUP_CONNECTION_SSL_CREDENTIALS, this will be a standard proxy - * connection, which can be used for requests to multiple origin - * servers. - * - * If you set %SOUP_CONNECTION_SERVER_ADDRESS, - * %SOUP_CONNECTION_PROXY_ADDRESS, and - * %SOUP_CONNECTION_SSL_CREDENTIALS, this will be a tunnel through the - * proxy to the origin server. - * - * You must call soup_connection_connect_async() or - * soup_connection_connect_sync() to connect it after creating it. - * - * Return value: the new connection (not yet ready for use). - **/ SoupConnection * soup_connection_new (const char *propname1, ...) { @@ -264,31 +208,20 @@ set_property (GObject *object, guint prop_id, SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object); switch (prop_id) { - case PROP_SERVER_ADDRESS: - priv->server_addr = g_value_dup_object (value); - goto changed_connection; - - case PROP_PROXY_ADDRESS: - priv->proxy_addr = g_value_dup_object (value); - goto changed_connection; - + case PROP_REMOTE_ADDRESS: + priv->remote_addr = g_value_dup_object (value); + break; + case PROP_TUNNEL_ADDRESS: + priv->tunnel_addr = g_value_dup_object (value); + break; + case PROP_PROXY_URI: + if (priv->proxy_uri) + soup_uri_free (priv->proxy_uri); + priv->proxy_uri = g_value_dup_boxed (value); + break; case PROP_SSL_CREDS: priv->ssl_creds = g_value_get_pointer (value); - goto changed_connection; - - changed_connection: - if (priv->proxy_addr) { - priv->conn_addr = priv->proxy_addr; - if (priv->server_addr && priv->ssl_creds) - priv->mode = SOUP_CONNECTION_MODE_TUNNEL; - else - priv->mode = SOUP_CONNECTION_MODE_PROXY; - } else { - priv->conn_addr = priv->server_addr; - priv->mode = SOUP_CONNECTION_MODE_DIRECT; - } break; - case PROP_ASYNC_CONTEXT: priv->async_context = g_value_get_pointer (value); if (priv->async_context) @@ -313,11 +246,14 @@ get_property (GObject *object, guint prop_id, SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object); switch (prop_id) { - case PROP_SERVER_ADDRESS: - g_value_set_object (value, priv->server_addr); + case PROP_REMOTE_ADDRESS: + g_value_set_object (value, priv->remote_addr); break; - case PROP_PROXY_ADDRESS: - g_value_set_object (value, priv->proxy_addr); + case PROP_TUNNEL_ADDRESS: + g_value_set_object (value, priv->tunnel_addr); + break; + case PROP_PROXY_URI: + g_value_set_boxed (value, priv->proxy_uri); break; case PROP_SSL_CREDS: g_value_set_pointer (value, priv->ssl_creds); @@ -375,7 +311,9 @@ set_current_request (SoupConnectionPrivate *priv, SoupMessage *req) soup_message_set_io_status (req, SOUP_MESSAGE_IO_STATUS_RUNNING); priv->cur_req = req; - priv->in_use = TRUE; + if (priv->state == SOUP_CONNECTION_IDLE || + req->method != SOUP_METHOD_CONNECT) + priv->state = SOUP_CONNECTION_IN_USE; g_object_add_weak_pointer (G_OBJECT (req), (gpointer)&priv->cur_req); } @@ -384,7 +322,8 @@ clear_current_request (SoupConnection *conn) { SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - priv->in_use = FALSE; + if (priv->state == SOUP_CONNECTION_IN_USE) + priv->state = SOUP_CONNECTION_IDLE; start_idle_timer (conn); if (priv->cur_req) { SoupMessage *cur_req = priv->cur_req; @@ -408,144 +347,41 @@ socket_disconnected (SoupSocket *sock, gpointer conn) soup_connection_disconnect (conn); } -static SoupMessage * -connect_message (SoupConnectionPrivate *priv) -{ - SoupURI *uri; - SoupMessage *msg; - - uri = soup_uri_new (NULL); - soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS); - soup_uri_set_host (uri, soup_address_get_name (priv->server_addr)); - soup_uri_set_port (uri, soup_address_get_port (priv->server_addr)); - soup_uri_set_path (uri, ""); - msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri); - soup_uri_free (uri); - - return msg; -} - -static void -tunnel_connect_finished (SoupMessage *msg, gpointer user_data) -{ - SoupConnection *conn = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - guint status = msg->status_code; - - clear_current_request (conn); - - if (SOUP_STATUS_IS_SUCCESSFUL (status) && priv->ssl_creds) { - const char *server_name = - soup_address_get_name (priv->server_addr); - if (soup_socket_start_proxy_ssl (priv->socket, server_name, - NULL)) - priv->connected = TRUE; - else - status = SOUP_STATUS_SSL_FAILED; - } else if (SOUP_STATUS_IS_REDIRECTION (status)) { - /* Oops, the proxy thinks we're a web browser. */ - status = SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED; - } - - if (priv->proxy_addr) - status = soup_status_proxify (status); - g_signal_emit (conn, signals[CONNECT_RESULT], 0, status); - g_object_unref (msg); -} - -static void -tunnel_connect_restarted (SoupMessage *msg, gpointer user_data) -{ - SoupConnection *conn = user_data; - guint status = msg->status_code; - - /* We only allow one restart: if another one happens, treat - * it as "finished". - */ - g_signal_handlers_disconnect_by_func (msg, tunnel_connect_restarted, conn); - g_signal_connect (msg, "restarted", - G_CALLBACK (tunnel_connect_finished), conn); - - if (status == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { - /* Our parent session has handled the authentication - * and attempted to restart the message. - */ - if (soup_message_is_keepalive (msg)) { - /* Connection is still open, so just send the - * message again. - */ - soup_connection_send_request (conn, msg); - } else { - /* Tell the session to try again. */ - soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN); - soup_message_finished (msg); - } - } else - soup_message_finished (msg); -} +typedef struct { + SoupConnection *conn; + SoupConnectionCallback callback; + gpointer callback_data; +} SoupConnectionAsyncConnectData; static void socket_connect_result (SoupSocket *sock, guint status, gpointer user_data) { - SoupConnection *conn = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); + SoupConnectionAsyncConnectData *data = user_data; + SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); if (!SOUP_STATUS_IS_SUCCESSFUL (status)) goto done; - if (priv->mode == SOUP_CONNECTION_MODE_DIRECT && priv->ssl_creds) { + if (priv->ssl_creds && !priv->tunnel_addr) { if (!soup_socket_start_ssl (sock, NULL)) { status = SOUP_STATUS_SSL_FAILED; goto done; } } - if (priv->mode == SOUP_CONNECTION_MODE_TUNNEL) { - SoupMessage *connect_msg = connect_message (priv); - - g_signal_connect (connect_msg, "restarted", - G_CALLBACK (tunnel_connect_restarted), conn); - g_signal_connect (connect_msg, "finished", - G_CALLBACK (tunnel_connect_finished), conn); - - soup_connection_send_request (conn, connect_msg); - return; - } + g_signal_connect (priv->socket, "disconnected", + G_CALLBACK (socket_disconnected), data->conn); - priv->connected = TRUE; - start_idle_timer (conn); + priv->state = SOUP_CONNECTION_IDLE; + start_idle_timer (data->conn); done: - if (priv->proxy_addr) - status = soup_status_proxify (status); - g_signal_emit (conn, signals[CONNECT_RESULT], 0, status); -} - -/* from soup-misc.c... will eventually go away */ -guint soup_signal_connect_once (gpointer instance, const char *detailed_signal, - GCallback c_handler, gpointer data); - -static void -address_resolved (SoupAddress *addr, guint status, gpointer data) -{ - SoupConnection *conn = data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - - if (status != SOUP_STATUS_OK) { - socket_connect_result (NULL, status, conn); - return; + if (data->callback) { + if (priv->proxy_uri != NULL) + status = soup_status_proxify (status); + data->callback (data->conn, status, data->callback_data); } - - priv->socket = - soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr, - SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, - SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, - SOUP_SOCKET_TIMEOUT, priv->io_timeout, - NULL); - soup_socket_connect_async (priv->socket, NULL, - socket_connect_result, conn); - g_signal_connect (priv->socket, "disconnected", - G_CALLBACK (socket_disconnected), conn); + g_slice_free (SoupConnectionAsyncConnectData, data); } /** @@ -561,19 +397,28 @@ soup_connection_connect_async (SoupConnection *conn, SoupConnectionCallback callback, gpointer user_data) { + SoupConnectionAsyncConnectData *data; SoupConnectionPrivate *priv; g_return_if_fail (SOUP_IS_CONNECTION (conn)); priv = SOUP_CONNECTION_GET_PRIVATE (conn); g_return_if_fail (priv->socket == NULL); - if (callback) { - soup_signal_connect_once (conn, "connect_result", - G_CALLBACK (callback), user_data); - } + priv->state = SOUP_CONNECTION_CONNECTING; + + data = g_slice_new (SoupConnectionAsyncConnectData); + data->conn = conn; + data->callback = callback; + data->callback_data = user_data; - soup_address_resolve_async (priv->conn_addr, priv->async_context, NULL, - address_resolved, conn); + priv->socket = + soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr, + SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, + SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, + SOUP_SOCKET_TIMEOUT, priv->io_timeout, + NULL); + soup_socket_connect_async (priv->socket, NULL, + socket_connect_result, data); } /** @@ -594,12 +439,10 @@ soup_connection_connect_sync (SoupConnection *conn) priv = SOUP_CONNECTION_GET_PRIVATE (conn); g_return_val_if_fail (priv->socket == NULL, SOUP_STATUS_MALFORMED); - status = soup_address_resolve_sync (priv->conn_addr, NULL); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - goto fail; + priv->state = SOUP_CONNECTION_CONNECTING; priv->socket = - soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->conn_addr, + soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr, SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, SOUP_SOCKET_TIMEOUT, priv->io_timeout, @@ -613,43 +456,15 @@ soup_connection_connect_sync (SoupConnection *conn) g_signal_connect (priv->socket, "disconnected", G_CALLBACK (socket_disconnected), conn); - if (priv->mode == SOUP_CONNECTION_MODE_DIRECT && priv->ssl_creds) { + if (priv->ssl_creds && !priv->tunnel_addr) { if (!soup_socket_start_ssl (priv->socket, NULL)) { status = SOUP_STATUS_SSL_FAILED; goto fail; } } - if (priv->mode == SOUP_CONNECTION_MODE_TUNNEL) { - SoupMessage *connect_msg = connect_message (priv); - - soup_connection_send_request (conn, connect_msg); - status = connect_msg->status_code; - - if (status == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED && - SOUP_MESSAGE_IS_STARTING (connect_msg)) { - if (soup_message_is_keepalive (connect_msg)) { - /* Try once more */ - soup_connection_send_request (conn, connect_msg); - status = connect_msg->status_code; - } else - status = SOUP_STATUS_TRY_AGAIN; - } - - g_object_unref (connect_msg); - - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - const char *server_name = - soup_address_get_name (priv->server_addr); - if (!soup_socket_start_proxy_ssl (priv->socket, - server_name, - NULL)) - status = SOUP_STATUS_SSL_FAILED; - } - } - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - priv->connected = TRUE; + priv->state = SOUP_CONNECTION_IDLE; start_idle_timer (conn); } else { fail: @@ -659,12 +474,36 @@ soup_connection_connect_sync (SoupConnection *conn) } } - if (priv->proxy_addr) + if (priv->proxy_uri != NULL) status = soup_status_proxify (status); - g_signal_emit (conn, signals[CONNECT_RESULT], 0, status); return status; } +SoupAddress * +soup_connection_get_tunnel_addr (SoupConnection *conn) +{ + SoupConnectionPrivate *priv; + + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); + priv = SOUP_CONNECTION_GET_PRIVATE (conn); + + return priv->tunnel_addr; +} + +gboolean +soup_connection_start_ssl (SoupConnection *conn) +{ + SoupConnectionPrivate *priv; + const char *server_name; + + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); + priv = SOUP_CONNECTION_GET_PRIVATE (conn); + + server_name = soup_address_get_name (priv->tunnel_addr ? + priv->tunnel_addr : + priv->remote_addr); + return soup_socket_start_proxy_ssl (priv->socket, server_name, NULL); +} /** * soup_connection_disconnect: @@ -691,10 +530,10 @@ soup_connection_disconnect (SoupConnection *conn) priv->socket = NULL; /* Don't emit "disconnected" if we aren't yet connected */ - if (!priv->connected) + if (priv->state < SOUP_CONNECTION_IDLE) return; - priv->connected = FALSE; + priv->state = SOUP_CONNECTION_DISCONNECTED; if (priv->cur_req && priv->cur_req->status_code == SOUP_STATUS_IO_ERROR && @@ -748,21 +587,33 @@ soup_connection_get_socket (SoupConnection *conn) return SOUP_CONNECTION_GET_PRIVATE (conn)->socket; } -/** - * soup_connection_is_in_use: - * @conn: a connection - * - * Tests whether or not @conn is in use. - * - * Return value: %TRUE if there is currently a request being processed - * on @conn. - **/ -gboolean -soup_connection_is_in_use (SoupConnection *conn) +SoupURI * +soup_connection_get_proxy_uri (SoupConnection *conn) { - g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); - return SOUP_CONNECTION_GET_PRIVATE (conn)->in_use; + return SOUP_CONNECTION_GET_PRIVATE (conn)->proxy_uri; +} + +SoupConnectionState +soup_connection_get_state (SoupConnection *conn) +{ + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), + SOUP_CONNECTION_DISCONNECTED); + + return SOUP_CONNECTION_GET_PRIVATE (conn)->state; +} + +void +soup_connection_set_state (SoupConnection *conn, SoupConnectionState state) +{ + g_return_if_fail (SOUP_IS_CONNECTION (conn)); + g_return_if_fail (state > SOUP_CONNECTION_NEW && + state < SOUP_CONNECTION_DISCONNECTED); + + SOUP_CONNECTION_GET_PRIVATE (conn)->state = state; + if (state == SOUP_CONNECTION_IDLE) + clear_current_request (conn); } /** @@ -782,20 +633,6 @@ soup_connection_last_used (SoupConnection *conn) return SOUP_CONNECTION_GET_PRIVATE (conn)->last_used; } -static void -send_request (SoupConnection *conn, SoupMessage *req) -{ - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - - if (req != priv->cur_req) { - set_current_request (priv, req); - g_signal_emit (conn, signals[REQUEST_STARTED], 0, req); - } - - soup_message_send_request (req, priv->socket, conn, - priv->mode == SOUP_CONNECTION_MODE_PROXY); -} - /** * soup_connection_send_request: * @conn: a #SoupConnection @@ -807,42 +644,15 @@ send_request (SoupConnection *conn, SoupMessage *req) void soup_connection_send_request (SoupConnection *conn, SoupMessage *req) { - g_return_if_fail (SOUP_IS_CONNECTION (conn)); - g_return_if_fail (SOUP_IS_MESSAGE (req)); - g_return_if_fail (SOUP_CONNECTION_GET_PRIVATE (conn)->socket != NULL); - - SOUP_CONNECTION_GET_CLASS (conn)->send_request (conn, req); -} - -/** - * soup_connection_reserve: - * @conn: a #SoupConnection - * - * Marks @conn as "in use" despite not actually having a message on - * it. This is used by #SoupSession to keep it from accidentally - * trying to queue two messages on the same connection from different - * threads at the same time. - **/ -void -soup_connection_reserve (SoupConnection *conn) -{ - g_return_if_fail (SOUP_IS_CONNECTION (conn)); - - SOUP_CONNECTION_GET_PRIVATE (conn)->in_use = TRUE; -} + SoupConnectionPrivate *priv; -/** - * soup_connection_release: - * @conn: a #SoupConnection - * - * Marks @conn as not "in use". This can be used to cancel the effect - * of a soup_connection_reserve(). It is not necessary to call this - * after soup_connection_send_request(). - **/ -void -soup_connection_release (SoupConnection *conn) -{ g_return_if_fail (SOUP_IS_CONNECTION (conn)); + g_return_if_fail (SOUP_IS_MESSAGE (req)); + priv = SOUP_CONNECTION_GET_PRIVATE (conn); + g_return_if_fail (priv->state != SOUP_CONNECTION_NEW && priv->state != SOUP_CONNECTION_DISCONNECTED); - clear_current_request (conn); + if (req != priv->cur_req) + set_current_request (priv, req); + soup_message_send_request (req, priv->socket, conn, + priv->proxy_uri != NULL); } diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h index 180ab28d..68a36abb 100644 --- a/libsoup/soup-connection.h +++ b/libsoup/soup-connection.h @@ -28,13 +28,8 @@ typedef struct { GObjectClass parent_class; /* signals */ - void (*connect_result) (SoupConnection *, guint); void (*disconnected) (SoupConnection *); - void (*request_started) (SoupConnection *, SoupMessage *); - - /* methods */ - void (*send_request) (SoupConnection *, SoupMessage *); } SoupConnectionClass; GType soup_connection_get_type (void); @@ -44,9 +39,17 @@ typedef void (*SoupConnectionCallback) (SoupConnection *conn, guint status, gpointer data); - -#define SOUP_CONNECTION_SERVER_ADDRESS "server_address" -#define SOUP_CONNECTION_PROXY_ADDRESS "proxy-address" +typedef enum { + SOUP_CONNECTION_NEW, + SOUP_CONNECTION_CONNECTING, + SOUP_CONNECTION_IDLE, + SOUP_CONNECTION_IN_USE, + SOUP_CONNECTION_DISCONNECTED +} SoupConnectionState; + +#define SOUP_CONNECTION_REMOTE_ADDRESS "remote-address" +#define SOUP_CONNECTION_TUNNEL_ADDRESS "tunnel-address" +#define SOUP_CONNECTION_PROXY_URI "proxy-uri" #define SOUP_CONNECTION_SSL_CREDENTIALS "ssl-creds" #define SOUP_CONNECTION_ASYNC_CONTEXT "async-context" #define SOUP_CONNECTION_TIMEOUT "timeout" @@ -59,20 +62,22 @@ void soup_connection_connect_async (SoupConnection *conn, SoupConnectionCallback callback, gpointer user_data); guint soup_connection_connect_sync (SoupConnection *conn); +SoupAddress *soup_connection_get_tunnel_addr(SoupConnection *conn); +gboolean soup_connection_start_ssl (SoupConnection *conn); void soup_connection_disconnect (SoupConnection *conn); SoupSocket *soup_connection_get_socket (SoupConnection *conn); +SoupURI *soup_connection_get_proxy_uri (SoupConnection *conn); -gboolean soup_connection_is_in_use (SoupConnection *conn); +SoupConnectionState soup_connection_get_state (SoupConnection *conn); +void soup_connection_set_state (SoupConnection *conn, + SoupConnectionState state); time_t soup_connection_last_used (SoupConnection *conn); void soup_connection_send_request (SoupConnection *conn, SoupMessage *req); -void soup_connection_reserve (SoupConnection *conn); -void soup_connection_release (SoupConnection *conn); - G_END_DECLS #endif /* SOUP_CONNECTION_H */ diff --git a/libsoup/soup-content-sniffer.c b/libsoup/soup-content-sniffer.c new file mode 100644 index 00000000..bf971368 --- /dev/null +++ b/libsoup/soup-content-sniffer.c @@ -0,0 +1,605 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-content-sniffer.c + * + * Copyright (C) 2009 Gustavo Noronha Silva. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <gio/gio.h> + +#include "soup-content-sniffer.h" +#include "soup-enum-types.h" +#include "soup-message.h" +#include "soup-message-private.h" +#include "soup-session-feature.h" +#include "soup-uri.h" + +/** + * SECTION:soup-content-sniffer + * @short_description: Content sniffing for #SoupSession + * + * A #SoupContentSniffer tries to detect the actual content type of + * the files that are being downloaded by looking at some of the data + * before the #SoupMessage emits its #SoupMessage::got-headers signal. + * #SoupContentSniffer implements #SoupSessionFeature, so you can add + * content sniffing to a session with soup_session_add_feature() or + * soup_session_add_feature_by_type(). + * + * Since: 2.27.3 + **/ + +static char *sniff (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer, GHashTable **params); +static gsize get_buffer_size (SoupContentSniffer *sniffer); + +static void soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); + +static void request_queued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg); +static void request_unqueued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg); + +G_DEFINE_TYPE_WITH_CODE (SoupContentSniffer, soup_content_sniffer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, + soup_content_sniffer_session_feature_init)) + +static void +soup_content_sniffer_init (SoupContentSniffer *content_sniffer) +{ +} + +static void +soup_content_sniffer_class_init (SoupContentSnifferClass *content_sniffer_class) +{ + content_sniffer_class->sniff = sniff; + content_sniffer_class->get_buffer_size = get_buffer_size; +} + +static void +soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_interface, + gpointer interface_data) +{ + feature_interface->request_queued = request_queued; + feature_interface->request_unqueued = request_unqueued; +} + +/** + * soup_content_sniffer_new: + * + * Creates a new #SoupContentSniffer. + * + * Returns: a new #SoupContentSniffer + * + * Since: 2.27.3 + **/ +SoupContentSniffer * +soup_content_sniffer_new () +{ + return g_object_new (SOUP_TYPE_CONTENT_SNIFFER, NULL); +} + +char * +soup_content_sniffer_sniff (SoupContentSniffer *sniffer, + SoupMessage *msg, SoupBuffer *buffer, + GHashTable **params) +{ + g_return_val_if_fail (SOUP_IS_CONTENT_SNIFFER (sniffer), NULL); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); + g_return_val_if_fail (buffer != NULL, NULL); + + return SOUP_CONTENT_SNIFFER_GET_CLASS (sniffer)->sniff (sniffer, msg, buffer, params); +} + +/* This table is based on the HTML5 spec; + * See 2.7.4 Content-Type sniffing: unknown type + */ +typedef struct { + /* @has_ws is TRUE if @pattern contains "generic" whitespace */ + gboolean has_ws; + const char *mask; + const char *pattern; + guint pattern_length; + const char *sniffed_type; + gboolean scriptable; +} SoupContentSnifferPattern; + +static SoupContentSnifferPattern types_table[] = { + { FALSE, + "\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF", + "\x3C\x21\x44\x4F\x43\x54\x59\x50\x45\x20\x48\x54\x4D\x4C", + 14, + "text/html", + TRUE }, + + { TRUE, + "\xFF\xFF\xDF\xDF\xDF\xDF", + " \x3C\x48\x54\x4D\x4C", + 5, + "text/html", + TRUE }, + + { TRUE, + "\xFF\xFF\xDF\xDF\xDF\xDF", + " \x3C\x48\x45\x41\x44", + 5, + "text/html", + TRUE }, + + { TRUE, + "\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF", + " \x3C\x53\x43\x52\x49\x50\x54", + 7, + "text/html", + TRUE }, + + { FALSE, + "\xFF\xFF\xFF\xFF\xFF", + "\x25\x50\x44\x46\x2D", + 5, + "application/pdf", + TRUE }, + + { FALSE, + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + "\x25\x21\x50\x53\x2D\x41\x64\x6F\x62\x65\x2D", + 11, + "application/postscript", + FALSE }, + + { FALSE, + "\xFF\xFF\x00\x00", + "\xFE\xFF\x00\x00", + 4, + "text/plain", + FALSE }, + + { FALSE, + "\xFF\xFF\x00\x00", + "\xFF\xFF\x00\x00", + 4, + "text/plain", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF\x00", + "\xEF\xBB\xBF\x00", + 4, + "text/plain", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF\xFF\xFF\xFF", + "\x47\x49\x46\x38\x37\x61", + 6, + "image/gif", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF\xFF\xFF\xFF", + "\x47\x49\x46\x38\x39\x61", + 6, + "image/gif", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", + 8, + "image/png", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF", + "\xFF\xD8\xFF", + 3, + "image/jpeg", + FALSE }, + + { FALSE, + "\xFF\xFF", + "\x42\x4D", + 2, + "image/bmp", + FALSE }, + + { FALSE, + "\xFF\xFF\xFF\xFF", + "\x00\x00\x01\x00", + 4, + "image/vnd.microsoft.icon", + FALSE } +}; + +/* Whether a given byte looks like it might be part of binary content. + * Source: HTML5 spec; borrowed from the Chromium mime sniffer code, + * which is BSD-licensed + */ +static char byte_looks_binary[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, /* 0x00 - 0x0F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, /* 0x10 - 0x1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0 - 0xBF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0 - 0xCF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */ +}; + +static char * +sniff_gio (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer) +{ + SoupURI *uri; + char *uri_path; + char *content_type; + char *mime_type; + gboolean uncertain; + + uri = soup_message_get_uri (msg); + uri_path = soup_uri_to_string (uri, TRUE); + + content_type= g_content_type_guess (uri_path, (const guchar*)buffer->data, buffer->length, &uncertain); + mime_type = g_content_type_get_mime_type (content_type); + + g_free (uri_path); + g_free (content_type); + + return mime_type; +} + +/* HTML5: 2.7.4 Content-Type sniffing: unknown type */ +static char* +sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, + SoupBuffer *buffer, gboolean for_text_or_binary) +{ + const char *resource = buffer->data; + int resource_length = MIN (512, buffer->length); + char *gio_guess; + int i; + + for (i = 0; i < G_N_ELEMENTS (types_table); i++) { + SoupContentSnifferPattern *type_row = &(types_table[i]); + + /* The scriptable types should be skiped for the text + * or binary path, but considered for other paths */ + if (for_text_or_binary && type_row->scriptable) + continue; + + if (type_row->has_ws) { + int index_stream = 0; + int index_pattern = 0; + gboolean skip_row = FALSE; + + while (index_stream < resource_length) { + /* Skip insignificant white space ("WS" in the spec) */ + if (type_row->pattern[index_pattern] == ' ') { + if (resource[index_stream] == '\x09' || + resource[index_stream] == '\x0a' || + resource[index_stream] == '\x0c' || + resource[index_stream] == '\x0d' || + resource[index_stream] == '\x20') + index_stream++; + else + index_pattern++; + } else { + if ((type_row->mask[index_pattern] & resource[index_stream]) != type_row->pattern[index_pattern]) { + skip_row = TRUE; + break; + } + index_pattern++; + index_stream++; + } + } + + if (skip_row) + continue; + + if (index_pattern > type_row->pattern_length) + return g_strdup (type_row->sniffed_type); + } else { + int j; + + if (resource_length < type_row->pattern_length) + continue; + + for (j = 0; j < type_row->pattern_length; j++) { + if ((type_row->mask[j] & resource[j]) != type_row->pattern[j]) + break; + } + + /* This means our comparison above matched completely */ + if (j == type_row->pattern_length) + return g_strdup (type_row->sniffed_type); + } + } + + /* The spec allows us to use platform sniffing to find out + * about other types that are not covered, but we need to be + * careful to not escalate privileges, if on text or binary. + */ + gio_guess = sniff_gio (sniffer, msg, buffer); + + if (for_text_or_binary) { + for (i = 0; i < G_N_ELEMENTS (types_table); i++) { + SoupContentSnifferPattern *type_row = &(types_table[i]); + + if (!g_ascii_strcasecmp (type_row->sniffed_type, gio_guess) && + type_row->scriptable) { + g_free (gio_guess); + gio_guess = NULL; + break; + } + } + } + + if (gio_guess) + return gio_guess; + + return g_strdup ("application/octet-stream"); +} + +/* HTML5: 2.7.3 Content-Type sniffing: text or binary */ +static char* +sniff_text_or_binary (SoupContentSniffer *sniffer, SoupMessage *msg, + SoupBuffer *buffer) +{ + const char *resource = buffer->data; + int resource_length = MIN (512, buffer->length); + gboolean looks_binary = FALSE; + int i; + + /* Detecting UTF-16BE, UTF-16LE, or UTF-8 BOMs means it's text/plain */ + if (resource_length >= 4) { + if ((resource[0] == 0xFE && resource[1] == 0xFF) || + (resource[0] == 0xFF && resource[1] == 0xFE) || + (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF)) + return g_strdup ("text/plain"); + } + + /* Look to see if any of the first n bytes looks binary */ + for (i = 0; i < resource_length; i++) { + if (byte_looks_binary[(unsigned char)resource[i]]) { + looks_binary = TRUE; + break; + } + } + + if (!looks_binary) + return g_strdup ("text/plain"); + + return sniff_unknown (sniffer, msg, buffer, TRUE); +} + +static char* +sniff_images (SoupContentSniffer *sniffer, SoupMessage *msg, + SoupBuffer *buffer, const char *content_type) +{ + const char *resource = buffer->data; + int resource_length = MIN (512, buffer->length); + int i; + + for (i = 0; i < G_N_ELEMENTS (types_table); i++) { + SoupContentSnifferPattern *type_row = &(types_table[i]); + + if (resource_length < type_row->pattern_length) + continue; + + if (!g_str_has_prefix (type_row->sniffed_type, "image/")) + continue; + + /* All of the image types use all-\xFF for the mask, + * so we can just memcmp. + */ + if (memcmp (type_row->pattern, resource, type_row->pattern_length) == 0) + return g_strdup (type_row->sniffed_type); + } + + return g_strdup (content_type); +} + +static char* +sniff_feed_or_html (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer) +{ + const char *resource = buffer->data; + int resource_length = MIN (512, buffer->length); + int pos = 0; + + if (resource_length < 3) + goto text_html; + + /* Skip a leading UTF-8 BOM */ + if (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF) + pos = 3; + + look_for_tag: + if (pos > resource_length) + goto text_html; + + /* Skip insignificant white space */ + while ((resource[pos] == '\x09') || + (resource[pos] == '\x20') || + (resource[pos] == '\x0A') || + (resource[pos] == '\x0D')) { + pos++; + + if (pos > resource_length) + goto text_html; + } + + /* != < */ + if (resource[pos] != '\x3C') + return g_strdup ("text/html"); + + pos++; + + if ((pos + 2) > resource_length) + goto text_html; + + /* Skipping comments */ + if ((resource[pos] == '\x2D') || + (resource[pos+1] == '\x2D') || + (resource[pos+2] == '\x3E')) { + pos = pos + 3; + + if ((pos + 2) > resource_length) + goto text_html; + + while ((resource[pos] != '\x2D') && + (resource[pos+1] != '\x2D') && + (resource[pos+2] != '\x3E')) { + pos++; + + if ((pos + 2) > resource_length) + goto text_html; + } + + goto look_for_tag; + } + + if (pos > resource_length) + goto text_html; + + /* == ! */ + if (resource[pos] == '\x21') { + do { + pos++; + + if (pos > resource_length) + goto text_html; + } while (resource[pos] != '\x3E'); + + pos++; + + goto look_for_tag; + } else if (resource[pos] == '\x3F') { /* ? */ + do { + pos++; + + if ((pos + 1) > resource_length) + goto text_html; + } while ((resource[pos] != '\x3F') && + (resource[pos+1] != '\x3E')); + + pos = pos + 2; + + goto look_for_tag; + } + + if ((pos + 2) > resource_length) + goto text_html; + + if ((resource[pos] == '\x72') && + (resource[pos+1] == '\x73') && + (resource[pos+2] == '\x73')) + return g_strdup ("application/rss+xml"); + + if ((pos + 3) > resource_length) + goto text_html; + + if ((resource[pos] == '\x66') && + (resource[pos+1] == '\x65') && + (resource[pos+2] == '\x65') && + (resource[pos+3] == '\x64')) + return g_strdup ("application/atom+xml"); + + text_html: + return g_strdup ("text/html"); +} + +static char* +sniff (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer, GHashTable **params) +{ + const char *content_type_with_params; + const char *content_type; + + content_type = soup_message_headers_get_content_type (msg->response_headers, params); + content_type_with_params = soup_message_headers_get_one (msg->response_headers, "Content-Type"); + + + /* These comparisons are done in an ASCII-case-insensitive + * manner because the spec requires it */ + if ((content_type == NULL) || + !g_ascii_strcasecmp (content_type, "unknown/unknown") || + !g_ascii_strcasecmp (content_type, "application/unknown") || + !g_ascii_strcasecmp (content_type, "*/*")) + return sniff_unknown (sniffer, msg, buffer, FALSE); + + if (g_str_has_suffix (content_type, "+xml") || + !g_ascii_strcasecmp (content_type, "text/xml") || + !g_ascii_strcasecmp (content_type, "application/xml")) + return g_strdup (content_type); + + /* 2.7.5 Content-Type sniffing: image + * The spec says: + * + * If the resource's official type is "image/svg+xml", then + * the sniffed type of the resource is its official type (an + * XML type) + * + * The XML case is handled by the if above; if you refactor + * this code, keep this in mind. + */ + if (!g_ascii_strncasecmp (content_type, "image/", 6)) + return sniff_images (sniffer, msg, buffer, content_type); + + /* If we got text/plain, use text_or_binary */ + if (g_str_equal (content_type_with_params, "text/plain") || + g_str_equal (content_type_with_params, "text/plain; charset=ISO-8859-1") || + g_str_equal (content_type_with_params, "text/plain; charset=iso-8859-1") || + g_str_equal (content_type_with_params, "text/plain; charset=UTF-8")) { + return sniff_text_or_binary (sniffer, msg, buffer); + } + + if (!g_ascii_strcasecmp (content_type, "text/html")) + return sniff_feed_or_html (sniffer, msg, buffer); + + return g_strdup (content_type); +} + +static gsize +get_buffer_size (SoupContentSniffer *sniffer) +{ + return 512; +} + +static void +soup_content_sniffer_got_headers_cb (SoupMessage *msg, SoupContentSniffer *sniffer) +{ + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + SoupContentSnifferClass *content_sniffer_class = SOUP_CONTENT_SNIFFER_GET_CLASS (sniffer); + + priv->bytes_for_sniffing = content_sniffer_class->get_buffer_size (sniffer); +} + +static void +request_queued (SoupSessionFeature *feature, SoupSession *session, + SoupMessage *msg) +{ + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + + priv->sniffer = g_object_ref (feature); + g_signal_connect (msg, "got-headers", + G_CALLBACK (soup_content_sniffer_got_headers_cb), + feature); +} + +static void +request_unqueued (SoupSessionFeature *feature, SoupSession *session, + SoupMessage *msg) +{ + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + + g_object_unref (priv->sniffer); + priv->sniffer = NULL; + + g_signal_handlers_disconnect_by_func (msg, soup_content_sniffer_got_headers_cb, feature); +} diff --git a/libsoup/soup-content-sniffer.h b/libsoup/soup-content-sniffer.h new file mode 100644 index 00000000..a8aa9156 --- /dev/null +++ b/libsoup/soup-content-sniffer.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2009 Gustavo Noronha Silva. + */ + +#ifndef SOUP_CONTENT_SNIFFER_H +#define SOUP_CONTENT_SNIFFER_H 1 + +#include <libsoup/soup-types.h> +#include <libsoup/soup-message-body.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_CONTENT_SNIFFER (soup_content_sniffer_get_type ()) +#define SOUP_CONTENT_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CONTENT_SNIFFER, SoupContentSniffer)) +#define SOUP_CONTENT_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONTENT_SNIFFER, SoupContentSnifferClass)) +#define SOUP_IS_CONTENT_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CONTENT_SNIFFER)) +#define SOUP_IS_CONTENT_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CONTENT_SNIFFER)) +#define SOUP_CONTENT_SNIFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONTENT_SNIFFER, SoupContentSnifferClass)) + +typedef struct _SoupContentSnifferPrivate SoupContentSnifferPrivate; + +typedef struct { + GObject parent; + + SoupContentSnifferPrivate *priv; +} SoupContentSniffer; + +typedef struct { + GObjectClass parent_class; + + char* (*sniff) (SoupContentSniffer *sniffer, + SoupMessage *msg, + SoupBuffer *buffer, + GHashTable **params); + gsize (*get_buffer_size) (SoupContentSniffer *sniffer); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); + void (*_libsoup_reserved5) (void); +} SoupContentSnifferClass; + +GType soup_content_sniffer_get_type (void); + +SoupContentSniffer *soup_content_sniffer_new (void); + +char *soup_content_sniffer_sniff (SoupContentSniffer *sniffer, + SoupMessage *msg, + SoupBuffer *buffer, + GHashTable **params); + +G_END_DECLS + +#endif /* SOUP_CONTENT_SNIFFER_H */ diff --git a/libsoup/soup-dns.c b/libsoup/soup-dns.c index cb4cee5a..68a2e745 100644 --- a/libsoup/soup-dns.c +++ b/libsoup/soup-dns.c @@ -338,9 +338,7 @@ resolve_address (SoupDNSCacheEntry *entry) memset (&hints, 0, sizeof (struct addrinfo)); # ifdef AI_ADDRCONFIG - hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; -# else - hints.ai_flags = AI_CANONNAME; + hints.ai_flags = AI_ADDRCONFIG; # endif hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; diff --git a/libsoup/soup-form.c b/libsoup/soup-form.c index a2103f78..7263890a 100644 --- a/libsoup/soup-form.c +++ b/libsoup/soup-form.c @@ -366,7 +366,7 @@ soup_form_request_for_data (const char *method, const char *uri_string, msg = soup_message_new_from_uri (method, uri); - if (!strcmp (method, "POST")) { + if (!strcmp (method, "POST") || !strcmp (method, "PUT")) { soup_message_set_request ( msg, SOUP_FORM_MIME_TYPE_URLENCODED, SOUP_MEMORY_TAKE, diff --git a/libsoup/soup-gnutls.c b/libsoup/soup-gnutls.c index b5a5f32a..0326c6ba 100644 --- a/libsoup/soup-gnutls.c +++ b/libsoup/soup-gnutls.c @@ -390,8 +390,12 @@ soup_gnutls_pull_func (gnutls_transport_ptr_t transport_data, SoupGNUTLSChannel *chan = transport_data; ssize_t nread; - nread = read (chan->sockfd, buf, buflen); + nread = recv (chan->sockfd, buf, buflen, 0); +#ifdef G_OS_WIN32 + chan->eagain = (nread == SOCKET_ERROR && WSAGetLastError () == WSAEWOULDBLOCK); +#else chan->eagain = (nread == -1 && errno == EAGAIN); +#endif return nread; } @@ -402,8 +406,12 @@ soup_gnutls_push_func (gnutls_transport_ptr_t transport_data, SoupGNUTLSChannel *chan = transport_data; ssize_t nwrote; - nwrote = write (chan->sockfd, buf, buflen); + nwrote = send (chan->sockfd, buf, buflen, 0); +#ifdef G_OS_WIN32 + chan->eagain = (nwrote == SOCKET_ERROR && WSAGetLastError () == WSAEWOULDBLOCK); +#else chan->eagain = (nwrote == -1 && errno == EAGAIN); +#endif return nwrote; } @@ -446,7 +454,8 @@ soup_ssl_wrap_iochannel (GIOChannel *sock, gboolean non_blocking, if (ret) goto THROW_CREATE_ERROR; - if (gnutls_set_default_priority (session) != 0) + /* See http://bugzilla.gnome.org/show_bug.cgi?id=581342 */ + if (gnutls_priority_set_direct (session, "NORMAL:!VERS-TLS1.1:!VERS-TLS1.0", NULL) != 0) goto THROW_CREATE_ERROR; if (gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, diff --git a/libsoup/soup-marshal.list b/libsoup/soup-marshal.list index 1a43570a..d0c53efa 100644 --- a/libsoup/soup-marshal.list +++ b/libsoup/soup-marshal.list @@ -6,3 +6,4 @@ NONE:OBJECT,OBJECT NONE:OBJECT,POINTER NONE:BOXED,BOXED NONE:OBJECT,OBJECT,BOOLEAN +NONE:STRING,BOXED diff --git a/libsoup/soup-message-client-io.c b/libsoup/soup-message-client-io.c index ebf144c7..81599229 100644 --- a/libsoup/soup-message-client-io.c +++ b/libsoup/soup-message-client-io.c @@ -109,7 +109,8 @@ get_request_headers (SoupMessage *req, GString *header, *encoding = soup_message_headers_get_encoding (req->request_headers); if ((*encoding == SOUP_ENCODING_CONTENT_LENGTH || *encoding == SOUP_ENCODING_NONE) && - req->request_body->length > 0 && + (req->request_body->length > 0 || + soup_message_headers_get_one (req->request_headers, "Content-Type")) && !soup_message_headers_get_content_length (req->request_headers)) { *encoding = SOUP_ENCODING_CONTENT_LENGTH; soup_message_headers_set_content_length (req->request_headers, diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c index f0abb789..6acca0c9 100644 --- a/libsoup/soup-message-headers.c +++ b/libsoup/soup-message-headers.c @@ -33,6 +33,7 @@ typedef void (*SoupHeaderSetter) (SoupMessageHeaders *, const char *); static const char *intern_header_name (const char *name, SoupHeaderSetter *setter); +static void clear_special_headers (SoupMessageHeaders *hdrs); typedef struct { const char *name; @@ -98,7 +99,6 @@ soup_message_headers_free (SoupMessageHeaders *hdrs) g_array_free (hdrs->array, TRUE); if (hdrs->concat) g_hash_table_destroy (hdrs->concat); - g_free (hdrs->content_type); g_slice_free (SoupMessageHeaders, hdrs); } } @@ -137,7 +137,7 @@ soup_message_headers_clear (SoupMessageHeaders *hdrs) if (hdrs->concat) g_hash_table_remove_all (hdrs->concat); - hdrs->encoding = -1; + clear_special_headers (hdrs); } /** @@ -226,6 +226,20 @@ find_header (SoupHeader *hdr_array, const char *interned_name, int nth) return -1; } +static int +find_last_header (SoupHeader *hdr_array, guint length, const char *interned_name, int nth) +{ + int i; + + for (i = length; i >= 0; i--) { + if (hdr_array[i].name == interned_name) { + if (nth-- == 0) + return i; + } + } + return -1; +} + /** * soup_message_headers_remove: * @hdrs: a #SoupMessageHeaders @@ -277,12 +291,15 @@ const char * soup_message_headers_get_one (SoupMessageHeaders *hdrs, const char *name) { SoupHeader *hdr_array = (SoupHeader *)(hdrs->array->data); + guint hdr_length = hdrs->array->len; int index; g_return_val_if_fail (name != NULL, NULL); name = intern_header_name (name, NULL); - index = find_header (hdr_array, name, 0); + + index = find_last_header (hdr_array, hdr_length, name, 0); + return (index == -1) ? NULL : hdr_array[index].value; } @@ -531,6 +548,22 @@ intern_header_name (const char *name, SoupHeaderSetter *setter) return interned; } +static void +clear_special_headers (SoupMessageHeaders *hdrs) +{ + SoupHeaderSetter setter; + GHashTableIter iter; + gpointer key, value; + + /* Make sure header_setters has been initialized */ + intern_header_name ("", NULL); + + g_hash_table_iter_init (&iter, header_setters); + while (g_hash_table_iter_next (&iter, &key, &value)) { + setter = value; + setter (hdrs, NULL); + } +} /* Specific headers */ diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c index 5364868f..74c934f1 100644 --- a/libsoup/soup-message-io.c +++ b/libsoup/soup-message-io.c @@ -53,6 +53,9 @@ typedef struct { SoupMessageBody *read_body; goffset read_length; + gboolean need_content_sniffed, need_got_chunk; + SoupMessageBody *sniff_data; + SoupMessageIOState write_state; SoupEncoding write_encoding; GString *write_buf; @@ -105,6 +108,9 @@ soup_message_io_cleanup (SoupMessage *msg) if (io->write_chunk) soup_buffer_free (io->write_chunk); + if (io->sniff_data) + soup_message_body_free (io->sniff_data); + g_slice_free (SoupMessageIOData, io); } @@ -151,7 +157,7 @@ soup_message_io_stop (SoupMessage *msg) else if (io->conn) { SoupConnection *conn = io->conn; io->conn = NULL; - soup_connection_release (conn); + soup_connection_set_state (conn, SOUP_CONNECTION_IDLE); g_object_unref (conn); } } @@ -207,6 +213,55 @@ io_disconnected (SoupSocket *sock, SoupMessage *msg) io_error (sock, msg, NULL); } +static gboolean +io_handle_sniffing (SoupMessage *msg, gboolean done_reading) +{ + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + SoupMessageIOData *io = priv->io_data; + SoupBuffer *sniffed_buffer; + char *sniffed_mime_type; + GHashTable *params = NULL; + + if (!priv->sniffer) + return TRUE; + + if (!io->sniff_data) { + io->sniff_data = soup_message_body_new (); + io->need_content_sniffed = TRUE; + } + + if (io->need_content_sniffed) { + if (io->sniff_data->length < priv->bytes_for_sniffing && + !done_reading) + return TRUE; + + io->need_content_sniffed = FALSE; + sniffed_buffer = soup_message_body_flatten (io->sniff_data); + sniffed_mime_type = soup_content_sniffer_sniff (priv->sniffer, msg, sniffed_buffer, ¶ms); + + SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; + soup_message_content_sniffed (msg, sniffed_mime_type, params); + g_free (sniffed_mime_type); + if (params) + g_hash_table_destroy (params); + if (sniffed_buffer) + soup_buffer_free (sniffed_buffer); + SOUP_MESSAGE_IO_RETURN_VAL_IF_CANCELLED_OR_PAUSED (FALSE); + } + + if (io->need_got_chunk) { + io->need_got_chunk = FALSE; + sniffed_buffer = soup_message_body_flatten (io->sniff_data); + + SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; + soup_message_got_chunk (msg, sniffed_buffer); + soup_buffer_free (sniffed_buffer); + SOUP_MESSAGE_IO_RETURN_VAL_IF_CANCELLED_OR_PAUSED (FALSE); + } + + return TRUE; +} + /* Reads data from io->sock into io->read_meta_buf. If @to_blank is * %TRUE, it reads up until a blank line ("CRLF CRLF" or "LF LF"). * Otherwise, it reads up until a single CRLF or LF. @@ -257,7 +312,15 @@ read_metadata (SoupMessage *msg, gboolean to_blank) if (got_lf) { if (!to_blank) break; - if (nread == 1 || (nread == 2 && read_buf[0] == '\r')) + if (nread == 1 && + !strncmp ((char *)io->read_meta_buf->data + + io->read_meta_buf->len - 2, + "\n\n", 2)) + break; + else if (nread == 2 && + !strncmp ((char *)io->read_meta_buf->data + + io->read_meta_buf->len - 3, + "\n\r\n", 3)) break; } } @@ -286,6 +349,9 @@ read_body_chunk (SoupMessage *msg) GError *error = NULL; SoupBuffer *buffer; + if (!io_handle_sniffing (msg, FALSE)) + return FALSE; + while (read_to_eof || io->read_length > 0) { if (priv->chunk_allocator) { buffer = priv->chunk_allocator (msg, io->read_length, priv->chunk_allocator_data); @@ -316,6 +382,14 @@ read_body_chunk (SoupMessage *msg) io->read_length -= nread; + if (io->need_content_sniffed) { + soup_message_body_append_buffer (io->sniff_data, buffer); + io->need_got_chunk = TRUE; + if (!io_handle_sniffing (msg, FALSE)) + return FALSE; + continue; + } + SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_got_chunk (msg, buffer); soup_buffer_free (buffer); @@ -774,6 +848,21 @@ io_read (SoupSocket *sock, SoupMessage *msg) return; got_body: + if (!io_handle_sniffing (msg, TRUE)) { + /* If the message was paused (as opposed to + * cancelled), we need to make sure we wind up + * back here when it's unpaused, even if it + * was doing a chunked or EOF-terminated read + * before. + */ + if (io == priv->io_data) { + io->read_state = SOUP_MESSAGE_IO_STATE_BODY; + io->read_encoding = SOUP_ENCODING_CONTENT_LENGTH; + io->read_length = 0; + } + return; + } + io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h index f47251a7..5c88cbc0 100644 --- a/libsoup/soup-message-private.h +++ b/libsoup/soup-message-private.h @@ -9,6 +9,7 @@ #include "soup-message.h" #include "soup-auth.h" #include "soup-connection.h" +#include "soup-content-sniffer.h" typedef enum { SOUP_MESSAGE_IO_STATUS_IDLE, @@ -29,6 +30,9 @@ typedef struct { guint msg_flags; gboolean server_side; + SoupContentSniffer *sniffer; + gsize bytes_for_sniffing; + SoupHTTPVersion http_version, orig_http_version; SoupURI *uri; diff --git a/libsoup/soup-message-queue.c b/libsoup/soup-message-queue.c index 7e73c807..bce5a16d 100644 --- a/libsoup/soup-message-queue.c +++ b/libsoup/soup-message-queue.c @@ -11,6 +11,7 @@ #endif #include "soup-message-queue.h" +#include "soup-uri.h" /** * SECTION:soup-message-queue @@ -55,6 +56,18 @@ soup_message_queue_destroy (SoupMessageQueue *queue) g_slice_free (SoupMessageQueue, queue); } +static void +queue_message_restarted (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + if (item->proxy_addr) { + g_object_unref (item->proxy_addr); + item->proxy_addr = NULL; + } + item->resolved_proxy_addr = FALSE; +} + /** * soup_message_queue_append: * @queue: a #SoupMessageQueue @@ -81,6 +94,9 @@ soup_message_queue_append (SoupMessageQueue *queue, SoupMessage *msg, item->callback_data = user_data; item->cancellable = g_cancellable_new (); + g_signal_connect (msg, "restarted", + G_CALLBACK (queue_message_restarted), item); + /* Note: the initial ref_count of 1 represents the caller's * ref; the queue's own ref is indicated by the absence of the * "removed" flag. @@ -145,12 +161,16 @@ soup_message_queue_item_unref (SoupMessageQueueItem *item) g_mutex_unlock (item->queue->mutex); /* And free it */ + g_signal_handlers_disconnect_by_func (item->msg, + queue_message_restarted, item); g_object_unref (item->msg); g_object_unref (item->cancellable); - if (item->msg_addr) - g_object_unref (item->msg_addr); if (item->proxy_addr) g_object_unref (item->proxy_addr); + if (item->proxy_uri) + soup_uri_free (item->proxy_uri); + if (item->conn) + g_object_unref (item->conn); g_slice_free (SoupMessageQueueItem, item); } diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h index 4a9ae8f0..b7bc5d12 100644 --- a/libsoup/soup-message-queue.h +++ b/libsoup/soup-message-queue.h @@ -9,6 +9,7 @@ #include <glib.h> #include <gio/gio.h> +#include <libsoup/soup-connection.h> #include <libsoup/soup-message.h> #include <libsoup/soup-session.h> @@ -26,15 +27,16 @@ struct SoupMessageQueueItem { gpointer callback_data; GCancellable *cancellable; - SoupAddress *msg_addr, *proxy_addr; + SoupAddress *proxy_addr; + SoupURI *proxy_uri; + SoupConnection *conn; - guint resolving_msg_addr : 1; guint resolving_proxy_addr : 1; guint resolved_proxy_addr : 1; /*< private >*/ guint removed : 1; - guint ref_count : 28; + guint ref_count : 29; SoupMessageQueueItem *prev, *next; }; diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index 68a21c1e..f614946b 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -99,6 +99,7 @@ enum { GOT_HEADERS, GOT_CHUNK, GOT_BODY, + CONTENT_SNIFFED, RESTARTED, FINISHED, @@ -402,6 +403,44 @@ soup_message_class_init (SoupMessageClass *message_class) G_TYPE_NONE, 0); /** + * SoupMessage::content-sniffed: + * @msg: the message + * @type: the content type that we got from sniffing + * @params: a #GHashTable with the parameters + * + * This signal is emitted after %got-headers, and before the + * first %got-chunk. If content sniffing is disabled, or no + * content sniffing will be performed, due to the sniffer + * deciding to trust the Content-Type sent by the server, this + * signal is emitted immediately after %got_headers, and @type + * is %NULL. + * + * If the #SoupContentSniffer feature is enabled, and the + * sniffer decided to perform sniffing, the first %got_chunk + * emission may be delayed, so that the sniffer has enough + * data to correctly sniff the content. It notified the + * library user that the content has been sniffed, and allows + * it to change the header contents in the message, if + * desired. + * + * After this signal is emitted, the data that was spooled so + * that sniffing could be done is delivered on the first + * emission of %got_chunk. + * + * Since: 2.27.3 + **/ + signals[CONTENT_SNIFFED] = + g_signal_new ("content_sniffed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + soup_marshal_NONE__STRING_BOXED, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_HASH_TABLE); + + /** * SoupMessage::restarted: * @msg: the message * @@ -858,6 +897,24 @@ soup_message_got_body (SoupMessage *msg) g_signal_emit (msg, signals[GOT_BODY], 0); } +/** + * soup_message_content_sniffed: + * @msg: a #SoupMessage + * @type: a string with the sniffed content type + * @params: a #GHashTable with the parameters + * + * Emits the %content_sniffed signal, indicating that the IO layer + * finished sniffing the content type for @msg. If content sniffing + * will not be performed, due to the sniffer deciding to trust the + * Content-Type sent by the server, this signal is emitted immediately + * after %got_headers, with %NULL as @content_type. + **/ +void +soup_message_content_sniffed (SoupMessage *msg, const char *content_type, GHashTable *params) +{ + g_signal_emit (msg, signals[CONTENT_SNIFFED], 0, content_type, params); +} + static void restarted (SoupMessage *req) { @@ -896,7 +953,10 @@ finished (SoupMessage *req) void soup_message_finished (SoupMessage *msg) { - g_signal_emit (msg, signals[FINISHED], 0); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + + if (priv->io_status != SOUP_MESSAGE_IO_STATUS_FINISHED) + g_signal_emit (msg, signals[FINISHED], 0); } static void diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h index 1b850beb..b940ac64 100644 --- a/libsoup/soup-message.h +++ b/libsoup/soup-message.h @@ -155,6 +155,7 @@ void soup_message_got_informational (SoupMessage *msg); void soup_message_got_headers (SoupMessage *msg); void soup_message_got_chunk (SoupMessage *msg, SoupBuffer *chunk); void soup_message_got_body (SoupMessage *msg); +void soup_message_content_sniffed (SoupMessage *msg, const char *content_type, GHashTable *params); void soup_message_restarted (SoupMessage *msg); void soup_message_finished (SoupMessage *msg); diff --git a/libsoup/soup-method.h b/libsoup/soup-method.h index f646fd2e..e716b8e5 100644 --- a/libsoup/soup-method.h +++ b/libsoup/soup-method.h @@ -7,6 +7,7 @@ #define SOUP_METHOD_H 1 #include <libsoup/soup-types.h> +#include <libsoup/soup-misc.h> G_BEGIN_DECLS @@ -33,24 +34,26 @@ G_BEGIN_DECLS * </programlisting></informalexample> **/ +#define _SOUP_INTERN_METHOD(method) (_SOUP_ATOMIC_INTERN_STRING (_SOUP_METHOD_##method, #method)) + /* HTTP/1.1 methods */ -#define SOUP_METHOD_OPTIONS (_SOUP_METHOD_OPTIONS ? _SOUP_METHOD_OPTIONS : (_SOUP_METHOD_OPTIONS = g_intern_static_string ("OPTIONS"))) -#define SOUP_METHOD_GET (_SOUP_METHOD_GET ? _SOUP_METHOD_GET : (_SOUP_METHOD_GET = g_intern_static_string ("GET"))) -#define SOUP_METHOD_HEAD (_SOUP_METHOD_HEAD ? _SOUP_METHOD_HEAD : (_SOUP_METHOD_HEAD = g_intern_static_string ("HEAD"))) -#define SOUP_METHOD_POST (_SOUP_METHOD_POST ? _SOUP_METHOD_POST : (_SOUP_METHOD_POST = g_intern_static_string ("POST"))) -#define SOUP_METHOD_PUT (_SOUP_METHOD_PUT ? _SOUP_METHOD_PUT : (_SOUP_METHOD_PUT = g_intern_static_string ("PUT"))) -#define SOUP_METHOD_DELETE (_SOUP_METHOD_DELETE ? _SOUP_METHOD_DELETE : (_SOUP_METHOD_DELETE = g_intern_static_string ("DELETE"))) -#define SOUP_METHOD_TRACE (_SOUP_METHOD_TRACE ? _SOUP_METHOD_TRACE : (_SOUP_METHOD_TRACE = g_intern_static_string ("TRACE"))) -#define SOUP_METHOD_CONNECT (_SOUP_METHOD_CONNECT ? _SOUP_METHOD_CONNECT : (_SOUP_METHOD_CONNECT = g_intern_static_string ("CONNECT"))) +#define SOUP_METHOD_OPTIONS _SOUP_INTERN_METHOD (OPTIONS) +#define SOUP_METHOD_GET _SOUP_INTERN_METHOD (GET) +#define SOUP_METHOD_HEAD _SOUP_INTERN_METHOD (HEAD) +#define SOUP_METHOD_POST _SOUP_INTERN_METHOD (POST) +#define SOUP_METHOD_PUT _SOUP_INTERN_METHOD (PUT) +#define SOUP_METHOD_DELETE _SOUP_INTERN_METHOD (DELETE) +#define SOUP_METHOD_TRACE _SOUP_INTERN_METHOD (TRACE) +#define SOUP_METHOD_CONNECT _SOUP_INTERN_METHOD (CONNECT) /* WebDAV methods */ -#define SOUP_METHOD_PROPFIND (_SOUP_METHOD_PROPFIND ? _SOUP_METHOD_PROPFIND : (_SOUP_METHOD_PROPFIND = g_intern_static_string ("PROPFIND"))) -#define SOUP_METHOD_PROPPATCH (_SOUP_METHOD_PROPPATCH ? _SOUP_METHOD_PROPPATCH : (_SOUP_METHOD_PROPPATCH = g_intern_static_string ("PROPPATCH"))) -#define SOUP_METHOD_MKCOL (_SOUP_METHOD_MKCOL ? _SOUP_METHOD_MKCOL : (_SOUP_METHOD_MKCOL = g_intern_static_string ("MKCOL"))) -#define SOUP_METHOD_COPY (_SOUP_METHOD_COPY ? _SOUP_METHOD_COPY : (_SOUP_METHOD_COPY = g_intern_static_string ("COPY"))) -#define SOUP_METHOD_MOVE (_SOUP_METHOD_MOVE ? _SOUP_METHOD_MOVE : (_SOUP_METHOD_MOVE = g_intern_static_string ("MOVE"))) -#define SOUP_METHOD_LOCK (_SOUP_METHOD_LOCK ? _SOUP_METHOD_LOCK : (_SOUP_METHOD_LOCK = g_intern_static_string ("LOCK"))) -#define SOUP_METHOD_UNLOCK (_SOUP_METHOD_UNLOCK ? _SOUP_METHOD_UNLOCK : (_SOUP_METHOD_UNLOCK = g_intern_static_string ("UNLOCK"))) +#define SOUP_METHOD_PROPFIND _SOUP_INTERN_METHOD (PROPFIND) +#define SOUP_METHOD_PROPPATCH _SOUP_INTERN_METHOD (PROPPATCH) +#define SOUP_METHOD_MKCOL _SOUP_INTERN_METHOD (MKCOL) +#define SOUP_METHOD_COPY _SOUP_INTERN_METHOD (COPY) +#define SOUP_METHOD_MOVE _SOUP_INTERN_METHOD (MOVE) +#define SOUP_METHOD_LOCK _SOUP_INTERN_METHOD (LOCK) +#define SOUP_METHOD_UNLOCK _SOUP_INTERN_METHOD (UNLOCK) /* Do not use these variables directly; use the macros above, which * ensure that they get initialized properly. diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c index d67345b2..5e99476f 100644 --- a/libsoup/soup-misc.c +++ b/libsoup/soup-misc.c @@ -56,63 +56,6 @@ soup_str_case_equal (gconstpointer v1, return g_ascii_strcasecmp (string1, string2) == 0; } -typedef struct { - gpointer instance; - guint signal_id; -} SoupSignalOnceData; - -static void -signal_once_object_destroyed (gpointer ssod, GObject *ex_object) -{ - g_slice_free (SoupSignalOnceData, ssod); -} - -static void -signal_once_metamarshal (GClosure *closure, GValue *return_value, - guint n_param_values, const GValue *param_values, - gpointer invocation_hint, gpointer marshal_data) -{ - SoupSignalOnceData *ssod = marshal_data; - - closure->marshal (closure, return_value, n_param_values, - param_values, invocation_hint, - ((GCClosure *)closure)->callback); - - if (g_signal_handler_is_connected (ssod->instance, ssod->signal_id)) - g_signal_handler_disconnect (ssod->instance, ssod->signal_id); - g_object_weak_unref (G_OBJECT (ssod->instance), signal_once_object_destroyed, ssod); - g_slice_free (SoupSignalOnceData, ssod); -} - -/* No longer prototyped in soup-misc.h, because it's only used by - * soup-connection.c, and will be going away once that usage is removed. - */ -guint soup_signal_connect_once (gpointer instance, const char *detailed_signal, - GCallback c_handler, gpointer data); - -guint -soup_signal_connect_once (gpointer instance, const char *detailed_signal, - GCallback c_handler, gpointer data) -{ - SoupSignalOnceData *ssod; - GClosure *closure; - - g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0); - g_return_val_if_fail (detailed_signal != NULL, 0); - g_return_val_if_fail (c_handler != NULL, 0); - - ssod = g_slice_new0 (SoupSignalOnceData); - ssod->instance = instance; - g_object_weak_ref (G_OBJECT (instance), signal_once_object_destroyed, ssod); - - closure = g_cclosure_new (c_handler, data, NULL); - g_closure_set_meta_marshal (closure, ssod, signal_once_metamarshal); - - ssod->signal_id = g_signal_connect_closure (instance, detailed_signal, - closure, FALSE); - return ssod->signal_id; -} - /** * soup_add_io_watch: * @async_context: the #GMainContext to dispatch the I/O watch in, or diff --git a/libsoup/soup-misc.h b/libsoup/soup-misc.h index 162ddacd..f8dde104 100644 --- a/libsoup/soup-misc.h +++ b/libsoup/soup-misc.h @@ -33,6 +33,8 @@ guint soup_str_case_hash (gconstpointer key); gboolean soup_str_case_equal (gconstpointer v1, gconstpointer v2); +#define _SOUP_ATOMIC_INTERN_STRING(variable, value) (g_once_init_enter ((gsize *)&variable) ? (g_once_init_leave ((gsize *)&variable, GPOINTER_TO_SIZE (g_intern_static_string (value))), variable) : variable) + /* SSL stuff */ extern const gboolean soup_ssl_supported; diff --git a/libsoup/soup-proxy-resolver-gnome.c b/libsoup/soup-proxy-resolver-gnome.c index 4371d5a6..1873fbbb 100644 --- a/libsoup/soup-proxy-resolver-gnome.c +++ b/libsoup/soup-proxy-resolver-gnome.c @@ -13,8 +13,7 @@ #include <stdlib.h> #include "soup-proxy-resolver-gnome.h" -#include "soup-address.h" -#include "soup-dns.h" +#include "soup-proxy-uri-resolver.h" #include "soup-message.h" #include "soup-misc.h" #include "soup-session-feature.h" @@ -37,15 +36,16 @@ typedef enum { G_LOCK_DEFINE_STATIC (resolver_gnome); static SoupProxyResolverGNOMEMode proxy_mode; static GConfClient *gconf_client; +static char *proxy_user, *proxy_password; static pxProxyFactory *libproxy_factory; static GThreadPool *libproxy_threadpool; -static void soup_proxy_resolver_gnome_interface_init (SoupProxyResolverInterface *proxy_resolver_interface); +static void soup_proxy_resolver_gnome_interface_init (SoupProxyURIResolverInterface *proxy_resolver_interface); G_DEFINE_TYPE_EXTENDED (SoupProxyResolverGNOME, soup_proxy_resolver_gnome, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, NULL) - G_IMPLEMENT_INTERFACE (SOUP_TYPE_PROXY_RESOLVER, soup_proxy_resolver_gnome_interface_init)) + G_IMPLEMENT_INTERFACE (SOUP_TYPE_PROXY_URI_RESOLVER, soup_proxy_resolver_gnome_interface_init)) static void gconf_value_changed (GConfClient *client, const char *key, GConfValue *value, gpointer user_data); @@ -53,16 +53,16 @@ static void update_proxy_settings (void); static void libproxy_threadpool_func (gpointer thread_data, gpointer user_data); -static void get_proxy_async (SoupProxyResolver *proxy_resolver, - SoupMessage *msg, - GMainContext *async_context, - GCancellable *cancellable, - SoupProxyResolverCallback callback, - gpointer user_data); -static guint get_proxy_sync (SoupProxyResolver *proxy_resolver, - SoupMessage *msg, - GCancellable *cancellable, - SoupAddress **addr); +static void get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GMainContext *async_context, + GCancellable *cancellable, + SoupProxyURIResolverCallback callback, + gpointer user_data); +static guint get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GCancellable *cancellable, + SoupURI **proxy_uri); typedef struct { GMutex *lock; @@ -149,16 +149,19 @@ soup_proxy_resolver_gnome_class_init (SoupProxyResolverGNOMEClass *gconf_class) } static void -soup_proxy_resolver_gnome_interface_init (SoupProxyResolverInterface *proxy_resolver_interface) +soup_proxy_resolver_gnome_interface_init (SoupProxyURIResolverInterface *proxy_uri_resolver_interface) { - proxy_resolver_interface->get_proxy_async = get_proxy_async; - proxy_resolver_interface->get_proxy_sync = get_proxy_sync; + proxy_uri_resolver_interface->get_proxy_uri_async = get_proxy_uri_async; + proxy_uri_resolver_interface->get_proxy_uri_sync = get_proxy_uri_sync; } #define SOUP_GCONF_PROXY_MODE "/system/proxy/mode" #define SOUP_GCONF_PROXY_AUTOCONFIG_URL "/system/proxy/autoconfig_url" #define SOUP_GCONF_HTTP_PROXY_HOST "/system/http_proxy/host" #define SOUP_GCONF_HTTP_PROXY_PORT "/system/http_proxy/port" +#define SOUP_GCONF_HTTP_USE_AUTH "/system/http_proxy/use_authentication" +#define SOUP_GCONF_HTTP_PROXY_USER "/system/http_proxy/authentication_user" +#define SOUP_GCONF_HTTP_PROXY_PASSWORD "/system/http_proxy/authentication_password" #define SOUP_GCONF_HTTPS_PROXY_HOST "/system/proxy/secure_host" #define SOUP_GCONF_HTTPS_PROXY_PORT "/system/proxy/secure_port" #define SOUP_GCONF_USE_SAME_PROXY "/system/http_proxy/use_same_proxy" @@ -179,6 +182,16 @@ update_proxy_settings (void) /* resolver_gnome is locked */ + if (proxy_user) { + g_free (proxy_user); + proxy_user = NULL; + } + if (proxy_password) { + memset (proxy_password, 0, strlen (proxy_password)); + g_free (proxy_password); + proxy_password = NULL; + } + /* Get new settings */ mode = gconf_client_get_string ( gconf_client, SOUP_GCONF_PROXY_MODE, NULL); @@ -244,6 +257,13 @@ update_proxy_settings (void) } g_free (host); } + + if (gconf_client_get_bool (gconf_client, SOUP_GCONF_HTTP_USE_AUTH, NULL)) { + proxy_user = gconf_client_get_string ( + gconf_client, SOUP_GCONF_HTTP_PROXY_USER, NULL); + proxy_password = gconf_client_get_string ( + gconf_client, SOUP_GCONF_HTTP_PROXY_PASSWORD, NULL); + } } ignore = gconf_client_get_list ( @@ -304,33 +324,31 @@ gconf_value_changed (GConfClient *client, const char *key, } static guint -get_proxy_for_message (SoupMessage *msg, SoupAddress **addr) +get_proxy_for_uri (SoupURI *uri, SoupURI **proxy_uri) { - char *msg_uri, **proxies; - SoupURI *proxy_uri; + char *uristr, **proxies; gboolean got_proxy; int i; + *proxy_uri = NULL; + /* resolver_gnome is locked */ - msg_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE); - proxies = px_proxy_factory_get_proxies (libproxy_factory, msg_uri); - g_free (msg_uri); + uristr = soup_uri_to_string (uri, FALSE); + proxies = px_proxy_factory_get_proxies (libproxy_factory, uristr); + g_free (uristr); - if (!proxies) { - *addr = NULL; + if (!proxies) return SOUP_STATUS_OK; - } got_proxy = FALSE; for (i = 0; proxies[i]; i++) { if (!strcmp (proxies[i], "direct://")) { - proxy_uri = NULL; got_proxy = TRUE; break; } if (strncmp (proxies[i], "http://", 7) == 0) { - proxy_uri = soup_uri_new (proxies[i]); + *proxy_uri = soup_uri_new (proxies[i]); got_proxy = TRUE; break; } @@ -339,55 +357,45 @@ get_proxy_for_message (SoupMessage *msg, SoupAddress **addr) free (proxies[i]); free (proxies); - if (!got_proxy) { - *addr = NULL; - return SOUP_STATUS_CANT_RESOLVE_PROXY; - } + if (got_proxy) { + if (*proxy_uri && proxy_user) { + soup_uri_set_user (*proxy_uri, proxy_user); + soup_uri_set_password (*proxy_uri, proxy_password); + } - if (proxy_uri) { - *addr = soup_address_new (proxy_uri->host, proxy_uri->port); - soup_uri_free (proxy_uri); + return SOUP_STATUS_OK; } else - *addr = NULL; - return SOUP_STATUS_OK; + return SOUP_STATUS_CANT_RESOLVE_PROXY; } typedef struct { - SoupProxyResolver *proxy_resolver; - SoupMessage *msg; - SoupAddress *addr; + SoupProxyURIResolver *proxy_uri_resolver; + SoupURI *uri, *proxy_uri; GMainContext *async_context; GCancellable *cancellable; guint status; - SoupProxyResolverCallback callback; + SoupProxyURIResolverCallback callback; gpointer user_data; } SoupGNOMEAsyncData; -static void -resolved_address (SoupAddress *addr, guint status, gpointer data) +static gboolean +resolved_proxy (gpointer data) { SoupGNOMEAsyncData *sgad = data; - sgad->callback (sgad->proxy_resolver, sgad->msg, - soup_status_proxify (status), addr, - sgad->user_data); - g_object_unref (sgad->proxy_resolver); - g_object_unref (sgad->msg); + sgad->callback (sgad->proxy_uri_resolver, sgad->status, + sgad->proxy_uri, sgad->user_data); + g_object_unref (sgad->proxy_uri_resolver); + if (sgad->uri) + soup_uri_free (sgad->uri); if (sgad->async_context) g_main_context_unref (sgad->async_context); if (sgad->cancellable) g_object_unref (sgad->cancellable); - if (addr) - g_object_unref (addr); + if (sgad->proxy_uri) + soup_uri_free (sgad->proxy_uri); g_slice_free (SoupGNOMEAsyncData, sgad); -} -static gboolean -idle_resolved_address (gpointer data) -{ - SoupGNOMEAsyncData *sgad = data; - - resolved_address (sgad->addr, sgad->status, data); return FALSE; } @@ -396,36 +404,35 @@ libproxy_threadpool_func (gpointer user_data, gpointer thread_data) { SoupGNOMEAsyncData *sgad = user_data; - /* We don't just call get_libproxy_proxy_for_message here, - * since it's possible that the proxy mode has changed... + /* We don't just call get_proxy_for_uri here, since it's + * possible that the proxy mode has changed... */ - sgad->status = get_proxy_sync (NULL, sgad->msg, sgad->cancellable, - &sgad->addr); - soup_add_completion (sgad->async_context, idle_resolved_address, sgad); + sgad->status = get_proxy_uri_sync (sgad->proxy_uri_resolver, + sgad->uri, sgad->cancellable, + &sgad->proxy_uri); + soup_add_completion (sgad->async_context, resolved_proxy, sgad); } static void -get_proxy_async (SoupProxyResolver *proxy_resolver, - SoupMessage *msg, - GMainContext *async_context, - GCancellable *cancellable, - SoupProxyResolverCallback callback, - gpointer user_data) +get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GMainContext *async_context, + GCancellable *cancellable, + SoupProxyURIResolverCallback callback, + gpointer user_data) { SoupGNOMEAsyncData *sgad; sgad = g_slice_new0 (SoupGNOMEAsyncData); - sgad->proxy_resolver = g_object_ref (proxy_resolver); - sgad->msg = g_object_ref (msg); + sgad->proxy_uri_resolver = g_object_ref (proxy_uri_resolver); sgad->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - sgad->async_context = async_context ? g_main_context_ref (async_context) : NULL; sgad->callback = callback; sgad->user_data = user_data; G_LOCK (resolver_gnome); switch (proxy_mode) { case SOUP_PROXY_RESOLVER_GNOME_MODE_NONE: - sgad->addr = NULL; + sgad->proxy_uri = NULL; sgad->status = SOUP_STATUS_OK; break; @@ -433,48 +440,37 @@ get_proxy_async (SoupProxyResolver *proxy_resolver, /* We know libproxy won't do PAC or WPAD in this case, * so we can make a "blocking" call to it. */ - sgad->status = get_proxy_for_message (msg, &sgad->addr); + sgad->status = get_proxy_for_uri (uri, &sgad->proxy_uri); break; case SOUP_PROXY_RESOLVER_GNOME_MODE_AUTO: /* FIXME: cancellable */ + sgad->uri = soup_uri_copy (uri); + sgad->async_context = async_context ? g_main_context_ref (async_context) : NULL; g_thread_pool_push (libproxy_threadpool, sgad, NULL); G_UNLOCK (resolver_gnome); return; } G_UNLOCK (resolver_gnome); - if (sgad->addr) { - soup_address_resolve_async (sgad->addr, async_context, - cancellable, resolved_address, - sgad); - } else - soup_add_idle (async_context, idle_resolved_address, sgad); + soup_add_completion (async_context, resolved_proxy, sgad); } static guint -get_proxy_sync (SoupProxyResolver *proxy_resolver, - SoupMessage *msg, - GCancellable *cancellable, - SoupAddress **addr) +get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GCancellable *cancellable, + SoupURI **proxy_uri) { guint status; G_LOCK (resolver_gnome); if (proxy_mode == SOUP_PROXY_RESOLVER_GNOME_MODE_NONE) { - *addr = NULL; + *proxy_uri = NULL; status = SOUP_STATUS_OK; } else - status = get_proxy_for_message (msg, addr); + status = get_proxy_for_uri (uri, proxy_uri); G_UNLOCK (resolver_gnome); - if (!*addr || status != SOUP_STATUS_OK) - return status; - - status = soup_address_resolve_sync (*addr, cancellable); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_object_unref (*addr); - *addr = NULL; - } - return soup_status_proxify (status); + return status; } diff --git a/libsoup/soup-proxy-resolver-gnome.h b/libsoup/soup-proxy-resolver-gnome.h index 9f29842c..2f879216 100644 --- a/libsoup/soup-proxy-resolver-gnome.h +++ b/libsoup/soup-proxy-resolver-gnome.h @@ -6,7 +6,6 @@ #ifndef SOUP_PROXY_RESOLVER_GNOME_H #define SOUP_PROXY_RESOLVER_GNOME_H 1 -#include "soup-proxy-resolver.h" #include "soup-gnome-features.h" /* SOUP_TYPE_PROXY_RESOLVER_GNOME and soup_proxy_resolver_gnome_get_type() diff --git a/libsoup/soup-proxy-resolver.c b/libsoup/soup-proxy-resolver.c index 630c10c1..33a908c0 100644 --- a/libsoup/soup-proxy-resolver.c +++ b/libsoup/soup-proxy-resolver.c @@ -10,7 +10,14 @@ #endif #include "soup-proxy-resolver.h" +#include "soup-proxy-uri-resolver.h" +#include "soup-address.h" +#include "soup-message.h" #include "soup-session-feature.h" +#include "soup-uri.h" + +static void soup_proxy_resolver_interface_init (GTypeInterface *interface); +static void soup_proxy_resolver_uri_resolver_interface_init (SoupProxyURIResolverInterface *uri_resolver_interface); GType soup_proxy_resolver_get_type (void) @@ -22,7 +29,7 @@ soup_proxy_resolver_get_type (void) g_type_register_static_simple (G_TYPE_INTERFACE, g_intern_static_string ("SoupProxyResolver"), sizeof (SoupProxyResolverInterface), - (GClassInitFunc)NULL, + (GClassInitFunc)soup_proxy_resolver_interface_init, 0, (GInstanceInitFunc)NULL, (GTypeFlags) 0); @@ -33,20 +40,44 @@ soup_proxy_resolver_get_type (void) return g_define_type_id__volatile; } -/** - * soup_proxy_resolver_get_proxy_async: - * @proxy_resolver: the #SoupProxyResolver - * @msg: the #SoupMessage you want a proxy for - * @async_context: the #GMainContext to invoke @callback in - * @cancellable: a #GCancellable, or %NULL - * @callback: callback to invoke with the proxy address - * @user_data: data for @callback - * - * Asynchronously determines a proxy server address to use for @msg - * and calls @callback. - * - * Since: 2.26 - **/ +static void +proxy_resolver_interface_check (gpointer func_data, gpointer g_iface) +{ + GTypeInterface *interface = g_iface; + GTypeClass *klass; + + if (interface->g_type != SOUP_TYPE_PROXY_RESOLVER) + return; + + klass = g_type_class_peek (interface->g_instance_type); + /* If the class hasn't already declared that it implements + * SoupProxyURIResolver, add our own compat implementation. + */ + if (!g_type_interface_peek (klass, SOUP_TYPE_PROXY_URI_RESOLVER)) { + const GInterfaceInfo uri_resolver_interface_info = { + (GInterfaceInitFunc) soup_proxy_resolver_uri_resolver_interface_init, NULL, NULL + }; + g_type_add_interface_static (interface->g_instance_type, + SOUP_TYPE_PROXY_URI_RESOLVER, + &uri_resolver_interface_info); + } +} + + +static void +soup_proxy_resolver_interface_init (GTypeInterface *interface) +{ + /* Add an interface_check where we can kludgily add the + * SoupProxyURIResolver interface to all SoupProxyResolvers. + * (SoupProxyResolver can't just implement + * SoupProxyURIResolver itself because interface types can't + * implement other interfaces.) This is an ugly hack, but it + * only gets used if someone actually creates a + * SoupProxyResolver... + */ + g_type_add_interface_check (NULL, proxy_resolver_interface_check); +} + void soup_proxy_resolver_get_proxy_async (SoupProxyResolver *proxy_resolver, SoupMessage *msg, @@ -61,22 +92,6 @@ soup_proxy_resolver_get_proxy_async (SoupProxyResolver *proxy_resolver, callback, user_data); } -/** - * soup_proxy_resolver_get_proxy_sync: - * @proxy_resolver: the #SoupProxyResolver - * @msg: the #SoupMessage you want a proxy for - * @cancellable: a #GCancellable, or %NULL - * @addr: on return, will contain the proxy address - * - * Synchronously determines a proxy server address to use for @msg. If - * @msg should be sent via proxy, *@addr will be set to the address of - * the proxy, else it will be set to %NULL. - * - * Return value: SOUP_STATUS_OK if successful, or a transport-level - * error. - * - * Since: 2.26 - **/ guint soup_proxy_resolver_get_proxy_sync (SoupProxyResolver *proxy_resolver, SoupMessage *msg, @@ -86,3 +101,90 @@ soup_proxy_resolver_get_proxy_sync (SoupProxyResolver *proxy_resolver, return SOUP_PROXY_RESOLVER_GET_CLASS (proxy_resolver)-> get_proxy_sync (proxy_resolver, msg, cancellable, addr); } + +/* SoupProxyURIResolver implementation */ + +static SoupURI * +uri_from_address (SoupAddress *addr) +{ + SoupURI *proxy_uri; + + proxy_uri = soup_uri_new (NULL); + soup_uri_set_scheme (proxy_uri, SOUP_URI_SCHEME_HTTP); + soup_uri_set_host (proxy_uri, soup_address_get_name (addr)); + soup_uri_set_port (proxy_uri, soup_address_get_port (addr)); + return proxy_uri; +} + +typedef struct { + SoupProxyURIResolverCallback callback; + gpointer user_data; +} ProxyURIResolverAsyncData; + +static void +compat_got_proxy (SoupProxyResolver *proxy_resolver, + SoupMessage *msg, guint status, SoupAddress *proxy_addr, + gpointer user_data) +{ + ProxyURIResolverAsyncData *purad = user_data; + SoupURI *proxy_uri; + + proxy_uri = proxy_addr ? uri_from_address (proxy_addr) : NULL; + purad->callback (SOUP_PROXY_URI_RESOLVER (proxy_resolver), + status, proxy_uri, purad->user_data); + g_object_unref (msg); + if (proxy_uri) + soup_uri_free (proxy_uri); + g_slice_free (ProxyURIResolverAsyncData, purad); +} + +static void +compat_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, GMainContext *async_context, + GCancellable *cancellable, + SoupProxyURIResolverCallback callback, + gpointer user_data) +{ + SoupMessage *dummy_msg; + ProxyURIResolverAsyncData *purad; + + dummy_msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); + + purad = g_slice_new (ProxyURIResolverAsyncData); + purad->callback = callback; + purad->user_data = user_data; + + soup_proxy_resolver_get_proxy_async ( + SOUP_PROXY_RESOLVER (proxy_uri_resolver), dummy_msg, + async_context, cancellable, + compat_got_proxy, purad); +} + +static guint +compat_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, GCancellable *cancellable, + SoupURI **proxy_uri) +{ + SoupMessage *dummy_msg; + SoupAddress *proxy_addr = NULL; + guint status; + + dummy_msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); + status = soup_proxy_resolver_get_proxy_sync ( + SOUP_PROXY_RESOLVER (proxy_uri_resolver), dummy_msg, + cancellable, &proxy_addr); + g_object_unref (dummy_msg); + if (!proxy_addr) + return status; + + *proxy_uri = uri_from_address (proxy_addr); + g_object_unref (proxy_addr); + return status; +} + +static void +soup_proxy_resolver_uri_resolver_interface_init (SoupProxyURIResolverInterface *uri_resolver_interface) +{ + uri_resolver_interface->get_proxy_uri_async = compat_get_proxy_uri_async; + uri_resolver_interface->get_proxy_uri_sync = compat_get_proxy_uri_sync; +} diff --git a/libsoup/soup-proxy-resolver.h b/libsoup/soup-proxy-resolver.h index f91f3e14..161ae762 100644 --- a/libsoup/soup-proxy-resolver.h +++ b/libsoup/soup-proxy-resolver.h @@ -11,6 +11,8 @@ G_BEGIN_DECLS +#ifndef LIBSOUP_DISABLE_DEPRECATED + #define SOUP_TYPE_PROXY_RESOLVER (soup_proxy_resolver_get_type ()) #define SOUP_PROXY_RESOLVER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PROXY_RESOLVER, SoupProxyResolver)) #define SOUP_PROXY_RESOLVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PROXY_RESOLVER, SoupProxyResolverInterface)) @@ -48,6 +50,8 @@ guint soup_proxy_resolver_get_proxy_sync (SoupProxyResolver *proxy_resolver, GCancellable *cancellable, SoupAddress **addr); +#endif + G_END_DECLS #endif /*SOUP_PROXY_RESOLVER_H*/ diff --git a/libsoup/soup-proxy-uri-resolver.c b/libsoup/soup-proxy-uri-resolver.c new file mode 100644 index 00000000..bb86f182 --- /dev/null +++ b/libsoup/soup-proxy-uri-resolver.c @@ -0,0 +1,97 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-proxy-uri-resolver.c: HTTP proxy resolver interface, take 2 + * + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-proxy-uri-resolver.h" +#include "soup-session-feature.h" + +GType +soup_proxy_uri_resolver_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 ("SoupProxyURIResolver"), + sizeof (SoupProxyURIResolverInterface), + (GClassInitFunc)NULL, + 0, + (GInstanceInitFunc)NULL, + (GTypeFlags) 0); + g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_OBJECT); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + return g_define_type_id__volatile; +} + +/** + * SoupProxyURIResolverCallback: + * @resolver: the #SoupProxyURIResolver + * @status: a #SoupKnownStatusCode + * @proxy_uri: the resolved proxy URI, or %NULL + * @user_data: data passed to soup_proxy_uri_resolver_get_proxy_uri_async() + * + * Callback for soup_proxy_uri_resolver_get_proxy_uri_async() + **/ + +/** + * soup_proxy_uri_resolver_get_proxy_uri_async: + * @proxy_uri_resolver: the #SoupProxyURIResolver + * @uri: the #SoupURI you want a proxy for + * @async_context: the #GMainContext to invoke @callback in + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to invoke with the proxy address + * @user_data: data for @callback + * + * Asynchronously determines a proxy URI to use for @msg and calls + * @callback. + * + * Since: 2.26.3 + **/ +void +soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GMainContext *async_context, + GCancellable *cancellable, + SoupProxyURIResolverCallback callback, + gpointer user_data) +{ + SOUP_PROXY_URI_RESOLVER_GET_CLASS (proxy_uri_resolver)-> + get_proxy_uri_async (proxy_uri_resolver, uri, + async_context, cancellable, + callback, user_data); +} + +/** + * soup_proxy_uri_resolver_get_proxy_uri_sync: + * @proxy_uri_resolver: the #SoupProxyURIResolver + * @uri: the #SoupURI you want a proxy for + * @cancellable: a #GCancellable, or %NULL + * @proxy_uri: on return, will contain the proxy URI + * + * Synchronously determines a proxy URI to use for @uri. If @uri + * should be sent via proxy, *@proxy_uri will be set to the URI of the + * proxy, else it will be set to %NULL. + * + * Return value: %SOUP_STATUS_OK if successful, or a transport-level + * error. + * + * Since: 2.26.3 + **/ +guint +soup_proxy_uri_resolver_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GCancellable *cancellable, + SoupURI **proxy_uri) +{ + return SOUP_PROXY_URI_RESOLVER_GET_CLASS (proxy_uri_resolver)-> + get_proxy_uri_sync (proxy_uri_resolver, uri, cancellable, proxy_uri); +} diff --git a/libsoup/soup-proxy-uri-resolver.h b/libsoup/soup-proxy-uri-resolver.h new file mode 100644 index 00000000..97476cb0 --- /dev/null +++ b/libsoup/soup-proxy-uri-resolver.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef SOUP_PROXY_URI_RESOLVER_H +#define SOUP_PROXY_URI_RESOLVER_H 1 + +#include <libsoup/soup-proxy-resolver.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_PROXY_URI_RESOLVER (soup_proxy_uri_resolver_get_type ()) +#define SOUP_PROXY_URI_RESOLVER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PROXY_URI_RESOLVER, SoupProxyURIResolver)) +#define SOUP_PROXY_URI_RESOLVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PROXY_URI_RESOLVER, SoupProxyURIResolverInterface)) +#define SOUP_IS_PROXY_URI_RESOLVER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_PROXY_URI_RESOLVER)) +#define SOUP_IS_PROXY_URI_RESOLVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_PROXY_URI_RESOLVER)) +#define SOUP_PROXY_URI_RESOLVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SOUP_TYPE_PROXY_URI_RESOLVER, SoupProxyURIResolverInterface)) + +typedef struct _SoupProxyURIResolver SoupProxyURIResolver; + +typedef void (*SoupProxyURIResolverCallback) (SoupProxyURIResolver *, + guint, SoupURI *, gpointer); + +typedef struct { + GTypeInterface base; + + /* virtual methods */ + void (*get_proxy_uri_async) (SoupProxyURIResolver *, SoupURI *, + GMainContext *, GCancellable *, + SoupProxyURIResolverCallback, gpointer); + guint (*get_proxy_uri_sync) (SoupProxyURIResolver *, SoupURI *, + GCancellable *, SoupURI **); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +} SoupProxyURIResolverInterface; + +GType soup_proxy_uri_resolver_get_type (void); + +void soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GMainContext *async_context, + GCancellable *cancellable, + SoupProxyURIResolverCallback callback, + gpointer user_data); +guint soup_proxy_uri_resolver_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, + SoupURI *uri, + GCancellable *cancellable, + SoupURI **proxy_uri); + +G_END_DECLS + +#endif /*SOUP_PROXY_URI_RESOLVER_H*/ diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c index 97d4f2f1..c57d3037 100644 --- a/libsoup/soup-session-async.c +++ b/libsoup/soup-session-async.c @@ -15,6 +15,8 @@ #include "soup-address.h" #include "soup-message-private.h" #include "soup-misc.h" +#include "soup-proxy-uri-resolver.h" +#include "soup-uri.h" /** * SECTION:soup-session-async @@ -108,71 +110,69 @@ soup_session_async_new_with_options (const char *optname1, ...) return session; } - -static void -resolved_msg_addr (SoupAddress *addr, guint status, gpointer user_data) +static gboolean +item_failed (SoupMessageQueueItem *item, guint status) { - SoupMessageQueueItem *item = user_data; - SoupSession *session = item->session; - if (item->removed) { - /* Message was cancelled before its address resolved */ soup_message_queue_item_unref (item); - return; + return TRUE; } if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - soup_session_cancel_message (session, item->msg, status); + if (status != SOUP_STATUS_CANCELLED) + soup_session_cancel_message (item->session, item->msg, status); soup_message_queue_item_unref (item); - return; + return TRUE; } - item->msg_addr = g_object_ref (addr); - item->resolving_msg_addr = FALSE; - - soup_message_queue_item_unref (item); - - /* If we got here we know session still exists */ - run_queue ((SoupSessionAsync *)session); + return FALSE; } static void -resolve_msg_addr (SoupMessageQueueItem *item) +resolved_proxy_addr (SoupAddress *addr, guint status, gpointer user_data) { - if (item->resolving_msg_addr) + SoupMessageQueueItem *item = user_data; + SoupSession *session = item->session; + + if (item_failed (item, status)) return; - item->resolving_msg_addr = TRUE; - soup_message_queue_item_ref (item); - soup_address_resolve_async (soup_message_get_address (item->msg), - soup_session_get_async_context (item->session), - item->cancellable, - resolved_msg_addr, item); + item->proxy_addr = g_object_ref (addr); + item->resolving_proxy_addr = FALSE; + item->resolved_proxy_addr = TRUE; + + soup_message_queue_item_unref (item); + + /* If we got here we know session still exists */ + run_queue ((SoupSessionAsync *)session); } static void -resolved_proxy_addr (SoupProxyResolver *proxy_resolver, SoupMessage *msg, - guint status, SoupAddress *proxy_addr, gpointer user_data) +resolved_proxy_uri (SoupProxyURIResolver *proxy_resolver, + guint status, SoupURI *proxy_uri, gpointer user_data) { SoupMessageQueueItem *item = user_data; SoupSession *session = item->session; - if (item->removed) { - /* Message was cancelled before its proxy addr resolved */ - soup_message_queue_item_unref (item); + if (item_failed (item, status)) return; - } - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - soup_session_cancel_message (session, item->msg, status); - soup_message_queue_item_unref (item); + if (proxy_uri) { + SoupAddress *proxy_addr; + + item->proxy_uri = soup_uri_copy (proxy_uri); + proxy_addr = soup_address_new (proxy_uri->host, + proxy_uri->port); + soup_address_resolve_async (proxy_addr, + soup_session_get_async_context (session), + item->cancellable, + resolved_proxy_addr, item); + g_object_unref (proxy_addr); return; } item->resolving_proxy_addr = FALSE; item->resolved_proxy_addr = TRUE; - item->proxy_addr = proxy_addr ? g_object_ref (proxy_addr) : NULL; - soup_message_queue_item_unref (item); /* If we got here we know session still exists */ @@ -181,17 +181,17 @@ resolved_proxy_addr (SoupProxyResolver *proxy_resolver, SoupMessage *msg, static void resolve_proxy_addr (SoupMessageQueueItem *item, - SoupProxyResolver *proxy_resolver) + SoupProxyURIResolver *proxy_resolver) { if (item->resolving_proxy_addr) return; item->resolving_proxy_addr = TRUE; soup_message_queue_item_ref (item); - soup_proxy_resolver_get_proxy_async (proxy_resolver, item->msg, - soup_session_get_async_context (item->session), - item->cancellable, - resolved_proxy_addr, item); + soup_proxy_uri_resolver_get_proxy_uri_async ( + proxy_resolver, soup_message_get_uri (item->msg), + soup_session_get_async_context (item->session), + item->cancellable, resolved_proxy_uri, item); } static void @@ -203,12 +203,68 @@ connection_closed (SoupConnection *conn, gpointer session) do_idle_run_queue (session); } +typedef struct { + SoupSession *session; + SoupConnection *conn; + SoupMessageQueueItem *item; +} SoupSessionAsyncTunnelData; + +static void +tunnel_connected (SoupMessage *msg, gpointer user_data) +{ + SoupSessionAsyncTunnelData *data = user_data; + + if (SOUP_MESSAGE_IS_STARTING (msg)) { + soup_session_send_queue_item (data->session, data->item, data->conn); + return; + } + + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + soup_session_connection_failed (data->session, data->conn, + msg->status_code); + goto done; + } + + if (!soup_connection_start_ssl (data->conn)) { + soup_session_connection_failed (data->session, data->conn, + SOUP_STATUS_SSL_FAILED); + goto done; + } + + g_signal_connect (data->conn, "disconnected", + G_CALLBACK (connection_closed), data->session); + soup_connection_set_state (data->conn, SOUP_CONNECTION_IDLE); + + do_idle_run_queue (data->session); + +done: + g_object_unref (data->session); + soup_message_queue_item_unref (data->item); + g_slice_free (SoupSessionAsyncTunnelData, data); +} + static void -got_connection (SoupConnection *conn, guint status, gpointer user_data) +got_connection (SoupConnection *conn, guint status, gpointer session) { - SoupSession *session = user_data; + SoupAddress *tunnel_addr; if (status == SOUP_STATUS_OK) { + tunnel_addr = soup_connection_get_tunnel_addr (conn); + if (tunnel_addr) { + SoupSessionAsyncTunnelData *data; + + data = g_slice_new (SoupSessionAsyncTunnelData); + data->session = session; + data->conn = conn; + data->item = soup_session_make_connect_message (session, tunnel_addr); + g_signal_connect (data->item->msg, "finished", + G_CALLBACK (tunnel_connected), data); + g_signal_connect (data->item->msg, "restarted", + G_CALLBACK (tunnel_connected), data); + soup_session_send_queue_item (session, data->item, conn); + return; + } + g_signal_connect (conn, "disconnected", G_CALLBACK (connection_closed), session); @@ -222,8 +278,9 @@ got_connection (SoupConnection *conn, guint status, gpointer user_data) * idle pool and then just run the queue and see what * happens. */ - soup_connection_release (conn); - } + soup_connection_set_state (conn, SOUP_CONNECTION_IDLE); + } else + soup_session_connection_failed (session, conn, status); /* Even if the connection failed, we run the queue, since * there may have been messages waiting for the connection @@ -239,13 +296,12 @@ run_queue (SoupSessionAsync *sa) SoupSession *session = SOUP_SESSION (sa); SoupMessageQueue *queue = soup_session_get_queue (session); SoupMessageQueueItem *item; - SoupProxyResolver *proxy_resolver = + SoupProxyURIResolver *proxy_resolver = soup_session_get_proxy_resolver (session); SoupMessage *msg; SoupMessageIOStatus cur_io_status = SOUP_MESSAGE_IO_STATUS_CONNECTING; SoupConnection *conn; gboolean try_pruning = TRUE, should_prune = FALSE; - gboolean is_new; try_again: for (item = soup_message_queue_first (queue); @@ -253,30 +309,29 @@ run_queue (SoupSessionAsync *sa) item = soup_message_queue_next (queue, item)) { msg = item->msg; + /* CONNECT messages are handled specially */ + if (msg->method == SOUP_METHOD_CONNECT) + continue; + if (soup_message_get_io_status (msg) != cur_io_status || soup_message_io_in_progress (msg)) continue; - if (!item->msg_addr) { - resolve_msg_addr (item); - continue; - } if (proxy_resolver && !item->resolved_proxy_addr) { resolve_proxy_addr (item, proxy_resolver); continue; } - conn = soup_session_get_connection (session, msg, - item->proxy_addr, - &should_prune, &is_new); + conn = soup_session_get_connection (session, item, + &should_prune); if (!conn) continue; - if (is_new) { + if (soup_connection_get_state (conn) == SOUP_CONNECTION_NEW) { soup_connection_connect_async (conn, got_connection, g_object_ref (session)); } else - soup_connection_send_request (conn, msg); + soup_session_send_queue_item (session, item, conn); } if (item) soup_message_queue_item_unref (item); @@ -303,17 +358,6 @@ request_restarted (SoupMessage *req, gpointer user_data) { SoupMessageQueueItem *item = user_data; - if (item->msg_addr && - item->msg_addr != soup_message_get_address (item->msg)) { - g_object_unref (item->msg_addr); - item->msg_addr = NULL; - } - if (item->proxy_addr) { - g_object_unref (item->proxy_addr); - item->proxy_addr = NULL; - } - item->resolved_proxy_addr = FALSE; - run_queue ((SoupSessionAsync *)item->session); } diff --git a/libsoup/soup-session-private.h b/libsoup/soup-session-private.h index 015704fa..591161f4 100644 --- a/libsoup/soup-session-private.h +++ b/libsoup/soup-session-private.h @@ -10,22 +10,29 @@ #include "soup-session.h" #include "soup-connection.h" #include "soup-message-queue.h" -#include "soup-proxy-resolver.h" +#include "soup-proxy-uri-resolver.h" G_BEGIN_DECLS /* "protected" methods for subclasses */ -SoupMessageQueue *soup_session_get_queue (SoupSession *session); - -SoupConnection *soup_session_get_connection (SoupSession *session, - SoupMessage *msg, - SoupAddress *proxy, - gboolean *try_pruning, - gboolean *is_new); -gboolean soup_session_try_prune_connection (SoupSession *session); - -SoupProxyResolver *soup_session_get_proxy_resolver (SoupSession *session); -SoupCache *soup_session_get_cache (SoupSession *session); +SoupMessageQueue *soup_session_get_queue (SoupSession *session); + +SoupMessageQueueItem *soup_session_make_connect_message (SoupSession *session, + SoupAddress *server_addr); +SoupConnection *soup_session_get_connection (SoupSession *session, + SoupMessageQueueItem *item, + gboolean *try_pruning); +gboolean soup_session_try_prune_connection (SoupSession *session); +void soup_session_connection_failed (SoupSession *session, + SoupConnection *conn, + guint status); + +SoupProxyURIResolver *soup_session_get_proxy_resolver (SoupSession *session); + +void soup_session_send_queue_item (SoupSession *session, + SoupMessageQueueItem *item, + SoupConnection *conn); +SoupCache *soup_session_get_cache (SoupSession *session); G_END_DECLS diff --git a/libsoup/soup-session-sync.c b/libsoup/soup-session-sync.c index bb8e336b..8c2e56ba 100644 --- a/libsoup/soup-session-sync.c +++ b/libsoup/soup-session-sync.c @@ -14,7 +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-uri.h" /** * SECTION:soup-session-sync @@ -127,54 +129,93 @@ soup_session_sync_new_with_options (const char *optname1, ...) return session; } +static gboolean +tunnel_connect (SoupSession *session, SoupConnection *conn, + SoupAddress *tunnel_addr) +{ + SoupMessageQueueItem *item; + guint status; + + item = soup_session_make_connect_message (session, tunnel_addr); + do + soup_session_send_queue_item (session, item, conn); + while (SOUP_MESSAGE_IS_STARTING (item->msg)); + + status = item->msg->status_code; + soup_message_queue_item_unref (item); + + if (SOUP_STATUS_IS_SUCCESSFUL (status)) { + if (!soup_connection_start_ssl (conn)) + status = SOUP_STATUS_SSL_FAILED; + } + + if (SOUP_STATUS_IS_SUCCESSFUL (status)) + return TRUE; + else { + soup_session_connection_failed (session, conn, status); + return FALSE; + } +} + static SoupConnection * -wait_for_connection (SoupSession *session, SoupMessage *msg) +wait_for_connection (SoupMessageQueueItem *item) { + SoupSession *session = item->session; + SoupMessage *msg = item->msg; SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - gboolean try_pruning = FALSE, is_new = FALSE; - SoupProxyResolver *proxy_resolver; - SoupAddress *proxy_addr = NULL; + gboolean try_pruning = FALSE; + SoupProxyURIResolver *proxy_resolver; + SoupAddress *tunnel_addr; SoupConnection *conn; guint status; proxy_resolver = soup_session_get_proxy_resolver (session); - g_mutex_lock (priv->lock); - - try_again: - if (proxy_resolver) { - status = soup_proxy_resolver_get_proxy_sync (proxy_resolver, msg, NULL, &proxy_addr); + if (proxy_resolver && !item->resolved_proxy_addr) { + status = soup_proxy_uri_resolver_get_proxy_uri_sync ( + proxy_resolver, soup_message_get_uri (msg), + item->cancellable, &item->proxy_uri); if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_mutex_unlock (priv->lock); - soup_session_cancel_message (session, msg, status); + if (status != SOUP_STATUS_CANCELLED) + soup_session_cancel_message (session, msg, status); return NULL; } + + if (item->proxy_uri) { + item->proxy_addr = soup_address_new ( + item->proxy_uri->host, item->proxy_uri->port); + status = soup_address_resolve_sync (item->proxy_addr, + item->cancellable); + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + if (status != SOUP_STATUS_CANCELLED) + soup_session_cancel_message (session, msg, status); + return NULL; + } + } + + item->resolved_proxy_addr = TRUE; } - conn = soup_session_get_connection (session, msg, proxy_addr, - &try_pruning, &is_new); - if (proxy_addr) - g_object_unref (proxy_addr); + g_mutex_lock (priv->lock); + + try_again: + conn = soup_session_get_connection (session, item, &try_pruning); if (conn) { - if (is_new) { + if (soup_connection_get_state (conn) == SOUP_CONNECTION_NEW) { status = soup_connection_connect_sync (conn); - /* If the connection attempt fails, SoupSession - * will notice, unref conn, and set an error - * status on msg. So all we need to do is just - * not return the no-longer-valid connection. - */ - - if (status == SOUP_STATUS_TRY_AGAIN) - goto try_again; - else if (!SOUP_STATUS_IS_SUCCESSFUL (status)) + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + soup_session_connection_failed (session, conn, status); conn = NULL; - else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) { + } else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) { /* Message was cancelled while we were * connecting. */ soup_connection_disconnect (conn); conn = NULL; - } + } else if ((tunnel_addr = soup_connection_get_tunnel_addr (conn))) { + if (!tunnel_connect (session, conn, tunnel_addr)) + conn = NULL; + } } g_mutex_unlock (priv->lock); @@ -200,29 +241,17 @@ static void process_queue_item (SoupMessageQueueItem *item) { SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (item->session); - SoupMessage *msg = item->msg; SoupConnection *conn; - SoupAddress *addr; - guint status; do { - /* Resolve address */ - addr = soup_message_get_address (msg); - status = soup_address_resolve_sync (addr, item->cancellable); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - if (status != SOUP_STATUS_CANCELLED) - soup_session_cancel_message (item->session, msg, status); - break; - } - - /* Get a connection */ - conn = wait_for_connection (item->session, msg); + conn = wait_for_connection (item); if (!conn) break; - soup_connection_send_request (conn, msg); + soup_session_send_queue_item (item->session, item, conn); g_cond_broadcast (priv->cond); - } while (soup_message_get_io_status (msg) != SOUP_MESSAGE_IO_STATUS_FINISHED); + } while (soup_message_get_io_status (item->msg) != + SOUP_MESSAGE_IO_STATUS_FINISHED); } static gboolean diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index b8841b30..992e3055 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -23,7 +23,9 @@ #include "soup-marshal.h" #include "soup-message-private.h" #include "soup-message-queue.h" +#include "soup-misc.h" #include "soup-proxy-resolver-static.h" +#include "soup-proxy-uri-resolver.h" #include "soup-session.h" #include "soup-session-feature.h" #include "soup-session-private.h" @@ -58,6 +60,7 @@ **/ typedef struct { + SoupURI *uri; SoupAddress *addr; GSList *connections; /* CONTAINS: SoupConnection */ @@ -65,7 +68,7 @@ typedef struct { } SoupSessionHost; typedef struct { - SoupProxyResolver *proxy_resolver; + SoupProxyURIResolver *proxy_resolver; SoupCache *cache; char *ssl_ca_file; @@ -78,7 +81,7 @@ typedef struct { GSList *features; SoupAuthManager *auth_manager; - GHashTable *hosts; /* SoupAddress -> SoupSessionHost */ + GHashTable *hosts; /* char* -> SoupSessionHost */ GHashTable *conns; /* SoupConnection -> SoupSessionHost */ guint num_conns; guint max_conns, max_conns_per_host; @@ -155,8 +158,9 @@ soup_session_init (SoupSession *session) priv->queue = soup_message_queue_new (session); priv->host_lock = g_mutex_new (); - priv->hosts = g_hash_table_new (soup_address_hash_by_ip, - soup_address_equal_by_ip); + priv->hosts = g_hash_table_new_full (soup_uri_host_hash, + soup_uri_host_equal, + NULL, (GDestroyNotify)free_host); priv->conns = g_hash_table_new (NULL, NULL); priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT; @@ -172,28 +176,6 @@ soup_session_init (SoupSession *session) soup_session_add_feature (session, SOUP_SESSION_FEATURE (priv->auth_manager)); } -static gboolean -foreach_free_host (gpointer key, gpointer host, gpointer data) -{ - free_host (host); - return TRUE; -} - -static void -cleanup_hosts (SoupSessionPrivate *priv) -{ - GHashTable *old_hosts; - - g_mutex_lock (priv->host_lock); - old_hosts = priv->hosts; - priv->hosts = g_hash_table_new (soup_address_hash_by_ip, - soup_address_equal_by_ip); - g_mutex_unlock (priv->host_lock); - - g_hash_table_foreach_remove (old_hosts, foreach_free_host, NULL); - g_hash_table_destroy (old_hosts); -} - static void dispose (GObject *object) { @@ -201,7 +183,6 @@ dispose (GObject *object) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); soup_session_abort (session); - cleanup_hosts (priv); while (priv->features) soup_session_remove_feature (session, priv->features->data); @@ -645,14 +626,13 @@ set_property (GObject *object, guint prop_id, if (uri) { soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER); - priv->proxy_resolver = soup_proxy_resolver_static_new (uri); + priv->proxy_resolver = SOUP_PROXY_URI_RESOLVER (soup_proxy_resolver_static_new (uri)); soup_session_add_feature (session, SOUP_SESSION_FEATURE (priv->proxy_resolver)); g_object_unref (priv->proxy_resolver); } else if (priv->proxy_resolver && SOUP_IS_PROXY_RESOLVER_STATIC (priv->proxy_resolver)) soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER); soup_session_abort (session); - cleanup_hosts (priv); break; case PROP_MAX_CONNS: priv->max_conns = g_value_get_int (value); @@ -674,13 +654,9 @@ set_property (GObject *object, guint prop_id, g_free (priv->ssl_ca_file); priv->ssl_ca_file = g_strdup (new_ca_file); - if (ca_file_changed) { - if (priv->ssl_creds) { - soup_ssl_free_client_credentials (priv->ssl_creds); - priv->ssl_creds = NULL; - } - - cleanup_hosts (priv); + if (ca_file_changed && priv->ssl_creds) { + soup_ssl_free_client_credentials (priv->ssl_creds); + priv->ssl_creds = NULL; } break; @@ -798,12 +774,14 @@ soup_session_get_async_context (SoupSession *session) /* Hosts */ static SoupSessionHost * -soup_session_host_new (SoupSession *session, SoupAddress *addr) +soup_session_host_new (SoupSession *session, SoupURI *uri) { SoupSessionHost *host; host = g_slice_new0 (SoupSessionHost); - host->addr = g_object_ref (addr); + host->uri = soup_uri_copy_host (uri); + host->addr = soup_address_new (host->uri->host, host->uri->port); + return host; } @@ -816,17 +794,14 @@ get_host_for_message (SoupSession *session, SoupMessage *msg) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupSessionHost *host; - SoupAddress *addr = soup_message_get_address (msg); - - if (!soup_address_is_resolved (addr)) - return NULL; + SoupURI *uri = soup_message_get_uri (msg); - host = g_hash_table_lookup (priv->hosts, addr); + host = g_hash_table_lookup (priv->hosts, uri); if (host) return host; - host = soup_session_host_new (session, addr); - g_hash_table_insert (priv->hosts, host->addr, host); + host = soup_session_host_new (session, uri); + g_hash_table_insert (priv->hosts, host->uri, host); return host; } @@ -841,6 +816,7 @@ free_host (SoupSessionHost *host) soup_connection_disconnect (conn); } + soup_uri_free (host->uri); g_object_unref (host->addr); g_slice_free (SoupSessionHost, host); } @@ -871,7 +847,9 @@ redirect_handler (SoupMessage *msg, gpointer user_data) if (msg->status_code == SOUP_STATUS_SEE_OTHER || (msg->status_code == SOUP_STATUS_FOUND && - !SOUP_METHOD_IS_SAFE (msg->method))) { + !SOUP_METHOD_IS_SAFE (msg->method)) || + (msg->status_code == SOUP_STATUS_MOVED_PERMANENTLY && + msg->method == SOUP_METHOD_POST)) { /* Redirect using a GET */ g_object_set (msg, SOUP_MESSAGE_METHOD, SOUP_METHOD_GET, @@ -924,26 +902,29 @@ redirect_handler (SoupMessage *msg, gpointer user_data) soup_session_requeue_message (session, msg); } -static void -connection_started_request (SoupConnection *conn, SoupMessage *msg, - gpointer data) +void +soup_session_send_queue_item (SoupSession *session, + SoupMessageQueueItem *item, + SoupConnection *conn) { - SoupSession *session = data; SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + if (item->conn) { + if (item->conn != conn) { + g_object_unref (item->conn); + item->conn = g_object_ref (conn); + } + } else + item->conn = g_object_ref (conn); + if (priv->user_agent) { - soup_message_headers_replace (msg->request_headers, + soup_message_headers_replace (item->msg->request_headers, "User-Agent", priv->user_agent); } - /* Kludge to deal with the fact that CONNECT msgs come from the - * SoupConnection rather than being queued normally. - */ - if (msg->method == SOUP_METHOD_CONNECT) - g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg); - g_signal_emit (session, signals[REQUEST_STARTED], 0, - msg, soup_connection_get_socket (conn)); + item->msg, soup_connection_get_socket (conn)); + soup_connection_send_request (conn, item->msg); } gboolean @@ -963,7 +944,7 @@ soup_session_try_prune_connection (SoupSession *session) /* Don't prune a connection that is currently in use, * or hasn't been used yet. */ - if (!soup_connection_is_in_use (conn) && + if (soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE && soup_connection_last_used (conn) > 0) g_ptr_array_add (conns, g_object_ref (conn)); } @@ -1006,32 +987,21 @@ connection_disconnected (SoupConnection *conn, gpointer user_data) g_object_unref (conn); } -static void -connect_result (SoupConnection *conn, guint status, gpointer user_data) +void +soup_session_connection_failed (SoupSession *session, + SoupConnection *conn, guint status) { - SoupSession *session = user_data; SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupSessionHost *host; SoupMessageQueueItem *item; SoupMessage *msg; g_mutex_lock (priv->host_lock); - host = g_hash_table_lookup (priv->conns, conn); - if (!host) { - g_mutex_unlock (priv->host_lock); + g_mutex_unlock (priv->host_lock); + if (!host) return; - } - if (status == SOUP_STATUS_OK) { - soup_connection_reserve (conn); - host->connections = g_slist_prepend (host->connections, conn); - g_mutex_unlock (priv->host_lock); - return; - } - - /* The connection failed. */ - g_mutex_unlock (priv->host_lock); connection_disconnected (conn, session); if (host->connections) { @@ -1043,56 +1013,83 @@ connect_result (SoupConnection *conn, guint status, gpointer user_data) return; } - /* There are two possibilities: either status is - * SOUP_STATUS_TRY_AGAIN, in which case the session implementation - * will create a new connection (and all we need to do here - * is downgrade the message from CONNECTING to QUEUED); or - * status is something else, probably CANT_CONNECT or - * CANT_RESOLVE or the like, in which case we need to cancel - * any messages waiting for this host, since they're out - * of luck. + /* Assume that there's something wrong with the host, and + * cancel any other messages waiting for a connection to it, + * since they're out of luck. */ g_object_ref (session); for (item = soup_message_queue_first (priv->queue); item; item = soup_message_queue_next (priv->queue, item)) { msg = item->msg; - if (get_host_for_message (session, msg) == host) { - if (status == SOUP_STATUS_TRY_AGAIN) { - if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING) - soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_QUEUED); - } else { - soup_session_cancel_message (session, msg, - status); - } - } + if (get_host_for_message (session, msg) == host) + soup_session_cancel_message (session, msg, status); } g_object_unref (session); } +static void +tunnel_connected (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupMessageQueueItem *item = + soup_message_queue_lookup (priv->queue, msg); + + /* Clear the connection's proxy_uri, since it is now + * (effectively) directly connected. + */ + g_object_set (item->conn, + SOUP_CONNECTION_PROXY_URI, NULL, + NULL); + } +} + +SoupMessageQueueItem * +soup_session_make_connect_message (SoupSession *session, + SoupAddress *server_addr) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupURI *uri; + SoupMessage *msg; + + uri = soup_uri_new (NULL); + soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS); + soup_uri_set_host (uri, soup_address_get_name (server_addr)); + soup_uri_set_port (uri, soup_address_get_port (server_addr)); + soup_uri_set_path (uri, ""); + msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri); + soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT); + soup_uri_free (uri); + + /* Call the base implementation of soup_session_queue_message + * directly, to add msg to the SoupMessageQueue and cause all + * the right signals to be emitted. + */ + queue_message (session, msg, tunnel_connected, NULL); + return soup_message_queue_lookup (priv->queue, msg); +} + /** * soup_session_get_connection: * @session: a #SoupSession - * @msg: a #SoupMessage + * @item: a #SoupMessageQueueItem * @try_pruning: on return, whether or not to try pruning a connection - * @is_new: on return, %TRUE if the returned connection is new and not - * yet connected * - * Tries to find or create a connection for @msg; this is an internal + * Tries to find or create a connection for @item; this is an internal * method for #SoupSession subclasses. * * If there is an idle connection to the relevant host available, then - * that connection will be returned (with *@is_new set to %FALSE). The - * connection will be marked "reserved", so the caller must call - * soup_connection_release() if it ends up not using the connection - * right away. + * that connection will be returned. The connection will be set to + * %SOUP_CONNECTION_IN_USE, so the caller must call + * soup_connection_set_state() to set it to %SOUP_CONNECTION_IDLE if + * it ends up not using the connection right away. * * If there is no idle connection available, but it is possible to - * create a new connection, then one will be created and returned, - * with *@is_new set to %TRUE. The caller MUST then call + * create a new connection, then one will be created and returned + * (with state %SOUP_CONNECTION_NEW). The caller MUST then call * soup_connection_connect_sync() or soup_connection_connect_async() - * to connect it. If the connection attempt succeeds, the connection - * will be marked "reserved" and added to @session's connection pool - * once it connects. If the connection attempt fails, the connection - * will be unreffed. + * to connect it. If the connection attempt fails, the caller must + * call soup_session_connection_failed() to tell the session to free + * the connection. * * If no connection is available and a new connection cannot be made, * soup_session_get_connection() will return %NULL. If @session has @@ -1107,32 +1104,35 @@ connect_result (SoupConnection *conn, guint status, gpointer user_data) * Return value: a #SoupConnection, or %NULL **/ SoupConnection * -soup_session_get_connection (SoupSession *session, SoupMessage *msg, - SoupAddress *proxy_addr, - gboolean *try_pruning, gboolean *is_new) +soup_session_get_connection (SoupSession *session, + SoupMessageQueueItem *item, + gboolean *try_pruning) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupConnection *conn; SoupSessionHost *host; + SoupAddress *remote_addr, *tunnel_addr; SoupSSLCredentials *ssl_creds; GSList *conns; + gboolean has_pending = FALSE; SoupURI *uri; g_mutex_lock (priv->host_lock); - host = get_host_for_message (session, msg); + host = get_host_for_message (session, item->msg); for (conns = host->connections; conns; conns = conns->next) { - if (!soup_connection_is_in_use (conns->data)) { - soup_connection_reserve (conns->data); + if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) { + soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE); g_mutex_unlock (priv->host_lock); - *is_new = FALSE; return conns->data; - } + } else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING) + has_pending = TRUE; } - if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING) { - /* We already started a connection for this - * message, so don't start another one. + if (has_pending) { + /* We've already started one connection to this + * address, so don't start another one until it's + * done. */ g_mutex_unlock (priv->host_lock); return NULL; @@ -1149,48 +1149,45 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg, return NULL; } - uri = soup_message_get_uri (msg); + if (item->proxy_addr) { + remote_addr = item->proxy_addr; + tunnel_addr = NULL; + } else { + remote_addr = host->addr; + tunnel_addr = NULL; + } + + uri = soup_message_get_uri (item->msg); if (uri->scheme == SOUP_URI_SCHEME_HTTPS) { if (!priv->ssl_creds) priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file); ssl_creds = priv->ssl_creds; + + if (item->proxy_addr) + tunnel_addr = host->addr; } else ssl_creds = NULL; conn = soup_connection_new ( - SOUP_CONNECTION_SERVER_ADDRESS, host->addr, - SOUP_CONNECTION_PROXY_ADDRESS, proxy_addr, + SOUP_CONNECTION_REMOTE_ADDRESS, remote_addr, + SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr, + SOUP_CONNECTION_PROXY_URI, item->proxy_uri, SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds, SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context, SOUP_CONNECTION_TIMEOUT, priv->io_timeout, SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout, NULL); - g_signal_connect (conn, "connect_result", - G_CALLBACK (connect_result), - session); g_signal_connect (conn, "disconnected", G_CALLBACK (connection_disconnected), session); - g_signal_connect (conn, "request_started", - G_CALLBACK (connection_started_request), - session); g_hash_table_insert (priv->conns, conn, host); - /* We increment the connection counts so it counts against the - * totals, but we don't add it to the host's connection list - * yet, since it's not ready for use. - */ priv->num_conns++; host->num_conns++; - - /* Mark the request as connecting, so we don't try to open - * another new connection for it while waiting for this one. - */ - soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_CONNECTING); + host->connections = g_slist_prepend (host->connections, conn); g_mutex_unlock (priv->host_lock); - *is_new = TRUE; return conn; } @@ -1209,6 +1206,11 @@ message_finished (SoupMessage *msg, gpointer user_data) SoupSession *session = item->session; SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + if (item->conn) { + g_object_unref (item->conn); + item->conn = NULL; + } + if (!SOUP_MESSAGE_IS_STARTING (msg)) { soup_message_queue_remove (priv->queue, item); g_signal_handlers_disconnect_by_func (msg, message_finished, item); @@ -1422,6 +1424,10 @@ soup_session_cancel_message (SoupSession *session, SoupMessage *msg, g_return_if_fail (SOUP_IS_SESSION (session)); g_return_if_fail (SOUP_IS_MESSAGE (msg)); + /* If the message is already ending, don't do anything */ + if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) + return; + SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code); } @@ -1494,9 +1500,8 @@ soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature) priv->features = g_slist_prepend (priv->features, g_object_ref (feature)); soup_session_feature_attach (feature, session); - if (SOUP_IS_PROXY_RESOLVER (feature)) - priv->proxy_resolver = SOUP_PROXY_RESOLVER (feature); - + if (SOUP_IS_PROXY_URI_RESOLVER (feature)) + priv->proxy_resolver = SOUP_PROXY_URI_RESOLVER (feature); if (SOUP_IS_CACHE (feature)) priv->cache = SOUP_CACHE (feature); } @@ -1647,7 +1652,7 @@ soup_session_get_feature (SoupSession *session, GType feature_type) return NULL; } -SoupProxyResolver * +SoupProxyURIResolver * soup_session_get_proxy_resolver (SoupSession *session) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index d3f77f93..0c06f0fe 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -75,6 +75,7 @@ typedef struct { GSource *watch_src; GSource *read_src, *write_src; GSource *read_timeout, *write_timeout; + GSource *connect_timeout; GByteArray *read_buf; GMutex *iolock, *addrlock; @@ -156,6 +157,8 @@ finalize (GObject *object) if (priv->watch_src) g_source_destroy (priv->watch_src); + if (priv->connect_timeout) + g_source_destroy (priv->connect_timeout); if (priv->async_context) g_main_context_unref (priv->async_context); @@ -604,6 +607,10 @@ connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) /* Remove the watch now in case we don't return immediately */ g_source_destroy (priv->watch_src); priv->watch_src = NULL; + if (priv->connect_timeout) { + g_source_destroy (priv->connect_timeout); + priv->connect_timeout = NULL; + } if ((condition & ~(G_IO_IN | G_IO_OUT)) || (getsockopt (priv->sockfd, SOL_SOCKET, SO_ERROR, @@ -614,6 +621,22 @@ connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) return idle_connect_result (sacd); } +static gboolean +connect_timeout (gpointer data) +{ + SoupSocketAsyncConnectData *sacd = data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); + + /* Remove the watch now in case we don't return immediately */ + g_source_destroy (priv->watch_src); + priv->watch_src = NULL; + g_source_destroy (priv->connect_timeout); + priv->connect_timeout = NULL; + + disconnect_internal (priv); + return idle_connect_result (sacd); +} + static void got_address (SoupAddress *addr, guint status, gpointer user_data) { @@ -731,6 +754,12 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable, G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, connect_watch, sacd); + if (priv->timeout) { + priv->connect_timeout = + soup_add_timeout (priv->async_context, + priv->timeout * 1000, + connect_timeout, sacd); + } if (cancellable) { sacd->cancel_id = g_signal_connect (cancellable, "cancelled", diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index 2743576c..72a900db 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -100,16 +100,17 @@ const char *_SOUP_URI_SCHEME_HTTP, *_SOUP_URI_SCHEME_HTTPS; static inline const char * soup_uri_get_scheme (const char *scheme, int len) { - if (len == 4 && !strncmp (scheme, "http", 4)) { + if (len == 4 && !g_ascii_strncasecmp (scheme, "http", len)) { return SOUP_URI_SCHEME_HTTP; - } else if (len == 5 && !strncmp (scheme, "https", 5)) { + } else if (len == 5 && !g_ascii_strncasecmp (scheme, "https", len)) { return SOUP_URI_SCHEME_HTTPS; } else { char *lower_scheme; lower_scheme = g_ascii_strdown (scheme, len); - scheme = g_intern_string (lower_scheme); - g_free (lower_scheme); + scheme = g_intern_static_string (lower_scheme); + if (scheme != (const char *)lower_scheme) + g_free (lower_scheme); return scheme; } } @@ -921,6 +922,79 @@ soup_uri_set_fragment (SoupURI *uri, const char *fragment) uri->fragment = g_strdup (fragment); } +/** + * soup_uri_copy_host: + * @uri: a #SoupUri + * + * Makes a copy of @uri, considering only the protocol, host, and port + * + * Return value: the new #SoupUri + * + * Since: 2.26.3 + **/ +SoupURI * +soup_uri_copy_host (SoupURI *uri) +{ + SoupURI *dup; + + g_return_val_if_fail (uri != NULL, NULL); + + dup = soup_uri_new (NULL); + dup->scheme = uri->scheme; + dup->host = g_strdup (uri->host); + dup->port = uri->port; + if (dup->scheme == SOUP_URI_SCHEME_HTTP || + dup->scheme == SOUP_URI_SCHEME_HTTPS) + dup->path = g_strdup (""); + + return dup; +} + +/** + * soup_uri_host_hash: + * @key: a #SoupURI + * + * Hashes @key, considering only the scheme, host, and port. + * + * Return value: a hash + * + * Since: 2.26.3 + **/ +guint +soup_uri_host_hash (gconstpointer key) +{ + const SoupURI *uri = key; + + return GPOINTER_TO_UINT (uri->scheme) + uri->port + + soup_str_case_hash (uri->host); +} + +/** + * soup_uri_host_equal: + * @v1: a #SoupURI + * @v2: a #SoupURI + * + * Compares @v1 and @v2, considering only the scheme, host, and port. + * + * Return value: whether or not the URIs are equal in scheme, host, + * and port. + * + * Since: 2.26.3 + **/ +gboolean +soup_uri_host_equal (gconstpointer v1, gconstpointer v2) +{ + const SoupURI *one = v1; + const SoupURI *two = v2; + + if (one->scheme != two->scheme) + return FALSE; + if (one->port != two->port) + return FALSE; + + return g_ascii_strcasecmp (one->host, two->host) == 0; +} + GType soup_uri_get_type (void) diff --git a/libsoup/soup-uri.h b/libsoup/soup-uri.h index 3a5c8dd0..c05f3f23 100644 --- a/libsoup/soup-uri.h +++ b/libsoup/soup-uri.h @@ -9,6 +9,7 @@ #define SOUP_URI_H 1 #include <libsoup/soup-types.h> +#include <libsoup/soup-misc.h> G_BEGIN_DECLS @@ -30,8 +31,8 @@ struct _SoupURI { GType soup_uri_get_type (void); #define SOUP_TYPE_URI (soup_uri_get_type ()) -#define SOUP_URI_SCHEME_HTTP (_SOUP_URI_SCHEME_HTTP ? _SOUP_URI_SCHEME_HTTP : (_SOUP_URI_SCHEME_HTTP = g_intern_static_string ("http"))) -#define SOUP_URI_SCHEME_HTTPS (_SOUP_URI_SCHEME_HTTPS ? _SOUP_URI_SCHEME_HTTPS : (_SOUP_URI_SCHEME_HTTPS = g_intern_static_string ("https"))) +#define SOUP_URI_SCHEME_HTTP _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTP, "http") +#define SOUP_URI_SCHEME_HTTPS _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTPS, "https") extern const char *_SOUP_URI_SCHEME_HTTP, *_SOUP_URI_SCHEME_HTTPS; SoupURI *soup_uri_new_with_base (SoupURI *base, @@ -74,10 +75,15 @@ void soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form); void soup_uri_set_query_from_fields (SoupURI *uri, const char *first_field, - ...); + ...) G_GNUC_NULL_TERMINATED; void soup_uri_set_fragment (SoupURI *uri, const char *fragment); +SoupURI *soup_uri_copy_host (SoupURI *uri); +guint soup_uri_host_hash (gconstpointer key); +gboolean soup_uri_host_equal (gconstpointer v1, + gconstpointer v2); + #define SOUP_URI_VALID_FOR_HTTP(uri) ((uri) && ((uri)->scheme == SOUP_URI_SCHEME_HTTP || (uri)->scheme == SOUP_URI_SCHEME_HTTPS) && (uri)->host) G_END_DECLS diff --git a/libsoup/soup-value-utils.h b/libsoup/soup-value-utils.h index a7f48b62..1d265263 100644 --- a/libsoup/soup-value-utils.h +++ b/libsoup/soup-value-utils.h @@ -50,7 +50,7 @@ gboolean soup_value_hash_lookup (GHashTable *hash, ...); gboolean soup_value_hash_lookup_vals (GHashTable *hash, const char *first_key, - ...); + ...) G_GNUC_NULL_TERMINATED; GValueArray *soup_value_array_from_args (va_list args); gboolean soup_value_array_to_args (GValueArray *array, diff --git a/libsoup/soup-xmlrpc.h b/libsoup/soup-xmlrpc.h index de0f1f31..380a31e4 100644 --- a/libsoup/soup-xmlrpc.h +++ b/libsoup/soup-xmlrpc.h @@ -46,7 +46,7 @@ void soup_xmlrpc_set_response (SoupMessage *msg, void soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code, const char *fault_format, - ...); + ...) G_GNUC_PRINTF (3, 4); /* Errors */ diff --git a/libsoup/soup.h b/libsoup/soup.h index 4ac09911..bfbea4e9 100644 --- a/libsoup/soup.h +++ b/libsoup/soup.h @@ -16,6 +16,7 @@ extern "C" { #include <libsoup/soup-auth-domain-basic.h> #include <libsoup/soup-auth-domain-digest.h> #include <libsoup/soup-cache.h> +#include <libsoup/soup-content-sniffer.h> #include <libsoup/soup-cookie.h> #include <libsoup/soup-cookie-jar.h> #include <libsoup/soup-cookie-jar-text.h> diff --git a/tests/Makefile.am b/tests/Makefile.am index 0d46df56..20716c24 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,6 +26,7 @@ noinst_PROGRAMS = \ redirect-test \ simple-httpd \ simple-proxy \ + sniffing-test \ streaming-test \ timeout-test \ uri-parsing \ @@ -58,6 +59,7 @@ redirect_test_SOURCES = redirect-test.c $(TEST_SRCS) server_auth_test_SOURCES = server-auth-test.c $(TEST_SRCS) simple_httpd_SOURCES = simple-httpd.c simple_proxy_SOURCES = simple-proxy.c +sniffing_test_SOURCES = sniffing-test.c $(TEST_SRCS) ssl_test_SOURCES = ssl-test.c $(TEST_SRCS) streaming_test_SOURCES = streaming-test.c $(TEST_SRCS) timeout_test_SOURCES = timeout-test.c $(TEST_SRCS) @@ -87,6 +89,7 @@ TESTS = \ misc-test \ ntlm-test \ redirect-test \ + sniffing-test \ streaming-test \ timeout-test \ uri-parsing \ @@ -95,15 +98,23 @@ TESTS = \ $(SSL_TESTS) \ $(XMLRPC_TESTS) -EXTRA_DIST = \ - libsoup.supp \ - test-cert.pem \ - test-key.pem \ - htdigest \ - htpasswd \ - httpd.conf.in \ - index.txt \ - xmlrpc-server.php +SNIFFING_FILES = \ + resources/atom.xml \ + resources/home.gif \ + resources/mbox \ + resources/rss20.xml \ + resources/test.html + +EXTRA_DIST = \ + htdigest \ + htpasswd \ + httpd.conf.in \ + index.txt \ + libsoup.supp \ + test-cert.pem \ + test-key.pem \ + xmlrpc-server.php \ + $(SNIFFING_FILES) if MISSING_REGRESSION_TEST_PACKAGES check-local: check-TESTS diff --git a/tests/auth-test.c b/tests/auth-test.c index cc992b1c..b090e239 100644 --- a/tests/auth-test.c +++ b/tests/auth-test.c @@ -50,8 +50,8 @@ static SoupAuthTest tests[] = { { "Should fail with no auth, fail again with bad password, and give up", "Basic/realm2/", "4", "04", SOUP_STATUS_UNAUTHORIZED }, - { "Known realm, auth provided, so should succeed immediately", - "Basic/realm1/", "1", "1", SOUP_STATUS_OK }, + { "Auth provided this time, so should succeed", + "Basic/realm1/", "1", "01", SOUP_STATUS_OK }, { "Now should automatically reuse previous auth", "Basic/realm1/", "", "1", SOUP_STATUS_OK }, @@ -62,8 +62,8 @@ static SoupAuthTest tests[] = { { "Subdir should retry last auth, but will fail this time", "Basic/realm1/realm2/", "", "1", SOUP_STATUS_UNAUTHORIZED }, - { "Now should use provided auth on first try", - "Basic/realm1/realm2/", "2", "2", SOUP_STATUS_OK }, + { "Now should use provided auth", + "Basic/realm1/realm2/", "2", "02", SOUP_STATUS_OK }, { "Reusing last auth. Should succeed on first try", "Basic/realm1/realm2/", "", "2", SOUP_STATUS_OK }, @@ -84,8 +84,8 @@ static SoupAuthTest tests[] = { { "Should fail with no auth, fail again with bad password, and give up", "Digest/realm2/", "4", "04", SOUP_STATUS_UNAUTHORIZED }, - { "Known realm, auth provided, so should succeed immediately", - "Digest/realm1/", "1", "1", SOUP_STATUS_OK }, + { "Known realm, auth provided, so should succeed", + "Digest/realm1/", "1", "01", SOUP_STATUS_OK }, { "Now should automatically reuse previous auth", "Digest/realm1/", "", "1", SOUP_STATUS_OK }, @@ -93,6 +93,9 @@ static SoupAuthTest tests[] = { { "Subdir should also automatically reuse auth", "Digest/realm1/subdir/", "", "1", SOUP_STATUS_OK }, + { "Password provided, should succeed", + "Digest/realm2/", "2", "02", SOUP_STATUS_OK }, + { "Should already know correct domain and use provided auth on first try", "Digest/realm1/realm2/", "2", "2", SOUP_STATUS_OK }, @@ -102,9 +105,6 @@ static SoupAuthTest tests[] = { { "Should succeed on first try because of earlier domain directive", "Digest/realm1/realm2/realm1/", "", "1", SOUP_STATUS_OK }, - { "Should succeed on first try. (Known realm with cached password)", - "Digest/realm2/", "", "2", SOUP_STATUS_OK }, - { "Fail once, then use typoed password, then use right password", "Digest/realm3/", "43", "043", SOUP_STATUS_OK }, @@ -398,15 +398,15 @@ async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data) } static void -async_authenticate_522601 (SoupSession *session, SoupMessage *msg, - SoupAuth *auth, gboolean retrying, gpointer data) +async_authenticate_assert_once (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, gpointer data) { gboolean *been_here = data; - debug_printf (2, " async_authenticate_522601\n"); + debug_printf (2, " async_authenticate_assert_once\n"); if (*been_here) { - debug_printf (1, " ERROR: async_authenticate_522601 called twice\n"); + debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n"); errors++; } *been_here = TRUE; @@ -531,8 +531,52 @@ do_async_auth_test (const char *base_uri) soup_session_unpause_message (session, msg1); auth_id = g_signal_connect (session, "authenticate", - G_CALLBACK (async_authenticate_522601), + G_CALLBACK (async_authenticate_assert_once), + &been_there); + g_main_loop_run (loop); + g_signal_handler_disconnect (session, auth_id); + + soup_test_session_abort_unref (session); + + g_object_unref (msg1); + + + /* Test that giving no password doesn't cause multiple + * authenticate signals the second time. + */ + debug_printf (1, "\nTesting async auth with no password (#583462):\n"); + + /* For this test, our first message will not finish twice */ + finished = 1; + been_there = FALSE; + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + /* Send a message that doesn't actually authenticate + */ + msg1 = soup_message_new ("GET", uri); + g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1)); + auth_id = g_signal_connect (session, "authenticate", + G_CALLBACK (async_authenticate), NULL); + g_object_ref (msg1); + soup_session_queue_message (session, msg1, async_finished, &finished); + g_main_loop_run (loop); + g_signal_handler_disconnect (session, auth_id); + soup_session_unpause_message (session, msg1); + g_main_loop_run (loop); + g_object_unref(msg1); + + /* Now send a second message */ + finished = 1; + msg1 = soup_message_new ("GET", uri); + g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2)); + g_object_ref (msg1); + auth_id = g_signal_connect (session, "authenticate", + G_CALLBACK (async_authenticate_assert_once), &been_there); + soup_session_queue_message (session, msg1, async_finished, &finished); + g_main_loop_run (loop); + soup_session_unpause_message (session, msg1); + g_main_loop_run (loop); g_signal_handler_disconnect (session, auth_id); diff --git a/tests/get.c b/tests/get.c index 9efed354..b0e5c576 100644 --- a/tests/get.c +++ b/tests/get.c @@ -22,141 +22,24 @@ #include <libsoup/soup.h> #endif -#ifdef G_OS_WIN32 -#include <io.h> -#define mkdir(path, mode) _mkdir (path) -#endif - static SoupSession *session; static GMainLoop *loop; -static gboolean recurse = FALSE, debug = FALSE; +static gboolean debug = FALSE; static const char *method; -static char *base; -static SoupURI *base_uri; -static GHashTable *fetched_urls; - -static GPtrArray * -find_hrefs (SoupURI *doc_base, const char *body, int length) -{ - GPtrArray *hrefs = g_ptr_array_new (); - char *buf = g_strndup (body, length); - char *start = buf, *end; - char *href, *frag; - SoupURI *uri; - - while ((start = strstr (start, "href"))) { - start += 4; - while (isspace ((unsigned char) *start)) - start++; - if (*start++ != '=') - continue; - while (isspace ((unsigned char) *start)) - start++; - if (*start++ != '"') - continue; - - end = strchr (start, '"'); - if (!end) - break; - - href = g_strndup (start, end - start); - start = end; - frag = strchr (href, '#'); - if (frag) - *frag = '\0'; - - uri = soup_uri_new_with_base (doc_base, href); - g_free (href); - - if (!uri) - continue; - if (doc_base->scheme != uri->scheme || - doc_base->port != uri->port || - g_ascii_strcasecmp (doc_base->host, uri->host) != 0) { - soup_uri_free (uri); - continue; - } - - if (strncmp (doc_base->path, uri->path, strlen (doc_base->path)) != 0) { - soup_uri_free (uri); - continue; - } - - g_ptr_array_add (hrefs, soup_uri_to_string (uri, FALSE)); - soup_uri_free (uri); - } - g_free (buf); - - return hrefs; -} - -static void -mkdirs (const char *path) -{ - char *slash; - - for (slash = strchr (path, '/'); slash; slash = strchr (slash + 1, '/')) { - *slash = '\0'; - if (*path && mkdir (path, 0755) == -1 && errno != EEXIST) { - fprintf (stderr, "Could not create '%s'\n", path); - g_main_loop_quit (loop); - return; - } - *slash = '/'; - } -} static void get_url (const char *url) { - char *url_to_get, *slash, *name; + const char *name; SoupMessage *msg; - int fd, i; - SoupURI *uri; - GPtrArray *hrefs; const char *header; - if (strncmp (url, base, strlen (base)) != 0) - return; - if (strchr (url, '?') && strcmp (url, base) != 0) - return; - - slash = strrchr (url, '/'); - if (slash && !slash[1]) - url_to_get = g_strdup_printf ("%sindex.html", url); - else - url_to_get = g_strdup (url); - - if (g_hash_table_lookup (fetched_urls, url_to_get)) - return; - g_hash_table_insert (fetched_urls, url_to_get, url_to_get); - - if (recurse) { - /* See if we're already downloading it, and create the - * file if not. - */ - - name = url_to_get + strlen (base); - if (*name == '/') - name++; - if (access (name, F_OK) == 0) - return; - - mkdirs (name); - fd = open (name, O_WRONLY | O_CREAT | O_TRUNC, 0644); - close (fd); - } - - msg = soup_message_new (method, url_to_get); + msg = soup_message_new (method, url); soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT); soup_session_send_message (session, msg); name = soup_message_get_uri (msg)->path; - if (strncmp (base_uri->path, name, strlen (base_uri->path)) != 0) { - fprintf (stderr, " Error: not under %s\n", base_uri->path); - return; - } if (debug) { SoupMessageHeadersIter iter; @@ -176,13 +59,7 @@ get_url (const char *url) } else printf ("%s: %d %s\n", name, msg->status_code, msg->reason_phrase); - name += strlen (base_uri->path); - if (*name == '/') - name++; - if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { - if (recurse) - unlink (name); header = soup_message_headers_get_one (msg->response_headers, "Location"); if (header) { @@ -190,47 +67,24 @@ get_url (const char *url) printf (" -> %s\n", header); get_url (header); } - return; + } else if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + fwrite (msg->response_body->data, 1, + msg->response_body->length, stdout); } - - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) - return; - - if (recurse) - fd = open (name, O_WRONLY | O_CREAT | O_TRUNC, 0644); - else - fd = STDOUT_FILENO; - write (fd, msg->response_body->data, msg->response_body->length); - if (!recurse) - return; - close (fd); - - header = soup_message_headers_get_content_type (msg->response_headers, NULL); - if (header && g_ascii_strcasecmp (header, "text/html") != 0) - return; - - uri = soup_uri_new (url); - hrefs = find_hrefs (uri, msg->response_body->data, msg->response_body->length); - soup_uri_free (uri); - for (i = 0; i < hrefs->len; i++) { - get_url (hrefs->pdata[i]); - g_free (hrefs->pdata[i]); - } - g_ptr_array_free (hrefs, TRUE); } static void usage (void) { - fprintf (stderr, "Usage: get [-c CAfile] [-p proxy URL] [-r] [-h] [-d] URL\n"); + fprintf (stderr, "Usage: get [-c CAfile] [-p proxy URL] [-h] [-d] URL\n"); exit (1); } int main (int argc, char **argv) { - const char *cafile = NULL; - SoupURI *proxy = NULL; + const char *cafile = NULL, *url; + SoupURI *proxy = NULL, *parsed; gboolean synchronous = FALSE; int opt; @@ -239,7 +93,7 @@ main (int argc, char **argv) method = SOUP_METHOD_GET; - while ((opt = getopt (argc, argv, "c:dhp:rs")) != -1) { + while ((opt = getopt (argc, argv, "c:dhp:s")) != -1) { switch (opt) { case 'c': cafile = optarg; @@ -263,10 +117,6 @@ main (int argc, char **argv) } break; - case 'r': - recurse = TRUE; - break; - case 's': synchronous = TRUE; break; @@ -281,14 +131,13 @@ main (int argc, char **argv) if (argc != 1) usage (); - base = argv[0]; - base_uri = soup_uri_new (base); - if (!base_uri) { - fprintf (stderr, "Could not parse '%s' as a URL\n", base); + url = argv[0]; + parsed = soup_uri_new (url); + if (!parsed) { + fprintf (stderr, "Could not parse '%s' as a URL\n", url); exit (1); } - - fetched_urls = g_hash_table_new (g_str_hash, g_str_equal); + soup_uri_free (parsed); if (synchronous) { session = soup_session_sync_new_with_options ( @@ -318,28 +167,13 @@ main (int argc, char **argv) NULL); } - if (recurse) { - char *outdir; - - outdir = g_strdup_printf ("%lu", (unsigned long)getpid ()); - if (mkdir (outdir, 0755) != 0) { - fprintf (stderr, "Could not make output directory\n"); - exit (1); - } - printf ("Output directory is '%s'\n", outdir); - chdir (outdir); - g_free (outdir); - } - if (!synchronous) loop = g_main_loop_new (NULL, TRUE); - get_url (base); + get_url (url); if (!synchronous) g_main_loop_unref (loop); - soup_uri_free (base_uri); - return 0; } diff --git a/tests/proxy-test.c b/tests/proxy-test.c index ebdd2cc1..8f57c1f0 100644 --- a/tests/proxy-test.c +++ b/tests/proxy-test.c @@ -21,7 +21,7 @@ static SoupProxyTest tests[] = { { "GET -> 404", "/not-found", SOUP_STATUS_NOT_FOUND }, { "GET -> 401 -> 200", "/Basic/realm1/", SOUP_STATUS_OK }, { "GET -> 401 -> 401", "/Basic/realm2/", SOUP_STATUS_UNAUTHORIZED }, - { "GET -> 403", "http://www.example.com/", SOUP_STATUS_FORBIDDEN }, + { "GET -> 403", "http://no-such-hostname.xx/", SOUP_STATUS_FORBIDDEN }, }; static int ntests = sizeof (tests) / sizeof (tests[0]); diff --git a/tests/redirect-test.c b/tests/redirect-test.c index 9d5d9b86..9e21bd1d 100644 --- a/tests/redirect-test.c +++ b/tests/redirect-test.c @@ -82,9 +82,10 @@ static struct { { "HEAD", "/302", 302 }, { "HEAD", "/", 200 } } }, - /* POST should only automatically redirect on 302 and 303 */ + /* POST should only automatically redirect on 301, 302 and 303 */ { { { "POST", "/301", 301 }, + { "GET", "/", 200 }, { NULL } } }, { { { "POST", "/302", 302 }, { "GET", "/", 200 }, diff --git a/tests/resources/atom.xml b/tests/resources/atom.xml new file mode 100644 index 00000000..962ecf47 --- /dev/null +++ b/tests/resources/atom.xml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<feed xmlns="http://www.w3.org/2005/Atom" xmlns:planet="http://planet.libsouprocks.net/" xmlns:indexing="urn:atom-extension:indexing" indexing:index="no"><access:restriction xmlns:access="http://www.bloglines.com/about/specs/fac-1.0" relationship="deny"/> + <title>A small ATOM feed</title> + <updated>2009-07-02T10:27:44Z</updated> + <generator>kov</generator> + <author> + <name>Anonymous Coward</name> + </author> + <id>http://libsoup.rocks/atom.xml</id> + <link href="http://libsoup.rocks/atom.xml" rel="self" type="application/atom+xml"/> + <link href="http://libsoup.rocks/" rel="alternate"/> + + <entry xml:lang="en"> + <id>http://libsoup.rocks/so/much/</id> + <link href="http://libsoup.rocks/so/much/" rel="alternate" type="text/html"/> + <title>One post too many</title> + <summary>woo [...]</summary> + <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>woohoo</p></div> + </content> + <updated>2009-07-02T10:38:28Z</updated> + <category term="Category1"/> + <category term="Personal"/> + <author> + <name>kov</name> + </author> + <source> + <id>http://libsoup.rocks/blog</id> + <link href="http://libsoup.rocks/blog/feed" rel="self" type="application/atom+xml"/> + <link href="http://libsoup.rocks/blog" rel="alternate" type="text/html"/> + <subtitle>Just stuff to test libsoup</subtitle> + <title>Random stuff to test libsoup</title> + <updated>2009-07-02T00:38:29Z</updated> + </source> + </entry> +</feed> diff --git a/tests/resources/home.gif b/tests/resources/home.gif Binary files differnew file mode 100644 index 00000000..55e1d599 --- /dev/null +++ b/tests/resources/home.gif diff --git a/tests/resources/mbox b/tests/resources/mbox new file mode 100644 index 00000000..2e967278 --- /dev/null +++ b/tests/resources/mbox @@ -0,0 +1,64 @@ +From email@here Wed Jun 17 21:20:48 2009 +Return-path: <email@here> +Envelope-to: email@here +Delivery-date: Wed, 17 Jun 2009 21:20:48 -0300 +Received: from email by here.domain with local (Exim 4.69) + (envelope-from <email@here>) + id 1MH5N2-0008Lq-7c + for email@here; Wed, 17 Jun 2009 21:20:48 -0300 +To: email@here +Subject: This is just so that I have a mailbox +Message-Id: <E1MH5N2-0008Lq-7c@here.domain> +From: A Nice User <email@here> +Date: Wed, 17 Jun 2009 21:20:48 -0300 + +This is a dumb email. + +From email@here Wed Jun 17 21:20:48 2009 +Return-path: <email@here> +Envelope-to: email@here +Delivery-date: Wed, 17 Jun 2009 21:20:48 -0300 +Received: from email by here.domain with local (Exim 4.69) + (envelope-from <email@here>) + id 1MH5N2-0008Lq-7c + for email@here; Wed, 17 Jun 2009 21:20:48 -0300 +To: email@here +Subject: This is just so that I have a mailbox +Message-Id: <E1MH5N2-0008Lq-7c@here.domain> +From: A Nice User <email@here> +Date: Wed, 17 Jun 2009 21:20:48 -0300 + +This is a dumb email. + +From email@here Wed Jun 17 21:20:48 2009 +Return-path: <email@here> +Envelope-to: email@here +Delivery-date: Wed, 17 Jun 2009 21:20:48 -0300 +Received: from email by here.domain with local (Exim 4.69) + (envelope-from <email@here>) + id 1MH5N2-0008Lq-7c + for email@here; Wed, 17 Jun 2009 21:20:48 -0300 +To: email@here +Subject: This is just so that I have a mailbox +Message-Id: <E1MH5N2-0008Lq-7c@here.domain> +From: A Nice User <email@here> +Date: Wed, 17 Jun 2009 21:20:48 -0300 + +This is a dumb email. + +From email@here Wed Jun 17 21:20:48 2009 +Return-path: <email@here> +Envelope-to: email@here +Delivery-date: Wed, 17 Jun 2009 21:20:48 -0300 +Received: from email by here.domain with local (Exim 4.69) + (envelope-from <email@here>) + id 1MH5N2-0008Lq-7c + for email@here; Wed, 17 Jun 2009 21:20:48 -0300 +To: email@here +Subject: This is just so that I have a mailbox +Message-Id: <E1MH5N2-0008Lq-7c@here.domain> +From: A Nice User <email@here> +Date: Wed, 17 Jun 2009 21:20:48 -0300 + +This is a dumb email. + diff --git a/tests/resources/rss20.xml b/tests/resources/rss20.xml new file mode 100644 index 00000000..d64bddac --- /dev/null +++ b/tests/resources/rss20.xml @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<rss version="2.0"> + +<channel> + <title>A small RSS</title> + <link>http://libsoup.rocks/</link> + <language>en</language> + <description>A small RSS to test libsoup</description> + +<item> + <title>One post too many</title> + <guid isPermaLink="true">http://libsoup.rocks/so/much/</guid> + <link>http://libsoup.rocks/so/much/</link> + <description><p>woohoo</p></description> + <pubDate>Wed, 02 Jul 2009 10:26:28 +0000</pubDate> +</item> +<item> + <title>GCDS will rock</title> + <guid isPermaLink="true">http://libsoup.rocks/so/much/again/</guid> + <link>http://libsoup.rocks/so/much/again/</link> + <description><p>I mean, really.</p></description> + <pubDate>Wed, 02 Jul 2009 10:26:28 +0000</pubDate> +</item> + +</channel> +</rss> diff --git a/tests/resources/test.html b/tests/resources/test.html new file mode 100644 index 00000000..5a6cc0c2 --- /dev/null +++ b/tests/resources/test.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title></title> +</head> +<body> +<h1>GNOME!</h1> +</body> +</html> diff --git a/tests/sniffing-test.c b/tests/sniffing-test.c new file mode 100644 index 00000000..a5c95872 --- /dev/null +++ b/tests/sniffing-test.c @@ -0,0 +1,481 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org>. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libsoup/soup.h> + +#include "test-utils.h" + +SoupSession *session; +SoupURI *base_uri; +SoupMessageBody *chunk_data; + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + GError *error = NULL; + char *query_key; + char *contents; + gsize length, offset; + gboolean empty_response = FALSE; + + if (msg->method != SOUP_METHOD_GET) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_OK); + + if (query) { + query_key = g_hash_table_lookup (query, "chunked"); + if (query_key && g_str_equal (query_key, "yes")) { + soup_message_headers_set_encoding (msg->response_headers, + SOUP_ENCODING_CHUNKED); + } + + query_key = g_hash_table_lookup (query, "empty_response"); + if (query_key && g_str_equal (query_key, "yes")) + empty_response = TRUE; + } + + if (!strcmp (path, "/mbox")) { + if (empty_response) { + contents = g_strdup (""); + length = 0; + } else { + g_file_get_contents (SRCDIR "/resources/mbox", + &contents, &length, + &error); + } + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + soup_message_headers_append (msg->response_headers, + "Content-Type", "text/plain"); + } + + if (g_str_has_prefix (path, "/text_or_binary/")) { + char *base_name = g_path_get_basename (path); + char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); + + g_file_get_contents (file_name, + &contents, &length, + &error); + + g_free (base_name); + g_free (file_name); + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + soup_message_headers_append (msg->response_headers, + "Content-Type", "text/plain"); + } + + if (g_str_has_prefix (path, "/unknown/")) { + char *base_name = g_path_get_basename (path); + char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); + + g_file_get_contents (file_name, + &contents, &length, + &error); + + g_free (base_name); + g_free (file_name); + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + soup_message_headers_append (msg->response_headers, + "Content-Type", "UNKNOWN/unknown"); + } + + if (g_str_has_prefix (path, "/type/")) { + char **components = g_strsplit (path, "/", 4); + char *ptr; + + char *base_name = g_path_get_basename (path); + char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); + + g_file_get_contents (file_name, + &contents, &length, + &error); + + g_free (base_name); + g_free (file_name); + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + /* Hack to allow passing type in the URI */ + ptr = g_strrstr (components[2], "_"); + *ptr = '/'; + + soup_message_headers_append (msg->response_headers, + "Content-Type", components[2]); + g_strfreev (components); + } + + if (g_str_has_prefix (path, "/multiple_headers/")) { + char *base_name = g_path_get_basename (path); + char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); + + g_file_get_contents (file_name, + &contents, &length, + &error); + + g_free (base_name); + g_free (file_name); + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + soup_message_headers_append (msg->response_headers, + "Content-Type", "text/xml"); + soup_message_headers_append (msg->response_headers, + "Content-Type", "text/plain"); + } + + for (offset = 0; offset < length; offset += 500) { + soup_message_body_append (msg->response_body, + SOUP_MEMORY_COPY, + contents + offset, + MIN(500, length - offset)); + } + soup_message_body_complete (msg->response_body); +} + +static gboolean +unpause_msg (gpointer data) +{ + SoupMessage *msg = (SoupMessage*)data; + debug_printf (2, " unpause\n"); + soup_session_unpause_message (session, msg); + return FALSE; +} + + +static void +content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data) +{ + gboolean should_pause = GPOINTER_TO_INT (data); + + debug_printf (2, " content-sniffed -> %s\n", content_type); + + if (g_object_get_data (G_OBJECT (msg), "got-chunk")) { + debug_printf (1, " got-chunk got emitted before content-sniffed\n"); + errors++; + } + + g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE)); + + if (should_pause) { + debug_printf (2, " pause\n"); + soup_session_pause_message (session, msg); + g_idle_add (unpause_msg, msg); + } +} + +static void +got_headers (SoupMessage *msg, gpointer data) +{ + gboolean should_pause = GPOINTER_TO_INT (data); + + debug_printf (2, " got-headers\n"); + + if (g_object_get_data (G_OBJECT (msg), "content-sniffed")) { + debug_printf (1, " content-sniffed got emitted before got-headers\n"); + errors++; + } + + g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE)); + + if (should_pause) { + debug_printf (2, " pause\n"); + soup_session_pause_message (session, msg); + g_idle_add (unpause_msg, msg); + } +} + +static void +got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data) +{ + gboolean should_accumulate = GPOINTER_TO_INT (data); + + debug_printf (2, " got-chunk\n"); + + g_object_set_data (G_OBJECT (msg), "got-chunk", GINT_TO_POINTER (TRUE)); + + if (!should_accumulate) { + if (!chunk_data) + chunk_data = soup_message_body_new (); + soup_message_body_append_buffer (chunk_data, chunk); + } +} + +static void +finished (SoupSession *session, SoupMessage *msg, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + g_main_loop_quit (loop); +} + +static void +do_signals_test (gboolean should_content_sniff, + gboolean should_pause, + gboolean should_accumulate, + gboolean chunked_encoding, + gboolean empty_response) +{ + SoupURI *uri = soup_uri_new_with_base (base_uri, "/mbox"); + SoupMessage *msg = soup_message_new_from_uri ("GET", uri); + GMainLoop *loop = g_main_loop_new (NULL, TRUE); + char *contents; + gsize length; + GError *error = NULL; + SoupBuffer *body = NULL; + + debug_printf (1, "do_signals_test(%ssniff, %spause, %saccumulate, %schunked, %sempty)\n", + should_content_sniff ? "" : "!", + should_pause ? "" : "!", + should_accumulate ? "" : "!", + chunked_encoding ? "" : "!", + empty_response ? "" : "!"); + + if (chunked_encoding) + soup_uri_set_query (uri, "chunked=yes"); + + if (empty_response) { + if (uri->query) { + char *tmp = uri->query; + uri->query = g_strdup_printf("%s&empty_response=yes", tmp); + g_free (tmp); + } else + soup_uri_set_query (uri, "empty_response=yes"); + } + + soup_message_set_uri (msg, uri); + + soup_message_body_set_accumulate (msg->response_body, should_accumulate); + + g_object_connect (msg, + "signal::got-headers", got_headers, GINT_TO_POINTER (should_pause), + "signal::got-chunk", got_chunk, GINT_TO_POINTER (should_accumulate), + "signal::content_sniffed", content_sniffed, GINT_TO_POINTER (should_pause), + NULL); + + g_object_ref (msg); + soup_session_queue_message (session, msg, finished, loop); + + g_main_loop_run (loop); + + if (!should_content_sniff && + g_object_get_data (G_OBJECT (msg), "content-sniffed")) { + debug_printf (1, " content-sniffed got emitted without a sniffer\n"); + errors++; + } else if (should_content_sniff && + !g_object_get_data (G_OBJECT (msg), "content-sniffed")) { + debug_printf (1, " content-sniffed did not get emitted\n"); + errors++; + } + + if (empty_response) { + contents = g_strdup (""); + length = 0; + } else { + g_file_get_contents (SRCDIR "/resources/mbox", + &contents, &length, + &error); + } + + if (error) { + g_error ("%s", error->message); + g_error_free (error); + exit (1); + } + + if (!should_accumulate && chunk_data) { + body = soup_message_body_flatten (chunk_data); + soup_message_body_free (chunk_data); + chunk_data = NULL; + } else if (msg->response_body) + body = soup_message_body_flatten (msg->response_body); + + if (body && body->length != length) { + debug_printf (1, " lengths do not match\n"); + errors++; + } + + if (body && memcmp (body->data, contents, length)) { + debug_printf (1, " downloaded data does not match\n"); + errors++; + } + + if (body) + soup_buffer_free (body); + + soup_uri_free (uri); + g_object_unref (msg); + g_main_loop_unref (loop); +} + +static void +sniffing_content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpointer data) +{ + char *expected_type = (char*)data; + + if (strcmp (content_type, expected_type)) { + debug_printf (1, " sniffing failed! expected %s, got %s\n", + expected_type, content_type); + errors++; + } +} + +static void +test_sniffing (const char *path, const char *expected_type) +{ + SoupURI *uri = soup_uri_new_with_base (base_uri, path); + SoupMessage *msg = soup_message_new_from_uri ("GET", uri); + GMainLoop *loop = g_main_loop_new (NULL, TRUE); + + debug_printf (1, "test_sniffing(\"%s\", \"%s\")\n", path, expected_type); + + g_object_connect (msg, + "signal::content_sniffed", sniffing_content_sniffed, expected_type, + NULL); + + g_object_ref (msg); + + soup_session_queue_message (session, msg, finished, loop); + + g_main_loop_run (loop); + + soup_uri_free (uri); + g_object_unref (msg); + g_main_loop_unref (loop); +} + +int +main (int argc, char **argv) +{ + SoupServer *server; + SoupContentSniffer *sniffer; + + test_init (argc, argv, NULL); + + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + base_uri = soup_uri_new ("http://127.0.0.1/"); + soup_uri_set_port (base_uri, soup_server_get_port (server)); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + /* No sniffer, no content_sniffed should be emitted */ + do_signals_test (FALSE, FALSE, FALSE, FALSE, FALSE); + do_signals_test (FALSE, FALSE, FALSE, TRUE, FALSE); + do_signals_test (FALSE, FALSE, TRUE, FALSE, FALSE); + do_signals_test (FALSE, FALSE, TRUE, TRUE, FALSE); + + do_signals_test (FALSE, TRUE, TRUE, FALSE, FALSE); + do_signals_test (FALSE, TRUE, TRUE, TRUE, FALSE); + do_signals_test (FALSE, TRUE, FALSE, FALSE, FALSE); + do_signals_test (FALSE, TRUE, FALSE, TRUE, FALSE); + + /* Tests that the signals are correctly emitted for empty + * responses; see + * http://bugzilla.gnome.org/show_bug.cgi?id=587907 */ + + do_signals_test (FALSE, TRUE, TRUE, FALSE, TRUE); + do_signals_test (FALSE, TRUE, TRUE, TRUE, TRUE); + + sniffer = soup_content_sniffer_new (); + soup_session_add_feature (session, (SoupSessionFeature*)sniffer); + + /* Now, with a sniffer, content_sniffed must be emitted after + * got-headers, and before got-chunk. + */ + do_signals_test (TRUE, FALSE, FALSE, FALSE, FALSE); + do_signals_test (TRUE, FALSE, FALSE, TRUE, FALSE); + do_signals_test (TRUE, FALSE, TRUE, FALSE, FALSE); + do_signals_test (TRUE, FALSE, TRUE, TRUE, FALSE); + + do_signals_test (TRUE, TRUE, TRUE, FALSE, FALSE); + do_signals_test (TRUE, TRUE, TRUE, TRUE, FALSE); + do_signals_test (TRUE, TRUE, FALSE, FALSE, FALSE); + do_signals_test (TRUE, TRUE, FALSE, TRUE, FALSE); + + /* Empty response tests */ + do_signals_test (TRUE, TRUE, TRUE, FALSE, TRUE); + do_signals_test (TRUE, TRUE, TRUE, TRUE, TRUE); + + /* Test the text_or_binary sniffing path */ + + /* GIF is a 'safe' type */ + test_sniffing ("/text_or_binary/home.gif", "image/gif"); + + /* With our current code, no sniffing is done using GIO, so + * the mbox will be identified as text/plain; should we change + * this? + */ + test_sniffing ("/text_or_binary/mbox", "text/plain"); + + /* HTML is considered unsafe for this algorithm, since it is + * scriptable, so going from text/plain to text/html is + * considered 'privilege escalation' + */ + test_sniffing ("/text_or_binary/test.html", "text/plain"); + + /* Test the unknown sniffing path */ + + test_sniffing ("/unknown/test.html", "text/html"); + test_sniffing ("/unknown/home.gif", "image/gif"); + test_sniffing ("/unknown/mbox", "application/mbox"); + + /* Test the XML sniffing path */ + + test_sniffing ("/type/text_xml/home.gif", "text/xml"); + test_sniffing ("/type/anice_type+xml/home.gif", "anice/type+xml"); + test_sniffing ("/type/application_xml/home.gif", "application/xml"); + + /* Test the image sniffing path */ + + test_sniffing ("/type/image_png/home.gif", "image/gif"); + + /* Test the feed or html path */ + + test_sniffing ("/type/text_html/test.html", "text/html"); + test_sniffing ("/type/text_html/rss20.xml", "application/rss+xml"); + test_sniffing ("/type/text_html/atom.xml", "application/atom+xml"); + + /* The spec tells us to only use the last Content-Type header */ + + test_sniffing ("/multiple_headers/home.gif", "image/gif"); + + soup_uri_free (base_uri); + + test_cleanup (); + return errors != 0; +} |