Bug 976073 - Part 1 : Implement spec, r=jdm r=dveditz
authoramy <amchung@mozilla.com>
Wed, 09 Nov 2016 17:14:21 +0800
changeset 351855 97a0b004f799c39c7f7c9876d1110a3fa642ab41
parent 351854 804bcd698ce569e5b83852358619f1453550f20e
child 351856 b86094c4eafc59ef953655fa6996ca242bd55320
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, dveditz
bugs976073
milestone52.0a1
Bug 976073 - Part 1 : Implement spec, r=jdm r=dveditz
modules/libpref/init/all.js
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/test/TestCookie.cpp
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/histogram-whitelists.json
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1980,16 +1980,17 @@ pref("network.proxy.proxy_over_tls",    
 pref("network.proxy.no_proxies_on",         "localhost, 127.0.0.1");
 pref("network.proxy.failover_timeout",      1800); // 30 minutes
 pref("network.online",                      true); //online/offline
 pref("network.cookie.cookieBehavior",       0); // 0-Accept, 1-dontAcceptForeign, 2-dontAcceptAny, 3-limitForeign
 #ifdef ANDROID
 pref("network.cookie.cookieBehavior",       0); // Keep the old default of accepting all cookies
 #endif
 pref("network.cookie.thirdparty.sessionOnly", false);
+pref("network.cookie.leave-secure-alone",   true);
 pref("network.cookie.lifetimePolicy",       0); // 0-accept, 1-dontUse 2-acceptForSession, 3-acceptForNDays
 pref("network.cookie.prefsMigrated",        false);
 pref("network.cookie.lifetime.days",        90); // Ignored unless network.cookie.lifetimePolicy is 3.
 
 // The PAC file to load.  Ignored unless network.proxy.type is 2.
 pref("network.proxy.autoconfig_url", "");
 // Strip off paths when sending URLs to PAC scripts
 pref("network.proxy.autoconfig_url.include_path", false);
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -108,21 +108,31 @@ static const int64_t kCookiePurgeAge =
 // default limits for the cookie list. these can be tuned by the
 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
 static const uint32_t kMaxNumberOfCookies = 3000;
 static const uint32_t kMaxCookiesPerHost  = 150;
 static const uint32_t kMaxBytesPerCookie  = 4096;
 static const uint32_t kMaxBytesPerPath    = 1024;
 
 // pref string constants
-static const char kPrefCookieBehavior[]     = "network.cookie.cookieBehavior";
-static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber";
-static const char kPrefMaxCookiesPerHost[]  = "network.cookie.maxPerHost";
-static const char kPrefCookiePurgeAge[]     = "network.cookie.purgeAge";
-static const char kPrefThirdPartySession[]  = "network.cookie.thirdparty.sessionOnly";
+static const char kPrefCookieBehavior[]       = "network.cookie.cookieBehavior";
+static const char kPrefMaxNumberOfCookies[]   = "network.cookie.maxNumber";
+static const char kPrefMaxCookiesPerHost[]    = "network.cookie.maxPerHost";
+static const char kPrefCookiePurgeAge[]       = "network.cookie.purgeAge";
+static const char kPrefThirdPartySession[]    = "network.cookie.thirdparty.sessionOnly";
+static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
+
+// For telemetry COOKIE_LEAVE_SECURE_ALONE
+#define BLOCKED_SECURE_SET_FROM_HTTP 0
+#define BLOCKED_DOWNGRADE_SECURE     1
+#define DOWNGRADE_SECURE_FROM_SECURE 2
+#define EVICTED_NEWER_INSECURE       3
+#define EVICTED_OLDEST_COOKIE        4
+#define EVICTED_PREFERRED_COOKIE     5
+#define EVICTING_SECURE_BLOCKED      6
 
 static void
 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
                      const nsCookieKey &aKey,
                      const nsCookie *aCookie);
 
 // struct for temporarily storing cookie attributes during header parsing
 struct nsCookieAttributes
