Skip to content
Commits on Source (16)
Changes in libsoup from 3.2.1 to 3.2.2:
* Various HTTP/2 Fixes: [Carlos Garcia Campos]
* Fix `content-sniffed` not being emitted for resources without content
* Fix leak of SoupServerConnection when stolen
Changes in libsoup from 3.2.0 to 3.2.1:
* When built against nghttp2 1.50.0+ be relaxed about header whitespace [Carlos Garcia Campos]
......
......@@ -77,6 +77,7 @@ typedef struct {
GTask *close_task;
gboolean session_terminated;
gboolean goaway_sent;
gboolean ever_used;
guint in_callback;
} SoupClientMessageIOHTTP2;
......@@ -234,6 +235,7 @@ soup_http2_message_data_can_be_restarted (SoupHTTP2MessageData *data,
return TRUE;
return data->state < STATE_READ_DATA_START &&
data->io->ever_used &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
......@@ -724,12 +726,15 @@ on_frame_recv_callback (nghttp2_session *session,
g_assert_not_reached ();
}
soup_message_got_headers (data->msg);
if (soup_message_get_status (data->msg) == SOUP_STATUS_NO_CONTENT || frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
h2_debug (io, data, "Stream done");
advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA);
advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA_START);
if (soup_message_has_content_sniffer (data->msg))
soup_message_content_sniffed (data->msg, "text/plain", NULL);
advance_state_from (data, STATE_READ_DATA_START, STATE_READ_DATA);
}
soup_message_got_headers (data->msg);
break;
}
case NGHTTP2_DATA:
......@@ -1072,7 +1077,16 @@ on_data_source_read_callback (nghttp2_session *session,
nghttp2_data_source *source,
void *user_data)
{
SoupClientMessageIOHTTP2 *io = user_data;
SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, stream_id);
h2_debug (io, data, "[SEND_BODY] stream_id=%u, paused=%d", stream_id, data ? data->paused : 0);
if (!data) {
/* This can happen in case of cancellation */
return 0;
}
data->io->in_callback++;
if (!data->item->async) {
......@@ -1414,6 +1428,7 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
gpointer completion_data;
SoupMessageIOCompletion completion;
gboolean is_closed;
SoupConnection *conn;
data = get_data_for_message (io, msg);
......@@ -1429,9 +1444,9 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
is_closed = nghttp2_session_get_stream_user_data (io->session, data->stream_id) == NULL;
nghttp2_session_set_stream_user_data (io->session, data->stream_id, NULL);
if (!io->is_shutdown && !is_closed) {
SoupConnection *conn;
conn = g_weak_ref_get (&io->conn);
if (!io->is_shutdown && !is_closed) {
NGCHECK (nghttp2_submit_rst_stream (io->session, NGHTTP2_FLAG_NONE, data->stream_id,
completion == SOUP_MESSAGE_IO_COMPLETE ? NGHTTP2_NO_ERROR : NGHTTP2_CANCEL));
soup_http2_message_data_close (data);
......@@ -1441,11 +1456,8 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
if (!g_hash_table_add (io->closed_messages, data))
g_warn_if_reached ();
conn = g_weak_ref_get (&io->conn);
if (conn) {
if (conn)
soup_connection_set_in_use (conn, TRUE);
g_object_unref (conn);
}
io_try_write (io, !io->async);
} else {
......@@ -1460,6 +1472,8 @@ soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
if (io->is_shutdown)
soup_client_message_io_http2_terminate_session (io);
g_clear_object (&conn);
}
static void
......@@ -1563,6 +1577,7 @@ client_stream_eof (SoupClientInputStream *stream,
h2_debug (io, data, "Client stream EOF");
soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END);
advance_state_from (data, STATE_READ_DATA, STATE_READ_DONE);
io->ever_used = TRUE;
g_signal_handlers_disconnect_by_func (stream, client_stream_eof, msg);
soup_message_got_body (data->msg);
}
......
......@@ -925,6 +925,14 @@ io_run (SoupServerMessageIOHTTP1 *server_io)
SOUP_MESSAGE_IO_STATE_DONE,
SOUP_MESSAGE_IO_STATE_DONE,
&error);
if (soup_server_message_get_io_data (msg) != (SoupServerMessageIO *)server_io) {
g_object_unref (msg);
g_clear_error (&error);
return;
}
server_io->in_io_run = FALSE;
if (success) {
......
......@@ -108,10 +108,12 @@ soup_server_connection_finalize (GObject *object)
SoupServerConnection *conn = SOUP_SERVER_CONNECTION (object);
SoupServerConnectionPrivate *priv = soup_server_connection_get_instance_private (conn);
if (priv->conn)
if (priv->conn) {
disconnect_internal (conn);
g_clear_pointer (&priv->io_data, soup_server_message_io_destroy);
} else {
g_clear_object (&priv->socket);
g_clear_pointer (&priv->io_data, soup_server_message_io_destroy);
}
g_clear_object (&priv->iostream);
......@@ -549,9 +551,10 @@ soup_server_connection_steal (SoupServerConnection *conn)
priv = soup_server_connection_get_instance_private (conn);
stream = priv->io_data ? soup_server_message_io_steal (priv->io_data) : NULL;
if (stream) {
if (stream && priv->socket) {
g_object_set_data_full (G_OBJECT (stream), "GSocket",
priv->socket, g_object_unref);
g_object_ref (priv->socket),
g_object_unref);
}
/* Cache local and remote address */
......@@ -562,6 +565,8 @@ soup_server_connection_steal (SoupServerConnection *conn)
g_clear_object (&priv->conn);
g_clear_object (&priv->iostream);
g_signal_emit (conn, signals[DISCONNECTED], 0);
return stream;
}
......
......@@ -1105,7 +1105,6 @@ soup_server_message_steal_connection (SoupServerMessage *msg)
g_object_ref (msg);
stream = soup_server_connection_steal (msg->conn);
g_signal_handlers_disconnect_by_data (msg, msg->conn);
connection_disconnected (msg);
g_object_unref (msg);
return stream;
......
......@@ -69,8 +69,6 @@
**/
typedef struct {
gboolean disposed;
GTlsDatabase *tlsdb;
GTlsInteraction *tls_interaction;
gboolean tlsdb_use_default;
......@@ -88,6 +86,7 @@ typedef struct {
GQueue *queue;
GMutex queue_sources_mutex;
GHashTable *queue_sources;
gint num_async_items;
guint in_async_run_queue;
gboolean needs_queue_sort;
......@@ -169,7 +168,7 @@ G_DEFINE_QUARK (soup-session-error-quark, soup_session_error)
typedef struct {
GSource source;
SoupSession* session;
GWeakRef session;
guint num_items;
} SoupMessageQueueSource;
......@@ -178,18 +177,33 @@ queue_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
SoupSession *session = ((SoupMessageQueueSource *)source)->session;
SoupMessageQueueSource *queue_source = (SoupMessageQueueSource *)source;
SoupSession *session = g_weak_ref_get (&queue_source->session);
if (!session)
return G_SOURCE_REMOVE;
g_source_set_ready_time (source, -1);
async_run_queue (session);
g_object_unref (session);
return G_SOURCE_CONTINUE;
}
static void
queue_finalize (GSource *source)
{
SoupMessageQueueSource *queue_source = (SoupMessageQueueSource *)source;
g_weak_ref_clear (&queue_source->session);
}
static GSourceFuncs queue_source_funcs = {
NULL, //queue_prepare,
NULL, //queue_check,
queue_dispatch,
NULL, NULL, NULL
queue_finalize,
NULL, NULL
};
static void
......@@ -199,13 +213,16 @@ soup_session_add_queue_source (SoupSession *session,
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
SoupMessageQueueSource *queue_source;
if (!priv->queue_sources)
priv->queue_sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_source_unref);
queue_source = g_hash_table_lookup (priv->queue_sources, context);
if (!queue_source) {
GSource *source;
source = g_source_new (&queue_source_funcs, sizeof (SoupMessageQueueSource));
queue_source = (SoupMessageQueueSource *)source;
queue_source->session = session;
g_weak_ref_init (&queue_source->session, session);
queue_source->num_items = 0;
g_source_set_name (source, "SoupMessageQueue");
g_source_set_can_recurse (source, TRUE);
......@@ -214,7 +231,6 @@ soup_session_add_queue_source (SoupSession *session,
}
queue_source->num_items++;
}
static void
......@@ -223,7 +239,7 @@ soup_session_add_queue_source_for_item (SoupSession *session,
{
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
if (item->context == priv->context)
if (!item->async)
return;
g_mutex_lock (&priv->queue_sources_mutex);
......@@ -255,6 +271,9 @@ soup_session_remove_queue_source_for_item (SoupSession *session,
{
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
if (!item->async)
return;
if (item->context == priv->context)
return;
......@@ -273,8 +292,6 @@ soup_session_init (SoupSession *session)
g_mutex_init (&priv->queue_mutex);
priv->queue = g_queue_new ();
g_mutex_init (&priv->queue_sources_mutex);
priv->queue_sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_source_unref);
soup_session_add_queue_source (session, priv->context);
priv->io_timeout = priv->idle_timeout = 60;
......@@ -313,14 +330,14 @@ soup_session_dispose (GObject *object)
SoupSession *session = SOUP_SESSION (object);
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
priv->disposed = TRUE;
soup_session_abort (session);
g_warn_if_fail (soup_connection_manager_get_num_conns (priv->conn_manager) == 0);
while (priv->features)
soup_session_remove_feature (session, priv->features->data);
g_hash_table_foreach (priv->queue_sources, (GHFunc)destroy_queue_source, NULL);
if (priv->queue_sources)
g_hash_table_foreach (priv->queue_sources, (GHFunc)destroy_queue_source, NULL);
G_OBJECT_CLASS (soup_session_parent_class)->dispose (object);
}
......@@ -334,7 +351,7 @@ soup_session_finalize (GObject *object)
g_warn_if_fail (g_queue_is_empty (priv->queue));
g_queue_free (priv->queue);
g_mutex_clear (&priv->queue_mutex);
g_hash_table_destroy (priv->queue_sources);
g_clear_pointer (&priv->queue_sources, g_hash_table_destroy);
g_mutex_clear (&priv->queue_sources_mutex);
g_main_context_unref (priv->context);
......@@ -1322,6 +1339,9 @@ soup_session_append_queue_item (SoupSession *session,
soup_session_add_queue_source_for_item (session, item);
if (async)
g_atomic_int_inc (&priv->num_async_items);
if (!soup_message_query_flags (msg, SOUP_MESSAGE_NO_REDIRECT)) {
soup_message_add_header_handler (
msg, "got_body", "Location",
......@@ -1410,6 +1430,9 @@ soup_session_unqueue_item (SoupSession *session,
soup_session_remove_queue_source_for_item (session, item);
if (item->async)
g_atomic_int_dec_and_test (&priv->num_async_items);
/* g_signal_handlers_disconnect_by_func doesn't work if you
* have a metamarshal, meaning it doesn't work with
* soup_message_add_header_handler()
......@@ -1817,7 +1840,6 @@ async_run_queue (SoupSession *session)
GList *items = NULL;
GList *i;
g_object_ref (session);
g_atomic_int_inc (&priv->in_async_run_queue);
soup_connection_manager_cleanup (priv->conn_manager, FALSE);
......@@ -1840,8 +1862,6 @@ async_run_queue (SoupSession *session)
g_mutex_unlock (&priv->queue_mutex);
g_atomic_int_set (&priv->needs_queue_sort, FALSE);
}
g_object_unref (session);
}
/**
......@@ -1898,8 +1918,12 @@ soup_session_kick_queue (SoupSession *session)
{
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
if (g_atomic_int_get (&priv->num_async_items) <= 0)
return;
g_mutex_lock (&priv->queue_sources_mutex);
g_hash_table_foreach (priv->queue_sources, (GHFunc)kick_queue_source, NULL);
if (priv->queue_sources)
g_hash_table_foreach (priv->queue_sources, (GHFunc)kick_queue_source, NULL);
g_mutex_unlock (&priv->queue_sources_mutex);
}
......
project('libsoup', 'c',
version: '3.2.1',
version: '3.2.2',
meson_version : '>= 0.54',
license : 'LGPL-2.0-or-later',
default_options : [
......
......@@ -130,11 +130,11 @@ message_send_cb (SoupSession *session,
GAsyncResult *result,
GMainContext *async_context)
{
GInputStream *stream;
GBytes *body;
g_assert_true (async_context == g_main_context_get_thread_default ());
stream = soup_session_send_finish (session, result, NULL);
g_clear_object (&stream);
body = soup_session_send_and_read_finish (session, result, NULL);
g_clear_pointer (&body, g_bytes_unref);
}
static void
......@@ -176,9 +176,9 @@ test1_thread (gpointer user_data)
msg = soup_message_new ("GET", uri);
loop = g_main_loop_new (async_context, FALSE);
g_signal_connect (msg, "finished", G_CALLBACK (message_finished), loop);
soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback)message_send_cb,
async_context);
soup_session_send_and_read_async (session, msg, G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback)message_send_cb,
async_context);
g_main_loop_run (loop);
/* We need one more iteration, because SoupMessage::finished is emitted
* right before the message is unqueued.
......@@ -215,7 +215,7 @@ do_test2 (void)
SoupSession *session;
char *uri;
SoupMessage *msg;
GInputStream *stream;
GBytes *body;
GMainLoop *loop;
idle = g_idle_add_full (G_PRIORITY_HIGH, idle_test2_fail, NULL, NULL);
......@@ -229,18 +229,18 @@ do_test2 (void)
debug_printf (1, " send_message\n");
msg = soup_message_new ("GET", uri);
stream = soup_session_send (session, msg, NULL, NULL);
body = soup_session_send_and_read (session, msg, NULL, NULL);
soup_test_assert_message_status (msg, SOUP_STATUS_OK);
g_object_unref (stream);
g_bytes_unref (body);
g_object_unref (msg);
debug_printf (1, " queue_message\n");
msg = soup_message_new ("GET", uri);
loop = g_main_loop_new (async_context, FALSE);
g_signal_connect (msg, "finished", G_CALLBACK (message_finished), loop);
soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback)message_send_cb,
async_context);
soup_session_send_and_read_async (session, msg, G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback)message_send_cb,
async_context);
g_main_loop_run (loop);
/* We need one more iteration, because SoupMessage::finished is emitted
* right before the message is unqueued.
......
......@@ -23,6 +23,7 @@
#include "soup-message-headers-private.h"
#include "soup-server-message-private.h"
#include "soup-body-input-stream-http2.h"
#include <gio/gnetworking.h>
static GUri *base_uri;
......@@ -1014,13 +1015,12 @@ do_invalid_header_rfc9113_received_test (Test *test, gconstpointer data)
static void
content_sniffed (SoupMessage *msg,
char *content_type,
GHashTable *params)
const char *content_type,
GHashTable *params,
char **sniffed_type)
{
soup_test_assert (g_object_get_data (G_OBJECT (msg), "got-chunk") == NULL,
"got-chunk got emitted before content-sniffed");
g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE));
*sniffed_type = g_strdup (content_type);
}
static void
......@@ -1055,12 +1055,13 @@ do_one_sniffer_test (SoupSession *session,
SoupMessage *msg;
GInputStream *stream = NULL;
GBytes *bytes;
char *sniffed_type = NULL;
uri = g_uri_parse_relative (base_uri, path, SOUP_HTTP_URI_FLAGS, NULL);
msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
g_object_connect (msg,
"signal::got-headers", got_headers, NULL,
"signal::content-sniffed", content_sniffed, NULL,
"signal::content-sniffed", content_sniffed, &sniffed_type,
NULL);
if (async_context) {
soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL,
......@@ -1080,14 +1081,17 @@ do_one_sniffer_test (SoupSession *session,
if (should_sniff) {
soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") != NULL,
"content-sniffed did not get emitted");
g_assert_cmpstr (sniffed_type, ==, "text/plain");
} else {
soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL,
"content-sniffed got emitted without a sniffer");
g_assert_null (sniffed_type);
}
bytes = read_stream_to_bytes_sync (stream);
g_assert_cmpuint (g_bytes_get_size (bytes), ==, expected_size);
g_free (sniffed_type);
g_object_unref (stream);
g_bytes_unref (bytes);
g_object_unref (msg);
......@@ -1098,12 +1102,14 @@ static void
do_sniffer_async_test (Test *test, gconstpointer data)
{
GMainContext *async_context = g_main_context_ref_thread_default ();
gboolean should_content_sniff = GPOINTER_TO_INT (data);
soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER);
if (should_content_sniff)
soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER);
do_one_sniffer_test (test->session, "/", 11, TRUE, async_context);
do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, TRUE, async_context);
do_one_sniffer_test (test->session, "/no-content", 0, FALSE, async_context);
do_one_sniffer_test (test->session, "/", 11, should_content_sniff, async_context);
do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, should_content_sniff, async_context);
do_one_sniffer_test (test->session, "/no-content", 0, should_content_sniff, async_context);
while (g_main_context_pending (async_context))
g_main_context_iteration (async_context, FALSE);
......@@ -1114,11 +1120,14 @@ do_sniffer_async_test (Test *test, gconstpointer data)
static void
do_sniffer_sync_test (Test *test, gconstpointer data)
{
soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER);
gboolean should_content_sniff = GPOINTER_TO_INT (data);
if (should_content_sniff)
soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER);
do_one_sniffer_test (test->session, "/", 11, TRUE, NULL);
do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, TRUE, NULL);
do_one_sniffer_test (test->session, "/no-content", 0, FALSE, NULL);
do_one_sniffer_test (test->session, "/", 11, should_content_sniff, NULL);
do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, should_content_sniff, NULL);
do_one_sniffer_test (test->session, "/no-content", 0, should_content_sniff, NULL);
}
static void
......@@ -1143,6 +1152,24 @@ do_timeout_test (Test *test, gconstpointer data)
g_main_context_iteration (NULL, FALSE);
}
static void
do_connection_closed_test (Test *test, gconstpointer data)
{
GUri *uri;
SoupMessage *msg;
GInputStream *stream;
GError *error = NULL;
uri = g_uri_parse_relative (base_uri, "/close", SOUP_HTTP_URI_FLAGS, NULL);
msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
stream = soup_session_send (test->session, msg, NULL, &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
g_clear_error (&error);
g_clear_object (&stream);
g_object_unref (msg);
g_uri_unref (uri);
}
static gboolean
unpause_message (SoupServerMessage *msg)
{
......@@ -1246,6 +1273,21 @@ server_handler (SoupServer *server,
SOUP_MEMORY_STATIC,
"Success!", 8);
soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
} else if (strcmp (path, "/close") == 0) {
SoupServerConnection *conn;
int fd;
conn = soup_server_message_get_connection (msg);
fd = g_socket_get_fd (soup_server_connection_get_socket (conn));
#ifdef G_OS_WIN32
shutdown (fd, SD_SEND);
#else
shutdown (fd, SHUT_WR);
#endif
soup_server_message_set_response (msg, "text/plain",
SOUP_MEMORY_STATIC,
"Success!", 8);
}
}
......@@ -1391,11 +1433,19 @@ main (int argc, char **argv)
do_invalid_header_rfc9113_received_test,
teardown_session);
#endif
g_test_add ("/http2/sniffer/async", Test, NULL,
g_test_add ("/http2/sniffer/with-sniffer/async", Test, GINT_TO_POINTER (TRUE),
setup_session,
do_sniffer_async_test,
teardown_session);
g_test_add ("/http2/sniffer/no-sniffer/async", Test, GINT_TO_POINTER (FALSE),
setup_session,
do_sniffer_async_test,
teardown_session);
g_test_add ("/http2/sniffer/sync", Test, NULL,
g_test_add ("/http2/sniffer/with-sniffer/sync", Test, GINT_TO_POINTER (TRUE),
setup_session,
do_sniffer_sync_test,
teardown_session);
g_test_add ("/http2/sniffer/no-sniffer/sync", Test, GINT_TO_POINTER (FALSE),
setup_session,
do_sniffer_sync_test,
teardown_session);
......@@ -1403,6 +1453,10 @@ main (int argc, char **argv)
setup_session,
do_timeout_test,
teardown_session);
g_test_add ("/http2/connection-closed", Test, NULL,
setup_session,
do_connection_closed_test,
teardown_session);
ret = g_test_run ();
......