1 // Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "libcef/browser/origin_whitelist_impl.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "include/cef_origin_whitelist.h"
11 #include "libcef/browser/browser_manager.h"
12 #include "libcef/browser/context.h"
13 #include "libcef/browser/thread_util.h"
14
15 #include "base/bind.h"
16 #include "base/lazy_instance.h"
17 #include "base/synchronization/lock.h"
18 #include "cef/libcef/common/mojom/cef.mojom.h"
19 #include "chrome/common/webui_url_constants.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "content/public/common/url_constants.h"
22 #include "extensions/common/constants.h"
23 #include "url/gurl.h"
24 #include "url/origin.h"
25
26 namespace {
27
28 // Class that manages cross-origin whitelist registrations.
29 class CefOriginWhitelistManager {
30 public:
31 CefOriginWhitelistManager() = default;
32
33 CefOriginWhitelistManager(const CefOriginWhitelistManager&) = delete;
34 CefOriginWhitelistManager& operator=(const CefOriginWhitelistManager&) =
35 delete;
36
37 // Retrieve the singleton instance.
38 static CefOriginWhitelistManager* GetInstance();
39
AddOriginEntry(const std::string & source_origin,const std::string & target_protocol,const std::string & target_domain,bool allow_target_subdomains)40 bool AddOriginEntry(const std::string& source_origin,
41 const std::string& target_protocol,
42 const std::string& target_domain,
43 bool allow_target_subdomains) {
44 auto info = cef::mojom::CrossOriginWhiteListEntry::New();
45 info->source_origin = source_origin;
46 info->target_protocol = target_protocol;
47 info->target_domain = target_domain;
48 info->allow_target_subdomains = allow_target_subdomains;
49
50 {
51 base::AutoLock lock_scope(lock_);
52
53 // Verify that the origin entry doesn't already exist.
54 for (const auto& entry : origin_list_) {
55 if (entry == info)
56 return false;
57 }
58
59 origin_list_.push_back(info->Clone());
60 }
61
62 SendModifyCrossOriginWhitelistEntry(true, info);
63 return true;
64 }
65
RemoveOriginEntry(const std::string & source_origin,const std::string & target_protocol,const std::string & target_domain,bool allow_target_subdomains)66 bool RemoveOriginEntry(const std::string& source_origin,
67 const std::string& target_protocol,
68 const std::string& target_domain,
69 bool allow_target_subdomains) {
70 auto info = cef::mojom::CrossOriginWhiteListEntry::New();
71 info->source_origin = source_origin;
72 info->target_protocol = target_protocol;
73 info->target_domain = target_domain;
74 info->allow_target_subdomains = allow_target_subdomains;
75
76 bool found = false;
77
78 {
79 base::AutoLock lock_scope(lock_);
80
81 CrossOriginWhiteList::iterator it = origin_list_.begin();
82 for (; it != origin_list_.end(); ++it) {
83 if (*it == info) {
84 origin_list_.erase(it);
85 found = true;
86 break;
87 }
88 }
89 }
90
91 if (!found)
92 return false;
93
94 SendModifyCrossOriginWhitelistEntry(false, info);
95 return true;
96 }
97
ClearOrigins()98 void ClearOrigins() {
99 {
100 base::AutoLock lock_scope(lock_);
101 origin_list_.clear();
102 }
103
104 SendClearCrossOriginWhitelist();
105 }
106
GetCrossOriginWhitelistEntries(absl::optional<CrossOriginWhiteList> * entries) const107 void GetCrossOriginWhitelistEntries(
108 absl::optional<CrossOriginWhiteList>* entries) const {
109 base::AutoLock lock_scope(lock_);
110
111 if (!origin_list_.empty()) {
112 CrossOriginWhiteList vec;
113 for (const auto& entry : origin_list_) {
114 vec.push_back(entry->Clone());
115 }
116 *entries = std::move(vec);
117 }
118 }
119
HasCrossOriginWhitelistEntry(const url::Origin & source,const url::Origin & target) const120 bool HasCrossOriginWhitelistEntry(const url::Origin& source,
121 const url::Origin& target) const {
122 base::AutoLock lock_scope(lock_);
123
124 if (!origin_list_.empty()) {
125 for (const auto& entry : origin_list_) {
126 if (IsMatch(source, target, entry))
127 return true;
128 }
129 }
130
131 return false;
132 }
133
134 private:
135 // Send the modify cross-origin whitelist entry message to all currently
136 // existing hosts.
SendModifyCrossOriginWhitelistEntry(bool add,const cef::mojom::CrossOriginWhiteListEntryPtr & info)137 static void SendModifyCrossOriginWhitelistEntry(
138 bool add,
139 const cef::mojom::CrossOriginWhiteListEntryPtr& info) {
140 CEF_REQUIRE_UIT();
141
142 content::RenderProcessHost::iterator i(
143 content::RenderProcessHost::AllHostsIterator());
144 for (; !i.IsAtEnd(); i.Advance()) {
145 auto render_manager =
146 CefBrowserManager::GetRenderManagerForProcess(i.GetCurrentValue());
147 render_manager->ModifyCrossOriginWhitelistEntry(add, info->Clone());
148 }
149 }
150
151 // Send the clear cross-origin whitelists message to all currently existing
152 // hosts.
SendClearCrossOriginWhitelist()153 static void SendClearCrossOriginWhitelist() {
154 CEF_REQUIRE_UIT();
155
156 content::RenderProcessHost::iterator i(
157 content::RenderProcessHost::AllHostsIterator());
158 for (; !i.IsAtEnd(); i.Advance()) {
159 auto render_manager =
160 CefBrowserManager::GetRenderManagerForProcess(i.GetCurrentValue());
161 render_manager->ClearCrossOriginWhitelist();
162 }
163 }
164
IsMatch(const url::Origin & source_origin,const url::Origin & target_origin,const cef::mojom::CrossOriginWhiteListEntryPtr & param)165 static bool IsMatch(const url::Origin& source_origin,
166 const url::Origin& target_origin,
167 const cef::mojom::CrossOriginWhiteListEntryPtr& param) {
168 if (!source_origin.IsSameOriginWith(
169 url::Origin::Create(GURL(param->source_origin)))) {
170 // Source origin does not match.
171 return false;
172 }
173
174 if (target_origin.scheme() != param->target_protocol) {
175 // Target scheme does not match.
176 return false;
177 }
178
179 if (param->allow_target_subdomains) {
180 if (param->target_domain.empty()) {
181 // Any domain will match.
182 return true;
183 } else {
184 // Match sub-domains.
185 return target_origin.DomainIs(param->target_domain.c_str());
186 }
187 } else {
188 // Match full domain.
189 return (target_origin.host() == param->target_domain);
190 }
191 }
192
193 mutable base::Lock lock_;
194
195 // List of registered origins. Access must be protected by |lock_|.
196 CrossOriginWhiteList origin_list_;
197 };
198
199 base::LazyInstance<CefOriginWhitelistManager>::Leaky g_manager =
200 LAZY_INSTANCE_INITIALIZER;
201
GetInstance()202 CefOriginWhitelistManager* CefOriginWhitelistManager::GetInstance() {
203 return g_manager.Pointer();
204 }
205
206 } // namespace
207
CefAddCrossOriginWhitelistEntry(const CefString & source_origin,const CefString & target_protocol,const CefString & target_domain,bool allow_target_subdomains)208 bool CefAddCrossOriginWhitelistEntry(const CefString& source_origin,
209 const CefString& target_protocol,
210 const CefString& target_domain,
211 bool allow_target_subdomains) {
212 // Verify that the context is in a valid state.
213 if (!CONTEXT_STATE_VALID()) {
214 NOTREACHED();
215 return false;
216 }
217
218 std::string source_url = source_origin;
219 GURL gurl = GURL(source_url);
220 if (gurl.is_empty() || !gurl.is_valid()) {
221 NOTREACHED() << "Invalid source_origin URL: " << source_url;
222 return false;
223 }
224
225 if (CEF_CURRENTLY_ON_UIT()) {
226 return CefOriginWhitelistManager::GetInstance()->AddOriginEntry(
227 source_origin, target_protocol, target_domain, allow_target_subdomains);
228 } else {
229 CEF_POST_TASK(
230 CEF_UIT,
231 base::BindOnce(base::IgnoreResult(&CefAddCrossOriginWhitelistEntry),
232 source_origin, target_protocol, target_domain,
233 allow_target_subdomains));
234 }
235
236 return true;
237 }
238
CefRemoveCrossOriginWhitelistEntry(const CefString & source_origin,const CefString & target_protocol,const CefString & target_domain,bool allow_target_subdomains)239 bool CefRemoveCrossOriginWhitelistEntry(const CefString& source_origin,
240 const CefString& target_protocol,
241 const CefString& target_domain,
242 bool allow_target_subdomains) {
243 // Verify that the context is in a valid state.
244 if (!CONTEXT_STATE_VALID()) {
245 NOTREACHED();
246 return false;
247 }
248
249 std::string source_url = source_origin;
250 GURL gurl = GURL(source_url);
251 if (gurl.is_empty() || !gurl.is_valid()) {
252 NOTREACHED() << "Invalid source_origin URL: " << source_url;
253 return false;
254 }
255
256 if (CEF_CURRENTLY_ON_UIT()) {
257 return CefOriginWhitelistManager::GetInstance()->RemoveOriginEntry(
258 source_origin, target_protocol, target_domain, allow_target_subdomains);
259 } else {
260 CEF_POST_TASK(
261 CEF_UIT,
262 base::BindOnce(base::IgnoreResult(&CefRemoveCrossOriginWhitelistEntry),
263 source_origin, target_protocol, target_domain,
264 allow_target_subdomains));
265 }
266
267 return true;
268 }
269
CefClearCrossOriginWhitelist()270 bool CefClearCrossOriginWhitelist() {
271 // Verify that the context is in a valid state.
272 if (!CONTEXT_STATE_VALID()) {
273 NOTREACHED();
274 return false;
275 }
276
277 if (CEF_CURRENTLY_ON_UIT()) {
278 CefOriginWhitelistManager::GetInstance()->ClearOrigins();
279 } else {
280 CEF_POST_TASK(
281 CEF_UIT,
282 base::BindOnce(base::IgnoreResult(&CefClearCrossOriginWhitelist)));
283 }
284
285 return true;
286 }
287
GetCrossOriginWhitelistEntries(absl::optional<CrossOriginWhiteList> * entries)288 void GetCrossOriginWhitelistEntries(
289 absl::optional<CrossOriginWhiteList>* entries) {
290 CefOriginWhitelistManager::GetInstance()->GetCrossOriginWhitelistEntries(
291 entries);
292 }
293
HasCrossOriginWhitelistEntry(const url::Origin & source,const url::Origin & target)294 bool HasCrossOriginWhitelistEntry(const url::Origin& source,
295 const url::Origin& target) {
296 // Components of chrome that are implemented as extensions or platform apps
297 // are allowed to use chrome://resources/ and chrome://theme/ URLs.
298 // See also RegisterNonNetworkSubresourceURLLoaderFactories.
299 if (source.scheme() == extensions::kExtensionScheme &&
300 target.scheme() == content::kChromeUIScheme &&
301 (target.host() == chrome::kChromeUIThemeHost ||
302 target.host() == content::kChromeUIResourcesHost)) {
303 return true;
304 }
305
306 return CefOriginWhitelistManager::GetInstance()->HasCrossOriginWhitelistEntry(
307 source, target);
308 }
309