@@ -697,16 +707,17 @@ NS_IMPL_ISUPPORTS(nsCookieService,
                   nsIObserver,
                   nsISupportsWeakReference,
                   nsIMemoryReporter)
 
 nsCookieService::nsCookieService()
  : mDBState(nullptr)
  , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
  , mThirdPartySession(false)
+ , mLeaveSecureAlone(true)
  , mMaxNumberOfCookies(kMaxNumberOfCookies)
  , mMaxCookiesPerHost(kMaxCookiesPerHost)
  , mCookiePurgeAge(kCookiePurgeAge)
 {
 }
 
 nsresult
 nsCookieService::Init()
@@ -719,21 +730,22 @@ nsCookieService::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // init our pref and observer
   nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefBranch) {
-    prefBranch->AddObserver(kPrefCookieBehavior,     this, true);
-    prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true);
-    prefBranch->AddObserver(kPrefMaxCookiesPerHost,  this, true);
-    prefBranch->AddObserver(kPrefCookiePurgeAge,     this, true);
-    prefBranch->AddObserver(kPrefThirdPartySession,  this, true);
+    prefBranch->AddObserver(kPrefCookieBehavior,        this, true);
+    prefBranch->AddObserver(kPrefMaxNumberOfCookies,    this, true);
+    prefBranch->AddObserver(kPrefMaxCookiesPerHost,     this, true);
+    prefBranch->AddObserver(kPrefCookiePurgeAge,        this, true);
+    prefBranch->AddObserver(kPrefThirdPartySession,     this, true);
+    prefBranch->AddObserver(kCookieLeaveSecurityAlone,  this, true);
     PrefChanged(prefBranch);
   }
 
   mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Init our default, and possibly private DBStates.
   InitDBStates();
@@ -2189,16 +2201,19 @@ nsCookieService::PrefChanged(nsIPrefBran
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
     mCookiePurgeAge =
       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
   }
 
   bool boolval;
   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
     mThirdPartySession = boolval;
+
+  if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval)))
+    mLeaveSecureAlone = boolval;
 }
 
 /******************************************************************************
  * nsICookieManager impl:
  * nsICookieManager
  ******************************************************************************/
 
 NS_IMETHODIMP
@@ -3437,40 +3452,84 @@ nsCookieService::AddInternal(const nsCoo
 
   // if the new cookie is httponly, make sure we're not coming from script
   if (!aFromHttp && aCookie->IsHttpOnly()) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
       "cookie is httponly; coming from script");
     return;
   }
 
