Bug 1286858 - Data storage and interface changes for SameSite cookies r?valentin draft
authorMark Goodwin <mgoodwin@mozilla.com>
Wed, 01 Mar 2017 12:21:27 +0000
changeset 492039 6b9fef87bb5a3db752aaf36747f554ffc69484c5
parent 490433 1bc2ad020aee2830e0a7941f10958dbec108c254
child 547618 f6ceacd6f2ccb352660ccc16c84d4ef6f325fca2
push id47494
push usermgoodwin@mozilla.com
push dateThu, 02 Mar 2017 17:28:01 +0000
reviewersvalentin
bugs1286858
milestone54.0a1
Bug 1286858 - Data storage and interface changes for SameSite cookies r?valentin MozReview-Commit-ID: At8uWkHm7YU
extensions/cookie/test/unit/test_cookies_sync_failure.js
netwerk/cookie/nsCookie.cpp
netwerk/cookie/nsCookie.h
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsICookie2.idl
netwerk/cookie/nsICookieManager2.idl
netwerk/test/TestCookie.cpp
--- a/extensions/cookie/test/unit/test_cookies_sync_failure.js
+++ b/extensions/cookie/test/unit/test_cookies_sync_failure.js
@@ -13,17 +13,17 @@
 // 4) Migration fails. This will have different modes depending on the initial
 //    version:
 //    a) Schema 1: the 'lastAccessed' column already exists.
 //    b) Schema 2: the 'baseDomain' column already exists; or 'baseDomain'
 //       cannot be computed for a particular host.
 //    c) Schema 3: the 'creationTime' column already exists; or the
 //       'moz_uniqueid' index already exists.
 
-var COOKIE_DATABASE_SCHEMA_CURRENT = 8;
+var COOKIE_DATABASE_SCHEMA_CURRENT = 9;
 
 var test_generator = do_run_test();
 
 function run_test() {
   do_test_pending();
   do_run_generator(test_generator);
 }
 
--- a/netwerk/cookie/nsCookie.cpp
+++ b/netwerk/cookie/nsCookie.cpp
@@ -74,17 +74,18 @@ nsCookie::Create(const nsACString &aName
                  const nsACString &aHost,
                  const nsACString &aPath,
                  int64_t           aExpiry,
                  int64_t           aLastAccessed,
                  int64_t           aCreationTime,
                  bool              aIsSession,
                  bool              aIsSecure,
                  bool              aIsHttpOnly,
-                 const OriginAttributes& aOriginAttributes)
+                 const OriginAttributes& aOriginAttributes,
+                 int32_t           aSameSite)
 {
   // Ensure mValue contains a valid UTF-8 sequence. Otherwise XPConnect will
   // truncate the string after the first invalid octet.
   RefPtr<nsUTF8ConverterService> converter = new nsUTF8ConverterService();
   nsAutoCString aUTF8Value;
   converter->ConvertStringToUTF8(aValue, "UTF-8", false, true, 1, aUTF8Value);
 
   // find the required string buffer size, adding 4 for the terminating nulls
@@ -103,21 +104,26 @@ nsCookie::Create(const nsACString &aName
   StrBlockCopy(aName, aUTF8Value, aHost, aPath,
                name, value, host, path, end);
 
   // If the creationTime given to us is higher than the running maximum, update
   // our maximum.
   if (aCreationTime > gLastCreationTime)
     gLastCreationTime = aCreationTime;
 
+  // If aSameSite is not a sensible value, assume strict
+  if (aSameSite < 0 || aSameSite > nsICookie2::SAMESITE_STRICT) {
+    aSameSite = nsICookie2::SAMESITE_STRICT;
+  }
+
   // construct the cookie. placement new, oh yeah!
   return new (place) nsCookie(name, value, host, path, end,
                               aExpiry, aLastAccessed, aCreationTime,
                               aIsSession, aIsSecure, aIsHttpOnly,
-                              aOriginAttributes);
+                              aOriginAttributes, aSameSite);
 }
 
 size_t
 nsCookie::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
     // There is no need to measure the sizes of the individual string
     // members, since the strings are stored in-line with the nsCookie.
     return aMallocSizeOf(this);
