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/AddonManagerWebAPI.h"
11 : #include "nsEscape.h"
12 : #include "nsISubstitutingProtocolHandler.h"
13 : #include "nsNetUtil.h"
14 : #include "nsPrintfCString.h"
15 :
16 : namespace mozilla {
17 : namespace extensions {
18 :
19 : using namespace dom;
20 :
21 : static inline Result<Ok, nsresult>
22 : WrapNSResult(PRStatus aRv)
23 : {
24 : if (aRv != PR_SUCCESS) {
25 : return Err(NS_ERROR_FAILURE);
26 : }
27 : return Ok();
28 : }
29 :
30 : static inline Result<Ok, nsresult>
31 0 : WrapNSResult(nsresult aRv)
32 : {
33 0 : if (NS_FAILED(aRv)) {
34 0 : return Err(aRv);
35 : }
36 0 : return Ok();
37 : }
38 :
39 : #define NS_TRY(expr) MOZ_TRY(WrapNSResult(expr))
40 :
41 : static const char kProto[] = "moz-extension";
42 :
43 : static const char kBackgroundPageHTMLStart[] = "<!DOCTYPE html>\n\
44 : <html>\n\
45 : <head><meta charset=\"utf-8\"></head>\n\
46 : <body>";
47 :
48 : static const char kBackgroundPageHTMLScript[] = "\n\
49 : <script type=\"text/javascript\" src=\"%s\"></script>";
50 :
51 : static const char kBackgroundPageHTMLEnd[] = "\n\
52 : <body>\n\
53 : </html>";
54 :
55 0 : class EscapeHTML final : public nsAdoptingCString
56 : {
57 : public:
58 0 : explicit EscapeHTML(const nsACString& str)
59 0 : : nsAdoptingCString(nsEscapeHTML(str.BeginReading()))
60 0 : {}
61 : };
62 :
63 :
64 : static inline ExtensionPolicyService&
65 0 : EPS()
66 : {
67 0 : return ExtensionPolicyService::GetSingleton();
68 : }
69 :
70 : static nsISubstitutingProtocolHandler*
71 0 : Proto()
72 : {
73 0 : static nsCOMPtr<nsISubstitutingProtocolHandler> sHandler;
74 :
75 0 : if (MOZ_UNLIKELY(!sHandler)) {
76 0 : nsCOMPtr<nsIIOService> ios = do_GetIOService();
77 0 : MOZ_RELEASE_ASSERT(ios);
78 :
79 0 : nsCOMPtr<nsIProtocolHandler> handler;
80 0 : ios->GetProtocolHandler(kProto, getter_AddRefs(handler));
81 :
82 0 : sHandler = do_QueryInterface(handler);
83 0 : MOZ_RELEASE_ASSERT(sHandler);
84 :
85 0 : ClearOnShutdown(&sHandler);
86 : }
87 :
88 0 : return sHandler;
89 : }
90 :
91 :
92 : /*****************************************************************************
93 : * WebExtensionPolicy
94 : *****************************************************************************/
95 :
96 0 : WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
97 : const WebExtensionInit& aInit,
98 0 : ErrorResult& aRv)
99 0 : : mId(NS_AtomizeMainThread(aInit.mId))
100 : , mHostname(aInit.mMozExtensionHostname)
101 : , mContentSecurityPolicy(aInit.mContentSecurityPolicy)
102 : , mLocalizeCallback(aInit.mLocalizeCallback)
103 0 : , mPermissions(new AtomSet(aInit.mPermissions))
104 0 : , mHostPermissions(aInit.mAllowedOrigins)
105 : {
106 0 : mWebAccessiblePaths.AppendElements(aInit.mWebAccessibleResources);
107 :
108 0 : if (!aInit.mBackgroundScripts.IsNull()) {
109 0 : mBackgroundScripts.SetValue().AppendElements(aInit.mBackgroundScripts.Value());
110 : }
111 :
112 0 : if (mContentSecurityPolicy.IsVoid()) {
113 0 : EPS().DefaultCSP(mContentSecurityPolicy);
114 : }
115 :
116 0 : mContentScripts.SetCapacity(aInit.mContentScripts.Length());
117 0 : for (const auto& scriptInit : aInit.mContentScripts) {
118 : RefPtr<WebExtensionContentScript> contentScript =
119 0 : new WebExtensionContentScript(*this, scriptInit, aRv);
120 0 : if (aRv.Failed()) {
121 0 : return;
122 : }
123 0 : mContentScripts.AppendElement(Move(contentScript));
124 : }
125 :
126 0 : nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
127 0 : if (NS_FAILED(rv)) {
128 0 : aRv.Throw(rv);
129 : }
130 : }
131 :
132 : already_AddRefed<WebExtensionPolicy>
133 0 : WebExtensionPolicy::Constructor(GlobalObject& aGlobal,
134 : const WebExtensionInit& aInit,
135 : ErrorResult& aRv)
136 : {
137 0 : RefPtr<WebExtensionPolicy> policy = new WebExtensionPolicy(aGlobal, aInit, aRv);
138 0 : if (aRv.Failed()) {
139 0 : return nullptr;
140 : }
141 0 : return policy.forget();
142 : }
143 :
144 :
145 : /* static */ void
146 0 : WebExtensionPolicy::GetActiveExtensions(dom::GlobalObject& aGlobal,
147 : nsTArray<RefPtr<WebExtensionPolicy>>& aResults)
148 : {
149 0 : EPS().GetAll(aResults);
150 0 : }
151 :
152 : /* static */ already_AddRefed<WebExtensionPolicy>
153 0 : WebExtensionPolicy::GetByID(dom::GlobalObject& aGlobal, const nsAString& aID)
154 : {
155 0 : return do_AddRef(EPS().GetByID(aID));
156 : }
157 :
158 : /* static */ already_AddRefed<WebExtensionPolicy>
159 0 : WebExtensionPolicy::GetByHostname(dom::GlobalObject& aGlobal, const nsACString& aHostname)
160 : {
161 0 : return do_AddRef(EPS().GetByHost(aHostname));
162 : }
163 :
164 : /* static */ already_AddRefed<WebExtensionPolicy>
165 0 : WebExtensionPolicy::GetByURI(dom::GlobalObject& aGlobal, nsIURI* aURI)
166 : {
167 0 : return do_AddRef(EPS().GetByURL(aURI));
168 : }
169 :
170 :
171 : void
172 0 : WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv)
173 : {
174 0 : if (aActive == mActive) {
175 0 : return;
176 : }
177 :
178 0 : bool ok = aActive ? Enable() : Disable();
179 :
180 0 : if (!ok) {
181 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
182 : }
183 : }
184 :
185 : bool
186 0 : WebExtensionPolicy::Enable()
187 : {
188 0 : MOZ_ASSERT(!mActive);
189 :
190 0 : if (!EPS().RegisterExtension(*this)) {
191 0 : return false;
192 : }
193 :
194 0 : Unused << Proto()->SetSubstitution(MozExtensionHostname(), mBaseURI);
195 :
196 0 : mActive = true;
197 0 : return true;
198 : }
199 :
200 : bool
201 0 : WebExtensionPolicy::Disable()
202 : {
203 0 : MOZ_ASSERT(mActive);
204 0 : MOZ_ASSERT(EPS().GetByID(Id()) == this);
205 :
206 0 : if (!EPS().UnregisterExtension(*this)) {
207 0 : return false;
208 : }
209 :
210 0 : Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
211 :
212 0 : mActive = false;
213 0 : return true;
214 : }
215 :
216 : void
217 0 : WebExtensionPolicy::GetURL(const nsAString& aPath,
218 : nsAString& aResult,
219 : ErrorResult& aRv) const
220 : {
221 0 : auto result = GetURL(aPath);
222 0 : if (result.isOk()) {
223 0 : aResult = result.unwrap();
224 : } else {
225 0 : aRv.Throw(result.unwrapErr());
226 : }
227 0 : }
228 :
229 : Result<nsString, nsresult>
230 0 : WebExtensionPolicy::GetURL(const nsAString& aPath) const
231 : {
232 0 : nsPrintfCString spec("%s://%s/", kProto, mHostname.get());
233 :
234 0 : nsCOMPtr<nsIURI> uri;
235 0 : NS_TRY(NS_NewURI(getter_AddRefs(uri), spec));
236 :
237 0 : NS_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
238 :
239 0 : return NS_ConvertUTF8toUTF16(spec);
240 : }
241 :
242 : /* static */ bool
243 0 : WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal)
244 : {
245 0 : return EPS().IsExtensionProcess();
246 : }
247 :
248 : nsCString
249 0 : WebExtensionPolicy::BackgroundPageHTML() const
250 : {
251 0 : nsAutoCString result;
252 :
253 0 : if (mBackgroundScripts.IsNull()) {
254 0 : result.SetIsVoid(true);
255 0 : return result;
256 : }
257 :
258 0 : result.AppendLiteral(kBackgroundPageHTMLStart);
259 :
260 0 : for (auto& script : mBackgroundScripts.Value()) {
261 0 : EscapeHTML escaped{NS_ConvertUTF16toUTF8(script)};
262 :
263 0 : result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
264 : }
265 :
266 0 : result.AppendLiteral(kBackgroundPageHTMLEnd);
267 0 : return result;
268 : }
269 :
270 : void
271 0 : WebExtensionPolicy::Localize(const nsAString& aInput, nsString& aOutput) const
272 : {
273 0 : mLocalizeCallback->Call(aInput, aOutput);
274 0 : }
275 :
276 :
277 : JSObject*
278 0 : WebExtensionPolicy::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
279 : {
280 0 : return WebExtensionPolicyBinding::Wrap(aCx, this, aGivenProto);
281 : }
282 :
283 : void
284 0 : WebExtensionPolicy::GetContentScripts(nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const
285 : {
286 0 : aScripts.AppendElements(mContentScripts);
287 0 : }
288 :
289 :
290 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionPolicy, mParent,
291 : mLocalizeCallback,
292 : mHostPermissions,
293 : mWebAccessiblePaths,
294 : mContentScripts)
295 :
296 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
297 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
298 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
299 0 : NS_INTERFACE_MAP_END
300 :
301 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
302 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
303 :
304 :
305 : /*****************************************************************************
306 : * WebExtensionContentScript
307 : *****************************************************************************/
308 :
309 : /* static */ already_AddRefed<WebExtensionContentScript>
310 0 : WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
311 : WebExtensionPolicy& aExtension,
312 : const ContentScriptInit& aInit,
313 : ErrorResult& aRv)
314 : {
315 0 : RefPtr<WebExtensionContentScript> script = new WebExtensionContentScript(aExtension, aInit, aRv);
316 0 : if (aRv.Failed()) {
317 0 : return nullptr;
318 : }
319 0 : return script.forget();
320 : }
321 :
322 0 : WebExtensionContentScript::WebExtensionContentScript(WebExtensionPolicy& aExtension,
323 : const ContentScriptInit& aInit,
324 0 : ErrorResult& aRv)
325 : : mExtension(&aExtension)
326 : , mMatches(aInit.mMatches)
327 : , mExcludeMatches(aInit.mExcludeMatches)
328 : , mCssPaths(aInit.mCssPaths)
329 : , mJsPaths(aInit.mJsPaths)
330 0 : , mRunAt(aInit.mRunAt)
331 0 : , mAllFrames(aInit.mAllFrames)
332 : , mFrameID(aInit.mFrameID)
333 0 : , mMatchAboutBlank(aInit.mMatchAboutBlank)
334 : {
335 0 : if (!aInit.mIncludeGlobs.IsNull()) {
336 0 : mIncludeGlobs.SetValue().AppendElements(aInit.mIncludeGlobs.Value());
337 : }
338 :
339 0 : if (!aInit.mExcludeGlobs.IsNull()) {
340 0 : mExcludeGlobs.SetValue().AppendElements(aInit.mExcludeGlobs.Value());
341 : }
342 0 : }
343 :
344 :
345 : bool
346 0 : WebExtensionContentScript::Matches(const DocInfo& aDoc) const
347 : {
348 0 : if (!mFrameID.IsNull()) {
349 0 : if (aDoc.FrameID() != mFrameID.Value()) {
350 0 : return false;
351 : }
352 : } else {
353 0 : if (!mAllFrames && !aDoc.IsTopLevel()) {
354 0 : return false;
355 : }
356 : }
357 :
358 0 : if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
359 0 : return false;
360 : }
361 :
362 : // Top-level about:blank is a special case. We treat it as a match if
363 : // matchAboutBlank is true and it has the null principal. In all other
364 : // cases, we test the URL of the principal that it inherits.
365 0 : if (mMatchAboutBlank && aDoc.IsTopLevel() &&
366 0 : aDoc.URL().Spec().EqualsLiteral("about:blank") &&
367 0 : aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
368 0 : return true;
369 : }
370 :
371 : // With the exception of top-level about:blank documents with null
372 : // principals, we never match documents that have non-codebase principals,
373 : // including those with null principals or system principals.
374 0 : if (aDoc.Principal() && !aDoc.Principal()->GetIsCodebasePrincipal()) {
375 0 : return false;
376 : }
377 :
378 0 : return MatchesURI(aDoc.PrincipalURL());
379 : }
380 :
381 : bool
382 0 : WebExtensionContentScript::MatchesURI(const URLInfo& aURL) const
383 : {
384 0 : if (!mMatches->Matches(aURL)) {
385 0 : return false;
386 : }
387 :
388 0 : if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
389 0 : return false;
390 : }
391 :
392 0 : if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
393 0 : return false;
394 : }
395 :
396 0 : if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
397 0 : return false;
398 : }
399 :
400 0 : if (AddonManagerWebAPI::IsValidSite(aURL.URI())) {
401 0 : return false;
402 : }
403 :
404 0 : return true;
405 : }
406 :
407 :
408 : JSObject*
409 0 : WebExtensionContentScript::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
410 : {
411 0 : return WebExtensionContentScriptBinding::Wrap(aCx, this, aGivenProto);
412 : }
413 :
414 :
415 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebExtensionContentScript,
416 : mMatches, mExcludeMatches,
417 : mIncludeGlobs, mExcludeGlobs,
418 : mExtension)
419 :
420 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionContentScript)
421 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
422 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
423 0 : NS_INTERFACE_MAP_END
424 :
425 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionContentScript)
426 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionContentScript)
427 :
428 :
429 : /*****************************************************************************
430 : * DocInfo
431 : *****************************************************************************/
432 :
433 0 : DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
434 : : mURL(aURL)
435 0 : , mObj(AsVariant(aLoadInfo))
436 0 : {}
437 :
438 0 : DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
439 : : mURL(aWindow->GetDocumentURI())
440 0 : , mObj(AsVariant(aWindow))
441 0 : {}
442 :
443 : bool
444 0 : DocInfo::IsTopLevel() const
445 : {
446 0 : if (mIsTopLevel.isNothing()) {
447 : struct Matcher
448 : {
449 0 : bool match(Window aWin) { return aWin->IsTopLevelWindow(); }
450 0 : bool match(LoadInfo aLoadInfo) { return aLoadInfo->GetIsTopLevelLoad(); }
451 : };
452 0 : mIsTopLevel.emplace(mObj.match(Matcher()));
453 : }
454 0 : return mIsTopLevel.ref();
455 : }
456 :
457 : uint64_t
458 0 : DocInfo::FrameID() const
459 : {
460 0 : if (mFrameID.isNothing()) {
461 0 : if (IsTopLevel()) {
462 0 : mFrameID.emplace(0);
463 : } else {
464 : struct Matcher
465 : {
466 0 : uint64_t match(Window aWin) { return aWin->WindowID(); }
467 0 : uint64_t match(LoadInfo aLoadInfo) { return aLoadInfo->GetOuterWindowID(); }
468 : };
469 0 : mFrameID.emplace(mObj.match(Matcher()));
470 : }
471 : }
472 0 : return mFrameID.ref();
473 : }
474 :
475 : nsIPrincipal*
476 0 : DocInfo::Principal() const
477 : {
478 0 : if (mPrincipal.isNothing()) {
479 : struct Matcher
480 : {
481 0 : explicit Matcher(const DocInfo& aThis) : mThis(aThis) {}
482 : const DocInfo& mThis;
483 :
484 0 : nsIPrincipal* match(Window aWin)
485 : {
486 0 : nsCOMPtr<nsIDocument> doc = aWin->GetDoc();
487 0 : return doc->NodePrincipal();
488 : }
489 0 : nsIPrincipal* match(LoadInfo aLoadInfo)
490 : {
491 0 : if (!(mThis.URL().InheritsPrincipal() || aLoadInfo->GetForceInheritPrincipal())) {
492 0 : return nullptr;
493 : }
494 0 : if (auto principal = aLoadInfo->PrincipalToInherit()) {
495 0 : return principal;
496 : }
497 0 : return aLoadInfo->TriggeringPrincipal();
498 : }
499 : };
500 0 : mPrincipal.emplace(mObj.match(Matcher(*this)));
501 : }
502 0 : return mPrincipal.ref();
503 : }
504 :
505 : const URLInfo&
506 0 : DocInfo::PrincipalURL() const
507 : {
508 0 : if (!URL().InheritsPrincipal() ||
509 0 : !(Principal() && Principal()->GetIsCodebasePrincipal())) {
510 0 : return URL();
511 : }
512 :
513 0 : if (mPrincipalURL.isNothing()) {
514 0 : nsIPrincipal* prin = Principal();
515 0 : nsCOMPtr<nsIURI> uri;
516 0 : if (NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
517 0 : MOZ_DIAGNOSTIC_ASSERT(uri);
518 0 : mPrincipalURL.emplace(uri);
519 : } else {
520 0 : mPrincipalURL.emplace(URL());
521 : }
522 : }
523 :
524 0 : return mPrincipalURL.ref();
525 : }
526 :
527 : } // namespace extensions
528 : } // namespace mozilla
|