-  nsListIter matchIter;
-  bool foundCookie = FindCookie(aKey, aCookie->Host(),
-    aCookie->Name(), aCookie->Path(), matchIter);
+  bool isSecure = true;
+  if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) {
+    isSecure = false;
+  }
+
+  // If the new cookie is non-https and wants to set secure flag,
+  // browser have to ignore this new cookie.
+  // (draft-ietf-httpbis-cookie-alone section 3.1)
+  if (mLeaveSecureAlone && aCookie->IsSecure() && !isSecure) {
+    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+      "non-https cookie can't set secure flag");
+    Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                          BLOCKED_SECURE_SET_FROM_HTTP);
+    return;
+  }
+  nsListIter exactIter;
+  bool foundCookie = false;
+  if (mLeaveSecureAlone) {
+    // Step1, call FindSecureCookie(). FindSecureCookie() would
+    // find the existing cookie with the security flag and has
+    // the same name, host and path of the new cookie, if there is any.
+    // Step2, Confirm new cookie's security setting. If any targeted
+    // cookie had been found in Step1, then confirm whether the
+    // new cookie could modify it. If the new created cookie’s
+    // "secure-only-flag" is not set, and the "scheme" component
+    // of the "request-uri" does not denote a "secure" protocol,
+    // then ignore the new cookie.
+    // (draft-ietf-httpbis-cookie-alone section 3.2)
+    foundCookie = FindSecureCookie(aKey, aCookie);
+    if (foundCookie && !aCookie->IsSecure()) {
+      if (!isSecure) {
+        COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
+           "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie");
+        Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                              BLOCKED_DOWNGRADE_SECURE);
+        return;
+      } else {
+        // A secure site is allowed to downgrade a secure cookie
+        // but we want to measure anyway
+        Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                              DOWNGRADE_SECURE_FROM_SECURE);
+      }
+    }
+  }
+
+  foundCookie = FindCookie(aKey, aCookie->Host(),
+                           aCookie->Name(), aCookie->Path(), exactIter);
 
   RefPtr<nsCookie> oldCookie;
   nsCOMPtr<nsIArray> purgedList;
   if (foundCookie) {
-    oldCookie = matchIter.Cookie();
+    oldCookie = exactIter.Cookie();
 
     // Check if the old cookie is stale (i.e. has already expired). If so, we
     // need to be careful about the semantics of removing it and adding the new
     // cookie: we want the behavior wrt adding the new cookie to be the same as
     // if it didn't exist, but we still want to fire a removal notification.
     if (oldCookie->Expiry() <= currentTime) {
       if (aCookie->Expiry() <= currentTime) {
         // The new cookie has expired and the old one is stale. Nothing to do.
         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
           "cookie has already expired");
         return;
       }
 
       // Remove the stale cookie. We save notification for later, once all list
       // modifications are complete.
-      RemoveCookieFromList(matchIter);
+      RemoveCookieFromList(exactIter);
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
         "stale cookie was purged");
       purgedList = CreatePurgeList(oldCookie);
 
       // We've done all we need to wrt removing and notifying the stale cookie.
       // From here on out, we pretend pretend it didn't exist, so that we
       // preserve expected notification semantics when adding the new cookie.
       foundCookie = false;
@@ -3497,17 +3556,17 @@ nsCookieService::AddInternal(const nsCoo
           !oldCookie->IsStale()) {
         // Update the last access time on the old cookie.
         oldCookie->SetLastAccessed(aCookie->LastAccessed());
         UpdateCookieOldestTime(mDBState, oldCookie);
         return;
       }
 
       // Remove the old cookie.
-      RemoveCookieFromList(matchIter);
+      RemoveCookieFromList(exactIter);
 
       // If the new cookie has expired -- i.e. the intent was simply to delete
       // the old cookie -- then we're done.
       if (aCookie->Expiry() <= currentTime) {
         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
           "previously stored cookie was deleted");
         NotifyChanged(oldCookie, u"deleted");
         return;
@@ -3524,24 +3583,44 @@ nsCookieService::AddInternal(const nsCoo
         "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
       nsListIter iter;
-      FindStaleCookie(entry, currentTime, aHostURI, iter);
+      // Prioritize evicting insecure cookies.
+      // (draft-ietf-httpbis-cookie-alone section 3.3)
+      mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
+      int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
+      if (iter.entry == nullptr) {
+        if (aCookie->IsSecure()) {
+          // It's valid to evict a secure cookie for another secure cookie.
+          oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
+        } else {
+          Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                                EVICTING_SECURE_BLOCKED);
+          COOKIE_LOGEVICTED(aCookie,
+            "Too many cookies for this domain and the new cookie is not a secure cookie");
+          return;
+        }
+      }
+
+      MOZ_ASSERT(iter.entry);
+
       oldCookie = iter.Cookie();
+      if (oldestCookieTime > 0 && mLeaveSecureAlone) {
+        TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
+      }
 
       // remove the oldest cookie from the domain
       RemoveCookieFromList(iter);
       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
       purgedList = CreatePurgeList(oldCookie);