@@ -146,16 +152,17 @@ NS_IMETHODIMP nsCookie::GetExpiry(int64_
 NS_IMETHODIMP nsCookie::GetIsSession(bool *aIsSession)   { *aIsSession = IsSession(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsDomain(bool *aIsDomain)     { *aIsDomain = IsDomain();   return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsSecure(bool *aIsSecure)     { *aIsSecure = IsSecure();   return NS_OK; }
 NS_IMETHODIMP nsCookie::GetIsHttpOnly(bool *aHttpOnly)   { *aHttpOnly = IsHttpOnly(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetStatus(nsCookieStatus *aStatus) { *aStatus = 0;              return NS_OK; }
 NS_IMETHODIMP nsCookie::GetPolicy(nsCookiePolicy *aPolicy) { *aPolicy = 0;              return NS_OK; }
 NS_IMETHODIMP nsCookie::GetCreationTime(int64_t *aCreation){ *aCreation = CreationTime(); return NS_OK; }
 NS_IMETHODIMP nsCookie::GetLastAccessed(int64_t *aTime)    { *aTime = LastAccessed();   return NS_OK; }
+NS_IMETHODIMP nsCookie::GetSameSite(int32_t *aSameSite)    { *aSameSite = SameSite();   return NS_OK; }
 
 NS_IMETHODIMP
 nsCookie::GetOriginAttributes(JSContext *aCx, JS::MutableHandle<JS::Value> aVal)
 {
   if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
--- a/netwerk/cookie/nsCookie.h
+++ b/netwerk/cookie/nsCookie.h
@@ -43,31 +43,33 @@ class nsCookie : public nsICookie2
              const char     *aPath,
              const char     *aEnd,
              int64_t         aExpiry,
              int64_t         aLastAccessed,
              int64_t         aCreationTime,
              bool            aIsSession,
              bool            aIsSecure,
              bool            aIsHttpOnly,
-             const OriginAttributes& aOriginAttributes)
+             const OriginAttributes& aOriginAttributes,
+             int32_t         aSameSite)
      : mName(aName)
      , mValue(aValue)
      , mHost(aHost)
      , mPath(aPath)
      , mEnd(aEnd)
      , mExpiry(aExpiry)
      , mLastAccessed(aLastAccessed)
      , mCreationTime(aCreationTime)
        // Defaults to 60s
      , mCookieStaleThreshold(mozilla::Preferences::GetInt("network.cookie.staleThreshold", 60))
      , mIsSession(aIsSession)
      , mIsSecure(aIsSecure)
      , mIsHttpOnly(aIsHttpOnly)
      , mOriginAttributes(aOriginAttributes)
+     , mSameSite(aSameSite)
     {
     }
 
   public:
     // Generate a unique and monotonically increasing creation time. See comment
     // in nsCookie.cpp.
     static int64_t GenerateUniqueCreationTime(int64_t aCreationTime);
 
@@ -78,33 +80,35 @@ class nsCookie : public nsICookie2
                              const nsACString &aHost,
                              const nsACString &aPath,
                              int64_t           aExpiry,
                              int64_t           aLastAccessed,
                              int64_t           aCreationTime,
                              bool              aIsSession,
                              bool              aIsSecure,
                              bool              aIsHttpOnly,
-                             const OriginAttributes& aOriginAttributes);
+                             const OriginAttributes& aOriginAttributes,
+                             int32_t           aSameSite);
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
     // fast (inline, non-xpcom) getters
     inline const nsDependentCString Name()  const { return nsDependentCString(mName, mValue - 1); }
     inline const nsDependentCString Value() const { return nsDependentCString(mValue, mHost - 1); }
     inline const nsDependentCString Host()  const { return nsDependentCString(mHost, mPath - 1); }
     inline const nsDependentCString RawHost() const { return nsDependentCString(IsDomain() ? mHost + 1 : mHost, mPath - 1); }
     inline const nsDependentCString Path()  const { return nsDependentCString(mPath, mEnd); }
     inline int64_t Expiry()                 const { return mExpiry; }        // in seconds
     inline int64_t LastAccessed()           const { return mLastAccessed; }  // in microseconds
     inline int64_t CreationTime()           const { return mCreationTime; }  // in microseconds
     inline bool IsSession()               const { return mIsSession; }
     inline bool IsDomain()                const { return *mHost == '.'; }
     inline bool IsSecure()                const { return mIsSecure; }
     inline bool IsHttpOnly()              const { return mIsHttpOnly; }
+    inline int32_t SameSite()               const { return mSameSite; }
 
     // setters
     inline void SetExpiry(int64_t aExpiry)        { mExpiry = aExpiry; }
     inline void SetLastAccessed(int64_t aTime)    { mLastAccessed = aTime; }
     inline void SetIsSession(bool aIsSession)     { mIsSession = aIsSession; }
     // Set the creation time manually, overriding the monotonicity checks in
     // Create(). Use with caution!
     inline void SetCreationTime(int64_t aTime)    { mCreationTime = aTime; }
@@ -130,11 +134,12 @@ class nsCookie : public nsICookie2
     int64_t      mExpiry;
     int64_t      mLastAccessed;
     int64_t      mCreationTime;
     int64_t      mCookieStaleThreshold;
     bool mIsSession;
     bool mIsSecure;
     bool mIsHttpOnly;
     mozilla::OriginAttributes mOriginAttributes;
+    int32_t     mSameSite;
 };
 
 #endif // nsCookie_h__
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -73,31 +73,32 @@ using namespace mozilla::net;
 
 static nsCookieService *gCookieService;
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
 #define HTTP_ONLY_PREFIX "#HttpOnly_"
 
 #define COOKIES_FILE "cookies.sqlite"
-#define COOKIES_SCHEMA_VERSION 8
+#define COOKIES_SCHEMA_VERSION 9
 
 // parameter indexes; see EnsureReadDomain, EnsureReadComplete and
 // ReadCookieDBListener::HandleResult
 #define IDX_NAME 0
 #define IDX_VALUE 1
 #define IDX_HOST 2
 #define IDX_PATH 3
 #define IDX_EXPIRY 4
 #define IDX_LAST_ACCESSED 5
 #define IDX_CREATION_TIME 6
 #define IDX_SECURE 7
 #define IDX_HTTPONLY 8
 #define IDX_BASE_DOMAIN 9
 #define IDX_ORIGIN_ATTRIBUTES 10
+#define IDX_SAME_SITE 11
 
 #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
 
 static const int64_t kCookiePurgeAge =
   int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds
 
 #define OLD_COOKIE_FILE_NAME "cookies.txt"
 
@@ -146,16 +147,17 @@ struct nsCookieAttributes
   nsAutoCString host;
   nsAutoCString path;
   nsAutoCString expires;
   nsAutoCString maxage;
   int64_t expiryTime;
   bool isSession;
   bool isSecure;
   bool isHttpOnly;
+  int8_t sameSite;
 };
 
 // stores the nsCookieEntry entryclass and an index into the cookie array
 // within that entryclass, for purposes of storing an iteration state that
 // points to a certain cookie.
 struct nsListIter
 {
   // default (non-initializing) constructor.
@@ -1401,16 +1403,26 @@ nsCookieService::TryInitDB(bool aRecreat
 
         // Recreate our index.
         rv = CreateIndex();
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 8"));
       }
+      MOZ_FALLTHROUGH;
+
+    case 8:
+      {
+        // Add the sameSite column to the table.
+        rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(
+          NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
+        COOKIE_LOGSTRING(LogLevel::Debug,
+          ("Upgraded database to schema version 9"));
+      }
 
       // No more upgrades. Update the schema version.
       rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
       MOZ_FALLTHROUGH;
 
     case COOKIES_SCHEMA_VERSION:
       break;
@@ -1448,17 +1460,18 @@ nsCookieService::TryInitDB(bool aRecreat
             "name, "
             "value, "
             "host, "
             "path, "
             "expiry, "
             "lastAccessed, "
             "creationTime, "
             "isSecure, "
-            "isHttpOnly "
+            "isHttpOnly, "
+            "sameSite "
           "FROM moz_cookies"), getter_AddRefs(stmt));
         if (NS_SUCCEEDED(rv))
           break;
 
         // our columns aren't there - drop the table!
         rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "DROP TABLE moz_cookies"));
         NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
