| 1 | #include "unit-test.h" |
| 2 | #include "urlmatch.h" |
| 3 | |
| 4 | static void check_url_normalizable(const char *url, unsigned int normalizable) |
| 5 | { |
| 6 | char *url_norm = url_normalize(url, NULL); |
| 7 | |
| 8 | cl_assert_equal_i(normalizable, url_norm ? 1 : 0); |
| 9 | free(url_norm); |
| 10 | } |
| 11 | |
| 12 | static void check_normalized_url(const char *url, const char *expect) |
| 13 | { |
| 14 | char *url_norm = url_normalize(url, NULL); |
| 15 | |
| 16 | cl_assert_equal_s(url_norm, expect); |
| 17 | free(url_norm); |
| 18 | } |
| 19 | |
| 20 | static void compare_normalized_urls(const char *url1, const char *url2, |
| 21 | unsigned int equal) |
| 22 | { |
| 23 | char *url1_norm = url_normalize(url1, NULL); |
| 24 | char *url2_norm = url_normalize(url2, NULL); |
| 25 | |
| 26 | if (equal) { |
| 27 | cl_assert_equal_s(url1_norm, url2_norm); |
| 28 | } else { |
| 29 | cl_assert(strcmp(url1_norm, url2_norm) != 0); |
| 30 | } |
| 31 | free(url1_norm); |
| 32 | free(url2_norm); |
| 33 | } |
| 34 | |
| 35 | static void check_normalized_url_length(const char *url, size_t len) |
| 36 | { |
| 37 | struct url_info info; |
| 38 | char *url_norm = url_normalize(url, &info); |
| 39 | |
| 40 | cl_assert_equal_i(info.url_len, len); |
| 41 | free(url_norm); |
| 42 | } |
| 43 | |
| 44 | /* Note that only "file:" URLs should be allowed without a host */ |
| 45 | void test_urlmatch_normalization__scheme(void) |
| 46 | { |
| 47 | check_url_normalizable("", 0); |
| 48 | check_url_normalizable("_", 0); |
| 49 | check_url_normalizable("scheme", 0); |
| 50 | check_url_normalizable("scheme:", 0); |
| 51 | check_url_normalizable("scheme:/", 0); |
| 52 | check_url_normalizable("scheme://", 0); |
| 53 | check_url_normalizable("file", 0); |
| 54 | check_url_normalizable("file:", 0); |
| 55 | check_url_normalizable("file:/", 0); |
| 56 | check_url_normalizable("file://", 1); |
| 57 | check_url_normalizable("://acme.co", 0); |
| 58 | check_url_normalizable("x_test://acme.co", 0); |
| 59 | check_url_normalizable("-test://acme.co", 0); |
| 60 | check_url_normalizable("0test://acme.co", 0); |
| 61 | check_url_normalizable("+test://acme.co", 0); |
| 62 | check_url_normalizable(".test://acme.co", 0); |
| 63 | check_url_normalizable("schem%6e://", 0); |
| 64 | check_url_normalizable("x-Test+v1.0://acme.co", 1); |
| 65 | check_normalized_url("AbCdeF://x.Y", "abcdef://x.y/"); |
| 66 | } |
| 67 | |
| 68 | void test_urlmatch_normalization__authority(void) |
| 69 | { |
| 70 | check_url_normalizable("scheme://user:pass@", 0); |
| 71 | check_url_normalizable("scheme://?", 0); |
| 72 | check_url_normalizable("scheme://#", 0); |
| 73 | check_url_normalizable("scheme:///", 0); |
| 74 | check_url_normalizable("scheme://:", 0); |
| 75 | check_url_normalizable("scheme://:555", 0); |
| 76 | check_url_normalizable("file://user:pass@", 1); |
| 77 | check_url_normalizable("file://?", 1); |
| 78 | check_url_normalizable("file://#", 1); |
| 79 | check_url_normalizable("file:///", 1); |
| 80 | check_url_normalizable("file://:", 1); |
| 81 | check_url_normalizable("file://:555", 0); |
| 82 | check_url_normalizable("scheme://user:pass@host", 1); |
| 83 | check_url_normalizable("scheme://@host", 1); |
| 84 | check_url_normalizable("scheme://%00@host", 1); |
| 85 | check_url_normalizable("scheme://%%@host", 0); |
| 86 | check_url_normalizable("scheme://host_", 1); |
| 87 | check_url_normalizable("scheme://user:pass@host/", 1); |
| 88 | check_url_normalizable("scheme://@host/", 1); |
| 89 | check_url_normalizable("scheme://host/", 1); |
| 90 | check_url_normalizable("scheme://host?x", 1); |
| 91 | check_url_normalizable("scheme://host#x", 1); |
| 92 | check_url_normalizable("scheme://host/@", 1); |
| 93 | check_url_normalizable("scheme://host?@x", 1); |
| 94 | check_url_normalizable("scheme://host#@x", 1); |
| 95 | check_url_normalizable("scheme://[::1]", 1); |
| 96 | check_url_normalizable("scheme://[::1]/", 1); |
| 97 | check_url_normalizable("scheme://hos%41/", 0); |
| 98 | check_url_normalizable("scheme://[invalid....:/", 1); |
| 99 | check_url_normalizable("scheme://invalid....:]/", 1); |
| 100 | check_url_normalizable("scheme://invalid....:[/", 0); |
| 101 | check_url_normalizable("scheme://invalid....:[", 0); |
| 102 | } |
| 103 | |
| 104 | void test_urlmatch_normalization__port(void) |
| 105 | { |
| 106 | check_url_normalizable("xyz://q@some.host:", 1); |
| 107 | check_url_normalizable("xyz://q@some.host:456/", 1); |
| 108 | check_url_normalizable("xyz://q@some.host:0", 0); |
| 109 | check_url_normalizable("xyz://q@some.host:0000000", 0); |
| 110 | check_url_normalizable("xyz://q@some.host:0000001?", 1); |
| 111 | check_url_normalizable("xyz://q@some.host:065535#", 1); |
| 112 | check_url_normalizable("xyz://q@some.host:65535", 1); |
| 113 | check_url_normalizable("xyz://q@some.host:65536", 0); |
| 114 | check_url_normalizable("xyz://q@some.host:99999", 0); |
| 115 | check_url_normalizable("xyz://q@some.host:100000", 0); |
| 116 | check_url_normalizable("xyz://q@some.host:100001", 0); |
| 117 | check_url_normalizable("http://q@some.host:80", 1); |
| 118 | check_url_normalizable("https://q@some.host:443", 1); |
| 119 | check_url_normalizable("http://q@some.host:80/", 1); |
| 120 | check_url_normalizable("https://q@some.host:443?", 1); |
| 121 | check_url_normalizable("http://q@:8008", 0); |
| 122 | check_url_normalizable("http://:8080", 0); |
| 123 | check_url_normalizable("http://:", 0); |
| 124 | check_url_normalizable("xyz://q@some.host:456/", 1); |
| 125 | check_url_normalizable("xyz://[::1]:456/", 1); |
| 126 | check_url_normalizable("xyz://[::1]:/", 1); |
| 127 | check_url_normalizable("xyz://[::1]:000/", 0); |
| 128 | check_url_normalizable("xyz://[::1]:0%300/", 0); |
| 129 | check_url_normalizable("xyz://[::1]:0x80/", 0); |
| 130 | check_url_normalizable("xyz://[::1]:4294967297/", 0); |
| 131 | check_url_normalizable("xyz://[::1]:030f/", 0); |
| 132 | } |
| 133 | |
| 134 | void test_urlmatch_normalization__port_normalization(void) |
| 135 | { |
| 136 | check_normalized_url("http://x:800", "http://x:800/"); |
| 137 | check_normalized_url("http://x:0800", "http://x:800/"); |
| 138 | check_normalized_url("http://x:00000800", "http://x:800/"); |
| 139 | check_normalized_url("http://x:065535", "http://x:65535/"); |
| 140 | check_normalized_url("http://x:1", "http://x:1/"); |
| 141 | check_normalized_url("http://x:80", "http://x/"); |
| 142 | check_normalized_url("http://x:080", "http://x/"); |
| 143 | check_normalized_url("http://x:000000080", "http://x/"); |
| 144 | check_normalized_url("https://x:443", "https://x/"); |
| 145 | check_normalized_url("https://x:0443", "https://x/"); |
| 146 | check_normalized_url("https://x:000000443", "https://x/"); |
| 147 | } |
| 148 | |
| 149 | void test_urlmatch_normalization__general_escape(void) |
| 150 | { |
| 151 | check_url_normalizable("http://x.y?%fg", 0); |
| 152 | check_normalized_url("X://W/%7e%41^%3a", "x://w/~A%5E%3A"); |
| 153 | check_normalized_url("X://W/:/?#[]@", "x://w/:/?#[]@"); |
| 154 | check_normalized_url("X://W/$&()*+,;=", "x://w/$&()*+,;="); |
| 155 | check_normalized_url("X://W/'", "x://w/'"); |
| 156 | check_normalized_url("X://W?!", "x://w/?!"); |
| 157 | } |
| 158 | |
| 159 | void test_urlmatch_normalization__high_bit(void) |
| 160 | { |
| 161 | check_normalized_url( |
| 162 | "x://q/\x01\x02\x03\x04\x05\x06\x07\x08\x0e\x0f\x10\x11\x12", |
| 163 | "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12"); |
| 164 | check_normalized_url( |
| 165 | "x://q/\x13\x14\x15\x16\x17\x18\x19\x1b\x1c\x1d\x1e\x1f\x7f", |
| 166 | "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F"); |
| 167 | check_normalized_url( |
| 168 | "x://q/\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", |
| 169 | "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F"); |
| 170 | check_normalized_url( |
| 171 | "x://q/\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", |
| 172 | "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F"); |
| 173 | check_normalized_url( |
| 174 | "x://q/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", |
| 175 | "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF"); |
| 176 | check_normalized_url( |
| 177 | "x://q/\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", |
| 178 | "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF"); |
| 179 | check_normalized_url( |
| 180 | "x://q/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", |
| 181 | "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF"); |
| 182 | check_normalized_url( |
| 183 | "x://q/\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", |
| 184 | "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF"); |
| 185 | check_normalized_url( |
| 186 | "x://q/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", |
| 187 | "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF"); |
| 188 | check_normalized_url( |
| 189 | "x://q/\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", |
| 190 | "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"); |
| 191 | } |
| 192 | |
| 193 | void test_urlmatch_normalization__utf8_escape(void) |
| 194 | { |
| 195 | check_normalized_url( |
| 196 | "x://q/\xc2\x80\xdf\xbf\xe0\xa0\x80\xef\xbf\xbd\xf0\x90\x80\x80\xf0\xaf\xbf\xbd", |
| 197 | "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD"); |
| 198 | } |
| 199 | |
| 200 | void test_urlmatch_normalization__username_pass(void) |
| 201 | { |
| 202 | check_normalized_url("x://%41%62(^):%70+d@foo", "x://Ab(%5E):p+d@foo/"); |
| 203 | } |
| 204 | |
| 205 | void test_urlmatch_normalization__length(void) |
| 206 | { |
| 207 | check_normalized_url_length("Http://%4d%65:%4d^%70@The.Host", 25); |
| 208 | check_normalized_url_length("http://%41:%42@x.y/%61/", 17); |
| 209 | check_normalized_url_length("http://@x.y/^", 15); |
| 210 | } |
| 211 | |
| 212 | void test_urlmatch_normalization__dots(void) |
| 213 | { |
| 214 | check_normalized_url("x://y/.", "x://y/"); |
| 215 | check_normalized_url("x://y/./", "x://y/"); |
| 216 | check_normalized_url("x://y/a/.", "x://y/a"); |
| 217 | check_normalized_url("x://y/a/./", "x://y/a/"); |
| 218 | check_normalized_url("x://y/.?", "x://y/?"); |
| 219 | check_normalized_url("x://y/./?", "x://y/?"); |
| 220 | check_normalized_url("x://y/a/.?", "x://y/a?"); |
| 221 | check_normalized_url("x://y/a/./?", "x://y/a/?"); |
| 222 | check_normalized_url("x://y/a/./b/.././../c", "x://y/c"); |
| 223 | check_normalized_url("x://y/a/./b/../.././c/", "x://y/c/"); |
| 224 | check_normalized_url("x://y/a/./b/.././../c/././.././.", "x://y/"); |
| 225 | check_url_normalizable("x://y/a/./b/.././../c/././.././..", 0); |
| 226 | check_normalized_url("x://y/a/./?/././..", "x://y/a/?/././.."); |
| 227 | check_normalized_url("x://y/%2e/", "x://y/"); |
| 228 | check_normalized_url("x://y/%2E/", "x://y/"); |
| 229 | check_normalized_url("x://y/a/%2e./", "x://y/"); |
| 230 | check_normalized_url("x://y/b/.%2E/", "x://y/"); |
| 231 | check_normalized_url("x://y/c/%2e%2E/", "x://y/"); |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | * "http://@foo" specifies an empty user name but does not specify a password. |
| 236 | * "http://foo" specifies neither a user name nor a password. |
| 237 | * So they should not be equivalent. |
| 238 | */ |
| 239 | void test_urlmatch_normalization__equivalents(void) |
| 240 | { |
| 241 | compare_normalized_urls("httP://x", "Http://X/", 1); |
| 242 | compare_normalized_urls("Http://%4d%65:%4d^%70@The.Host", "hTTP://Me:%4D^p@the.HOST:80/", 1); |
| 243 | compare_normalized_urls("https://@x.y/^", "httpS://x.y:443/^", 0); |
| 244 | compare_normalized_urls("https://@x.y/^", "httpS://@x.y:0443/^", 1); |
| 245 | compare_normalized_urls("https://@x.y/^/../abc", "httpS://@x.y:0443/abc", 1); |
| 246 | compare_normalized_urls("https://@x.y/^/..", "httpS://@x.y:0443/", 1); |
| 247 | } |
| 248 | |
| 249 | static void check_parsed_path(const char *url, const char *expected_path) |
| 250 | { |
| 251 | struct url_info info; |
| 252 | char *parsed = url_parse(url, &info); |
| 253 | char *path; |
| 254 | |
| 255 | cl_assert(parsed != NULL); |
| 256 | path = xstrndup(parsed + info.path_off, info.path_len); |
| 257 | cl_assert_equal_s(path, expected_path); |
| 258 | free(path); |
| 259 | free(parsed); |
| 260 | } |
| 261 | |
| 262 | void test_urlmatch_normalization__parse_scp(void) |
| 263 | { |
| 264 | check_parsed_path("host:path", "/path"); |
| 265 | check_parsed_path("user@host:path", "/path"); |
| 266 | check_parsed_path("host:~user/repo", "~user/repo"); |
| 267 | check_parsed_path("user@host:~user/repo", "~user/repo"); |
| 268 | check_parsed_path("[host]:src", "/src"); |
| 269 | check_parsed_path("[host:123]:src", "/src"); |
| 270 | check_parsed_path("[::1]:repo", "/repo"); |
| 271 | check_parsed_path("user@[::1]:repo", "/repo"); |
| 272 | } |
| 273 | |
| 274 | void test_urlmatch_normalization__parse_url_form(void) |
| 275 | { |
| 276 | check_parsed_path("ssh://host/repo", "/repo"); |
| 277 | check_parsed_path("ssh://host/~user/repo", "~user/repo"); |
| 278 | check_parsed_path("git://host:9418/repo", "/repo"); |
| 279 | check_parsed_path("git://host/~user/repo", "~user/repo"); |
| 280 | check_parsed_path("ssh://[::1]:1234/repo", "/repo"); |
| 281 | check_parsed_path("http://[2001:db8::1]/repo", "/repo"); |
| 282 | } |
| 283 | |
| 284 | void test_urlmatch_normalization__parse_strips_query_and_fragment(void) |
| 285 | { |
| 286 | check_parsed_path("ssh://host/~user/repo?q", "~user/repo"); |
| 287 | check_parsed_path("ssh://host/~user/repo#frag", "~user/repo"); |
| 288 | check_parsed_path("git://host/~user/repo?q", "~user/repo"); |
| 289 | check_parsed_path("user@host:~user/repo?q", "~user/repo"); |
| 290 | check_parsed_path("https://host/repo?q", "/repo"); |
| 291 | check_parsed_path("https://host/repo#frag", "/repo"); |
| 292 | } |