1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CookieCommons.h"
7 #include "CookieLogging.h"
8 #include "CookieServiceParent.h"
9 #include "mozilla/dom/ContentParent.h"
10 #include "mozilla/net/CookieService.h"
11 #include "mozilla/net/CookieServiceParent.h"
13 #include "mozilla/ipc/URIUtils.h"
14 #include "mozilla/StoragePrincipalHelper.h"
15 #include "mozIThirdPartyUtil.h"
16 #include "nsArrayUtils.h"
17 #include "nsIChannel.h"
18 #include "mozilla/StaticPrefs_network.h"
19 #include "nsIEffectiveTLDService.h"
21 #include "nsMixedContentBlocker.h"
23 using namespace mozilla::ipc
;
28 CookieServiceParent::CookieServiceParent(dom::ContentParent
* aContentParent
) {
29 MOZ_ASSERT(aContentParent
);
31 // Instantiate the cookieservice via the service manager, so it sticks around
33 nsCOMPtr
<nsICookieService
> cs
= do_GetService(NS_COOKIESERVICE_CONTRACTID
);
35 // Get the CookieService instance directly, so we can call internal methods.
36 mCookieService
= CookieService::GetSingleton();
37 NS_ASSERTION(mCookieService
, "couldn't get nsICookieService");
39 mTLDService
= do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
);
40 MOZ_ALWAYS_TRUE(mTLDService
);
42 mProcessingCookie
= false;
44 nsTArray
<nsCOMPtr
<nsIPrincipal
>> list
;
45 aContentParent
->TakeCookieInProcessCache(list
);
47 for (nsIPrincipal
* principal
: list
) {
48 nsCOMPtr
<nsIURI
> uri
= principal
->GetURI();
49 UpdateCookieInContentList(uri
, principal
->OriginAttributesRef());
53 void CookieServiceParent::RemoveBatchDeletedCookies(nsIArray
* aCookieList
) {
55 aCookieList
->GetLength(&len
);
56 OriginAttributes attrs
;
57 CookieStruct cookieStruct
;
58 nsTArray
<CookieStruct
> cookieStructList
;
59 nsTArray
<OriginAttributes
> attrsList
;
60 for (uint32_t i
= 0; i
< len
; i
++) {
61 nsCOMPtr
<nsICookie
> xpcCookie
= do_QueryElementAt(aCookieList
, i
);
62 const auto& cookie
= xpcCookie
->AsCookie();
63 attrs
= cookie
.OriginAttributesRef();
64 cookieStruct
= cookie
.ToIPC();
66 // Child only needs to know HttpOnly cookies exists, not its value
67 // Same for Secure cookies going to a process for an insecure site.
68 if (cookie
.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie
)) {
69 cookieStruct
.value() = "";
71 cookieStructList
.AppendElement(cookieStruct
);
72 attrsList
.AppendElement(attrs
);
74 Unused
<< SendRemoveBatchDeletedCookies(cookieStructList
, attrsList
);
77 void CookieServiceParent::RemoveAll() { Unused
<< SendRemoveAll(); }
79 void CookieServiceParent::RemoveCookie(const Cookie
& cookie
,
80 const nsID
* aOperationID
) {
81 const OriginAttributes
& attrs
= cookie
.OriginAttributesRef();
82 CookieStruct cookieStruct
= cookie
.ToIPC();
84 // Child only needs to know HttpOnly cookies exists, not its value
85 // Same for Secure cookies going to a process for an insecure site.
86 if (cookie
.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie
)) {
87 cookieStruct
.value() = "";
89 Unused
<< SendRemoveCookie(cookieStruct
, attrs
,
90 aOperationID
? Some(*aOperationID
) : Nothing());
93 void CookieServiceParent::AddCookie(const Cookie
& cookie
,
94 const nsID
* aOperationID
) {
95 const OriginAttributes
& attrs
= cookie
.OriginAttributesRef();
96 CookieStruct cookieStruct
= cookie
.ToIPC();
98 // Child only needs to know HttpOnly cookies exists, not its value
99 // Same for Secure cookies going to a process for an insecure site.
100 if (cookie
.IsHttpOnly() || !InsecureCookieOrSecureOrigin(cookie
)) {
101 cookieStruct
.value() = "";
103 Unused
<< SendAddCookie(cookieStruct
, attrs
,
104 aOperationID
? Some(*aOperationID
) : Nothing());
107 bool CookieServiceParent::ContentProcessHasCookie(const Cookie
& cookie
) {
108 return ContentProcessHasCookie(cookie
.Host(), cookie
.OriginAttributesRef());
111 bool CookieServiceParent::ContentProcessHasCookie(
112 const nsACString
& aHost
, const OriginAttributes
& aOriginAttributes
) {
113 nsCString baseDomain
;
114 if (NS_WARN_IF(NS_FAILED(CookieCommons::GetBaseDomainFromHost(
115 mTLDService
, aHost
, baseDomain
)))) {
119 CookieKey
cookieKey(baseDomain
, aOriginAttributes
);
120 return mCookieKeysInContent
.MaybeGet(cookieKey
).isSome();
123 bool CookieServiceParent::InsecureCookieOrSecureOrigin(const Cookie
& cookie
) {
124 nsCString baseDomain
;
125 // CookieStorage notifications triggering this won't fail to get base domain
126 MOZ_ALWAYS_SUCCEEDS(CookieCommons::GetBaseDomainFromHost(
127 mTLDService
, cookie
.Host(), baseDomain
));
129 // cookie is insecure or cookie is associated with a secure-origin process
130 CookieKey
cookieKey(baseDomain
, cookie
.OriginAttributesRef());
131 if (Maybe
<bool> allowSecure
= mCookieKeysInContent
.MaybeGet(cookieKey
)) {
132 return (!cookie
.IsSecure() || *allowSecure
);
137 void CookieServiceParent::TrackCookieLoad(nsIChannel
* aChannel
) {
138 nsCOMPtr
<nsIURI
> uri
;
139 aChannel
->GetURI(getter_AddRefs(uri
));
141 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
142 bool isSafeTopLevelNav
= CookieCommons::IsSafeTopLevelNav(aChannel
);
143 bool hadCrossSiteRedirects
= false;
144 bool isSameSiteForeign
=
145 CookieCommons::IsSameSiteForeign(aChannel
, uri
, &hadCrossSiteRedirects
);
147 nsCOMPtr
<mozIThirdPartyUtil
> thirdPartyUtil
;
148 thirdPartyUtil
= do_GetService(THIRDPARTYUTIL_CONTRACTID
);
150 uint32_t rejectedReason
= 0;
151 ThirdPartyAnalysisResult result
= thirdPartyUtil
->AnalyzeChannel(
152 aChannel
, false, nullptr, nullptr, &rejectedReason
);
154 OriginAttributes storageOriginAttributes
= loadInfo
->GetOriginAttributes();
155 StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
156 aChannel
, storageOriginAttributes
);
158 nsTArray
<OriginAttributes
> originAttributesList
;
159 originAttributesList
.AppendElement(storageOriginAttributes
);
161 // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available
162 // (and therefore the partitioned OriginAttributes), the unpartitioned cookie
163 // jar is only available in first-party or third-party with storageAccess
165 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
=
166 CookieCommons::GetCookieJarSettings(aChannel
);
167 bool isCHIPS
= StaticPrefs::network_cookie_CHIPS_enabled() &&
168 !cookieJarSettings
->GetBlockingAllContexts();
169 bool isUnpartitioned
=
170 !result
.contains(ThirdPartyAnalysis::IsForeign
) ||
171 result
.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted
);
172 if (isCHIPS
&& isUnpartitioned
) {
173 // Assert that the storage originAttributes is empty. In other words,
174 // it's unpartitioned.
175 MOZ_ASSERT(storageOriginAttributes
.mPartitionKey
.IsEmpty());
176 // Add the partitioned principal to principals
177 OriginAttributes partitionedOriginAttributes
;
178 StoragePrincipalHelper::GetOriginAttributes(
179 aChannel
, partitionedOriginAttributes
,
180 StoragePrincipalHelper::ePartitionedPrincipal
);
181 // Only append the partitioned originAttributes if the partitionKey is set.
182 // The partitionKey could be empty for partitionKey in partitioned
183 // originAttributes if the channel is for privilege request, such as
184 // extension's requests.
185 if (!partitionedOriginAttributes
.mPartitionKey
.IsEmpty()) {
186 originAttributesList
.AppendElement(partitionedOriginAttributes
);
190 for (auto& originAttributes
: originAttributesList
) {
191 UpdateCookieInContentList(uri
, originAttributes
);
194 // Send matching cookies to Child.
195 nsTArray
<RefPtr
<Cookie
>> foundCookieList
;
196 mCookieService
->GetCookiesForURI(
197 uri
, aChannel
, result
.contains(ThirdPartyAnalysis::IsForeign
),
198 result
.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource
),
199 result
.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource
),
200 result
.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted
),
201 rejectedReason
, isSafeTopLevelNav
, isSameSiteForeign
,
202 hadCrossSiteRedirects
, false, true, originAttributesList
,
204 nsTArray
<CookieStructTable
> matchingCookiesListTable
;
205 SerializeCookieListTable(foundCookieList
, matchingCookiesListTable
, uri
);
206 Unused
<< SendTrackCookiesLoad(matchingCookiesListTable
);
209 // we append outgoing cookie info into a list here so the ContentParent can
210 // filter cookies passing to unnecessary ContentProcesses
211 void CookieServiceParent::UpdateCookieInContentList(
212 nsIURI
* uri
, const OriginAttributes
& originAttrs
) {
213 nsCString baseDomain
;
214 bool requireAHostMatch
= false;
216 // prevent malformed urls from being added to the cookie list
217 if (NS_WARN_IF(NS_FAILED(CookieCommons::GetBaseDomain(
218 mTLDService
, uri
, baseDomain
, requireAHostMatch
)))) {
222 CookieKey
cookieKey(baseDomain
, originAttrs
);
223 bool& allowSecure
= mCookieKeysInContent
.LookupOrInsert(cookieKey
, false);
225 allowSecure
|| nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri
);
229 void CookieServiceParent::SerializeCookieListTable(
230 const nsTArray
<RefPtr
<Cookie
>>& aFoundCookieList
,
231 nsTArray
<CookieStructTable
>& aCookiesListTable
, nsIURI
* aHostURI
) {
232 // Stores the index in aCookiesListTable by origin attributes suffix.
233 nsTHashMap
<nsCStringHashKey
, size_t> cookieListTable
;
235 for (Cookie
* cookie
: aFoundCookieList
) {
236 nsAutoCString attrsSuffix
;
237 cookie
->OriginAttributesRef().CreateSuffix(attrsSuffix
);
238 size_t tableIndex
= cookieListTable
.LookupOrInsertWith(attrsSuffix
, [&] {
239 size_t index
= aCookiesListTable
.Length();
240 CookieStructTable
* newTable
= aCookiesListTable
.AppendElement();
241 newTable
->attrs() = cookie
->OriginAttributesRef();
245 CookieStruct
* cookieStruct
=
246 aCookiesListTable
[tableIndex
].cookies().AppendElement();
247 *cookieStruct
= cookie
->ToIPC();
249 // clear http-only cookie values
250 if (cookie
->IsHttpOnly()) {
251 // Value only needs to exist if an HttpOnly cookie exists.
252 cookieStruct
->value() = "";
255 // clear secure cookie values in insecure context
256 bool potentiallyTurstworthy
=
257 nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI
);
258 if (cookie
->IsSecure() && !potentiallyTurstworthy
) {
259 cookieStruct
->value() = "";
264 IPCResult
CookieServiceParent::RecvGetCookieList(
265 nsIURI
* aHost
, const bool& aIsForeign
,
266 const bool& aIsThirdPartyTrackingResource
,
267 const bool& aIsThirdPartySocialTrackingResource
,
268 const bool& aStorageAccessPermissionGranted
,
269 const uint32_t& aRejectedReason
, const bool& aIsSafeTopLevelNav
,
270 const bool& aIsSameSiteForeign
, const bool& aHadCrossSiteRedirects
,
271 nsTArray
<OriginAttributes
>&& aAttrsList
, GetCookieListResolver
&& aResolve
) {
272 // Send matching cookies to Child.
274 return IPC_FAIL(this, "aHost must not be null");
277 // we append outgoing cookie info into a list here so the ContentParent can
278 // filter cookies that do not need to go to certain ContentProcesses
279 for (const auto& attrs
: aAttrsList
) {
280 UpdateCookieInContentList(aHost
, attrs
);
283 nsTArray
<RefPtr
<Cookie
>> foundCookieList
;
284 // Note: passing nullptr as aChannel to GetCookiesForURI() here is fine since
285 // this argument is only used for proper reporting of cookie loads, but the
286 // child process already does the necessary reporting in this case for us.
287 mCookieService
->GetCookiesForURI(
288 aHost
, nullptr, aIsForeign
, aIsThirdPartyTrackingResource
,
289 aIsThirdPartySocialTrackingResource
, aStorageAccessPermissionGranted
,
290 aRejectedReason
, aIsSafeTopLevelNav
, aIsSameSiteForeign
,
291 aHadCrossSiteRedirects
, false, true, aAttrsList
, foundCookieList
);
293 nsTArray
<CookieStructTable
> matchingCookiesListTable
;
294 SerializeCookieListTable(foundCookieList
, matchingCookiesListTable
, aHost
);
296 aResolve(matchingCookiesListTable
);
301 void CookieServiceParent::ActorDestroy(ActorDestroyReason aWhy
) {
302 // Nothing needed here. Called right before destructor since this is a
303 // non-refcounted class.
306 IPCResult
CookieServiceParent::RecvSetCookies(
307 const nsCString
& aBaseDomain
, const OriginAttributes
& aOriginAttributes
,
308 nsIURI
* aHost
, bool aFromHttp
, bool aIsThirdParty
,
309 const nsTArray
<CookieStruct
>& aCookies
) {
310 if (!ContentProcessHasCookie(aBaseDomain
, aOriginAttributes
)) {
311 return IPC_FAIL(this, "Invalid set-cookie request from content process");
314 return SetCookies(aBaseDomain
, aOriginAttributes
, aHost
, aFromHttp
,
315 aIsThirdParty
, aCookies
);
318 IPCResult
CookieServiceParent::SetCookies(
319 const nsCString
& aBaseDomain
, const OriginAttributes
& aOriginAttributes
,
320 nsIURI
* aHost
, bool aFromHttp
, bool aIsThirdParty
,
321 const nsTArray
<CookieStruct
>& aCookies
,
322 dom::BrowsingContext
* aBrowsingContext
) {
323 if (!mCookieService
) {
327 // Deserialize URI. Having a host URI is mandatory and should always be
328 // provided by the child; thus we consider failure fatal.
330 return IPC_FAIL(this, "aHost must not be null");
333 // We set this to true while processing this cookie update, to make sure
334 // we don't send it back to the same content process.
335 mProcessingCookie
= true;
337 bool ok
= mCookieService
->SetCookiesFromIPC(aBaseDomain
, aOriginAttributes
,
338 aHost
, aFromHttp
, aIsThirdParty
,
339 aCookies
, aBrowsingContext
);
340 mProcessingCookie
= false;
341 return ok
? IPC_OK() : IPC_FAIL(this, "Invalid cookie received.");
345 } // namespace mozilla