@@ -1489,29 +1502,31 @@ nsCookieService::TryInitDB(bool aRecreat
       "name, "
       "value, "
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
-      "isHttpOnly"
+      "isHttpOnly, "
+      "sameSite "
     ") VALUES ("
       ":baseDomain, "
       ":originAttributes, "
       ":name, "
       ":value, "
       ":host, "
       ":path, "
       ":expiry, "
       ":lastAccessed, "
       ":creationTime, "
       ":isSecure, "
-      ":isHttpOnly"
+      ":isHttpOnly, "
+      ":sameSite"
     ")"),
     getter_AddRefs(mDefaultDBState->stmtInsert));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DELETE FROM moz_cookies "
     "WHERE name = :name AND host = :host AND path = :path"),
     getter_AddRefs(mDefaultDBState->stmtDelete));
@@ -1568,16 +1583,17 @@ nsCookieService::CreateTableWorker(const
       "host TEXT, "
       "path TEXT, "
       "expiry INTEGER, "
       "lastAccessed INTEGER, "
       "creationTime INTEGER, "
       "isSecure INTEGER, "
       "isHttpOnly INTEGER, "
       "inBrowserElement INTEGER DEFAULT 0, "
+      "sameSite INTEGER DEFAULT 0, "
       "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)"
     ")");
   return mDefaultDBState->dbConn->ExecuteSimpleSQL(command);
 }
 
 // Sets the schema version and creates the moz_cookies table.
 nsresult
 nsCookieService::CreateTable()
