CookieStore.cpp (29577B)
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 "CookieStore.h" 8 9 #include "CookieStoreChild.h" 10 #include "CookieStoreNotificationWatcherWrapper.h" 11 #include "CookieStoreNotifier.h" 12 #include "ThirdPartyUtil.h" 13 #include "mozilla/ScopeExit.h" 14 #include "mozilla/StorageAccess.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/WorkerCommon.h" 18 #include "mozilla/dom/WorkerPrivate.h" 19 #include "mozilla/ipc/BackgroundChild.h" 20 #include "mozilla/ipc/PBackgroundChild.h" 21 #include "mozilla/net/CookieCommons.h" 22 #include "mozilla/net/CookiePrefixes.h" 23 #include "mozilla/net/NeckoChannelParams.h" 24 #include "nsGlobalWindowInner.h" 25 #include "nsICookie.h" 26 #include "nsIGlobalObject.h" 27 #include "nsIPrincipal.h" 28 #include "nsIURL.h" 29 #include "nsReadableUtils.h" 30 #include "nsSandboxFlags.h" 31 32 using namespace mozilla::net; 33 34 namespace mozilla::dom { 35 36 namespace { 37 38 int32_t SameSiteToConst(const CookieSameSite& aSameSite) { 39 switch (aSameSite) { 40 case CookieSameSite::Strict: 41 return nsICookie::SAMESITE_STRICT; 42 case CookieSameSite::Lax: 43 return nsICookie::SAMESITE_LAX; 44 default: 45 MOZ_ASSERT(aSameSite == CookieSameSite::None); 46 return nsICookie::SAMESITE_NONE; 47 } 48 } 49 50 bool ValidateCookieNameOrValue(const nsAString& aStr) { 51 for (auto iter = aStr.BeginReading(), end = aStr.EndReading(); iter < end; 52 ++iter) { 53 if (*iter == 0x3B || *iter == 0x7F || (*iter <= 0x1F && *iter != 0x09)) { 54 return false; 55 } 56 } 57 return true; 58 } 59 60 const nsDependentSubstring TrimTabAndSpace(const nsAString& aStr) { 61 nsAString::const_iterator start, end; 62 63 aStr.BeginReading(start); 64 aStr.EndReading(end); 65 66 auto TabOrSpace = [](char16_t ch) { return ch == 0x09 || ch == 0x20; }; 67 68 while (start != end && TabOrSpace(*start)) { 69 ++start; 70 } 71 72 while (end != start) { 73 --end; 74 75 if (!TabOrSpace(*end)) { 76 ++end; 77 78 break; 79 } 80 } 81 82 return Substring(start, end); 83 } 84 85 bool ValidateCookieNameAndValue(const nsAString& aName, const nsAString& aValue, 86 Promise* aPromise) { 87 MOZ_ASSERT(aPromise); 88 89 if (!ValidateCookieNameOrValue(aName)) { 90 aPromise->MaybeRejectWithTypeError("Cookie name contains invalid chars"); 91 return false; 92 } 93 94 if (!ValidateCookieNameOrValue(aValue)) { 95 aPromise->MaybeRejectWithTypeError("Cookie value contains invalid chars"); 96 return false; 97 } 98 99 if (aName.Contains('=')) { 100 aPromise->MaybeRejectWithTypeError("Cookie name cannot contain '='"); 101 return false; 102 } 103 104 if (aName.IsEmpty() && aValue.Contains('=')) { 105 aPromise->MaybeRejectWithTypeError( 106 "Cookie value cannot contain '=' if the name is empty"); 107 return false; 108 } 109 110 if (aName.IsEmpty() && aValue.IsEmpty()) { 111 aPromise->MaybeRejectWithTypeError( 112 "Cookie name and value both cannot be empty"); 113 return false; 114 } 115 116 if (aName.Length() + aValue.Length() > 4096) { 117 aPromise->MaybeRejectWithTypeError( 118 "Cookie name and value size cannot be greater than 4096 bytes"); 119 return false; 120 } 121 122 return true; 123 } 124 125 bool ValidateCookieDomain(nsIPrincipal* aPrincipal, const nsAString& aName, 126 const nsAString& aDomain, nsAString& aRetDomain, 127 Promise* aPromise) { 128 MOZ_ASSERT(aPromise); 129 130 aRetDomain.Assign(aDomain); 131 ToLowerCase(aRetDomain); 132 133 if (aRetDomain.IsEmpty()) { 134 return true; 135 } 136 137 nsAutoCString utf8Domain; 138 nsresult rv = 139 nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, utf8Domain); 140 if (NS_WARN_IF(NS_FAILED(rv))) { 141 aPromise->MaybeRejectWithNotAllowedError("Permission denied"); 142 return false; 143 } 144 145 // If the name has a __Host- prefix, then aRetDomain must be empty. 146 if (CookiePrefixes::Has(CookiePrefixes::eHost, aName) && 147 !aRetDomain.IsEmpty()) { 148 aPromise->MaybeRejectWithTypeError( 149 "Cookie domain is not allowed for cookies with a __Host- prefix"); 150 return false; 151 } 152 153 if (aRetDomain[0] == '.') { 154 aPromise->MaybeRejectWithTypeError("Cookie domain cannot start with '.'"); 155 return false; 156 } 157 158 NS_ConvertUTF8toUTF16 host(utf8Domain); 159 160 if (host != aRetDomain) { 161 if ((host.Length() < aRetDomain.Length() + 1) || 162 !StringEndsWith(host, aRetDomain) || 163 host[host.Length() - aRetDomain.Length() - 1] != '.') { 164 aPromise->MaybeRejectWithTypeError( 165 "Cookie domain must domain-match current host"); 166 return false; 167 } 168 } 169 170 if (aRetDomain.Length() > 1024) { 171 aPromise->MaybeRejectWithTypeError( 172 "Cookie domain size cannot be greater than 1024 bytes"); 173 return false; 174 } 175 176 return true; 177 } 178 179 bool ValidateExpiresAndMaxAge(const Nullable<double>& aExpires, 180 const Nullable<int64_t>& aMaxAge, 181 int64_t& aComputedExpiry, Promise* aPromise) { 182 MOZ_ASSERT(aPromise); 183 184 if (aExpires.IsNull() && aMaxAge.IsNull()) { 185 // Session cookie 186 aComputedExpiry = INT64_MAX; 187 return true; 188 } 189 190 if (!aExpires.IsNull() && !aMaxAge.IsNull()) { 191 aPromise->MaybeRejectWithTypeError( 192 "Cookie expires and maxAge attributes cannot both be set"); 193 return false; 194 } 195 196 int64_t creationTimeInMSec = PR_Now() / PR_USEC_PER_MSEC; 197 198 if (!aExpires.IsNull()) { 199 aComputedExpiry = 200 CookieCommons::MaybeCapExpiry(creationTimeInMSec, aExpires.Value()); 201 } else { 202 aComputedExpiry = 203 CookieCommons::MaybeCapMaxAge(creationTimeInMSec, aMaxAge.Value()); 204 } 205 206 return true; 207 } 208 209 bool ValidateCookiePath(nsIURI* aURI, const nsAString& aPath, 210 nsAString& aRetPath, Promise* aPromise) { 211 MOZ_ASSERT(aURI); 212 MOZ_ASSERT(aPromise); 213 214 aRetPath = aPath; 215 216 if (aRetPath.IsEmpty()) { 217 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); 218 219 if (!url) { 220 aPromise->MaybeRejectWithNotAllowedError("Permission denied"); 221 return false; 222 } 223 224 nsAutoCString directory; 225 nsresult rv = url->GetDirectory(directory); 226 227 if (NS_WARN_IF(NS_FAILED(rv))) { 228 aPromise->MaybeRejectWithNotAllowedError("Permission denied"); 229 return false; 230 } 231 232 if (!directory.IsEmpty() && directory.Last() == '/') { 233 directory.Truncate(directory.Length() - 1); 234 } 235 236 CopyUTF8toUTF16(directory, aRetPath); 237 } 238 239 if (aRetPath[0] != '/') { 240 aPromise->MaybeRejectWithTypeError("Cookie path must start with '/'"); 241 return false; 242 } 243 244 if (aRetPath.Length() > 1024) { 245 aPromise->MaybeRejectWithTypeError( 246 "Cookie domain size cannot be greater than 1024 bytes"); 247 return false; 248 } 249 250 return true; 251 } 252 253 // Reject cookies whose name starts with the magic prefixes from 254 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis 255 // if they do not meet the criteria required by the prefix. 256 bool ValidateCookieNamePrefix(const nsAString& aName, const nsAString& aValue, 257 const nsAString& aOptionDomain, 258 const nsAString& aPath, Promise* aPromise) { 259 MOZ_ASSERT(aPromise); 260 261 if (aName.IsEmpty() && 262 (CookiePrefixes::Has(CookiePrefixes::eHost, aValue) || 263 CookiePrefixes::Has(CookiePrefixes::eHostHttp, aValue) || 264 CookiePrefixes::Has(CookiePrefixes::eHttp, aValue) || 265 CookiePrefixes::Has(CookiePrefixes::eSecure, aValue))) { 266 aPromise->MaybeRejectWithTypeError( 267 "Nameless cookies should not begin with special prefixes"); 268 return false; 269 } 270 271 if (CookiePrefixes::Has(CookiePrefixes::eHttp, aName) || 272 CookiePrefixes::Has(CookiePrefixes::eHostHttp, aName)) { 273 aPromise->MaybeRejectWithTypeError( 274 "Cookie name should not begin with special prefixes"); 275 return false; 276 } 277 278 if (!CookiePrefixes::Has(CookiePrefixes::eHost, aName)) { 279 return true; 280 } 281 282 if (!aOptionDomain.IsEmpty()) { 283 aPromise->MaybeRejectWithTypeError( 284 "Cookie domain cannot be used when the cookie name uses special " 285 "prefixes"); 286 return false; 287 } 288 289 if (!aPath.EqualsLiteral("/")) { 290 aPromise->MaybeRejectWithTypeError( 291 "Cookie path cannot be different than '/' when the cookie name uses " 292 "special prefixes"); 293 return false; 294 } 295 296 return true; 297 } 298 299 void CookieStructToList(const nsTArray<CookieStruct>& aData, 300 nsTArray<CookieListItem>& aResult) { 301 for (const CookieStruct& data : aData) { 302 CookieListItem* item = aResult.AppendElement(); 303 CookieStore::CookieStructToItem(data, item); 304 } 305 } 306 307 void ResolvePromiseAsync(Promise* aPromise) { 308 MOZ_ASSERT(aPromise); 309 310 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 311 __func__, 312 [promise = RefPtr(aPromise)] { promise->MaybeResolveWithUndefined(); })); 313 } 314 315 bool GetContextAttributes(CookieStore* aCookieStore, bool* aThirdPartyContext, 316 bool* aPartitionForeign, bool* aUsingStorageAccess, 317 bool* aIsOn3PCBExceptionList, Promise* aPromise) { 318 MOZ_ASSERT(aCookieStore); 319 MOZ_ASSERT(aThirdPartyContext); 320 MOZ_ASSERT(aPartitionForeign); 321 MOZ_ASSERT(aUsingStorageAccess); 322 MOZ_ASSERT(aIsOn3PCBExceptionList); 323 MOZ_ASSERT(aPromise); 324 325 if (NS_IsMainThread()) { 326 nsCOMPtr<nsPIDOMWindowInner> window = aCookieStore->GetOwnerWindow(); 327 if (NS_WARN_IF(!window)) { 328 aPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 329 return false; 330 } 331 332 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); 333 if (thirdPartyUtil) { 334 (void)thirdPartyUtil->IsThirdPartyWindow(window->GetOuterWindow(), 335 nullptr, aThirdPartyContext); 336 } 337 338 nsCOMPtr<Document> document = window->GetExtantDoc(); 339 if (NS_WARN_IF(!document)) { 340 aPromise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 341 return false; 342 } 343 344 *aPartitionForeign = document->CookieJarSettings()->GetPartitionForeign(); 345 *aUsingStorageAccess = document->UsingStorageAccess(); 346 *aIsOn3PCBExceptionList = document->IsOn3PCBExceptionList(); 347 return true; 348 } 349 350 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 351 MOZ_ASSERT(workerPrivate); 352 353 *aThirdPartyContext = workerPrivate->IsThirdPartyContext(); 354 *aPartitionForeign = 355 workerPrivate->CookieJarSettings()->GetPartitionForeign(); 356 *aUsingStorageAccess = workerPrivate->UsingStorageAccess(); 357 *aIsOn3PCBExceptionList = workerPrivate->IsOn3PCBExceptionList(); 358 return true; 359 } 360 361 } // namespace 362 363 NS_IMPL_CYCLE_COLLECTION_CLASS(CookieStore) 364 365 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CookieStore, 366 DOMEventTargetHelper) 367 tmp->Shutdown(); 368 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 369 370 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CookieStore, 371 DOMEventTargetHelper) 372 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 373 374 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CookieStore) 375 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 376 377 NS_IMPL_ADDREF_INHERITED(CookieStore, DOMEventTargetHelper) 378 NS_IMPL_RELEASE_INHERITED(CookieStore, DOMEventTargetHelper) 379 380 // static 381 already_AddRefed<CookieStore> CookieStore::Create(nsIGlobalObject* aGlobal) { 382 return do_AddRef(new CookieStore(aGlobal)); 383 } 384 385 CookieStore::CookieStore(nsIGlobalObject* aGlobal) 386 : DOMEventTargetHelper(aGlobal) { 387 if (NS_IsMainThread()) { 388 mNotifier = CookieStoreNotifier::Create(this); 389 } 390 391 // This must be created _after_ CookieStoreNotifier because we rely on the 392 // notification order. 393 mNotificationWatcher = CookieStoreNotificationWatcherWrapper::Create(this); 394 } 395 396 CookieStore::~CookieStore() { Shutdown(); } 397 398 JSObject* CookieStore::WrapObject(JSContext* aCx, 399 JS::Handle<JSObject*> aGivenProto) { 400 return CookieStore_Binding::Wrap(aCx, this, aGivenProto); 401 } 402 403 already_AddRefed<Promise> CookieStore::Get(const nsAString& aName, 404 ErrorResult& aRv) { 405 CookieStoreGetOptions options; 406 options.mName.Construct(aName); 407 return Get(options, aRv); 408 } 409 410 already_AddRefed<Promise> CookieStore::Get( 411 const CookieStoreGetOptions& aOptions, ErrorResult& aRv) { 412 if (!aOptions.mName.WasPassed() && !aOptions.mUrl.WasPassed()) { 413 aRv.ThrowTypeError("CookieStoreGetOptions must not be empty"); 414 return nullptr; 415 } 416 417 return GetInternal(aOptions, true, aRv); 418 } 419 420 already_AddRefed<Promise> CookieStore::GetAll(const nsAString& aName, 421 ErrorResult& aRv) { 422 CookieStoreGetOptions options; 423 options.mName.Construct(aName); 424 return GetAll(options, aRv); 425 } 426 427 already_AddRefed<Promise> CookieStore::GetAll( 428 const CookieStoreGetOptions& aOptions, ErrorResult& aRv) { 429 return GetInternal(aOptions, false, aRv); 430 } 431 432 already_AddRefed<Promise> CookieStore::Set(const nsAString& aName, 433 const nsAString& aValue, 434 ErrorResult& aRv) { 435 CookieInit init; 436 init.mName = aName; 437 init.mValue = aValue; 438 return Set(init, aRv); 439 } 440 441 already_AddRefed<Promise> CookieStore::Set(const CookieInit& aOptions, 442 ErrorResult& aRv) { 443 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv); 444 if (NS_WARN_IF(!promise)) { 445 return nullptr; 446 } 447 448 nsCOMPtr<nsIPrincipal> cookiePrincipal; 449 switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 450 MaybeGetDocument(), getter_AddRefs(cookiePrincipal), nullptr)) { 451 case CookieCommons::SecurityChecksResult::eSandboxedError: 452 [[fallthrough]]; 453 454 case CookieCommons::SecurityChecksResult::eSecurityError: 455 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 456 return nullptr; 457 458 case CookieCommons::SecurityChecksResult::eDoNotContinue: 459 ResolvePromiseAsync(promise); 460 return promise.forget(); 461 462 case CookieCommons::SecurityChecksResult::eContinue: 463 MOZ_ASSERT(cookiePrincipal); 464 break; 465 } 466 467 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 468 __func__, [self = RefPtr(this), promise = RefPtr(promise), aOptions, 469 cookiePrincipal = RefPtr(cookiePrincipal.get())]() { 470 nsCOMPtr<nsIURI> cookieURI = cookiePrincipal->GetURI(); 471 472 nsString name(TrimTabAndSpace(aOptions.mName)); 473 nsString value(TrimTabAndSpace(aOptions.mValue)); 474 475 if (!ValidateCookieNameAndValue(name, value, promise)) { 476 return; 477 } 478 479 nsString domain; 480 if (!ValidateCookieDomain(cookiePrincipal, name, aOptions.mDomain, 481 domain, promise)) { 482 return; 483 } 484 485 int64_t expiry; 486 if (!ValidateExpiresAndMaxAge(aOptions.mExpires, aOptions.mMaxAge, 487 expiry, promise)) { 488 return; 489 } 490 491 nsString path; 492 if (!ValidateCookiePath(cookieURI, aOptions.mPath, path, promise)) { 493 return; 494 } 495 496 if (!ValidateCookieNamePrefix(name, value, domain, path, promise)) { 497 return; 498 } 499 500 bool thirdPartyContext = true; 501 bool partitionForeign = true; 502 bool usingStorageAccess = false; 503 bool isOn3PCBExceptionList = false; 504 505 if (!GetContextAttributes(self, &thirdPartyContext, &partitionForeign, 506 &usingStorageAccess, &isOn3PCBExceptionList, 507 promise)) { 508 return; 509 } 510 511 if (!self->MaybeCreateActor()) { 512 promise->MaybeRejectWithNotAllowedError("Permission denied"); 513 return; 514 } 515 516 if (!self->mNotificationWatcher) { 517 promise->MaybeReject(NS_ERROR_UNEXPECTED); 518 return; 519 } 520 521 nsID operationID; 522 nsresult rv = nsID::GenerateUUIDInPlace(operationID); 523 if (NS_WARN_IF(NS_FAILED(rv))) { 524 promise->MaybeReject(NS_ERROR_UNEXPECTED); 525 return; 526 } 527 528 self->mNotificationWatcher->ResolvePromiseWhenNotified(operationID, 529 promise); 530 531 RefPtr<CookieStoreChild::SetRequestPromise> ipcPromise = 532 self->mActor->SendSetRequest( 533 mozilla::WrapNotNull(cookieURI.get()), 534 cookiePrincipal->OriginAttributesRef(), thirdPartyContext, 535 partitionForeign, usingStorageAccess, isOn3PCBExceptionList, 536 name, value, /* session cookie: */ expiry == INT64_MAX, expiry, 537 domain, path, SameSiteToConst(aOptions.mSameSite), 538 aOptions.mPartitioned, operationID); 539 if (NS_WARN_IF(!ipcPromise)) { 540 promise->MaybeResolveWithUndefined(); 541 return; 542 } 543 544 ipcPromise->Then( 545 NS_GetCurrentThread(), __func__, 546 [promise = RefPtr<dom::Promise>(promise), self = RefPtr(self), 547 operationID]( 548 const CookieStoreChild::SetRequestPromise::ResolveOrRejectValue& 549 aResult) { 550 auto cleanupNotificationWatcher = MakeScopeExit([&]() { 551 self->mNotificationWatcher->ForgetOperationID(operationID); 552 }); 553 554 if (!aResult.IsResolve()) { 555 promise->MaybeResolveWithUndefined(); 556 return; 557 } 558 559 const CookieStoreResult& result = aResult.ResolveValue(); 560 if (!result.success()) { 561 promise->MaybeRejectWithTypeError("Invalid cookie"); 562 return; 563 } 564 565 if (!result.waitForNotification()) { 566 promise->MaybeResolveWithUndefined(); 567 return; 568 } 569 570 cleanupNotificationWatcher.release(); 571 }); 572 })); 573 574 return promise.forget(); 575 } 576 577 already_AddRefed<Promise> CookieStore::Delete(const nsAString& aName, 578 ErrorResult& aRv) { 579 CookieStoreDeleteOptions options; 580 options.mName = aName; 581 return Delete(options, aRv); 582 } 583 584 already_AddRefed<Promise> CookieStore::Delete( 585 const CookieStoreDeleteOptions& aOptions, ErrorResult& aRv) { 586 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv); 587 if (NS_WARN_IF(!promise)) { 588 return nullptr; 589 } 590 591 nsCOMPtr<nsIPrincipal> cookiePrincipal; 592 switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 593 MaybeGetDocument(), getter_AddRefs(cookiePrincipal), nullptr)) { 594 case CookieCommons::SecurityChecksResult::eSandboxedError: 595 [[fallthrough]]; 596 597 case CookieCommons::SecurityChecksResult::eSecurityError: 598 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 599 return nullptr; 600 601 case CookieCommons::SecurityChecksResult::eDoNotContinue: 602 ResolvePromiseAsync(promise); 603 return promise.forget(); 604 605 case CookieCommons::SecurityChecksResult::eContinue: 606 MOZ_ASSERT(cookiePrincipal); 607 break; 608 } 609 610 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 611 __func__, [self = RefPtr(this), promise = RefPtr(promise), aOptions, 612 cookiePrincipal = RefPtr(cookiePrincipal.get())]() { 613 nsCOMPtr<nsIURI> cookieURI = cookiePrincipal->GetURI(); 614 nsString name(TrimTabAndSpace(aOptions.mName)); 615 616 nsString domain; 617 if (!ValidateCookieDomain(cookiePrincipal, name, aOptions.mDomain, 618 domain, promise)) { 619 return; 620 } 621 622 nsString path; 623 if (!ValidateCookiePath(cookieURI, aOptions.mPath, path, promise)) { 624 return; 625 } 626 627 if (!ValidateCookieNamePrefix(name, u""_ns, domain, path, promise)) { 628 return; 629 } 630 631 bool thirdPartyContext = true; 632 bool partitionForeign = true; 633 bool usingStorageAccess = false; 634 bool isOn3PCBExceptionList = false; 635 636 if (!GetContextAttributes(self, &thirdPartyContext, &partitionForeign, 637 &usingStorageAccess, &isOn3PCBExceptionList, 638 promise)) { 639 return; 640 } 641 642 if (!self->MaybeCreateActor()) { 643 promise->MaybeRejectWithNotAllowedError("Permission denied"); 644 return; 645 } 646 647 if (!self->mNotificationWatcher) { 648 promise->MaybeReject(NS_ERROR_UNEXPECTED); 649 return; 650 } 651 652 nsID operationID; 653 nsresult rv = nsID::GenerateUUIDInPlace(operationID); 654 if (NS_WARN_IF(NS_FAILED(rv))) { 655 promise->MaybeReject(NS_ERROR_UNEXPECTED); 656 return; 657 } 658 659 self->mNotificationWatcher->ResolvePromiseWhenNotified(operationID, 660 promise); 661 RefPtr<CookieStoreChild::DeleteRequestPromise> ipcPromise = 662 self->mActor->SendDeleteRequest( 663 mozilla::WrapNotNull(cookieURI.get()), 664 cookiePrincipal->OriginAttributesRef(), thirdPartyContext, 665 partitionForeign, usingStorageAccess, isOn3PCBExceptionList, 666 name, domain, path, aOptions.mPartitioned, operationID); 667 if (NS_WARN_IF(!ipcPromise)) { 668 promise->MaybeResolveWithUndefined(); 669 return; 670 } 671 672 ipcPromise->Then( 673 NS_GetCurrentThread(), __func__, 674 [promise = RefPtr<dom::Promise>(promise), self = RefPtr(self), 675 operationID](const CookieStoreChild::DeleteRequestPromise:: 676 ResolveOrRejectValue& aResult) { 677 if (!aResult.IsResolve() || !aResult.ResolveValue()) { 678 self->mNotificationWatcher->ForgetOperationID(operationID); 679 promise->MaybeResolveWithUndefined(); 680 } 681 }); 682 })); 683 684 return promise.forget(); 685 } 686 687 void CookieStore::Shutdown() { 688 if (mActor) { 689 mActor->Close(); 690 mActor = nullptr; 691 } 692 693 if (mNotifier) { 694 mNotifier->Disentangle(); 695 mNotifier = nullptr; 696 } 697 } 698 699 bool CookieStore::MaybeCreateActor() { 700 if (mActor) { 701 return mActor->CanSend(); 702 } 703 704 mozilla::ipc::PBackgroundChild* actorChild = 705 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); 706 if (NS_WARN_IF(!actorChild)) { 707 // The process is probably shutting down. Let's return a 'generic' error. 708 return false; 709 } 710 711 PCookieStoreChild* actor = actorChild->SendPCookieStoreConstructor(); 712 if (!actor) { 713 return false; 714 } 715 716 mActor = static_cast<CookieStoreChild*>(actor); 717 718 return true; 719 } 720 721 already_AddRefed<Promise> CookieStore::GetInternal( 722 const CookieStoreGetOptions& aOptions, bool aOnlyTheFirstMatch, 723 ErrorResult& aRv) { 724 RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv); 725 if (NS_WARN_IF(!promise)) { 726 return nullptr; 727 } 728 729 nsCOMPtr<nsIPrincipal> cookiePrincipal; 730 nsCOMPtr<nsIPrincipal> partitionedCookiePrincipal; 731 switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 732 MaybeGetDocument(), getter_AddRefs(cookiePrincipal), 733 getter_AddRefs(partitionedCookiePrincipal))) { 734 case CookieCommons::SecurityChecksResult::eSandboxedError: 735 [[fallthrough]]; 736 737 case CookieCommons::SecurityChecksResult::eSecurityError: 738 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 739 return nullptr; 740 741 case CookieCommons::SecurityChecksResult::eDoNotContinue: 742 ResolvePromiseAsync(promise); 743 return promise.forget(); 744 745 case CookieCommons::SecurityChecksResult::eContinue: 746 MOZ_ASSERT(cookiePrincipal); 747 break; 748 } 749 750 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 751 __func__, 752 [self = RefPtr(this), promise = RefPtr(promise), aOptions, 753 cookiePrincipal = RefPtr(cookiePrincipal.get()), 754 partitionedCookiePrincipal = RefPtr(partitionedCookiePrincipal.get()), 755 aOnlyTheFirstMatch]() { 756 nsAutoString name; 757 if (aOptions.mName.WasPassed()) { 758 name = TrimTabAndSpace(aOptions.mName.Value()); 759 } 760 761 nsAutoCString path; 762 nsresult rv = cookiePrincipal->GetFilePath(path); 763 if (NS_WARN_IF(NS_FAILED(rv))) { 764 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 765 return; 766 } 767 768 if (aOptions.mUrl.WasPassed()) { 769 nsString url(aOptions.mUrl.Value()); 770 771 if (NS_IsMainThread()) { 772 nsCOMPtr<nsPIDOMWindowInner> window = self->GetOwnerWindow(); 773 if (NS_WARN_IF(!window)) { 774 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 775 return; 776 } 777 778 nsCOMPtr<Document> document = window->GetExtantDoc(); 779 if (NS_WARN_IF(!document)) { 780 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 781 return; 782 } 783 784 nsIURI* creationURI = document->GetOriginalURI(); 785 if (NS_WARN_IF(!creationURI)) { 786 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 787 return; 788 } 789 790 nsCOMPtr<nsIURI> resolvedURI; 791 rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr, 792 creationURI); 793 if (NS_WARN_IF(NS_FAILED(rv))) { 794 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 795 NS_ConvertUTF16toUTF8(url)); 796 return; 797 } 798 799 bool equal = false; 800 if (!resolvedURI || 801 NS_WARN_IF(NS_FAILED( 802 resolvedURI->EqualsExceptRef(creationURI, &equal))) || 803 !equal) { 804 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 805 NS_ConvertUTF16toUTF8(url)); 806 return; 807 } 808 } else { 809 nsCOMPtr<nsIURI> baseURI = cookiePrincipal->GetURI(); 810 if (NS_WARN_IF(!baseURI)) { 811 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 812 return; 813 } 814 815 nsCOMPtr<nsIURI> resolvedURI; 816 rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr, baseURI); 817 if (NS_WARN_IF(NS_FAILED(rv))) { 818 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 819 NS_ConvertUTF16toUTF8(url)); 820 return; 821 } 822 823 if (!cookiePrincipal->IsSameOrigin(resolvedURI)) { 824 promise->MaybeRejectWithTypeError<MSG_INVALID_URL>( 825 NS_ConvertUTF16toUTF8(url)); 826 return; 827 } 828 829 rv = resolvedURI->GetFilePath(path); 830 if (NS_WARN_IF(NS_FAILED(rv))) { 831 promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); 832 return; 833 } 834 } 835 } 836 837 bool thirdPartyContext = true; 838 bool partitionForeign = true; 839 bool usingStorageAccess = false; 840 bool isOn3PCBExceptionList = false; 841 842 if (!GetContextAttributes(self, &thirdPartyContext, &partitionForeign, 843 &usingStorageAccess, &isOn3PCBExceptionList, 844 promise)) { 845 return; 846 } 847 848 if (!self->MaybeCreateActor()) { 849 promise->MaybeRejectWithNotAllowedError("Permission denied"); 850 return; 851 } 852 853 nsAutoCString baseDomain; 854 rv = net::CookieCommons::GetBaseDomain(cookiePrincipal, baseDomain); 855 if (NS_WARN_IF(NS_FAILED(rv))) { 856 promise->MaybeRejectWithNotAllowedError("Permission denied"); 857 return; 858 } 859 860 nsCOMPtr<nsIURI> cookieURI = cookiePrincipal->GetURI(); 861 RefPtr<CookieStoreChild::GetRequestPromise> ipcPromise = 862 self->mActor->SendGetRequest( 863 mozilla::WrapNotNull(cookieURI.get()), 864 cookiePrincipal->OriginAttributesRef(), 865 partitionedCookiePrincipal 866 ? Some(partitionedCookiePrincipal->OriginAttributesRef()) 867 : Nothing(), 868 thirdPartyContext, partitionForeign, usingStorageAccess, 869 isOn3PCBExceptionList, aOptions.mName.WasPassed(), name, path, 870 aOnlyTheFirstMatch); 871 if (NS_WARN_IF(!ipcPromise)) { 872 promise->MaybeResolveWithUndefined(); 873 return; 874 } 875 876 ipcPromise->Then( 877 NS_GetCurrentThread(), __func__, 878 [promise = RefPtr<dom::Promise>(promise), aOnlyTheFirstMatch]( 879 const CookieStoreChild::GetRequestPromise::ResolveOrRejectValue& 880 aResult) { 881 if (!aResult.IsResolve()) { 882 promise->MaybeResolveWithUndefined(); 883 return; 884 } 885 886 nsTArray<CookieListItem> list; 887 CookieStructToList(aResult.ResolveValue(), list); 888 889 if (!aOnlyTheFirstMatch) { 890 promise->MaybeResolve(list); 891 return; 892 } 893 894 if (list.IsEmpty()) { 895 promise->MaybeResolve(JS::NullHandleValue); 896 return; 897 } 898 899 promise->MaybeResolve(list[0]); 900 }); 901 })); 902 903 return promise.forget(); 904 } 905 906 void CookieStore::FireDelayedDOMEvents() { 907 MOZ_ASSERT(NS_IsMainThread()); 908 909 if (mNotifier) { 910 mNotifier->FireDelayedDOMEvents(); 911 } 912 } 913 914 Document* CookieStore::MaybeGetDocument() const { 915 if (NS_IsMainThread()) { 916 nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow(); 917 MOZ_ASSERT(window); 918 return window->GetExtantDoc(); 919 } 920 921 return nullptr; 922 } 923 924 // static 925 void CookieStore::CookieStructToItem(const CookieStruct& aData, 926 CookieListItem* aItem) { 927 aItem->mName.Construct(aData.name()); 928 aItem->mValue.Construct(aData.value()); 929 } 930 931 } // namespace mozilla::dom