Bug 1475599 - part 2 - CookieStore API - IPDL, r=edgul,webidl,smaug

Differential Revision: https://phabricator.services.mozilla.com/D215145
This commit is contained in:
Andrea Marchesini 2024-09-12 16:48:19 +00:00
Родитель c6b5fb82a2
Коммит 4b3f5d6dbb
26 изменённых файлов: 1666 добавлений и 117 удалений

Просмотреть файл

@ -6551,43 +6551,28 @@ void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
aCookie.Truncate(); // clear current cookie in case service fails;
// no cookie isn't an error condition.
if (mDisableCookieAccess) {
return;
}
nsCOMPtr<nsIPrincipal> cookiePrincipal;
nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal;
// If the document's sandboxed origin flag is set, then reading cookies
// is prohibited.
if (mSandboxFlags & SANDBOXED_ORIGIN) {
aRv.ThrowSecurityError(
"Forbidden in a sandboxed document without the 'allow-same-origin' "
"flag.");
return;
}
// GTests do not create an inner window and because of these a few security
// checks will block this method.
if (!StaticPrefs::dom_cookie_testing_enabled()) {
StorageAccess storageAccess = CookieAllowedForDocument(this);
if (storageAccess == StorageAccess::eDeny) {
CookieCommons::SecurityChecksResult checkResult =
CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
this, getter_AddRefs(cookiePrincipal),
getter_AddRefs(cookiePartitionedPrincipal));
switch (checkResult) {
case CookieCommons::SecurityChecksResult::eSandboxedError:
aRv.ThrowSecurityError(
"Forbidden in a sandboxed document without the 'allow-same-origin' "
"flag.");
return;
}
if (ShouldPartitionStorage(storageAccess) &&
!StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
case CookieCommons::SecurityChecksResult::eSecurityError:
[[fallthrough]];
case CookieCommons::SecurityChecksResult::eDoNotContinue:
return;
}
// If the document is a cookie-averse Document... return the empty string.
if (IsCookieAverse()) {
return;
}
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service =
do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (!service) {
return;
case CookieCommons::SecurityChecksResult::eContinue:
break;
}
bool thirdParty = true;
@ -6603,35 +6588,13 @@ void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
}
}
nsCOMPtr<nsIPrincipal> cookiePrincipal = EffectiveCookiePrincipal();
nsTArray<nsCOMPtr<nsIPrincipal>> principals;
MOZ_ASSERT(cookiePrincipal);
principals.AppendElement(cookiePrincipal);
// CHIPS - If CHIPS is enabled the partitioned cookie jar is always available
// (and therefore the partitioned principal), the unpartitioned cookie jar is
// only available in first-party or third-party with storageAccess contexts.
// In both cases, the document will have storage access.
bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() &&
CookieJarSettings()->GetPartitionForeign();
bool documentHasStorageAccess = false;
nsresult rv = HasStorageAccessSync(documentHasStorageAccess);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (isCHIPS && documentHasStorageAccess) {
// Assert that the cookie principal is unpartitioned.
MOZ_ASSERT(cookiePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
// Only append the partitioned originAttributes if the partitionKey is set.
// The partitionKey could be empty for partitionKey in partitioned
// originAttributes if the document is for privilege context, such as the
// extension's background page.
if (!PartitionedPrincipal()
->OriginAttributesRef()
.mPartitionKey.IsEmpty()) {
principals.AppendElement(PartitionedPrincipal());
}
if (cookiePartitionedPrincipal) {
principals.AppendElement(cookiePartitionedPrincipal);
}
nsTArray<RefPtr<Cookie>> cookieList;
@ -6639,13 +6602,16 @@ void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
int64_t currentTimeInUsec = PR_Now();
int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
for (auto& principal : principals) {
if (!CookieCommons::IsSchemeSupported(principal)) {
return;
}
// not having a cookie service isn't an error
nsCOMPtr<nsICookieService> service =
do_GetService(NS_COOKIESERVICE_CONTRACTID);
if (!service) {
return;
}
for (auto& principal : principals) {
nsAutoCString baseDomain;
rv = CookieCommons::GetBaseDomain(principal, baseDomain);
nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
@ -6740,32 +6706,26 @@ void Document::GetCookie(nsAString& aCookie, ErrorResult& aRv) {
}
void Document::SetCookie(const nsAString& aCookieString, ErrorResult& aRv) {
if (mDisableCookieAccess) {
return;
}
nsCOMPtr<nsIPrincipal> cookiePrincipal;
// If the document's sandboxed origin flag is set, then setting cookies
// is prohibited.
if (mSandboxFlags & SANDBOXED_ORIGIN) {
aRv.ThrowSecurityError(
"Forbidden in a sandboxed document without the 'allow-same-origin' "
"flag.");
return;
}
CookieCommons::SecurityChecksResult checkResult =
CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
this, getter_AddRefs(cookiePrincipal), nullptr);
switch (checkResult) {
case CookieCommons::SecurityChecksResult::eSandboxedError:
aRv.ThrowSecurityError(
"Forbidden in a sandboxed document without the 'allow-same-origin' "
"flag.");
return;
StorageAccess storageAccess = CookieAllowedForDocument(this);
if (storageAccess == StorageAccess::eDeny) {
return;
}
case CookieCommons::SecurityChecksResult::eSecurityError:
[[fallthrough]];
if (ShouldPartitionStorage(storageAccess) &&
!StoragePartitioningEnabled(storageAccess, CookieJarSettings())) {
return;
}
case CookieCommons::SecurityChecksResult::eDoNotContinue:
return;
// If the document is a cookie-averse Document... do nothing.
if (IsCookieAverse()) {
return;
case CookieCommons::SecurityChecksResult::eContinue:
break;
}
if (!mDocumentURI) {

Просмотреть файл

@ -1098,6 +1098,8 @@ class Document : public nsINode,
*/
void DisableCookieAccess() { mDisableCookieAccess = true; }
bool CookieAccessDisabled() const { return mDisableCookieAccess; }
void SetLinkHandlingEnabled(bool aValue) { mLinksEnabled = aValue; }
bool LinkHandlingEnabled() { return mLinksEnabled; }

Просмотреть файл

@ -5932,6 +5932,10 @@ nsresult nsGlobalWindowInner::FireDelayedDOMEvents(bool aIncludeSubWindows) {
// Fires an offline status event if the offline status has changed
FireOfflineStatusEventIfChanged();
if (mCookieStore) {
mCookieStore->FireDelayedDOMEvents();
}
if (!aIncludeSubWindows) {
return NS_OK;
}

Просмотреть файл

@ -50,4 +50,30 @@ void CookieChangeEvent::GetDeleted(nsTArray<CookieListItem>& aList) const {
return event.forget();
}
/* static */ already_AddRefed<CookieChangeEvent>
CookieChangeEvent::CreateForChangedCookie(EventTarget* aEventTarget,
const CookieListItem& aItem) {
RefPtr<CookieChangeEvent> event =
new CookieChangeEvent(aEventTarget, nullptr, nullptr);
event->InitEvent(u"change"_ns, false, false);
event->SetTrusted(true);
event->mChanged.AppendElement(aItem);
return event.forget();
}
/* static */ already_AddRefed<CookieChangeEvent>
CookieChangeEvent::CreateForDeletedCookie(EventTarget* aEventTarget,
const CookieListItem& aItem) {
RefPtr<CookieChangeEvent> event =
new CookieChangeEvent(aEventTarget, nullptr, nullptr);
event->InitEvent(u"change"_ns, false, false);
event->SetTrusted(true);
event->mDeleted.AppendElement(aItem);
return event.forget();
}
} // namespace mozilla::dom

Просмотреть файл

@ -29,6 +29,12 @@ class CookieChangeEvent final : public Event {
void GetDeleted(nsTArray<CookieListItem>& aList) const;
static already_AddRefed<CookieChangeEvent> CreateForChangedCookie(
EventTarget* aEventTarget, const CookieListItem& aItem);
static already_AddRefed<CookieChangeEvent> CreateForDeletedCookie(
EventTarget* aEventTarget, const CookieListItem& aItem);
private:
~CookieChangeEvent();

Просмотреть файл

@ -5,16 +5,200 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CookieStore.h"
#include "CookieStoreChild.h"
#include "CookieStoreNotifier.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/net/CookieCommons.h"
#include "mozilla/StorageAccess.h"
#include "nsICookie.h"
#include "nsIGlobalObject.h"
#include "nsIPrincipal.h"
#include "nsReadableUtils.h"
#include "nsSandboxFlags.h"
using namespace mozilla::net;
namespace mozilla::dom {
namespace {
int32_t SameSiteToConst(const CookieSameSite& aSameSite) {
switch (aSameSite) {
case CookieSameSite::Strict:
return nsICookie::SAMESITE_STRICT;
case CookieSameSite::Lax:
return nsICookie::SAMESITE_LAX;
default:
MOZ_ASSERT(aSameSite == CookieSameSite::None);
return nsICookie::SAMESITE_NONE;
}
}
bool ValidateCookieNameOrValue(const nsAString& aStr) {
for (auto iter = aStr.BeginReading(), end = aStr.EndReading(); iter < end;
++iter) {
if (*iter == 0x3B || *iter == 0x7F || (*iter <= 0x1F && *iter != 0x09)) {
return false;
}
}
return true;
}
bool ValidateCookieNameAndValue(const nsAString& aName, const nsAString& aValue,
Promise* aPromise) {
MOZ_ASSERT(aPromise);
if (!ValidateCookieNameOrValue(aName)) {
aPromise->MaybeRejectWithTypeError("Cookie name contains invalid chars");
return false;
}
if (!ValidateCookieNameOrValue(aValue)) {
aPromise->MaybeRejectWithTypeError("Cookie value contains invalid chars");
return false;
}
if (aName.IsEmpty() && aValue.Contains('=')) {
aPromise->MaybeRejectWithTypeError(
"Cookie value cannot contain '=' if the name is empty");
return false;
}
if (aName.IsEmpty() && aValue.IsEmpty()) {
aPromise->MaybeRejectWithTypeError(
"Cookie name and value both cannot be empty");
return false;
}
if (aName.Length() + aValue.Length() > 1024) {
aPromise->MaybeRejectWithTypeError(
"Cookie name and value size cannot be greater than 1024 bytes");
return false;
}
return true;
}
bool ValidateCookieDomain(const nsAString& aHost, const nsAString& aDomain,
Promise* aPromise) {
MOZ_ASSERT(aPromise);
if (aDomain.IsEmpty()) {
return true;
}
if (aDomain[0] == '.') {
aPromise->MaybeRejectWithTypeError("Cookie domain cannot start with '.'");
return false;
}
if (aHost != aDomain) {
if ((aHost.Length() < aDomain.Length() + 1) ||
!StringEndsWith(aHost, aDomain) ||
aHost[aHost.Length() - aDomain.Length() - 1] != '.') {
aPromise->MaybeRejectWithTypeError(
"Cookie domain must domain-match current host");
return false;
}
}
if (aDomain.Length() > 1024) {
aPromise->MaybeRejectWithTypeError(
"Cookie domain size cannot be greater than 1024 bytes");
return false;
}
return true;
}
bool ValidateCookiePath(const nsAString& aPath, nsAString& retPath,
Promise* aPromise) {
MOZ_ASSERT(aPromise);
if (!aPath.IsEmpty() && aPath[0] != '/') {
aPromise->MaybeRejectWithTypeError("Cookie path must start with '/'");
return false;
}
nsString path(aPath);
if (path.IsEmpty() || path[path.Length() - 1] != '/') {
path.Append('/');
}
if (path.Length() > 1024) {
aPromise->MaybeRejectWithTypeError(
"Cookie domain size cannot be greater than 1024 bytes");
return false;
}
retPath.Assign(path);
return true;
}
// Reject cookies whose name starts with the magic prefixes from
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
// if they do not meet the criteria required by the prefix.
bool ValidateCookieNamePrefix(const nsAString& aName,
const nsAString& aOptionDomain,
const nsAString& aPath, Promise* aPromise) {
MOZ_ASSERT(aPromise);
if (!StringBeginsWith(aName, u"__Host-"_ns,
nsCaseInsensitiveStringComparator)) {
return true;
}
if (!aOptionDomain.IsEmpty()) {
aPromise->MaybeRejectWithTypeError(
"Cookie domain cannot be used when the cookie name uses special "
"prefixes");
return false;
}
if (!aPath.EqualsLiteral("/")) {
aPromise->MaybeRejectWithTypeError(
"Cookie path cannot be different than '/' when the cookie name uses "
"special prefixes");
return false;
}
return true;
}
void CookieDataToItem(const CookieData& aData, CookieListItem* aItem) {
aItem->mName.Construct(aData.name());
aItem->mValue.Construct(aData.value());
}
void CookieDataToList(const nsTArray<CookieData>& aData,
nsTArray<CookieListItem>& aResult) {
for (const CookieData& data : aData) {
CookieListItem* item = aResult.AppendElement();
CookieDataToItem(data, item);
}
}
void ResolvePromiseAsync(Promise* aPromise) {
MOZ_ASSERT(aPromise);
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
__func__,
[promise = RefPtr(aPromise)] { promise->MaybeResolveWithUndefined(); }));
}
} // namespace
NS_IMPL_CYCLE_COLLECTION_CLASS(CookieStore)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CookieStore,
DOMEventTargetHelper)
tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CookieStore,
@ -33,9 +217,11 @@ already_AddRefed<CookieStore> CookieStore::Create(nsIGlobalObject* aGlobal) {
}
CookieStore::CookieStore(nsIGlobalObject* aGlobal)
: DOMEventTargetHelper(aGlobal) {}
: DOMEventTargetHelper(aGlobal) {
mNotifier = CookieStoreNotifier::Create(this);
}
CookieStore::~CookieStore() = default;
CookieStore::~CookieStore() { Shutdown(); }
JSObject* CookieStore::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
@ -44,50 +230,423 @@ JSObject* CookieStore::WrapObject(JSContext* aCx,
already_AddRefed<Promise> CookieStore::Get(const nsAString& aName,
ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
CookieStoreGetOptions options;
options.mName.Construct(aName);
return Get(options, aRv);
}
already_AddRefed<Promise> CookieStore::Get(
const CookieStoreGetOptions& aOptions, ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
return GetInternal(aOptions, true, aRv);
}
already_AddRefed<Promise> CookieStore::GetAll(const nsAString& aName,
ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
CookieStoreGetOptions options;
options.mName.Construct(aName);
return GetAll(options, aRv);
}
already_AddRefed<Promise> CookieStore::GetAll(
const CookieStoreGetOptions& aOptions, ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
return GetInternal(aOptions, false, aRv);
}
already_AddRefed<Promise> CookieStore::Set(const nsAString& aName,
const nsAString& aValue,
ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
CookieInit init;
init.mName = aName;
init.mValue = aValue;
return Set(init, aRv);
}
already_AddRefed<Promise> CookieStore::Set(const CookieInit& aOptions,
ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsCOMPtr<nsIPrincipal> cookiePrincipal;
switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
MaybeGetDocument(), getter_AddRefs(cookiePrincipal), nullptr)) {
case CookieCommons::SecurityChecksResult::eSandboxedError:
[[fallthrough]];
case CookieCommons::SecurityChecksResult::eSecurityError:
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
case CookieCommons::SecurityChecksResult::eDoNotContinue:
ResolvePromiseAsync(promise);
return promise.forget();
case CookieCommons::SecurityChecksResult::eContinue:
MOZ_ASSERT(cookiePrincipal);
break;
}
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
__func__, [self = RefPtr(this), promise = RefPtr(promise), aOptions,
cookiePrincipal = RefPtr(cookiePrincipal.get())]() {
if (!ValidateCookieNameAndValue(aOptions.mName, aOptions.mValue,
promise)) {
return;
}
nsAutoCString baseDomainUtf8;
nsresult rv =
net::CookieCommons::GetBaseDomain(cookiePrincipal, baseDomainUtf8);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
NS_ConvertUTF8toUTF16 baseDomain(baseDomainUtf8);
if (!ValidateCookieDomain(baseDomain, aOptions.mDomain, promise)) {
return;
}
nsString path;
if (!ValidateCookiePath(aOptions.mPath, path, promise)) {
return;
}
if (!ValidateCookieNamePrefix(aOptions.mName, aOptions.mDomain, path,
promise)) {
return;
}
if (!self->MaybeCreateActor()) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
RefPtr<CookieStoreChild::SetRequestPromise> ipcPromise =
self->mActor->SendSetRequest(
aOptions.mDomain.IsEmpty() ? nsString(baseDomain)
: nsString(aOptions.mDomain),
cookiePrincipal->OriginAttributesRef(),
nsString(aOptions.mName), nsString(aOptions.mValue),
// If expires is not set, it's a session cookie.
aOptions.mExpires.IsNull(),
aOptions.mExpires.IsNull()
? INT64_MAX
: static_cast<int64_t>(aOptions.mExpires.Value() / 1000),
path, SameSiteToConst(aOptions.mSameSite),
aOptions.mPartitioned);
if (NS_WARN_IF(!ipcPromise)) {
promise->MaybeResolveWithUndefined();
return;
}
ipcPromise->Then(
NS_GetCurrentThread(), __func__,
[promise = RefPtr<dom::Promise>(promise)](
const CookieStoreChild::SetRequestPromise::ResolveOrRejectValue&
aResult) { promise->MaybeResolveWithUndefined(); });
}));
return promise.forget();
}
already_AddRefed<Promise> CookieStore::Delete(const nsAString& aName,
ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;
CookieStoreDeleteOptions options;
options.mName = aName;
return Delete(options, aRv);
}
already_AddRefed<Promise> CookieStore::Delete(
const CookieStoreDeleteOptions& aOptions, ErrorResult& aRv) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsCOMPtr<nsIPrincipal> cookiePrincipal;
switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
MaybeGetDocument(), getter_AddRefs(cookiePrincipal), nullptr)) {
case CookieCommons::SecurityChecksResult::eSandboxedError:
[[fallthrough]];
case CookieCommons::SecurityChecksResult::eSecurityError:
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
case CookieCommons::SecurityChecksResult::eDoNotContinue:
ResolvePromiseAsync(promise);
return promise.forget();
case CookieCommons::SecurityChecksResult::eContinue:
MOZ_ASSERT(cookiePrincipal);
break;
}
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
__func__, [self = RefPtr(this), promise = RefPtr(promise), aOptions,
cookiePrincipal = RefPtr(cookiePrincipal.get())]() {
nsAutoCString baseDomainUtf8;
nsresult rv =
net::CookieCommons::GetBaseDomain(cookiePrincipal, baseDomainUtf8);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
NS_ConvertUTF8toUTF16 baseDomain(baseDomainUtf8);
if (!ValidateCookieDomain(baseDomain, aOptions.mDomain, promise)) {
return;
}
nsString path;
if (!ValidateCookiePath(aOptions.mPath, path, promise)) {
return;
}
if (!ValidateCookieNamePrefix(aOptions.mName, aOptions.mDomain, path,
promise)) {
return;
}
if (!self->MaybeCreateActor()) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
RefPtr<CookieStoreChild::DeleteRequestPromise> ipcPromise =
self->mActor->SendDeleteRequest(
aOptions.mDomain.IsEmpty() ? nsString(baseDomain)
: nsString(aOptions.mDomain),
cookiePrincipal->OriginAttributesRef(),
nsString(aOptions.mName), path, aOptions.mPartitioned);
if (NS_WARN_IF(!ipcPromise)) {
promise->MaybeResolveWithUndefined();
return;
}
ipcPromise->Then(NS_GetCurrentThread(), __func__,
[promise = RefPtr<dom::Promise>(promise)](
const CookieStoreChild::DeleteRequestPromise::
ResolveOrRejectValue& aResult) {
MOZ_ASSERT(aResult.IsResolve());
promise->MaybeResolveWithUndefined();
});
}));
return promise.forget();
}
void CookieStore::Shutdown() {
if (mActor) {
mActor->Close();
mActor = nullptr;
}
if (mNotifier) {
mNotifier->Disentangle();
mNotifier = nullptr;
}
}
bool CookieStore::MaybeCreateActor() {
if (mActor) {
return mActor->CanSend();
}
mozilla::ipc::PBackgroundChild* actorChild =
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!actorChild)) {
// The process is probably shutting down. Let's return a 'generic' error.
return false;
}
PCookieStoreChild* actor = actorChild->SendPCookieStoreConstructor();
if (!actor) {
return false;
}
mActor = static_cast<CookieStoreChild*>(actor);
return true;
}
already_AddRefed<Promise> CookieStore::GetInternal(
const CookieStoreGetOptions& aOptions, bool aOnlyTheFirstMatch,
ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
}
nsCOMPtr<nsIPrincipal> cookiePrincipal;
switch (CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
MaybeGetDocument(), getter_AddRefs(cookiePrincipal), nullptr)) {
case CookieCommons::SecurityChecksResult::eSandboxedError:
[[fallthrough]];
case CookieCommons::SecurityChecksResult::eSecurityError:
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
case CookieCommons::SecurityChecksResult::eDoNotContinue:
ResolvePromiseAsync(promise);
return promise.forget();
case CookieCommons::SecurityChecksResult::eContinue:
MOZ_ASSERT(cookiePrincipal);
break;
}
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
__func__,
[self = RefPtr(this), promise = RefPtr(promise), aOptions,
cookiePrincipal = RefPtr(cookiePrincipal.get()), aOnlyTheFirstMatch]() {
nsAutoString name;
if (aOptions.mName.WasPassed()) {
name = aOptions.mName.Value();
}
nsAutoCString path;
nsresult rv = cookiePrincipal->GetFilePath(path);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return;
}
if (aOptions.mUrl.WasPassed()) {
nsString url(aOptions.mUrl.Value());
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = self->GetOwnerWindow();
MOZ_ASSERT(window);
nsCOMPtr<Document> document = window->GetExtantDoc();
if (NS_WARN_IF(!document)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsIURI* creationURI = document->GetOriginalURI();
if (NS_WARN_IF(!creationURI)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIURI> resolvedURI;
rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr,
creationURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
NS_ConvertUTF16toUTF8(url));
return;
}
bool equal = false;
if (!resolvedURI ||
NS_WARN_IF(NS_FAILED(
resolvedURI->EqualsExceptRef(creationURI, &equal))) ||
!equal) {
promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
NS_ConvertUTF16toUTF8(url));
return;
}
} else {
nsCOMPtr<nsIURI> baseURI = cookiePrincipal->GetURI();
if (NS_WARN_IF(!baseURI)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIURI> resolvedURI;
rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr, baseURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
NS_ConvertUTF16toUTF8(url));
return;
}
if (!cookiePrincipal->IsSameOrigin(resolvedURI)) {
promise->MaybeRejectWithTypeError<MSG_INVALID_URL>(
NS_ConvertUTF16toUTF8(url));
return;
}
rv = resolvedURI->GetFilePath(path);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return;
}
}
}
if (!self->MaybeCreateActor()) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
nsAutoCString baseDomain;
rv = net::CookieCommons::GetBaseDomain(cookiePrincipal, baseDomain);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeRejectWithNotAllowedError("Permission denied");
return;
}
RefPtr<CookieStoreChild::GetRequestPromise> ipcPromise =
self->mActor->SendGetRequest(NS_ConvertUTF8toUTF16(baseDomain),
cookiePrincipal->OriginAttributesRef(),
aOptions.mName.WasPassed(),
nsString(name), path,
aOnlyTheFirstMatch);
if (NS_WARN_IF(!ipcPromise)) {
promise->MaybeResolveWithUndefined();
return;
}
ipcPromise->Then(
NS_GetCurrentThread(), __func__,
[promise = RefPtr<dom::Promise>(promise), aOnlyTheFirstMatch](
const CookieStoreChild::GetRequestPromise::ResolveOrRejectValue&
aResult) {
nsTArray<CookieListItem> list;
MOZ_ASSERT(aResult.IsResolve());
CookieDataToList(aResult.ResolveValue(), list);
if (!aOnlyTheFirstMatch) {
promise->MaybeResolve(list);
return;
}
if (list.IsEmpty()) {
promise->MaybeResolve(JS::NullHandleValue);
return;
}
promise->MaybeResolve(list[0]);
});
}));
return promise.forget();
}
void CookieStore::FireDelayedDOMEvents() {
MOZ_ASSERT(NS_IsMainThread());
if (mNotifier) {
mNotifier->FireDelayedDOMEvents();
}
}
Document* CookieStore::MaybeGetDocument() const {
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
MOZ_ASSERT(window);
return window->GetExtantDoc();
}
return nullptr;
}

