url_pattern.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. // Copyright (c) 2012 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 "extensions/common/url_pattern.h"
  5. #include <stddef.h>
  6. #include <ostream>
  7. #include "base/strings/pattern.h"
  8. #include "base/strings/strcat.h"
  9. #include "base/strings/string_number_conversions.h"
  10. #include "base/strings/string_piece.h"
  11. #include "base/strings/string_split.h"
  12. #include "base/strings/string_util.h"
  13. #include "content/public/common/url_constants.h"
  14. #include "extensions/common/constants.h"
  15. #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
  16. #include "net/base/url_util.h"
  17. #include "url/gurl.h"
  18. #include "url/url_util.h"
  19. const char URLPattern::kAllUrlsPattern[] = "<all_urls>";
  20. namespace {
  21. // TODO(aa): What about more obscure schemes like javascript: ?
  22. // Note: keep this array in sync with kValidSchemeMasks.
  23. const char* const kValidSchemes[] = {
  24. url::kHttpScheme, url::kHttpsScheme,
  25. url::kFileScheme, url::kFtpScheme,
  26. content::kChromeUIScheme, extensions::kExtensionScheme,
  27. url::kFileSystemScheme, url::kWsScheme,
  28. url::kWssScheme, url::kDataScheme,
  29. url::kUuidInPackageScheme,
  30. };
  31. const int kValidSchemeMasks[] = {
  32. URLPattern::SCHEME_HTTP,
  33. URLPattern::SCHEME_HTTPS,
  34. URLPattern::SCHEME_FILE,
  35. URLPattern::SCHEME_FTP,
  36. URLPattern::SCHEME_CHROMEUI,
  37. URLPattern::SCHEME_EXTENSION,
  38. URLPattern::SCHEME_FILESYSTEM,
  39. URLPattern::SCHEME_WS,
  40. URLPattern::SCHEME_WSS,
  41. URLPattern::SCHEME_DATA,
  42. URLPattern::SCHEME_UUID_IN_PACKAGE,
  43. };
  44. static_assert(std::size(kValidSchemes) == std::size(kValidSchemeMasks),
  45. "must keep these arrays in sync");
  46. const char kParseSuccess[] = "Success.";
  47. const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator.";
  48. const char kParseErrorInvalidScheme[] = "Invalid scheme.";
  49. const char kParseErrorWrongSchemeType[] = "Wrong scheme type.";
  50. const char kParseErrorEmptyHost[] = "Host can not be empty.";
  51. const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard.";
  52. const char kParseErrorEmptyPath[] = "Empty path.";
  53. const char kParseErrorInvalidPort[] = "Invalid port.";
  54. const char kParseErrorInvalidHost[] = "Invalid host.";
  55. // Message explaining each URLPattern::ParseResult.
  56. const char* const kParseResultMessages[] = {
  57. kParseSuccess,
  58. kParseErrorMissingSchemeSeparator,
  59. kParseErrorInvalidScheme,
  60. kParseErrorWrongSchemeType,
  61. kParseErrorEmptyHost,
  62. kParseErrorInvalidHostWildcard,
  63. kParseErrorEmptyPath,
  64. kParseErrorInvalidPort,
  65. kParseErrorInvalidHost,
  66. };
  67. static_assert(static_cast<int>(URLPattern::ParseResult::kNumParseResults) ==
  68. std::size(kParseResultMessages),
  69. "must add message for each parse result");
  70. const char kPathSeparator[] = "/";
  71. bool IsStandardScheme(base::StringPiece scheme) {
  72. // "*" gets the same treatment as a standard scheme.
  73. if (scheme == "*")
  74. return true;
  75. return url::IsStandard(scheme.data(),
  76. url::Component(0, static_cast<int>(scheme.length())));
  77. }
  78. bool IsValidPortForScheme(base::StringPiece scheme, base::StringPiece port) {
  79. if (port == "*")
  80. return true;
  81. // Only accept non-wildcard ports if the scheme uses ports.
  82. if (url::DefaultPortForScheme(scheme.data(), scheme.length()) ==
  83. url::PORT_UNSPECIFIED) {
  84. return false;
  85. }
  86. int parsed_port = url::PORT_UNSPECIFIED;
  87. if (!base::StringToInt(port, &parsed_port))
  88. return false;
  89. return (parsed_port >= 0) && (parsed_port < 65536);
  90. }
  91. // Returns |path| with the trailing wildcard stripped if one existed.
  92. //
  93. // The functions that rely on this (OverlapsWith and Contains) are only
  94. // called for the patterns inside URLPatternSet. In those cases, we know that
  95. // the path will have only a single wildcard at the end. This makes figuring
  96. // out overlap much easier. It seems like there is probably a computer-sciency
  97. // way to solve the general case, but we don't need that yet.
  98. base::StringPiece StripTrailingWildcard(base::StringPiece path) {
  99. if (base::EndsWith(path, "*"))
  100. path.remove_suffix(1);
  101. return path;
  102. }
  103. // Removes trailing dot from |host_piece| if any.
  104. base::StringPiece CanonicalizeHostForMatching(base::StringPiece host_piece) {
  105. if (base::EndsWith(host_piece, "."))
  106. host_piece.remove_suffix(1);
  107. return host_piece;
  108. }
  109. } // namespace
  110. // static
  111. bool URLPattern::IsValidSchemeForExtensions(base::StringPiece scheme) {
  112. for (size_t i = 0; i < std::size(kValidSchemes); ++i) {
  113. if (scheme == kValidSchemes[i])
  114. return true;
  115. }
  116. return false;
  117. }
  118. // static
  119. int URLPattern::GetValidSchemeMaskForExtensions() {
  120. int result = 0;
  121. for (size_t i = 0; i < std::size(kValidSchemeMasks); ++i)
  122. result |= kValidSchemeMasks[i];
  123. return result;
  124. }
  125. URLPattern::URLPattern()
  126. : valid_schemes_(SCHEME_NONE),
  127. match_all_urls_(false),
  128. match_subdomains_(false),
  129. port_("*") {}
  130. URLPattern::URLPattern(int valid_schemes)
  131. : valid_schemes_(valid_schemes),
  132. match_all_urls_(false),
  133. match_subdomains_(false),
  134. port_("*") {}
  135. URLPattern::URLPattern(int valid_schemes, base::StringPiece pattern)
  136. // Strict error checking is used, because this constructor is only
  137. // appropriate when we know |pattern| is valid.
  138. : valid_schemes_(valid_schemes),
  139. match_all_urls_(false),
  140. match_subdomains_(false),
  141. port_("*") {
  142. ParseResult result = Parse(pattern);
  143. DCHECK_EQ(ParseResult::kSuccess, result)
  144. << "Parsing unexpectedly failed for pattern: " << pattern << ": "
  145. << GetParseResultString(result);
  146. }
  147. URLPattern::URLPattern(const URLPattern& other) = default;
  148. URLPattern::URLPattern(URLPattern&& other) = default;
  149. URLPattern::~URLPattern() {
  150. }
  151. URLPattern& URLPattern::operator=(const URLPattern& other) = default;
  152. URLPattern& URLPattern::operator=(URLPattern&& other) = default;
  153. bool URLPattern::operator<(const URLPattern& other) const {
  154. return GetAsString() < other.GetAsString();
  155. }
  156. bool URLPattern::operator>(const URLPattern& other) const {
  157. return GetAsString() > other.GetAsString();
  158. }
  159. bool URLPattern::operator==(const URLPattern& other) const {
  160. return GetAsString() == other.GetAsString();
  161. }
  162. std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern) {
  163. return out << '"' << url_pattern.GetAsString() << '"';
  164. }
  165. URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern) {
  166. spec_.clear();
  167. SetMatchAllURLs(false);
  168. SetMatchSubdomains(false);
  169. SetPort("*");
  170. // Special case pattern to match every valid URL.
  171. if (pattern == kAllUrlsPattern) {
  172. SetMatchAllURLs(true);
  173. return ParseResult::kSuccess;
  174. }
  175. // Parse out the scheme.
  176. size_t scheme_end_pos = pattern.find(url::kStandardSchemeSeparator);
  177. bool has_standard_scheme_separator = true;
  178. // Some urls also use ':' alone as the scheme separator.
  179. if (scheme_end_pos == base::StringPiece::npos) {
  180. scheme_end_pos = pattern.find(':');
  181. has_standard_scheme_separator = false;
  182. }
  183. if (scheme_end_pos == base::StringPiece::npos)
  184. return ParseResult::kMissingSchemeSeparator;
  185. if (!SetScheme(pattern.substr(0, scheme_end_pos)))
  186. return ParseResult::kInvalidScheme;
  187. bool standard_scheme = IsStandardScheme(scheme_);
  188. if (standard_scheme != has_standard_scheme_separator)
  189. return ParseResult::kWrongSchemeSeparator;
  190. // Advance past the scheme separator.
  191. scheme_end_pos +=
  192. (standard_scheme ? strlen(url::kStandardSchemeSeparator) : 1);
  193. if (scheme_end_pos >= pattern.size())
  194. return ParseResult::kEmptyHost;
  195. // Parse out the host and path.
  196. size_t host_start_pos = scheme_end_pos;
  197. size_t path_start_pos = 0;
  198. if (!standard_scheme) {
  199. path_start_pos = host_start_pos;
  200. } else if (scheme_ == url::kFileScheme) {
  201. size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
  202. if (host_end_pos == base::StringPiece::npos) {
  203. // Allow hostname omission.
  204. // e.g. file://* is interpreted as file:///*,
  205. // file://foo* is interpreted as file:///foo*.
  206. path_start_pos = host_start_pos - 1;
  207. } else {
  208. // Ignore hostname if scheme is file://.
  209. // e.g. file://localhost/foo is equal to file:///foo.
  210. path_start_pos = host_end_pos;
  211. }
  212. } else {
  213. size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
  214. // Host is required.
  215. if (host_start_pos == host_end_pos)
  216. return ParseResult::kEmptyHost;
  217. if (host_end_pos == base::StringPiece::npos)
  218. return ParseResult::kEmptyPath;
  219. base::StringPiece host_and_port =
  220. pattern.substr(host_start_pos, host_end_pos - host_start_pos);
  221. size_t port_separator_pos = base::StringPiece::npos;
  222. if (host_and_port[0] != '[') {
  223. // Not IPv6 (either IPv4 or just a normal address).
  224. port_separator_pos = host_and_port.find(':');
  225. } else { // IPv6.
  226. size_t ipv6_host_end_pos = host_and_port.find(']');
  227. if (ipv6_host_end_pos == base::StringPiece::npos)
  228. return ParseResult::kInvalidHost;
  229. if (ipv6_host_end_pos == 1)
  230. return ParseResult::kEmptyHost;
  231. if (ipv6_host_end_pos < host_and_port.length() - 1) {
  232. // The host isn't the only component. Check for a port. This would
  233. // require a ':' to follow the closing ']' from the host.
  234. if (host_and_port[ipv6_host_end_pos + 1] != ':')
  235. return ParseResult::kInvalidHost;
  236. port_separator_pos = ipv6_host_end_pos + 1;
  237. }
  238. }
  239. if (port_separator_pos != base::StringPiece::npos &&
  240. !SetPort(host_and_port.substr(port_separator_pos + 1))) {
  241. return ParseResult::kInvalidPort;
  242. }
  243. // Note: this substr() will be the entire string if the port position
  244. // wasn't found.
  245. base::StringPiece host_piece = host_and_port.substr(0, port_separator_pos);
  246. if (host_piece.empty())
  247. return ParseResult::kEmptyHost;
  248. if (host_piece == "*") {
  249. match_subdomains_ = true;
  250. host_piece = base::StringPiece();
  251. } else if (base::StartsWith(host_piece, "*.")) {
  252. if (host_piece.length() == 2) {
  253. // We don't allow just '*.' as a host.
  254. return ParseResult::kEmptyHost;
  255. }
  256. match_subdomains_ = true;
  257. host_piece = host_piece.substr(2);
  258. }
  259. host_ = std::string(host_piece);
  260. path_start_pos = host_end_pos;
  261. }
  262. SetPath(pattern.substr(path_start_pos));
  263. // No other '*' can occur in the host, though. This isn't necessary, but is
  264. // done as a convenience to developers who might otherwise be confused and
  265. // think '*' works as a glob in the host.
  266. if (host_.find('*') != std::string::npos)
  267. return ParseResult::kInvalidHostWildcard;
  268. if (!host_.empty()) {
  269. // If |host_| is present (i.e., isn't a wildcard), we need to canonicalize
  270. // it.
  271. url::CanonHostInfo host_info;
  272. host_ = net::CanonicalizeHost(host_, &host_info);
  273. // net::CanonicalizeHost() returns an empty string on failure.
  274. if (host_.empty())
  275. return ParseResult::kInvalidHost;
  276. }
  277. // Null characters are not allowed in hosts.
  278. if (host_.find('\0') != std::string::npos)
  279. return ParseResult::kInvalidHost;
  280. return ParseResult::kSuccess;
  281. }
  282. void URLPattern::SetValidSchemes(int valid_schemes) {
  283. // TODO(devlin): Should we check that valid_schemes agrees with |scheme_|
  284. // here? Otherwise, valid_schemes_ and schemes_ may stop agreeing with each
  285. // other (e.g., in the case of `*://*/*`, where the scheme should only be
  286. // http or https).
  287. spec_.clear();
  288. valid_schemes_ = valid_schemes;
  289. }
  290. void URLPattern::SetHost(base::StringPiece host) {
  291. spec_.clear();
  292. host_.assign(host.data(), host.size());
  293. }
  294. void URLPattern::SetMatchAllURLs(bool val) {
  295. spec_.clear();
  296. match_all_urls_ = val;
  297. if (val) {
  298. match_subdomains_ = true;
  299. scheme_ = "*";
  300. host_.clear();
  301. SetPath("/*");
  302. }
  303. }
  304. void URLPattern::SetMatchSubdomains(bool val) {
  305. spec_.clear();
  306. match_subdomains_ = val;
  307. }
  308. bool URLPattern::SetScheme(base::StringPiece scheme) {
  309. spec_.clear();
  310. scheme_.assign(scheme.data(), scheme.size());
  311. if (scheme_ == "*") {
  312. valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS);
  313. } else if (!IsValidScheme(scheme_)) {
  314. return false;
  315. }
  316. return true;
  317. }
  318. bool URLPattern::IsValidScheme(base::StringPiece scheme) const {
  319. if (valid_schemes_ == SCHEME_ALL)
  320. return true;
  321. for (size_t i = 0; i < std::size(kValidSchemes); ++i) {
  322. if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i]))
  323. return true;
  324. }
  325. return false;
  326. }
  327. void URLPattern::SetPath(base::StringPiece path) {
  328. spec_.clear();
  329. path_.assign(path.data(), path.size());
  330. path_escaped_ = path_;
  331. base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\");
  332. base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?");
  333. }
  334. bool URLPattern::SetPort(base::StringPiece port) {
  335. spec_.clear();
  336. if (IsValidPortForScheme(scheme_, port)) {
  337. port_.assign(port.data(), port.size());
  338. return true;
  339. }
  340. return false;
  341. }
  342. bool URLPattern::MatchesURL(const GURL& test) const {
  343. // Invalid URLs can never match.
  344. if (!test.is_valid())
  345. return false;
  346. const GURL* test_url = &test;
  347. bool has_inner_url = test.inner_url() != nullptr;
  348. if (has_inner_url) {
  349. if (!test.SchemeIsFileSystem())
  350. return false; // The only nested URLs we handle are filesystem URLs.
  351. test_url = test.inner_url();
  352. }
  353. // Ensure the scheme matches first, since <all_urls> may not match this URL if
  354. // the scheme is excluded.
  355. if (!MatchesScheme(test_url->scheme_piece()))
  356. return false;
  357. if (match_all_urls_)
  358. return true;
  359. // Unless |match_all_urls_| is true, the grammar only permits matching
  360. // URLs with nonempty paths.
  361. if (!test.has_path())
  362. return false;
  363. std::string path_for_request = test.PathForRequest();
  364. if (has_inner_url) {
  365. path_for_request = base::StrCat({test_url->path_piece(), path_for_request});
  366. }
  367. return MatchesSecurityOriginHelper(*test_url) &&
  368. MatchesPath(path_for_request);
  369. }
  370. bool URLPattern::MatchesSecurityOrigin(const GURL& test) const {
  371. const GURL* test_url = &test;
  372. bool has_inner_url = test.inner_url() != NULL;
  373. if (has_inner_url) {
  374. if (!test.SchemeIsFileSystem())
  375. return false; // The only nested URLs we handle are filesystem URLs.
  376. test_url = test.inner_url();
  377. }
  378. if (!MatchesScheme(test_url->scheme()))
  379. return false;
  380. if (match_all_urls_)
  381. return true;
  382. return MatchesSecurityOriginHelper(*test_url);
  383. }
  384. bool URLPattern::MatchesScheme(base::StringPiece test) const {
  385. if (!IsValidScheme(test))
  386. return false;
  387. return scheme_ == "*" || test == scheme_;
  388. }
  389. bool URLPattern::MatchesHost(base::StringPiece host) const {
  390. // TODO(devlin): This is a bit sad. Parsing urls is expensive. However, it's
  391. // important that we do this conversion to a GURL in order to canonicalize the
  392. // host (the pattern's host_ already is canonicalized from Parse()). We can't
  393. // just do string comparison.
  394. return MatchesHost(GURL(base::StrCat(
  395. {url::kHttpScheme, url::kStandardSchemeSeparator, host, "/"})));
  396. }
  397. bool URLPattern::MatchesHost(const GURL& test) const {
  398. base::StringPiece test_host(CanonicalizeHostForMatching(test.host_piece()));
  399. const base::StringPiece pattern_host(CanonicalizeHostForMatching(host_));
  400. // If the hosts are exactly equal, we have a match.
  401. if (test_host == pattern_host)
  402. return true;
  403. // If we're matching subdomains, and we have no host in the match pattern,
  404. // that means that we're matching all hosts, which means we have a match no
  405. // matter what the test host is.
  406. if (match_subdomains_ && pattern_host.empty())
  407. return true;
  408. // Otherwise, we can only match if our match pattern matches subdomains.
  409. if (!match_subdomains_)
  410. return false;
  411. // We don't do subdomain matching against IP addresses, so we can give up now
  412. // if the test host is an IP address.
  413. if (test.HostIsIPAddress())
  414. return false;
  415. // Check if the test host is a subdomain of our host.
  416. if (test_host.length() <= (pattern_host.length() + 1))
  417. return false;
  418. if (!base::EndsWith(test_host, pattern_host))
  419. return false;
  420. return test_host[test_host.length() - pattern_host.length() - 1] == '.';
  421. }
  422. bool URLPattern::MatchesEffectiveTld(
  423. net::registry_controlled_domains::PrivateRegistryFilter private_filter,
  424. net::registry_controlled_domains::UnknownRegistryFilter unknown_filter)
  425. const {
  426. // Check if it matches all urls or is a pattern like http://*/*.
  427. if (match_all_urls_ || (match_subdomains_ && host_.empty()))
  428. return true;
  429. // If this doesn't even match subdomains, it can't possibly be a TLD wildcard.
  430. if (!match_subdomains_)
  431. return false;
  432. // If there was more than just a TLD in the host (e.g., *.foobar.com), it
  433. // doesn't match all hosts in an effective TLD.
  434. if (net::registry_controlled_domains::HostHasRegistryControlledDomain(
  435. host_, unknown_filter, private_filter)) {
  436. return false;
  437. }
  438. // At this point the host could either be just a TLD ("com") or some unknown
  439. // TLD-like string ("notatld"). To disambiguate between them construct a
  440. // fake URL, and check the registry.
  441. //
  442. // If we recognized this TLD, then this is a pattern like *.com, and it
  443. // matches an effective TLD.
  444. return net::registry_controlled_domains::HostHasRegistryControlledDomain(
  445. "notatld." + host_, unknown_filter, private_filter);
  446. }
  447. bool URLPattern::MatchesSingleOrigin() const {
  448. // Strictly speaking, the port is part of the origin, but in URLPattern it
  449. // defaults to *. It's not very interesting anyway, so leave it out.
  450. return !MatchesEffectiveTld() && scheme_ != "*" && !match_subdomains_;
  451. }
  452. bool URLPattern::MatchesPath(base::StringPiece test) const {
  453. // Make the behaviour of OverlapsWith consistent with MatchesURL, which is
  454. // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'.
  455. // The below if is a no-copy way of doing (test + "/*" == path_escaped_).
  456. if (path_escaped_.length() == test.length() + 2 &&
  457. base::StartsWith(path_escaped_.c_str(), test) &&
  458. base::EndsWith(path_escaped_, "/*")) {
  459. return true;
  460. }
  461. return base::MatchPattern(test, path_escaped_);
  462. }
  463. const std::string& URLPattern::GetAsString() const {
  464. if (!spec_.empty())
  465. return spec_;
  466. if (match_all_urls_) {
  467. spec_ = kAllUrlsPattern;
  468. return spec_;
  469. }
  470. bool standard_scheme = IsStandardScheme(scheme_);
  471. std::string spec = scheme_ +
  472. (standard_scheme ? url::kStandardSchemeSeparator : ":");
  473. if (scheme_ != url::kFileScheme && standard_scheme) {
  474. if (match_subdomains_) {
  475. spec += "*";
  476. if (!host_.empty())
  477. spec += ".";
  478. }
  479. if (!host_.empty())
  480. spec += host_;
  481. if (port_ != "*") {
  482. spec += ":";
  483. spec += port_;
  484. }
  485. }
  486. if (!path_.empty())
  487. spec += path_;
  488. spec_ = std::move(spec);
  489. return spec_;
  490. }
  491. bool URLPattern::OverlapsWith(const URLPattern& other) const {
  492. if (match_all_urls() || other.match_all_urls())
  493. return true;
  494. return (MatchesAnyScheme(other.GetExplicitSchemes()) ||
  495. other.MatchesAnyScheme(GetExplicitSchemes()))
  496. && (MatchesHost(other.host()) || other.MatchesHost(host()))
  497. && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port()))
  498. && (MatchesPath(StripTrailingWildcard(other.path())) ||
  499. other.MatchesPath(StripTrailingWildcard(path())));
  500. }
  501. bool URLPattern::Contains(const URLPattern& other) const {
  502. // Important: it's not enough to just check match_all_urls(); we also need to
  503. // make sure that the schemes in this pattern are a superset of those in
  504. // |other|.
  505. if (match_all_urls() &&
  506. (valid_schemes_ & other.valid_schemes_) == other.valid_schemes_) {
  507. return true;
  508. }
  509. return MatchesAllSchemes(other.GetExplicitSchemes()) &&
  510. MatchesHost(other.host()) &&
  511. (!other.match_subdomains_ || match_subdomains_) &&
  512. MatchesPortPattern(other.port()) &&
  513. MatchesPath(StripTrailingWildcard(other.path()));
  514. }
  515. absl::optional<URLPattern> URLPattern::CreateIntersection(
  516. const URLPattern& other) const {
  517. // Easy case: Schemes don't overlap. Return nullopt.
  518. int intersection_schemes = URLPattern::SCHEME_NONE;
  519. if (valid_schemes_ == URLPattern::SCHEME_ALL)
  520. intersection_schemes = other.valid_schemes_;
  521. else if (other.valid_schemes_ == URLPattern::SCHEME_ALL)
  522. intersection_schemes = valid_schemes_;
  523. else
  524. intersection_schemes = valid_schemes_ & other.valid_schemes_;
  525. if (intersection_schemes == URLPattern::SCHEME_NONE)
  526. return absl::nullopt;
  527. {
  528. // In a few cases, we can (mostly) return a copy of one of the patterns.
  529. // This can happen when either:
  530. // - The URLPattern's are identical (possibly excluding valid_schemes_)
  531. // - One of the patterns has match_all_urls() equal to true.
  532. // NOTE(devlin): Theoretically, we could use Contains() instead of
  533. // match_all_urls() here. However, Contains() strips the trailing wildcard
  534. // from the path, which could yield the incorrect result.
  535. const URLPattern* copy_source = nullptr;
  536. if (*this == other || other.match_all_urls())
  537. copy_source = this;
  538. else if (match_all_urls())
  539. copy_source = &other;
  540. if (copy_source) {
  541. // NOTE: equality checks don't take into account valid_schemes_, and
  542. // schemes can be different in the case of match_all_urls() as well, so
  543. // we can't always just return *copy_source.
  544. if (intersection_schemes == copy_source->valid_schemes_)
  545. return *copy_source;
  546. URLPattern result(intersection_schemes);
  547. ParseResult parse_result = result.Parse(copy_source->GetAsString());
  548. CHECK_EQ(ParseResult::kSuccess, parse_result);
  549. return result;
  550. }
  551. }
  552. // No more easy cases. Go through component by component to find the patterns
  553. // that intersect.
  554. // Note: Alias the function type (rather than using auto) because
  555. // MatchesHost() is overloaded.
  556. using match_function_type = bool (URLPattern::*)(base::StringPiece) const;
  557. auto get_intersection = [this, &other](base::StringPiece own_str,
  558. base::StringPiece other_str,
  559. match_function_type match_function,
  560. base::StringPiece* out) {
  561. if ((this->*match_function)(other_str)) {
  562. *out = other_str;
  563. return true;
  564. }
  565. if ((other.*match_function)(own_str)) {
  566. *out = own_str;
  567. return true;
  568. }
  569. return false;
  570. };
  571. base::StringPiece scheme;
  572. base::StringPiece host;
  573. base::StringPiece port;
  574. base::StringPiece path;
  575. // If any pieces fail to overlap, then there is no intersection.
  576. if (!get_intersection(scheme_, other.scheme_, &URLPattern::MatchesScheme,
  577. &scheme) ||
  578. !get_intersection(host_, other.host_, &URLPattern::MatchesHost, &host) ||
  579. !get_intersection(port_, other.port_, &URLPattern::MatchesPortPattern,
  580. &port) ||
  581. !get_intersection(path_, other.path_, &URLPattern::MatchesPath, &path)) {
  582. return absl::nullopt;
  583. }
  584. // Only match subdomains if both patterns match subdomains.
  585. base::StringPiece subdomains;
  586. if (match_subdomains_ && other.match_subdomains_) {
  587. // The host may be empty (e.g., in the case of *://*/* - in that case, only
  588. // append '*' instead of '*.'.
  589. subdomains = host.empty() ? "*" : "*.";
  590. }
  591. base::StringPiece scheme_separator =
  592. IsStandardScheme(scheme) ? url::kStandardSchemeSeparator : ":";
  593. std::string pattern_str = base::StrCat(
  594. {scheme, scheme_separator, subdomains, host, ":", port, path});
  595. URLPattern pattern(intersection_schemes);
  596. ParseResult result = pattern.Parse(pattern_str);
  597. // TODO(devlin): I don't think there's any way this should ever fail, but
  598. // use a CHECK() to flush any cases out. If nothing crops up, downgrade this
  599. // to a DCHECK in M72.
  600. CHECK_EQ(ParseResult::kSuccess, result);
  601. return pattern;
  602. }
  603. bool URLPattern::MatchesAnyScheme(
  604. const std::vector<std::string>& schemes) const {
  605. for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
  606. if (MatchesScheme(*i))
  607. return true;
  608. }
  609. return false;
  610. }
  611. bool URLPattern::MatchesAllSchemes(
  612. const std::vector<std::string>& schemes) const {
  613. for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
  614. if (!MatchesScheme(*i))
  615. return false;
  616. }
  617. return true;
  618. }
  619. bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const {
  620. // Ignore hostname if scheme is file://.
  621. if (scheme_ != url::kFileScheme && !MatchesHost(test))
  622. return false;
  623. if (!MatchesPortPattern(base::NumberToString(test.EffectiveIntPort())))
  624. return false;
  625. return true;
  626. }
  627. bool URLPattern::MatchesPortPattern(base::StringPiece port) const {
  628. return port_ == "*" || port_ == port;
  629. }
  630. std::vector<std::string> URLPattern::GetExplicitSchemes() const {
  631. std::vector<std::string> result;
  632. if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) {
  633. result.push_back(scheme_);
  634. return result;
  635. }
  636. for (size_t i = 0; i < std::size(kValidSchemes); ++i) {
  637. if (MatchesScheme(kValidSchemes[i])) {
  638. result.push_back(kValidSchemes[i]);
  639. }
  640. }
  641. return result;
  642. }
  643. std::vector<URLPattern> URLPattern::ConvertToExplicitSchemes() const {
  644. std::vector<std::string> explicit_schemes = GetExplicitSchemes();
  645. std::vector<URLPattern> result;
  646. for (std::vector<std::string>::const_iterator i = explicit_schemes.begin();
  647. i != explicit_schemes.end(); ++i) {
  648. URLPattern temp = *this;
  649. temp.SetScheme(*i);
  650. temp.SetMatchAllURLs(false);
  651. result.push_back(temp);
  652. }
  653. return result;
  654. }
  655. // static
  656. const char* URLPattern::GetParseResultString(
  657. URLPattern::ParseResult parse_result) {
  658. return kParseResultMessages[static_cast<int>(parse_result)];
  659. }