-
     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
       if (maxAge >= purgeAge) {
         // we're over both size and age limits by 10%; time to purge the table!
         // do this by:
         // 1) removing expired cookies;
         // 2) evicting the balance of old cookies until we reach the size limit.
@@ -4425,22 +4504,24 @@ nsCookieService::CookieExistsNative(nsIC
   nsListIter iter;
   *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
                              host, name, path, iter);
   return NS_OK;
 }
 
 // For a given base domain, find either an expired cookie or the oldest cookie
 // by lastAccessed time.
-void
+int64_t
 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
                                  int64_t aCurrentTime,
                                  nsIURI* aSource,
+                                 mozilla::Maybe<bool> aIsSecure,
                                  nsListIter &aIter)
 {
+  aIter.entry = nullptr;
   bool requireHostMatch = true;
   nsAutoCString baseDomain, sourceHost, sourcePath;
   if (aSource) {
     GetBaseDomain(aSource, baseDomain, requireHostMatch);
     aSource->GetAsciiHost(sourceHost);
     sourcePath = GetPathFromURI(aSource);
   }
 
@@ -4457,40 +4538,56 @@ nsCookieService::FindStaleCookie(nsCooki
   int64_t oldestNonMatchingNonSessionCookieTime = 0;
   nsListIter oldestNonMatchingNonSessionCookie;
   oldestNonMatchingNonSessionCookie.entry = nullptr;
 
   int64_t oldestCookieTime = 0;
   nsListIter oldestCookie;
   oldestCookie.entry = nullptr;
 
+  int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
 
     // If we found an expired cookie, we're done.
     if (cookie->Expiry() <= aCurrentTime) {
       aIter.entry = aEntry;
       aIter.index = i;
-      return;
+      return -1;
+    }
+
+    int64_t lastAccessed = cookie->LastAccessed();
+    // Record the age of the oldest cookie that is stored for this host.
+    // oldestCookieTime is the age of the oldest cookie with a matching
+    // secure flag, which may be more recent than an older cookie with
+    // a non-matching secure flag.
+    if (actualOldestCookieTime > lastAccessed) {
+      actualOldestCookieTime = lastAccessed;
+    }
+    if (aIsSecure.isSome() && !aIsSecure.value()) {
+      // We want to look for the oldest non-secure cookie first time through,
+      // then find the oldest secure cookie the second time we are called.
+      if (cookie->IsSecure()) {
+        continue;
+      }
     }
 
     // Update our various records of oldest cookies fitting several restrictions:
     // * session cookies
     // * non-session cookies
     // * cookies with paths and domains that don't match the cookie triggering this purge
 
     // This cookie is a candidate for eviction if we have no information about
     // the source request, or if it is not a path or domain match against the
     // source request.
     bool isPrimaryEvictionCandidate = true;
     if (aSource) {
       isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
     }
 
-    int64_t lastAccessed = cookie->LastAccessed();
     if (cookie->IsSession()) {
       if (!oldestSessionCookie.entry || oldestSessionCookieTime > lastAccessed) {
         oldestSessionCookieTime = lastAccessed;
         oldestSessionCookie.entry = aEntry;
         oldestSessionCookie.index = i;
       }
 
       if (isPrimaryEvictionCandidate &&
@@ -4524,16 +4621,37 @@ nsCookieService::FindStaleCookie(nsCooki
     aIter = oldestNonMatchingSessionCookie;
   } else if (oldestSessionCookie.entry) {
     aIter = oldestSessionCookie;
   } else if (oldestNonMatchingNonSessionCookie.entry) {
     aIter = oldestNonMatchingNonSessionCookie;
   } else {
     aIter = oldestCookie;
   }
+
+  return actualOldestCookieTime;
+}
+
+void
+nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted,
+                                                 int64_t oldestCookieTime)
+{
+  // We need to record the evicting cookie to telemetry.
+  if (!aEvicted->IsSecure()) {
+    if (aEvicted->LastAccessed() > oldestCookieTime) {
+      Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                            EVICTED_NEWER_INSECURE);
+    } else {
+      Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                            EVICTED_OLDEST_COOKIE);
+    }
+  } else {
+    Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
+                          EVICTED_PREFERRED_COOKIE);
+  }
 }
 
 // count the number of cookies stored by a particular host. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
                                       uint32_t         *aCountFromHost)
 {
@@ -4731,16 +4849,50 @@ nsCookieService::RemoveCookiesWithOrigin
       nsresult rv = Remove(host, entry->mOriginAttributes, name, path, false);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
+// find an secure cookie specified by host and name
+bool
+nsCookieService::FindSecureCookie(const nsCookieKey    &aKey,
+                                  nsCookie             *aCookie)
+{
+  EnsureReadDomain(aKey);
+
+  nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
+  if (!entry)
+    return false;
+
+  const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
+  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+    nsCookie *cookie = cookies[i];
+    // isn't a match if insecure or a different name
+    if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name()))
+      continue;
+
+    // The host must "domain-match" an existing cookie or vice-versa
+    if (DomainMatches(cookie, aCookie->Host()) ||
+        DomainMatches(aCookie, cookie->Host())) {
+      // If the path of new cookie and the path of existing cookie
+      // aren't "/", then this situation needs to compare paths to
+      // ensure only that a newly-created non-secure cookie does not
+      // overlay an existing secure cookie.
+      if (PathMatches(cookie, aCookie->Path())) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 // find an exact cookie specified by host, name, and path that hasn't expired.
 bool
 nsCookieService::FindCookie(const nsCookieKey    &aKey,
                             const nsAFlatCString &aHost,
                             const nsAFlatCString &aName,
                             const nsAFlatCString &aPath,
                             nsListIter           &aIter)
 {
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -24,18 +24,18 @@
 #include "mozIStorageConnection.h"
 #include "mozIStorageRow.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageStatementCallback.h"
 #include "mozIStorageFunction.h"
 #include "nsIVariant.h"
 #include "nsIFile.h"
 #include "mozilla/BasePrincipal.h"
-
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Maybe.h"
 
 using mozilla::NeckoOriginAttributes;
 using mozilla::OriginAttributes;
 
 class nsICookiePermission;
 class nsIEffectiveTLDService;
 class nsIIDNService;
 class nsIPrefBranch;
@@ -288,36 +288,38 @@ class nsCookieService final : public nsI
     void                          AsyncReadComplete();
     void                          CancelAsyncRead(bool aPurgeReadSet);
     void                          EnsureReadDomain(const nsCookieKey &aKey);
     void                          EnsureReadComplete();
     nsresult                      NormalizeHost(nsCString &aHost);
     nsresult                      GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, bool &aRequireHostMatch);
     nsresult                      GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
     nsresult                      GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie);
