Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / caps / OriginAttributes.cpp
blob734a23049582767076aacfa5c7d5f38156ac28d5
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et 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/. */
7 #include "mozilla/OriginAttributes.h"
8 #include "mozilla/Assertions.h"
9 #include "mozilla/Preferences.h"
10 #include "mozilla/dom/BlobURLProtocolHandler.h"
11 #include "mozilla/dom/quota/QuotaManager.h"
12 #include "nsIEffectiveTLDService.h"
13 #include "nsIURI.h"
14 #include "nsNetCID.h"
15 #include "nsNetUtil.h"
16 #include "nsString.h"
17 #include "nsURLHelper.h"
19 static const char kSourceChar = ':';
20 static const char kSanitizedChar = '+';
22 namespace mozilla {
24 static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost,
25 int32_t aPort, bool aUseSite,
26 nsAString& aTopLevelInfo) {
27 if (!aUseSite) {
28 aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(aHost));
29 return;
32 // Note: If you change the serialization of the partition-key, please update
33 // StoragePrincipalHelper.cpp too.
35 nsAutoCString site;
36 site.AssignLiteral("(");
37 site.Append(aScheme);
38 site.Append(",");
39 site.Append(aHost);
40 if (aPort != -1) {
41 site.Append(",");
42 site.AppendInt(aPort);
44 site.AppendLiteral(")");
46 aTopLevelInfo.Assign(NS_ConvertUTF8toUTF16(site));
49 static void MakeTopLevelInfo(const nsACString& aScheme, const nsACString& aHost,
50 bool aUseSite, nsAString& aTopLevelInfo) {
51 MakeTopLevelInfo(aScheme, aHost, -1, aUseSite, aTopLevelInfo);
54 static void PopulateTopLevelInfoFromURI(const bool aIsTopLevelDocument,
55 nsIURI* aURI, bool aIsFirstPartyEnabled,
56 bool aForced, bool aUseSite,
57 nsString OriginAttributes::*aTarget,
58 OriginAttributes& aOriginAttributes) {
59 nsresult rv;
61 if (!aURI) {
62 return;
65 // If the prefs are off or this is not a top level load, bail out.
66 if ((!aIsFirstPartyEnabled || !aIsTopLevelDocument) && !aForced) {
67 return;
70 nsAString& topLevelInfo = aOriginAttributes.*aTarget;
72 nsAutoCString scheme;
73 nsCOMPtr<nsIURI> uri = aURI;
74 // The URI could be nested (for example view-source:http://example.com), in
75 // that case we want to get the innermost URI (http://example.com).
76 nsCOMPtr<nsINestedURI> nestedURI;
77 do {
78 NS_ENSURE_SUCCESS_VOID(uri->GetScheme(scheme));
79 nestedURI = do_QueryInterface(uri);
80 // We can't just use GetInnermostURI on the nested URI, since that would
81 // also unwrap some about: URIs to hidden moz-safe-about: URIs, which we do
82 // not want. Thus we loop through with GetInnerURI until the URI isn't
83 // nested anymore or we encounter a about: scheme.
84 } while (nestedURI && !scheme.EqualsLiteral("about") &&
85 NS_SUCCEEDED(nestedURI->GetInnerURI(getter_AddRefs(uri))));
87 if (scheme.EqualsLiteral("about")) {
88 MakeTopLevelInfo(scheme, nsLiteralCString(ABOUT_URI_FIRST_PARTY_DOMAIN),
89 aUseSite, topLevelInfo);
90 return;
93 // If a null principal URI was provided, extract the UUID portion of the URI
94 // to use for the first-party domain.
95 if (scheme.EqualsLiteral("moz-nullprincipal")) {
96 // Get the UUID portion of the URI, ignoring the precursor principal.
97 nsAutoCString filePath;
98 rv = uri->GetFilePath(filePath);
99 MOZ_ASSERT(NS_SUCCEEDED(rv));
100 // Remove the `{}` characters from both ends.
101 filePath.Mid(filePath, 1, filePath.Length() - 2);
102 filePath.AppendLiteral(".mozilla");
103 // Store the generated file path.
104 topLevelInfo = NS_ConvertUTF8toUTF16(filePath);
105 return;
108 // Add-on principals should never get any first-party domain
109 // attributes in order to guarantee their storage integrity when switching
110 // FPI on and off.
111 if (scheme.EqualsLiteral("moz-extension")) {
112 return;
115 nsCOMPtr<nsIPrincipal> blobPrincipal;
116 if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
117 uri, getter_AddRefs(blobPrincipal))) {
118 MOZ_ASSERT(blobPrincipal);
119 topLevelInfo = blobPrincipal->OriginAttributesRef().*aTarget;
120 return;
123 nsCOMPtr<nsIEffectiveTLDService> tldService =
124 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
125 MOZ_ASSERT(tldService);
126 NS_ENSURE_TRUE_VOID(tldService);
128 nsAutoCString baseDomain;
129 rv = tldService->GetBaseDomain(uri, 0, baseDomain);
130 if (NS_SUCCEEDED(rv)) {
131 MakeTopLevelInfo(scheme, baseDomain, aUseSite, topLevelInfo);
132 return;
135 // Saving before rv is overwritten.
136 bool isIpAddress = (rv == NS_ERROR_HOST_IS_IP_ADDRESS);
137 bool isInsufficientDomainLevels = (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS);
139 int32_t port;
140 rv = uri->GetPort(&port);
141 NS_ENSURE_SUCCESS_VOID(rv);
143 nsAutoCString host;
144 rv = uri->GetHost(host);
145 NS_ENSURE_SUCCESS_VOID(rv);
147 if (isIpAddress) {
148 // If the host is an IPv4/IPv6 address, we still accept it as a
149 // valid topLevelInfo.
150 nsAutoCString ipAddr;
152 if (net_IsValidIPv6Addr(host)) {
153 // According to RFC2732, the host of an IPv6 address should be an
154 // IPv6reference. The GetHost() of nsIURI will only return the IPv6
155 // address. So, we need to convert it back to IPv6reference here.
156 ipAddr.AssignLiteral("[");
157 ipAddr.Append(host);
158 ipAddr.AppendLiteral("]");
159 } else {
160 ipAddr = host;
163 MakeTopLevelInfo(scheme, ipAddr, port, aUseSite, topLevelInfo);
164 return;
167 if (aUseSite) {
168 MakeTopLevelInfo(scheme, host, port, aUseSite, topLevelInfo);
169 return;
172 if (isInsufficientDomainLevels) {
173 nsAutoCString publicSuffix;
174 rv = tldService->GetPublicSuffix(uri, publicSuffix);
175 if (NS_SUCCEEDED(rv)) {
176 MakeTopLevelInfo(scheme, publicSuffix, port, aUseSite, topLevelInfo);
177 return;
182 void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
183 nsIURI* aURI, bool aForced) {
184 PopulateTopLevelInfoFromURI(
185 aIsTopLevelDocument, aURI, IsFirstPartyEnabled(), aForced,
186 StaticPrefs::privacy_firstparty_isolate_use_site(),
187 &OriginAttributes::mFirstPartyDomain, *this);
190 void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
191 const nsACString& aDomain) {
192 SetFirstPartyDomain(aIsTopLevelDocument, NS_ConvertUTF8toUTF16(aDomain));
195 void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
196 const nsAString& aDomain,
197 bool aForced) {
198 // If the pref is off or this is not a top level load, bail out.
199 if ((!IsFirstPartyEnabled() || !aIsTopLevelDocument) && !aForced) {
200 return;
203 mFirstPartyDomain = aDomain;
206 void OriginAttributes::SetPartitionKey(nsIURI* aURI) {
207 PopulateTopLevelInfoFromURI(
208 false /* aIsTopLevelDocument */, aURI, IsFirstPartyEnabled(),
209 true /* aForced */, StaticPrefs::privacy_dynamic_firstparty_use_site(),
210 &OriginAttributes::mPartitionKey, *this);
213 void OriginAttributes::SetPartitionKey(const nsACString& aDomain) {
214 SetPartitionKey(NS_ConvertUTF8toUTF16(aDomain));
217 void OriginAttributes::SetPartitionKey(const nsAString& aDomain) {
218 mPartitionKey = aDomain;
221 void OriginAttributes::CreateSuffix(nsACString& aStr) const {
222 URLParams params;
223 nsAutoString value;
226 // Important: While serializing any string-valued attributes, perform a
227 // release-mode assertion to make sure that they don't contain characters that
228 // will break the quota manager when it uses the serialization for file
229 // naming.
232 if (mInIsolatedMozBrowser) {
233 params.Set(u"inBrowser"_ns, u"1"_ns);
236 if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
237 value.Truncate();
238 value.AppendInt(mUserContextId);
239 params.Set(u"userContextId"_ns, value);
242 if (mPrivateBrowsingId) {
243 value.Truncate();
244 value.AppendInt(mPrivateBrowsingId);
245 params.Set(u"privateBrowsingId"_ns, value);
248 if (!mFirstPartyDomain.IsEmpty()) {
249 nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain);
250 sanitizedFirstPartyDomain.ReplaceChar(kSourceChar, kSanitizedChar);
252 params.Set(u"firstPartyDomain"_ns, sanitizedFirstPartyDomain);
255 if (!mGeckoViewSessionContextId.IsEmpty()) {
256 nsAutoString sanitizedGeckoViewUserContextId(mGeckoViewSessionContextId);
257 sanitizedGeckoViewUserContextId.ReplaceChar(
258 dom::quota::QuotaManager::kReplaceChars16, kSanitizedChar);
260 params.Set(u"geckoViewUserContextId"_ns, sanitizedGeckoViewUserContextId);
263 if (!mPartitionKey.IsEmpty()) {
264 nsAutoString sanitizedPartitionKey(mPartitionKey);
265 sanitizedPartitionKey.ReplaceChar(kSourceChar, kSanitizedChar);
267 params.Set(u"partitionKey"_ns, sanitizedPartitionKey);
270 aStr.Truncate();
272 params.Serialize(value, true);
273 if (!value.IsEmpty()) {
274 aStr.AppendLiteral("^");
275 aStr.Append(NS_ConvertUTF16toUTF8(value));
278 // In debug builds, check the whole string for illegal characters too (just in
279 // case).
280 #ifdef DEBUG
281 nsAutoCString str;
282 str.Assign(aStr);
283 MOZ_ASSERT(str.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) ==
284 kNotFound);
285 #endif
288 already_AddRefed<nsAtom> OriginAttributes::CreateSuffixAtom() const {
289 nsAutoCString suffix;
290 CreateSuffix(suffix);
291 return NS_Atomize(suffix);
294 void OriginAttributes::CreateAnonymizedSuffix(nsACString& aStr) const {
295 OriginAttributes attrs = *this;
297 if (!attrs.mFirstPartyDomain.IsEmpty()) {
298 attrs.mFirstPartyDomain.AssignLiteral("_anonymizedFirstPartyDomain_");
301 if (!attrs.mPartitionKey.IsEmpty()) {
302 attrs.mPartitionKey.AssignLiteral("_anonymizedPartitionKey_");
305 attrs.CreateSuffix(aStr);
308 bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) {
309 if (aStr.IsEmpty()) {
310 return true;
313 if (aStr[0] != '^') {
314 return false;
317 // If a non-default mPrivateBrowsingId is passed and is not present in the
318 // suffix, then it will retain the id when it should be default according
319 // to the suffix. Set to default before iterating to fix this.
320 mPrivateBrowsingId = nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
322 // Checking that we are in a pristine state
324 MOZ_RELEASE_ASSERT(mUserContextId == 0);
325 MOZ_RELEASE_ASSERT(mPrivateBrowsingId == 0);
326 MOZ_RELEASE_ASSERT(mFirstPartyDomain.IsEmpty());
327 MOZ_RELEASE_ASSERT(mGeckoViewSessionContextId.IsEmpty());
328 MOZ_RELEASE_ASSERT(mPartitionKey.IsEmpty());
330 return URLParams::Parse(
331 Substring(aStr, 1, aStr.Length() - 1),
332 [this](const nsAString& aName, const nsAString& aValue) {
333 if (aName.EqualsLiteral("inBrowser")) {
334 if (!aValue.EqualsLiteral("1")) {
335 return false;
338 mInIsolatedMozBrowser = true;
339 return true;
342 if (aName.EqualsLiteral("addonId") || aName.EqualsLiteral("appId")) {
343 // No longer supported. Silently ignore so that legacy origin strings
344 // don't cause failures.
345 return true;
348 if (aName.EqualsLiteral("userContextId")) {
349 nsresult rv;
350 int64_t val = aValue.ToInteger64(&rv);
351 NS_ENSURE_SUCCESS(rv, false);
352 NS_ENSURE_TRUE(val <= UINT32_MAX, false);
353 mUserContextId = static_cast<uint32_t>(val);
355 return true;
358 if (aName.EqualsLiteral("privateBrowsingId")) {
359 nsresult rv;
360 int64_t val = aValue.ToInteger64(&rv);
361 NS_ENSURE_SUCCESS(rv, false);
362 NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false);
363 mPrivateBrowsingId = static_cast<uint32_t>(val);
365 return true;
368 if (aName.EqualsLiteral("firstPartyDomain")) {
369 nsAutoString firstPartyDomain(aValue);
370 firstPartyDomain.ReplaceChar(kSanitizedChar, kSourceChar);
371 mFirstPartyDomain.Assign(firstPartyDomain);
372 return true;
375 if (aName.EqualsLiteral("geckoViewUserContextId")) {
376 mGeckoViewSessionContextId.Assign(aValue);
377 return true;
380 if (aName.EqualsLiteral("partitionKey")) {
381 nsAutoString partitionKey(aValue);
382 partitionKey.ReplaceChar(kSanitizedChar, kSourceChar);
383 mPartitionKey.Assign(partitionKey);
384 return true;
387 // No other attributes are supported.
388 return false;
392 bool OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin,
393 nsACString& aOriginNoSuffix) {
394 // RFindChar is only available on nsCString.
395 nsCString origin(aOrigin);
396 int32_t pos = origin.RFindChar('^');
398 if (pos == kNotFound) {
399 aOriginNoSuffix = origin;
400 return true;
403 aOriginNoSuffix = Substring(origin, 0, pos);
404 return PopulateFromSuffix(Substring(origin, pos));
407 void OriginAttributes::SyncAttributesWithPrivateBrowsing(
408 bool aInPrivateBrowsing) {
409 mPrivateBrowsingId = aInPrivateBrowsing ? 1 : 0;
412 /* static */
413 bool OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin) {
414 nsAutoCString dummy;
415 OriginAttributes attrs;
416 if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) {
417 return false;
420 return !!attrs.mPrivateBrowsingId;
423 /* static */
424 bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey,
425 nsAString& outScheme,
426 nsAString& outBaseDomain,
427 int32_t& outPort) {
428 outScheme.Truncate();
429 outBaseDomain.Truncate();
430 outPort = -1;
432 // Partition keys have the format "(<scheme>,<baseDomain>,[port])". The port
433 // is optional. For example: "(https,example.com,8443)" or
434 // "(http,example.org)".
435 // When privacy.dynamic_firstparty.use_site = false, the partitionKey contains
436 // only the host, e.g. "example.com".
437 // See MakeTopLevelInfo for the partitionKey serialization code.
439 if (aPartitionKey.IsEmpty()) {
440 return true;
443 // PartitionKey contains only the host.
444 if (!StaticPrefs::privacy_dynamic_firstparty_use_site()) {
445 outBaseDomain = aPartitionKey;
446 return true;
449 // Smallest possible partitionKey is "(x,x)". Scheme and base domain are
450 // mandatory.
451 if (NS_WARN_IF(aPartitionKey.Length() < 5)) {
452 return false;
455 if (NS_WARN_IF(aPartitionKey.First() != '(' || aPartitionKey.Last() != ')')) {
456 return false;
459 // Remove outer brackets so we can string split.
460 nsAutoString str(Substring(aPartitionKey, 1, aPartitionKey.Length() - 2));
462 uint32_t fieldIndex = 0;
463 for (const nsAString& field : str.Split(',')) {
464 if (NS_WARN_IF(field.IsEmpty())) {
465 // There cannot be empty fields.
466 return false;
469 if (fieldIndex == 0) {
470 outScheme.Assign(field);
471 } else if (fieldIndex == 1) {
472 outBaseDomain.Assign(field);
473 } else if (fieldIndex == 2) {
474 // Parse the port which is represented in the partitionKey string as a
475 // decimal (base 10) number.
476 long port = strtol(NS_ConvertUTF16toUTF8(field).get(), nullptr, 10);
477 // Invalid port.
478 if (NS_WARN_IF(port == 0)) {
479 return false;
481 outPort = static_cast<int32_t>(port);
482 } else {
483 NS_WARNING("Invalid partitionKey. Too many tokens");
484 return false;
487 fieldIndex++;
490 // scheme and base domain are required.
491 return fieldIndex > 1;
494 } // namespace mozilla