Revert bug 572223 to fix creation of duplicate cookies.

This commit is contained in:
Dan Witte 2010-08-01 12:24:38 -07:00
parent e65cba989c
commit dfcfc976fb
7 changed files with 128 additions and 539 deletions

View File

@ -17,33 +17,36 @@ XPCOMUtils.defineLazyServiceGetter(Services, "cookiemgr",
"@mozilla.org/cookiemanager;1",
"nsICookieManager2");
function _observer(generator, service, topic) {
Services.obs.addObserver(this, topic, false);
// Close and reload the cookie database.
function do_reload_profile(generator, profile, cleanse) {
function _observer(generator, service, topic) {
Services.obs.addObserver(this, topic, false);
this.service = service;
this.generator = generator;
this.topic = topic;
}
this.service = service;
this.generator = generator;
this.topic = topic;
}
_observer.prototype = {
observe: function (subject, topic, data) {
do_check_eq(this.topic, topic);
_observer.prototype = {
observe: function (subject, topic, data) {
do_check_eq(this.topic, topic);
Services.obs.removeObserver(this, this.topic);
Services.obs.removeObserver(this, this.topic);
// Continue executing the generator function.
if (this.generator)
// Fire the notification to reload the database, and continue executing
// the generator function.
this.service.observe(null, "profile-do-change", "");
this.generator.next();
this.generator = null;
this.service = null;
this.topic = null;
this.generator = null;
this.service = null;
this.topic = null;
}
}
}
// Close the cookie database. If a generator is supplied, it will be invoked
// once the close is complete.
function do_close_profile(generator, cleanse) {
let dbfile = profile.QueryInterface(Ci.nsILocalFile).clone();
dbfile.append("cookies.sqlite");
// Register an observer for db close.
let service = Services.cookies.QueryInterface(Ci.nsIObserver);
let obs = new _observer(generator, service, "cookie-db-closed");
@ -52,17 +55,6 @@ function do_close_profile(generator, cleanse) {
service.observe(null, "profile-before-change", cleanse ? cleanse : "");
}
// Load the cookie database. If a generator is supplied, it will be invoked
// once the load is complete.
function do_load_profile(generator) {
// Register an observer for read completion.
let service = Services.cookies.QueryInterface(Ci.nsIObserver);
let obs = new _observer(generator, service, "cookie-db-read");
// Load the profile.
service.observe(null, "profile-do-change", "");
}
// Set four cookies; with & without channel, http and non-http; and test
// the cookie count against 'expected' after each set.
function do_set_cookies(uri, channel, session, expected) {
@ -81,17 +73,3 @@ function do_set_cookies(uri, channel, session, expected) {
Services.cookies.setCookieStringFromHttp(uri, null, null, "hot=dog" + suffix, null, channel);
do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[3]);
}
function do_count_enumerator(enumerator) {
let i = 0;
while (enumerator.hasMoreElements()) {
enumerator.getNext();
++i;
}
return i;
}
function do_count_cookies() {
return do_count_enumerator(Services.cookiemgr.enumerator);
}

View File

@ -47,24 +47,14 @@ function do_run_test() {
do_set_cookies(uri2, channel2, true, [1, 2, 3, 4]);
// fake a profile change
do_close_profile(test_generator);
do_reload_profile(test_generator, profile);
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
// Again, but don't wait for the async close to complete. This should always
// work, since we blocked on close above and haven't kicked off any writes
// since then.
do_close_profile();
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
// cleanse them
do_close_profile(test_generator, "shutdown-cleanse");
do_reload_profile(test_generator, profile, "shutdown-cleanse");
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
@ -74,9 +64,8 @@ function do_run_test() {
do_set_cookies(uri2, channel2, true, [1, 2, 3, 4]);
// fake a profile change
do_close_profile(test_generator);
do_reload_profile(test_generator, profile);
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);

View File

@ -1,67 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// test cookie database asynchronous read operation.
let test_generator = do_run_test();
function run_test() {
do_test_pending();
test_generator.next();
}
function finish_test() {
do_execute_soon(function() {
test_generator.close();
do_test_finished();
});
}
function do_run_test() {
// Set up a profile.
let profile = do_get_profile();
for (let i = 0; i < 3000; ++i) {
let uri = NetUtil.newURI("http://" + i + ".com/");
Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
}
do_check_eq(do_count_cookies(), 3000);
// fake a profile change
do_close_profile(test_generator);
yield;
do_load_profile();
// test a few random cookies
do_check_eq(Services.cookiemgr.countCookiesFromHost("2000.com"), 1);
do_check_eq(Services.cookiemgr.countCookiesFromHost("abc.com"), 0);
do_check_eq(Services.cookiemgr.countCookiesFromHost("100.com"), 1);
do_check_eq(Services.cookiemgr.countCookiesFromHost("1400.com"), 1);
do_check_eq(Services.cookiemgr.countCookiesFromHost("xyz.com"), 0);
// force synchronous load of everything
do_check_eq(do_count_cookies(), 3000);
// check that everything's precisely correct
for (let i = 0; i < 3000; ++i) {
let host = i.toString() + ".com";
do_check_eq(Services.cookiemgr.countCookiesFromHost(host), 1);
}
// reload again, but wait for async read completion
do_close_profile(test_generator);
yield;
do_load_profile(test_generator);
yield;
// check that everything's precisely correct
for (let i = 0; i < 3000; ++i) {
let host = i.toString() + ".com";
do_check_eq(Services.cookiemgr.countCookiesFromHost(host), 1);
}
finish_test();
}

View File

@ -47,16 +47,14 @@ function do_run_test() {
do_set_cookies(uri2, channel1, true, [1, 2, 3, 4]);
// fake a profile change
do_close_profile(test_generator);
do_reload_profile(test_generator, profile);
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
// cleanse them
do_close_profile(test_generator, "shutdown-cleanse");
do_reload_profile(test_generator, profile, "shutdown-cleanse");
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
@ -66,9 +64,8 @@ function do_run_test() {
do_set_cookies(uri2, channel1, true, [1, 2, 3, 4]);
// fake a profile change
do_close_profile(test_generator);
do_reload_profile(test_generator, profile);
yield;
do_load_profile();
do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);

View File

@ -87,8 +87,6 @@ using namespace mozilla::net;
* useful types & constants
******************************************************************************/
static nsCookieService *gCookieService;
// XXX_hack. See bug 178993.
// This is a hack to hide HttpOnly cookies from older browsers
static const char kHttpOnlyPrefix[] = "#HttpOnly_";
@ -359,8 +357,9 @@ LogSuccess(PRBool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieStr
#define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
#endif
namespace {
/******************************************************************************
* DBListenerErrorHandler impl:
* DBListenerErrorHandler imp:
* Parent class for our async storage listeners that handles the logging of
* errors.
******************************************************************************/
@ -374,7 +373,6 @@ public:
NS_IMETHOD HandleError(mozIStorageError* aError)
{
// XXX Ignore corruption handling for now. See bug 547031.
#ifdef PR_LOGGING
PRInt32 result = -1;
aError->GetResult(&result);
@ -391,8 +389,9 @@ public:
NS_IMPL_ISUPPORTS1(DBListenerErrorHandler, mozIStorageStatementCallback)
/******************************************************************************
* InsertCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous insertion operations.
* InsertCookieDBListener imp:
* Static mozIStorageStatementCallback used to track asynchronous insertion
* operations.
******************************************************************************/
class InsertCookieDBListener : public DBListenerErrorHandler
{
@ -412,8 +411,9 @@ public:
};
/******************************************************************************
* UpdateCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous update operations.
* UpdateCookieDBListener imp:
* Static mozIStorageStatementCallback used to track asynchronous update
* operations.
******************************************************************************/
class UpdateCookieDBListener : public DBListenerErrorHandler
{
@ -433,8 +433,9 @@ public:
};
/******************************************************************************
* RemoveCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous removal operations.
* RemoveCookieDBListener imp:
* Static mozIStorageStatementCallback used to track asynchronous removal
* operations.
******************************************************************************/
class RemoveCookieDBListener : public DBListenerErrorHandler
{
@ -453,78 +454,6 @@ public:
}
};
/******************************************************************************
* ReadCookieDBListener impl:
* mozIStorageStatementCallback used to track asynchronous removal operations.
******************************************************************************/
class ReadCookieDBListener : public DBListenerErrorHandler
{
protected:
virtual const char *GetOpType() { return "READ"; }
bool mCanceled;
public:
ReadCookieDBListener() : mCanceled(false) { }
void Cancel() { mCanceled = true; }
NS_IMETHOD HandleResult(mozIStorageResultSet *aResult)
{
nsresult rv;
nsCOMPtr<mozIStorageRow> row;
nsTArray<CookieDomainTuple> &cookieArray =
gCookieService->mDefaultDBState.hostArray;
while (1) {
rv = aResult->GetNextRow(getter_AddRefs(row));
NS_ASSERT_SUCCESS(rv);
if (!row)
break;
CookieDomainTuple *tuple = cookieArray.AppendElement();
row->GetUTF8String(9, tuple->baseDomain);
tuple->cookie = gCookieService->GetCookieFromRow(row);
}
return NS_OK;
}
NS_IMETHOD HandleCompletion(PRUint16 aReason)
{
// Process the completion of the read operation. If we have been canceled,
// we cannot assume that the cookieservice still has an open connection
// or that it even refers to the same database, so we must return early.
// Conversely, the cookieservice guarantees that if we have not been
// canceled, the database connection is still alive and we can safely
// operate on it.
if (mCanceled) {
// We may receive a REASON_FINISHED after being canceled;
// tweak the reason accordingly.
aReason = mozIStorageStatementCallback::REASON_CANCELED;
}
switch (aReason) {
case mozIStorageStatementCallback::REASON_FINISHED:
gCookieService->AsyncReadComplete();
break;
case mozIStorageStatementCallback::REASON_CANCELED:
// Nothing more to do here. The partially read data has already been
// thrown away.
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled"));
break;
case mozIStorageStatementCallback::REASON_ERROR:
// Nothing more to do here. DBListenerErrorHandler::HandleError()
// can take handle it.
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error"));
break;
default:
NS_NOTREACHED("invalid reason");
}
return NS_OK;
}
};
/******************************************************************************
* CloseCookieDBListener imp:
* Static mozIStorageCompletionCallback used to notify when the database is
@ -549,11 +478,15 @@ public:
NS_IMPL_ISUPPORTS1(CloseCookieDBListener, mozIStorageCompletionCallback)
} // anonymous namespace
/******************************************************************************
* nsCookieService impl:
* singleton instance ctor/dtor methods
******************************************************************************/
static nsCookieService *gCookieService;
nsICookieService*
nsCookieService::GetXPCOMSingleton()
{
@ -643,9 +576,6 @@ nsCookieService::Init()
PrefChanged(prefBranch);
}
mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// failure here is non-fatal (we can run fine without
// persistent storage - e.g. if there's no profile)
rv = InitDB();
@ -724,10 +654,13 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<mozIStorageService> storage = do_GetService("@mozilla.org/storage/service;1");
if (!storage)
return NS_ERROR_UNEXPECTED;
// open a connection to the cookie database, and only cache our connection
// and statements upon success. The connection is opened shared such that
// the main and background threads can operate on the db concurrently.
rv = mStorageService->OpenDatabase(cookieFile, getter_AddRefs(mDBState->dbConn));
// and statements upon success.
rv = storage->OpenUnsharedDatabase(cookieFile, getter_AddRefs(mDBState->dbConn));
NS_ENSURE_SUCCESS(rv, rv);
PRBool tableExists = PR_FALSE;
@ -968,13 +901,6 @@ nsCookieService::CloseDB()
mDefaultDBState.stmtDelete = nsnull;
mDefaultDBState.stmtUpdate = nsnull;
if (mDefaultDBState.dbConn) {
// Cancel any pending read. No further results will be received by our
// read listener.
if (mDefaultDBState.pendingRead) {
CancelAsyncRead(PR_TRUE);
mDefaultDBState.syncConn = nsnull;
}
mDefaultDBState.dbConn->AsyncClose(mCloseListener);
mDefaultDBState.dbConn = nsnull;
}
@ -1001,7 +927,9 @@ nsCookieService::Observe(nsISupports *aSubject,
if (mDBState->dbConn) {
if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
// clear the cookie file
RemoveAll();
nsresult rv = mDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
if (NS_FAILED(rv))
NS_WARNING("db delete failed");
}
// Close the DB connection before changing
@ -1254,15 +1182,16 @@ nsCookieService::RemoveAll()
if (mDBState->dbConn) {
NS_ASSERTION(mDBState == &mDefaultDBState, "not in default DB state");
// XXX Ignore corruption for now. See bug 547031.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDefaultDBState.dbConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_cookies"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = mDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_cookies"));
if (NS_FAILED(rv)) {
// Database must be corrupted, so remove it completely.
nsCOMPtr<nsIFile> dbFile;
mDBState->dbConn->GetDatabaseFile(getter_AddRefs(dbFile));
CloseDB();
dbFile->Remove(PR_FALSE);
nsCOMPtr<mozIStoragePendingStatement> handle;
rv = stmt->ExecuteAsync(mRemoveListener, getter_AddRefs(handle));
NS_ASSERT_SUCCESS(rv);
InitDB();
}
}
NotifyChanged(nsnull, NS_LITERAL_STRING("cleared").get());
@ -1303,8 +1232,6 @@ nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
nsGetEnumeratorData data(&cookieList, PR_Now() / PR_USEC_PER_SEC);
EnsureReadComplete();
mDBState->hostTable.EnumerateEntries(COMArrayCallback, &data);
return NS_NewArrayEnumerator(aEnumerator, cookieList);
@ -1402,9 +1329,28 @@ nsCookieService::Remove(const nsACString &aHost,
nsresult
nsCookieService::Read()
{
// Let the reading begin!
nsresult rv;
// delete expired cookies, before we read in the db
{
// scope the deletion, so the write lock is released when finished
nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
rv = mDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_cookies WHERE expiry <= ?1"),
getter_AddRefs(stmtDeleteExpired));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmtDeleteExpired->BindInt64ByIndex(0, PR_Now() / PR_USEC_PER_SEC);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasResult;
rv = stmtDeleteExpired->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
}
// let the reading begin!
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDefaultDBState.dbConn->CreateStatement(NS_LITERAL_CSTRING(
rv = mDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"id, "
"name, "
@ -1419,261 +1365,49 @@ nsCookieService::Read()
"FROM moz_cookies"), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<ReadCookieDBListener> readListener = new ReadCookieDBListener;
rv = stmt->ExecuteAsync(readListener,
getter_AddRefs(mDefaultDBState.pendingRead));
NS_ASSERT_SUCCESS(rv);
mDefaultDBState.readListener = readListener;
if (!mDefaultDBState.readSet.IsInitialized())
mDefaultDBState.readSet.Init();
return NS_OK;
}
// Extract data from a single result row and create an nsCookie.
// This is templated since 'T' is different for sync vs async results.
template<class T> nsCookie*
nsCookieService::GetCookieFromRow(T &aRow)
{
// Skip reading 'baseDomain' -- up to the caller.
PRInt64 creationID = aRow->AsInt64(0);
nsCString name, value, host, path;
nsresult rv = aRow->GetUTF8String(1, name);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(2, value);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(3, host);
NS_ASSERT_SUCCESS(rv);
rv = aRow->GetUTF8String(4, path);
NS_ASSERT_SUCCESS(rv);
PRInt64 expiry = aRow->AsInt64(5);
PRInt64 lastAccessed = aRow->AsInt64(6);
PRBool isSecure = 0 != aRow->AsInt32(7);
PRBool isHttpOnly = 0 != aRow->AsInt32(8);
// create a new nsCookie and assign the data.
return nsCookie::Create(name, value, host, path,
expiry,
lastAccessed,
creationID,
PR_FALSE,
isSecure,
isHttpOnly);
}
void
nsCookieService::AsyncReadComplete()
{
NS_ASSERTION(mDBState == &mDefaultDBState, "not in default db state");
NS_ASSERTION(mDBState->pendingRead, "no pending read");
NS_ASSERTION(mDBState->readListener, "no read listener");
// Merge the data read on the background thread with the data synchronously
// read on the main thread. Note that transactions on the cookie table may
// have occurred on the main thread since, making the background data stale.
for (PRUint32 i = 0; i < mDefaultDBState.hostArray.Length(); ++i) {
const CookieDomainTuple &tuple = mDefaultDBState.hostArray[i];
// Tiebreak: if the given base domain has already been read in, ignore
// the background data. Note that readSet may contain domains that were
// queried but found not to be in the db -- that's harmless.
if (mDefaultDBState.readSet.GetEntry(tuple.baseDomain))
continue;
AddCookieToList(tuple.baseDomain, tuple.cookie, NULL, PR_FALSE);
}
mDefaultDBState.stmtReadDomain = nsnull;
mDefaultDBState.pendingRead = nsnull;
mDefaultDBState.readListener = nsnull;
mDefaultDBState.syncConn = nsnull;
mDefaultDBState.hostArray.Clear();
mDefaultDBState.readSet.Clear();
mObserverService->NotifyObservers(nsnull, "cookie-db-read", nsnull);
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read",
mDefaultDBState.cookieCount));
}
void
nsCookieService::CancelAsyncRead(PRBool aPurgeReadSet)
{
NS_ASSERTION(mDBState == &mDefaultDBState, "not in default db state");
NS_ASSERTION(mDBState->pendingRead, "no pending read");
NS_ASSERTION(mDBState->readListener, "no read listener");
// Cancel the pending read, kill the read listener, and empty the array
// of data already read in on the background thread.
mDefaultDBState.readListener->Cancel();
nsresult rv = mDefaultDBState.pendingRead->Cancel();
NS_ASSERT_SUCCESS(rv);
mDefaultDBState.stmtReadDomain = nsnull;
mDefaultDBState.pendingRead = nsnull;
mDefaultDBState.readListener = nsnull;
mDefaultDBState.hostArray.Clear();
// Only clear the 'readSet' table if we no longer need to know what set of
// data is already accounted for.
if (aPurgeReadSet)
mDefaultDBState.readSet.Clear();
}
mozIStorageConnection*
nsCookieService::GetSyncDBConn()
{
NS_ASSERTION(!mDefaultDBState.syncConn, "already have sync db connection");
// Start a new connection for sync reads to reduce contention with the
// background thread.
nsCOMPtr<nsIFile> cookieFile;
mDefaultDBState.dbConn->GetDatabaseFile(getter_AddRefs(cookieFile));
NS_ASSERTION(cookieFile, "no cookie file on connection");
mStorageService->OpenDatabase(cookieFile,
getter_AddRefs(mDefaultDBState.syncConn));
NS_ASSERTION(mDefaultDBState.syncConn, "can't open sync db connection");
return mDefaultDBState.syncConn;
}
void
nsCookieService::EnsureReadDomain(const nsCString &aBaseDomain)
{
NS_ASSERTION(!mDBState->dbConn || mDBState == &mDefaultDBState,
"not in default db state");
// Fast path 1: nothing to read, or we've already finished reading.
if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState.pendingRead))
return;
// Fast path 2: already read in this particular domain.
if (NS_LIKELY(mDefaultDBState.readSet.GetEntry(aBaseDomain)))
return;
// Read in the data synchronously.
nsresult rv;
if (!mDefaultDBState.stmtReadDomain) {
if (!GetSyncDBConn())
return;
// Cache the statement, since it's likely to be used again.
rv = mDefaultDBState.syncConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"id, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"isSecure, "
"isHttpOnly "
"FROM moz_cookies "
"WHERE baseDomain = ?1"),
getter_AddRefs(mDefaultDBState.stmtReadDomain));
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
}
NS_ASSERTION(mDefaultDBState.syncConn, "should have a sync db connection");
mozStorageStatementScoper scoper(mDefaultDBState.stmtReadDomain);
rv = mDefaultDBState.stmtReadDomain->BindUTF8StringByIndex(0, aBaseDomain);
NS_ASSERT_SUCCESS(rv);
nsCAutoString baseDomain, name, value, host, path;
PRBool hasResult;
PRUint32 readCount = 0;
nsCString name, value, host, path;
while (1) {
rv = mDefaultDBState.stmtReadDomain->ExecuteStep(&hasResult);
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) {
PRInt64 creationID = stmt->AsInt64(0);
if (!hasResult)
break;
stmt->GetUTF8String(1, name);
stmt->GetUTF8String(2, value);
stmt->GetUTF8String(3, host);
stmt->GetUTF8String(4, path);
nsCookie* newCookie = GetCookieFromRow(mDefaultDBState.stmtReadDomain);
AddCookieToList(aBaseDomain, newCookie, NULL, PR_FALSE);
++readCount;
}
PRInt64 expiry = stmt->AsInt64(5);
PRInt64 lastAccessed = stmt->AsInt64(6);
PRBool isSecure = 0 != stmt->AsInt32(7);
PRBool isHttpOnly = 0 != stmt->AsInt32(8);
// Add it to the hashset of read entries, so we don't read it again.
mDefaultDBState.readSet.PutEntry(aBaseDomain);
COOKIE_LOGSTRING(PR_LOG_DEBUG,
("EnsureReadDomain(): %ld cookies read for base domain %s",
readCount, aBaseDomain.get()));
}
void
nsCookieService::EnsureReadComplete()
{
NS_ASSERTION(!mDBState->dbConn || mDBState == &mDefaultDBState,
"not in default db state");
// Fast path 1: nothing to read, or we've already finished reading.
if (NS_LIKELY(!mDBState->dbConn || !mDefaultDBState.pendingRead))
return;
// Cancel the pending read, so we don't get any more results.
CancelAsyncRead(PR_FALSE);
if (!mDefaultDBState.syncConn && !GetSyncDBConn())
return;
// Read in the data synchronously.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDefaultDBState.syncConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"id, "
"name, "
"value, "
"host, "
"path, "
"expiry, "
"lastAccessed, "
"isSecure, "
"isHttpOnly, "
"baseDomain "
"FROM moz_cookies"), getter_AddRefs(stmt));
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
nsCString baseDomain, name, value, host, path;
PRBool hasResult;
PRUint32 readCount = 0;
while (1) {
rv = stmt->ExecuteStep(&hasResult);
// XXX Ignore corruption for now. See bug 547031.
if (NS_FAILED(rv)) return;
if (!hasResult)
break;
// Make sure we haven't already read the data.
stmt->GetUTF8String(9, baseDomain);
if (mDefaultDBState.readSet.GetEntry(baseDomain))
continue;
nsCookie* newCookie = GetCookieFromRow(stmt);
AddCookieToList(baseDomain, newCookie, NULL, PR_FALSE);
++readCount;
// create a new nsCookie and assign the data.
nsCookie* newCookie =
nsCookie::Create(name, value, host, path,
expiry,
lastAccessed,
creationID,
PR_FALSE,
isSecure,
isHttpOnly);
if (!newCookie)
return NS_ERROR_OUT_OF_MEMORY;
if (!AddCookieToList(baseDomain, newCookie, NULL, PR_FALSE))
// It is purpose that created us; purpose that connects us;
// purpose that pulls us; that guides us; that drives us.
// It is purpose that defines us; purpose that binds us.
// When a cookie no longer has purpose, it has a choice:
// it can return to the source to be deleted, or it can go
// into exile, and stay hidden inside the Matrix.
// Let's choose deletion.
delete newCookie;
}
mDefaultDBState.syncConn = nsnull;
mDefaultDBState.readSet.Clear();
COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read", mDBState->cookieCount));
mObserverService->NotifyObservers(nsnull, "cookie-db-read", nsnull);
COOKIE_LOGSTRING(PR_LOG_DEBUG,
("EnsureReadComplete(): %ld cookies read", readCount));
return rv;
}
NS_IMETHODIMP
@ -1727,9 +1461,6 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile)
*
*/
// First, ensure we've read in everything from the database, if we have one.
EnsureReadComplete();
// We will likely be adding a bunch of cookies to the DB, so we use async
// batching with storage to make this super fast.
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
@ -1926,8 +1657,6 @@ nsCookieService::GetCookieInternal(nsIURI *aHostURI,
PRInt64 currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
PRBool stale = PR_FALSE;
EnsureReadDomain(baseDomain);
// perform the hash lookup
nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
if (!entry)
@ -2987,8 +2716,6 @@ nsCookieService::PurgeCookies(PRInt64 aCurrentTimeInUsec)
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
}
EnsureReadComplete();
nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
@ -3081,8 +2808,6 @@ PRUint32
nsCookieService::CountCookiesFromHostInternal(const nsCString &aBaseDomain,
nsEnumerationData &aData)
{
EnsureReadDomain(aBaseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
if (!entry)
return 0;
@ -3146,8 +2871,6 @@ nsCookieService::GetCookiesFromHost(const nsACString &aHost,
nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost);
PRInt64 currentTime = PR_Now() / PR_USEC_PER_SEC;
EnsureReadDomain(baseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(baseDomain);
if (!entry)
return NS_NewEmptyEnumerator(aEnumerator);
@ -3173,8 +2896,6 @@ nsCookieService::FindCookie(const nsCString &aBaseDomain,
nsListIter &aIter,
PRInt64 aCurrentTime)
{
EnsureReadDomain(aBaseDomain);
nsCookieEntry *entry = mDBState->hostTable.GetEntry(aBaseDomain);
if (!entry)
return PR_FALSE;
@ -3294,7 +3015,7 @@ bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
NS_ASSERT_SUCCESS(rv);
}
void
PRBool
nsCookieService::AddCookieToList(const nsCString &aBaseDomain,
nsCookie *aCookie,
mozIStorageBindingParamsArray *aParamsArray,
@ -3306,7 +3027,10 @@ nsCookieService::AddCookieToList(const nsCString &aBaseDomain,
"Do not have a DB connection but have a params array?");
nsCookieEntry *entry = mDBState->hostTable.PutEntry(aBaseDomain);
NS_ASSERTION(entry, "can't insert element into a null entry!");
if (!entry) {
NS_ERROR("can't insert element into a null entry!");
return PR_FALSE;
}
entry->GetCookies().AppendElement(aCookie);
++mDBState->cookieCount;
@ -3334,6 +3058,8 @@ nsCookieService::AddCookieToList(const nsCString &aBaseDomain,
NS_ASSERT_SUCCESS(rv);
}
}
return PR_TRUE;
}
void

View File

@ -54,9 +54,7 @@
#include "nsHashKeys.h"
#include "nsTHashtable.h"
#include "mozIStorageStatement.h"
#include "mozIStoragePendingStatement.h"
#include "mozIStorageConnection.h"
#include "mozIStorageRow.h"
class nsICookiePermission;
class nsIEffectiveTLDService;
@ -65,10 +63,9 @@ class nsIPrefBranch;
class nsIObserverService;
class nsIURI;
class nsIChannel;
class mozIStorageService;
class DBListenerErrorHandler;
class mozIStorageStatementCallback;
class mozIStorageCompletionCallback;
class ReadCookieDBListener;
struct nsCookieAttributes;
struct nsListIter;
@ -138,13 +135,6 @@ class nsCookieEntry : public PLDHashEntryHdr
ArrayType mCookies;
};
// encapsulates a (baseDomain, nsCookie) tuple for temporary storage purposes.
struct CookieDomainTuple
{
nsCString baseDomain;
nsRefPtr<nsCookie> cookie;
};
// encapsulates in-memory and on-disk DB states, so we can
// conveniently switch state when entering or exiting private browsing.
struct DBState
@ -158,22 +148,6 @@ struct DBState
nsCOMPtr<mozIStorageStatement> stmtInsert;
nsCOMPtr<mozIStorageStatement> stmtDelete;
nsCOMPtr<mozIStorageStatement> stmtUpdate;
// Various parts representing asynchronous read state. These are useful
// while the background read is taking place.
nsCOMPtr<mozIStorageConnection> syncConn;
nsCOMPtr<mozIStorageStatement> stmtReadDomain;
nsCOMPtr<mozIStoragePendingStatement> pendingRead;
// The asynchronous read listener. This is a weak ref (storage has ownership)
// since it may need to outlive the DBState's database connection.
ReadCookieDBListener* readListener;
// An array of (baseDomain, cookie) tuples representing data read in
// asynchronously. This is merged into hostTable once read is complete.
nsTArray<CookieDomainTuple> hostArray;
// A hashset of baseDomains read in synchronously, while the async read is
// in flight. This is used to keep track of which data in hostArray is stale
// when the time comes to merge.
nsTHashtable<nsCStringHashKey> readSet;
};
// these constants represent a decision about a cookie based on user prefs.
@ -219,12 +193,6 @@ class nsCookieService : public nsICookieService
nsresult CreateTable();
void CloseDB();
nsresult Read();
template<class T> nsCookie* GetCookieFromRow(T &aRow);
void AsyncReadComplete();
void CancelAsyncRead(PRBool aPurgeReadSet);
mozIStorageConnection* GetSyncDBConn();
void EnsureReadDomain(const nsCString &aBaseDomain);
void EnsureReadComplete();
nsresult NormalizeHost(nsCString &aHost);
nsresult GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, PRBool &aRequireHostMatch);
nsresult GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
@ -233,7 +201,7 @@ class nsCookieService : public nsICookieService
PRBool SetCookieInternal(nsIURI *aHostURI, const nsCString& aBaseDomain, PRBool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
void AddInternal(const nsCString& aBaseDomain, nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
void RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = NULL);
void AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, mozIStorageBindingParamsArray *aParamsArray, PRBool aWriteToDB = PR_TRUE);
PRBool AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, mozIStorageBindingParamsArray *aParamsArray, PRBool aWriteToDB = PR_TRUE);
void UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed, mozIStorageBindingParamsArray *aParamsArray);
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);
@ -256,7 +224,6 @@ class nsCookieService : public nsICookieService
nsCOMPtr<nsICookiePermission> mPermissionService;
nsCOMPtr<nsIEffectiveTLDService> mTLDService;
nsCOMPtr<nsIIDNService> mIDNService;
nsCOMPtr<mozIStorageService> mStorageService;
// we have two separate DB states: one for normal browsing and one for
// private browsing, switching between them as appropriate. this state
@ -280,9 +247,8 @@ class nsCookieService : public nsICookieService
PRUint16 mMaxCookiesPerHost;
PRInt64 mCookiePurgeAge;
// friends!
// this callback needs access to member functions
friend PLDHashOperator purgeCookiesCallback(nsCookieEntry *aEntry, void *aArg);
friend class ReadCookieDBListener;
static nsCookieService* GetSingleton();
#ifdef MOZ_IPC

View File

@ -783,7 +783,7 @@ Statement::GetUTF8String(PRUint32 aIndex,
nsresult rv = GetTypeOfIndex(aIndex, &type);
NS_ENSURE_SUCCESS(rv, rv);
if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
// NULL columns should have IsVoid set to distinguish them from the empty
// NULL columns should have IsVod set to distinguis them from an empty
// string.
_value.Truncate(0);
_value.SetIsVoid(PR_TRUE);
@ -806,7 +806,7 @@ Statement::GetString(PRUint32 aIndex,
nsresult rv = GetTypeOfIndex(aIndex, &type);
NS_ENSURE_SUCCESS(rv, rv);
if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
// NULL columns should have IsVoid set to distinguish them from the empty
// NULL columns should have IsVod set to distinguis them from an empty
// string.
_value.Truncate(0);
_value.SetIsVoid(PR_TRUE);