@@ -2408,44 +2424,46 @@ nsCookieService::Add(const nsACString &a
                      const nsACString &aPath,
                      const nsACString &aName,
                      const nsACString &aValue,
                      bool              aIsSecure,
                      bool              aIsHttpOnly,
                      bool              aIsSession,
                      int64_t           aExpiry,
                      JS::HandleValue   aOriginAttributes,
+                     int32_t           aSameSite,
                      JSContext*        aCx,
                      uint8_t           aArgc)
 {
   MOZ_ASSERT(aArgc == 0 || aArgc == 1);
 
   OriginAttributes attrs;
   nsresult rv = InitializeOriginAttributes(&attrs,
                                            aOriginAttributes,
                                            aCx,
                                            aArgc,
                                            u"nsICookieManager2.add()",
                                            u"2");
   NS_ENSURE_SUCCESS(rv, rv);
 
   return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly,
-                   aIsSession, aExpiry, &attrs);
+                   aIsSession, aExpiry, &attrs, aSameSite);
 }
 
 NS_IMETHODIMP_(nsresult)
 nsCookieService::AddNative(const nsACString &aHost,
                            const nsACString &aPath,
                            const nsACString &aName,
                            const nsACString &aValue,
                            bool              aIsSecure,
                            bool              aIsHttpOnly,
                            bool              aIsSession,
                            int64_t           aExpiry,
-                           OriginAttributes* aOriginAttributes)
+                           OriginAttributes* aOriginAttributes,
+                           int32_t           aSameSite)
 {
   if (NS_WARN_IF(!aOriginAttributes)) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
@@ -2468,17 +2486,18 @@ nsCookieService::AddNative(const nsACStr
   RefPtr<nsCookie> cookie =
     nsCookie::Create(aName, aValue, host, aPath,
                      aExpiry,
                      currentTimeInUsec,
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      aIsSession,
                      aIsSecure,
                      aIsHttpOnly,
-                     key.mOriginAttributes);
+                     key.mOriginAttributes,
+                     aSameSite);
   if (!cookie) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true);
   return NS_OK;
 }
 
