diff --git a/mozilla/netwerk/cookie/src/nsCookie.cpp b/mozilla/netwerk/cookie/src/nsCookie.cpp index 83b8b6e76c4..010f4b4eecc 100644 --- a/mozilla/netwerk/cookie/src/nsCookie.cpp +++ b/mozilla/netwerk/cookie/src/nsCookie.cpp @@ -89,6 +89,7 @@ nsCookie::Create(const nsACString &aName, const nsACString &aHost, const nsACString &aPath, PRInt64 aExpiry, + PRInt64 aLastAccessed, PRInt64 aCreationID, PRBool aIsSession, PRBool aIsSecure, @@ -119,7 +120,7 @@ nsCookie::Create(const nsACString &aName, // construct the cookie. placement new, oh yeah! return new (place) nsCookie(name, value, host, path, end, - aExpiry, aCreationID, + aExpiry, aLastAccessed, aCreationID, aIsSession, aIsSecure, aIsHttpOnly); } diff --git a/mozilla/netwerk/cookie/src/nsCookie.h b/mozilla/netwerk/cookie/src/nsCookie.h index 957897fa7d7..4902b5ac549 100644 --- a/mozilla/netwerk/cookie/src/nsCookie.h +++ b/mozilla/netwerk/cookie/src/nsCookie.h @@ -70,6 +70,7 @@ class nsCookie : public nsICookie2 const char *aPath, const char *aEnd, PRInt64 aExpiry, + PRInt64 aLastAccessed, PRInt64 aCreationID, PRBool aIsSession, PRBool aIsSecure, @@ -81,6 +82,7 @@ class nsCookie : public nsICookie2 , mPath(aPath) , mEnd(aEnd) , mExpiry(aExpiry) + , mLastAccessed(aLastAccessed) , mCreationID(aCreationID) , mIsSession(aIsSession != PR_FALSE) , mIsSecure(aIsSecure != PR_FALSE) @@ -96,6 +98,7 @@ class nsCookie : public nsICookie2 const nsACString &aHost, const nsACString &aPath, PRInt64 aExpiry, + PRInt64 aLastAccessed, PRInt64 aCreationID, PRBool aIsSession, PRBool aIsSecure, @@ -110,6 +113,7 @@ class nsCookie : public nsICookie2 inline const nsDependentCString RawHost() const { return nsDependentCString(IsDomain() ? mHost + 1 : mHost, mPath - 1); } inline const nsDependentCString Path() const { return nsDependentCString(mPath, mEnd); } inline PRInt64 Expiry() const { return mExpiry; } + inline PRInt64 LastAccessed() const { return mLastAccessed; } inline PRInt64 CreationID() const { return mCreationID; } // cookie creation time, in seconds inline PRInt64 CreationTime() const { return mCreationID / PR_USEC_PER_SEC; } @@ -120,6 +124,7 @@ class nsCookie : public nsICookie2 // setters inline void SetExpiry(PRInt64 aExpiry) { mExpiry = aExpiry; } + inline void SetLastAccessed(PRInt64 aTime) { mLastAccessed = aTime; } inline void SetIsSession(PRBool aIsSession) { mIsSession = aIsSession; } // set the creation id manually, overriding the monotonicity checks in Create(). // use with caution! @@ -142,6 +147,7 @@ class nsCookie : public nsICookie2 const char *mPath; const char *mEnd; PRInt64 mExpiry; + PRInt64 mLastAccessed; // creation id is unique for each cookie and approximately represents the cookie // creation time, in microseconds. PRInt64 mCreationID; diff --git a/mozilla/netwerk/cookie/src/nsCookieService.cpp b/mozilla/netwerk/cookie/src/nsCookieService.cpp index 2d1fa73e7ee..52a743da2f4 100644 --- a/mozilla/netwerk/cookie/src/nsCookieService.cpp +++ b/mozilla/netwerk/cookie/src/nsCookieService.cpp @@ -1,5 +1,4 @@ -// vim:ts=2:sw=2:et: -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -83,7 +82,9 @@ static const char kHttpOnlyPrefix[] = "#HttpOnly_"; static const char kCookieFileName[] = "cookies.sqlite"; -#define COOKIES_SCHEMA_VERSION 1 +#define COOKIES_SCHEMA_VERSION 2 + +static const PRUint32 kCookieStaleThreshold = 60; // 1 minute in seconds static const char kOldCookieFileName[] = "cookies.txt"; @@ -126,7 +127,6 @@ struct nsCookieAttributes nsCAutoString expires; nsCAutoString maxage; PRInt64 expiryTime; - PRInt64 creationID; PRBool isSession; PRBool isSecure; PRBool isHttpOnly; @@ -162,17 +162,17 @@ struct nsListIter struct nsEnumerationData { nsEnumerationData(PRInt64 aCurrentTime, - PRInt64 aOldestID) + PRInt64 aOldestTime) : currentTime(aCurrentTime) - , oldestID(aOldestID) + , oldestTime(aOldestTime) , iter(nsnull, nsnull, nsnull) {} // the current time PRInt64 currentTime; - // oldest creation id in the cookie list. use aOldestID = LL_MAXINT + // oldest lastAccessed time in the cookie list. use aOldestTime = LL_MAXINT // to enable this search, LL_MININT to disable it. - PRInt64 oldestID; + PRInt64 oldestTime; // an iterator object that points to the desired cookie nsListIter iter; @@ -482,59 +482,65 @@ nsCookieService::InitDB() PRInt32 dbSchemaVersion; rv = mDBConn->GetSchemaVersion(&dbSchemaVersion); NS_ENSURE_SUCCESS(rv, rv); - - if (dbSchemaVersion == 0) { - NS_WARNING("couldn't get schema version!"); - - // the table may be usable; someone might've just clobbered the schema - // version. we can treat this case like a downgrade using the codepath - // below, by verifying the columns we care about are all there. for now, - // re-set the schema version in the db, in case the checks succeed (if - // they don't, we're dropping the table anyway). - rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); - NS_ENSURE_SUCCESS(rv, rv); - // set this to a large number, to force the downgrade codepath - dbSchemaVersion = PR_INT32_MAX; - } + switch (dbSchemaVersion) { + // upgrading. + // every time you increment the database schema, you need to implement + // the upgrading code from the previous version to the new one. + case 1: + { + // add the lastAccessed column to the table + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE moz_cookies ADD lastAccessed INTEGER")); + NS_ENSURE_SUCCESS(rv, rv); - if (dbSchemaVersion != COOKIES_SCHEMA_VERSION) { - // migration how-to: - // - // 1. increment COOKIES_SCHEMA_VERSION. - // 2. implement a method that performs up/sidegrade to your version - // from the current version. + // update the schema version + rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); + NS_ENSURE_SUCCESS(rv, rv); + } + // fall through to the next upgrade - if (dbSchemaVersion > COOKIES_SCHEMA_VERSION) { - // downgrading. - // if columns have been added to the table, we can still use the ones we - // understand safely. if columns have been deleted or altered, just - // blow away the table and start from scratch! if you change the way - // a column is interpreted, make sure you also change its name so this - // check will catch it. - - // NOTE: if you change the code below, make sure the db schema version - // getter above still falls through to this codepath on failure! - + case COOKIES_SCHEMA_VERSION: + break; + + case 0: + { + NS_WARNING("couldn't get schema version!"); + + // the table may be usable; someone might've just clobbered the schema + // version. we can treat this case like a downgrade using the codepath + // below, by verifying the columns we care about are all there. for now, + // re-set the schema version in the db, in case the checks succeed (if + // they don't, we're dropping the table anyway). + rv = mDBConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); + NS_ENSURE_SUCCESS(rv, rv); + } + // fall through to downgrade check + + // downgrading. + // if columns have been added to the table, we can still use the ones we + // understand safely. if columns have been deleted or altered, just + // blow away the table and start from scratch! if you change the way + // a column is interpreted, make sure you also change its name so this + // check will catch it. + default: + { // check if all the expected columns exist nsCOMPtr stmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly " "FROM moz_cookies"), getter_AddRefs(stmt)); - if (NS_SUCCEEDED(rv)) { - PRBool hasResult; - rv = stmt->ExecuteStep(&hasResult); - } - - if (NS_FAILED(rv)) { - // our columns aren't there - drop the table! - rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies")); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_SUCCEEDED(rv)) + break; - rv = CreateTable(); - NS_ENSURE_SUCCESS(rv, rv); - } + // our columns aren't there - drop the table! + rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_cookies")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CreateTable(); + NS_ENSURE_SUCCESS(rv, rv); } + break; } } @@ -544,14 +550,18 @@ nsCookieService::InitDB() // cache frequently used statements (for insertion, deletion, and updating) rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "INSERT INTO moz_cookies " - "(id, name, value, host, path, expiry, isSecure, isHttpOnly) " - "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"), getter_AddRefs(mStmtInsert)); + "(id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly) " + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"), getter_AddRefs(mStmtInsert)); NS_ENSURE_SUCCESS(rv, rv); rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( "DELETE FROM moz_cookies WHERE id = ?1"), getter_AddRefs(mStmtDelete)); NS_ENSURE_SUCCESS(rv, rv); + rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( + "UPDATE moz_cookies SET lastAccessed = ?1 WHERE id = ?2"), getter_AddRefs(mStmtUpdate)); + NS_ENSURE_SUCCESS(rv, rv); + // check whether to import or just read in the db if (tableExists) return Read(); @@ -580,7 +590,7 @@ nsCookieService::CreateTable() return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE moz_cookies (" "id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT," - "expiry INTEGER, isSecure INTEGER, isHttpOnly INTEGER)")); + "expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)")); } nsCookieService::~nsCookieService() @@ -599,13 +609,14 @@ nsCookieService::Observe(nsISupports *aSubject, // or is going away because the application is shutting down. RemoveAllFromMemory(); - if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get()) && mDBConn) + if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get()) && mDBConn) { // clear the cookie file if (mDBConn) { nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies")); if (NS_FAILED(rv)) NS_WARNING("db delete failed"); } + } } else if (!strcmp(aTopic, "profile-do-change")) { // the profile has already changed; init the db from the new location @@ -824,6 +835,7 @@ nsCookieService::Add(const nsACString &aDomain, nsRefPtr cookie = nsCookie::Create(aName, aValue, aDomain, aPath, aExpiry, + currentTimeInUsec / PR_USEC_PER_SEC, currentTimeInUsec, aIsSession, aIsSecure, @@ -893,7 +905,7 @@ nsCookieService::Read() // let the reading begin! nsCOMPtr stmt; rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING( - "SELECT id, name, value, host, path, expiry, isSecure, isHttpOnly " + "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly " "FROM moz_cookies"), getter_AddRefs(stmt)); NS_ENSURE_SUCCESS(rv, rv); @@ -908,13 +920,15 @@ nsCookieService::Read() stmt->GetUTF8String(4, path); PRInt64 expiry = stmt->AsInt64(5); - PRBool isSecure = stmt->AsInt32(6); - PRBool isHttpOnly = stmt->AsInt32(7); + PRInt64 lastAccessed = stmt->AsInt64(6); + PRBool isSecure = stmt->AsInt32(7); + PRBool isHttpOnly = stmt->AsInt32(8); // create a new nsCookie and assign the data. nsCookie* newCookie = nsCookie::Create(name, value, host, path, expiry, + lastAccessed, creationID, PR_FALSE, isSecure, @@ -964,12 +978,11 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile) PRBool isDomain, isHttpOnly = PR_FALSE; PRUint32 originalCookieCount = mCookieCount; - // generate a creation id for all the cookies we're going to read in, by - // using the current time and successively decrementing it, to keep - // the most-recently-used cookie ordering. the actual creation time is - // unknown, so this is the best we can do. - PRInt64 creationIDCounter = PR_Now(); - PRInt64 currentTime = creationIDCounter / PR_USEC_PER_SEC; + PRInt64 currentTimeInUsec = PR_Now(); + PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC; + // we use lastAccessedCounter to keep cookies in recently-used order, + // so we start by initializing to currentTime (somewhat arbitrary) + PRInt64 lastAccessedCounter = currentTime; /* file format is: * @@ -979,7 +992,9 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile) * isDomain is "TRUE" or "FALSE" (default to "FALSE") * isSecure is "TRUE" or "FALSE" (default to "TRUE") * expires is a PRInt64 integer - * note: cookie can contain tabs. + * note 1: cookie can contain tabs. + * note 2: cookies will be stored in order of lastAccessed time: + * most-recently used come first; least-recently-used come last. */ /* @@ -1035,13 +1050,17 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile) } // create a new nsCookie and assign the data. + // we don't know the cookie creation time, so just use the current time; + // this is okay, since nsCookie::Create() will make sure the creation id + // ends up monotonically increasing. nsRefPtr newCookie = nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1), Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), host, Substring(buffer, pathIndex, secureIndex - pathIndex - 1), expires, - creationIDCounter, + lastAccessedCounter, + currentTimeInUsec, PR_FALSE, Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue), isHttpOnly); @@ -1049,10 +1068,9 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile) return NS_ERROR_OUT_OF_MEMORY; } - // manually set the creation id. this is okay, since nsCookie::Create() will keep - // track of the largest id we've used, and we're setting it to a smaller - // known unique one here. - newCookie->SetCreationID(--creationIDCounter); + // trick: preserve the most-recently-used cookie ordering, + // by successively decrementing the lastAccessed time + lastAccessedCounter--; if (originalCookieCount == 0) AddCookieToList(newCookie); @@ -1125,6 +1143,7 @@ nsCookieService::GetCookieInternal(nsIURI *aHostURI, PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC; const char *currentDot = hostFromURI.get(); const char *nextDot = currentDot + 1; + PRBool stale = PR_FALSE; // begin hash lookup, walking up the subdomain levels. // we use nextDot to force a lookup of the original host (without leading dot). @@ -1172,8 +1191,10 @@ nsCookieService::GetCookieInternal(nsIURI *aHostURI, continue; } - // all checks passed - add to list + // all checks passed - add to list and check if lastAccessed stamp needs updating foundCookieList.AppendElement(cookie); + if (currentTime - cookie->LastAccessed() > kCookieStaleThreshold) + stale = PR_TRUE; } currentDot = nextDot; @@ -1182,13 +1203,31 @@ nsCookieService::GetCookieInternal(nsIURI *aHostURI, } while (currentDot); + PRInt32 count = foundCookieList.Count(); + if (count == 0) + return; + + // update lastAccessed timestamps. we only do this if the timestamp is stale + // by a certain amount, to avoid thrashing the db during pageload. + if (stale) { + // start a transaction on the storage db, to optimize updates. + // transaction will automically commit on completion. + mozStorageTransaction transaction(mDBConn, PR_TRUE); + + for (PRInt32 i = 0; i < count; ++i) { + cookie = static_cast(foundCookieList.ElementAt(i)); + + if (currentTime - cookie->LastAccessed() > kCookieStaleThreshold) + UpdateCookieInList(cookie, currentTime); + } + } + // return cookies in order of path length; longest to shortest. // this is required per RFC2109. if cookies match in length, // then sort by creation time (see bug 236772). foundCookieList.Sort(compareCookiesForSending, nsnull); nsCAutoString cookieData; - PRInt32 count = foundCookieList.Count(); for (PRInt32 i = 0; i < count; ++i) { cookie = static_cast(foundCookieList.ElementAt(i)); @@ -1242,9 +1281,7 @@ nsCookieService::SetCookieInternal(nsIURI *aHostURI, // so we can handle them separately. PRBool newCookie = ParseAttributes(aCookieHeader, cookieAttributes); - // generate a creation id for the cookie PRInt64 currentTimeInUsec = PR_Now(); - cookieAttributes.creationID = currentTimeInUsec; // calculate expiry time of cookie. cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime, @@ -1278,7 +1315,8 @@ nsCookieService::SetCookieInternal(nsIURI *aHostURI, cookieAttributes.host, cookieAttributes.path, cookieAttributes.expiryTime, - cookieAttributes.creationID, + currentTimeInUsec / PR_USEC_PER_SEC, + currentTimeInUsec, cookieAttributes.isSession, cookieAttributes.isSecure, cookieAttributes.isHttpOnly); @@ -1360,6 +1398,10 @@ nsCookieService::AddInternal(nsCookie *aCookie, return; } + // preserve creation time of cookie + if (oldCookie) + aCookie->SetCreationID(oldCookie->CreationID()); + } else { // check if cookie has already expired if (aCookie->Expiry() <= aCurrentTime) { @@ -1381,7 +1423,7 @@ nsCookieService::AddInternal(nsCookie *aCookie, // check if we still have to get rid of something if (mCookieCount >= mMaxNumberOfCookies) { // find the position of the oldest cookie, and remove it - data.oldestID = LL_MAXINT; + data.oldestTime = LL_MAXINT; FindOldestCookie(data); oldCookie = data.iter.current; RemoveCookieFromList(data.iter); @@ -2091,8 +2133,8 @@ nsCookieService::CountCookiesFromHostInternal(const nsACString &aHost, ++countFromHost; // check if we've found the oldest cookie so far - if (aData.oldestID > iter.current->CreationID()) { - aData.oldestID = iter.current->CreationID(); + if (aData.oldestTime > iter.current->LastAccessed()) { + aData.oldestTime = iter.current->LastAccessed(); aData.iter = iter; } } @@ -2205,10 +2247,13 @@ bindCookieParameters(mozIStorageStatement* aStmt, const nsCookie* aCookie) rv = aStmt->BindInt64Parameter(5, aCookie->Expiry()); if (NS_FAILED(rv)) return rv; - rv = aStmt->BindInt32Parameter(6, aCookie->IsSecure()); + rv = aStmt->BindInt64Parameter(6, aCookie->LastAccessed()); if (NS_FAILED(rv)) return rv; - rv = aStmt->BindInt32Parameter(7, aCookie->IsHttpOnly()); + rv = aStmt->BindInt32Parameter(7, aCookie->IsSecure()); + if (NS_FAILED(rv)) return rv; + + rv = aStmt->BindInt32Parameter(8, aCookie->IsHttpOnly()); return rv; } @@ -2248,6 +2293,33 @@ nsCookieService::AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB) return PR_TRUE; } +void +nsCookieService::UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed) +{ + // update the lastAccessed timestamp + aCookie->SetLastAccessed(aLastAccessed); + + // if it's a non-session cookie, update it in the db too + if (!aCookie->IsSession() && mStmtUpdate) { + // use our cached sqlite "update" statement + mozStorageStatementScoper scoper(mStmtUpdate); + + nsresult rv = mStmtUpdate->BindInt64Parameter(0, aLastAccessed); + if (NS_SUCCEEDED(rv)) { + rv = mStmtUpdate->BindInt64Parameter(1, aCookie->CreationID()); + if (NS_SUCCEEDED(rv)) { + PRBool hasResult; + rv = mStmtUpdate->ExecuteStep(&hasResult); + } + } + + if (NS_FAILED(rv)) { + NS_WARNING("db update failed!"); + COOKIE_LOGSTRING(PR_LOG_WARNING, ("UpdateCookieInList(): updating db gave error %x", rv)); + } + } +} + PR_STATIC_CALLBACK(PLDHashOperator) findOldestCallback(nsCookieEntry *aEntry, void *aArg) @@ -2255,8 +2327,8 @@ findOldestCallback(nsCookieEntry *aEntry, nsEnumerationData *data = static_cast(aArg); for (nsListIter iter(aEntry, nsnull, aEntry->Head()); iter.current; ++iter) { // check if we've found the oldest cookie so far - if (data->oldestID > iter.current->CreationID()) { - data->oldestID = iter.current->CreationID(); + if (data->oldestTime > iter.current->LastAccessed()) { + data->oldestTime = iter.current->LastAccessed(); data->iter = iter; } } @@ -2268,3 +2340,4 @@ nsCookieService::FindOldestCookie(nsEnumerationData &aData) { mHostTable.EnumerateEntries(findOldestCallback, &aData); } + diff --git a/mozilla/netwerk/cookie/src/nsCookieService.h b/mozilla/netwerk/cookie/src/nsCookieService.h index 69b33ef7988..6fec4b4788a 100644 --- a/mozilla/netwerk/cookie/src/nsCookieService.h +++ b/mozilla/netwerk/cookie/src/nsCookieService.h @@ -173,6 +173,7 @@ class nsCookieService : public nsICookieService void AddInternal(nsCookie *aCookie, PRInt64 aCurrentTime, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp); void RemoveCookieFromList(nsListIter &aIter); PRBool AddCookieToList(nsCookie *aCookie, PRBool aWriteToDB = PR_TRUE); + void UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed); static PRBool GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, PRBool &aEqualsFound); static PRBool ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie); static PRBool IsIPAddress(const nsAFlatCString &aHost); @@ -195,6 +196,7 @@ class nsCookieService : public nsICookieService nsCOMPtr mDBConn; nsCOMPtr mStmtInsert; nsCOMPtr mStmtDelete; + nsCOMPtr mStmtUpdate; nsCOMPtr mObserverService; nsCOMPtr mPermissionService;