1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// found in the LICENSE file.
4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Functions to canonicalize "standard" URLs, which are ones that have an
6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// authority section including a host name.
7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "url/url_canon.h"
9c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "url/url_canon_internal.h"
10f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "url/url_constants.h"
11c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochnamespace url {
13c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)namespace {
15c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
16c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)template<typename CHAR, typename UCHAR>
17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool DoCanonicalizeStandardURL(const URLComponentSource<CHAR>& source,
180529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                               const Parsed& parsed,
19c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                               CharsetConverter* query_converter,
20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                               CanonOutput* output,
210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                               Parsed* new_parsed) {
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Scheme: this will append the colon.
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  bool success = CanonicalizeScheme(source.scheme, parsed.scheme,
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    output, &new_parsed->scheme);
25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Authority (username, password, host, port)
27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  bool have_authority;
28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (parsed.username.is_valid() || parsed.password.is_valid() ||
29c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      parsed.host.is_nonempty() || parsed.port.is_valid()) {
30c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    have_authority = true;
31c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
32c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // Only write the authority separators when we have a scheme.
33c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if (parsed.scheme.is_valid()) {
34c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      output->push_back('/');
35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      output->push_back('/');
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    }
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // User info: the canonicalizer will handle the : and @.
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    success &= CanonicalizeUserInfo(source.username, parsed.username,
40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    source.password, parsed.password,
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    output,
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    &new_parsed->username,
43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                    &new_parsed->password);
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    success &= CanonicalizeHost(source.host, parsed.host,
46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                output, &new_parsed->host);
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
48c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // Host must not be empty for standard URLs.
49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if (!parsed.host.is_nonempty())
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      success = false;
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // Port: the port canonicalizer will handle the colon.
53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    int default_port = DefaultPortForScheme(
54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        &output->data()[new_parsed->scheme.begin], new_parsed->scheme.len);
55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    success &= CanonicalizePort(source.port, parsed.port, default_port,
56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                output, &new_parsed->port);
57c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  } else {
58c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // No authority, clear the components.
59c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    have_authority = false;
60c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    new_parsed->host.reset();
61c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    new_parsed->username.reset();
62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    new_parsed->password.reset();
63c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    new_parsed->port.reset();
64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    success = false;  // Standard URLs must have an authority.
65c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
66c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
67c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Path
68c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (parsed.path.is_valid()) {
69c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    success &= CanonicalizePath(source.path, parsed.path,
70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                output, &new_parsed->path);
71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  } else if (have_authority ||
72c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)             parsed.query.is_valid() || parsed.ref.is_valid()) {
73c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // When we have an empty path, make up a path when we have an authority
74c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // or something following the path. The only time we allow an empty
75c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // output path is when there is nothing else.
760529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    new_parsed->path = Component(output->length(), 1);
77c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    output->push_back('/');
78c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  } else {
79c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // No path at all
80c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    new_parsed->path.reset();
81c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
83c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Query
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  CanonicalizeQuery(source.query, parsed.query, query_converter,
85c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                    output, &new_parsed->query);
86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
87c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // Ref: ignore failure for this, since the page can probably still be loaded.
88c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  CanonicalizeRef(source.ref, parsed.ref, output, &new_parsed->ref);
89c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
90c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return success;
91c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
92c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
93c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}  // namespace
94c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
95c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Returns the default port for the given canonical scheme, or PORT_UNSPECIFIED
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// if the scheme is unknown.
98c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)int DefaultPortForScheme(const char* scheme, int scheme_len) {
990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  int default_port = PORT_UNSPECIFIED;
100c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  switch (scheme_len) {
101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    case 4:
102f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if (!strncmp(scheme, kHttpScheme, scheme_len))
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 80;
104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
105c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    case 5:
106f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if (!strncmp(scheme, kHttpsScheme, scheme_len))
107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 443;
108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
109c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    case 3:
110f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if (!strncmp(scheme, kFtpScheme, scheme_len))
111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 21;
112f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      else if (!strncmp(scheme, kWssScheme, scheme_len))
113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 443;
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    case 6:
116f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if (!strncmp(scheme, kGopherScheme, scheme_len))
117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 70;
118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    case 2:
120f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if (!strncmp(scheme, kWsScheme, scheme_len))
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        default_port = 80;
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      break;
123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  }
124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return default_port;
125c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool CanonicalizeStandardURL(const char* spec,
128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             int spec_len,
1290529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                             const Parsed& parsed,
130c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             CharsetConverter* query_converter,
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             CanonOutput* output,
1320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                             Parsed* new_parsed) {
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return DoCanonicalizeStandardURL<char, unsigned char>(
134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      URLComponentSource<char>(spec), parsed, query_converter,
135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      output, new_parsed);
136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1387d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)bool CanonicalizeStandardURL(const base::char16* spec,
139c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             int spec_len,
1400529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                             const Parsed& parsed,
141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             CharsetConverter* query_converter,
142c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                             CanonOutput* output,
1430529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                             Parsed* new_parsed) {
1447d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  return DoCanonicalizeStandardURL<base::char16, base::char16>(
1457d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)      URLComponentSource<base::char16>(spec), parsed, query_converter,
146c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      output, new_parsed);
147c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
148c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
149c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// It might be nice in the future to optimize this so unchanged components don't
150c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// need to be recanonicalized. This is especially true since the common case for
151c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// ReplaceComponents is removing things we don't want, like reference fragments
152c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// and usernames. These cases can become more efficient if we can assume the
153c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// rest of the URL is OK with these removed (or only the modified parts
154c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// recanonicalized). This would be much more complex to implement, however.
155c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)//
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// You would also need to update DoReplaceComponents in url_util.cc which
157c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// relies on this re-checking everything (see the comment there for why).
158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool ReplaceStandardURL(const char* base,
1590529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                        const Parsed& base_parsed,
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                        const Replacements<char>& replacements,
161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                        CharsetConverter* query_converter,
162c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                        CanonOutput* output,
1630529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                        Parsed* new_parsed) {
164c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  URLComponentSource<char> source(base);
1650529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  Parsed parsed(base_parsed);
166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  SetupOverrideComponents(base, replacements, &source, &parsed);
167c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return DoCanonicalizeStandardURL<char, unsigned char>(
168c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      source, parsed, query_converter, output, new_parsed);
169c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
170c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
171c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// For 16-bit replacements, we turn all the replacements into UTF-8 so the
172c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// regular codepath can be used.
173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool ReplaceStandardURL(const char* base,
1740529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                        const Parsed& base_parsed,
1757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)                        const Replacements<base::char16>& replacements,
176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                        CharsetConverter* query_converter,
177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                        CanonOutput* output,
1780529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                        Parsed* new_parsed) {
179c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  RawCanonOutput<1024> utf8;
180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  URLComponentSource<char> source(base);
1810529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  Parsed parsed(base_parsed);
182c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  SetupUTF16OverrideComponents(base, replacements, &utf8, &source, &parsed);
183c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  return DoCanonicalizeStandardURL<char, unsigned char>(
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      source, parsed, query_converter, output, new_parsed);
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)}
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1870529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch}  // namespace url
188