1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/cache/CacheStorage.h"
9 #include "mozilla/Preferences.h"
10 #include "mozilla/Unused.h"
11 #include "mozilla/dom/CacheBinding.h"
12 #include "mozilla/dom/CacheStorageBinding.h"
13 #include "mozilla/dom/InternalRequest.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/Response.h"
16 #include "mozilla/dom/cache/AutoUtils.h"
17 #include "mozilla/dom/cache/Cache.h"
18 #include "mozilla/dom/cache/CacheChild.h"
19 #include "mozilla/dom/cache/CacheCommon.h"
20 #include "mozilla/dom/cache/CacheStorageChild.h"
21 #include "mozilla/dom/cache/CacheWorkerRef.h"
22 #include "mozilla/dom/cache/PCacheChild.h"
23 #include "mozilla/dom/cache/ReadStream.h"
24 #include "mozilla/dom/cache/TypeUtils.h"
25 #include "mozilla/dom/quota/PrincipalUtils.h"
26 #include "mozilla/dom/quota/ResultExtensions.h"
27 #include "mozilla/dom/WorkerPrivate.h"
28 #include "mozilla/ipc/BackgroundChild.h"
29 #include "mozilla/ipc/BackgroundUtils.h"
30 #include "mozilla/ipc/PBackgroundChild.h"
31 #include "mozilla/ipc/PBackgroundSharedTypes.h"
32 #include "mozilla/StaticPrefs_dom.h"
33 #include "mozilla/StaticPrefs_extensions.h"
34 #include "nsContentUtils.h"
35 #include "mozilla/dom/Document.h"
36 #include "nsIGlobalObject.h"
37 #include "nsMixedContentBlocker.h"
38 #include "nsURLParsers.h"
39 #include "js/Object.h" // JS::GetClass
40 #include "js/PropertyAndElement.h" // JS_DefineProperty
42 namespace mozilla::dom::cache
{
44 using mozilla::ErrorResult
;
45 using mozilla::ipc::BackgroundChild
;
46 using mozilla::ipc::PBackgroundChild
;
47 using mozilla::ipc::PrincipalInfo
;
48 using mozilla::ipc::PrincipalToPrincipalInfo
;
50 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage
);
51 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage
);
52 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::CacheStorage
,
55 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage
)
56 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
57 NS_INTERFACE_MAP_ENTRY(nsISupports
)
60 // We cannot reference IPC types in a webidl binding implementation header. So
61 // define this in the .cpp.
62 struct CacheStorage::Entry final
{
63 RefPtr
<Promise
> mPromise
;
65 // We cannot add the requests until after the actor is present. So store
66 // the request data separately for now.
67 SafeRefPtr
<InternalRequest
> mRequest
;
72 bool IsTrusted(const PrincipalInfo
& aPrincipalInfo
, bool aTestingPrefEnabled
) {
73 // Can happen on main thread or worker thread
75 if (aPrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
) {
79 // Require a ContentPrincipal to avoid null principal, etc.
80 QM_TRY(OkIf(aPrincipalInfo
.type() == PrincipalInfo::TContentPrincipalInfo
),
83 // If we're in testing mode, then don't do any more work to determine if
84 // the origin is trusted. We have to run some tests as http.
85 if (aTestingPrefEnabled
) {
89 // Now parse the scheme of the principal's origin. This is a short term
90 // method for determining "trust". In the long term we need to implement
91 // the full algorithm here:
93 // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure
95 // TODO: Implement full secure setting algorithm. (bug 1177856)
97 const nsCString
& flatURL
= aPrincipalInfo
.get_ContentPrincipalInfo().spec();
98 const char* const url
= flatURL
.get();
100 // off the main thread URL parsing using nsStdURLParser.
101 const nsCOMPtr
<nsIURLParser
> urlParser
= new nsStdURLParser();
107 QM_TRY(MOZ_TO_RESULT(urlParser
->ParseURL(url
, flatURL
.Length(), &schemePos
,
108 &schemeLen
, &authPos
, &authLen
,
109 nullptr, nullptr)), // ignore path
112 const nsAutoCString
scheme(Substring(flatURL
, schemePos
, schemeLen
));
113 if (scheme
.LowerCaseEqualsLiteral("https") ||
114 scheme
.LowerCaseEqualsLiteral("file") ||
115 scheme
.LowerCaseEqualsLiteral("moz-extension")) {
121 QM_TRY(MOZ_TO_RESULT(
122 urlParser
->ParseAuthority(url
+ authPos
, authLen
, nullptr,
123 nullptr, // ignore username
124 nullptr, nullptr, // ignore password
126 nullptr)), // ignore port
129 return nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
130 nsDependentCSubstring(url
+ authPos
+ hostPos
, hostLen
));
136 already_AddRefed
<CacheStorage
> CacheStorage::CreateOnMainThread(
137 Namespace aNamespace
, nsIGlobalObject
* aGlobal
, nsIPrincipal
* aPrincipal
,
138 bool aForceTrustedOrigin
, ErrorResult
& aRv
) {
139 MOZ_DIAGNOSTIC_ASSERT(aGlobal
);
140 MOZ_DIAGNOSTIC_ASSERT(aPrincipal
);
141 MOZ_ASSERT(NS_IsMainThread());
143 PrincipalInfo principalInfo
;
144 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
)),
145 nullptr, [&aRv
](const nsresult rv
) { aRv
.Throw(rv
); });
147 QM_TRY(OkIf(quota::IsPrincipalInfoValid(principalInfo
)),
148 RefPtr
{new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
)}.forget(),
150 NS_WARNING("CacheStorage not supported on invalid origins.");
153 const bool testingEnabled
=
154 aForceTrustedOrigin
||
155 Preferences::GetBool("dom.caches.testing.enabled", false) ||
156 StaticPrefs::dom_serviceWorkers_testing_enabled();
158 if (!IsTrusted(principalInfo
, testingEnabled
)) {
159 NS_WARNING("CacheStorage not supported on untrusted origins.");
160 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
164 RefPtr
<CacheStorage
> ref
=
165 new CacheStorage(aNamespace
, aGlobal
, principalInfo
, nullptr);
170 already_AddRefed
<CacheStorage
> CacheStorage::CreateOnWorker(
171 Namespace aNamespace
, nsIGlobalObject
* aGlobal
,
172 WorkerPrivate
* aWorkerPrivate
, ErrorResult
& aRv
) {
173 MOZ_DIAGNOSTIC_ASSERT(aGlobal
);
174 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate
);
175 aWorkerPrivate
->AssertIsOnWorkerThread();
177 if (aWorkerPrivate
->GetOriginAttributes().IsPrivateBrowsing() &&
178 !StaticPrefs::dom_cache_privateBrowsing_enabled()) {
179 NS_WARNING("CacheStorage not supported during private browsing.");
180 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
184 SafeRefPtr
<CacheWorkerRef
> workerRef
=
185 CacheWorkerRef::Create(aWorkerPrivate
, CacheWorkerRef::eIPCWorkerRef
);
187 NS_WARNING("Worker thread is shutting down.");
188 aRv
.Throw(NS_ERROR_FAILURE
);
192 const PrincipalInfo
& principalInfo
=
193 aWorkerPrivate
->GetEffectiveStoragePrincipalInfo();
195 QM_TRY(OkIf(quota::IsPrincipalInfoValid(principalInfo
)), nullptr,
196 [&aRv
](const auto) { aRv
.Throw(NS_ERROR_FAILURE
); });
198 // We have a number of cases where we want to skip the https scheme
201 // 1) Any worker when dom.caches.testing.enabled pref is true.
202 // 2) Any worker when dom.serviceWorkers.testing.enabled pref is true. This
203 // is mainly because most sites using SWs will expect Cache to work if
205 // 3) If the window that created this worker has the devtools SW testing
206 // option enabled. Same reasoning as (2).
207 // 4) If the worker itself is a ServiceWorker, then we always skip the
208 // origin checks. The ServiceWorker has its own trusted origin checks
209 // that are better than ours. In addition, we don't have information
210 // about the window any more, so we can't do our own checks.
211 bool testingEnabled
= StaticPrefs::dom_caches_testing_enabled() ||
212 StaticPrefs::dom_serviceWorkers_testing_enabled() ||
213 aWorkerPrivate
->ServiceWorkersTestingInWindow() ||
214 aWorkerPrivate
->IsServiceWorker();
216 if (!IsTrusted(principalInfo
, testingEnabled
)) {
217 NS_WARNING("CacheStorage not supported on untrusted origins.");
218 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
222 RefPtr
<CacheStorage
> ref
= new CacheStorage(
223 aNamespace
, aGlobal
, principalInfo
, std::move(workerRef
));
228 bool CacheStorage::DefineCachesForSandbox(JSContext
* aCx
,
229 JS::Handle
<JSObject
*> aGlobal
) {
230 MOZ_ASSERT(NS_IsMainThread());
231 MOZ_DIAGNOSTIC_ASSERT(JS::GetClass(aGlobal
)->flags
& JSCLASS_DOM_GLOBAL
,
232 "Passed object is not a global object!");
233 js::AssertSameCompartment(aCx
, aGlobal
);
235 if (NS_WARN_IF(!CacheStorage_Binding::CreateAndDefineOnGlobal(aCx
) ||
236 !Cache_Binding::CreateAndDefineOnGlobal(aCx
))) {
240 nsIPrincipal
* principal
= nsContentUtils::ObjectPrincipal(aGlobal
);
241 MOZ_DIAGNOSTIC_ASSERT(principal
);
244 RefPtr
<CacheStorage
> storage
=
245 CreateOnMainThread(DEFAULT_NAMESPACE
, xpc::NativeGlobal(aGlobal
),
246 principal
, true, /* force trusted */
248 if (NS_WARN_IF(rv
.MaybeSetPendingException(aCx
))) {
252 JS::Rooted
<JS::Value
> caches(aCx
);
253 if (NS_WARN_IF(!ToJSValue(aCx
, storage
, &caches
))) {
257 return JS_DefineProperty(aCx
, aGlobal
, "caches", caches
, JSPROP_ENUMERATE
);
260 CacheStorage::CacheStorage(Namespace aNamespace
, nsIGlobalObject
* aGlobal
,
261 const PrincipalInfo
& aPrincipalInfo
,
262 SafeRefPtr
<CacheWorkerRef
> aWorkerRef
)
263 : mNamespace(aNamespace
),
265 mPrincipalInfo(MakeUnique
<PrincipalInfo
>(aPrincipalInfo
)),
268 MOZ_DIAGNOSTIC_ASSERT(mGlobal
);
270 // If the PBackground actor is already initialized then we can
271 // immediately use it
272 PBackgroundChild
* actor
= BackgroundChild::GetOrCreateForCurrentThread();
273 if (NS_WARN_IF(!actor
)) {
274 mStatus
= NS_ERROR_UNEXPECTED
;
278 // WorkerRef ownership is passed to the CacheStorageChild actor and any
279 // actors it may create. The WorkerRef will keep the worker thread alive
280 // until the actors can gracefully shutdown.
281 CacheStorageChild
* newActor
=
282 new CacheStorageChild(this, std::move(aWorkerRef
));
283 PCacheStorageChild
* constructedActor
= actor
->SendPCacheStorageConstructor(
284 newActor
, mNamespace
, *mPrincipalInfo
);
286 if (NS_WARN_IF(!constructedActor
)) {
287 mStatus
= NS_ERROR_UNEXPECTED
;
291 MOZ_DIAGNOSTIC_ASSERT(constructedActor
== newActor
);
295 CacheStorage::CacheStorage(nsresult aFailureResult
)
296 : mNamespace(INVALID_NAMESPACE
), mActor(nullptr), mStatus(aFailureResult
) {
297 MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mStatus
));
300 already_AddRefed
<Promise
> CacheStorage::Match(
301 JSContext
* aCx
, const RequestOrUTF8String
& aRequest
,
302 const MultiCacheQueryOptions
& aOptions
, ErrorResult
& aRv
) {
303 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
305 if (!HasStorageAccess(eUseCounter_custom_PrivateBrowsingCachesMatch
,
306 UseCounterWorker::Custom_PrivateBrowsingCachesMatch
)) {
307 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
311 if (NS_WARN_IF(NS_FAILED(mStatus
))) {
316 SafeRefPtr
<InternalRequest
> request
=
317 ToInternalRequest(aCx
, aRequest
, IgnoreBody
, aRv
);
318 if (NS_WARN_IF(aRv
.Failed())) {
322 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
323 if (NS_WARN_IF(!promise
)) {
327 CacheQueryParams params
;
328 ToCacheQueryParams(params
, aOptions
);
330 auto entry
= MakeUnique
<Entry
>();
331 entry
->mPromise
= promise
;
332 entry
->mArgs
= StorageMatchArgs(CacheRequest(), params
, GetOpenMode());
333 entry
->mRequest
= std::move(request
);
335 RunRequest(std::move(entry
));
337 return promise
.forget();
340 already_AddRefed
<Promise
> CacheStorage::Has(const nsAString
& aKey
,
342 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
344 if (!HasStorageAccess(eUseCounter_custom_PrivateBrowsingCachesHas
,
345 UseCounterWorker::Custom_PrivateBrowsingCachesHas
)) {
346 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
350 if (NS_WARN_IF(NS_FAILED(mStatus
))) {
355 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
356 if (NS_WARN_IF(!promise
)) {
360 auto entry
= MakeUnique
<Entry
>();
361 entry
->mPromise
= promise
;
362 entry
->mArgs
= StorageHasArgs(nsString(aKey
));
364 RunRequest(std::move(entry
));
366 return promise
.forget();
369 already_AddRefed
<Promise
> CacheStorage::Open(const nsAString
& aKey
,
371 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
373 if (!HasStorageAccess(eUseCounter_custom_PrivateBrowsingCachesOpen
,
374 UseCounterWorker::Custom_PrivateBrowsingCachesOpen
)) {
375 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
379 if (NS_WARN_IF(NS_FAILED(mStatus
))) {
384 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
385 if (NS_WARN_IF(!promise
)) {
389 auto entry
= MakeUnique
<Entry
>();
390 entry
->mPromise
= promise
;
391 entry
->mArgs
= StorageOpenArgs(nsString(aKey
));
393 RunRequest(std::move(entry
));
395 return promise
.forget();
398 already_AddRefed
<Promise
> CacheStorage::Delete(const nsAString
& aKey
,
400 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
402 if (!HasStorageAccess(eUseCounter_custom_PrivateBrowsingCachesDelete
,
403 UseCounterWorker::Custom_PrivateBrowsingCachesDelete
)) {
404 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
408 if (NS_WARN_IF(NS_FAILED(mStatus
))) {
413 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
414 if (NS_WARN_IF(!promise
)) {
418 auto entry
= MakeUnique
<Entry
>();
419 entry
->mPromise
= promise
;
420 entry
->mArgs
= StorageDeleteArgs(nsString(aKey
));
422 RunRequest(std::move(entry
));
424 return promise
.forget();
427 already_AddRefed
<Promise
> CacheStorage::Keys(ErrorResult
& aRv
) {
428 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
430 if (!HasStorageAccess(eUseCounter_custom_PrivateBrowsingCachesKeys
,
431 UseCounterWorker::Custom_PrivateBrowsingCachesKeys
)) {
432 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
436 if (NS_WARN_IF(NS_FAILED(mStatus
))) {
441 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
442 if (NS_WARN_IF(!promise
)) {
446 auto entry
= MakeUnique
<Entry
>();
447 entry
->mPromise
= promise
;
448 entry
->mArgs
= StorageKeysArgs();
450 RunRequest(std::move(entry
));
452 return promise
.forget();
456 already_AddRefed
<CacheStorage
> CacheStorage::Constructor(
457 const GlobalObject
& aGlobal
, CacheStorageNamespace aNamespace
,
458 nsIPrincipal
* aPrincipal
, ErrorResult
& aRv
) {
459 if (NS_WARN_IF(!NS_IsMainThread())) {
460 aRv
.Throw(NS_ERROR_FAILURE
);
464 // TODO: remove Namespace in favor of CacheStorageNamespace
465 static_assert(DEFAULT_NAMESPACE
== (uint32_t)CacheStorageNamespace::Content
,
466 "Default namespace should match webidl Content enum");
468 CHROME_ONLY_NAMESPACE
== (uint32_t)CacheStorageNamespace::Chrome
,
469 "Chrome namespace should match webidl Chrome enum");
471 NUMBER_OF_NAMESPACES
== ContiguousEnumSize
<CacheStorageNamespace
>::value
,
472 "Number of namespace should match webidl count");
474 Namespace ns
= static_cast<Namespace
>(aNamespace
);
475 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
477 bool privateBrowsing
= false;
478 if (nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
)) {
479 RefPtr
<Document
> doc
= window
->GetExtantDoc();
481 nsCOMPtr
<nsILoadContext
> loadContext
= doc
->GetLoadContext();
482 privateBrowsing
= loadContext
&& loadContext
->UsePrivateBrowsing();
486 if (privateBrowsing
&& !StaticPrefs::dom_cache_privateBrowsing_enabled()) {
487 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
491 // Create a CacheStorage object bypassing the trusted origin checks
492 // since this is a chrome-only constructor.
493 return CreateOnMainThread(ns
, global
, aPrincipal
,
494 true /* force trusted origin */, aRv
);
497 nsISupports
* CacheStorage::GetParentObject() const { return mGlobal
; }
499 JSObject
* CacheStorage::WrapObject(JSContext
* aContext
,
500 JS::Handle
<JSObject
*> aGivenProto
) {
501 return mozilla::dom::CacheStorage_Binding::Wrap(aContext
, this, aGivenProto
);
504 void CacheStorage::DestroyInternal(CacheStorageChild
* aActor
) {
505 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
506 MOZ_DIAGNOSTIC_ASSERT(mActor
);
507 MOZ_DIAGNOSTIC_ASSERT(mActor
== aActor
);
508 MOZ_DIAGNOSTIC_ASSERT(!NS_FAILED(mStatus
));
509 mActor
->ClearListener();
511 mStatus
= NS_ERROR_UNEXPECTED
;
513 // Note that we will never get an actor again in case another request is
514 // made before this object is destructed.
517 nsIGlobalObject
* CacheStorage::GetGlobalObject() const { return mGlobal
; }
520 void CacheStorage::AssertOwningThread() const {
521 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
525 PBackgroundChild
* CacheStorage::GetIPCManager() {
526 // This is true because CacheStorage always uses IgnoreBody for requests.
527 // So we should never need to get the IPC manager during Request or
528 // Response serialization.
529 MOZ_CRASH("CacheStorage does not implement TypeUtils::GetIPCManager()");
532 CacheStorage::~CacheStorage() {
533 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
535 mActor
->StartDestroyFromListener();
536 // DestroyInternal() is called synchronously by StartDestroyFromListener().
537 // So we should have already cleared the mActor.
538 MOZ_DIAGNOSTIC_ASSERT(!mActor
);
542 void CacheStorage::RunRequest(UniquePtr
<Entry
> aEntry
) {
545 AutoChildOpArgs
args(this, aEntry
->mArgs
, 1);
547 if (aEntry
->mRequest
) {
549 args
.Add(*aEntry
->mRequest
, IgnoreBody
, IgnoreInvalidScheme
, rv
);
550 if (NS_WARN_IF(rv
.Failed())) {
551 aEntry
->mPromise
->MaybeReject(std::move(rv
));
556 mActor
->ExecuteOp(mGlobal
, aEntry
->mPromise
, this, args
.SendAsOpArgs());
559 OpenMode
CacheStorage::GetOpenMode() const {
560 return mNamespace
== CHROME_ONLY_NAMESPACE
? OpenMode::Eager
: OpenMode::Lazy
;
563 bool CacheStorage::HasStorageAccess(UseCounter aLabel
,
564 UseCounterWorker aLabelWorker
) const {
565 NS_ASSERT_OWNINGTHREAD(CacheStorage
);
566 if (NS_WARN_IF(!mGlobal
)) {
570 StorageAccess access
= mGlobal
->GetStorageAccess();
571 if (access
== StorageAccess::ePrivateBrowsing
) {
572 if (NS_IsMainThread()) {
573 SetUseCounter(mGlobal
->GetGlobalJSObject(), aLabel
);
575 SetUseCounter(aLabelWorker
);
579 // Deny storage access for private browsing unless pref is toggled on.
580 if (nsIPrincipal
* principal
= mGlobal
->PrincipalOrNull()) {
581 if (!principal
->IsSystemPrincipal() &&
582 principal
->GetPrivateBrowsingId() !=
583 nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID
&&
584 !StaticPrefs::dom_cache_privateBrowsing_enabled()) {
589 return access
> StorageAccess::eDeny
||
591 privacy_partition_always_partition_third_party_non_cookie_storage() &&
592 ShouldPartitionStorage(access
));
595 } // namespace mozilla::dom::cache