--- 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
}