1 // Copyright (c) 2017 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 
5 #include <string>
6 
7 #include "content/browser/isolated_origin_util.h"
8 
9 #include "base/logging.h"
10 #include "base/strings/string_util.h"
11 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
12 #include "services/network/public/cpp/is_potentially_trustworthy.h"
13 #include "url/gurl.h"
14 
15 const char* kAllSubdomainsWildcard = "[*.]";
16 
17 namespace content {
18 
IsolatedOriginPattern(base::StringPiece pattern)19 IsolatedOriginPattern::IsolatedOriginPattern(base::StringPiece pattern)
20     : isolate_all_subdomains_(false), is_valid_(false) {
21   Parse(pattern);
22 }
23 
IsolatedOriginPattern(const url::Origin & origin)24 IsolatedOriginPattern::IsolatedOriginPattern(const url::Origin& origin)
25     : IsolatedOriginPattern(origin.GetURL().spec()) {}
26 
27 IsolatedOriginPattern::~IsolatedOriginPattern() = default;
28 IsolatedOriginPattern::IsolatedOriginPattern(
29     const IsolatedOriginPattern& other) = default;
30 IsolatedOriginPattern& IsolatedOriginPattern::operator=(
31     const IsolatedOriginPattern& other) = default;
32 IsolatedOriginPattern::IsolatedOriginPattern(IsolatedOriginPattern&& other) =
33     default;
34 IsolatedOriginPattern& IsolatedOriginPattern::operator=(
35     IsolatedOriginPattern&& other) = default;
36 
Parse(const base::StringPiece & unparsed_pattern)37 bool IsolatedOriginPattern::Parse(const base::StringPiece& unparsed_pattern) {
38   pattern_ = unparsed_pattern.as_string();
39   origin_ = url::Origin();
40   isolate_all_subdomains_ = false;
41   is_valid_ = false;
42 
43   size_t host_begin = unparsed_pattern.find(url::kStandardSchemeSeparator);
44   if (host_begin == base::StringPiece::npos || host_begin == 0)
45     return false;
46 
47   // Skip over the scheme separator.
48   host_begin += strlen(url::kStandardSchemeSeparator);
49   if (host_begin >= unparsed_pattern.size())
50     return false;
51 
52   base::StringPiece scheme_part = unparsed_pattern.substr(0, host_begin);
53   base::StringPiece host_part = unparsed_pattern.substr(host_begin);
54 
55   // Empty schemes or hosts are invalid for isolation purposes.
56   if (host_part.size() == 0)
57     return false;
58 
59   if (base::StartsWith(host_part, kAllSubdomainsWildcard)) {
60     isolate_all_subdomains_ = true;
61     host_part.remove_prefix(strlen(kAllSubdomainsWildcard));
62   }
63 
64   GURL conformant_url(base::JoinString({scheme_part, host_part}, ""));
65   origin_ = url::Origin::Create(conformant_url);
66 
67   // Ports are ignored when matching isolated origins (see also
68   // https://crbug.com/914511).
69   const std::string& scheme = origin_.scheme();
70   int default_port = url::DefaultPortForScheme(scheme.data(), scheme.length());
71   if (origin_.port() != default_port) {
72     LOG(ERROR) << "Ignoring port number in isolated origin: " << origin_;
73     origin_ = url::Origin::Create(GURL(
74         origin_.scheme() + url::kStandardSchemeSeparator + origin_.host()));
75   }
76 
77   // Can't isolate subdomains of an IP address, must be a valid isolated origin
78   // after processing.
79   if ((conformant_url.HostIsIPAddress() && isolate_all_subdomains_) ||
80       !IsolatedOriginUtil::IsValidIsolatedOrigin(origin_)) {
81     origin_ = url::Origin();
82     isolate_all_subdomains_ = false;
83     return false;
84   }
85 
86   DCHECK(!is_valid_ || !origin_.opaque());
87   is_valid_ = true;
88   return true;
89 }
90 
91 // static
DoesOriginMatchIsolatedOrigin(const url::Origin & origin,const url::Origin & isolated_origin)92 bool IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
93     const url::Origin& origin,
94     const url::Origin& isolated_origin) {
95   // Don't match subdomains if the isolated origin is an IP address.
96   if (isolated_origin.GetURL().HostIsIPAddress())
97     return origin == isolated_origin;
98 
99   // Compare scheme and hostname, but don't compare ports - see
100   // https://crbug.com/914511.
101   if (origin.scheme() != isolated_origin.scheme())
102     return false;
103 
104   // Subdomains of an isolated origin are considered to be in the same isolated
105   // origin.
106   return origin.DomainIs(isolated_origin.host());
107 }
108 
109 // static
IsValidIsolatedOrigin(const url::Origin & origin)110 bool IsolatedOriginUtil::IsValidIsolatedOrigin(const url::Origin& origin) {
111   return IsValidIsolatedOriginImpl(origin, true);
112 }
113 
114 // static
IsValidOriginForOptInIsolation(const url::Origin & origin)115 bool IsolatedOriginUtil::IsValidOriginForOptInIsolation(
116     const url::Origin& origin) {
117   // Per https://html.spec.whatwg.org/C/#initialise-the-document-object,
118   // non-secure contexts cannot be isolated via opt-in origin isolation.
119   return IsValidIsolatedOriginImpl(origin, false) &&
120          network::IsOriginPotentiallyTrustworthy(origin);
121 }
122 
123 // static
IsValidIsolatedOriginImpl(const url::Origin & origin,bool check_has_registry_domain)124 bool IsolatedOriginUtil::IsValidIsolatedOriginImpl(
125     const url::Origin& origin,
126     bool check_has_registry_domain) {
127   if (origin.opaque())
128     return false;
129 
130   // Isolated origins should have HTTP or HTTPS schemes.  Hosts in other
131   // schemes may not be compatible with subdomain matching.
132   GURL origin_gurl = origin.GetURL();
133   if (!origin_gurl.SchemeIsHTTPOrHTTPS())
134     return false;
135 
136   // IP addresses are allowed.
137   if (origin_gurl.HostIsIPAddress())
138     return true;
139 
140   // Disallow hosts such as http://co.uk/, which don't have a valid
141   // registry-controlled domain.  This prevents subdomain matching from
142   // grouping unrelated sites on a registry into the same origin.
143   //
144   // This is not relevant for opt-in origin isolation, which doesn't need to
145   // match subdomains. (And it'd be bad to check this in that case, as it
146   // prohibits http://localhost/; see https://crbug.com/1142894.)
147   if (check_has_registry_domain) {
148     const bool has_registry_domain =
149         net::registry_controlled_domains::HostHasRegistryControlledDomain(
150             origin.host(),
151             net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
152             net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
153     if (!has_registry_domain)
154       return false;
155   }
156 
157   // For now, disallow hosts with a trailing dot.
158   // TODO(alexmos): Enabling this would require carefully thinking about
159   // whether hosts without a trailing dot should match it.
160   if (origin.host().back() == '.')
161     return false;
162 
163   return true;
164 }
165 
166 }  // namespace content
167