Bug 1345573 - Part 1: Key http, https, and ftp URIs on origin instead of eTLD+1, r=baku
authorMichael Layzell <michael@thelayzells.com>
Wed, 08 Mar 2017 14:28:04 -0500
changeset 348632 035042b8b4aabb0f6f427a0eb9d12a160dbc6f5a
parent 348631 55fe816879882db8a366c005ebf95cf192e70eb4
child 348633 63e7bf43da6505b9789b714495bebca9fc1c05da
push id88279
push usermichael@thelayzells.com
push dateTue, 21 Mar 2017 15:11:09 +0000
treeherdermozilla-inbound@f86ef08fdae0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1345573
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1345573 - Part 1: Key http, https, and ftp URIs on origin instead of eTLD+1, r=baku MozReview-Commit-ID: Gihc4QFf11R
dom/ipc/ContentParent.cpp
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5091,19 +5091,22 @@ ContentParent::TransmitPermissionsFor(ns
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIPrincipal> principal;
   rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Create the key, and send it down to the content process.
-  nsAutoCString key;
-  nsPermissionManager::GetKeyForPrincipal(principal, key);
-  EnsurePermissionsByKey(key);
+  nsTArray<nsCString> keys =
+    nsPermissionManager::GetAllKeysForPrincipal(principal);
+  MOZ_ASSERT(keys.Length() >= 1);
+  for (auto& key : keys) {
+    EnsurePermissionsByKey(key);
+  }
 #endif
 
   return NS_OK;
 }
 
 void
 ContentParent::EnsurePermissionsByKey(const nsCString& aKey)
 {
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -171,16 +171,64 @@ GetNextSubDomainForHost(const nsACString
   // subdomain.
   if (NS_FAILED(rv)) {
     return EmptyCString();
   }
 
   return subDomain;
 }
 
+// This function produces a nsIPrincipal which is identical to the current
+// nsIPrincipal, except that it has one less subdomain segment. It returns
+// `nullptr` if there are no more segments to remove.
+already_AddRefed<nsIPrincipal>
+GetNextSubDomainPrincipal(nsIPrincipal* aPrincipal)
+{
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+  if (NS_FAILED(rv) || !uri) {
+    return nullptr;
+  }
+
+  nsAutoCString host;
+  rv = uri->GetHost(host);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  nsCString domain = GetNextSubDomainForHost(host);
+  if (domain.IsEmpty()) {
+    return nullptr;
+  }
+
+  // Create a new principal which is identical to the current one, but with the new host
+  nsCOMPtr<nsIURI> newURI;
+  rv = uri->Clone(getter_AddRefs(newURI));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  rv = newURI->SetHost(domain);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  // Copy the attributes over
+  mozilla::OriginAttributes attrs = aPrincipal->OriginAttributesRef();
+
+  // Disable userContext and firstParty isolation for permissions.
+  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
+
+  nsCOMPtr<nsIPrincipal> principal =
+    mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
+
+  return principal.forget();
+}
+
 class ClearOriginDataObserver final : public nsIObserver {
   ~ClearOriginDataObserver() {}
 
 public:
   NS_DECL_ISUPPORTS
 
   // nsIObserver implementation.
   NS_IMETHOD
@@ -2180,56 +2228,21 @@ nsPermissionManager::GetPermissionHashKe
   }
 
   if (entry) {
     return entry;
   }
 
   // If aExactHostMatch wasn't true, we can check if the base domain has a permission entry.
   if (!aExactHostMatch) {
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    nsAutoCString host;
-    rv = uri->GetHost(host);
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    nsCString domain = GetNextSubDomainForHost(host);
-    if (domain.IsEmpty()) {
-      return nullptr;
+    nsCOMPtr<nsIPrincipal> principal =
+      GetNextSubDomainPrincipal(aPrincipal);
+    if (principal) {
+      return GetPermissionHashKey(principal, aType, aExactHostMatch);
     }
-
-    // Create a new principal which is identical to the current one, but with the new host
-    nsCOMPtr<nsIURI> newURI;
-    rv = uri->Clone(getter_AddRefs(newURI));
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    rv = newURI->SetHost(domain);
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    // Copy the attributes over
-    mozilla::OriginAttributes attrs = aPrincipal->OriginAttributesRef();
-
-    // Disable userContext and firstParty isolation for permissions.
-    attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
-                          mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
-
-    nsCOMPtr<nsIPrincipal> principal =
-      mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
-
-    return GetPermissionHashKey(principal, aType, aExactHostMatch);
   }
 
   // No entry, really...
   return nullptr;
 }
 
 NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
 {
@@ -3009,49 +3022,68 @@ nsPermissionManager::SetPermissionsWithK
     uint64_t modificationTime = 0;
     AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
                 perm.expireTime, modificationTime, eNotify, eNoDBOperation,
                 true /* ignoreSessionPermissions */);
   }
   return NS_OK;
 }
 
