Bug 1883706: part 3) Implement `createHTML`, `createScript` and `createScriptURL...
[gecko.git] / caps / OriginAttributes.cpp
blob8540d2a12a47eebc9e837f3668de8e9c315496da
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 (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
233 value.Truncate();
234 value.AppendInt(mUserContextId);
235 params.Set(u"userContextId"_ns, value);
238 if (mPrivateBrowsingId) {
239 value.Truncate();
240 value.AppendInt(mPrivateBrowsingId);
241 params.Set(u"privateBrowsingId"_ns, value);
244 if (!mFirstPartyDomain.IsEmpty()) {
245 nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain);
246 sanitizedFirstPartyDomain.ReplaceChar(kSourceChar, kSanitizedChar);
248 params.Set(u"firstPartyDomain"_ns, sanitizedFirstPartyDomain);
251 if (!mGeckoViewSessionContextId.IsEmpty()) {
252 nsAutoString sanitizedGeckoViewUserContextId(mGeckoViewSessionContextId);
253 sanitizedGeckoViewUserContextId.ReplaceChar(
254 dom::quota::QuotaManager::kReplaceChars16, kSanitizedChar);
256 params.Set(u"geckoViewUserContextId"_ns, sanitizedGeckoViewUserContextId);
259 if (!mPartitionKey.IsEmpty()) {
260 nsAutoString sanitizedPartitionKey(mPartitionKey);
261 sanitizedPartitionKey.ReplaceChar(kSourceChar, kSanitizedChar);
263 params.Set(u"partitionKey"_ns, sanitizedPartitionKey);
266 aStr.Truncate();
268 params.Serialize(value, true);
269 if (!value.IsEmpty()) {
270 aStr.AppendLiteral("^");
271 aStr.Append(NS_ConvertUTF16toUTF8(value));
274 // In debug builds, check the whole string for illegal characters too (just in
275 // case).
276 #ifdef DEBUG
277 nsAutoCString str;
278 str.Assign(aStr);
279 MOZ_ASSERT(str.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) ==
280 kNotFound);
281 #endif
284 already_AddRefed<nsAtom> OriginAttributes::CreateSuffixAtom() const {
285 nsAutoCString suffix;
286 CreateSuffix(suffix);
287 return NS_Atomize(suffix);
290 void OriginAttributes::CreateAnonymizedSuffix(nsACString& aStr) const {
291 OriginAttributes attrs = *this;
293 if (!attrs.mFirstPartyDomain.IsEmpty()) {
294 attrs.mFirstPartyDomain.AssignLiteral("_anonymizedFirstPartyDomain_");
297 if (!attrs.mPartitionKey.IsEmpty()) {
298 attrs.mPartitionKey.AssignLiteral("_anonymizedPartitionKey_");
301 attrs.CreateSuffix(aStr);
304 bool OriginAttributes::PopulateFromSuffix(const nsACString& aStr) {
305 if (aStr.IsEmpty()) {
306 return true;
309 if (aStr[0] != '^') {
310 return false;
313 // If a non-default mPrivateBrowsingId is passed and is not present in the
314 // suffix, then it will retain the id when it should be default according
315 // to the suffix. Set to default before iterating to fix this.
316 mPrivateBrowsingId = nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
318 // Checking that we are in a pristine state
320 MOZ_RELEASE_ASSERT(mUserContextId == 0);
321 MOZ_RELEASE_ASSERT(mPrivateBrowsingId == 0);
322 MOZ_RELEASE_ASSERT(mFirstPartyDomain.IsEmpty());
323 MOZ_RELEASE_ASSERT(mGeckoViewSessionContextId.IsEmpty());
324 MOZ_RELEASE_ASSERT(mPartitionKey.IsEmpty());
326 return URLParams::Parse(
327 Substring(aStr, 1, aStr.Length() - 1),
328 [this](const nsAString& aName, const nsAString& aValue) {
329 if (aName.EqualsLiteral("inBrowser")) {
330 if (!aValue.EqualsLiteral("1")) {
331 return false;
334 return true;
337 if (aName.EqualsLiteral("addonId") || aName.EqualsLiteral("appId")) {
338 // No longer supported. Silently ignore so that legacy origin strings
339 // don't cause failures.
340 return true;
343 if (aName.EqualsLiteral("userContextId")) {
344 nsresult rv;
345 int64_t val = aValue.ToInteger64(&rv);
346 NS_ENSURE_SUCCESS(rv, false);
347 NS_ENSURE_TRUE(val <= UINT32_MAX, false);
348 mUserContextId = static_cast<uint32_t>(val);
350 return true;
353 if (aName.EqualsLiteral("privateBrowsingId")) {
354 nsresult rv;
355 int64_t val = aValue.ToInteger64(&rv);
356 NS_ENSURE_SUCCESS(rv, false);
357 NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false);
358 mPrivateBrowsingId = static_cast<uint32_t>(val);
360 return true;
363 if (aName.EqualsLiteral("firstPartyDomain")) {
364 nsAutoString firstPartyDomain(aValue);
365 firstPartyDomain.ReplaceChar(kSanitizedChar, kSourceChar);
366 mFirstPartyDomain.Assign(firstPartyDomain);
367 return true;
370 if (aName.EqualsLiteral("geckoViewUserContextId")) {
371 mGeckoViewSessionContextId.Assign(aValue);
372 return true;
375 if (aName.EqualsLiteral("partitionKey")) {
376 nsAutoString partitionKey(aValue);
377 partitionKey.ReplaceChar(kSanitizedChar, kSourceChar);
378 mPartitionKey.Assign(partitionKey);
379 return true;
382 // No other attributes are supported.
383 return false;
387 bool OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin,
388 nsACString& aOriginNoSuffix) {
389 // RFindChar is only available on nsCString.
390 nsCString origin(aOrigin);
391 int32_t pos = origin.RFindChar('^');
393 if (pos == kNotFound) {
394 aOriginNoSuffix = origin;
395 return true;
398 aOriginNoSuffix = Substring(origin, 0, pos);
399 return PopulateFromSuffix(Substring(origin, pos));
402 void OriginAttributes::SyncAttributesWithPrivateBrowsing(
403 bool aInPrivateBrowsing) {
404 mPrivateBrowsingId = aInPrivateBrowsing ? 1 : 0;
407 /* static */
408 bool OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin) {
409 nsAutoCString dummy;
410 OriginAttributes attrs;
411 if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) {
412 return false;
415 return !!attrs.mPrivateBrowsingId;
418 /* static */
419 bool OriginAttributes::ParsePartitionKey(const nsAString& aPartitionKey,
420 nsAString& outScheme,
421 nsAString& outBaseDomain,
422 int32_t& outPort) {
423 outScheme.Truncate();
424 outBaseDomain.Truncate();
425 outPort = -1;
427 // Partition keys have the format "(<scheme>,<baseDomain>,[port])". The port
428 // is optional. For example: "(https,example.com,8443)" or
429 // "(http,example.org)".
430 // When privacy.dynamic_firstparty.use_site = false, the partitionKey contains
431 // only the host, e.g. "example.com".
432 // See MakeTopLevelInfo for the partitionKey serialization code.
434 if (aPartitionKey.IsEmpty()) {
435 return true;
438 // PartitionKey contains only the host.
439 if (!StaticPrefs::privacy_dynamic_firstparty_use_site()) {
440 outBaseDomain = aPartitionKey;
441 return true;
444 // Smallest possible partitionKey is "(x,x)". Scheme and base domain are
445 // mandatory.
446 if (NS_WARN_IF(aPartitionKey.Length() < 5)) {
447 return false;
450 if (NS_WARN_IF(aPartitionKey.First() != '(' || aPartitionKey.Last() != ')')) {
451 return false;
454 // Remove outer brackets so we can string split.
455 nsAutoString str(Substring(aPartitionKey, 1, aPartitionKey.Length() - 2));
457 uint32_t fieldIndex = 0;
458 for (const nsAString& field : str.Split(',')) {
459 if (NS_WARN_IF(field.IsEmpty())) {
460 // There cannot be empty fields.
461 return false;
464 if (fieldIndex == 0) {
465 outScheme.Assign(field);
466 } else if (fieldIndex == 1) {
467 outBaseDomain.Assign(field);
468 } else if (fieldIndex == 2) {
469 // Parse the port which is represented in the partitionKey string as a
470 // decimal (base 10) number.
471 long port = strtol(NS_ConvertUTF16toUTF8(field).get(), nullptr, 10);
472 // Invalid port.
473 if (NS_WARN_IF(port == 0)) {
474 return false;
476 outPort = static_cast<int32_t>(port);
477 } else {
478 NS_WARNING("Invalid partitionKey. Too many tokens");
479 return false;
482 fieldIndex++;
485 // scheme and base domain are required.
486 return fieldIndex > 1;
489 } // namespace mozilla