diff options
author | Carlos Garcia Campos <cgarcia@igalia.com> | 2022-09-30 10:31:21 +0200 |
---|---|---|
committer | Carlos Garcia Campos <cgarcia@igalia.com> | 2022-10-09 11:41:31 +0200 |
commit | a948ac11b143d5db832749fdb19113fff6b614c1 (patch) | |
tree | 74ffbc4763c0e25dbc98bc6f79ac3a9685f61da9 | |
parent | 081feef95030a378aed7bf93aeed980931802aa2 (diff) | |
download | libsoup-a948ac11b143d5db832749fdb19113fff6b614c1.tar.gz |
http2: disable RFC 9113 header value validation
It breaks many popular web sites.
Fixes #300.
-rw-r--r-- | libsoup/http2/soup-client-message-io-http2.c | 27 | ||||
-rw-r--r-- | meson.build | 7 | ||||
-rw-r--r-- | tests/http2-test.c | 45 |
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 3080594c..0487fbc1 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, |