Просмотреть файл

@ -15,6 +15,9 @@ class nsIGlobalObject;
namespace mozilla::dom {
class CookieData;
class CookieStoreChild;
class CookieStoreNotifier;
class Promise;
class CookieStore final : public DOMEventTargetHelper {
@ -29,6 +32,8 @@ class CookieStore final : public DOMEventTargetHelper {
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
void FireDelayedDOMEvents();
already_AddRefed<Promise> Get(const nsAString& aName, ErrorResult& aRv);
already_AddRefed<Promise> Get(const CookieStoreGetOptions& aOptions,
@ -54,6 +59,19 @@ class CookieStore final : public DOMEventTargetHelper {
private:
explicit CookieStore(nsIGlobalObject* aGlobal);
~CookieStore();
void Shutdown();
Document* MaybeGetDocument() const;
already_AddRefed<Promise> GetInternal(const CookieStoreGetOptions& aOptions,
bool aOnlyTheFirstMatch,
ErrorResult& aRv);
bool MaybeCreateActor();
RefPtr<CookieStoreChild> mActor;
RefPtr<CookieStoreNotifier> mNotifier;
};
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CookieStore.h"
#include "CookieStoreChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
namespace mozilla {
using namespace ipc;
namespace dom {
CookieStoreChild::CookieStoreChild() = default;
CookieStoreChild::~CookieStoreChild() = default;
void CookieStoreChild::Close() {
if (CanSend()) {
SendClose();
}
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,31 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_CookieStoreChild_h
#define mozilla_dom_CookieStoreChild_h
#include "mozilla/dom/PCookieStoreChild.h"
#include "nsISupportsImpl.h"
namespace mozilla::dom {
class CookieStoreChild final : public PCookieStoreChild {
friend class PCookieStoreChild;
public:
NS_INLINE_DECL_REFCOUNTING(CookieStoreChild)
CookieStoreChild();
void Close();
private:
~CookieStoreChild();
};
} // namespace mozilla::dom
#endif // mozilla_dom_CookieStoreChild_h

Просмотреть файл

@ -0,0 +1,245 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CookieStoreNotifier.h"
#include "CookieStore.h"
#include "mozilla/net/CookieCommons.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/Unused.h"
#include "nsICookie.h"
#include "nsICookieNotification.h"
#include "nsISerialEventTarget.h"
namespace mozilla::dom {
NS_IMPL_ISUPPORTS(CookieStoreNotifier, nsIObserver);
// static
already_AddRefed<CookieStoreNotifier> CookieStoreNotifier::Create(
CookieStore* aCookieStore) {
nsIPrincipal* principal = nullptr;
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
principal = workerPrivate->GetPrincipal();
} else {
nsCOMPtr<nsPIDOMWindowInner> window = aCookieStore->GetOwnerWindow();
MOZ_ASSERT(window);
nsCOMPtr<Document> document = window->GetExtantDoc();
if (NS_WARN_IF(!document)) {
return nullptr;
}
principal = document->NodePrincipal();
}
if (NS_WARN_IF(!principal)) {
return nullptr;
}
nsCString baseDomain;
if (NS_WARN_IF(NS_FAILED(
net::CookieCommons::GetBaseDomain(principal, baseDomain)))) {
return nullptr;
}
if (baseDomain.IsEmpty()) {
return nullptr;
}
RefPtr<CookieStoreNotifier> notifier = new CookieStoreNotifier(
aCookieStore, baseDomain, principal->OriginAttributesRef());
bool privateBrowsing = principal->OriginAttributesRef().IsPrivateBrowsing();
notifier->mEventTarget = GetCurrentSerialEventTarget();
if (!NS_IsMainThread()) {
NS_DispatchToMainThread(
NS_NewRunnableFunction(__func__, [notifier, privateBrowsing] {
notifier->AddObserversOnMainThread(privateBrowsing);
}));
} else {
notifier->AddObserversOnMainThread(privateBrowsing);
}
return notifier.forget();
}
CookieStoreNotifier::CookieStoreNotifier(
CookieStore* aCookieStore, const nsACString& aBaseDomain,
const OriginAttributes& aOriginAttributes)
: mCookieStore(aCookieStore),
mBaseDomain(aBaseDomain),
mOriginAttributes(aOriginAttributes) {
MOZ_ASSERT(aCookieStore);
}
CookieStoreNotifier::~CookieStoreNotifier() = default;
void CookieStoreNotifier::Disentangle() {
mCookieStore = nullptr;
bool privateBrowsing = mOriginAttributes.IsPrivateBrowsing();
if (!NS_IsMainThread()) {
NS_DispatchToMainThread(NS_NewRunnableFunction(
__func__, [self = RefPtr(this), privateBrowsing] {
self->RemoveObserversOnMainThread(privateBrowsing);
}));
} else {
RemoveObserversOnMainThread(privateBrowsing);
}
}
void CookieStoreNotifier::AddObserversOnMainThread(bool aPrivateBrowsing) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
return;
}
nsresult rv = os->AddObserver(
this, aPrivateBrowsing ? "private-cookie-changed" : "cookie-changed",
false);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
void CookieStoreNotifier::RemoveObserversOnMainThread(bool aPrivateBrowsing) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
return;
}
nsresult rv = os->RemoveObserver(
this, aPrivateBrowsing ? "private-cookie-changed" : "cookie-changed");
Unused << NS_WARN_IF(NS_FAILED(rv));
}
NS_IMETHODIMP
CookieStoreNotifier::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject);
NS_ENSURE_TRUE(notification, NS_ERROR_FAILURE);
auto action = notification->GetAction();
if (action != nsICookieNotification::COOKIE_DELETED &&
action != nsICookieNotification::COOKIE_ADDED &&
action != nsICookieNotification::COOKIE_CHANGED) {
return NS_OK;
}
nsAutoCString baseDomain;
nsresult rv = notification->GetBaseDomain(baseDomain);
if (NS_WARN_IF(NS_FAILED(rv)) || baseDomain.IsEmpty()) {
return rv;
}
if (baseDomain != mBaseDomain) {
return NS_OK;
}
nsCOMPtr<nsICookie> cookie;
rv = notification->GetCookie(getter_AddRefs(cookie));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (cookie->OriginAttributesNative() != mOriginAttributes) {
return NS_OK;
}
bool isHttpOnly;
rv = cookie->GetIsHttpOnly(&isHttpOnly);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (isHttpOnly) {
return NS_OK;
}
CookieListItem item;
nsAutoCString name;
rv = cookie->GetName(name);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
item.mName.Construct(NS_ConvertUTF8toUTF16(name));
if (action != nsICookieNotification::COOKIE_DELETED) {
nsAutoCString value;
rv = cookie->GetValue(value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
item.mValue.Construct(NS_ConvertUTF8toUTF16(value));
}
bool deletedEvent = action == nsICookieNotification::COOKIE_DELETED;
mEventTarget->Dispatch(NS_NewRunnableFunction(
__func__, [self = RefPtr(this), item, deletedEvent] {
if (!self->mCookieStore) {
return;
}
RefPtr<Event> event = deletedEvent
? CookieChangeEvent::CreateForDeletedCookie(
self->mCookieStore, item)
: CookieChangeEvent::CreateForChangedCookie(
self->mCookieStore, item);
if (!event) {
return;
}
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window =
self->mCookieStore->GetOwnerWindow();
if (!window) {
return;
}
RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
if (!bc) {
return;
}
if (bc->IsInBFCache()) {
self->mDelayedDOMEvents.AppendElement(event);
return;
}
}
self->mCookieStore->DispatchEvent(*event);
}));
return NS_OK;
}
void CookieStoreNotifier::FireDelayedDOMEvents() {
MOZ_ASSERT(NS_IsMainThread());
nsTArray<RefPtr<Event>> delayedDOMEvents;
delayedDOMEvents.SwapElements(mDelayedDOMEvents);
for (Event* event : delayedDOMEvents) {
mCookieStore->DispatchEvent(*event);
}
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,54 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_CookieStoreNotifier_h
#define mozilla_dom_CookieStoreNotifier_h
#include "nsIObserver.h"
#include "mozilla/OriginAttributes.h"
class nsISerialEventTarget;
namespace mozilla::dom {
class CookieStore;
struct CookieListItem;
class Event;
class CookieStoreNotifier final : public nsIObserver {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static already_AddRefed<CookieStoreNotifier> Create(
CookieStore* aCookieStore);
void Disentangle();
void FireDelayedDOMEvents();
private:
CookieStoreNotifier(CookieStore* aCookieStore, const nsACString& aBaseDomain,
const OriginAttributes& aOriginAttributes);
~CookieStoreNotifier();
void AddObserversOnMainThread(bool aPrivateBrowsing);
void RemoveObserversOnMainThread(bool aPrivateBrowsing);
// Raw pointer because this object is kept alive by this CookieStore object.
CookieStore* mCookieStore;
nsCString mBaseDomain;
OriginAttributes mOriginAttributes;
RefPtr<nsISerialEventTarget> mEventTarget;
nsTArray<RefPtr<Event>> mDelayedDOMEvents;
};
} // namespace mozilla::dom
#endif /* mozilla_dom_CookieStoreNotifier_h */

Просмотреть файл

@ -0,0 +1,262 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CookieStoreParent.h"
#include "mozilla/Maybe.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/net/CookieCommons.h"
#include "mozilla/Unused.h"
#include "nsICookieManager.h"
using namespace mozilla::ipc;
namespace mozilla::dom {
namespace {
void GetRequestHelper(const nsAString& aDomain,
const OriginAttributes& aOriginAttributes,
bool aMatchName, const nsAString& aName,
const nsACString& aPath, bool aOnlyFirstMatch,
nsTArray<CookieData>& aResults) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsICookieManager> service =
do_GetService(NS_COOKIEMANAGER_CONTRACTID);
if (!service) {
return;
}
OriginAttributes attrs(aOriginAttributes);
nsTArray<RefPtr<nsICookie>> results;
nsresult rv = service->GetCookiesFromHostNative(
NS_ConvertUTF16toUTF8(aDomain), &attrs, results);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
NS_ConvertUTF16toUTF8 matchName(aName);
nsTArray<CookieData> list;
for (nsICookie* cookie : results) {
bool isHttpOnly;
rv = cookie->GetIsHttpOnly(&isHttpOnly);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (isHttpOnly) {
continue;
}
nsAutoCString name;
rv = cookie->GetName(name);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (aMatchName && !matchName.Equals(name)) {
continue;
}
nsAutoCString cookiePath;
rv = cookie->GetPath(cookiePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!net::CookieCommons::PathMatches(cookiePath, aPath)) {
continue;
}
nsAutoCString value;
rv = cookie->GetValue(value);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
CookieData* data = list.AppendElement();
data->name() = NS_ConvertUTF8toUTF16(name);
data->value() = NS_ConvertUTF8toUTF16(value);
if (aOnlyFirstMatch) {
break;
}
}
aResults.SwapElements(list);
}
void SetRequestHelper(const nsAString& aDomain,
const OriginAttributes& aOriginAttributes,
const nsAString& aName, const nsAString& aValue,
bool aSession, int64_t aExpires, const nsAString& aPath,
int32_t aSameSite, bool aPartitioned) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsICookieManager> service =
do_GetService(NS_COOKIEMANAGER_CONTRACTID);
if (!service) {
return;
}
OriginAttributes attrs(aOriginAttributes);
nsresult rv = service->AddNative(
NS_ConvertUTF16toUTF8(aDomain), NS_ConvertUTF16toUTF8(aPath),
NS_ConvertUTF16toUTF8(aName), NS_ConvertUTF16toUTF8(aValue),
true, // secure
false, // mHttpOnly,
aSession, aSession ? PR_Now() : aExpires, &attrs, aSameSite,
nsICookie::SCHEME_HTTPS);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
void DeleteRequestHelper(const nsAString& aDomain,
const OriginAttributes& aOriginAttributes,
const nsAString& aName, const nsAString& aPath,
bool aPartitioned) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsICookieManager> service =
do_GetService(NS_COOKIEMANAGER_CONTRACTID);
if (!service) {
return;
}
NS_ConvertUTF16toUTF8 domainUtf8(aDomain);
OriginAttributes attrs(aOriginAttributes);
nsTArray<RefPtr<nsICookie>> results;
nsresult rv = service->GetCookiesFromHostNative(domainUtf8, &attrs, results);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
NS_ConvertUTF16toUTF8 matchName(aName);
NS_ConvertUTF16toUTF8 matchPath(aPath);
for (nsICookie* cookie : results) {
MOZ_ASSERT(cookie);
nsAutoCString name;
rv = cookie->GetName(name);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!matchName.Equals(name)) {
continue;
}
nsAutoCString path;
rv = cookie->GetPath(path);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!matchPath.IsEmpty() && !matchPath.Equals(path)) {
continue;
}
bool isPartitioned = false;
rv = cookie->GetIsPartitioned(&isPartitioned);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (isPartitioned != aPartitioned) continue;
rv = service->RemoveNative(domainUtf8, matchName, path, &attrs);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
}
} // namespace
CookieStoreParent::CookieStoreParent() { AssertIsOnBackgroundThread(); }
CookieStoreParent::~CookieStoreParent() { AssertIsOnBackgroundThread(); }
mozilla::ipc::IPCResult CookieStoreParent::RecvGetRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const bool& aMatchName, const nsString& aName, const nsCString& aPath,
const bool& aOnlyFirstMatch, GetRequestResolver&& aResolver) {
InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
[aDomain, aOriginAttributes, aMatchName, aName, aPath,
aOnlyFirstMatch]() {
CopyableTArray<CookieData> results;
GetRequestHelper(aDomain, aOriginAttributes, aMatchName, aName,
aPath, aOnlyFirstMatch, results);
return GetRequestPromise::CreateAndResolve(std::move(results),
__func__);
})
->Then(GetCurrentSerialEventTarget(), __func__,
[aResolver = std::move(aResolver)](
const GetRequestPromise::ResolveOrRejectValue& aResult) {
MOZ_ASSERT(aResult.IsResolve());
aResolver(aResult.ResolveValue());
});
return IPC_OK();
}
mozilla::ipc::IPCResult CookieStoreParent::RecvSetRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const nsString& aName, const nsString& aValue, const bool& aSession,
const int64_t& aExpires, const nsString& aPath, const int32_t& aSameSite,
const bool& aPartitioned, SetRequestResolver&& aResolver) {
InvokeAsync(
GetMainThreadSerialEventTarget(), __func__,
[aDomain, aOriginAttributes, aName, aValue, aSession, aExpires, aPath,
aSameSite, aPartitioned]() {
SetRequestHelper(aDomain, aOriginAttributes, aName, aValue, aSession,
aExpires, aPath, aSameSite, aPartitioned);
return SetDeleteRequestPromise::CreateAndResolve(true, __func__);
})
->Then(GetCurrentSerialEventTarget(), __func__,
[aResolver = std::move(aResolver)](
const SetDeleteRequestPromise::ResolveOrRejectValue& aResult) {
MOZ_ASSERT(aResult.IsResolve());
aResolver(aResult.ResolveValue());
});
return IPC_OK();
}
mozilla::ipc::IPCResult CookieStoreParent::RecvDeleteRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const nsString& aName, const nsString& aPath, const bool& aPartitioned,
DeleteRequestResolver&& aResolver) {
AssertIsOnBackgroundThread();
InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
[aDomain, aOriginAttributes, aName, aPath, aPartitioned]() {
DeleteRequestHelper(aDomain, aOriginAttributes, aName, aPath,
aPartitioned);
return SetDeleteRequestPromise::CreateAndResolve(true,
__func__);
})
->Then(GetCurrentSerialEventTarget(), __func__,
[aResolver = std::move(aResolver)](
const SetDeleteRequestPromise::ResolveOrRejectValue& aResult) {
MOZ_ASSERT(aResult.IsResolve());
aResolver(aResult.ResolveValue());
});
return IPC_OK();
}
mozilla::ipc::IPCResult CookieStoreParent::RecvClose() {
AssertIsOnBackgroundThread();
Unused << Send__delete__(this);
return IPC_OK();
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,53 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_CookieStoreParent_h
#define mozilla_dom_CookieStoreParent_h
#include "mozilla/dom/PCookieStoreParent.h"
#include "mozilla/MozPromise.h"
namespace mozilla::dom {
class CookieStoreService;
class CookieStoreParent final : public PCookieStoreParent {
friend class PCookieStoreParent;
public:
using GetRequestPromise =
MozPromise<CopyableTArray<CookieData>, nsresult, true>;
using SetDeleteRequestPromise = MozPromise<bool, nsresult, true>;
NS_INLINE_DECL_REFCOUNTING(CookieStoreParent)
CookieStoreParent();
private:
~CookieStoreParent();
mozilla::ipc::IPCResult RecvGetRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const bool& aMatchName, const nsString& aName, const nsCString& aPath,
const bool& aOnlyFirstMatch, GetRequestResolver&& aResolver);
mozilla::ipc::IPCResult RecvSetRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const nsString& aName, const nsString& aValue, const bool& aSession,
const int64_t& aExpires, const nsString& aPath, const int32_t& aSameSite,
const bool& aPartitioned, SetRequestResolver&& aResolver);
mozilla::ipc::IPCResult RecvDeleteRequest(
const nsString& aDomain, const OriginAttributes& aOriginAttributes,
const nsString& aName, const nsString& aPath, const bool& aPartitioned,
DeleteRequestResolver&& aResolver);
mozilla::ipc::IPCResult RecvClose();
};
} // namespace mozilla::dom
#endif // mozilla_dom_CookieStoreParent_h