-  void                            GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const NeckoOriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookie);
+    void                          GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const NeckoOriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookie);
     nsresult                      SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp);
-  void                            SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const NeckoOriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel* aChannel);
+    void                          SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const NeckoOriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel* aChannel);
     bool                          SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp, nsIChannel* aChannel);
     void                          AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp);
     void                          RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = nullptr);
     void                          AddCookieToList(const nsCookieKey& aKey, nsCookie *aCookie, DBState *aDBState, mozIStorageBindingParamsArray *aParamsArray, bool aWriteToDB = true);
     void                          UpdateCookieInList(nsCookie *aCookie, int64_t aLastAccessed, mozIStorageBindingParamsArray *aParamsArray);
     static bool                   GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, bool &aEqualsFound);
     static bool                   ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
     bool                          RequireThirdPartyCheck();
     CookieStatus                  CheckPrefs(nsIURI *aHostURI, bool aIsForeign, const char *aCookieHeader);
     bool                          CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch);
     static bool                   CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static bool                   CheckPrefixes(nsCookieAttributes &aCookie, bool aSecureRequest);
     static bool                   GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
     void                          RemoveAllFromMemory();
     already_AddRefed<nsIArray>    PurgeCookies(int64_t aCurrentTimeInUsec);
     bool                          FindCookie(const nsCookieKey& aKey, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter);
