[libsoup] session: do not emit tls interaction signals for preconnect requests



commit f4c7545e59655a3fb3f7f68ee269c8456c54aa5c
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Wed Jun 16 12:09:40 2021 +0200

    session: do not emit tls interaction signals for preconnect requests
    
    Keep the tls pending interactions pending and transfer them to the
    actual message when the connection is stolen.

 libsoup/soup-message-private.h |   6 ++
 libsoup/soup-message.c         |  97 +++++++++++++++++++++++
 libsoup/soup-session.c         |   5 +-
 tests/ssl-test.c               | 174 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 280 insertions(+), 2 deletions(-)
---
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index 82bf7d89..8b3ad56f 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -100,6 +100,12 @@ GList *soup_message_get_disabled_features (SoupMessage *msg);
 SoupConnection *soup_message_get_connection (SoupMessage    *msg);
 void            soup_message_set_connection (SoupMessage    *msg,
                                             SoupConnection *conn);
+void            soup_message_transfer_connection (SoupMessage *preconnect_msg,
+                                                  SoupMessage *msg);
+void            soup_message_set_is_preconnect   (SoupMessage *msg,
+                                                  gboolean     is_preconnect);
+gboolean        soup_message_has_pending_tls_cert_request      (SoupMessage *msg);
+gboolean        soup_message_has_pending_tls_cert_pass_request (SoupMessage *msg);
 
 SoupClientMessageIO *soup_message_get_io_data (SoupMessage             *msg);
 
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index 0f977816..41aed806 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -92,12 +92,15 @@ typedef struct {
 
         GTlsCertificate *tls_client_certificate;
         GTask *pending_tls_cert_request;
+        GTlsClientConnection *pending_tls_cert_conn;
         GTask *pending_tls_cert_pass_request;
+        GTlsPassword *pending_tls_cert_password;
 
        SoupMessagePriority priority;
 
        gboolean is_top_level_navigation;
         gboolean is_options_ping;
+        gboolean is_preconnect;
         gboolean force_http1;
         gboolean is_misdirected_retry;
         guint    last_connection_id;
@@ -181,11 +184,13 @@ soup_message_finalize (GObject *object)
                 g_task_return_int (priv->pending_tls_cert_request, G_TLS_INTERACTION_FAILED);
                 g_object_unref (priv->pending_tls_cert_request);
         }
+        g_clear_object (&priv->pending_tls_cert_conn);
 
         if (priv->pending_tls_cert_pass_request) {
                 g_task_return_int (priv->pending_tls_cert_pass_request, G_TLS_INTERACTION_FAILED);
                 g_object_unref (priv->pending_tls_cert_pass_request);
         }
+        g_clear_object (&priv->pending_tls_cert_password);
 
        soup_message_set_connection (msg, NULL);
 