@@ -2616,17 +2635,18 @@ nsCookieService::Read()
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
       "baseDomain, "
-      "originAttributes "
+      "originAttributes, "
+      "sameSite "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
   NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
 
   // Set up a statement to delete any rows with a nullptr 'baseDomain'
   // column. This takes care of any cookies set by browsers that don't
   // understand the 'baseDomain' column, where the database schema version
   // is from one that does. (This would occur when downgrading.)
@@ -2677,26 +2697,28 @@ nsCookieService::GetCookieFromRow(T &aRo
   rv = aRow->GetUTF8String(IDX_PATH, path);
   NS_ASSERT_SUCCESS(rv);
 
   int64_t expiry = aRow->AsInt64(IDX_EXPIRY);
   int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED);
   int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME);
   bool isSecure = 0 != aRow->AsInt32(IDX_SECURE);
   bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY);
+  int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE);
 
   // Create a new nsCookie and assign the data.
   return nsCookie::Create(name, value, host, path,
                           expiry,
                           lastAccessed,
                           creationTime,
                           false,
                           isSecure,
                           isHttpOnly,
-                          aOriginAttributes);
+                          aOriginAttributes,
+                          sameSite);
 }
 
 void
 nsCookieService::AsyncReadComplete()
 {
   // We may be in the private browsing DB state, with a pending read on the
   // default DB state. (This would occur if we started up in private browsing
   // mode.) As long as we do all our operations on the default state, we're OK.
@@ -2786,17 +2808,20 @@ nsCookieService::EnsureReadDomain(const 
         "name, "
         "value, "
         "host, "
         "path, "
         "expiry, "
         "lastAccessed, "
         "creationTime, "
         "isSecure, "
-        "isHttpOnly "
+        "isHttpOnly, "
+        "baseDomain, "
+        "originAttributes, "
+        "sameSite "
       "FROM moz_cookies "
       "WHERE baseDomain = :baseDomain "
       "  AND originAttributes = :originAttributes"),
       getter_AddRefs(mDefaultDBState->stmtReadDomain));
 
     if (NS_FAILED(rv)) {
       // Recreate the database.
       COOKIE_LOGSTRING(LogLevel::Debug,
@@ -2880,17 +2905,18 @@ nsCookieService::EnsureReadComplete()
       "host, "
       "path, "
       "expiry, "
       "lastAccessed, "
       "creationTime, "
       "isSecure, "
       "isHttpOnly, "
       "baseDomain, "
-      "originAttributes  "
+      "originAttributes, "
+      "sameSite "
     "FROM moz_cookies "
     "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt));
 
   if (NS_FAILED(rv)) {
     // Recreate the database.
     COOKIE_LOGSTRING(LogLevel::Debug,
       ("EnsureReadComplete(): corruption detected when creating statement "
        "with rv 0x%" PRIx32, static_cast<uint32_t>(rv)));
@@ -3081,17 +3107,18 @@ nsCookieService::ImportCookies(nsIFile *
                        host,
                        Substring(buffer, pathIndex, secureIndex - pathIndex - 1),
                        expires,
                        lastAccessedCounter,
                        nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                        false,
                        Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue),
                        isHttpOnly,
-                       key.mOriginAttributes);
+                       key.mOriginAttributes,
+                       nsICookie2::SAMESITE_UNSET);
     if (!newCookie) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     // trick: preserve the most-recently-used cookie ordering,
     // by successively decrementing the lastAccessed time
     lastAccessedCounter--;
 
@@ -3485,17 +3512,18 @@ nsCookieService::SetCookieInternal(nsIUR
                      cookieAttributes.host,
                      cookieAttributes.path,
                      cookieAttributes.expiryTime,
                      currentTimeInUsec,
                      nsCookie::GenerateUniqueCreationTime(currentTimeInUsec),
                      cookieAttributes.isSession,
                      cookieAttributes.isSecure,
                      cookieAttributes.isHttpOnly,
-                     aKey.mOriginAttributes);
+                     aKey.mOriginAttributes,
+                     cookieAttributes.sameSite);
   if (!cookie)
     return newCookie;
 
   // check permissions from site permission list, or ask the user,
   // to determine if we can set the cookie
   if (mPermissionService) {
     bool permission;
     mPermissionService->CanSetCookie(aHostURI,
@@ -3893,24 +3921,27 @@ nsCookieService::ParseAttributes(nsDepen
                                  nsCookieAttributes &aCookieAttributes)
 {
   static const char kPath[]    = "path";
   static const char kDomain[]  = "domain";
   static const char kExpires[] = "expires";
   static const char kMaxage[]  = "max-age";
   static const char kSecure[]  = "secure";
   static const char kHttpOnly[]  = "httponly";
+  static const char kSameSite[]       = "samesite";
+  static const char kSameSiteLax[]    = "lax";
 
   nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd;
   nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd;
   aCookieHeader.BeginReading(cookieStart);
   aCookieHeader.EndReading(cookieEnd);
 
   aCookieAttributes.isSecure = false;
   aCookieAttributes.isHttpOnly = false;
+  aCookieAttributes.sameSite = nsICookie2::SAMESITE_UNSET;
 
   nsDependentCSubstring tokenString(cookieStart, cookieStart);
   nsDependentCSubstring tokenValue (cookieStart, cookieStart);
   bool newCookie, equalsFound;
 
   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
   // if we find multiple cookies, return for processing
   // note: if there's no '=', we assume token is <VALUE>. this is required by
@@ -3949,16 +3980,24 @@ nsCookieService::ParseAttributes(nsDepen
     // ignore any tokenValue for isSecure; just set the boolean
     else if (tokenString.LowerCaseEqualsLiteral(kSecure))
       aCookieAttributes.isSecure = true;
 
     // ignore any tokenValue for isHttpOnly (see bug 178993);
     // just set the boolean
     else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly))
       aCookieAttributes.isHttpOnly = true;
+
+    else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) {
+      if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) {
+        aCookieAttributes.sameSite = nsICookie2::SAMESITE_LAX;
+      } else {
+        aCookieAttributes.sameSite = nsICookie2::SAMESITE_STRICT;
+      }
+    }
   }
 
   // rebind aCookieHeader, in case we need to process another cookie
   aCookieHeader.Rebind(cookieStart, cookieEnd);
   return newCookie;
 }
 
 /******************************************************************************
@@ -5092,16 +5131,20 @@ bindCookieParameters(mozIStorageBindingP
   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"),
                                aCookie->IsSecure());
   NS_ASSERT_SUCCESS(rv);
 
   rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"),
                                aCookie->IsHttpOnly());
   NS_ASSERT_SUCCESS(rv);
 
+  rv = params->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"),
+                               aCookie->SameSite());
+  NS_ASSERT_SUCCESS(rv);
+
   // Bind the params to the array.
   rv = aParamsArray->AddParams(params);
   NS_ASSERT_SUCCESS(rv);
 }
 
 void
 nsCookieService::UpdateCookieOldestTime(DBState* aDBState,
                                         nsCookie* aCookie)
--- a/netwerk/cookie/nsICookie2.idl
+++ b/netwerk/cookie/nsICookie2.idl
@@ -6,20 +6,23 @@
 #include "nsICookie.idl"
 
 /** 
  * Main cookie object interface for use by consumers:
  * extends nsICookie, a frozen interface for external
  * access of cookie objects
  */
 
