1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "url/scheme_host_port.h"
12 #include "base/logging.h"
13 #include "base/numerics/safe_conversions.h"
14 #include "base/strings/string_number_conversions.h"
16 #include "url/third_party/mozilla/url_parse.h"
17 #include "url/url_canon.h"
18 #include "url/url_canon_stdstring.h"
19 #include "url/url_constants.h"
20 #include "url/url_util.h"
26 bool IsCanonicalHost(const base::StringPiece& host) {
27 std::string canon_host;
29 // Try to canonicalize the host (copy/pasted from net/base. :( ).
30 const Component raw_host_component(0,
31 base::checked_cast<int>(host.length()));
32 StdStringCanonOutput canon_host_output(&canon_host);
33 CanonHostInfo host_info;
34 CanonicalizeHostVerbose(host.data(), raw_host_component,
35 &canon_host_output, &host_info);
37 if (host_info.out_host.is_nonempty() &&
38 host_info.family != CanonHostInfo::BROKEN) {
39 // Success! Assert that there's no extra garbage.
40 canon_host_output.Complete();
41 DCHECK_EQ(host_info.out_host.len, static_cast<int>(canon_host.length()));
43 // Empty host, or canonicalization failed.
47 return host == canon_host;
50 bool IsValidInput(const base::StringPiece& scheme,
51 const base::StringPiece& host,
53 SchemeHostPort::ConstructPolicy policy) {
54 SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
55 bool is_standard = GetStandardSchemeType(
57 Component(0, base::checked_cast<int>(scheme.length())),
62 switch (scheme_type) {
63 case SCHEME_WITH_HOST_AND_PORT:
64 case SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION:
65 // A URL with |scheme| is required to have the host and port (may be
66 // omitted in a serialization if it's the same as the default value).
67 // Return an invalid instance if either of them is not given.
68 if (host.empty() || port == 0)
71 // Don't do an expensive canonicalization if the host is already
73 DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
74 IsCanonicalHost(host));
75 if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
76 !IsCanonicalHost(host)) {
82 case SCHEME_WITH_HOST:
84 // Return an invalid object if a URL with the scheme never represents
85 // the port data but the given |port| is non-zero.
89 // Don't do an expensive canonicalization if the host is already
91 DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
92 IsCanonicalHost(host));
93 if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
94 !IsCanonicalHost(host)) {
100 case SCHEME_WITHOUT_AUTHORITY:
111 SchemeHostPort::SchemeHostPort() : port_(0) {
114 SchemeHostPort::SchemeHostPort(std::string scheme,
117 ConstructPolicy policy)
119 if (!IsValidInput(scheme, host, port, policy))
122 scheme_ = std::move(scheme);
123 host_ = std::move(host);
127 SchemeHostPort::SchemeHostPort(base::StringPiece scheme,
128 base::StringPiece host,
130 : SchemeHostPort(scheme.as_string(),
133 ConstructPolicy::CHECK_CANONICALIZATION) {}
135 SchemeHostPort::SchemeHostPort(const GURL& url) : port_(0) {
139 base::StringPiece scheme = url.scheme_piece();
140 base::StringPiece host = url.host_piece();
142 // A valid GURL never returns PORT_INVALID.
143 int port = url.EffectiveIntPort();
144 if (port == PORT_UNSPECIFIED) {
148 DCHECK_LE(port, 65535);
151 if (!IsValidInput(scheme, host, port, ALREADY_CANONICALIZED))
154 scheme.CopyToString(&scheme_);
155 host.CopyToString(&host_);
159 SchemeHostPort::~SchemeHostPort() = default;
161 bool SchemeHostPort::IsInvalid() const {
162 return scheme_.empty() && host_.empty() && !port_;
165 std::string SchemeHostPort::Serialize() const {
166 // Null checking for |parsed| in SerializeInternal is probably slower than
167 // just filling it in and discarding it here.
169 return SerializeInternal(&parsed);
172 GURL SchemeHostPort::GetURL() const {
174 std::string serialized = SerializeInternal(&parsed);
177 return GURL(std::move(serialized), parsed, false);
179 // SchemeHostPort does not have enough information to determine if an empty
180 // host is valid or not for the given scheme. Force re-parsing.
181 DCHECK(!scheme_.empty());
183 return GURL(serialized);
185 // If the serialized string is passed to GURL for parsing, it will append an
186 // empty path "/". Add that here. Note: per RFC 6454 we cannot do this for
187 // normal Origin serialization.
188 DCHECK(!parsed.path.is_valid());
189 parsed.path = Component(serialized.length(), 1);
190 serialized.append("/");
191 return GURL(std::move(serialized), parsed, true);
194 bool SchemeHostPort::Equals(const SchemeHostPort& other) const {
195 return port_ == other.port() && scheme_ == other.scheme() &&
196 host_ == other.host();
199 bool SchemeHostPort::operator<(const SchemeHostPort& other) const {
200 return std::tie(port_, scheme_, host_) <
201 std::tie(other.port_, other.scheme_, other.host_);
204 std::string SchemeHostPort::SerializeInternal(url::Parsed* parsed) const {
209 // Reserve enough space for the "normal" case of scheme://host/.
210 result.reserve(scheme_.size() + host_.size() + 4);
212 if (!scheme_.empty()) {
213 parsed->scheme = Component(0, scheme_.length());
214 result.append(scheme_);
217 result.append(kStandardSchemeSeparator);
219 if (!host_.empty()) {
220 parsed->host = Component(result.length(), host_.length());
221 result.append(host_);
227 // Omit the port component if the port matches with the default port
228 // defined for the scheme, if any.
229 int default_port = DefaultPortForScheme(scheme_.data(),
230 static_cast<int>(scheme_.length()));
231 if (default_port == PORT_UNSPECIFIED)
233 if (port_ != default_port) {
234 result.push_back(':');
235 std::string port(base::UintToString(port_));
236 parsed->port = Component(result.length(), port.length());
237 result.append(std::move(port));