scheme_host_port.cc 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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.
  4. #include "url/scheme_host_port.h"
  5. #include <stdint.h>
  6. #include <string.h>
  7. #include <ostream>
  8. #include <tuple>
  9. #include "base/check_op.h"
  10. #include "base/containers/contains.h"
  11. #include "base/notreached.h"
  12. #include "base/numerics/safe_conversions.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/string_piece.h"
  15. #include "url/gurl.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"
  21. namespace url {
  22. namespace {
  23. bool IsCanonicalHost(const base::StringPiece& host) {
  24. std::string canon_host;
  25. // Try to canonicalize the host (copy/pasted from net/base. :( ).
  26. const Component raw_host_component(0,
  27. base::checked_cast<int>(host.length()));
  28. StdStringCanonOutput canon_host_output(&canon_host);
  29. CanonHostInfo host_info;
  30. CanonicalizeHostVerbose(host.data(), raw_host_component,
  31. &canon_host_output, &host_info);
  32. if (host_info.out_host.is_nonempty() &&
  33. host_info.family != CanonHostInfo::BROKEN) {
  34. // Success! Assert that there's no extra garbage.
  35. canon_host_output.Complete();
  36. DCHECK_EQ(host_info.out_host.len, static_cast<int>(canon_host.length()));
  37. } else {
  38. // Empty host, or canonicalization failed.
  39. canon_host.clear();
  40. }
  41. return host == canon_host;
  42. }
  43. // Note: When changing IsValidInput, consider also updating
  44. // ShouldTreatAsOpaqueOrigin in Blink (there might be existing differences in
  45. // behavior between these 2 layers, but we should avoid introducing new
  46. // differences).
  47. bool IsValidInput(const base::StringPiece& scheme,
  48. const base::StringPiece& host,
  49. uint16_t port,
  50. SchemeHostPort::ConstructPolicy policy) {
  51. // Empty schemes are never valid.
  52. if (scheme.empty())
  53. return false;
  54. // about:blank and other no-access schemes translate into an opaque origin.
  55. // This helps consistency with ShouldTreatAsOpaqueOrigin in Blink.
  56. if (base::Contains(GetNoAccessSchemes(), scheme))
  57. return false;
  58. SchemeType scheme_type = SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
  59. bool is_standard = GetStandardSchemeType(
  60. scheme.data(),
  61. Component(0, base::checked_cast<int>(scheme.length())),
  62. &scheme_type);
  63. if (!is_standard) {
  64. // To be consistent with ShouldTreatAsOpaqueOrigin in Blink, local
  65. // non-standard schemes are currently allowed to be tuple origins.
  66. // Nonstandard schemes don't have hostnames, so their tuple is just
  67. // ("protocol", "", 0).
  68. //
  69. // TODO: Migrate "content:" and "externalfile:" to be standard schemes, and
  70. // remove this local scheme exception.
  71. if (base::Contains(GetLocalSchemes(), scheme) && host.empty() && port == 0)
  72. return true;
  73. // Otherwise, allow non-standard schemes only if the Android WebView
  74. // workaround is enabled.
  75. return AllowNonStandardSchemesForAndroidWebView();
  76. }
  77. switch (scheme_type) {
  78. case SCHEME_WITH_HOST_AND_PORT:
  79. case SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION:
  80. // A URL with |scheme| is required to have the host and port, so return an
  81. // invalid instance if host is not given. Note that a valid port is
  82. // always provided by SchemeHostPort(const GURL&) constructor (a missing
  83. // port is replaced with a default port if needed by
  84. // GURL::EffectiveIntPort()).
  85. if (host.empty())
  86. return false;
  87. // Don't do an expensive canonicalization if the host is already
  88. // canonicalized.
  89. DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
  90. IsCanonicalHost(host));
  91. if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
  92. !IsCanonicalHost(host)) {
  93. return false;
  94. }
  95. return true;
  96. case SCHEME_WITH_HOST:
  97. if (port != 0) {
  98. // Return an invalid object if a URL with the scheme never represents
  99. // the port data but the given |port| is non-zero.
  100. return false;
  101. }
  102. // Don't do an expensive canonicalization if the host is already
  103. // canonicalized.
  104. DCHECK(policy == SchemeHostPort::CHECK_CANONICALIZATION ||
  105. IsCanonicalHost(host));
  106. if (policy == SchemeHostPort::CHECK_CANONICALIZATION &&
  107. !IsCanonicalHost(host)) {
  108. return false;
  109. }
  110. return true;
  111. case SCHEME_WITHOUT_AUTHORITY:
  112. return false;
  113. default:
  114. NOTREACHED();
  115. return false;
  116. }
  117. }
  118. } // namespace
  119. SchemeHostPort::SchemeHostPort() = default;
  120. SchemeHostPort::SchemeHostPort(std::string scheme,
  121. std::string host,
  122. uint16_t port,
  123. ConstructPolicy policy) {
  124. if (!IsValidInput(scheme, host, port, policy)) {
  125. DCHECK(!IsValid());
  126. return;
  127. }
  128. scheme_ = std::move(scheme);
  129. host_ = std::move(host);
  130. port_ = port;
  131. DCHECK(IsValid()) << "Scheme: " << scheme_ << " Host: " << host_
  132. << " Port: " << port;
  133. }
  134. SchemeHostPort::SchemeHostPort(base::StringPiece scheme,
  135. base::StringPiece host,
  136. uint16_t port)
  137. : SchemeHostPort(std::string(scheme),
  138. std::string(host),
  139. port,
  140. ConstructPolicy::CHECK_CANONICALIZATION) {}
  141. SchemeHostPort::SchemeHostPort(const GURL& url) {
  142. if (!url.is_valid())
  143. return;
  144. base::StringPiece scheme = url.scheme_piece();
  145. base::StringPiece host = url.host_piece();
  146. // A valid GURL never returns PORT_INVALID.
  147. int port = url.EffectiveIntPort();
  148. if (port == PORT_UNSPECIFIED) {
  149. port = 0;
  150. } else {
  151. DCHECK_GE(port, 0);
  152. DCHECK_LE(port, 65535);
  153. }
  154. if (!IsValidInput(scheme, host, port, ALREADY_CANONICALIZED))
  155. return;
  156. scheme_ = std::string(scheme);
  157. host_ = std::string(host);
  158. port_ = port;
  159. }
  160. SchemeHostPort::~SchemeHostPort() = default;
  161. bool SchemeHostPort::IsValid() const {
  162. // It suffices to just check |scheme_| for emptiness; the other fields are
  163. // never present without it.
  164. DCHECK(!scheme_.empty() || host_.empty());
  165. DCHECK(!scheme_.empty() || port_ == 0);
  166. return !scheme_.empty();
  167. }
  168. std::string SchemeHostPort::Serialize() const {
  169. // Null checking for |parsed| in SerializeInternal is probably slower than
  170. // just filling it in and discarding it here.
  171. url::Parsed parsed;
  172. return SerializeInternal(&parsed);
  173. }
  174. GURL SchemeHostPort::GetURL() const {
  175. url::Parsed parsed;
  176. std::string serialized = SerializeInternal(&parsed);
  177. if (!IsValid())
  178. 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());
  182. if (host_.empty())
  183. return GURL(serialized);
  184. // If the serialized string is passed to GURL for parsing, it will append an
  185. // empty path "/". Add that here. Note: per RFC 6454 we cannot do this for
  186. // normal Origin serialization.
  187. DCHECK(!parsed.path.is_valid());
  188. parsed.path = Component(serialized.length(), 1);
  189. serialized.append("/");
  190. return GURL(std::move(serialized), parsed, true);
  191. }
  192. bool SchemeHostPort::operator<(const SchemeHostPort& other) const {
  193. return std::tie(port_, scheme_, host_) <
  194. std::tie(other.port_, other.scheme_, other.host_);
  195. }
  196. std::string SchemeHostPort::SerializeInternal(url::Parsed* parsed) const {
  197. std::string result;
  198. if (!IsValid())
  199. return result;
  200. // Reserve enough space for the "normal" case of scheme://host/.
  201. result.reserve(scheme_.size() + host_.size() + 4);
  202. if (!scheme_.empty()) {
  203. parsed->scheme = Component(0, scheme_.length());
  204. result.append(scheme_);
  205. }
  206. result.append(kStandardSchemeSeparator);
  207. if (!host_.empty()) {
  208. parsed->host = Component(result.length(), host_.length());
  209. result.append(host_);
  210. }
  211. // Omit the port component if the port matches with the default port
  212. // defined for the scheme, if any.
  213. int default_port = DefaultPortForScheme(scheme_.data(),
  214. static_cast<int>(scheme_.length()));
  215. if (default_port == PORT_UNSPECIFIED)
  216. return result;
  217. if (port_ != default_port) {
  218. result.push_back(':');
  219. std::string port(base::NumberToString(port_));
  220. parsed->port = Component(result.length(), port.length());
  221. result.append(std::move(port));
  222. }
  223. return result;
  224. }
  225. std::ostream& operator<<(std::ostream& out,
  226. const SchemeHostPort& scheme_host_port) {
  227. return out << scheme_host_port.Serialize();
  228. }
  229. } // namespace url