1 /* Based on nsURLParsers.cc from Mozilla 2 * ------------------------------------- 3 * The contents of this file are subject to the Mozilla Public License Version 4 * 1.1 (the "License"); you may not use this file except in compliance with 5 * the License. You may obtain a copy of the License at 6 * http://www.mozilla.org/MPL/ 7 * 8 * Software distributed under the License is distributed on an "AS IS" basis, 9 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 10 * for the specific language governing rights and limitations under the 11 * License. 12 * 13 * The Original Code is mozilla.org code. 14 * 15 * The Initial Developer of the Original Code is 16 * Netscape Communications Corporation. 17 * Portions created by the Initial Developer are Copyright (C) 1998 18 * the Initial Developer. All Rights Reserved. 19 * 20 * Contributor(s): 21 * Darin Fisher (original author) 22 * 23 * Alternatively, the contents of this file may be used under the terms of 24 * either the GNU General Public License Version 2 or later (the "GPL"), or 25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 26 * in which case the provisions of the GPL or the LGPL are applicable instead 27 * of those above. If you wish to allow use of your version of this file only 28 * under the terms of either the GPL or the LGPL, and not to allow others to 29 * use your version of this file under the terms of the MPL, indicate your 30 * decision by deleting the provisions above and replace them with the notice 31 * and other provisions required by the GPL or the LGPL. If you do not delete 32 * the provisions above, a recipient may use your version of this file under 33 * the terms of any one of the MPL, the GPL or the LGPL. 34 * 35 * ***** END LICENSE BLOCK ***** */ 36 37 #include "googleurl/src/url_parse.h" 38 39 #include <stdlib.h> 40 41 #include "base/logging.h" 42 #include "googleurl/src/url_parse_internal.h" 43 44 namespace url_parse { 45 46 namespace { 47 48 // Returns true if the given character is a valid digit to use in a port. 49 inline bool IsPortDigit(char16 ch) { 50 return ch >= '0' && ch <= '9'; 51 } 52 53 // Returns the offset of the next authority terminator in the input starting 54 // from start_offset. If no terminator is found, the return value will be equal 55 // to spec_len. 56 template<typename CHAR> 57 int FindNextAuthorityTerminator(const CHAR* spec, 58 int start_offset, 59 int spec_len) { 60 for (int i = start_offset; i < spec_len; i++) { 61 if (IsAuthorityTerminator(spec[i])) 62 return i; 63 } 64 return spec_len; // Not found. 65 } 66 67 // Fills in all members of the Parsed structure except for the scheme. 68 // 69 // |spec| is the full spec being parsed, of length |spec_len|. 70 // |after_scheme| is the character immediately following the scheme (after the 71 // colon) where we'll begin parsing. 72 // 73 // Compatability data points. I list "host", "path" extracted: 74 // Input IE6 Firefox Us 75 // ----- -------------- -------------- -------------- 76 // http://foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" 77 // http:foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" 78 // http:/foo.com/ fail(*) "foo.com", "/" "foo.com", "/" 79 // http:\foo.com/ fail(*) "\foo.com", "/"(fail) "foo.com", "/" 80 // http:////foo.com/ "foo.com", "/" "foo.com", "/" "foo.com", "/" 81 // 82 // (*) Interestingly, although IE fails to load these URLs, its history 83 // canonicalizer handles them, meaning if you've been to the corresponding 84 // "http://foo.com/" link, it will be colored. 85 template <typename CHAR> 86 void DoParseAfterScheme(const CHAR* spec, 87 int spec_len, 88 int after_scheme, 89 Parsed* parsed) { 90 int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len); 91 int after_slashes = after_scheme + num_slashes; 92 93 // First split into two main parts, the authority (username, password, host, 94 // and port) and the full path (path, query, and reference). 95 Component authority; 96 Component full_path; 97 98 // Found "//<some data>", looks like an authority section. Treat everything 99 // from there to the next slash (or end of spec) to be the authority. Note 100 // that we ignore the number of slashes and treat it as the authority. 101 int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len); 102 authority = Component(after_slashes, end_auth - after_slashes); 103 104 if (end_auth == spec_len) // No beginning of path found. 105 full_path = Component(); 106 else // Everything starting from the slash to the end is the path. 107 full_path = Component(end_auth, spec_len - end_auth); 108 109 // Now parse those two sub-parts. 110 DoParseAuthority(spec, authority, &parsed->username, &parsed->password, 111 &parsed->host, &parsed->port); 112 ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref); 113 } 114 115 template<typename CHAR> 116 void ParseUserInfo(const CHAR* spec, 117 const Component& user, 118 Component* username, 119 Component* password) { 120 // Find the first colon in the user section, which separates the username and 121 // password. 122 int colon_offset = 0; 123 while (colon_offset < user.len && spec[user.begin + colon_offset] != ':') 124 colon_offset++; 125 126 if (colon_offset < user.len) { 127 // Found separator: <username>:<password> 128 *username = Component(user.begin, colon_offset); 129 *password = MakeRange(user.begin + colon_offset + 1, 130 user.begin + user.len); 131 } else { 132 // No separator, treat everything as the username 133 *username = user; 134 *password = Component(); 135 } 136 } 137 138 template<typename CHAR> 139 void ParseServerInfo(const CHAR* spec, 140 const Component& serverinfo, 141 Component* hostname, 142 Component* port_num) { 143 if (serverinfo.len == 0) { 144 // No server info, host name is empty. 145 hostname->reset(); 146 port_num->reset(); 147 return; 148 } 149 150 // If the host starts with a left-bracket, assume the entire host is an 151 // IPv6 literal. Otherwise, assume none of the host is an IPv6 literal. 152 // This assumption will be overridden if we find a right-bracket. 153 // 154 // Our IPv6 address canonicalization code requires both brackets to exist, 155 // but the ability to locate an incomplete address can still be useful. 156 int ipv6_terminator = spec[serverinfo.begin] == '[' ? serverinfo.end() : -1; 157 int colon = -1; 158 159 // Find the last right-bracket, and the last colon. 160 for (int i = serverinfo.begin; i < serverinfo.end(); i++) { 161 switch (spec[i]) { 162 case ']': 163 ipv6_terminator = i; 164 break; 165 case ':': 166 colon = i; 167 break; 168 } 169 } 170 171 if (colon > ipv6_terminator) { 172 // Found a port number: <hostname>:<port> 173 *hostname = MakeRange(serverinfo.begin, colon); 174 if (hostname->len == 0) 175 hostname->reset(); 176 *port_num = MakeRange(colon + 1, serverinfo.end()); 177 } else { 178 // No port: <hostname> 179 *hostname = serverinfo; 180 port_num->reset(); 181 } 182 } 183 184 // Given an already-identified auth section, breaks it into its consituent 185 // parts. The port number will be parsed and the resulting integer will be 186 // filled into the given *port variable, or -1 if there is no port number or it 187 // is invalid. 188 template<typename CHAR> 189 void DoParseAuthority(const CHAR* spec, 190 const Component& auth, 191 Component* username, 192 Component* password, 193 Component* hostname, 194 Component* port_num) { 195 DCHECK(auth.is_valid()) << "We should always get an authority"; 196 if (auth.len == 0) { 197 username->reset(); 198 password->reset(); 199 hostname->reset(); 200 port_num->reset(); 201 return; 202 } 203 204 // Search backwards for @, which is the separator between the user info and 205 // the server info. 206 int i = auth.begin + auth.len - 1; 207 while (i > auth.begin && spec[i] != '@') 208 i--; 209 210 if (spec[i] == '@') { 211 // Found user info: <user-info>@<server-info> 212 ParseUserInfo(spec, Component(auth.begin, i - auth.begin), 213 username, password); 214 ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), 215 hostname, port_num); 216 } else { 217 // No user info, everything is server info. 218 username->reset(); 219 password->reset(); 220 ParseServerInfo(spec, auth, hostname, port_num); 221 } 222 } 223 224 template<typename CHAR> 225 void ParsePath(const CHAR* spec, 226 const Component& path, 227 Component* filepath, 228 Component* query, 229 Component* ref) { 230 // path = [/]<segment1>/<segment2>/<...>/<segmentN>;<param>?<query>#<ref> 231 232 // Special case when there is no path. 233 if (path.len == -1) { 234 filepath->reset(); 235 query->reset(); 236 ref->reset(); 237 return; 238 } 239 DCHECK(path.len > 0) << "We should never have 0 length paths"; 240 241 // Search for first occurrence of either ? or #. 242 int path_end = path.begin + path.len; 243 244 int query_separator = -1; // Index of the '?' 245 int ref_separator = -1; // Index of the '#' 246 for (int i = path.begin; i < path_end; i++) { 247 switch (spec[i]) { 248 case '?': 249 // Only match the query string if it precedes the reference fragment 250 // and when we haven't found one already. 251 if (ref_separator < 0 && query_separator < 0) 252 query_separator = i; 253 break; 254 case '#': 255 // Record the first # sign only. 256 if (ref_separator < 0) 257 ref_separator = i; 258 break; 259 } 260 } 261 262 // Markers pointing to the character after each of these corresponding 263 // components. The code below words from the end back to the beginning, 264 // and will update these indices as it finds components that exist. 265 int file_end, query_end; 266 267 // Ref fragment: from the # to the end of the path. 268 if (ref_separator >= 0) { 269 file_end = query_end = ref_separator; 270 *ref = MakeRange(ref_separator + 1, path_end); 271 } else { 272 file_end = query_end = path_end; 273 ref->reset(); 274 } 275 276 // Query fragment: everything from the ? to the next boundary (either the end 277 // of the path or the ref fragment). 278 if (query_separator >= 0) { 279 file_end = query_separator; 280 *query = MakeRange(query_separator + 1, query_end); 281 } else { 282 query->reset(); 283 } 284 285 // File path: treat an empty file path as no file path. 286 if (file_end != path.begin) 287 *filepath = MakeRange(path.begin, file_end); 288 else 289 filepath->reset(); 290 } 291 292 template<typename CHAR> 293 bool DoExtractScheme(const CHAR* url, 294 int url_len, 295 Component* scheme) { 296 // Skip leading whitespace and control characters. 297 int begin = 0; 298 while (begin < url_len && ShouldTrimFromURL(url[begin])) 299 begin++; 300 if (begin == url_len) 301 return false; // Input is empty or all whitespace. 302 303 // Find the first colon character. 304 for (int i = begin; i < url_len; i++) { 305 if (url[i] == ':') { 306 *scheme = MakeRange(begin, i); 307 return true; 308 } 309 } 310 return false; // No colon found: no scheme 311 } 312 313 // The main parsing function for standard URLs. Standard URLs have a scheme, 314 // host, path, etc. 315 template<typename CHAR> 316 void DoParseStandardURL(const CHAR* spec, int spec_len, Parsed* parsed) { 317 DCHECK(spec_len >= 0); 318 319 // Strip leading & trailing spaces and control characters. 320 int begin = 0; 321 TrimURL(spec, &begin, &spec_len); 322 323 int after_scheme; 324 if (DoExtractScheme(spec, spec_len, &parsed->scheme)) { 325 after_scheme = parsed->scheme.end() + 1; // Skip past the colon. 326 } else { 327 // Say there's no scheme when there is a colon. We could also say that 328 // everything is the scheme. Both would produce an invalid URL, but this way 329 // seems less wrong in more cases. 330 parsed->scheme.reset(); 331 after_scheme = begin; 332 } 333 DoParseAfterScheme(spec, spec_len, after_scheme, parsed); 334 } 335 336 // Initializes a path URL which is merely a scheme followed by a path. Examples 337 // include "about:foo" and "javascript:alert('bar');" 338 template<typename CHAR> 339 void DoParsePathURL(const CHAR* spec, int spec_len, Parsed* parsed) { 340 // Get the non-path and non-scheme parts of the URL out of the way, we never 341 // use them. 342 parsed->username.reset(); 343 parsed->password.reset(); 344 parsed->host.reset(); 345 parsed->port.reset(); 346 parsed->query.reset(); 347 parsed->ref.reset(); 348 349 // Strip leading & trailing spaces and control characters. 350 int begin = 0; 351 TrimURL(spec, &begin, &spec_len); 352 353 // Handle empty specs or ones that contain only whitespace or control chars. 354 if (begin == spec_len) { 355 parsed->scheme.reset(); 356 parsed->path.reset(); 357 return; 358 } 359 360 // Extract the scheme, with the path being everything following. We also 361 // handle the case where there is no scheme. 362 if (ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { 363 // Offset the results since we gave ExtractScheme a substring. 364 parsed->scheme.begin += begin; 365 366 // For compatability with the standard URL parser, we treat no path as 367 // -1, rather than having a length of 0 (we normally wouldn't care so 368 // much for these non-standard URLs). 369 if (parsed->scheme.end() == spec_len - 1) 370 parsed->path.reset(); 371 else 372 parsed->path = MakeRange(parsed->scheme.end() + 1, spec_len); 373 } else { 374 // No scheme found, just path. 375 parsed->scheme.reset(); 376 parsed->path = MakeRange(begin, spec_len); 377 } 378 } 379 380 template<typename CHAR> 381 void DoParseMailtoURL(const CHAR* spec, int spec_len, Parsed* parsed) { 382 DCHECK(spec_len >= 0); 383 384 // Get the non-path and non-scheme parts of the URL out of the way, we never 385 // use them. 386 parsed->username.reset(); 387 parsed->password.reset(); 388 parsed->host.reset(); 389 parsed->port.reset(); 390 parsed->ref.reset(); 391 parsed->query.reset(); // May use this; reset for convenience. 392 393 // Strip leading & trailing spaces and control characters. 394 int begin = 0; 395 TrimURL(spec, &begin, &spec_len); 396 397 // Handle empty specs or ones that contain only whitespace or control chars. 398 if (begin == spec_len) { 399 parsed->scheme.reset(); 400 parsed->path.reset(); 401 return; 402 } 403 404 int path_begin = -1; 405 int path_end = -1; 406 407 // Extract the scheme, with the path being everything following. We also 408 // handle the case where there is no scheme. 409 if (ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) { 410 // Offset the results since we gave ExtractScheme a substring. 411 parsed->scheme.begin += begin; 412 413 if (parsed->scheme.end() != spec_len - 1) { 414 path_begin = parsed->scheme.end() + 1; 415 path_end = spec_len; 416 } 417 } else { 418 // No scheme found, just path. 419 parsed->scheme.reset(); 420 path_begin = begin; 421 path_end = spec_len; 422 } 423 424 // Split [path_begin, path_end) into a path + query. 425 for (int i = path_begin; i < path_end; ++i) { 426 if (spec[i] == '?') { 427 parsed->query = MakeRange(i + 1, path_end); 428 path_end = i; 429 break; 430 } 431 } 432 433 // For compatability with the standard URL parser, treat no path as 434 // -1, rather than having a length of 0 435 if (path_begin == path_end) { 436 parsed->path.reset(); 437 } else { 438 parsed->path = MakeRange(path_begin, path_end); 439 } 440 } 441 442 // Converts a port number in a string to an integer. We'd like to just call 443 // sscanf but our input is not NULL-terminated, which sscanf requires. Instead, 444 // we copy the digits to a small stack buffer (since we know the maximum number 445 // of digits in a valid port number) that we can NULL terminate. 446 template<typename CHAR> 447 int DoParsePort(const CHAR* spec, const Component& component) { 448 // Easy success case when there is no port. 449 const int kMaxDigits = 5; 450 if (!component.is_nonempty()) 451 return PORT_UNSPECIFIED; 452 453 // Skip over any leading 0s. 454 Component digits_comp(component.end(), 0); 455 for (int i = 0; i < component.len; i++) { 456 if (spec[component.begin + i] != '0') { 457 digits_comp = MakeRange(component.begin + i, component.end()); 458 break; 459 } 460 } 461 if (digits_comp.len == 0) 462 return 0; // All digits were 0. 463 464 // Verify we don't have too many digits (we'll be copying to our buffer so 465 // we need to double-check). 466 if (digits_comp.len > kMaxDigits) 467 return PORT_INVALID; 468 469 // Copy valid digits to the buffer. 470 char digits[kMaxDigits + 1]; // +1 for null terminator 471 for (int i = 0; i < digits_comp.len; i++) { 472 CHAR ch = spec[digits_comp.begin + i]; 473 if (!IsPortDigit(ch)) { 474 // Invalid port digit, fail. 475 return PORT_INVALID; 476 } 477 digits[i] = static_cast<char>(ch); 478 } 479 480 // Null-terminate the string and convert to integer. Since we guarantee 481 // only digits, atoi's lack of error handling is OK. 482 digits[digits_comp.len] = 0; 483 int port = atoi(digits); 484 if (port > 65535) 485 return PORT_INVALID; // Out of range. 486 return port; 487 } 488 489 template<typename CHAR> 490 void DoExtractFileName(const CHAR* spec, 491 const Component& path, 492 Component* file_name) { 493 // Handle empty paths: they have no file names. 494 if (!path.is_nonempty()) { 495 file_name->reset(); 496 return; 497 } 498 499 // Search backwards for a parameter, which is a normally unused field in a 500 // URL delimited by a semicolon. We parse the parameter as part of the 501 // path, but here, we don't want to count it. The last semicolon is the 502 // parameter. The path should start with a slash, so we don't need to check 503 // the first one. 504 int file_end = path.end(); 505 for (int i = path.end() - 1; i > path.begin; i--) { 506 if (spec[i] == ';') { 507 file_end = i; 508 break; 509 } 510 } 511 512 // Now search backwards from the filename end to the previous slash 513 // to find the beginning of the filename. 514 for (int i = file_end - 1; i >= path.begin; i--) { 515 if (IsURLSlash(spec[i])) { 516 // File name is everything following this character to the end 517 *file_name = MakeRange(i + 1, file_end); 518 return; 519 } 520 } 521 522 // No slash found, this means the input was degenerate (generally paths 523 // will start with a slash). Let's call everything the file name. 524 *file_name = MakeRange(path.begin, file_end); 525 return; 526 } 527 528 template<typename CHAR> 529 bool DoExtractQueryKeyValue(const CHAR* spec, 530 Component* query, 531 Component* key, 532 Component* value) { 533 if (!query->is_nonempty()) 534 return false; 535 536 int start = query->begin; 537 int cur = start; 538 int end = query->end(); 539 540 // We assume the beginning of the input is the beginning of the "key" and we 541 // skip to the end of it. 542 key->begin = cur; 543 while (cur < end && spec[cur] != '&' && spec[cur] != '=') 544 cur++; 545 key->len = cur - key->begin; 546 547 // Skip the separator after the key (if any). 548 if (cur < end && spec[cur] == '=') 549 cur++; 550 551 // Find the value part. 552 value->begin = cur; 553 while (cur < end && spec[cur] != '&') 554 cur++; 555 value->len = cur - value->begin; 556 557 // Finally skip the next separator if any 558 if (cur < end && spec[cur] == '&') 559 cur++; 560 561 // Save the new query 562 *query = url_parse::MakeRange(cur, end); 563 return true; 564 } 565 566 } // namespace 567 568 int Parsed::Length() const { 569 if (ref.is_valid()) 570 return ref.end(); 571 return CountCharactersBefore(REF, false); 572 } 573 574 int Parsed::CountCharactersBefore(ComponentType type, 575 bool include_delimiter) const { 576 if (type == SCHEME) 577 return scheme.begin; 578 579 // There will be some characters after the scheme like "://" and we don't 580 // know how many. Search forwards for the next thing until we find one. 581 int cur = 0; 582 if (scheme.is_valid()) 583 cur = scheme.end() + 1; // Advance over the ':' at the end of the scheme. 584 585 if (username.is_valid()) { 586 if (type <= USERNAME) 587 return username.begin; 588 cur = username.end() + 1; // Advance over the '@' or ':' at the end. 589 } 590 591 if (password.is_valid()) { 592 if (type <= PASSWORD) 593 return password.begin; 594 cur = password.end() + 1; // Advance over the '@' at the end. 595 } 596 597 if (host.is_valid()) { 598 if (type <= HOST) 599 return host.begin; 600 cur = host.end(); 601 } 602 603 if (port.is_valid()) { 604 if (type < PORT || (type == PORT && include_delimiter)) 605 return port.begin - 1; // Back over delimiter. 606 if (type == PORT) 607 return port.begin; // Don't want delimiter counted. 608 cur = port.end(); 609 } 610 611 if (path.is_valid()) { 612 if (type <= PATH) 613 return path.begin; 614 cur = path.end(); 615 } 616 617 if (query.is_valid()) { 618 if (type < QUERY || (type == QUERY && include_delimiter)) 619 return query.begin - 1; // Back over delimiter. 620 if (type == QUERY) 621 return query.begin; // Don't want delimiter counted. 622 cur = query.end(); 623 } 624 625 if (ref.is_valid()) { 626 if (type == REF && !include_delimiter) 627 return ref.begin; // Back over delimiter. 628 629 // When there is a ref and we get here, the component we wanted was before 630 // this and not found, so we always know the beginning of the ref is right. 631 return ref.begin - 1; // Don't want delimiter counted. 632 } 633 634 return cur; 635 } 636 637 bool ExtractScheme(const char* url, int url_len, Component* scheme) { 638 return DoExtractScheme(url, url_len, scheme); 639 } 640 641 bool ExtractScheme(const char16* url, int url_len, Component* scheme) { 642 return DoExtractScheme(url, url_len, scheme); 643 } 644 645 // This handles everything that may be an authority terminator, including 646 // backslash. For special backslash handling see DoParseAfterScheme. 647 bool IsAuthorityTerminator(char16 ch) { 648 return IsURLSlash(ch) || ch == '?' || ch == '#' || ch == ';'; 649 } 650 651 void ExtractFileName(const char* url, 652 const Component& path, 653 Component* file_name) { 654 DoExtractFileName(url, path, file_name); 655 } 656 657 void ExtractFileName(const char16* url, 658 const Component& path, 659 Component* file_name) { 660 DoExtractFileName(url, path, file_name); 661 } 662 663 bool ExtractQueryKeyValue(const char* url, 664 Component* query, 665 Component* key, 666 Component* value) { 667 return DoExtractQueryKeyValue(url, query, key, value); 668 } 669 670 bool ExtractQueryKeyValue(const char16* url, 671 Component* query, 672 Component* key, 673 Component* value) { 674 return DoExtractQueryKeyValue(url, query, key, value); 675 } 676 677 void ParseAuthority(const char* spec, 678 const Component& auth, 679 Component* username, 680 Component* password, 681 Component* hostname, 682 Component* port_num) { 683 DoParseAuthority(spec, auth, username, password, hostname, port_num); 684 } 685 686 void ParseAuthority(char16* spec, 687 const Component& auth, 688 Component* username, 689 Component* password, 690 Component* hostname, 691 Component* port_num) { 692 DoParseAuthority(spec, auth, username, password, hostname, port_num); 693 } 694 695 int ParsePort(const char* url, const Component& port) { 696 return DoParsePort(url, port); 697 } 698 699 int ParsePort(const char16* url, const Component& port) { 700 return DoParsePort(url, port); 701 } 702 703 void ParseStandardURL(const char* url, int url_len, Parsed* parsed) { 704 DoParseStandardURL(url, url_len, parsed); 705 } 706 707 void ParseStandardURL(const char16* url, int url_len, Parsed* parsed) { 708 DoParseStandardURL(url, url_len, parsed); 709 } 710 711 void ParsePathURL(const char* url, int url_len, Parsed* parsed) { 712 DoParsePathURL(url, url_len, parsed); 713 } 714 715 void ParsePathURL(const char16* url, int url_len, Parsed* parsed) { 716 DoParsePathURL(url, url_len, parsed); 717 } 718 719 void ParseMailtoURL(const char* url, int url_len, Parsed* parsed) { 720 DoParseMailtoURL(url, url_len, parsed); 721 } 722 723 void ParseMailtoURL(const char16* url, int url_len, Parsed* parsed) { 724 DoParseMailtoURL(url, url_len, parsed); 725 } 726 727 void ParsePathInternal(const char* spec, 728 const Component& path, 729 Component* filepath, 730 Component* query, 731 Component* ref) { 732 ParsePath(spec, path, filepath, query, ref); 733 } 734 735 void ParsePathInternal(const char16* spec, 736 const Component& path, 737 Component* filepath, 738 Component* query, 739 Component* ref) { 740 ParsePath(spec, path, filepath, query, ref); 741 } 742 743 void ParseAfterScheme(const char* spec, 744 int spec_len, 745 int after_scheme, 746 Parsed* parsed) { 747 DoParseAfterScheme(spec, spec_len, after_scheme, parsed); 748 } 749 750 void ParseAfterScheme(const char16* spec, 751 int spec_len, 752 int after_scheme, 753 Parsed* parsed) { 754 DoParseAfterScheme(spec, spec_len, after_scheme, parsed); 755 } 756 757 } // namespace url_parse 758