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/QuotaManager.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::dom::quota::QuotaManager
;
46 using mozilla::ipc::BackgroundChild
;
47 using mozilla::ipc::PBackgroundChild
;
48 using mozilla::ipc::PrincipalInfo
;
49 using mozilla::ipc::PrincipalToPrincipalInfo
;
51 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage
);
52 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage
);
53 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::CacheStorage
,
56 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage
)
57 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
58 NS_INTERFACE_MAP_ENTRY(nsISupports
)
61 // We cannot reference IPC types in a webidl binding implementation header. So
62 // define this in the .cpp.
63 struct CacheStorage::Entry final
{
64 RefPtr
<Promise
> mPromise
;
66 // We cannot add the requests until after the actor is present. So store
67 // the request data separately for now.
68 SafeRefPtr
<InternalRequest
> mRequest
;
73 bool IsTrusted(const PrincipalInfo
& aPrincipalInfo
, bool aTestingPrefEnabled
) {
74 // Can happen on main thread or worker thread
76 if (aPrincipalInfo
.type() == PrincipalInfo::TSystemPrincipalInfo
) {
80 // Require a ContentPrincipal to avoid null principal, etc.
81 QM_TRY(OkIf(aPrincipalInfo
.type() == PrincipalInfo::TContentPrincipalInfo
),
84 // If we're in testing mode, then don't do any more work to determine if
85 // the origin is trusted. We have to run some tests as http.
86 if (aTestingPrefEnabled
) {
90 // Now parse the scheme of the principal's origin. This is a short term
91 // method for determining "trust". In the long term we need to implement
92 // the full algorithm here:
94 // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure
96 // TODO: Implement full secure setting algorithm. (bug 1177856)
98 const nsCString
& flatURL
= aPrincipalInfo
.get_ContentPrincipalInfo().spec();
99 const char* const url
= flatURL
.get();
101 // off the main thread URL parsing using nsStdURLParser.
102 const nsCOMPtr
<nsIURLParser
> urlParser
= new nsStdURLParser();
108 QM_TRY(MOZ_TO_RESULT(urlParser
->ParseURL(url
, flatURL
.Length(), &schemePos
,
109 &schemeLen
, &authPos
, &authLen
,
110 nullptr, nullptr)), // ignore path
113 const nsAutoCString
scheme(Substring(flatURL
, schemePos
, schemeLen
));
114 if (scheme
.LowerCaseEqualsLiteral("https") ||
115 scheme
.LowerCaseEqualsLiteral("file") ||
116 scheme
.LowerCaseEqualsLiteral("moz-extension")) {
122 QM_TRY(MOZ_TO_RESULT(
123 urlParser
->ParseAuthority(url
+ authPos
, authLen
, nullptr,
124 nullptr, // ignore username
125 nullptr, nullptr, // ignore password
127 nullptr)), // ignore port
130 return nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
131 nsDependentCSubstring(url
+ authPos
+ hostPos
, hostLen
));
137 already_AddRefed
<CacheStorage
> CacheStorage::CreateOnMainThread(
138 Namespace aNamespace
, nsIGlobalObject
* aGlobal
, nsIPrincipal
* aPrincipal
,
139 bool aForceTrustedOrigin
, ErrorResult
& aRv
) {
140 MOZ_DIAGNOSTIC_ASSERT(aGlobal
);
141 MOZ_DIAGNOSTIC_ASSERT(aPrincipal
);
142 MOZ_ASSERT(NS_IsMainThread());
144 PrincipalInfo principalInfo
;
145 QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
)),
146 nullptr, [&aRv
](const nsresult rv
) { aRv
.Throw(rv
); });
148 QM_TRY(OkIf(QuotaManager::IsPrincipalInfoValid(principalInfo
)),
149 RefPtr
{new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
)}.forget(),
151 NS_WARNING("CacheStorage not supported on invalid origins.");
154 const bool testingEnabled
=
155 aForceTrustedOrigin
||
156 Preferences::GetBool("dom.caches.testing.enabled", false) ||
157 StaticPrefs::dom_serviceWorkers_testing_enabled();
159 if (!IsTrusted(principalInfo
, testingEnabled
)) {
160 NS_WARNING("CacheStorage not supported on untrusted origins.");
161 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
165 RefPtr
<CacheStorage
> ref
=
166 new CacheStorage(aNamespace
, aGlobal
, principalInfo
, nullptr);
171 already_AddRefed
<CacheStorage
> CacheStorage::CreateOnWorker(
172 Namespace aNamespace
, nsIGlobalObject
* aGlobal
,
173 WorkerPrivate
* aWorkerPrivate
, ErrorResult
& aRv
) {
174 MOZ_DIAGNOSTIC_ASSERT(aGlobal
);
175 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate
);
176 aWorkerPrivate
->AssertIsOnWorkerThread();
178 if (aWorkerPrivate
->GetOriginAttributes().mPrivateBrowsingId
> 0 &&
179 !StaticPrefs::dom_cache_privateBrowsing_enabled()) {
180 NS_WARNING("CacheStorage not supported during private browsing.");
181 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
185 SafeRefPtr
<CacheWorkerRef
> workerRef
=
186 CacheWorkerRef::Create(aWorkerPrivate
, CacheWorkerRef::eIPCWorkerRef
);
188 NS_WARNING("Worker thread is shutting down.");
189 aRv
.Throw(NS_ERROR_FAILURE
);
193 const PrincipalInfo
& principalInfo
=
194 aWorkerPrivate
->GetEffectiveStoragePrincipalInfo();
196 QM_TRY(OkIf(QuotaManager::IsPrincipalInfoValid(principalInfo
)), nullptr,
197 [&aRv
](const auto) { aRv
.Throw(NS_ERROR_FAILURE
); });
199 // We have a number of cases where we want to skip the https scheme
202 // 1) Any worker when dom.caches.testing.enabled pref is true.
203 // 2) Any worker when dom.serviceWorkers.testing.enabled pref is true. This
204 // is mainly because most sites using SWs will expect Cache to work if
206 // 3) If the window that created this worker has the devtools SW testing
207 // option enabled. Same reasoning as (2).
208 // 4) If the worker itself is a ServiceWorker, then we always skip the
209 // origin checks. The ServiceWorker has its own trusted origin checks
210 // that are better than ours. In addition, we don't have information
211 // about the window any more, so we can't do our own checks.
212 bool testingEnabled
= StaticPrefs::dom_caches_testing_enabled() ||
213 StaticPrefs::dom_serviceWorkers_testing_enabled() ||
214 aWorkerPrivate
->ServiceWorkersTestingInWindow() ||
215 aWorkerPrivate
->IsServiceWorker();
217 if (!IsTrusted(principalInfo
, testingEnabled
)) {
218 NS_WARNING("CacheStorage not supported on untrusted origins.");
219 RefPtr
<CacheStorage
> ref
= new CacheStorage(NS_ERROR_DOM_SECURITY_ERR
);
223 RefPtr
<CacheStorage
> ref
= new CacheStorage(
224 aNamespace
, aGlobal
, principalInfo
, std::move(workerRef
));
229 bool CacheStorage::DefineCaches(JSContext
* aCx
, 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::GetConstructorObject(aCx
) ||
236 !Cache_Binding::GetConstructorObject(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 RequestOrUSVString
& 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