-    void                          FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, nsListIter &aIter);
+    bool                          FindSecureCookie(const nsCookieKey& aKey, nsCookie* aCookie);
+    int64_t                       FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, mozilla::Maybe<bool> aIsSecure, nsListIter &aIter);
+    void                          TelemetryForEvictingStaleCookie(nsCookie* aEvicted, int64_t oldestCookieTime);
     void                          NotifyRejected(nsIURI *aHostURI);
     void                          NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
     void                          NotifyChanged(nsISupports *aSubject, const char16_t *aData);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
     void                          UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
 
     nsresult                      GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, nsISimpleEnumerator **aEnumerator);
@@ -347,16 +349,17 @@ class nsCookieService final : public nsI
     // want to be dealing with the on-disk DB when in private browsing.
     DBState                      *mDBState;
     RefPtr<DBState>             mDefaultDBState;
     RefPtr<DBState>             mPrivateDBState;
 
     // cached prefs
     uint8_t                       mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN}
     bool                          mThirdPartySession;
+    bool                          mLeaveSecureAlone;
     uint16_t                      mMaxNumberOfCookies;
     uint16_t                      mMaxCookiesPerHost;
     int64_t                       mCookiePurgeAge;
 
     // friends!
     friend class DBListenerErrorHandler;
     friend class ReadCookieDBListener;
     friend class CloseCookieDBListener;
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -25,16 +25,17 @@ static NS_DEFINE_CID(kCookieServiceCID, 
 static NS_DEFINE_CID(kPrefServiceCID,   NS_PREFSERVICE_CID);
 
 // various pref strings
 static const char kCookiesPermissions[] = "network.cookie.cookieBehavior";
 static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
 static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
 static const char kCookiesLifetimeCurrentSession[] = "network.cookie.lifetime.behavior";
 static const char kCookiesMaxPerHost[] = "network.cookie.maxPerHost";
+static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
 
 static char *sBuffer;
 
 #define OFFSET_ONE_WEEK int64_t(604800) * PR_USEC_PER_SEC
 #define OFFSET_ONE_DAY int64_t(86400) * PR_USEC_PER_SEC
 
 //Set server time or expiry time
 void
@@ -208,16 +209,17 @@ InitPrefs(nsIPrefBranch *aPrefBranch)
 {
     // init some relevant prefs, so the tests don't go awry.
     // we use the most restrictive set of prefs we can;
     // however, we don't test third party blocking here.
     aPrefBranch->SetIntPref(kCookiesPermissions, 0); // accept all
     aPrefBranch->SetBoolPref(kCookiesLifetimeEnabled, true);
     aPrefBranch->SetIntPref(kCookiesLifetimeCurrentSession, 0);
     aPrefBranch->SetIntPref(kCookiesLifetimeDays, 1);
+    aPrefBranch->SetBoolPref(kCookieLeaveSecurityAlone, true);
     // Set the base domain limit to 50 so we have a known value.
     aPrefBranch->SetIntPref(kCookiesMaxPerHost, 50);
 }
 
 
 int
 main(int32_t argc, char *argv[])
 {
@@ -681,16 +683,59 @@ main(int32_t argc, char *argv[])
       SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-e=test; secure", nullptr);
       SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-f=test; secure; path=/", nullptr);
       SetACookie(cookieService, "https://host.prefixed.test/some/path", nullptr, "__Host-g=test; secure; path=/some", nullptr);
       GetACookie(cookieService, "https://host.prefixed.test/", nullptr, getter_Copies(cookie));
       rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "__Host-f=test");
 
       allTestsPassed = PrintResult(rv, 7) && allTestsPassed;
 
+      // *** leave-secure-alone tests
+      sBuffer = PR_sprintf_append(sBuffer, "*** Beginning leave-secure-alone tests...\n");
+
+      // testing items 0 & 1 for 3.1 of spec Deprecate modification of ’secure’
+      // cookies from non-secure origins
+      SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security; secure", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+      rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+      SetACookie(cookieService, "https://www.security.test/path/", nullptr, "test=security; secure; path=/path/", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+      rv[1] = CheckResult(cookie.get(), MUST_EQUAL, "test=security");
+      // testing items 2 & 3 & 4 for 3.2 of spec Deprecate modification of ’secure’
+      // cookies from non-secure origins
+      // Secure site can modify cookie value
+      SetACookie(cookieService, "https://www.security.test/path/", nullptr, "test=security2; secure; path=/path/", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/path/", getter_Copies(cookie));
+      rv[2] = CheckResult(cookie.get(), MUST_EQUAL, "test=security2");
+      // If new cookie contains same name, same host and partially matching path with
+      // an existing security cookie on non-security site, it can't modify an existing
+      // security cookie.
+      SetACookie(cookieService, "http://www.security.test/path/foo/", nullptr, "test=non-security; path=/path/foo", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/path/foo/", getter_Copies(cookie));
+      rv[3] = CheckResult(cookie.get(), MUST_EQUAL, "test=security2");
+      // Non-secure cookie can set by same name, same host and non-matching path.
+      SetACookie(cookieService, "http://www.security.test/bar/", nullptr, "test=non-security; path=/bar", nullptr);
+      GetACookieNoHttp(cookieService, "http://www.security.test/bar/", getter_Copies(cookie));
+      rv[4] = CheckResult(cookie.get(), MUST_EQUAL, "test=non-security");
+      // Modify value and downgrade secure level.
+      SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=security-cookie; secure; domain=.security.test", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+      rv[5] = CheckResult(cookie.get(), MUST_EQUAL, "test_modify_cookie=security-cookie");
+      SetACookie(cookieService, "https://www.security.test/", nullptr, "test_modify_cookie=non-security-cookie; domain=.security.test", nullptr);
+      GetACookieNoHttp(cookieService, "https://www.security.test/", getter_Copies(cookie));
+      rv[6] = CheckResult(cookie.get(), MUST_EQUAL, "test_modify_cookie=non-security-cookie");
+      // Test the non-security cookie can set when domain or path not same to secure cookie of same name.
+      SetACookie(cookieService, "https://www.security.test/", nullptr, "test=security3", nullptr);
+      GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+      rv[7] = CheckResult(cookie.get(), MUST_CONTAIN, "test=security3");
+      SetACookie(cookieService, "http://www.security.test/", nullptr, "test=non-security2; domain=security.test", nullptr);
+      GetACookieNoHttp(cookieService, "http://www.security.test/", getter_Copies(cookie));
+      rv[8] = CheckResult(cookie.get(), MUST_CONTAIN, "test=non-security2");
+
+      allTestsPassed = PrintResult(rv, 9) && allTestsPassed;
 
       // *** nsICookieManager{2} interface tests
       sBuffer = PR_sprintf_append(sBuffer, "*** Beginning nsICookieManager{2} interface tests...\n");
       nsCOMPtr<nsICookieManager> cookieMgr = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0);
       if (NS_FAILED(rv0)) return -1;
       nsCOMPtr<nsICookieManager2> cookieMgr2 = do_QueryInterface(cookieMgr);
       if (!cookieMgr2) return -1;
 
@@ -815,16 +860,40 @@ main(int32_t argc, char *argv[])
             expected += NS_LITERAL_CSTRING("; ");
         }
       }
       GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
       rv[0] = CheckResult(cookie.get(), MUST_EQUAL, expected.get());
 
       allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
 