-// XXX: Support file URIs here as well!
 /* static */ void
 nsPermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal, nsACString& aKey)
 {
   MOZ_ASSERT(aPrincipal);
   aKey.Truncate();
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
   if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
     // NOTE: We don't propagate the error here, instead we produce the default
     // "" permission key. This means that we can assign every principal a key,
     // even if the GetURI operation on that principal is not meaningful.
+    aKey.Truncate();
     return;
   }
 
-  // If the URI isn't of one of the supported schemes, it has the "" permission
-  // key. We can do an early return in that case.
   nsAutoCString scheme;
-  uri->GetScheme(scheme);
-  if (!scheme.EqualsLiteral("http") &&
-      !scheme.EqualsLiteral("https") &&
-      !scheme.EqualsLiteral("ftp")) {
+  rv = uri->GetScheme(scheme);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // NOTE: Produce the default "" key as a fallback.
+    aKey.Truncate();
     return;
   }
 
-  // We key sets of permissions to be sent over IPC based on their eTLD+1, or in
-  // the case where that isn't meaningful, on their IP address or spec.
-  nsCOMPtr<nsIEffectiveTLDService> etldService =
-    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-  rv = etldService->GetBaseDomain(uri, 0, aKey);
-  if (NS_FAILED(rv)) {
-    rv = uri->GetHost(aKey);
+  // URIs which have schemes other than http, https and ftp share the ""
+  // permission key.
+  if (scheme.EqualsLiteral("http") ||
+      scheme.EqualsLiteral("https") ||
+      scheme.EqualsLiteral("ftp")) {
+    rv = GetOriginFromPrincipal(aPrincipal, aKey);
+    if (NS_SUCCEEDED(rv)) {
+      return;
+    }
   }
-  if (NS_FAILED(rv)) {
-    rv = uri->GetSpec(aKey);
+
+  // NOTE: Produce the default "" key as a fallback.
+  aKey.Truncate();
+  return;
+}
+
+/* static */ nsTArray<nsCString>
+nsPermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(aPrincipal);
+
+  nsTArray<nsCString> keys;
+  nsCOMPtr<nsIPrincipal> prin = aPrincipal;
+  while (prin) {
+    // Add the key to the list
+    nsCString* key = keys.AppendElement();
+    GetKeyForPrincipal(prin, *key);
+
+    // Get the next subdomain principal and loop back around.
+    prin = GetNextSubDomainPrincipal(prin);
   }
-  if (NS_FAILED(rv)) {
-    aKey.Truncate();
-  }
+
+  MOZ_ASSERT(keys.Length() >= 1,
+             "Every principal should have at least one key.");
+  return keys;
 }
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -218,16 +218,33 @@ public:
    * principal. Principals which don't have meaningful URIs with http://,
    * https://, or ftp:// schemes are given the default "" Permission Key.
    *
    * @param aPrincipal  The Principal which the key is to be extracted from.
    * @param aPermissionKey  A string which will be filled with the permission key.
    */
   static void GetKeyForPrincipal(nsIPrincipal* aPrincipal, nsACString& aPermissionKey);
 
+  /**
+   * See `nsIPermissionManager::GetPermissionsWithKey` for more info on
+   * permission keys.
+   *
+   * Get all permissions keys which could correspond to the given principal.
+   * This method, like GetKeyForPrincipal, is infallible and should always
+   * produce at least one key.
+   *
+   * Unlike GetKeyForPrincipal, this method also gets the keys for base domains
+   * of the given principal. All keys returned by this method must be avaliable
+   * in the content process for a given URL to successfully have its permissions
+   * checked in the `aExactHostMatch = false` situation.
+   *
+   * @param aPrincipal  The Principal which the key is to be extracted from.
+   */
+  static nsTArray<nsCString> GetAllKeysForPrincipal(nsIPrincipal* aPrincipal);
+
 private:
   virtual ~nsPermissionManager();
 
   int32_t GetTypeIndex(const char *aTypeString,
                        bool        aAdd);
 
   PermissionHashKey* GetPermissionHashKey(nsIPrincipal* aPrincipal,
                                           uint32_t      aType,