[libsoup/carlosgc/http2-disable-strict-header-validation] http2: disable RFC 9113 header value validation




commit d320f53447c9dd8681a4465905c270f5f4bccd53
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Fri Sep 30 10:31:21 2022 +0200

    http2: disable RFC 9113 header value validation
    
    It breaks many popular web sites.
    
    See #300.

 libsoup/http2/soup-client-message-io-http2.c | 27 +++++++++++++++++
 meson.build                                  |  7 +++--
 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 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,


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