|
| 1 | +From 167ef0c6817658c1a089c75c462482209e207db4 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Carlos Garcia Campos <cgarcia@igalia.com> |
| 3 | +Date: Thu, 22 Jan 2026 15:26:18 +0100 |
| 4 | +Subject: [PATCH] uri-utils: do host validation when checking if a GUri is |
| 5 | + valid |
| 6 | + |
| 7 | +Currently we only check if the host is not NULL and not empty, but it |
| 8 | +might contain invalid characters not allowed for a host name in a URL. |
| 9 | +This patch replaces the SOUP_URI_IS_VALID internal macro by a function |
| 10 | +that in addition to the existing checks, it also validates the host. |
| 11 | + |
| 12 | +Closes #488 |
| 13 | +Upstream Patch Reference: https://gitlab.gnome.org/GNOME/libsoup/-/commit/167ef0c6817658c1a089c75c462482209e207db4.patch |
| 14 | +--- |
| 15 | + libsoup/auth/soup-auth.c | 2 +- |
| 16 | + libsoup/soup-message.c | 9 ++--- |
| 17 | + libsoup/soup-uri-utils-private.h | 4 +-- |
| 18 | + libsoup/soup-uri-utils.c | 60 ++++++++++++++++++++++++++++++++ |
| 19 | + tests/uri-parsing-test.c | 46 ++++++++++++++++++++++++ |
| 20 | + 5 files changed, 114 insertions(+), 7 deletions(-) |
| 21 | + |
| 22 | +diff --git a/libsoup/auth/soup-auth.c b/libsoup/auth/soup-auth.c |
| 23 | +index d9bf4af..278baa1 100644 |
| 24 | +--- a/libsoup/auth/soup-auth.c |
| 25 | ++++ b/libsoup/auth/soup-auth.c |
| 26 | +@@ -643,7 +643,7 @@ GSList * |
| 27 | + soup_auth_get_protection_space (SoupAuth *auth, GUri *source_uri) |
| 28 | + { |
| 29 | + g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); |
| 30 | +- g_return_val_if_fail (SOUP_URI_IS_VALID (source_uri), NULL); |
| 31 | ++ g_return_val_if_fail (soup_uri_is_valid (source_uri), NULL); |
| 32 | + |
| 33 | + GUri *source_uri_normalized = soup_uri_copy_with_normalized_flags (source_uri); |
| 34 | + GSList *ret = SOUP_AUTH_GET_CLASS (auth)->get_protection_space (auth, source_uri_normalized); |
| 35 | +diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c |
| 36 | +index 2f5b267..292e4e5 100644 |
| 37 | +--- a/libsoup/soup-message.c |
| 38 | ++++ b/libsoup/soup-message.c |
| 39 | +@@ -923,7 +923,8 @@ soup_message_new (const char *method, const char *uri_string) |
| 40 | + uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL); |
| 41 | + if (!uri) |
| 42 | + return NULL; |
| 43 | +- if (!g_uri_get_host (uri)) { |
| 44 | ++ |
| 45 | ++ if (!soup_uri_is_valid (uri)) { |
| 46 | + g_uri_unref (uri); |
| 47 | + return NULL; |
| 48 | + } |
| 49 | +@@ -946,7 +947,7 @@ SoupMessage * |
| 50 | + soup_message_new_from_uri (const char *method, GUri *uri) |
| 51 | + { |
| 52 | + g_return_val_if_fail (method != NULL, NULL); |
| 53 | +- g_return_val_if_fail (SOUP_URI_IS_VALID (uri), NULL); |
| 54 | ++ g_return_val_if_fail (soup_uri_is_valid (uri), NULL); |
| 55 | + |
| 56 | + return g_object_new (SOUP_TYPE_MESSAGE, |
| 57 | + "method", method, |
| 58 | +@@ -966,7 +967,7 @@ soup_message_new_from_uri (const char *method, GUri *uri) |
| 59 | + SoupMessage * |
| 60 | + soup_message_new_options_ping (GUri *base_uri) |
| 61 | + { |
| 62 | +- g_return_val_if_fail (SOUP_URI_IS_VALID (base_uri), NULL); |
| 63 | ++ g_return_val_if_fail (soup_uri_is_valid (base_uri), NULL); |
| 64 | + |
| 65 | + return g_object_new (SOUP_TYPE_MESSAGE, |
| 66 | + "method", SOUP_METHOD_OPTIONS, |
| 67 | +@@ -2039,7 +2040,7 @@ soup_message_set_uri (SoupMessage *msg, GUri *uri) |
| 68 | + GUri *normalized_uri; |
| 69 | + |
| 70 | + g_return_if_fail (SOUP_IS_MESSAGE (msg)); |
| 71 | +- g_return_if_fail (SOUP_URI_IS_VALID (uri)); |
| 72 | ++ g_return_if_fail (soup_uri_is_valid (uri)); |
| 73 | + |
| 74 | + priv = soup_message_get_instance_private (msg); |
| 75 | + |
| 76 | +diff --git a/libsoup/soup-uri-utils-private.h b/libsoup/soup-uri-utils-private.h |
| 77 | +index 3dbdb85..a73e882 100644 |
| 78 | +--- a/libsoup/soup-uri-utils-private.h |
| 79 | ++++ b/libsoup/soup-uri-utils-private.h |
| 80 | +@@ -10,6 +10,8 @@ |
| 81 | + |
| 82 | + G_BEGIN_DECLS |
| 83 | + |
| 84 | ++gboolean soup_uri_is_valid (GUri *uri); |
| 85 | ++ |
| 86 | + gboolean soup_uri_is_http (GUri *uri); |
| 87 | + |
| 88 | + gboolean soup_uri_is_https (GUri *uri); |
| 89 | +@@ -28,6 +30,4 @@ GUri *soup_uri_copy_with_normalized_flags (GUri *uri); |
| 90 | + |
| 91 | + char *soup_uri_get_host_for_headers (GUri *uri); |
| 92 | + |
| 93 | +-#define SOUP_URI_IS_VALID(x) (x && g_uri_get_host(x) && g_uri_get_host(x)[0]) |
| 94 | +- |
| 95 | + G_END_DECLS |
| 96 | +diff --git a/libsoup/soup-uri-utils.c b/libsoup/soup-uri-utils.c |
| 97 | +index ce9b2a1..cfac991 100644 |
| 98 | +--- a/libsoup/soup-uri-utils.c |
| 99 | ++++ b/libsoup/soup-uri-utils.c |
| 100 | +@@ -244,6 +244,66 @@ soup_uri_host_equal (gconstpointer v1, gconstpointer v2) |
| 101 | + return g_ascii_strcasecmp (one_host, two_host) == 0; |
| 102 | + } |
| 103 | + |
| 104 | ++static gboolean |
| 105 | ++is_valid_character_for_host (char c) |
| 106 | ++{ |
| 107 | ++ static const char forbidden_chars[] = { '\t', '\n', '\r', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|' }; |
| 108 | ++ int i; |
| 109 | ++ |
| 110 | ++ for (i = 0; i < G_N_ELEMENTS (forbidden_chars); ++i) { |
| 111 | ++ if (c == forbidden_chars[i]) |
| 112 | ++ return FALSE; |
| 113 | ++ } |
| 114 | ++ |
| 115 | ++ return TRUE; |
| 116 | ++} |
| 117 | ++ |
| 118 | ++static gboolean |
| 119 | ++is_host_valid (const char* host) |
| 120 | ++{ |
| 121 | ++ int i; |
| 122 | ++ gboolean is_valid; |
| 123 | ++ char *ascii_host = NULL; |
| 124 | ++ |
| 125 | ++ if (!host || !host[0]) |
| 126 | ++ return FALSE; |
| 127 | ++ |
| 128 | ++ if (g_hostname_is_non_ascii (host)) { |
| 129 | ++ ascii_host = g_hostname_to_ascii (host); |
| 130 | ++ if (!ascii_host) |
| 131 | ++ return FALSE; |
| 132 | ++ |
| 133 | ++ host = ascii_host; |
| 134 | ++ } |
| 135 | ++ |
| 136 | ++ if ((g_ascii_isdigit (host[0]) || strchr (host, ':')) && g_hostname_is_ip_address (host)) { |
| 137 | ++ g_free (ascii_host); |
| 138 | ++ return TRUE; |
| 139 | ++ } |
| 140 | ++ |
| 141 | ++ is_valid = TRUE; |
| 142 | ++ for (i = 0; host[i] && is_valid; i++) |
| 143 | ++ is_valid = is_valid_character_for_host (host[i]); |
| 144 | ++ |
| 145 | ++ g_free (ascii_host); |
| 146 | ++ |
| 147 | ++ return is_valid; |
| 148 | ++} |
| 149 | ++ |
| 150 | ++gboolean |
| 151 | ++soup_uri_is_valid (GUri *uri) |
| 152 | ++{ |
| 153 | ++ if (!uri) |
| 154 | ++ return FALSE; |
| 155 | ++ |
| 156 | ++ if (!is_host_valid (g_uri_get_host (uri))) |
| 157 | ++ return FALSE; |
| 158 | ++ |
| 159 | ++ /* FIXME: validate other URI components? */ |
| 160 | ++ |
| 161 | ++ return TRUE; |
| 162 | ++} |
| 163 | ++ |
| 164 | + gboolean |
| 165 | + soup_uri_is_https (GUri *uri) |
| 166 | + { |
| 167 | +diff --git a/tests/uri-parsing-test.c b/tests/uri-parsing-test.c |
| 168 | +index 4c16d7e..a0e9cc2 100644 |
| 169 | +--- a/tests/uri-parsing-test.c |
| 170 | ++++ b/tests/uri-parsing-test.c |
| 171 | +@@ -116,6 +116,51 @@ do_copy_tests (void) |
| 172 | + g_uri_unref (uri); |
| 173 | + } |
| 174 | + |
| 175 | ++static struct { |
| 176 | ++ const char *scheme; |
| 177 | ++ const char *host; |
| 178 | ++ const char *as_string; |
| 179 | ++ gboolean valid; |
| 180 | ++} valid_tests[] = { |
| 181 | ++ { "http", "example.com", "http://example.com/", TRUE }, |
| 182 | ++ { "http", "localhost", "http://localhost/", TRUE }, |
| 183 | ++ { "http", "127.0.0.1", "http://127.0.0.1/", TRUE }, |
| 184 | ++ { "http", "::1", "http://[::1]/", TRUE }, |
| 185 | ++ { "http", "::192.168.0.10", "http://[::192.168.0.10]/", TRUE }, |
| 186 | ++ { "http", "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/", TRUE }, |
| 187 | ++ { "http", "\xe4\xbe\x8b\xe5\xad\x90.\xe6\xb5\x8b\xe8\xaf\x95", "http://\xe4\xbe\x8b\xe5\xad\x90.\xe6\xb5\x8b\xe8\xaf\x95/", TRUE }, |
| 188 | ++ { "http", "012x:4567:89AB:cdef:3210:7654:ba98:FeDc", "http://012x:4567:89AB:cdef:3210:7654:ba98:FeDc/", FALSE }, |
| 189 | ++ { "http", "\texample.com", "http://\texample.com/", FALSE }, |
| 190 | ++ { "http", "example.com\n", "http://example.com\n/", FALSE }, |
| 191 | ++ { "http", "\r\nexample.com", "http://\r\nexample.com/", FALSE }, |
| 192 | ++ { "http", "example .com", "http://example .com/", FALSE }, |
| 193 | ++ { "http", "example:com", "http://example:com/", FALSE }, |
| 194 | ++ { "http", "exampl<e>.com", "http://exampl<e>.com/", FALSE }, |
| 195 | ++ { "http", "exampl[e].com", "http://exampl[e].com/", FALSE }, |
| 196 | ++ { "http", "exampl^e.com", "http://exampl^e.com/", FALSE }, |
| 197 | ++ { "http", "examp|e.com", "http://examp|e.com/", FALSE }, |
| 198 | ++}; |
| 199 | ++ |
| 200 | ++static void |
| 201 | ++do_valid_tests (void) |
| 202 | ++{ |
| 203 | ++ int i; |
| 204 | ++ |
| 205 | ++ for (i = 0; i < G_N_ELEMENTS (valid_tests); ++i) { |
| 206 | ++ GUri *uri; |
| 207 | ++ char *uri_str; |
| 208 | ++ |
| 209 | ++ uri = g_uri_build (SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_ENCODED, valid_tests[i].scheme, NULL, valid_tests[i].host, -1, "", NULL, NULL); |
| 210 | ++ uri_str = g_uri_to_string (uri); |
| 211 | ++ |
| 212 | ++ g_assert_cmpstr (uri_str, ==, valid_tests[i].as_string); |
| 213 | ++ g_assert_true (soup_uri_is_valid (uri) == valid_tests[i].valid); |
| 214 | ++ |
| 215 | ++ g_free (uri_str); |
| 216 | ++ g_uri_unref (uri); |
| 217 | ++ } |
| 218 | ++} |
| 219 | ++ |
| 220 | + #define CONTENT_TYPE_DEFAULT "text/plain;charset=US-ASCII" |
| 221 | + |
| 222 | + static struct { |
| 223 | +@@ -192,6 +237,7 @@ main (int argc, char **argv) |
| 224 | + |
| 225 | + g_test_add_func ("/uri/equality", do_equality_tests); |
| 226 | + g_test_add_func ("/uri/copy", do_copy_tests); |
| 227 | ++ g_test_add_func ("/uri/valid", do_valid_tests); |
| 228 | + g_test_add_func ("/data", do_data_uri_tests); |
| 229 | + g_test_add_func ("/path_and_query", do_path_and_query_tests); |
| 230 | + |
| 231 | +-- |
| 232 | +2.45.4 |
| 233 | + |
0 commit comments