CookieStoreManager.cpp (9784B)
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/. */ 6 7 #include "CookieStoreManager.h" 8 9 #include "CookieStoreChild.h" 10 #include "mozilla/dom/Promise.h" 11 #include "mozilla/dom/WorkerCommon.h" 12 #include "mozilla/dom/WorkerPrivate.h" 13 #include "mozilla/ipc/BackgroundChild.h" 14 #include "mozilla/ipc/PBackgroundChild.h" 15 #include "nsGlobalWindowInner.h" 16 #include "nsIGlobalObject.h" 17 #include "nsIPrincipal.h" 18 19 using mozilla::ipc::PrincipalInfo; 20 21 namespace mozilla::dom { 22 23 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(CookieStoreManager) 24 25 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CookieStoreManager) 26 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObject) 27 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 28 tmp->Shutdown(); 29 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 30 31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CookieStoreManager) 32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObject) 33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 34 35 CookieStoreManager::CookieStoreManager( 36 nsIGlobalObject* aGlobalObject, 37 const nsACString& aServiceWorkerRegistrationScopeURL) 38 : mGlobalObject(aGlobalObject), 39 mScopeURL(aServiceWorkerRegistrationScopeURL) { 40 MOZ_ASSERT(aGlobalObject); 41 } 42 43 namespace { 44 45 nsIPrincipal* RetrievePrincipal(nsIGlobalObject* aGlobalObject) { 46 if (NS_IsMainThread()) { 47 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobalObject); 48 if (NS_WARN_IF(!window)) { 49 return nullptr; 50 } 51 52 return nsGlobalWindowInner::Cast(window)->GetClientPrincipal(); 53 } 54 55 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 56 MOZ_ASSERT(worker); 57 worker->AssertIsOnWorkerThread(); 58 59 return worker->GetPrincipal(); 60 } 61 62 } // namespace 63 64 CookieStoreManager::~CookieStoreManager() { Shutdown(); } 65 66 JSObject* CookieStoreManager::WrapObject(JSContext* aCx, 67 JS::Handle<JSObject*> aGivenProto) { 68 return CookieStoreManager_Binding::Wrap(aCx, this, aGivenProto); 69 } 70 71 already_AddRefed<Promise> CookieStoreManager::Subscribe( 72 const CopyableTArray<CookieStoreGetOptions>& aSubscriptions, 73 ErrorResult& aRv) { 74 return SubscribeOrUnsubscribe(Action::eSubscribe, aSubscriptions, aRv); 75 } 76 77 already_AddRefed<Promise> CookieStoreManager::GetSubscriptions( 78 ErrorResult& aRv) { 79 nsCOMPtr<nsIPrincipal> principal = RetrievePrincipal(mGlobalObject); 80 if (NS_WARN_IF(!principal)) { 81 aRv.ThrowInvalidStateError("Invalid context"); 82 return nullptr; 83 } 84 85 RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv); 86 if (NS_WARN_IF(!promise)) { 87 return nullptr; 88 } 89 90 // ServiceWorkers only have one principal: it's either partitioned or 91 // unpartitioned. If the "context" is partitioned, the window or the 92 // WorkerPrivate will return a partitioned principal. 93 94 // Let's dispatch a runnable to implement the "Run the following steps in 95 // parallel". 96 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 97 __func__, [self = RefPtr(this), principal = RefPtr(principal.get()), 98 promise = RefPtr(promise)]() { 99 if (!self->MaybeCreateActor()) { 100 promise->MaybeRejectWithNotAllowedError("Permission denied"); 101 return; 102 } 103 104 PrincipalInfo principalInfo; 105 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo); 106 if (NS_WARN_IF(NS_FAILED(rv))) { 107 promise->MaybeResolve(nsTArray<CookieStoreGetOptions>()); 108 return; 109 } 110 111 RefPtr<CookieStoreChild::GetSubscriptionsRequestPromise> ipcPromise = 112 self->mActor->SendGetSubscriptionsRequest(principalInfo, 113 self->mScopeURL); 114 if (NS_WARN_IF(!ipcPromise)) { 115 promise->MaybeResolve(nsTArray<CookieStoreGetOptions>()); 116 return; 117 } 118 119 ipcPromise->Then( 120 NS_GetCurrentThread(), __func__, 121 [promise = RefPtr(promise)]( 122 const CookieStoreChild::GetSubscriptionsRequestPromise:: 123 ResolveOrRejectValue& aResult) { 124 if (aResult.IsReject()) { 125 promise->MaybeResolve(nsTArray<CookieStoreGetOptions>()); 126 return; 127 } 128 129 nsTArray<CookieStoreGetOptions> results; 130 for (const auto& subscription : aResult.ResolveValue()) { 131 CookieStoreGetOptions* result = results.AppendElement(); 132 133 if (subscription.name().isSome()) { 134 result->mName.Construct(); 135 result->mName.Value() = subscription.name().value(); 136 } 137 138 result->mUrl.Construct(); 139 result->mUrl.Value() = subscription.url(); 140 } 141 142 promise->MaybeResolve(results); 143 }); 144 })); 145 146 return promise.forget(); 147 } 148 149 already_AddRefed<Promise> CookieStoreManager::Unsubscribe( 150 const CopyableTArray<CookieStoreGetOptions>& aSubscriptions, 151 ErrorResult& aRv) { 152 return SubscribeOrUnsubscribe(Action::eUnsubscribe, aSubscriptions, aRv); 153 } 154 155 already_AddRefed<Promise> CookieStoreManager::SubscribeOrUnsubscribe( 156 Action aAction, const CopyableTArray<CookieStoreGetOptions>& aSubscriptions, 157 ErrorResult& aRv) { 158 nsCOMPtr<nsIPrincipal> principal = RetrievePrincipal(mGlobalObject); 159 if (NS_WARN_IF(!principal)) { 160 aRv.ThrowInvalidStateError("Invalid context"); 161 return nullptr; 162 } 163 164 RefPtr<Promise> promise = Promise::Create(mGlobalObject, aRv); 165 if (NS_WARN_IF(!promise)) { 166 return nullptr; 167 } 168 169 // ServiceWorkers only have one principal: it's either partitioned or 170 // unpartitioned. If the "context" is partitioned, the window or the 171 // WorkerPrivate will return a partitioned principal. 172 173 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 174 __func__, 175 [self = RefPtr(this), promise = RefPtr(promise), 176 principal = RefPtr(principal.get()), aSubscriptions, aAction]() { 177 nsCOMPtr<nsIURI> baseURI; 178 nsresult rv = NS_NewURI(getter_AddRefs(baseURI), self->mScopeURL, 179 nullptr, nullptr); 180 if (NS_WARN_IF(NS_FAILED(rv))) { 181 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(self->mScopeURL); 182 return; 183 } 184 185 if (NS_WARN_IF(!baseURI)) { 186 promise->MaybeRejectWithSecurityError( 187 "Couldn't acquire the base URI of this context"); 188 return; 189 } 190 191 nsTArray<CookieSubscription> subscriptions; 192 193 for (const CookieStoreGetOptions& subscription : aSubscriptions) { 194 nsString subscriptionURL; 195 if (subscription.mUrl.WasPassed()) { 196 subscriptionURL.Assign(subscription.mUrl.Value()); 197 } 198 199 nsCOMPtr<nsIURI> uri; 200 rv = 201 NS_NewURI(getter_AddRefs(uri), subscriptionURL, nullptr, baseURI); 202 if (NS_WARN_IF(NS_FAILED(rv))) { 203 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 204 NS_ConvertUTF16toUTF8(subscriptionURL)); 205 return; 206 } 207 208 nsAutoCString subscriptionURI; 209 rv = uri->GetSpec(subscriptionURI); 210 if (NS_WARN_IF(NS_FAILED(rv))) { 211 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 212 NS_ConvertUTF16toUTF8(subscriptionURL)); 213 return; 214 } 215 216 if (!StringBeginsWith(subscriptionURI, self->mScopeURL)) { 217 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 218 NS_ConvertUTF16toUTF8(subscriptionURL)); 219 return; 220 } 221 222 subscriptions.AppendElement(CookieSubscription{ 223 subscription.mName.WasPassed() ? Some(subscription.mName.Value()) 224 : Nothing(), 225 NS_ConvertUTF8toUTF16(subscriptionURI)}); 226 } 227 228 if (!self->MaybeCreateActor()) { 229 promise->MaybeRejectWithNotAllowedError("Permission denied"); 230 return; 231 } 232 233 PrincipalInfo principalInfo; 234 rv = PrincipalToPrincipalInfo(principal, &principalInfo); 235 if (NS_WARN_IF(NS_FAILED(rv))) { 236 promise->MaybeResolveWithUndefined(); 237 return; 238 } 239 240 RefPtr<CookieStoreChild::SubscribeOrUnsubscribeRequestPromise> 241 ipcPromise = self->mActor->SendSubscribeOrUnsubscribeRequest( 242 principalInfo, self->mScopeURL, subscriptions, 243 (aAction == Action::eSubscribe)); 244 if (NS_WARN_IF(!ipcPromise)) { 245 promise->MaybeResolveWithUndefined(); 246 return; 247 } 248 249 ipcPromise->Then( 250 NS_GetCurrentThread(), __func__, 251 [promise = RefPtr(promise)]( 252 const CookieStoreChild::SubscribeOrUnsubscribeRequestPromise:: 253 ResolveOrRejectValue& aResult) { 254 // TODO We don't really want to expose internal errors. 255 promise->MaybeResolveWithUndefined(); 256 }); 257 })); 258 259 return promise.forget(); 260 } 261 262 bool CookieStoreManager::MaybeCreateActor() { 263 if (mActor) { 264 return mActor->CanSend(); 265 } 266 267 mozilla::ipc::PBackgroundChild* actorChild = 268 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); 269 if (NS_WARN_IF(!actorChild)) { 270 // The process is probably shutting down. Let's return a 'generic' error. 271 return false; 272 } 273 274 PCookieStoreChild* actor = actorChild->SendPCookieStoreConstructor(); 275 if (!actor) { 276 return false; 277 } 278 279 mActor = static_cast<CookieStoreChild*>(actor); 280 281 return true; 282 } 283 284 void CookieStoreManager::Shutdown() { 285 if (mActor) { 286 mActor->Close(); 287 mActor = nullptr; 288 } 289 } 290 291 } // namespace mozilla::dom