summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2022-09-30 10:31:21 +0200
committerPatrick Griffis <pgriffis@igalia.com>2022-10-05 19:02:13 +0000
commitd673228b0c74088ce59a5bfeea4db245fd7e8ed0 (patch)
tree59ffd841aa093ab5d76abb0026f6798a0fcb84b3
parent43f23bd1972b9feef13e52c0505f61861a808039 (diff)
downloadlibsoup-d673228b0c74088ce59a5bfeea4db245fd7e8ed0.tar.gz
http2: disable RFC 9113 header value validation
It breaks many popular web sites. See #300.
-rw-r--r--libsoup/http2/soup-client-message-io-http2.c27
-rw-r--r--meson.build7
-rw-r--r--tests/http2-test.c45
3 files changed, 77 insertions, 2 deletions
diff --git a/libsoup/http2/soup-client-message-io-http2.c b/libsoup/http2/soup-client-message-io-http2.c
index fdeb6841..e45ce132 100644
--- a/libsoup/http2/soup-client-message-io-http2.c
+++ b/libsoup/http2/soup-client-message-io-http2.c
@@ -545,6 +545,22 @@ on_header_callback (nghttp2_session *session,
return 0;
}
+static int
+on_invalid_header_callback (nghttp2_session *session,
+ const nghttp2_frame *frame,
+ const uint8_t *name,
+ size_t namelen,
+ const uint8_t *value,
+ size_t valuelen,
+ uint8_t flags,
+ void *user_data)
+{
+ SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
+
+ h2_debug (user_data, data, "[HEADERS] Invalid header received: name=[%.*s] value=[%.*s]", namelen, name, valuelen, value);
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+
static GError *
memory_stream_need_more_data_callback (SoupBodyInputStreamHttp2 *stream,
gboolean blocking,
@@ -1832,6 +1848,7 @@ soup_client_message_io_http2_init (SoupClientMessageIOHTTP2 *io)
nghttp2_session_callbacks *callbacks;
NGCHECK (nghttp2_session_callbacks_new (&callbacks));
nghttp2_session_callbacks_set_on_header_callback (callbacks, on_header_callback);
+ nghttp2_session_callbacks_set_on_invalid_header_callback (callbacks, on_invalid_header_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback (callbacks, on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback (callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_begin_frame_callback (callbacks, on_begin_frame_callback);
@@ -1840,7 +1857,17 @@ soup_client_message_io_http2_init (SoupClientMessageIOHTTP2 *io)
nghttp2_session_callbacks_set_on_frame_send_callback (callbacks, on_frame_send_callback);
nghttp2_session_callbacks_set_on_stream_close_callback (callbacks, on_stream_close_callback);
+#ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
+ nghttp2_option *option;
+
+ nghttp2_option_new (&option);
+ nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation (option, 1);
+ NGCHECK (nghttp2_session_client_new2 (&io->session, callbacks, io, option));
+ nghttp2_option_del (option);
+#else
NGCHECK (nghttp2_session_client_new (&io->session, callbacks, io));
+#endif
+
nghttp2_session_callbacks_del (callbacks);
io->messages = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)soup_http2_message_data_free);
diff --git a/meson.build b/meson.build
index 8dad1b4e..b21fe6dc 100644
--- a/meson.build
+++ b/meson.build
@@ -110,7 +110,12 @@ gio_dep = dependency('gio-2.0', version : glib_required_version,
glib_deps = [glib_dep, gmodule_dep, gobject_dep, gio_dep]
+cdata = configuration_data()
+
libnghttp2_dep = dependency('libnghttp2')
+if cc.has_function('nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation', prefix : '#include <nghttp2/nghttp2.h>', dependencies : libnghttp2_dep)
+ cdata.set('HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION', '1')
+endif
sqlite_dep = dependency('sqlite3', required: false)
@@ -126,8 +131,6 @@ if not sqlite_dep.found()
sqlite_dep = dependency('sqlite3')
endif
-cdata = configuration_data()
-
brotlidec_dep = dependency('libbrotlidec', required : get_option('brotli'))
if brotlidec_dep.found()
cdata.set('WITH_BROTLI', true)
diff --git a/tests/http2-test.c b/tests/http2-test.c
index 12012c3b..5fd7ff01 100644
--- a/tests/http2-test.c
+++ b/tests/http2-test.c
@@ -986,6 +986,32 @@ do_invalid_header_received_test (Test *test, gconstpointer data)
g_object_unref (msg);
}
+#ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
+static void
+do_invalid_header_rfc9113_received_test (Test *test, gconstpointer data)
+{
+ gboolean async = GPOINTER_TO_INT (data);
+ GUri *uri;
+ SoupMessage *msg;
+ GBytes *response;
+ GError *error = NULL;
+
+ uri = g_uri_parse_relative (base_uri, "/invalid-header-rfc9113", SOUP_HTTP_URI_FLAGS, NULL);
+ msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+
+ if (async)
+ response = soup_test_session_async_send (test->session, msg, NULL, &error);
+ else
+ response = soup_session_send_and_read (test->session, msg, NULL, &error);
+
+ g_assert_nonnull (response);
+ g_assert_no_error (error);
+ g_clear_error (&error);
+ g_object_unref (msg);
+ g_uri_unref (uri);
+}
+#endif
+
static void
content_sniffed (SoupMessage *msg,
char *content_type,
@@ -1211,6 +1237,15 @@ server_handler (SoupServer *server,
/* Use soup_message_headers_append_common to skip the validation check. */
soup_message_headers_append_common (response_headers, SOUP_HEADER_CONTENT_TYPE, "\r");
soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+ } else if (strcmp (path, "/invalid-header-rfc9113") == 0) {
+ SoupMessageHeaders *response_headers;
+
+ response_headers = soup_server_message_get_response_headers (msg);
+ soup_message_headers_append (response_headers, "Invalid-Header-Value", "foo ");
+ soup_server_message_set_response (msg, "text/plain",
+ SOUP_MEMORY_STATIC,
+ "Success!", 8);
+ soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
}
}
@@ -1346,6 +1381,16 @@ main (int argc, char **argv)
setup_session,
do_invalid_header_received_test,
teardown_session);
+#ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
+ g_test_add ("/http2/invalid-header-rfc9113-received/async", Test, GINT_TO_POINTER (TRUE),
+ setup_session,
+ do_invalid_header_rfc9113_received_test,
+ teardown_session);
+ g_test_add ("/http2/invalid-header-rfc9113-received/sync", Test, GINT_TO_POINTER (FALSE),
+ setup_session,
+ do_invalid_header_rfc9113_received_test,
+ teardown_session);
+#endif
g_test_add ("/http2/sniffer/async", Test, NULL,
setup_session,
do_sniffer_async_test,