xref: /aosp_15_r20/external/cronet/url/third_party/mozilla/url_parse.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 "url/third_party/mozilla/url_parse.h"
38 
39 #include <stdlib.h>
40 
41 #include <ostream>
42 #include <string_view>
43 
44 #include "base/check_op.h"
45 #include "url/url_parse_internal.h"
46 #include "url/url_util.h"
47 #include "url/url_util_internal.h"
48 
49 namespace url {
50 
operator <<(std::ostream & os,const Parsed & parsed)51 std::ostream& operator<<(std::ostream& os, const Parsed& parsed) {
52   return os << "{ scheme: " << parsed.scheme
53             << ", username: " << parsed.username
54             << ", password: " << parsed.password << ", host: " << parsed.host
55             << ", port: " << parsed.port << ", path: " << parsed.path
56             << ", query: " << parsed.query << ", ref: " << parsed.ref
57             << ", has_opaque_path: " << parsed.has_opaque_path << " }";
58 }
59 
60 namespace {
61 
62 // Returns true if the given character is a valid digit to use in a port.
IsPortDigit(char16_t ch)63 inline bool IsPortDigit(char16_t ch) {
64   return ch >= '0' && ch <= '9';
65 }
66 
67 // Returns the offset of the next authority terminator in the input starting
68 // from start_offset. If no terminator is found, the return value will be equal
69 // to spec_len.
70 template <typename CHAR>
FindNextAuthorityTerminator(const CHAR * spec,int start_offset,int spec_len,ParserMode parser_mode)71 int FindNextAuthorityTerminator(const CHAR* spec,
72                                 int start_offset,
73                                 int spec_len,
74                                 ParserMode parser_mode) {
75   for (int i = start_offset; i < spec_len; i++) {
76     if (IsAuthorityTerminator(spec[i], parser_mode)) {
77       return i;
78     }
79   }
80   return spec_len;  // Not found.
81 }
82 
83 template <typename CHAR>
ParseUserInfo(const CHAR * spec,const Component & user,Component * username,Component * password)84 void ParseUserInfo(const CHAR* spec,
85                    const Component& user,
86                    Component* username,
87                    Component* password) {
88   // Find the first colon in the user section, which separates the username and
89   // password.
90   int colon_offset = 0;
91   while (colon_offset < user.len && spec[user.begin + colon_offset] != ':')
92     colon_offset++;
93 
94   if (colon_offset < user.len) {
95     // Found separator: <username>:<password>
96     *username = Component(user.begin, colon_offset);
97     *password = MakeRange(user.begin + colon_offset + 1, user.begin + user.len);
98   } else {
99     // No separator, treat everything as the username
100     *username = user;
101     *password = Component();
102   }
103 }
104 
105 template <typename CHAR>
ParseServerInfo(const CHAR * spec,const Component & serverinfo,Component * hostname,Component * port_num)106 void ParseServerInfo(const CHAR* spec,
107                      const Component& serverinfo,
108                      Component* hostname,
109                      Component* port_num) {
110   if (serverinfo.len == 0) {
111     // No server info, host name is empty.
112     hostname->reset();
113     port_num->reset();
114     return;
115   }
116 
117   // If the host starts with a left-bracket, assume the entire host is an
118   // IPv6 literal.  Otherwise, assume none of the host is an IPv6 literal.
119   // This assumption will be overridden if we find a right-bracket.
120   //
121   // Our IPv6 address canonicalization code requires both brackets to exist,
122   // but the ability to locate an incomplete address can still be useful.
123   int ipv6_terminator = spec[serverinfo.begin] == '[' ? serverinfo.end() : -1;
124   int colon = -1;
125 
126   // Find the last right-bracket, and the last colon.
127   for (int i = serverinfo.begin; i < serverinfo.end(); i++) {
128     switch (spec[i]) {
129       case ']':
130         ipv6_terminator = i;
131         break;
132       case ':':
133         colon = i;
134         break;
135     }
136   }
137 
138   if (colon > ipv6_terminator) {
139     // Found a port number: <hostname>:<port>
140     *hostname = MakeRange(serverinfo.begin, colon);
141     if (hostname->len == 0)
142       hostname->reset();
143     *port_num = MakeRange(colon + 1, serverinfo.end());
144   } else {
145     // No port: <hostname>
146     *hostname = serverinfo;
147     port_num->reset();
148   }
149 }
150 
151 // Given an already-identified auth section, breaks it into its consituent
152 // parts. The port number will be parsed and the resulting integer will be
153 // filled into the given *port variable, or -1 if there is no port number or it
154 // is invalid.
155 template <typename CHAR>
DoParseAuthority(const CHAR * spec,const Component & auth,ParserMode parser_mode,Component * username,Component * password,Component * hostname,Component * port_num)156 void DoParseAuthority(const CHAR* spec,
157                       const Component& auth,
158                       ParserMode parser_mode,
159                       Component* username,
160                       Component* password,
161                       Component* hostname,
162                       Component* port_num) {
163   DCHECK(auth.is_valid()) << "We should always get an authority";
164   if (auth.len == 0) {
165     username->reset();
166     password->reset();
167     if (parser_mode == ParserMode::kSpecialURL) {
168       hostname->reset();
169     } else {
170       // Non-special URLs can have an empty host. The difference between "host
171       // is empty" and "host does not exist" matters in the canonicalization
172       // phase.
173       //
174       // Examples:
175       // - "git:///" => host is empty (this case).
176       // - "git:/" => host does not exist.
177       *hostname = Component(auth.begin, 0);
178     }
179     port_num->reset();
180     return;
181   }
182 
183   // Search backwards for @, which is the separator between the user info and
184   // the server info.
185   int i = auth.begin + auth.len - 1;
186   while (i > auth.begin && spec[i] != '@')
187     i--;
188 
189   if (spec[i] == '@') {
190     // Found user info: <user-info>@<server-info>
191     ParseUserInfo(spec, Component(auth.begin, i - auth.begin), username,
192                   password);
193     ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), hostname,
194                     port_num);
195   } else {
196     // No user info, everything is server info.
197     username->reset();
198     password->reset();
199     ParseServerInfo(spec, auth, hostname, port_num);
200   }
201 }
202 
203 template <typename CHAR>
FindQueryAndRefParts(const CHAR * spec,const Component & path,int * query_separator,int * ref_separator)204 inline void FindQueryAndRefParts(const CHAR* spec,
205                                  const Component& path,
206                                  int* query_separator,
207                                  int* ref_separator) {
208   if constexpr (sizeof(*spec) == 1) {
209     // memchr is much faster than any scalar code we can write.
210     const CHAR* ptr = spec + path.begin;
211     const CHAR* first_hash =
212         reinterpret_cast<const CHAR*>(memchr(ptr, '#', path.len));
213     size_t len_before_fragment =
214         first_hash == nullptr ? path.len : first_hash - ptr;
215     const CHAR* first_question =
216         reinterpret_cast<const CHAR*>(memchr(ptr, '?', len_before_fragment));
217     if (first_hash != nullptr) {
218       *ref_separator = first_hash - spec;
219     }
220     if (first_question != nullptr) {
221       *query_separator = first_question - spec;
222     }
223   } else {
224     int path_end = path.begin + path.len;
225     for (int i = path.begin; i < path_end; i++) {
226       switch (spec[i]) {
227         case '?':
228           // Only match the query string if it precedes the reference fragment
229           // and when we haven't found one already.
230           if (*query_separator < 0)
231             *query_separator = i;
232           break;
233         case '#':
234           // Record the first # sign only.
235           if (*ref_separator < 0) {
236             *ref_separator = i;
237             return;
238           }
239           break;
240       }
241     }
242   }
243 }
244 
245 template <typename CHAR>
ParsePath(const CHAR * spec,const Component & path,Component * filepath,Component * query,Component * ref)246 void ParsePath(const CHAR* spec,
247                const Component& path,
248                Component* filepath,
249                Component* query,
250                Component* ref) {
251   // path = [/]<segment1>/<segment2>/<...>/<segmentN>;<param>?<query>#<ref>
252   DCHECK(path.is_valid());
253 
254   // Search for first occurrence of either ? or #.
255   int query_separator = -1;  // Index of the '?'
256   int ref_separator = -1;    // Index of the '#'
257   FindQueryAndRefParts(spec, path, &query_separator, &ref_separator);
258 
259   // Markers pointing to the character after each of these corresponding
260   // components. The code below words from the end back to the beginning,
261   // and will update these indices as it finds components that exist.
262   int file_end, query_end;
263 
264   // Ref fragment: from the # to the end of the path.
265   int path_end = path.begin + path.len;
266   if (ref_separator >= 0) {
267     file_end = query_end = ref_separator;
268     *ref = MakeRange(ref_separator + 1, path_end);
269   } else {
270     file_end = query_end = path_end;
271     ref->reset();
272   }
273 
274   // Query fragment: everything from the ? to the next boundary (either the end
275   // of the path or the ref fragment).
276   if (query_separator >= 0) {
277     file_end = query_separator;
278     *query = MakeRange(query_separator + 1, query_end);
279   } else {
280     query->reset();
281   }
282 
283   if (file_end != path.begin) {
284     *filepath = MakeRange(path.begin, file_end);
285   } else {
286     // File path: treat an empty file path as no file path.
287     //
288     // TODO(crbug.com/1416006): Consider to assign zero-length path component
289     // for non-special URLs because a path can be empty in non-special URLs.
290     // Currently, we don't have to distinguish between them. There is no visible
291     // difference.
292     filepath->reset();
293   }
294 }
295 
296 template <typename CharT>
DoExtractScheme(std::basic_string_view<CharT> url,Component * scheme)297 bool DoExtractScheme(std::basic_string_view<CharT> url, Component* scheme) {
298   // Skip leading whitespace and control characters.
299   size_t begin = 0;
300   while (begin < url.size() && ShouldTrimFromURL(url[begin])) {
301     begin++;
302   }
303   if (begin == url.size()) {
304     return false;  // Input is empty or all whitespace.
305   }
306 
307   // Find the first colon character.
308   for (size_t i = begin; i < url.size(); i++) {
309     if (url[i] == ':') {
310       *scheme = MakeRange(begin, base::checked_cast<int>(i));
311       return true;
312     }
313   }
314   return false;  // No colon found: no scheme
315 }
316 
317 // Fills in all members of the Parsed structure except for the scheme.
318 //
319 // |spec| is the full spec being parsed, of length |spec_len|.
320 // |after_scheme| is the character immediately following the scheme (after the
321 //   colon) where we'll begin parsing.
322 //
323 // Compatability data points. I list "host", "path" extracted:
324 // Input                IE6             Firefox                Us
325 // -----                --------------  --------------         --------------
326 // http://foo.com/      "foo.com", "/"  "foo.com", "/"         "foo.com", "/"
327 // http:foo.com/        "foo.com", "/"  "foo.com", "/"         "foo.com", "/"
328 // http:/foo.com/       fail(*)         "foo.com", "/"         "foo.com", "/"
329 // http:\foo.com/       fail(*)         "\foo.com", "/"(fail)  "foo.com", "/"
330 // http:////foo.com/    "foo.com", "/"  "foo.com", "/"         "foo.com", "/"
331 //
332 // (*) Interestingly, although IE fails to load these URLs, its history
333 // canonicalizer handles them, meaning if you've been to the corresponding
334 // "http://foo.com/" link, it will be colored.
335 template <typename CHAR>
DoParseAfterSpecialScheme(const CHAR * spec,int spec_len,int after_scheme,Parsed * parsed)336 void DoParseAfterSpecialScheme(const CHAR* spec,
337                                int spec_len,
338                                int after_scheme,
339                                Parsed* parsed) {
340   int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len);
341   int after_slashes = after_scheme + num_slashes;
342 
343   // First split into two main parts, the authority (username, password, host,
344   // and port) and the full path (path, query, and reference).
345   //
346   // Treat everything from `after_slashes` to the next slash (or end of spec) to
347   // be the authority. Note that we ignore the number of slashes and treat it as
348   // the authority.
349   int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len,
350                                              ParserMode::kSpecialURL);
351 
352   Component authority(after_slashes, end_auth - after_slashes);
353   // Everything starting from the slash to the end is the path.
354   Component full_path(end_auth, spec_len - end_auth);
355 
356   // Now parse those two sub-parts.
357   DoParseAuthority(spec, authority, ParserMode::kSpecialURL, &parsed->username,
358                    &parsed->password, &parsed->host, &parsed->port);
359   ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
360 }
361 
362 // The main parsing function for standard URLs. Standard URLs have a scheme,
363 // host, path, etc.
364 template <typename CHAR>
DoParseStandardURL(const CHAR * spec,int spec_len,Parsed * parsed)365 void DoParseStandardURL(const CHAR* spec, int spec_len, Parsed* parsed) {
366   DCHECK(spec_len >= 0);
367   parsed->has_opaque_path = false;
368 
369   // Strip leading & trailing spaces and control characters.
370   int begin = 0;
371   TrimURL(spec, &begin, &spec_len);
372 
373   int after_scheme;
374   if (DoExtractScheme(std::basic_string_view(spec, spec_len),
375                       &parsed->scheme)) {
376     after_scheme = parsed->scheme.end() + 1;  // Skip past the colon.
377   } else {
378     // Say there's no scheme when there is no colon. We could also say that
379     // everything is the scheme. Both would produce an invalid URL, but this way
380     // seems less wrong in more cases.
381     parsed->scheme.reset();
382     after_scheme = begin;
383   }
384   DoParseAfterSpecialScheme(spec, spec_len, after_scheme, parsed);
385 }
386 
387 template <typename CHAR>
DoParseAfterNonSpecialScheme(const CHAR * spec,int spec_len,int after_scheme,Parsed * parsed)388 void DoParseAfterNonSpecialScheme(const CHAR* spec,
389                                   int spec_len,
390                                   int after_scheme,
391                                   Parsed* parsed) {
392   // The implementation is similar to `DoParseAfterSpecialScheme()`, but there
393   // are many subtle differences. So we have a different function for parsing
394   // non-special URLs.
395 
396   int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len);
397 
398   if (num_slashes >= 2) {
399     // Found "//<some data>", looks like an authority section.
400     //
401     // e.g.
402     //   "git://host:8000/path"
403     //          ^
404     //
405     // The state machine transition in the URL Standard is:
406     //
407     // https://url.spec.whatwg.org/#scheme-state
408     // => https://url.spec.whatwg.org/#path-or-authority-state
409     // => https://url.spec.whatwg.org/#authority-state
410     //
411     parsed->has_opaque_path = false;
412 
413     int after_slashes = after_scheme + 2;
414 
415     // First split into two main parts, the authority (username, password, host,
416     // and port) and the full path (path, query, and reference).
417     //
418     // Treat everything from there to the next slash (or end of spec) to be the
419     // authority. Note that we ignore the number of slashes and treat it as the
420     // authority.
421     int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len,
422                                                ParserMode::kNonSpecialURL);
423     Component authority(after_slashes, end_auth - after_slashes);
424 
425     // Now parse those two sub-parts.
426     DoParseAuthority(spec, authority, ParserMode::kNonSpecialURL,
427                      &parsed->username, &parsed->password, &parsed->host,
428                      &parsed->port);
429 
430     // Everything starting from the slash to the end is the path.
431     Component full_path(end_auth, spec_len - end_auth);
432     ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
433     return;
434   }
435 
436   if (num_slashes == 1) {
437     // Examples:
438     //   "git:/path"
439     //        ^
440     //
441     // The state machine transition in the URL Standard is:
442     //
443     // https://url.spec.whatwg.org/#scheme-state
444     // => https://url.spec.whatwg.org/#path-or-authority-state
445     // => https://url.spec.whatwg.org/#path-state
446     parsed->has_opaque_path = false;
447   } else {
448     // We didn't found "//" nor "/", so entering into an opaque-path-state.
449     //
450     // Examples:
451     //   "git:opaque path"
452     //        ^
453     //
454     // The state machine transition in the URL Standard is:
455     //
456     // https://url.spec.whatwg.org/#scheme-state
457     // => https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
458     parsed->has_opaque_path = true;
459   }
460 
461   parsed->username.reset();
462   parsed->password.reset();
463   // It's important to reset `parsed->host` here to distinguish between "host
464   // is empty" and "host doesn't exist".
465   parsed->host.reset();
466   parsed->port.reset();
467 
468   // Everything starting after scheme to the end is the path.
469   Component full_path(after_scheme, spec_len - after_scheme);
470   ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
471 }
472 
473 // The main parsing function for non-special scheme URLs.
474 template <typename CHAR>
DoParseNonSpecialURL(const CHAR * spec,int spec_len,bool trim_path_end,Parsed * parsed)475 void DoParseNonSpecialURL(const CHAR* spec,
476                           int spec_len,
477                           bool trim_path_end,
478                           Parsed* parsed) {
479   DCHECK(spec_len >= 0);
480 
481   // Strip leading & trailing spaces and control characters.
482   int begin = 0;
483   TrimURL(spec, &begin, &spec_len, trim_path_end);
484 
485   int after_scheme;
486   if (DoExtractScheme(std::basic_string_view(spec, spec_len),
487                       &parsed->scheme)) {
488     after_scheme = parsed->scheme.end() + 1;  // Skip past the colon.
489   } else {
490     // Say there's no scheme when there is no colon. We could also say that
491     // everything is the scheme. Both would produce an invalid URL, but this way
492     // seems less wrong in more cases.
493     parsed->scheme.reset();
494     after_scheme = 0;
495   }
496   DoParseAfterNonSpecialScheme(spec, spec_len, after_scheme, parsed);
497 }
498 
499 template <typename CharT>
DoParseFileSystemURL(std::basic_string_view<CharT> url)500 Parsed DoParseFileSystemURL(std::basic_string_view<CharT> url) {
501   // Strip leading & trailing spaces and control characters.
502   int begin = 0;
503   int url_len = base::checked_cast<int>(url.size());
504   TrimURL(url.data(), &begin, &url_len);
505 
506   // Handle empty specs or ones that contain only whitespace or control chars.
507   if (begin == url_len) {
508     return {};
509   }
510 
511   int inner_start = -1;
512   // Extract the scheme.  We also handle the case where there is no scheme.
513   Parsed parsed;
514   if (DoExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
515     // Offset the results since we gave ExtractScheme a substring.
516     parsed.scheme.begin += begin;
517 
518     if (parsed.scheme.end() == url_len - 1) {
519       return {};
520     }
521 
522     inner_start = parsed.scheme.end() + 1;
523   } else {
524     // No scheme found; that's not valid for filesystem URLs.
525     return {};
526   }
527 
528   Component inner_scheme;
529   std::basic_string_view inner_url =
530       url.substr(inner_start, url_len - inner_start);
531   if (DoExtractScheme(inner_url, &inner_scheme)) {
532     // Offset the results since we gave ExtractScheme a substring.
533     inner_scheme.begin += inner_start;
534 
535     if (inner_scheme.end() == url_len - 1) {
536       return parsed;
537     }
538   } else {
539     // No scheme found; that's not valid for filesystem URLs.
540     // The best we can do is return "filesystem://".
541     return parsed;
542   }
543 
544   Parsed inner_parsed;
545 
546   if (CompareSchemeComponent(url.data(), inner_scheme, kFileScheme)) {
547     // File URLs are special. The static cast is safe because we calculated the
548     // size above as the difference of two ints.
549     ParseFileURL(inner_url.data(), static_cast<int>(inner_url.size()),
550                  &inner_parsed);
551   } else if (CompareSchemeComponent(url.data(), inner_scheme,
552                                     kFileSystemScheme)) {
553     // Filesystem URLs don't nest.
554     return parsed;
555   } else if (IsStandard(url.data(), inner_scheme)) {
556     // All "normal" URLs.
557     DoParseStandardURL(inner_url.data(), static_cast<int>(inner_url.size()),
558                        &inner_parsed);
559   } else {
560     return parsed;
561   }
562 
563   // All members of inner_parsed need to be offset by inner_start.
564   // If we had any scheme that supported nesting more than one level deep,
565   // we'd have to recurse into the inner_parsed's inner_parsed when
566   // adjusting by inner_start.
567   inner_parsed.scheme.begin += inner_start;
568   inner_parsed.username.begin += inner_start;
569   inner_parsed.password.begin += inner_start;
570   inner_parsed.host.begin += inner_start;
571   inner_parsed.port.begin += inner_start;
572   inner_parsed.query.begin += inner_start;
573   inner_parsed.ref.begin += inner_start;
574   inner_parsed.path.begin += inner_start;
575 
576   // Query and ref move from inner_parsed to parsed.
577   parsed.query = inner_parsed.query;
578   inner_parsed.query.reset();
579   parsed.ref = inner_parsed.ref;
580   inner_parsed.ref.reset();
581 
582   parsed.set_inner_parsed(inner_parsed);
583   if (!inner_parsed.scheme.is_valid() || !inner_parsed.path.is_valid() ||
584       inner_parsed.inner_parsed()) {
585     return parsed;
586   }
587 
588   // The path in inner_parsed should start with a slash, then have a filesystem
589   // type followed by a slash.  From the first slash up to but excluding the
590   // second should be what it keeps; the rest goes to parsed.  If the path ends
591   // before the second slash, it's still pretty clear what the user meant, so
592   // we'll let that through.
593   if (!IsSlashOrBackslash(url[inner_parsed.path.begin])) {
594     return parsed;
595   }
596   int inner_path_end = inner_parsed.path.begin + 1;  // skip the leading slash
597   while (inner_path_end < url_len && !IsSlashOrBackslash(url[inner_path_end])) {
598     ++inner_path_end;
599   }
600   parsed.path.begin = inner_path_end;
601   int new_inner_path_length = inner_path_end - inner_parsed.path.begin;
602   parsed.path.len = inner_parsed.path.len - new_inner_path_length;
603   parsed.inner_parsed()->path.len = new_inner_path_length;
604   return parsed;
605 }
606 
607 // Initializes a path URL which is merely a scheme followed by a path. Examples
608 // include "about:foo" and "javascript:alert('bar');"
609 template <typename CHAR>
DoParsePathURL(const CHAR * spec,int spec_len,bool trim_path_end,Parsed * parsed)610 void DoParsePathURL(const CHAR* spec,
611                     int spec_len,
612                     bool trim_path_end,
613                     Parsed* parsed) {
614   // Get the non-path and non-scheme parts of the URL out of the way, we never
615   // use them.
616   parsed->username.reset();
617   parsed->password.reset();
618   parsed->host.reset();
619   parsed->port.reset();
620   parsed->path.reset();
621   parsed->query.reset();
622   parsed->ref.reset();
623   // In practice, we don't need to set `has_opaque_path` here because:
624   //
625   // 1. `has_opaque_path` will be used only when the
626   //     `kStandardCompliantNonSpecialSchemeURLParsing` feature is enabled.
627   // 2. `DoParsePathURL` will not be used when the flag is enabled (planned).
628   //
629   // However, for predictable results, it is better to explicitly set it
630   // `false`.
631   parsed->has_opaque_path = false;
632 
633   // Strip leading & trailing spaces and control characters.
634   int scheme_begin = 0;
635   TrimURL(spec, &scheme_begin, &spec_len, trim_path_end);
636 
637   // Handle empty specs or ones that contain only whitespace or control chars.
638   if (scheme_begin == spec_len) {
639     parsed->scheme.reset();
640     parsed->path.reset();
641     return;
642   }
643 
644   int path_begin;
645   // Extract the scheme, with the path being everything following. We also
646   // handle the case where there is no scheme.
647   if (ExtractScheme(&spec[scheme_begin], spec_len - scheme_begin,
648                     &parsed->scheme)) {
649     // Offset the results since we gave ExtractScheme a substring.
650     parsed->scheme.begin += scheme_begin;
651     path_begin = parsed->scheme.end() + 1;
652   } else {
653     // No scheme case.
654     parsed->scheme.reset();
655     path_begin = scheme_begin;
656   }
657 
658   if (path_begin == spec_len)
659     return;
660   DCHECK_LT(path_begin, spec_len);
661 
662   ParsePath(spec, MakeRange(path_begin, spec_len), &parsed->path,
663             &parsed->query, &parsed->ref);
664 }
665 
666 template <typename CharT>
DoParseMailtoURL(std::basic_string_view<CharT> url)667 Parsed DoParseMailtoURL(std::basic_string_view<CharT> url) {
668   // Strip leading & trailing spaces and control characters.
669   int begin = 0;
670   // TODO(crbug.com/325408566): Transition to size_t and avoid the checked_cast
671   // once Component's members are no longer integers.
672   int url_len = base::checked_cast<int>(url.size());
673   TrimURL(url.data(), &begin, &url_len);
674 
675   // Handle empty specs or ones that contain only whitespace or control chars.
676   if (begin == url_len) {
677     return {};
678   }
679 
680   int path_begin = -1;
681   int path_end = -1;
682 
683   // Extract the scheme, with the path being everything following. We also
684   // handle the case where there is no scheme.
685   Parsed parsed;
686   if (ExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
687     // Offset the results since we gave ExtractScheme a substring.
688     parsed.scheme.begin += begin;
689 
690     if (parsed.scheme.end() != url_len - 1) {
691       path_begin = parsed.scheme.end() + 1;
692       path_end = url_len;
693     }
694   } else {
695     // No scheme found, just path.
696     parsed.scheme.reset();
697     path_begin = begin;
698     path_end = url_len;
699   }
700 
701   // Split [path_begin, path_end) into a path + query.
702   for (int i = path_begin; i < path_end; ++i) {
703     if (url[i] == '?') {
704       parsed.query = MakeRange(i + 1, path_end);
705       path_end = i;
706       break;
707     }
708   }
709 
710   // For compatability with the standard URL parser, treat no path as
711   // -1, rather than having a length of 0
712   if (path_begin == path_end) {
713     parsed.path.reset();
714   } else {
715     parsed.path = MakeRange(path_begin, path_end);
716   }
717   return parsed;
718 }
719 
720 // Converts a port number in a string to an integer. We'd like to just call
721 // sscanf but our input is not NULL-terminated, which sscanf requires. Instead,
722 // we copy the digits to a small stack buffer (since we know the maximum number
723 // of digits in a valid port number) that we can NULL terminate.
724 template <typename CHAR>
DoParsePort(const CHAR * spec,const Component & component)725 int DoParsePort(const CHAR* spec, const Component& component) {
726   // Easy success case when there is no port.
727   const int kMaxDigits = 5;
728   if (component.is_empty())
729     return PORT_UNSPECIFIED;
730 
731   // Skip over any leading 0s.
732   Component digits_comp(component.end(), 0);
733   for (int i = 0; i < component.len; i++) {
734     if (spec[component.begin + i] != '0') {
735       digits_comp = MakeRange(component.begin + i, component.end());
736       break;
737     }
738   }
739   if (digits_comp.len == 0)
740     return 0;  // All digits were 0.
741 
742   // Verify we don't have too many digits (we'll be copying to our buffer so
743   // we need to double-check).
744   if (digits_comp.len > kMaxDigits)
745     return PORT_INVALID;
746 
747   // Copy valid digits to the buffer.
748   char digits[kMaxDigits + 1];  // +1 for null terminator
749   for (int i = 0; i < digits_comp.len; i++) {
750     CHAR ch = spec[digits_comp.begin + i];
751     if (!IsPortDigit(ch)) {
752       // Invalid port digit, fail.
753       return PORT_INVALID;
754     }
755     digits[i] = static_cast<char>(ch);
756   }
757 
758   // Null-terminate the string and convert to integer. Since we guarantee
759   // only digits, atoi's lack of error handling is OK.
760   digits[digits_comp.len] = 0;
761   int port = atoi(digits);
762   if (port > 65535)
763     return PORT_INVALID;  // Out of range.
764   return port;
765 }
766 
767 template <typename CHAR>
DoExtractFileName(const CHAR * spec,const Component & path,Component * file_name)768 void DoExtractFileName(const CHAR* spec,
769                        const Component& path,
770                        Component* file_name) {
771   // Handle empty paths: they have no file names.
772   if (path.is_empty()) {
773     file_name->reset();
774     return;
775   }
776 
777   // Extract the filename range from the path which is between
778   // the last slash and the following semicolon.
779   int file_end = path.end();
780   for (int i = path.end() - 1; i >= path.begin; i--) {
781     if (spec[i] == ';') {
782       file_end = i;
783     } else if (IsSlashOrBackslash(spec[i])) {
784       // File name is everything following this character to the end
785       *file_name = MakeRange(i + 1, file_end);
786       return;
787     }
788   }
789 
790   // No slash found, this means the input was degenerate (generally paths
791   // will start with a slash). Let's call everything the file name.
792   *file_name = MakeRange(path.begin, file_end);
793   return;
794 }
795 
796 template <typename CharT>
DoExtractQueryKeyValue(std::basic_string_view<CharT> spec,Component * query,Component * key,Component * value)797 bool DoExtractQueryKeyValue(std::basic_string_view<CharT> spec,
798                             Component* query,
799                             Component* key,
800                             Component* value) {
801   if (!query->is_nonempty())
802     return false;
803 
804   int start = query->begin;
805   int cur = start;
806   int end = query->end();
807 
808   // We assume the beginning of the input is the beginning of the "key" and we
809   // skip to the end of it.
810   key->begin = cur;
811   while (cur < end && spec[cur] != '&' && spec[cur] != '=')
812     cur++;
813   key->len = cur - key->begin;
814 
815   // Skip the separator after the key (if any).
816   if (cur < end && spec[cur] == '=')
817     cur++;
818 
819   // Find the value part.
820   value->begin = cur;
821   while (cur < end && spec[cur] != '&')
822     cur++;
823   value->len = cur - value->begin;
824 
825   // Finally skip the next separator if any
826   if (cur < end && spec[cur] == '&')
827     cur++;
828 
829   // Save the new query
830   *query = MakeRange(cur, end);
831   return true;
832 }
833 
834 }  // namespace
835 
836 COMPONENT_EXPORT(URL)
837 std::ostream& operator<<(std::ostream& os, const Component& component) {
838   return os << '{' << component.begin << ", " << component.len << "}";
839 }
840 
841 Parsed::Parsed() = default;
842 
Parsed(const Parsed & other)843 Parsed::Parsed(const Parsed& other)
844     : scheme(other.scheme),
845       username(other.username),
846       password(other.password),
847       host(other.host),
848       port(other.port),
849       path(other.path),
850       query(other.query),
851       ref(other.ref),
852       potentially_dangling_markup(other.potentially_dangling_markup),
853       has_opaque_path(other.has_opaque_path) {
854   if (other.inner_parsed_)
855     set_inner_parsed(*other.inner_parsed_);
856 }
857 
operator =(const Parsed & other)858 Parsed& Parsed::operator=(const Parsed& other) {
859   if (this != &other) {
860     scheme = other.scheme;
861     username = other.username;
862     password = other.password;
863     host = other.host;
864     port = other.port;
865     path = other.path;
866     query = other.query;
867     ref = other.ref;
868     potentially_dangling_markup = other.potentially_dangling_markup;
869     has_opaque_path = other.has_opaque_path;
870     if (other.inner_parsed_)
871       set_inner_parsed(*other.inner_parsed_);
872     else
873       clear_inner_parsed();
874   }
875   return *this;
876 }
877 
~Parsed()878 Parsed::~Parsed() {
879   delete inner_parsed_;
880 }
881 
Length() const882 int Parsed::Length() const {
883   if (ref.is_valid())
884     return ref.end();
885   return CountCharactersBefore(REF, false);
886 }
887 
CountCharactersBefore(ComponentType type,bool include_delimiter) const888 int Parsed::CountCharactersBefore(ComponentType type,
889                                   bool include_delimiter) const {
890   if (type == SCHEME)
891     return scheme.begin;
892 
893   // There will be some characters after the scheme like "://" and we don't
894   // know how many. Search forwards for the next thing until we find one.
895   int cur = 0;
896   if (scheme.is_valid())
897     cur = scheme.end() + 1;  // Advance over the ':' at the end of the scheme.
898 
899   if (username.is_valid()) {
900     if (type <= USERNAME)
901       return username.begin;
902     cur = username.end() + 1;  // Advance over the '@' or ':' at the end.
903   }
904 
905   if (password.is_valid()) {
906     if (type <= PASSWORD)
907       return password.begin;
908     cur = password.end() + 1;  // Advance over the '@' at the end.
909   }
910 
911   if (host.is_valid()) {
912     if (type <= HOST)
913       return host.begin;
914     cur = host.end();
915   }
916 
917   if (port.is_valid()) {
918     if (type < PORT || (type == PORT && include_delimiter))
919       return port.begin - 1;  // Back over delimiter.
920     if (type == PORT)
921       return port.begin;  // Don't want delimiter counted.
922     cur = port.end();
923   }
924 
925   if (path.is_valid()) {
926     if (type <= PATH)
927       return path.begin;
928     cur = path.end();
929   }
930 
931   if (query.is_valid()) {
932     if (type < QUERY || (type == QUERY && include_delimiter))
933       return query.begin - 1;  // Back over delimiter.
934     if (type == QUERY)
935       return query.begin;  // Don't want delimiter counted.
936     cur = query.end();
937   }
938 
939   if (ref.is_valid()) {
940     if (type == REF && !include_delimiter)
941       return ref.begin;  // Back over delimiter.
942 
943     // When there is a ref and we get here, the component we wanted was before
944     // this and not found, so we always know the beginning of the ref is right.
945     return ref.begin - 1;  // Don't want delimiter counted.
946   }
947 
948   return cur;
949 }
950 
GetContent() const951 Component Parsed::GetContent() const {
952   const int begin = CountCharactersBefore(USERNAME, false);
953   const int len = Length() - begin;
954   // For compatability with the standard URL parser, we treat no content as
955   // -1, rather than having a length of 0 (we normally wouldn't care so
956   // much for these non-standard URLs).
957   return len ? Component(begin, len) : Component();
958 }
959 
ExtractScheme(std::string_view url,Component * scheme)960 bool ExtractScheme(std::string_view url, Component* scheme) {
961   return DoExtractScheme(url, scheme);
962 }
963 
ExtractScheme(std::u16string_view url,Component * scheme)964 bool ExtractScheme(std::u16string_view url, Component* scheme) {
965   return DoExtractScheme(url, scheme);
966 }
967 
ExtractScheme(const char * url,int url_len,Component * scheme)968 bool ExtractScheme(const char* url, int url_len, Component* scheme) {
969   return DoExtractScheme(std::string_view(url, url_len), scheme);
970 }
971 
ExtractScheme(const char16_t * url,int url_len,Component * scheme)972 bool ExtractScheme(const char16_t* url, int url_len, Component* scheme) {
973   return DoExtractScheme(std::u16string_view(url, url_len), scheme);
974 }
975 
976 // This handles everything that may be an authority terminator.
977 //
978 // URL Standard:
979 // https://url.spec.whatwg.org/#authority-state
980 // >> 2. Otherwise, if one of the following is true:
981 // >>    - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
982 // >>    - url is special and c is U+005C (\)
IsAuthorityTerminator(char16_t ch,ParserMode parser_mode)983 bool IsAuthorityTerminator(char16_t ch, ParserMode parser_mode) {
984   if (parser_mode == ParserMode::kSpecialURL) {
985     return IsSlashOrBackslash(ch) || ch == '?' || ch == '#';
986   }
987   return ch == '/' || ch == '?' || ch == '#';
988 }
989 
ExtractFileName(const char * url,const Component & path,Component * file_name)990 void ExtractFileName(const char* url,
991                      const Component& path,
992                      Component* file_name) {
993   DoExtractFileName(url, path, file_name);
994 }
995 
ExtractFileName(const char16_t * url,const Component & path,Component * file_name)996 void ExtractFileName(const char16_t* url,
997                      const Component& path,
998                      Component* file_name) {
999   DoExtractFileName(url, path, file_name);
1000 }
1001 
ExtractQueryKeyValue(std::string_view url,Component * query,Component * key,Component * value)1002 bool ExtractQueryKeyValue(std::string_view url,
1003                           Component* query,
1004                           Component* key,
1005                           Component* value) {
1006   return DoExtractQueryKeyValue(url, query, key, value);
1007 }
1008 
ExtractQueryKeyValue(std::u16string_view url,Component * query,Component * key,Component * value)1009 bool ExtractQueryKeyValue(std::u16string_view url,
1010                           Component* query,
1011                           Component* key,
1012                           Component* value) {
1013   return DoExtractQueryKeyValue(url, query, key, value);
1014 }
1015 
ParseAuthority(const char * spec,const Component & auth,Component * username,Component * password,Component * hostname,Component * port_num)1016 void ParseAuthority(const char* spec,
1017                     const Component& auth,
1018                     Component* username,
1019                     Component* password,
1020                     Component* hostname,
1021                     Component* port_num) {
1022   DoParseAuthority(spec, auth, ParserMode::kSpecialURL, username, password,
1023                    hostname, port_num);
1024 }
1025 
ParseAuthority(const char16_t * spec,const Component & auth,Component * username,Component * password,Component * hostname,Component * port_num)1026 void ParseAuthority(const char16_t* spec,
1027                     const Component& auth,
1028                     Component* username,
1029                     Component* password,
1030                     Component* hostname,
1031                     Component* port_num) {
1032   DoParseAuthority(spec, auth, ParserMode::kSpecialURL, username, password,
1033                    hostname, port_num);
1034 }
1035 
ParseAuthority(const char * spec,const Component & auth,ParserMode parser_mode,Component * username,Component * password,Component * hostname,Component * port_num)1036 void ParseAuthority(const char* spec,
1037                     const Component& auth,
1038                     ParserMode parser_mode,
1039                     Component* username,
1040                     Component* password,
1041                     Component* hostname,
1042                     Component* port_num) {
1043   DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
1044                    port_num);
1045 }
1046 
ParseAuthority(const char16_t * spec,const Component & auth,ParserMode parser_mode,Component * username,Component * password,Component * hostname,Component * port_num)1047 void ParseAuthority(const char16_t* spec,
1048                     const Component& auth,
1049                     ParserMode parser_mode,
1050                     Component* username,
1051                     Component* password,
1052                     Component* hostname,
1053                     Component* port_num) {
1054   DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
1055                    port_num);
1056 }
1057 
ParsePort(const char * url,const Component & port)1058 int ParsePort(const char* url, const Component& port) {
1059   return DoParsePort(url, port);
1060 }
1061 
ParsePort(const char16_t * url,const Component & port)1062 int ParsePort(const char16_t* url, const Component& port) {
1063   return DoParsePort(url, port);
1064 }
1065 
ParseStandardURL(const char * url,int url_len,Parsed * parsed)1066 void ParseStandardURL(const char* url, int url_len, Parsed* parsed) {
1067   DoParseStandardURL(url, url_len, parsed);
1068 }
1069 
ParseStandardURL(const char16_t * url,int url_len,Parsed * parsed)1070 void ParseStandardURL(const char16_t* url, int url_len, Parsed* parsed) {
1071   DoParseStandardURL(url, url_len, parsed);
1072 }
1073 
ParseNonSpecialURL(const char * url,int url_len,Parsed * parsed)1074 void ParseNonSpecialURL(const char* url, int url_len, Parsed* parsed) {
1075   DoParseNonSpecialURL(url, url_len, /*trim_path_end=*/true, parsed);
1076 }
1077 
ParseNonSpecialURL(const char16_t * url,int url_len,Parsed * parsed)1078 void ParseNonSpecialURL(const char16_t* url, int url_len, Parsed* parsed) {
1079   DoParseNonSpecialURL(url, url_len, /*trim_path_end=*/true, parsed);
1080 }
1081 
ParseNonSpecialURLInternal(const char * url,int url_len,bool trim_path_end,Parsed * parsed)1082 void ParseNonSpecialURLInternal(const char* url,
1083                                 int url_len,
1084                                 bool trim_path_end,
1085                                 Parsed* parsed) {
1086   DoParseNonSpecialURL(url, url_len, trim_path_end, parsed);
1087 }
1088 
ParseNonSpecialURLInternal(const char16_t * url,int url_len,bool trim_path_end,Parsed * parsed)1089 void ParseNonSpecialURLInternal(const char16_t* url,
1090                                 int url_len,
1091                                 bool trim_path_end,
1092                                 Parsed* parsed) {
1093   DoParseNonSpecialURL(url, url_len, trim_path_end, parsed);
1094 }
1095 
ParsePathURL(const char * url,int url_len,bool trim_path_end,Parsed * parsed)1096 void ParsePathURL(const char* url,
1097                   int url_len,
1098                   bool trim_path_end,
1099                   Parsed* parsed) {
1100   DoParsePathURL(url, url_len, trim_path_end, parsed);
1101 }
1102 
ParsePathURL(const char16_t * url,int url_len,bool trim_path_end,Parsed * parsed)1103 void ParsePathURL(const char16_t* url,
1104                   int url_len,
1105                   bool trim_path_end,
1106                   Parsed* parsed) {
1107   DoParsePathURL(url, url_len, trim_path_end, parsed);
1108 }
1109 
ParseFileSystemURL(std::string_view url)1110 Parsed ParseFileSystemURL(std::string_view url) {
1111   return DoParseFileSystemURL(url);
1112 }
1113 
ParseFileSystemURL(std::u16string_view url)1114 Parsed ParseFileSystemURL(std::u16string_view url) {
1115   return DoParseFileSystemURL(url);
1116 }
1117 
ParseMailtoURL(std::string_view url)1118 Parsed ParseMailtoURL(std::string_view url) {
1119   return DoParseMailtoURL(url);
1120 }
1121 
ParseMailtoURL(std::u16string_view url)1122 Parsed ParseMailtoURL(std::u16string_view url) {
1123   return DoParseMailtoURL(url);
1124 }
1125 
ParsePathInternal(const char * spec,const Component & path,Component * filepath,Component * query,Component * ref)1126 void ParsePathInternal(const char* spec,
1127                        const Component& path,
1128                        Component* filepath,
1129                        Component* query,
1130                        Component* ref) {
1131   ParsePath(spec, path, filepath, query, ref);
1132 }
1133 
ParsePathInternal(const char16_t * spec,const Component & path,Component * filepath,Component * query,Component * ref)1134 void ParsePathInternal(const char16_t* spec,
1135                        const Component& path,
1136                        Component* filepath,
1137                        Component* query,
1138                        Component* ref) {
1139   ParsePath(spec, path, filepath, query, ref);
1140 }
1141 
ParseAfterSpecialScheme(const char * spec,int spec_len,int after_scheme,Parsed * parsed)1142 void ParseAfterSpecialScheme(const char* spec,
1143                              int spec_len,
1144                              int after_scheme,
1145                              Parsed* parsed) {
1146   DoParseAfterSpecialScheme(spec, spec_len, after_scheme, parsed);
1147 }
1148 
ParseAfterSpecialScheme(const char16_t * spec,int spec_len,int after_scheme,Parsed * parsed)1149 void ParseAfterSpecialScheme(const char16_t* spec,
1150                              int spec_len,
1151                              int after_scheme,
1152                              Parsed* parsed) {
1153   DoParseAfterSpecialScheme(spec, spec_len, after_scheme, parsed);
1154 }
1155 
ParseAfterNonSpecialScheme(const char * spec,int spec_len,int after_scheme,Parsed * parsed)1156 void ParseAfterNonSpecialScheme(const char* spec,
1157                                 int spec_len,
1158                                 int after_scheme,
1159                                 Parsed* parsed) {
1160   DoParseAfterNonSpecialScheme(spec, spec_len, after_scheme, parsed);
1161 }
1162 
ParseAfterNonSpecialScheme(const char16_t * spec,int spec_len,int after_scheme,Parsed * parsed)1163 void ParseAfterNonSpecialScheme(const char16_t* spec,
1164                                 int spec_len,
1165                                 int after_scheme,
1166                                 Parsed* parsed) {
1167   DoParseAfterNonSpecialScheme(spec, spec_len, after_scheme, parsed);
1168 }
1169 
1170 }  // namespace url
1171