url-test.c++ (21701B)
1 // Copyright (c) 2017 Cloudflare, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #include "url.h" 23 #include <kj/debug.h> 24 #include <kj/test.h> 25 26 namespace kj { 27 namespace { 28 29 Url parseAndCheck(kj::StringPtr originalText, kj::StringPtr expectedRestringified = nullptr, 30 Url::Options options = {}) { 31 if (expectedRestringified == nullptr) expectedRestringified = originalText; 32 auto url = Url::parse(originalText, Url::REMOTE_HREF, options); 33 KJ_EXPECT(kj::str(url) == expectedRestringified, url, originalText, expectedRestringified); 34 // Make sure clones also restringify to the expected string. 35 auto clone = url.clone(); 36 KJ_EXPECT(kj::str(clone) == expectedRestringified, clone, originalText, expectedRestringified); 37 return url; 38 } 39 40 static constexpr Url::Options NO_DECODE { 41 false, // percentDecode 42 false, // allowEmpty 43 }; 44 45 static constexpr Url::Options ALLOW_EMPTY { 46 true, // percentDecode 47 true, // allowEmpty 48 }; 49 50 KJ_TEST("parse / stringify URL") { 51 { 52 auto url = parseAndCheck("https://capnproto.org"); 53 KJ_EXPECT(url.scheme == "https"); 54 KJ_EXPECT(url.userInfo == nullptr); 55 KJ_EXPECT(url.host == "capnproto.org"); 56 KJ_EXPECT(url.path == nullptr); 57 KJ_EXPECT(!url.hasTrailingSlash); 58 KJ_EXPECT(url.query == nullptr); 59 KJ_EXPECT(url.fragment == nullptr); 60 } 61 62 { 63 auto url = parseAndCheck("https://capnproto.org:80"); 64 KJ_EXPECT(url.scheme == "https"); 65 KJ_EXPECT(url.userInfo == nullptr); 66 KJ_EXPECT(url.host == "capnproto.org:80"); 67 KJ_EXPECT(url.path == nullptr); 68 KJ_EXPECT(!url.hasTrailingSlash); 69 KJ_EXPECT(url.query == nullptr); 70 KJ_EXPECT(url.fragment == nullptr); 71 } 72 73 { 74 auto url = parseAndCheck("https://capnproto.org/"); 75 KJ_EXPECT(url.scheme == "https"); 76 KJ_EXPECT(url.userInfo == nullptr); 77 KJ_EXPECT(url.host == "capnproto.org"); 78 KJ_EXPECT(url.path == nullptr); 79 KJ_EXPECT(url.hasTrailingSlash); 80 KJ_EXPECT(url.query == nullptr); 81 KJ_EXPECT(url.fragment == nullptr); 82 } 83 84 { 85 auto url = parseAndCheck("https://capnproto.org/foo/bar"); 86 KJ_EXPECT(url.scheme == "https"); 87 KJ_EXPECT(url.userInfo == nullptr); 88 KJ_EXPECT(url.host == "capnproto.org"); 89 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 90 KJ_EXPECT(!url.hasTrailingSlash); 91 KJ_EXPECT(url.query == nullptr); 92 KJ_EXPECT(url.fragment == nullptr); 93 } 94 95 { 96 auto url = parseAndCheck("https://capnproto.org/foo/bar/"); 97 KJ_EXPECT(url.scheme == "https"); 98 KJ_EXPECT(url.userInfo == nullptr); 99 KJ_EXPECT(url.host == "capnproto.org"); 100 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 101 KJ_EXPECT(url.hasTrailingSlash); 102 KJ_EXPECT(url.query == nullptr); 103 KJ_EXPECT(url.fragment == nullptr); 104 } 105 106 { 107 auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux&corge#garply"); 108 KJ_EXPECT(url.scheme == "https"); 109 KJ_EXPECT(url.userInfo == nullptr); 110 KJ_EXPECT(url.host == "capnproto.org"); 111 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 112 KJ_EXPECT(!url.hasTrailingSlash); 113 KJ_ASSERT(url.query.size() == 2); 114 KJ_EXPECT(url.query[0].name == "baz"); 115 KJ_EXPECT(url.query[0].value == "qux"); 116 KJ_EXPECT(url.query[1].name == "corge"); 117 KJ_EXPECT(url.query[1].value == nullptr); 118 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 119 } 120 121 { 122 auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux&corge=#garply"); 123 KJ_EXPECT(url.scheme == "https"); 124 KJ_EXPECT(url.userInfo == nullptr); 125 KJ_EXPECT(url.host == "capnproto.org"); 126 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 127 KJ_EXPECT(!url.hasTrailingSlash); 128 KJ_ASSERT(url.query.size() == 2); 129 KJ_EXPECT(url.query[0].name == "baz"); 130 KJ_EXPECT(url.query[0].value == "qux"); 131 KJ_EXPECT(url.query[1].name == "corge"); 132 KJ_EXPECT(url.query[1].value == nullptr); 133 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 134 } 135 136 { 137 auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=&corge=grault#garply"); 138 KJ_EXPECT(url.scheme == "https"); 139 KJ_EXPECT(url.userInfo == nullptr); 140 KJ_EXPECT(url.host == "capnproto.org"); 141 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 142 KJ_EXPECT(!url.hasTrailingSlash); 143 KJ_ASSERT(url.query.size() == 2); 144 KJ_EXPECT(url.query[0].name == "baz"); 145 KJ_EXPECT(url.query[0].value == ""); 146 KJ_EXPECT(url.query[1].name == "corge"); 147 KJ_EXPECT(url.query[1].value == "grault"); 148 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 149 } 150 151 { 152 auto url = parseAndCheck("https://capnproto.org/foo/bar/?baz=qux&corge=grault#garply"); 153 KJ_EXPECT(url.scheme == "https"); 154 KJ_EXPECT(url.userInfo == nullptr); 155 KJ_EXPECT(url.host == "capnproto.org"); 156 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 157 KJ_EXPECT(url.hasTrailingSlash); 158 KJ_ASSERT(url.query.size() == 2); 159 KJ_EXPECT(url.query[0].name == "baz"); 160 KJ_EXPECT(url.query[0].value == "qux"); 161 KJ_EXPECT(url.query[1].name == "corge"); 162 KJ_EXPECT(url.query[1].value == "grault"); 163 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 164 } 165 166 { 167 auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux#garply"); 168 KJ_EXPECT(url.scheme == "https"); 169 KJ_EXPECT(url.userInfo == nullptr); 170 KJ_EXPECT(url.host == "capnproto.org"); 171 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 172 KJ_EXPECT(!url.hasTrailingSlash); 173 KJ_ASSERT(url.query.size() == 1); 174 KJ_EXPECT(url.query[0].name == "baz"); 175 KJ_EXPECT(url.query[0].value == "qux"); 176 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 177 } 178 179 { 180 auto url = parseAndCheck("https://capnproto.org/foo?bar%20baz=qux+quux", 181 "https://capnproto.org/foo?bar+baz=qux+quux"); 182 KJ_ASSERT(url.query.size() == 1); 183 KJ_EXPECT(url.query[0].name == "bar baz"); 184 KJ_EXPECT(url.query[0].value == "qux quux"); 185 } 186 187 { 188 auto url = parseAndCheck("https://capnproto.org/foo/bar#garply"); 189 KJ_EXPECT(url.scheme == "https"); 190 KJ_EXPECT(url.userInfo == nullptr); 191 KJ_EXPECT(url.host == "capnproto.org"); 192 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 193 KJ_EXPECT(!url.hasTrailingSlash); 194 KJ_EXPECT(url.query == nullptr); 195 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 196 } 197 198 { 199 auto url = parseAndCheck("https://capnproto.org/foo/bar/#garply"); 200 KJ_EXPECT(url.scheme == "https"); 201 KJ_EXPECT(url.userInfo == nullptr); 202 KJ_EXPECT(url.host == "capnproto.org"); 203 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 204 KJ_EXPECT(url.hasTrailingSlash); 205 KJ_EXPECT(url.query == nullptr); 206 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 207 } 208 209 { 210 auto url = parseAndCheck("https://capnproto.org#garply"); 211 KJ_EXPECT(url.scheme == "https"); 212 KJ_EXPECT(url.userInfo == nullptr); 213 KJ_EXPECT(url.host == "capnproto.org"); 214 KJ_EXPECT(url.path == nullptr); 215 KJ_EXPECT(!url.hasTrailingSlash); 216 KJ_EXPECT(url.query == nullptr); 217 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 218 } 219 220 { 221 auto url = parseAndCheck("https://capnproto.org/#garply"); 222 KJ_EXPECT(url.scheme == "https"); 223 KJ_EXPECT(url.userInfo == nullptr); 224 KJ_EXPECT(url.host == "capnproto.org"); 225 KJ_EXPECT(url.path == nullptr); 226 KJ_EXPECT(url.hasTrailingSlash); 227 KJ_EXPECT(url.query == nullptr); 228 KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); 229 } 230 231 { 232 auto url = parseAndCheck("https://foo@capnproto.org"); 233 KJ_EXPECT(url.scheme == "https"); 234 auto& user = KJ_ASSERT_NONNULL(url.userInfo); 235 KJ_EXPECT(user.username == "foo"); 236 KJ_EXPECT(user.password == nullptr); 237 KJ_EXPECT(url.host == "capnproto.org"); 238 KJ_EXPECT(url.path == nullptr); 239 KJ_EXPECT(!url.hasTrailingSlash); 240 KJ_EXPECT(url.query == nullptr); 241 KJ_EXPECT(url.fragment == nullptr); 242 } 243 244 { 245 auto url = parseAndCheck("https://$foo&:12+,34@capnproto.org"); 246 KJ_EXPECT(url.scheme == "https"); 247 auto& user = KJ_ASSERT_NONNULL(url.userInfo); 248 KJ_EXPECT(user.username == "$foo&"); 249 KJ_EXPECT(KJ_ASSERT_NONNULL(user.password) == "12+,34"); 250 KJ_EXPECT(url.host == "capnproto.org"); 251 KJ_EXPECT(url.path == nullptr); 252 KJ_EXPECT(!url.hasTrailingSlash); 253 KJ_EXPECT(url.query == nullptr); 254 KJ_EXPECT(url.fragment == nullptr); 255 } 256 257 { 258 auto url = parseAndCheck("https://[2001:db8::1234]:80/foo"); 259 KJ_EXPECT(url.scheme == "https"); 260 KJ_EXPECT(url.userInfo == nullptr); 261 KJ_EXPECT(url.host == "[2001:db8::1234]:80"); 262 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo"})); 263 KJ_EXPECT(!url.hasTrailingSlash); 264 KJ_EXPECT(url.query == nullptr); 265 KJ_EXPECT(url.fragment == nullptr); 266 } 267 268 { 269 auto url = parseAndCheck("https://capnproto.org/foo%2Fbar/baz"); 270 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo/bar", "baz"})); 271 } 272 273 parseAndCheck("https://capnproto.org/foo/bar?", "https://capnproto.org/foo/bar"); 274 parseAndCheck("https://capnproto.org/foo/bar?#", "https://capnproto.org/foo/bar#"); 275 parseAndCheck("https://capnproto.org/foo/bar#"); 276 277 // Scheme and host are forced to lower-case. 278 parseAndCheck("hTtP://capNprotO.org/fOo/bAr", "http://capnproto.org/fOo/bAr"); 279 280 // URLs with underscores in their hostnames are allowed, but you probably shouldn't use them. They 281 // are not valid domain names. 282 parseAndCheck("https://bad_domain.capnproto.org/"); 283 284 // Make sure URLs with %-encoded '%' signs in their userinfo, path, query, and fragment components 285 // get correctly re-encoded. 286 parseAndCheck("https://foo%25bar:baz%25qux@capnproto.org/"); 287 parseAndCheck("https://capnproto.org/foo%25bar"); 288 parseAndCheck("https://capnproto.org/?foo%25bar=baz%25qux"); 289 parseAndCheck("https://capnproto.org/#foo%25bar"); 290 291 // Make sure redundant /'s and &'s aren't collapsed when options.removeEmpty is false. 292 parseAndCheck("https://capnproto.org/foo//bar///test//?foo=bar&&baz=qux&", nullptr, ALLOW_EMPTY); 293 294 // "." and ".." are still processed, though. 295 parseAndCheck("https://capnproto.org/foo//../bar/.", 296 "https://capnproto.org/foo/bar/", ALLOW_EMPTY); 297 298 { 299 auto url = parseAndCheck("https://foo/", nullptr, ALLOW_EMPTY); 300 KJ_EXPECT(url.path.size() == 0); 301 KJ_EXPECT(url.hasTrailingSlash); 302 } 303 304 { 305 auto url = parseAndCheck("https://foo/bar/", nullptr, ALLOW_EMPTY); 306 KJ_EXPECT(url.path.size() == 1); 307 KJ_EXPECT(url.hasTrailingSlash); 308 } 309 } 310 311 KJ_TEST("URL percent encoding") { 312 parseAndCheck( 313 "https://b%6fb:%61bcd@capnpr%6fto.org/f%6fo?b%61r=b%61z#q%75x", 314 "https://bob:abcd@capnproto.org/foo?bar=baz#qux"); 315 316 parseAndCheck( 317 "https://b\001b:\001bcd@capnproto.org/f\001o?b\001r=b\001z#q\001x", 318 "https://b%01b:%01bcd@capnproto.org/f%01o?b%01r=b%01z#q%01x"); 319 320 parseAndCheck( 321 "https://b b: bcd@capnproto.org/f o?b r=b z#q x", 322 "https://b%20b:%20bcd@capnproto.org/f%20o?b+r=b+z#q%20x"); 323 324 parseAndCheck( 325 "https://capnproto.org/foo?bar=baz#@?#^[\\]{|}", 326 "https://capnproto.org/foo?bar=baz#@?#^[\\]{|}"); 327 328 // All permissible non-alphanumeric, non-separator path characters. 329 parseAndCheck( 330 "https://capnproto.org/!$&'()*+,-.:;=@[]^_|~", 331 "https://capnproto.org/!$&'()*+,-.:;=@[]^_|~"); 332 } 333 334 KJ_TEST("parse / stringify URL w/o decoding") { 335 { 336 auto url = parseAndCheck("https://capnproto.org/foo%2Fbar/baz", nullptr, NO_DECODE); 337 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo%2Fbar", "baz"})); 338 } 339 340 { 341 // This case would throw an exception without NO_DECODE. 342 Url url = parseAndCheck("https://capnproto.org/R%20%26%20S?%foo=%QQ", nullptr, NO_DECODE); 343 KJ_EXPECT(url.scheme == "https"); 344 KJ_EXPECT(url.host == "capnproto.org"); 345 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"R%20%26%20S"})); 346 KJ_EXPECT(!url.hasTrailingSlash); 347 KJ_ASSERT(url.query.size() == 1); 348 KJ_EXPECT(url.query[0].name == "%foo"); 349 KJ_EXPECT(url.query[0].value == "%QQ"); 350 } 351 } 352 353 KJ_TEST("URL relative paths") { 354 parseAndCheck( 355 "https://capnproto.org/foo//bar", 356 "https://capnproto.org/foo/bar"); 357 358 parseAndCheck( 359 "https://capnproto.org/foo/./bar", 360 "https://capnproto.org/foo/bar"); 361 362 parseAndCheck( 363 "https://capnproto.org/foo/bar//", 364 "https://capnproto.org/foo/bar/"); 365 366 parseAndCheck( 367 "https://capnproto.org/foo/bar/.", 368 "https://capnproto.org/foo/bar/"); 369 370 parseAndCheck( 371 "https://capnproto.org/foo/baz/../bar", 372 "https://capnproto.org/foo/bar"); 373 374 parseAndCheck( 375 "https://capnproto.org/foo/bar/baz/..", 376 "https://capnproto.org/foo/bar/"); 377 378 parseAndCheck( 379 "https://capnproto.org/..", 380 "https://capnproto.org/"); 381 382 parseAndCheck( 383 "https://capnproto.org/foo/../..", 384 "https://capnproto.org/"); 385 } 386 387 KJ_TEST("URL for HTTP request") { 388 { 389 Url url = Url::parse("https://bob:1234@capnproto.org/foo/bar?baz=qux#corge"); 390 KJ_EXPECT(url.toString(Url::REMOTE_HREF) == 391 "https://bob:1234@capnproto.org/foo/bar?baz=qux#corge"); 392 KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/foo/bar?baz=qux"); 393 KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/foo/bar?baz=qux"); 394 } 395 396 { 397 Url url = Url::parse("https://capnproto.org"); 398 KJ_EXPECT(url.toString(Url::REMOTE_HREF) == "https://capnproto.org"); 399 KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org"); 400 KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/"); 401 } 402 403 { 404 Url url = Url::parse("/foo/bar?baz=qux&corge", Url::HTTP_REQUEST); 405 KJ_EXPECT(url.scheme == nullptr); 406 KJ_EXPECT(url.host == nullptr); 407 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 408 KJ_EXPECT(!url.hasTrailingSlash); 409 KJ_ASSERT(url.query.size() == 2); 410 KJ_EXPECT(url.query[0].name == "baz"); 411 KJ_EXPECT(url.query[0].value == "qux"); 412 KJ_EXPECT(url.query[1].name == "corge"); 413 KJ_EXPECT(url.query[1].value == nullptr); 414 } 415 416 { 417 Url url = Url::parse("https://capnproto.org/foo/bar?baz=qux&corge", Url::HTTP_PROXY_REQUEST); 418 KJ_EXPECT(url.scheme == "https"); 419 KJ_EXPECT(url.host == "capnproto.org"); 420 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"})); 421 KJ_EXPECT(!url.hasTrailingSlash); 422 KJ_ASSERT(url.query.size() == 2); 423 KJ_EXPECT(url.query[0].name == "baz"); 424 KJ_EXPECT(url.query[0].value == "qux"); 425 KJ_EXPECT(url.query[1].name == "corge"); 426 KJ_EXPECT(url.query[1].value == nullptr); 427 } 428 429 { 430 // '#' is allowed in path components in the HTTP_REQUEST context. 431 Url url = Url::parse("/foo#bar", Url::HTTP_REQUEST); 432 KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/foo%23bar"); 433 KJ_EXPECT(url.scheme == nullptr); 434 KJ_EXPECT(url.host == nullptr); 435 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>{"foo#bar"}); 436 KJ_EXPECT(!url.hasTrailingSlash); 437 KJ_EXPECT(url.query == nullptr); 438 KJ_EXPECT(url.fragment == nullptr); 439 } 440 441 { 442 // '#' is allowed in path components in the HTTP_PROXY_REQUEST context. 443 Url url = Url::parse("https://capnproto.org/foo#bar", Url::HTTP_PROXY_REQUEST); 444 KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/foo%23bar"); 445 KJ_EXPECT(url.scheme == "https"); 446 KJ_EXPECT(url.host == "capnproto.org"); 447 KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>{"foo#bar"}); 448 KJ_EXPECT(!url.hasTrailingSlash); 449 KJ_EXPECT(url.query == nullptr); 450 KJ_EXPECT(url.fragment == nullptr); 451 } 452 453 { 454 // '#' is allowed in query components in the HTTP_REQUEST context. 455 Url url = Url::parse("/?foo=bar#123", Url::HTTP_REQUEST); 456 KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/?foo=bar%23123"); 457 KJ_EXPECT(url.scheme == nullptr); 458 KJ_EXPECT(url.host == nullptr); 459 KJ_EXPECT(url.path == nullptr); 460 KJ_EXPECT(url.hasTrailingSlash); 461 KJ_ASSERT(url.query.size() == 1); 462 KJ_EXPECT(url.query[0].name == "foo"); 463 KJ_EXPECT(url.query[0].value == "bar#123"); 464 KJ_EXPECT(url.fragment == nullptr); 465 } 466 467 { 468 // '#' is allowed in query components in the HTTP_PROXY_REQUEST context. 469 Url url = Url::parse("https://capnproto.org/?foo=bar#123", Url::HTTP_PROXY_REQUEST); 470 KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/?foo=bar%23123"); 471 KJ_EXPECT(url.scheme == "https"); 472 KJ_EXPECT(url.host == "capnproto.org"); 473 KJ_EXPECT(url.path == nullptr); 474 KJ_EXPECT(url.hasTrailingSlash); 475 KJ_ASSERT(url.query.size() == 1); 476 KJ_EXPECT(url.query[0].name == "foo"); 477 KJ_EXPECT(url.query[0].value == "bar#123"); 478 KJ_EXPECT(url.fragment == nullptr); 479 } 480 } 481 482 KJ_TEST("parse URL failure") { 483 KJ_EXPECT(Url::tryParse("ht/tps://capnproto.org") == nullptr); 484 KJ_EXPECT(Url::tryParse("capnproto.org") == nullptr); 485 KJ_EXPECT(Url::tryParse("https:foo") == nullptr); 486 487 // percent-decode errors 488 KJ_EXPECT(Url::tryParse("https://capnproto.org/f%nno") == nullptr); 489 KJ_EXPECT(Url::tryParse("https://capnproto.org/foo?b%nnr=baz") == nullptr); 490 491 // components not valid in context 492 KJ_EXPECT(Url::tryParse("https://capnproto.org/foo", Url::HTTP_REQUEST) == nullptr); 493 KJ_EXPECT(Url::tryParse("https://bob:123@capnproto.org/foo", Url::HTTP_PROXY_REQUEST) == nullptr); 494 KJ_EXPECT(Url::tryParse("https://capnproto.org#/foo", Url::HTTP_PROXY_REQUEST) == nullptr); 495 } 496 497 void parseAndCheckRelative(kj::StringPtr base, kj::StringPtr relative, kj::StringPtr expected, 498 Url::Options options = {}) { 499 auto parsed = Url::parse(base, Url::REMOTE_HREF, options).parseRelative(relative); 500 KJ_EXPECT(kj::str(parsed) == expected, parsed, expected); 501 auto clone = parsed.clone(); 502 KJ_EXPECT(kj::str(clone) == expected, clone, expected); 503 } 504 505 KJ_TEST("parse relative URL") { 506 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 507 "#grault", 508 "https://capnproto.org/foo/bar?baz=qux#grault"); 509 parseAndCheckRelative("https://capnproto.org/foo/bar?baz#corge", 510 "#grault", 511 "https://capnproto.org/foo/bar?baz#grault"); 512 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=#corge", 513 "#grault", 514 "https://capnproto.org/foo/bar?baz=#grault"); 515 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 516 "?grault", 517 "https://capnproto.org/foo/bar?grault"); 518 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 519 "?grault=", 520 "https://capnproto.org/foo/bar?grault="); 521 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 522 "?grault+garply=waldo", 523 "https://capnproto.org/foo/bar?grault+garply=waldo"); 524 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 525 "grault", 526 "https://capnproto.org/foo/grault"); 527 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 528 "/grault", 529 "https://capnproto.org/grault"); 530 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 531 "//grault", 532 "https://grault"); 533 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 534 "//grault/garply", 535 "https://grault/garply"); 536 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 537 "http:/grault", 538 "http://capnproto.org/grault"); 539 parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", 540 "/http:/grault", 541 "https://capnproto.org/http:/grault"); 542 parseAndCheckRelative("https://capnproto.org/", 543 "/foo/../bar", 544 "https://capnproto.org/bar"); 545 } 546 547 KJ_TEST("parse relative URL w/o decoding") { 548 // This case would throw an exception without NO_DECODE. 549 parseAndCheckRelative("https://capnproto.org/R%20%26%20S?%foo=%QQ", 550 "%ANOTH%ERBAD%URL", 551 "https://capnproto.org/%ANOTH%ERBAD%URL", NO_DECODE); 552 } 553 554 KJ_TEST("parse relative URL failure") { 555 auto base = Url::parse("https://example.com/"); 556 KJ_EXPECT(base.tryParseRelative("https://[not a host]") == nullptr); 557 } 558 559 } // namespace 560 } // namespace kj