+      // *** eviction and creation ordering tests after enable network.cookie.leave-secure-alone
+      sBuffer = PR_sprintf_append(sBuffer, "*** Beginning eviction and creation tests after enable nework.cookie.leave-secure-alone...\n");
+      // reset cookie
+      cookieMgr->RemoveAll();
+
+      for (int32_t i = 0; i < 60; ++i) {
+        name = NS_LITERAL_CSTRING("test");
+        name.AppendInt(i);
+        name += NS_LITERAL_CSTRING("=delete_non_security");
+
+        // Create 50 cookies that include the secure flag.
+        if (i < 50) {
+          name += NS_LITERAL_CSTRING("; secure");
+          SetACookie(cookieService, "https://creation.ordering.tests/", nullptr, name.get(), nullptr);
+        } else {
+          // non-security cookies will be removed beside the latest cookie that be created.
+          SetACookie(cookieService, "http://creation.ordering.tests/", nullptr, name.get(), nullptr);
+        }
+      }
+      GetACookie(cookieService, "http://creation.ordering.tests/", nullptr, getter_Copies(cookie));
+      rv[0] = CheckResult(cookie.get(), MUST_BE_NULL);
+
+      allTestsPassed = PrintResult(rv, 1) && allTestsPassed;
+
 
       // XXX the following are placeholders: add these tests please!
       // *** "noncompliant cookie" tests
       // *** IP address tests
       // *** speed tests
 
 
       sBuffer = PR_sprintf_append(sBuffer, "\n*** Result: %s!\n\n", allTestsPassed ? "all tests passed" : "TEST(S) FAILED");
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7884,16 +7884,24 @@
   "COOKIE_SCHEME_SECURITY": {
     "alert_emails": ["seceng@mozilla.org"],
     "expires_in_version": "55",
     "kind": "enumerated",
     "n_values": 10,
     "releaseChannelCollection": "opt-out",
     "description": "How often are secure cookies set from non-secure origins, and vice-versa? 0=nonsecure/http, 1=nonsecure/https, 2=secure/http, 3=secure/https"
   },