-[scriptable, uuid(05c420e5-03d0-4c7b-a605-df7ebe5ca326)]
+[scriptable, uuid(be205dae-4f4c-11e6-80ba-ea5cd310c1a8)]
 
 interface nsICookie2 : nsICookie
 {
+    const uint32_t SAMESITE_UNSET  = 0;
+    const uint32_t SAMESITE_LAX    = 1;
+    const uint32_t SAMESITE_STRICT = 2;
 
     /**
      * the host (possibly fully qualified) of the cookie,
      * without a leading dot to represent if it is a
      * domain cookie.
      */
     readonly attribute AUTF8String rawHost;
 
@@ -55,9 +58,20 @@ interface nsICookie2 : nsICookie
      * the last time the cookie was accessed (i.e. created,
      * modified, or read by the server), in microseconds
      * since midnight (00:00:00), January 1, 1970 UTC.
      *
      * note that this time may be approximate.
      */
     readonly attribute int64_t lastAccessed;
 
+    /**
+     * the sameSite attribute; this controls the cookie behavior for cross-site
+     * requests as per
+     * https://tools.ietf.org/html/draft-west-first-party-cookies-07
+     *
+     * This should be one of:
+     * - SAMESITE_UNSET - the SameSite attribute is not present
+     * - SAMESITE_LAX - the SameSite attribute is present, but not strict
+     * - SAMESITE_STRICT - the SameSite attribute is present and strict
+     */
+    readonly attribute int32_t sameSite;
 };
