Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/ExtensionPolicyService.h"
7 : #include "mozilla/extensions/WebExtensionContentScript.h"
8 : #include "mozilla/extensions/WebExtensionPolicy.h"
9 :
10 : #include "mozilla/ClearOnShutdown.h"
11 : #include "mozilla/Preferences.h"
12 : #include "mozilla/Services.h"
13 : #include "mozilla/dom/ContentChild.h"
14 : #include "mozilla/dom/ContentParent.h"
15 : #include "mozIExtensionProcessScript.h"
16 : #include "nsEscape.h"
17 : #include "nsGkAtoms.h"
18 : #include "nsIChannel.h"
19 : #include "nsIContentPolicy.h"
20 : #include "nsIDOMDocument.h"
21 : #include "nsIDocument.h"
22 : #include "nsILoadInfo.h"
23 : #include "nsNetUtil.h"
24 : #include "nsPIDOMWindow.h"
25 : #include "nsXULAppAPI.h"
26 :
27 : namespace mozilla {
28 :
29 : using namespace extensions;
30 :
31 : #define DEFAULT_BASE_CSP \
32 : "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
33 : "object-src 'self' https://* moz-extension: blob: filesystem:;"
34 :
35 : #define DEFAULT_DEFAULT_CSP \
36 : "script-src 'self'; object-src 'self';"
37 :
38 :
39 : #define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
40 : #define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script"
41 :
42 :
43 : static mozIExtensionProcessScript&
44 0 : ProcessScript()
45 : {
46 0 : static nsCOMPtr<mozIExtensionProcessScript> sProcessScript;
47 :
48 0 : if (MOZ_UNLIKELY(!sProcessScript)) {
49 0 : sProcessScript = do_GetService("@mozilla.org/webextensions/extension-process-script;1");
50 0 : MOZ_RELEASE_ASSERT(sProcessScript);
51 0 : ClearOnShutdown(&sProcessScript);
52 : }
53 0 : return *sProcessScript;
54 : }
55 :
56 : /*****************************************************************************
57 : * ExtensionPolicyService
58 : *****************************************************************************/
59 :
60 : /* static */ bool ExtensionPolicyService::sRemoteExtensions;
61 :
62 : /* static */ ExtensionPolicyService&
63 0 : ExtensionPolicyService::GetSingleton()
64 : {
65 0 : static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
66 :
67 0 : if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
68 0 : sExtensionPolicyService = new ExtensionPolicyService();
69 0 : ClearOnShutdown(&sExtensionPolicyService);
70 : }
71 0 : return *sExtensionPolicyService.get();
72 : }
73 :
74 0 : ExtensionPolicyService::ExtensionPolicyService()
75 : {
76 0 : mObs = services::GetObserverService();
77 0 : MOZ_RELEASE_ASSERT(mObs);
78 :
79 0 : Preferences::AddBoolVarCache(&sRemoteExtensions, "extensions.webextensions.remote", false);
80 :
81 0 : RegisterObservers();
82 0 : }
83 :
84 :
85 : bool
86 0 : ExtensionPolicyService::IsExtensionProcess() const
87 : {
88 0 : if (sRemoteExtensions && XRE_IsContentProcess()) {
89 0 : auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
90 0 : return remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE);
91 : }
92 0 : return XRE_IsParentProcess();
93 : }
94 :
95 :
96 : WebExtensionPolicy*
97 0 : ExtensionPolicyService::GetByURL(const URLInfo& aURL)
98 : {
99 0 : if (aURL.Scheme() == nsGkAtoms::moz_extension) {
100 0 : return GetByHost(aURL.Host());
101 : }
102 0 : return nullptr;
103 : }
104 :
105 : void
106 0 : ExtensionPolicyService::GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult)
107 : {
108 0 : for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
109 0 : aResult.AppendElement(iter.Data());
110 : }
111 0 : }
112 :
113 : bool
114 0 : ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy)
115 : {
116 0 : bool ok = (!GetByID(aPolicy.Id()) &&
117 0 : !GetByHost(aPolicy.MozExtensionHostname()));
118 0 : MOZ_ASSERT(ok);
119 :
120 0 : if (!ok) {
121 0 : return false;
122 : }
123 :
124 0 : mExtensions.Put(aPolicy.Id(), &aPolicy);
125 0 : mExtensionHosts.Put(aPolicy.MozExtensionHostname(), &aPolicy);
126 0 : return true;
127 : }
128 :
129 : bool
130 0 : ExtensionPolicyService::UnregisterExtension(WebExtensionPolicy& aPolicy)
131 : {
132 0 : bool ok = (GetByID(aPolicy.Id()) == &aPolicy &&
133 0 : GetByHost(aPolicy.MozExtensionHostname()) == &aPolicy);
134 0 : MOZ_ASSERT(ok);
135 :
136 0 : if (!ok) {
137 0 : return false;
138 : }
139 :
140 0 : mExtensions.Remove(aPolicy.Id());
141 0 : mExtensionHosts.Remove(aPolicy.MozExtensionHostname());
142 0 : return true;
143 : }
144 :
145 :
146 : void
147 0 : ExtensionPolicyService::BaseCSP(nsAString& aBaseCSP) const
148 : {
149 : nsresult rv;
150 :
151 0 : rv = Preferences::GetString("extensions.webextensions.base-content-security-policy", &aBaseCSP);
152 0 : if (NS_FAILED(rv)) {
153 0 : aBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
154 : }
155 0 : }
156 :
157 : void
158 0 : ExtensionPolicyService::DefaultCSP(nsAString& aDefaultCSP) const
159 : {
160 : nsresult rv;
161 :
162 0 : rv = Preferences::GetString("extensions.webextensions.default-content-security-policy", &aDefaultCSP);
163 0 : if (NS_FAILED(rv)) {
164 0 : aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
165 : }
166 0 : }
167 :
168 :
169 : /*****************************************************************************
170 : * Content script management
171 : *****************************************************************************/
172 :
173 : void
174 0 : ExtensionPolicyService::RegisterObservers()
175 : {
176 0 : mObs->AddObserver(this, "content-document-global-created", false);
177 0 : mObs->AddObserver(this, "document-element-inserted", false);
178 0 : if (XRE_IsContentProcess()) {
179 0 : mObs->AddObserver(this, "http-on-opening-request", false);
180 : }
181 0 : }
182 :
183 : void
184 0 : ExtensionPolicyService::UnregisterObservers()
185 : {
186 0 : mObs->RemoveObserver(this, "content-document-global-created");
187 0 : mObs->RemoveObserver(this, "document-element-inserted");
188 0 : if (XRE_IsContentProcess()) {
189 0 : mObs->RemoveObserver(this, "http-on-opening-request");
190 : }
191 0 : }
192 :
193 : nsresult
194 0 : ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
195 : {
196 0 : if (!strcmp(aTopic, "content-document-global-created")) {
197 0 : nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(aSubject);
198 0 : if (win) {
199 0 : CheckWindow(win);
200 : }
201 0 : } else if (!strcmp(aTopic, "document-element-inserted")) {
202 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aSubject);
203 0 : if (doc) {
204 0 : CheckDocument(doc);
205 : }
206 0 : } else if (!strcmp(aTopic, "http-on-opening-request")) {
207 0 : nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject);
208 0 : if (chan) {
209 0 : CheckRequest(chan);
210 : }
211 : }
212 0 : return NS_OK;
213 : }
214 :
215 : // Checks a request for matching content scripts, and begins pre-loading them
216 : // if necessary.
217 : void
218 0 : ExtensionPolicyService::CheckRequest(nsIChannel* aChannel)
219 : {
220 0 : nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
221 0 : if (!loadInfo) {
222 0 : return;
223 : }
224 :
225 0 : auto loadType = loadInfo->GetExternalContentPolicyType();
226 0 : if (loadType != nsIContentPolicy::TYPE_DOCUMENT &&
227 : loadType != nsIContentPolicy::TYPE_SUBDOCUMENT) {
228 0 : return;
229 : }
230 :
231 0 : nsCOMPtr<nsIURI> uri;
232 0 : if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) {
233 0 : return;
234 : }
235 :
236 0 : CheckContentScripts({uri.get(), loadInfo}, true);
237 : }
238 :
239 : // Checks a document, just after the document element has been inserted, for
240 : // matching content scripts or extension principals, and loads them if
241 : // necessary.
242 : void
243 0 : ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
244 : {
245 0 : nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
246 0 : if (win) {
247 0 : if (win->GetDocumentURI()) {
248 0 : CheckContentScripts(win.get(), false);
249 : }
250 :
251 0 : nsIPrincipal* principal = aDocument->NodePrincipal();
252 :
253 0 : nsAutoString addonId;
254 0 : Unused << principal->GetAddonId(addonId);
255 :
256 0 : RefPtr<WebExtensionPolicy> policy = GetByID(addonId);
257 0 : if (policy) {
258 0 : nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
259 0 : ProcessScript().InitExtensionDocument(policy, doc);
260 : }
261 : }
262 0 : }
263 :
264 : // Checks for loads of about:blank into new window globals, and loads any
265 : // matching content scripts. about:blank loads do not trigger document element
266 : // inserted events, so they're the only load type that are special cased this
267 : // way.
268 : void
269 0 : ExtensionPolicyService::CheckWindow(nsPIDOMWindowOuter* aWindow)
270 : {
271 : // We only care about non-initial document loads here. The initial
272 : // about:blank document will usually be re-used to load another document.
273 0 : nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
274 0 : if (!doc || doc->IsInitialDocument() ||
275 0 : doc->GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED) {
276 0 : return;
277 : }
278 :
279 0 : nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
280 0 : nsCOMPtr<nsIURI> uri;
281 0 : if (!docUri || NS_FAILED(docUri->CloneIgnoringRef(getter_AddRefs(uri))) ||
282 0 : !NS_IsAboutBlank(uri)) {
283 0 : return;
284 : }
285 :
286 0 : CheckContentScripts(aWindow, false);
287 : }
288 :
289 : void
290 0 : ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
291 : {
292 0 : for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
293 0 : RefPtr<WebExtensionPolicy> policy = iter.Data();
294 :
295 0 : for (auto& script : policy->ContentScripts()) {
296 0 : if (script->Matches(aDocInfo)) {
297 0 : if (aIsPreload) {
298 0 : ProcessScript().PreloadContentScript(script);
299 : } else {
300 0 : ProcessScript().LoadContentScript(script, aDocInfo.GetWindow());
301 : }
302 : }
303 : }
304 : }
305 0 : }
306 :
307 :
308 : /*****************************************************************************
309 : * nsIAddonPolicyService
310 : *****************************************************************************/
311 :
312 : nsresult
313 0 : ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP)
314 : {
315 0 : BaseCSP(aBaseCSP);
316 0 : return NS_OK;
317 : }
318 :
319 : nsresult
320 0 : ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP)
321 : {
322 0 : DefaultCSP(aDefaultCSP);
323 0 : return NS_OK;
324 : }
325 :
326 : nsresult
327 0 : ExtensionPolicyService::GetAddonCSP(const nsAString& aAddonId,
328 : nsAString& aResult)
329 : {
330 0 : if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
331 0 : policy->GetContentSecurityPolicy(aResult);
332 0 : return NS_OK;
333 : }
334 0 : return NS_ERROR_INVALID_ARG;
335 : }
336 :
337 : nsresult
338 0 : ExtensionPolicyService::GetGeneratedBackgroundPageUrl(const nsACString& aHostname,
339 : nsACString& aResult)
340 : {
341 0 : if (WebExtensionPolicy* policy = GetByHost(aHostname)) {
342 0 : nsAutoCString url("data:text/html,");
343 :
344 0 : nsCString html = policy->BackgroundPageHTML();
345 0 : nsAutoCString escaped;
346 :
347 0 : url.Append(NS_EscapeURL(html, esc_Minimal, escaped));
348 :
349 0 : aResult = url;
350 0 : return NS_OK;
351 : }
352 0 : return NS_ERROR_INVALID_ARG;
353 : }
354 :
355 : nsresult
356 0 : ExtensionPolicyService::AddonHasPermission(const nsAString& aAddonId,
357 : const nsAString& aPerm,
358 : bool* aResult)
359 : {
360 0 : if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
361 0 : *aResult = policy->HasPermission(aPerm);
362 0 : return NS_OK;
363 : }
364 0 : return NS_ERROR_INVALID_ARG;
365 : }
366 :
367 : nsresult
368 0 : ExtensionPolicyService::AddonMayLoadURI(const nsAString& aAddonId,
369 : nsIURI* aURI,
370 : bool aExplicit,
371 : bool* aResult)
372 : {
373 0 : if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
374 0 : *aResult = policy->CanAccessURI(aURI, aExplicit);
375 0 : return NS_OK;
376 : }
377 0 : return NS_ERROR_INVALID_ARG;
378 : }
379 :
380 : nsresult
381 0 : ExtensionPolicyService::ExtensionURILoadableByAnyone(nsIURI* aURI, bool* aResult)
382 : {
383 0 : URLInfo url(aURI);
384 0 : if (WebExtensionPolicy* policy = GetByURL(url)) {
385 0 : *aResult = policy->IsPathWebAccessible(url.FilePath());
386 0 : return NS_OK;
387 : }
388 0 : return NS_ERROR_INVALID_ARG;
389 : }
390 :
391 : nsresult
392 0 : ExtensionPolicyService::ExtensionURIToAddonId(nsIURI* aURI, nsAString& aResult)
393 : {
394 0 : if (WebExtensionPolicy* policy = GetByURL(aURI)) {
395 0 : policy->GetId(aResult);
396 : } else {
397 0 : aResult.SetIsVoid(true);
398 : }
399 0 : return NS_OK;
400 : }
401 :
402 :
403 0 : NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts)
404 :
405 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
406 0 : NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
407 0 : NS_INTERFACE_MAP_ENTRY(nsIObserver)
408 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService)
409 0 : NS_INTERFACE_MAP_END
410 :
411 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
412 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
413 :
414 : } // namespace mozilla
|