Просмотреть файл

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PBackground;
using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
namespace mozilla {
namespace dom {
struct CookieData
{
nsString name;
nsString value;
};
union MaybeCookieData
{
CookieData;
void_t;
};
// This protocol is used for the CookieStore API
[ManualDealloc]
protocol PCookieStore
{
manager PBackground;
parent:
async GetRequest(nsString domain,
OriginAttributes attrs,
bool matchName,
nsString name,
nsCString path,
bool onlyFirstMatch) returns (CookieData[] data);
async SetRequest(nsString domain,
OriginAttributes attrs,
nsString name,
nsString value,
bool session,
int64_t expires,
nsString path,
int32_t sameSite,
bool partitioned) returns (bool v);
async DeleteRequest(nsString domain,
OriginAttributes attrs,
nsString name,
nsString path,
bool partitioned) returns (bool v);
async Close();
child:
async __delete__();
};
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -8,11 +8,16 @@ with Files("**"):
EXPORTS.mozilla.dom += [
"CookieChangeEvent.h",
"CookieStore.h",
"CookieStoreChild.h",
"CookieStoreParent.h",
]
UNIFIED_SOURCES += [
"CookieChangeEvent.cpp",
"CookieStore.cpp",
"CookieStoreChild.cpp",
"CookieStoreNotifier.cpp",
"CookieStoreParent.cpp",
]
LOCAL_INCLUDES += [
@ -20,6 +25,10 @@ LOCAL_INCLUDES += [
"../events",
]
IPDL_SOURCES += [
"PCookieStore.ipdl",
]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"

Просмотреть файл

@ -19,6 +19,7 @@
#include "mozilla/dom/PBackgroundLSRequestChild.h"
#include "mozilla/dom/PBackgroundLSSimpleRequestChild.h"
#include "mozilla/dom/PBackgroundSDBConnectionChild.h"
#include "mozilla/dom/CookieStoreChild.h"
#include "mozilla/dom/PFileSystemRequestChild.h"
#include "mozilla/dom/EndpointForReportChild.h"
#include "mozilla/dom/PVsync.h"
@ -320,6 +321,26 @@ bool BackgroundChildImpl::DeallocPBroadcastChannelChild(
return true;
}
// -----------------------------------------------------------------------------
// CookieStore API
// -----------------------------------------------------------------------------
dom::PCookieStoreChild* BackgroundChildImpl::AllocPCookieStoreChild() {
RefPtr<dom::CookieStoreChild> child = new dom::CookieStoreChild();
return child.forget().take();
}
bool BackgroundChildImpl::DeallocPCookieStoreChild(PCookieStoreChild* aActor) {
RefPtr<dom::CookieStoreChild> child =
dont_AddRef(static_cast<dom::CookieStoreChild*>(aActor));
MOZ_ASSERT(child);
return true;
}
// -----------------------------------------------------------------------------
// Camera API
// -----------------------------------------------------------------------------
camera::PCamerasChild* BackgroundChildImpl::AllocPCamerasChild() {
#ifdef MOZ_WEBRTC
RefPtr<camera::CamerasChild> agent = new camera::CamerasChild();

Просмотреть файл

@ -125,6 +125,10 @@ class BackgroundChildImpl : public PBackgroundChild {
virtual bool DeallocPBroadcastChannelChild(
PBroadcastChannelChild* aActor) override;
virtual PCookieStoreChild* AllocPCookieStoreChild() override;
virtual bool DeallocPCookieStoreChild(PCookieStoreChild* aActor) override;
virtual PServiceWorkerManagerChild* AllocPServiceWorkerManagerChild()
override;

Просмотреть файл

@ -18,6 +18,7 @@
#include "mozilla/dom/BackgroundSessionStorageServiceParent.h"
#include "mozilla/dom/ClientManagerActors.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/CookieStoreParent.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/EndpointForReportParent.h"
#include "mozilla/dom/FetchParent.h"
@ -821,6 +822,27 @@ bool BackgroundParentImpl::DeallocPBroadcastChannelParent(
return true;
}
mozilla::dom::PCookieStoreParent*
BackgroundParentImpl::AllocPCookieStoreParent() {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
RefPtr<mozilla::dom::CookieStoreParent> actor =
new mozilla::dom::CookieStoreParent();
return actor.forget().take();
}
bool BackgroundParentImpl::DeallocPCookieStoreParent(
PCookieStoreParent* aActor) {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
RefPtr<mozilla::dom::CookieStoreParent> actor =
dont_AddRef(static_cast<mozilla::dom::CookieStoreParent*>(aActor));
return true;
}
mozilla::dom::PServiceWorkerManagerParent*
BackgroundParentImpl::AllocPServiceWorkerManagerParent() {
AssertIsInMainProcess();

Просмотреть файл

@ -192,6 +192,10 @@ class BackgroundParentImpl : public PBackgroundParent {
bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override;
virtual PCookieStoreParent* AllocPCookieStoreParent() override;
virtual bool DeallocPCookieStoreParent(PCookieStoreParent* aActor) override;
PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override;
bool DeallocPServiceWorkerManagerParent(

Просмотреть файл

@ -20,6 +20,7 @@ include protocol PCache;
include protocol PCacheStorage;
include protocol PCacheStreamControl;
include protocol PClientManager;
include protocol PCookieStore;
include protocol PEndpointForReport;
include protocol PFileSystemManager;
include protocol PFileSystemRequest;
@ -99,6 +100,7 @@ sync protocol PBackground
manages PCacheStorage;
manages PCacheStreamControl;
manages PClientManager;
manages PCookieStore;
manages PEndpointForReport;
manages PFileSystemRequest;
manages PGamepadEventChannel;
@ -197,6 +199,8 @@ parent:
async PUDPSocket(PrincipalInfo? pInfo, nsCString filter);
async PBroadcastChannel(PrincipalInfo pInfo, nsCString origin, nsString channel);
async PCookieStore();
async PServiceWorkerManager();
async ShutdownServiceWorkerRegistrar();

Просмотреть файл

@ -12090,7 +12090,7 @@
# Whether to support CHIPS(Cookies Having Independent Partitioned State).
- name: network.cookie.CHIPS.enabled
type: bool
type: RelaxedAtomicBool
value: true
mirror: always

Просмотреть файл

@ -16,6 +16,8 @@
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/Unused.h"
#include "mozIThirdPartyUtil.h"
@ -23,10 +25,12 @@
#include "nsICookiePermission.h"
#include "nsICookieService.h"
#include "nsIEffectiveTLDService.h"
#include "nsIGlobalObject.h"
#include "nsIHttpChannel.h"
#include "nsIRedirectHistoryEntry.h"
#include "nsIWebProgressListener.h"
#include "nsNetUtil.h"
#include "nsSandboxFlags.h"
#include "nsScriptSecurityManager.h"
#include "ThirdPartyUtil.h"
@ -47,31 +51,35 @@ bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) {
// static
bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) {
const nsCString& cookiePath(aCookie->Path());
return PathMatches(aCookie->Path(), aPath);
}
// static
bool CookieCommons::PathMatches(const nsACString& aCookiePath,
const nsACString& aPath) {
// if our cookie path is empty we can't really perform our prefix check, and
// also we can't check the last character of the cookie path, so we would
// never return a successful match.
if (cookiePath.IsEmpty()) {
if (aCookiePath.IsEmpty()) {
return false;
}
// if the cookie path and the request path are identical, they match.
if (cookiePath.Equals(aPath)) {
if (aCookiePath.Equals(aPath)) {
return true;
}
// if the cookie path is a prefix of the request path, and the last character
// of the cookie path is %x2F ("/"), they match.
bool isPrefix = StringBeginsWith(aPath, cookiePath);
if (isPrefix && cookiePath.Last() == '/') {
bool isPrefix = StringBeginsWith(aPath, aCookiePath);
if (isPrefix && aCookiePath.Last() == '/') {
return true;
}
// if the cookie path is a prefix of the request path, and the first character
// of the request path that is not included in the cookie path is a %x2F ("/")
// character, they match.
uint32_t cookiePathLen = cookiePath.Length();
uint32_t cookiePathLen = aCookiePath.Length();
return isPrefix && aPath[cookiePathLen] == '/';
}
@ -826,6 +834,138 @@ void CookieCommons::ComposeCookieString(nsTArray<RefPtr<Cookie>>& aCookieList,
}
}
// static
CookieCommons::SecurityChecksResult
CookieCommons::CheckGlobalAndRetrieveCookiePrincipals(
Document* aDocument, nsIPrincipal** aCookiePrincipal,
nsIPrincipal** aCookiePartitionedPrincipal) {
MOZ_ASSERT(aCookiePrincipal);
nsCOMPtr<nsIPrincipal> cookiePrincipal;
nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal;
if (!NS_IsMainThread()) {
MOZ_ASSERT(!aDocument);
dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
StorageAccess storageAccess = workerPrivate->StorageAccess();
if (storageAccess == StorageAccess::eDeny) {
return SecurityChecksResult::eDoNotContinue;
}
cookiePrincipal = workerPrivate->GetPrincipal();
if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) {
return SecurityChecksResult::eSecurityError;
}
// CHIPS - If CHIPS is enabled the partitioned cookie jar is always
// available (and therefore the partitioned principal), the unpartitioned
// cookie jar is only available in first-party or third-party with
// storageAccess contexts. In both cases, the Worker will have storage
// access.
bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() &&
workerPrivate->CookieJarSettings()->GetPartitionForeign();
bool workerHasStorageAccess =
workerPrivate->StorageAccess() == StorageAccess::eAllow;
if (isCHIPS && workerHasStorageAccess) {
// Assert that the cookie principal is unpartitioned.
MOZ_ASSERT(
cookiePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
// Only retrieve the partitioned originAttributes if the partitionKey is
// set. The partitionKey could be empty for partitionKey in partitioned
// originAttributes if the aWorker is for privilege context, such as the
// extension's background page.
nsCOMPtr<nsIPrincipal> partitionedPrincipal =
workerPrivate->GetPartitionedPrincipal();
if (partitionedPrincipal && !partitionedPrincipal->OriginAttributesRef()
.mPartitionKey.IsEmpty()) {
cookiePartitionedPrincipal = partitionedPrincipal;
}
}
} else {
if (!aDocument) {
return SecurityChecksResult::eDoNotContinue;
}
// If the document's sandboxed origin flag is set, then reading cookies
// is prohibited.
if (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN) {
return SecurityChecksResult::eSandboxedError;
}
cookiePrincipal = aDocument->EffectiveCookiePrincipal();
if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) {
return SecurityChecksResult::eSecurityError;
}
if (aDocument->CookieAccessDisabled()) {
return SecurityChecksResult::eDoNotContinue;
}
// GTests do not create an inner window and because of these a few security
// checks will block this method.
if (!StaticPrefs::dom_cookie_testing_enabled()) {
StorageAccess storageAccess = CookieAllowedForDocument(aDocument);
if (storageAccess == StorageAccess::eDeny) {
return SecurityChecksResult::eDoNotContinue;
}
if (ShouldPartitionStorage(storageAccess) &&
!StoragePartitioningEnabled(storageAccess,
aDocument->CookieJarSettings())) {
return SecurityChecksResult::eDoNotContinue;
}
// If the document is a cookie-averse Document... return the empty string.
if (aDocument->IsCookieAverse()) {
return SecurityChecksResult::eDoNotContinue;
}
}
// CHIPS - If CHIPS is enabled the partitioned cookie jar is always
// available (and therefore the partitioned principal), the unpartitioned
// cookie jar is only available in first-party or third-party with
// storageAccess contexts. In both cases, the aDocument will have storage
// access.
bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() &&
aDocument->CookieJarSettings()->GetPartitionForeign();
bool documentHasStorageAccess = false;
nsresult rv = aDocument->HasStorageAccessSync(documentHasStorageAccess);
if (NS_WARN_IF(NS_FAILED(rv))) {
return SecurityChecksResult::eDoNotContinue;
}
if (isCHIPS && documentHasStorageAccess) {
// Assert that the cookie principal is unpartitioned.
MOZ_ASSERT(
cookiePrincipal->OriginAttributesRef().mPartitionKey.IsEmpty());
// Only append the partitioned originAttributes if the partitionKey is
// set. The partitionKey could be empty for partitionKey in partitioned
// originAttributes if the aDocument is for privilege context, such as the
// extension's background page.
if (!aDocument->PartitionedPrincipal()
->OriginAttributesRef()
.mPartitionKey.IsEmpty()) {
cookiePartitionedPrincipal = aDocument->PartitionedPrincipal();
}
}
}
if (!IsSchemeSupported(cookiePrincipal)) {
return SecurityChecksResult::eDoNotContinue;
}
cookiePrincipal.forget(aCookiePrincipal);
if (aCookiePartitionedPrincipal) {
cookiePartitionedPrincipal.forget(aCookiePartitionedPrincipal);
}
return SecurityChecksResult::eContinue;
}
// static
void CookieCommons::GetServerDateHeader(nsIChannel* aChannel,
nsACString& aServerDateHeader) {

Просмотреть файл

@ -69,6 +69,9 @@ class CookieCommons final {
static bool PathMatches(Cookie* aCookie, const nsACString& aPath);
static bool PathMatches(const nsACString& aCookiePath,
const nsACString& aPath);
static nsresult GetBaseDomain(nsIEffectiveTLDService* aTLDService,
nsIURI* aHostURI, nsACString& aBaseDomain,
bool& aRequireHostMatch);
@ -145,6 +148,26 @@ class CookieCommons final {
static void GetServerDateHeader(nsIChannel* aChannel,
nsACString& aServerDateHeader);
enum class SecurityChecksResult {
// A sandboxed context detected.
eSandboxedError,
// A security error needs to be thrown.
eSecurityError,
// This context should not see cookies without returning errors.
eDoNotContinue,
// No security issues found. Proceed to expose cookies.
eContinue,
};
// Runs the security checks requied by specs on the current context (Document
// or Worker) to see if it's allowed to set/get cookies. In case it does
// (eContinue), the cookie principals are returned. Use the
// `aCookiePartitionedPrincipal` to retrieve CHIP cookies. Use
// `aCookiePrincipal` to retrieve non-CHIP cookies.
static SecurityChecksResult CheckGlobalAndRetrieveCookiePrincipals(
mozilla::dom::Document* aDocument, nsIPrincipal** aCookiePrincipal,
nsIPrincipal** aCookiePartitionedPrincipal);
};
} // namespace net

Просмотреть файл

@ -395,7 +395,7 @@ void CookieJarSettings::Serialize(CookieJarSettingsArgs& aData) {
continue;
}
ipc::PrincipalInfo principalInfo;
mozilla::ipc::PrincipalInfo principalInfo;
rv = PrincipalToPrincipalInfo(principal, &principalInfo,
true /* aSkipBaseDomain */);
if (NS_WARN_IF(NS_FAILED(rv))) {

Просмотреть файл

@ -1266,6 +1266,18 @@ CookieService::GetCookiesFromHost(const nsACString& aHost,
JS::Handle<JS::Value> aOriginAttributes,
JSContext* aCx,
nsTArray<RefPtr<nsICookie>>& aResult) {
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
return GetCookiesFromHostNative(aHost, &attrs, aResult);
}
NS_IMETHODIMP
CookieService::GetCookiesFromHostNative(const nsACString& aHost,
OriginAttributes* aAttrs,
nsTArray<RefPtr<nsICookie>>& aResult) {
// first, normalize the hostname, and fail if it contains illegal characters.
nsAutoCString host(aHost);
nsresult rv = NormalizeHost(host);
@ -1275,19 +1287,14 @@ CookieService::GetCookiesFromHost(const nsACString& aHost,
rv = CookieCommons::GetBaseDomainFromHost(mTLDService, host, baseDomain);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
if (!IsInitialized()) {
return NS_ERROR_NOT_AVAILABLE;
}
CookieStorage* storage = PickStorage(attrs);
CookieStorage* storage = PickStorage(*aAttrs);
nsTArray<RefPtr<Cookie>> cookies;
storage->GetCookiesFromHost(baseDomain, attrs, cookies);
storage->GetCookiesFromHost(baseDomain, *aAttrs, cookies);
if (cookies.IsEmpty()) {
return NS_OK;

Просмотреть файл

@ -229,6 +229,11 @@ interface nsICookieManager : nsISupports
Array<nsICookie> getCookiesFromHost(in AUTF8String aHost,
in jsval aOriginAttributes);
[notxpcom]
nsresult getCookiesFromHostNative(in AUTF8String aHost,
in OriginAttributesPtr aOriginAttributes,
out Array<nsICookie> aCookies);
/**
* Returns an array of all cookies whose origin attributes matches aPattern
*