--- a/netwerk/cookie/nsICookieManager2.idl
+++ b/netwerk/cookie/nsICookieManager2.idl
@@ -41,40 +41,46 @@ interface nsICookieManager2 : nsICookieM
    *        modified by, an http connection.
    * @param aIsSession
    *        true if the cookie should exist for the current session only.
    *        see aExpiry.
    * @param aExpiry
    *        expiration date, in seconds since midnight (00:00:00), January 1,
    *        1970 UTC. note that expiry time will also be honored for session cookies;
    *        in this way, the more restrictive of the two will take effect.
-   * @param aOriginAttributes The originAttributes of this cookie. This
-   *                          attribute is optional to avoid breaking add-ons.
+   * @param aOriginAttributes
+   *        the originAttributes of this cookie. This attribute is optional to
+   *        avoid breaking add-ons.
+   * @param aSameSite
+   *        the SameSite attribute. This attribute is optional to avoid breaking
+   *        addons
    */
   [implicit_jscontext, optional_argc]
   void add(in AUTF8String aHost,
            in AUTF8String aPath,
            in ACString    aName,
            in ACString    aValue,
            in boolean     aIsSecure,
            in boolean     aIsHttpOnly,
            in boolean     aIsSession,
            in int64_t     aExpiry,
-           [optional] in jsval aOriginAttributes);
+           [optional] in jsval aOriginAttributes,
+           [optional] in int32_t aSameSite);
 
   [notxpcom]
   nsresult addNative(in AUTF8String aHost,
                      in AUTF8String aPath,
                      in ACString    aName,
                      in ACString    aValue,
                      in boolean     aIsSecure,
                      in boolean     aIsHttpOnly,
                      in boolean     aIsSession,
                      in int64_t     aExpiry,
-                     in OriginAttributesPtr aOriginAttributes);
+                     in OriginAttributesPtr aOriginAttributes,
+                     in int32_t aSameSite);
 
   /**
    * Find whether a given cookie already exists.
    *
    * @param aCookie
    *        the cookie to look for
    * @param aOriginAttributes
    *        nsICookie2 contains an originAttributes but if nsICookie2 is
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -629,35 +629,38 @@ TEST(TestCookie,TestCookieMain)
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
                                                    NS_LITERAL_CSTRING("/foo"),           // path
                                                    NS_LITERAL_CSTRING("test1"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MAX,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("cookiemgr.test"), // domain
                                                    NS_LITERAL_CSTRING("/foo"),           // path
                                                    NS_LITERAL_CSTRING("test2"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    true,                              // is httponly
                                                    true,                              // is session
                                                    PR_Now() / PR_USEC_PER_SEC + 2,       // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"),     // domain
                                                    NS_LITERAL_CSTRING("/rabbit"),        // path
                                                    NS_LITERAL_CSTRING("test3"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MAX,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     // confirm using enumerator
     nsCOMPtr<nsISimpleEnumerator> enumerator;
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))));
     int32_t i = 0;
     bool more;
     nsCOMPtr<nsICookie2> expiredCookie, newDomainCookie;
     while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsISupports> cookie;
@@ -701,17 +704,18 @@ TEST(TestCookie,TestCookieMain)
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->AddNative(NS_LITERAL_CSTRING("new.domain"),     // domain
                                                    NS_LITERAL_CSTRING("/rabbit"),        // path
                                                    NS_LITERAL_CSTRING("test3"),          // name
                                                    NS_LITERAL_CSTRING("yes"),            // value
                                                    false,                             // is secure
                                                    false,                             // is httponly
                                                    true,                              // is session
                                                    INT64_MIN,                            // expiry time
-                                                   &attrs)));                         // originAttributes
+                                                   &attrs,                            // originAttributes
+                                                   nsICookie2::SAMESITE_UNSET)));
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CookieExistsNative(newDomainCookie, &attrs, &found)));
     EXPECT_FALSE(found);
     // sleep four seconds, to make sure the second cookie has expired
     PR_Sleep(4 * PR_TicksPerSecond());
     // check that both CountCookiesFromHost() and CookieExistsNative() count the
     // expired cookie
     EXPECT_TRUE(NS_SUCCEEDED(cookieMgr2->CountCookiesFromHost(NS_LITERAL_CSTRING("cookiemgr.test"), &hostCookies)));
     EXPECT_EQ(hostCookies, 2u);
@@ -759,15 +763,60 @@ TEST(TestCookie,TestCookieMain)
         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));
+
     EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
 
+
+    // *** SameSite attribute - parsing and cookie storage tests
+    // Clear the cookies
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->RemoveAll()));
+
+    // Set cookies with various incantations of the samesite attribute:
+    // No same site attribute present
+    SetACookie(cookieService, "http://samesite.test", nullptr, "unset=yes", nullptr);
+    // samesite attribute present but with no value
+    SetACookie(cookieService, "http://samesite.test", nullptr, "unspecified=yes; samesite", nullptr);
+    // samesite=strict
+    SetACookie(cookieService, "http://samesite.test", nullptr, "strict=yes; samesite=strict", nullptr);
+    // samesite=lax
+    SetACookie(cookieService, "http://samesite.test", nullptr, "lax=yes; samesite=lax", nullptr);
+
+    EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetEnumerator(getter_AddRefs(enumerator))));
+    i = 0;
+
+    // check the cookies for the required samesite value
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsISupports> cookie;
+      if (NS_FAILED(enumerator->GetNext(getter_AddRefs(cookie)))) break;
+      ++i;
+
+      // keep tabs on the second and third cookies, so we can check them later
+      nsCOMPtr<nsICookie2> cookie2(do_QueryInterface(cookie));
+      if (!cookie2) break;
+      nsAutoCString name;
+      cookie2->GetName(name);
+      int32_t sameSiteAttr;
+      cookie2->GetSameSite(&sameSiteAttr);
+      if (name.EqualsLiteral("unset")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_UNSET);
+      } else if (name.EqualsLiteral("unspecified")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_STRICT);
+      } else if (name.EqualsLiteral("strict")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_STRICT);
+      } else if (name.EqualsLiteral("lax")) {
+        EXPECT_TRUE(sameSiteAttr == nsICookie2::SAMESITE_LAX);
+      }
+    }
+
+    EXPECT_TRUE(i == 4);
+
     // XXX the following are placeholders: add these tests please!
     // *** "noncompliant cookie" tests
     // *** IP address tests
     // *** speed tests
 }