CookieStoreParent.cpp (20323B)
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 "CookieStoreParent.h" 8 9 #include "CookieStoreNotificationWatcher.h" 10 #include "CookieStoreSubscriptionService.h" 11 #include "mozilla/Components.h" 12 #include "mozilla/Maybe.h" 13 #include "mozilla/ScopeExit.h" 14 #include "mozilla/ipc/BackgroundParent.h" 15 #include "mozilla/ipc/URIUtils.h" // for ParamTraits<nsIURI*> 16 #include "mozilla/net/Cookie.h" 17 #include "mozilla/net/CookieCommons.h" 18 #include "mozilla/net/CookieParser.h" 19 #include "mozilla/net/CookiePrefixes.h" 20 #include "mozilla/net/CookieServiceParent.h" 21 #include "mozilla/net/CookieValidation.h" 22 #include "mozilla/net/NeckoParent.h" 23 #include "nsICookieManager.h" 24 #include "nsICookieService.h" 25 #include "nsIEffectiveTLDService.h" 26 #include "nsProxyRelease.h" 27 28 using namespace mozilla::ipc; 29 using namespace mozilla::net; 30 31 namespace mozilla::dom { 32 33 namespace { 34 35 bool CheckContentProcessSecurity(ThreadsafeContentParentHandle* aParent, 36 const nsACString& aDomain, 37 const OriginAttributes& aOriginAttributes) { 38 AssertIsOnMainThread(); 39 40 // ContentParent is null if we are dealing with the same process. 41 if (!aParent) { 42 return true; 43 } 44 45 RefPtr<ContentParent> contentParent = aParent->GetContentParent(); 46 if (!contentParent) { 47 return true; 48 } 49 50 PNeckoParent* neckoParent = 51 LoneManagedOrNullAsserts(contentParent->ManagedPNeckoParent()); 52 if (!neckoParent) { 53 return true; 54 } 55 56 PCookieServiceParent* csParent = 57 LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); 58 if (!csParent) { 59 return true; 60 } 61 62 auto* cs = static_cast<CookieServiceParent*>(csParent); 63 64 return cs->ContentProcessHasCookie(aDomain, aOriginAttributes); 65 } 66 67 } // namespace 68 69 CookieStoreParent::CookieStoreParent() { AssertIsOnBackgroundThread(); } 70 71 CookieStoreParent::~CookieStoreParent() { 72 CookieStoreNotificationWatcher::ReleaseOnMainThread( 73 mNotificationWatcherOnMainThread.forget()); 74 } 75 76 mozilla::ipc::IPCResult CookieStoreParent::RecvGetRequest( 77 NotNull<RefPtr<nsIURI>> aCookieURI, 78 const OriginAttributes& aOriginAttributes, 79 const Maybe<OriginAttributes>& aPartitionedOriginAttributes, 80 const bool& aThirdPartyContext, const bool& aPartitionForeign, 81 const bool& aUsingStorageAccess, const bool& aIsOn3PCBExceptionList, 82 const bool& aMatchName, const nsString& aName, const nsCString& aPath, 83 const bool& aOnlyFirstMatch, GetRequestResolver&& aResolver) { 84 AssertIsOnBackgroundThread(); 85 86 InvokeAsync(GetMainThreadSerialEventTarget(), __func__, 87 [self = RefPtr(this), uri = aCookieURI.get(), aOriginAttributes, 88 aPartitionedOriginAttributes, aThirdPartyContext, 89 aPartitionForeign, aUsingStorageAccess, aIsOn3PCBExceptionList, 90 aMatchName, aName, aPath, aOnlyFirstMatch]() { 91 CopyableTArray<CookieStruct> results; 92 self->GetRequestOnMainThread( 93 uri, aOriginAttributes, aPartitionedOriginAttributes, 94 aThirdPartyContext, aPartitionForeign, aUsingStorageAccess, 95 aIsOn3PCBExceptionList, aMatchName, aName, aPath, 96 aOnlyFirstMatch, results); 97 return GetRequestPromise::CreateAndResolve(std::move(results), 98 __func__); 99 }) 100 ->Then(GetCurrentSerialEventTarget(), __func__, 101 [aResolver = std::move(aResolver)]( 102 const GetRequestPromise::ResolveOrRejectValue& aResult) { 103 MOZ_ASSERT(aResult.IsResolve()); 104 aResolver(aResult.ResolveValue()); 105 }); 106 107 return IPC_OK(); 108 } 109 110 mozilla::ipc::IPCResult CookieStoreParent::RecvSetRequest( 111 NotNull<RefPtr<nsIURI>> aCookieURI, 112 const OriginAttributes& aOriginAttributes, const bool& aThirdPartyContext, 113 const bool& aPartitionForeign, const bool& aUsingStorageAccess, 114 const bool& aIsOn3PCBExceptionList, const nsString& aName, 115 const nsString& aValue, const bool& aSession, const int64_t& aExpires, 116 const nsString& aDomain, const nsString& aPath, const int32_t& aSameSite, 117 const bool& aPartitioned, const nsID& aOperationID, 118 SetRequestResolver&& aResolver) { 119 AssertIsOnBackgroundThread(); 120 121 RefPtr<ThreadsafeContentParentHandle> parent = 122 BackgroundParent::GetContentParentHandle(Manager()); 123 124 InvokeAsync( 125 GetMainThreadSerialEventTarget(), __func__, 126 [self = RefPtr(this), parent = RefPtr(parent), uri = aCookieURI.get(), 127 aDomain, aOriginAttributes, aThirdPartyContext, aPartitionForeign, 128 aUsingStorageAccess, aIsOn3PCBExceptionList, aName, aValue, aSession, 129 aExpires, aPath, aSameSite, aPartitioned, aOperationID]() { 130 bool waitForNotification = false; 131 SetReturnType ret = self->SetRequestOnMainThread( 132 parent, uri, aDomain, aOriginAttributes, aThirdPartyContext, 133 aPartitionForeign, aUsingStorageAccess, aIsOn3PCBExceptionList, 134 aName, aValue, aSession, aExpires, aPath, aSameSite, aPartitioned, 135 aOperationID, waitForNotification); 136 137 switch (ret) { 138 case eFailure: 139 return SetDeleteRequestPromise::CreateAndReject(false, __func__); 140 141 case eSuccess: 142 return SetDeleteRequestPromise::CreateAndResolve( 143 waitForNotification, __func__); 144 145 case eSilentFailure: 146 default: 147 return SetDeleteRequestPromise::CreateAndResolve(false, __func__); 148 } 149 }) 150 ->Then(GetCurrentSerialEventTarget(), __func__, 151 [aResolver = std::move(aResolver)]( 152 const SetDeleteRequestPromise::ResolveOrRejectValue& aResult) { 153 if (aResult.IsResolve()) { 154 aResolver(CookieStoreResult(true, aResult.ResolveValue())); 155 return; 156 } 157 158 aResolver(CookieStoreResult(false, false)); 159 }); 160 161 return IPC_OK(); 162 } 163 164 mozilla::ipc::IPCResult CookieStoreParent::RecvDeleteRequest( 165 NotNull<RefPtr<nsIURI>> aCookieURI, 166 const OriginAttributes& aOriginAttributes, const bool& aThirdPartyContext, 167 const bool& aPartitionForeign, const bool& aUsingStorageAccess, 168 const bool& aIsOn3PCBExceptionList, const nsString& aName, 169 const nsString& aDomain, const nsString& aPath, const bool& aPartitioned, 170 const nsID& aOperationID, DeleteRequestResolver&& aResolver) { 171 AssertIsOnBackgroundThread(); 172 173 RefPtr<ThreadsafeContentParentHandle> parent = 174 BackgroundParent::GetContentParentHandle(Manager()); 175 176 InvokeAsync( 177 GetMainThreadSerialEventTarget(), __func__, 178 [self = RefPtr(this), parent = RefPtr(parent), uri = aCookieURI.get(), 179 aDomain, aOriginAttributes, aThirdPartyContext, aPartitionForeign, 180 aUsingStorageAccess, aIsOn3PCBExceptionList, aName, aPath, aPartitioned, 181 aOperationID]() { 182 bool waitForNotification = self->DeleteRequestOnMainThread( 183 parent, uri, aDomain, aOriginAttributes, aThirdPartyContext, 184 aPartitionForeign, aUsingStorageAccess, aIsOn3PCBExceptionList, 185 aName, aPath, aPartitioned, aOperationID); 186 return SetDeleteRequestPromise::CreateAndResolve(waitForNotification, 187 __func__); 188 }) 189 ->Then(GetCurrentSerialEventTarget(), __func__, 190 [aResolver = std::move(aResolver)]( 191 const SetDeleteRequestPromise::ResolveOrRejectValue& aResult) { 192 MOZ_ASSERT(aResult.IsResolve()); 193 aResolver(aResult.ResolveValue()); 194 }); 195 return IPC_OK(); 196 } 197 198 mozilla::ipc::IPCResult CookieStoreParent::RecvGetSubscriptionsRequest( 199 const PrincipalInfo& aPrincipalInfo, const nsCString& aScopeURL, 200 GetSubscriptionsRequestResolver&& aResolver) { 201 AssertIsOnBackgroundThread(); 202 203 InvokeAsync(GetMainThreadSerialEventTarget(), __func__, 204 [self = RefPtr(this), aPrincipalInfo, aScopeURL]() { 205 CookieStoreSubscriptionService* service = 206 CookieStoreSubscriptionService::Instance(); 207 if (!service) { 208 return GetSubscriptionsRequestPromise::CreateAndReject( 209 NS_ERROR_FAILURE, __func__); 210 } 211 212 nsTArray<CookieSubscription> subscriptions; 213 service->GetSubscriptions(aPrincipalInfo, aScopeURL, 214 subscriptions); 215 216 return GetSubscriptionsRequestPromise::CreateAndResolve( 217 std::move(subscriptions), __func__); 218 }) 219 ->Then(GetCurrentSerialEventTarget(), __func__, 220 [aResolver = std::move(aResolver)]( 221 const GetSubscriptionsRequestPromise::ResolveOrRejectValue& 222 aResult) { 223 if (aResult.IsResolve()) { 224 aResolver(aResult.ResolveValue()); 225 return; 226 } 227 228 aResolver(nsTArray<CookieSubscription>()); 229 }); 230 231 return IPC_OK(); 232 } 233 234 mozilla::ipc::IPCResult CookieStoreParent::RecvSubscribeOrUnsubscribeRequest( 235 const PrincipalInfo& aPrincipalInfo, const nsCString& aScopeURL, 236 const CopyableTArray<CookieSubscription>& aSubscriptions, 237 bool aSubscription, SubscribeOrUnsubscribeRequestResolver&& aResolver) { 238 AssertIsOnBackgroundThread(); 239 240 InvokeAsync(GetMainThreadSerialEventTarget(), __func__, 241 [self = RefPtr(this), aPrincipalInfo, aScopeURL, aSubscriptions, 242 aSubscription]() { 243 CookieStoreSubscriptionService* service = 244 CookieStoreSubscriptionService::Instance(); 245 if (!service) { 246 return SubscribeOrUnsubscribeRequestPromise::CreateAndReject( 247 NS_ERROR_FAILURE, __func__); 248 } 249 250 if (aSubscription) { 251 service->Subscribe(aPrincipalInfo, aScopeURL, aSubscriptions); 252 } else { 253 service->Unsubscribe(aPrincipalInfo, aScopeURL, 254 aSubscriptions); 255 } 256 257 return SubscribeOrUnsubscribeRequestPromise::CreateAndResolve( 258 true, __func__); 259 }) 260 ->Then( 261 GetCurrentSerialEventTarget(), __func__, 262 [aResolver = std::move(aResolver)]( 263 const SubscribeOrUnsubscribeRequestPromise::ResolveOrRejectValue& 264 aResult) { aResolver(aResult.IsResolve()); }); 265 266 return IPC_OK(); 267 } 268 269 mozilla::ipc::IPCResult CookieStoreParent::RecvClose() { 270 AssertIsOnBackgroundThread(); 271 272 (void)Send__delete__(this); 273 return IPC_OK(); 274 } 275 276 void CookieStoreParent::GetRequestOnMainThread( 277 const RefPtr<nsIURI> aCookieURI, const OriginAttributes& aOriginAttributes, 278 const Maybe<OriginAttributes>& aPartitionedOriginAttributes, 279 bool aThirdPartyContext, bool aPartitionForeign, bool aUsingStorageAccess, 280 bool aIsOn3PCBExceptionList, bool aMatchName, const nsAString& aName, 281 const nsACString& aPath, bool aOnlyFirstMatch, 282 nsTArray<CookieStruct>& aResults) { 283 nsresult rv; 284 MOZ_ASSERT(NS_IsMainThread()); 285 286 nsCOMPtr<nsICookieService> service = 287 do_GetService(NS_COOKIESERVICE_CONTRACTID); 288 if (!service) { 289 return; 290 } 291 292 nsAutoCString baseDomain; 293 nsCOMPtr<nsIEffectiveTLDService> etld = 294 mozilla::components::EffectiveTLD::Service(); 295 bool requireMatch = false; 296 rv = CookieCommons::GetBaseDomain(etld, aCookieURI, baseDomain, requireMatch); 297 if (NS_FAILED(rv)) { 298 return; 299 } 300 301 nsAutoCString hostName; 302 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aCookieURI, hostName); 303 if (NS_FAILED(rv)) { 304 return; 305 } 306 307 NS_ConvertUTF16toUTF8 matchName(aName); 308 309 nsTArray<OriginAttributes> attrsList; 310 attrsList.AppendElement(aOriginAttributes); 311 312 if (aPartitionedOriginAttributes) { 313 attrsList.AppendElement(aPartitionedOriginAttributes.value()); 314 } 315 316 nsTArray<CookieStruct> list; 317 318 for (const OriginAttributes& attrs : attrsList) { 319 nsTArray<RefPtr<Cookie>> cookies; 320 service->GetCookiesFromHost(baseDomain, attrs, cookies); 321 322 for (Cookie* cookie : cookies) { 323 if (!CookieCommons::DomainMatches(cookie, hostName)) { 324 continue; 325 } 326 if (cookie->IsHttpOnly()) { 327 continue; 328 } 329 330 if (aThirdPartyContext && 331 !CookieCommons::ShouldIncludeCrossSiteCookie( 332 cookie, aCookieURI, aPartitionForeign, attrs.IsPrivateBrowsing(), 333 aUsingStorageAccess, aIsOn3PCBExceptionList)) { 334 continue; 335 } 336 337 if (aMatchName && !matchName.Equals(cookie->Name())) { 338 continue; 339 } 340 341 if (!net::CookieCommons::PathMatches(cookie->Path(), aPath)) { 342 continue; 343 } 344 345 list.AppendElement(cookie->ToIPC()); 346 347 if (aOnlyFirstMatch) { 348 break; 349 } 350 } 351 352 if (!list.IsEmpty() && aOnlyFirstMatch) { 353 break; 354 } 355 } 356 357 aResults.SwapElements(list); 358 } 359 360 CookieStoreParent::SetReturnType CookieStoreParent::SetRequestOnMainThread( 361 ThreadsafeContentParentHandle* aParent, const RefPtr<nsIURI> aCookieURI, 362 const nsAString& aDomain, const OriginAttributes& aOriginAttributes, 363 bool aThirdPartyContext, bool aPartitionForeign, bool aUsingStorageAccess, 364 bool aIsOn3PCBExceptionList, const nsAString& aName, 365 const nsAString& aValue, bool aSession, int64_t aExpires, 366 const nsAString& aPath, int32_t aSameSite, bool aPartitioned, 367 const nsID& aOperationID, bool& aWaitForNotification) { 368 AssertIsOnMainThread(); 369 nsresult rv; 370 371 // By default, no notification should be expected. 372 aWaitForNotification = false; 373 374 NS_ConvertUTF16toUTF8 domain(aDomain); 375 nsAutoCString domainWithDot; 376 377 if (CookiePrefixes::Has(CookiePrefixes::eHttp, aName) || 378 CookiePrefixes::Has(CookiePrefixes::eHostHttp, aName)) { 379 MOZ_DIAGNOSTIC_CRASH("This should not be allowed by CookieStore"); 380 return eSilentFailure; 381 } 382 383 if (CookiePrefixes::Has(CookiePrefixes::eHost, aName) && !domain.IsEmpty()) { 384 MOZ_DIAGNOSTIC_CRASH("This should not be allowed by CookieStore"); 385 return eSilentFailure; 386 } 387 388 // If aDomain is `domain.com` then domainWithDot will be `.domain.com` 389 // Otherwise, when aDomain is empty, domain and domainWithDot will both 390 // be the host of aCookieURI 391 if (!domain.IsEmpty()) { 392 MOZ_ASSERT(!domain.IsEmpty()); 393 domainWithDot.Insert('.', 0); 394 } else { 395 domain.Truncate(); 396 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aCookieURI, domain); 397 if (NS_FAILED(rv)) { 398 return eSilentFailure; 399 } 400 } 401 domainWithDot.Append(domain); 402 403 if (!CheckContentProcessSecurity(aParent, domain, aOriginAttributes)) { 404 return eSilentFailure; 405 } 406 407 if (aThirdPartyContext && 408 !CookieCommons::ShouldIncludeCrossSiteCookie( 409 aCookieURI, aSameSite, 410 aPartitioned && !aOriginAttributes.mPartitionKey.IsEmpty(), 411 aPartitionForeign, aOriginAttributes.IsPrivateBrowsing(), 412 aUsingStorageAccess, aIsOn3PCBExceptionList)) { 413 return eSilentFailure; 414 } 415 416 nsCOMPtr<nsICookieManager> service = 417 do_GetService(NS_COOKIEMANAGER_CONTRACTID); 418 if (!service) { 419 return eSilentFailure; 420 } 421 422 bool notified = false; 423 auto notificationCb = [&]() { notified = true; }; 424 425 CookieStoreNotificationWatcher* notificationWatcher = 426 GetOrCreateNotificationWatcherOnMainThread(aOriginAttributes); 427 if (!notificationWatcher) { 428 return eSilentFailure; 429 } 430 431 notificationWatcher->CallbackWhenNotified(aOperationID, notificationCb); 432 433 auto cleanupNotificationWatcher = MakeScopeExit( 434 [&]() { notificationWatcher->ForgetOperationID(aOperationID); }); 435 436 OriginAttributes attrs(aOriginAttributes); 437 438 nsCOMPtr<nsICookieValidation> validation; 439 rv = service->AddNative( 440 aCookieURI, domainWithDot, NS_ConvertUTF16toUTF8(aPath), 441 NS_ConvertUTF16toUTF8(aName), NS_ConvertUTF16toUTF8(aValue), 442 /* secure: */ true, 443 /* http-only: */ false, aSession, aSession ? INT64_MAX : aExpires, &attrs, 444 aSameSite, nsICookie::SCHEME_HTTPS, aPartitioned, /* from http: */ false, 445 &aOperationID, getter_AddRefs(validation)); 446 447 if (NS_WARN_IF(NS_FAILED(rv))) { 448 if (rv == NS_ERROR_ILLEGAL_VALUE && validation && 449 CookieValidation::Cast(validation)->Result() != 450 nsICookieValidation::eOK) { 451 return eFailure; 452 } 453 454 return eSilentFailure; 455 } 456 457 aWaitForNotification = notified; 458 return eSuccess; 459 } 460 461 bool CookieStoreParent::DeleteRequestOnMainThread( 462 ThreadsafeContentParentHandle* aParent, const RefPtr<nsIURI> aCookieURI, 463 const nsAString& aDomain, const OriginAttributes& aOriginAttributes, 464 bool aThirdPartyContext, bool aPartitionForeign, bool aUsingStorageAccess, 465 bool aIsOn3PCBExceptionList, const nsAString& aName, const nsAString& aPath, 466 bool aPartitioned, const nsID& aOperationID) { 467 MOZ_ASSERT(NS_IsMainThread()); 468 nsresult rv; 469 470 nsAutoCString baseDomain; 471 nsCOMPtr<nsIEffectiveTLDService> etld = 472 mozilla::components::EffectiveTLD::Service(); 473 bool requireMatch = false; 474 rv = CookieCommons::GetBaseDomain(etld, aCookieURI, baseDomain, requireMatch); 475 if (NS_FAILED(rv)) { 476 return false; 477 } 478 479 nsAutoCString hostName; 480 nsContentUtils::GetHostOrIPv6WithBrackets(aCookieURI, hostName); 481 482 nsAutoCString cookiesForDomain; 483 if (aDomain.IsEmpty()) { 484 cookiesForDomain = hostName; 485 } else { 486 cookiesForDomain = NS_ConvertUTF16toUTF8(aDomain); 487 } 488 489 if (!CheckContentProcessSecurity(aParent, cookiesForDomain, 490 aOriginAttributes)) { 491 return false; 492 } 493 494 nsCOMPtr<nsICookieService> service = 495 do_GetService(NS_COOKIESERVICE_CONTRACTID); 496 if (!service) { 497 return false; 498 } 499 nsCOMPtr<nsICookieManager> cookieManager = do_QueryInterface(service); 500 501 NS_ConvertUTF16toUTF8 matchName(aName); 502 NS_ConvertUTF16toUTF8 matchPath(aPath); 503 504 nsTArray<RefPtr<Cookie>> cookies; 505 OriginAttributes attrs(aOriginAttributes); 506 service->GetCookiesFromHost(baseDomain, attrs, cookies); 507 508 for (Cookie* cookie : cookies) { 509 MOZ_ASSERT(cookie); 510 if (!matchName.Equals(cookie->Name())) { 511 continue; 512 } 513 if (!CookieCommons::DomainMatches(cookie, cookiesForDomain)) { 514 continue; 515 } 516 517 if (!matchPath.IsEmpty() && !matchPath.Equals(cookie->Path())) { 518 continue; 519 } 520 521 if (cookie->IsPartitioned() != aPartitioned) continue; 522 523 if (aThirdPartyContext) { 524 int32_t sameSiteAttr = cookie->SameSite(); 525 526 if (!CookieCommons::ShouldIncludeCrossSiteCookie( 527 aCookieURI, sameSiteAttr, 528 aPartitioned && !aOriginAttributes.mPartitionKey.IsEmpty(), 529 aPartitionForeign, attrs.IsPrivateBrowsing(), aUsingStorageAccess, 530 aIsOn3PCBExceptionList)) { 531 return false; 532 } 533 } 534 535 bool notified = false; 536 auto notificationCb = [&]() { notified = true; }; 537 538 CookieStoreNotificationWatcher* notificationWatcher = 539 GetOrCreateNotificationWatcherOnMainThread(aOriginAttributes); 540 if (!notificationWatcher) { 541 return false; 542 } 543 544 notificationWatcher->CallbackWhenNotified(aOperationID, notificationCb); 545 546 auto cleanupNotificationWatcher = MakeScopeExit( 547 [&]() { notificationWatcher->ForgetOperationID(aOperationID); }); 548 549 rv = cookieManager->RemoveNative(cookie->Host(), matchName, cookie->Path(), 550 &attrs, /* from http: */ false, 551 &aOperationID); 552 if (NS_WARN_IF(NS_FAILED(rv))) { 553 return false; 554 } 555 556 return notified; 557 } 558 559 return false; 560 } 561 562 CookieStoreNotificationWatcher* 563 CookieStoreParent::GetOrCreateNotificationWatcherOnMainThread( 564 const OriginAttributes& aOriginAttributes) { 565 MOZ_ASSERT(NS_IsMainThread()); 566 567 if (!mNotificationWatcherOnMainThread) { 568 mNotificationWatcherOnMainThread = CookieStoreNotificationWatcher::Create( 569 aOriginAttributes.IsPrivateBrowsing()); 570 } 571 572 return mNotificationWatcherOnMainThread; 573 } 574 575 } // namespace mozilla::dom