@@ -1476,6 +1481,15 @@ re_emit_request_certificate (SoupMessage          *msg,
 
         priv->pending_tls_cert_request = g_object_ref (task);
 
+        /* Skip interaction for preconnect requests, keep the operation
+         * pending that will be handled by the new message once the
+         * connection is transferred.
+         */
+        if (priv->is_preconnect) {
+                priv->pending_tls_cert_conn = g_object_ref (tls_conn);
+                return TRUE;
+        }
+
         g_signal_emit (msg, signals[REQUEST_CERTIFICATE], 0, tls_conn, &handled);
         if (!handled)
                 g_clear_object (&priv->pending_tls_cert_request);
@@ -1493,6 +1507,15 @@ re_emit_request_certificate_password (SoupMessage  *msg,
 
         priv->pending_tls_cert_pass_request = g_object_ref (task);
 
+        /* Skip interaction for preconnect requests, keep the operation
+         * pending that will be handled by the new message once the
+         * connection is transferred.
+         */
+        if (priv->is_preconnect) {
+                priv->pending_tls_cert_password = g_object_ref (password);
+                return TRUE;
+        }
+
         g_signal_emit (msg, signals[REQUEST_CERTIFICATE_PASSWORD], 0, password, &handled);
         if (!handled)
                 g_clear_object (&priv->pending_tls_cert_pass_request);
@@ -1580,6 +1603,80 @@ soup_message_set_connection (SoupMessage    *msg,
                                  msg, G_CONNECT_SWAPPED);
 }
 
+void
+soup_message_set_is_preconnect (SoupMessage *msg,
+                                gboolean     is_preconnect)
+{
+        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
+
+        priv->is_preconnect = is_preconnect;
+}
+
+void
+soup_message_transfer_connection (SoupMessage *preconnect_msg,
+                                  SoupMessage *msg)
+{
+        SoupMessagePrivate *preconnect_priv = soup_message_get_instance_private (preconnect_msg);
+        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
+        GTlsCertificate *client_certificate = NULL;
+
+        g_assert (preconnect_priv->is_preconnect);
+        g_assert (!priv->connection);
+        client_certificate = g_steal_pointer (&priv->tls_client_certificate);
+        soup_message_set_connection (msg, preconnect_priv->connection);
+
+        /* If connection has pending interactions, transfer them too */
+        g_assert (!priv->pending_tls_cert_request);
+        priv->pending_tls_cert_request = g_steal_pointer (&preconnect_priv->pending_tls_cert_request);
+        if (priv->pending_tls_cert_request) {
+                if (client_certificate) {
+                        soup_connection_complete_tls_certificate_request (priv->connection,
+                                                                          client_certificate,
+                                                                          g_steal_pointer 
(&priv->pending_tls_cert_request));
+                        g_object_unref (client_certificate);
+                } else {
+                        gboolean handled = FALSE;
+
+                        g_signal_emit (msg, signals[REQUEST_CERTIFICATE], 0, 
preconnect_priv->pending_tls_cert_conn, &handled);
+                        g_clear_object (&preconnect_priv->pending_tls_cert_conn);
+                        if (!handled)
+                                g_clear_object (&priv->pending_tls_cert_request);
+                }
+        } else if (client_certificate) {
+                soup_connection_set_tls_client_certificate (priv->connection, client_certificate);
+                g_object_unref (client_certificate);
+        }
+
+        g_assert (!priv->pending_tls_cert_pass_request);
+        priv->pending_tls_cert_pass_request = g_steal_pointer 
(&preconnect_priv->pending_tls_cert_pass_request);
+        if (priv->pending_tls_cert_pass_request) {
+                gboolean handled = FALSE;
+
+                g_signal_emit (msg, signals[REQUEST_CERTIFICATE_PASSWORD], 0, 
preconnect_priv->pending_tls_cert_password, &handled);
+                g_clear_object (&preconnect_priv->pending_tls_cert_password);
+                if (!handled)
+                        g_clear_object (&priv->pending_tls_cert_pass_request);
+        }
+
+        soup_message_set_connection (preconnect_msg, NULL);
+}
+
+gboolean
+soup_message_has_pending_tls_cert_request (SoupMessage *msg)
+{
+        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
+
+        return priv->pending_tls_cert_request != NULL;
+}
+
+gboolean
+soup_message_has_pending_tls_cert_pass_request (SoupMessage *msg)
+{
+        SoupMessagePrivate *priv = soup_message_get_instance_private (msg);
+
+        return priv->pending_tls_cert_pass_request != NULL;
+}
+
 /**
  * soup_message_cleanup_response:
  * @msg: a #SoupMessage
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 4789fc63..9c674e57 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -1360,6 +1360,7 @@ soup_session_append_queue_item (SoupSession        *session,
 
         soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_FETCH_START);
        soup_message_cleanup_response (msg);
+        soup_message_set_is_preconnect (msg, FALSE);
 
        item = soup_message_queue_item_new (session, msg, async, cancellable);
        g_queue_insert_sorted (priv->queue,
@@ -1786,8 +1787,7 @@ steal_preconnection (SoupSession          *session,
         if (!preconnect_item->connect_only || preconnect_item->state != SOUP_MESSAGE_CONNECTING)
                 return FALSE;
 
-        soup_message_set_connection (item->msg, conn);
-        soup_message_set_connection (preconnect_item->msg, NULL);
+        soup_message_transfer_connection (preconnect_item->msg, item->msg);
         g_assert (preconnect_item->related == NULL);
         preconnect_item->related = soup_message_queue_item_ref (item);
 
@@ -4040,6 +4040,7 @@ soup_session_preconnect_async (SoupSession        *session,
         item = soup_session_append_queue_item (session, msg, TRUE, cancellable);
         item->connect_only = TRUE;
         item->io_priority = io_priority;
+        soup_message_set_is_preconnect (msg, TRUE);
 
         task = g_task_new (session, item->cancellable, callback, user_data);
         g_task_set_priority (task, io_priority);
diff --git a/tests/ssl-test.c b/tests/ssl-test.c
index 4f183a9a..08563515 100644
--- a/tests/ssl-test.c
+++ b/tests/ssl-test.c
@@ -1,6 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
 
 #include "test-utils.h"
+#include "soup-message-private.h"
 #include "soup-server-message-private.h"
 
 #if HAVE_GNUTLS
@@ -505,6 +506,178 @@ do_tls_interaction_msg_test (gconstpointer data)
         g_object_unref (wrong_certificate);
 }
 
+static gboolean
+preconnect_request_certificate (SoupMessage          *msg,
+                                GTlsClientConnection *conn,
+                                gboolean             *called)
+{
+        *called = TRUE;
+
+        return FALSE;
+}
+
+static gboolean
+preconnect_request_certificate_password (SoupMessage  *msg,
+                                         GTlsPassword *password,
+                                         gboolean     *called)
+{
+        *called = TRUE;
+
+        return FALSE;
+}
+
+static void
+preconnect_finished_cb (SoupSession  *session,
+                        GAsyncResult *result,
+                        gboolean     *preconnect_finished)
+{
+        g_assert_true (soup_session_preconnect_finish (session, result, NULL));
+        *preconnect_finished = TRUE;
+}
+
+static void
+do_tls_interaction_preconnect_test (gconstpointer data)
+{
+        SoupServer *server = (SoupServer *)data;
+        SoupSession *session;
+        SoupMessage *preconnect_msg;
+        SoupMessage *msg;
+        GBytes *body;
+        GTlsDatabase *tls_db;
+        GTlsCertificate *certificate;
+        GError *error = NULL;
+        gboolean preconnect_request_cert_called = FALSE;
+        gboolean preconnect_request_cert_pass_called = FALSE;
+        gboolean preconnect_finished = FALSE;
+
+        SOUP_TEST_SKIP_IF_NO_TLS;
+
+        session = soup_test_session_new (NULL);
+        tls_db = soup_session_get_tls_database (session);
+        g_object_set (server, "tls-database", tls_db, "tls-auth-mode", G_TLS_AUTHENTICATION_REQUIRED, NULL);
+        g_object_get (server, "tls-certificate", &certificate, NULL);
+        g_signal_connect (server, "request-started",
+                          G_CALLBACK (server_request_started),
+                          session);
+
+        /* Start a preconnect until it get blocked on tls interaction */
+        preconnect_msg = soup_message_new_from_uri ("HEAD", uri);
+        g_signal_connect (preconnect_msg, "request-certificate",
+                          G_CALLBACK (preconnect_request_certificate),
+                          &preconnect_request_cert_called);
+        soup_session_preconnect_async (session, preconnect_msg, G_PRIORITY_DEFAULT, NULL, NULL, NULL);
+        while (!soup_message_has_pending_tls_cert_request (preconnect_msg))
+                g_main_context_iteration (NULL, TRUE);
+
+        /* New message should steal the preconnect connection */
+        msg = soup_message_new_from_uri ("GET", uri);
+        g_signal_connect (msg, "request-certificate",
+                          G_CALLBACK (request_certificate_cb),
+                          certificate);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_assert_false (preconnect_request_cert_called);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+        g_object_unref (preconnect_msg);
+
+        soup_session_abort (session);
+
+        /* Preconnect finishes if we set the certificate before */
+        preconnect_msg = soup_message_new_from_uri ("HEAD", uri);
+        g_signal_connect (preconnect_msg, "request-certificate",
+                          G_CALLBACK (preconnect_request_certificate),
+                          &preconnect_request_cert_called);
+        soup_message_set_tls_client_certificate (preconnect_msg, certificate);
+        soup_session_preconnect_async (session, preconnect_msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished_cb,
+                                       &preconnect_finished);
+        while (!preconnect_finished)
+                g_main_context_iteration (NULL, TRUE);
+        g_assert_false (preconnect_request_cert_called);
+        g_object_unref (preconnect_msg);
+        /* New request will use the idle connection without having to provide a certificate */
+        msg = soup_message_new_from_uri ("GET", uri);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        soup_session_abort (session);
+
+        /* request-certificate signal is not emitted either if the message stealing the
+         * preconnect connection has a certificate set.
+         */
+        preconnect_msg = soup_message_new_from_uri ("HEAD", uri);
+        g_signal_connect (preconnect_msg, "request-certificate",
+                          G_CALLBACK (preconnect_request_certificate),
+                          &preconnect_request_cert_called);
+        soup_session_preconnect_async (session, preconnect_msg, G_PRIORITY_DEFAULT, NULL, NULL, NULL);
+        while (!soup_message_has_pending_tls_cert_request (preconnect_msg))
+                g_main_context_iteration (NULL, TRUE);
+
+        /* New message should steal the preconnect connection */
+        msg = soup_message_new_from_uri ("GET", uri);
+        soup_message_set_tls_client_certificate (msg, certificate);
+        body = soup_test_session_async_send (session, msg, NULL, &error);
+        g_assert_no_error (error);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_assert_false (preconnect_request_cert_called);
+        g_clear_error (&error);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+        g_object_unref (preconnect_msg);
+
+        soup_session_abort (session);
+
+        /* Currently on the gnutls backend supports pkcs#11 */
+        if (g_strcmp0 (g_type_name (G_TYPE_FROM_INSTANCE (g_tls_backend_get_default ())), 
"GTlsBackendGnutls") == 0) {
+                GTlsCertificate *pkcs11_certificate;
+
+                pkcs11_certificate = g_tls_certificate_new_from_pkcs11_uris (
+                        
"pkcs11:model=mock;serial=1;token=Mock%20Certificate;id=%4D%6F%63%6B%20%43%65%72%74%69%66%69%63%61%74%65;object=Mock%20Certificate;type=cert",
+                        
"pkcs11:model=mock;serial=1;token=Mock%20Certificate;id=%4D%6F%63%6B%20%50%72%69%76%61%74%65%20%4B%65%79;object=Mock%20Private%20Key;type=private",
+                        &error
+                );
+                g_assert_no_error (error);
+
+                preconnect_msg = soup_message_new_from_uri ("HEAD", uri);
+                g_signal_connect (preconnect_msg, "request-certificate-password",
+                                  G_CALLBACK (preconnect_request_certificate_password),
+                                  &preconnect_request_cert_pass_called);
+                soup_message_set_tls_client_certificate (preconnect_msg, pkcs11_certificate);
+                soup_session_preconnect_async (session, preconnect_msg, G_PRIORITY_DEFAULT, NULL, NULL, 
NULL);
+                while (!soup_message_has_pending_tls_cert_pass_request (preconnect_msg))
+                        g_main_context_iteration (NULL, TRUE);
+
+                /* New message should steal the preconnect connection */
+                msg = soup_message_new_from_uri ("GET", uri);
+                g_signal_connect (msg, "request-certificate-password",
+                                  G_CALLBACK (request_certificate_password_cb),
+                                  "ABC123");
+                body = soup_test_session_async_send (session, msg, NULL, &error);
+                g_assert_no_error (error);
+                soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+                g_assert_false (preconnect_request_cert_pass_called);
+                g_clear_error (&error);
+                g_bytes_unref (body);
+                g_object_unref (msg);
+                g_object_unref (preconnect_msg);
+
+                g_object_unref (pkcs11_certificate);
+        }
+
+        g_object_set (server, "tls-database", NULL, "tls-auth-mode", G_TLS_AUTHENTICATION_NONE, NULL);
+        g_signal_handlers_disconnect_by_data (server, session);
+
+        soup_test_session_abort_unref (session);
+        g_object_unref (certificate);
+}
+
 static void
 server_handler (SoupServer        *server,
                SoupServerMessage *msg,
@@ -544,6 +717,7 @@ main (int argc, char **argv)
 
        g_test_add_data_func ("/ssl/tls-interaction", server, do_tls_interaction_test);
         g_test_add_data_func ("/ssl/tls-interaction-msg", server, do_tls_interaction_msg_test);
+        g_test_add_data_func ("/ssl/tls-interaction/preconnect", server, do_tls_interaction_preconnect_test);
 
        for (i = 0; i < G_N_ELEMENTS (strictness_tests); i++) {
                g_test_add_data_func (strictness_tests[i].name,


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