+  "COOKIE_LEAVE_SECURE_ALONE": {
+    "alert_emails": ["seceng@mozilla.org"],
+    "expires_in_version": "57",
+    "kind": "enumerated",
+    "n_values": 10,
+    "releaseChannelCollection": "opt-out",
+    "description": "Measuring the effects of draft-ietf-httpbis-cookie-alone blocking. 0=blocked http setting secure cookie; 1=blocked http downgrading secure cookie; 2=blocked evicting secure cookie; 3=evicting newer insecure cookie; 4=evicting the oldest insecure cookie; 5=evicting the preferred cookie; 6=evicting the secure blocked"
+  },
   "NTLM_MODULE_USED_2": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 8,
     "description": "The module used for the NTLM protocol (Windows_API, Kerberos, Samba_auth or Generic) and whether or not the authentication was used to connect to a proxy server. This data is collected only once per session (at first NTLM authentification) ; fixed version."
   },
   "FX_THUMBNAILS_BG_QUEUE_SIZE_ON_CAPTURE": {
     "expires_in_version": "default",
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -853,16 +853,17 @@
     "CHARSET_OVERRIDE_USED",
     "CHECK_ADDONS_MODIFIED_MS",
     "CHECK_JAVA_ENABLED",
     "COMPONENTS_SHIM_ACCESSED_BY_CONTENT",
     "COMPOSITE_FRAME_ROUNDTRIP_TIME",
     "COMPOSITE_TIME",
     "CONTENT_DOCUMENTS_DESTROYED",
     "COOKIE_SCHEME_SECURITY",
+    "COOKIE_LEAVE_SECURE_ALONE",
     "CRASH_STORE_COMPRESSED_BYTES",
     "CYCLE_COLLECTOR",
     "CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING",
     "CYCLE_COLLECTOR_COLLECTED",
     "CYCLE_COLLECTOR_FINISH_IGC",
     "CYCLE_COLLECTOR_FULL",
     "CYCLE_COLLECTOR_MAX_PAUSE",
     "CYCLE_COLLECTOR_NEED_GC",