Bug 547031 - Improve async error handling in cookies. Part 2: Clean up TryInitDB(). r=sdwilsh, a=betaN+

This commit is contained in:
Dan Witte 2010-11-12 09:32:35 -08:00
Родитель 8a34bcf933
Коммит f1930d2c4e
4 изменённых файлов: 466 добавлений и 93 удалений

Просмотреть файл

@ -196,6 +196,47 @@ function CookieDatabaseConnection(profile, schema)
this.db.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
switch (schema) {
case 1:
{
if (!exists) {
this.db.executeSimpleSQL(
"CREATE TABLE moz_cookies ( \
id INTEGER PRIMARY KEY, \
name TEXT, \
value TEXT, \
host TEXT, \
path TEXT, \
expiry INTEGER, \
isSecure INTEGER, \
isHttpOnly INTEGER)");
}
this.stmtInsert = this.db.createStatement(
"INSERT INTO moz_cookies ( \
id, \
name, \
value, \
host, \
path, \
expiry, \
isSecure, \
isHttpOnly) \
VALUES ( \
:id, \
:name, \
:value, \
:host, \
:path, \
:expiry, \
:isSecure, \
:isHttpOnly)");
this.stmtDelete = this.db.createStatement(
"DELETE FROM moz_cookies WHERE id = :id");
break;
}
case 2:
{
if (!exists) {
@ -370,6 +411,17 @@ CookieDatabaseConnection.prototype =
switch (this.schema)
{
case 1:
this.stmtInsert.bindByName("id", cookie.creationTime);
this.stmtInsert.bindByName("name", cookie.name);
this.stmtInsert.bindByName("value", cookie.value);
this.stmtInsert.bindByName("host", cookie.host);
this.stmtInsert.bindByName("path", cookie.path);
this.stmtInsert.bindByName("expiry", cookie.expiry);
this.stmtInsert.bindByName("isSecure", cookie.isSecure);
this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
break;
case 2:
this.stmtInsert.bindByName("id", cookie.creationTime);
this.stmtInsert.bindByName("name", cookie.name);
@ -395,7 +447,7 @@ CookieDatabaseConnection.prototype =
this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
break;
case 3:
case 4:
this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
this.stmtInsert.bindByName("name", cookie.name);
this.stmtInsert.bindByName("value", cookie.value);
@ -422,6 +474,7 @@ CookieDatabaseConnection.prototype =
switch (this.db.schemaVersion)
{
case 1:
case 2:
case 3:
this.stmtDelete.bindByName("id", cookie.creationTime);
@ -447,6 +500,9 @@ CookieDatabaseConnection.prototype =
switch (this.db.schemaVersion)
{
case 1:
do_throw("can't update a schema 1 cookie!");
case 2:
case 3:
this.stmtUpdate.bindByName("id", cookie.creationTime);
@ -471,7 +527,8 @@ CookieDatabaseConnection.prototype =
{
this.stmtInsert.finalize();
this.stmtDelete.finalize();
this.stmtUpdate.finalize();
if (this.stmtUpdate)
this.stmtUpdate.finalize();
this.db.close();
this.stmtInsert = null;

Просмотреть файл

@ -0,0 +1,277 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test the various ways opening a cookie database can fail in a synchronous
// (i.e. immediate) manner, and that the database is renamed and recreated
// under each circumstance. These circumstances are, in no particular order:
//
// 1) A corrupt database, such that opening the connection fails.
// 2) The 'moz_cookies' table doesn't exist.
// 3) Not all of the expected columns exist, and statement creation fails when:
// a) The schema version is larger than the current version.
// b) The schema version is less than or equal to the current version.
// 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.
let test_generator = do_run_test();
function run_test() {
do_test_pending();
do_run_generator(test_generator);
}
function finish_test() {
do_execute_soon(function() {
test_generator.close();
do_test_finished();
});
}
function do_run_test() {
// Set up a profile.
this.profile = do_get_profile();
// Get the cookie file and the backup file.
this.cookieFile = profile.clone();
cookieFile.append("cookies.sqlite");
this.backupFile = profile.clone();
backupFile.append("cookies.sqlite.bak");
do_check_false(cookieFile.exists());
do_check_false(backupFile.exists());
// Create a cookie object for testing.
this.now = Date.now() * 1000;
this.futureExpiry = Math.round(this.now / 1e6 + 1000);
this.cookie = new Cookie("oh", "hai", "bar.com", "/", this.futureExpiry,
this.now, this.now, false, false, false);
this.sub_generator = run_test_1(test_generator);
sub_generator.next();
yield;
this.sub_generator = run_test_2(test_generator);
sub_generator.next();
yield;
this.sub_generator = run_test_3(test_generator, 99);
sub_generator.next();
yield;
this.sub_generator = run_test_3(test_generator, 4);
sub_generator.next();
yield;
this.sub_generator = run_test_3(test_generator, 3);
sub_generator.next();
yield;
this.sub_generator = run_test_4_exists(test_generator, 1,
"ALTER TABLE moz_cookies ADD lastAccessed INTEGER");
sub_generator.next();
yield;
this.sub_generator = run_test_4_exists(test_generator, 2,
"ALTER TABLE moz_cookies ADD baseDomain TEXT");
sub_generator.next();
yield;
this.sub_generator = run_test_4_baseDomain(test_generator);
sub_generator.next();
yield;
this.sub_generator = run_test_4_exists(test_generator, 3,
"ALTER TABLE moz_cookies ADD creationTime INTEGER");
sub_generator.next();
yield;
this.sub_generator = run_test_4_exists(test_generator, 3,
"CREATE UNIQUE INDEX moz_uniqueid ON moz_cookies (name, host, path)");
sub_generator.next();
yield;
finish_test();
return;
}
const garbage = "hello thar!";
function create_garbage_file(file)
{
// Create an empty database file.
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, -1);
do_check_true(file.exists());
do_check_eq(file.fileSize, 0);
// Write some garbage to it.
let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
ostream.init(file, -1, -1, 0);
ostream.write(garbage, garbage.length);
ostream.close();
file = file.clone(); // Windows maintains a stat cache. It's lame.
do_check_eq(file.fileSize, garbage.length);
}
function check_garbage_file(file)
{
do_check_true(file.exists());
do_check_eq(file.fileSize, garbage.length);
file.remove(false);
do_check_false(file.exists());
}
function run_test_1(generator)
{
// Create a garbage database file.
create_garbage_file(cookieFile);
// Load the profile and populate it.
let uri = NetUtil.newURI("http://foo.com/");
Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
// Fake a profile change.
do_close_profile(sub_generator);
yield;
do_load_profile();
// Check that the new database contains the cookie, and the old file was
// renamed.
do_check_eq(do_count_cookies(), 1);
check_garbage_file(backupFile);
// Close the profile.
do_close_profile(sub_generator);
yield;
// Clean up.
cookieFile.remove(false);
do_check_false(cookieFile.exists());
do_run_generator(generator);
}
function run_test_2(generator)
{
// Load the profile and populate it.
do_load_profile();
let uri = NetUtil.newURI("http://foo.com/");
Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
// Fake a profile change.
do_close_profile(sub_generator);
yield;
// Drop the table.
let db = Services.storage.openDatabase(cookieFile);
db.executeSimpleSQL("DROP TABLE moz_cookies");
db.close();
// Load the profile and check that the table is recreated in-place.
do_load_profile();
do_check_eq(do_count_cookies(), 0);
do_check_false(backupFile.exists());
// Close the profile.
do_close_profile(sub_generator);
yield;
// Clean up.
cookieFile.remove(false);
do_check_false(cookieFile.exists());
do_run_generator(generator);
}
function run_test_3(generator, schema)
{
// Manually create a schema 2 database, populate it, and set the schema
// version to the desired number.
let schema2db = new CookieDatabaseConnection(profile, 2);
schema2db.insertCookie(cookie);
schema2db.db.schemaVersion = schema;
schema2db.close();
// Load the profile and check that the column existence test fails.
do_load_profile();
do_check_eq(do_count_cookies(), 0);
// Close the profile.
do_close_profile(sub_generator);
yield;
// Check that the schema version has been reset.
let db = Services.storage.openDatabase(cookieFile);
do_check_eq(db.schemaVersion, 4);
db.close();
// Clean up.
cookieFile.remove(false);
do_check_false(cookieFile.exists());
do_run_generator(generator);
}
function run_test_4_exists(generator, schema, stmt)
{
// Manually create a database, populate it, and add the desired column.
let db = new CookieDatabaseConnection(profile, schema);
db.insertCookie(cookie);
db.db.executeSimpleSQL(stmt);
db.close();
// Load the profile and check that migration fails.
do_load_profile();
do_check_eq(do_count_cookies(), 0);
// Close the profile.
do_close_profile(sub_generator);
yield;
// Check that the schema version has been reset and the backup file exists.
db = Services.storage.openDatabase(cookieFile);
do_check_eq(db.schemaVersion, 4);
db.close();
do_check_true(backupFile.exists());
// Clean up.
cookieFile.remove(false);
backupFile.remove(false);
do_check_false(cookieFile.exists());
do_check_false(backupFile.exists());
do_run_generator(generator);
}
function run_test_4_baseDomain(generator)
{
// Manually create a database and populate it with a bad host.
let db = new CookieDatabaseConnection(profile, 2);
let badCookie = new Cookie("oh", "hai", ".", "/", this.futureExpiry, this.now,
this.now, false, false, false);
db.insertCookie(badCookie);
db.close();
// Load the profile and check that migration fails.
do_load_profile();
do_check_eq(do_count_cookies(), 0);
// Close the profile.
do_close_profile(sub_generator);
yield;
// Check that the schema version has been reset and the backup file exists.
db = Services.storage.openDatabase(cookieFile);
do_check_eq(db.schemaVersion, 4);
db.close();
do_check_true(backupFile.exists());
// Clean up.
cookieFile.remove(false);
backupFile.remove(false);
do_check_false(cookieFile.exists());
do_check_false(backupFile.exists());
do_run_generator(generator);
}

Просмотреть файл

@ -99,7 +99,7 @@ static nsCookieService *gCookieService;
// This is a hack to hide HttpOnly cookies from older browsers
static const char kHttpOnlyPrefix[] = "#HttpOnly_";
static const char kCookieFileName[] = "cookies.sqlite";
#define COOKIES_FILE "cookies.sqlite"
#define COOKIES_SCHEMA_VERSION 4
static const PRInt64 kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
@ -656,32 +656,15 @@ nsCookieService::Init()
void
nsCookieService::InitDBStates()
{
NS_ASSERTION(!mDBState, "already have a DB state");
NS_ASSERTION(!mDBState, "already have a DBState");
NS_ASSERTION(!mDefaultDBState, "already have a default DBState");
NS_ASSERTION(!mPrivateDBState, "already have a private DBState");
// Create a new default DBState.
// Create a new default DBState and set our current one.
mDefaultDBState = new DBState();
mDBState = mDefaultDBState;
// attempt to open and read the database
nsresult rv = TryInitDB(PR_FALSE);
if (rv == NS_ERROR_FILE_CORRUPTED) {
COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDB(): db corrupt, trying again", rv));
// Synchronously close the connection, clean up the default DBState, and try
// again.
CloseDefaultDBConnection();
rv = TryInitDB(PR_TRUE);
}
if (NS_FAILED(rv)) {
// Reset our DB connection and statements, and run without a db connection.
// This is nonfatal -- we can run fine without persistent storage, e.g. if
// there's no profile.
CloseDefaultDBConnection();
COOKIE_LOGSTRING(PR_LOG_WARNING, ("TryInitDB() gave error %x", rv));
}
// If we're in private browsing mode, switch the DBState over.
// If we're in private browsing mode, create a private DBState.
nsCOMPtr<nsIPrivateBrowsingService> pbs =
do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
if (pbs) {
@ -692,35 +675,82 @@ nsCookieService::InitDBStates()
mDBState = mPrivateDBState;
}
}
// Get our cookie file.
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mDefaultDBState->cookieFile));
if (NS_FAILED(rv)) {
// We've already set up our DBStates appropriately; nothing more to do.
COOKIE_LOGSTRING(PR_LOG_WARNING,
("InitDBStates(): couldn't get cookie file"));
return;
}
mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE));
// Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY,
// do so.
OpenDBResult result = TryInitDB(false);
if (result == RESULT_RETRY) {
// Database may be corrupt. Synchronously close the connection, clean up the
// default DBState, and try again.
COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()"));
CloseDefaultDBConnection();
result = TryInitDB(true);
if (result == RESULT_RETRY) {
// We're done. Change the code to failure so we clean up below.
result = RESULT_FAILURE;
}
}
if (result == RESULT_FAILURE) {
COOKIE_LOGSTRING(PR_LOG_WARNING,
("InitDBStates(): TryInitDB() failed, closing connection"));
// Connection failure is unrecoverable. Clean up our connection. We can run
// fine without persistent storage -- e.g. if there's no profile.
CloseDefaultDBConnection();
}
}
nsresult
nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
/* Attempt to open and read the database. If 'aRecreateDB' is true, try to
* move the existing database file out of the way and create a new one.
*
* @returns RESULT_OK if opening or creating the database succeeded;
* RESULT_RETRY if the database cannot be opened, is corrupt, or some
* other failure occurred that might be resolved by recreating the
* database; or RESULT_FAILED if there was an unrecoverable error and
* we must run without a database.
*
* If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform
* cleanup of the default DBState.
*/
OpenDBResult
nsCookieService::TryInitDB(bool aRecreateDB)
{
NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn");
NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert");
NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener");
NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn");
nsCOMPtr<nsIFile> cookieFile;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(cookieFile));
if (NS_FAILED(rv)) return rv;
cookieFile->AppendNative(NS_LITERAL_CSTRING(kCookieFileName));
// remove an existing db, if we've been told to (i.e. it's corrupt)
if (aDeleteExistingDB) {
rv = cookieFile->Remove(PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
// Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't
// want to delete it outright, since it may be useful for debugging purposes,
// so we move it out of the way.
nsresult rv;
if (aRecreateDB) {
nsCOMPtr<nsIFile> backupFile;
mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile));
rv = backupFile->MoveToNative(NULL,
NS_LITERAL_CSTRING(COOKIES_FILE ".bak"));
NS_ENSURE_SUCCESS(rv, RESULT_FAILURE);
}
// open a connection to the cookie database, and only cache our connection
// and statements upon success. The connection is opened unshared to eliminate
// cache contention between the main and background threads.
rv = mStorageService->OpenUnsharedDatabase(cookieFile,
rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
getter_AddRefs(mDefaultDBState->dbConn));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Set up our listeners.
mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState);
@ -736,27 +766,30 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
&tableExists);
if (!tableExists) {
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
} else {
// table already exists; check the schema version before reading
PRInt32 dbSchemaVersion;
rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Start a transaction for the whole migration block.
mozStorageTransaction transaction(mDefaultDBState->dbConn, PR_TRUE);
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.
// Upgrading.
// Every time you increment the database schema, you need to implement
// the upgrading code from the previous version to the new one. If migration
// fails for any reason, it's a bug -- so we return RESULT_RETRY such that
// the original database will be saved, in the hopes that we might one day
// see it and fix it.
case 1:
{
// Add the lastAccessed column to the table.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_cookies ADD lastAccessed INTEGER"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
@ -765,7 +798,7 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
// Add the baseDomain column and index to the table.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_cookies ADD baseDomain TEXT"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Compute the baseDomains for the table. This must be done eagerly
// otherwise we won't be able to synchronously read in individual
@ -773,19 +806,19 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
nsCOMPtr<mozIStorageStatement> select;
rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id, host FROM moz_cookies"), getter_AddRefs(select));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCOMPtr<mozIStorageStatement> update;
rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"),
getter_AddRefs(update));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCString baseDomain, host;
PRBool hasResult;
while (1) {
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (!hasResult)
break;
@ -794,8 +827,7 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
select->GetUTF8String(1, host);
rv = GetBaseDomainFromHost(host, baseDomain);
if (NS_FAILED(rv))
continue;
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
mozStorageStatementScoper scoper(update);
@ -807,13 +839,13 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
NS_ASSERT_SUCCESS(rv);
rv = update->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Create an index on baseDomain.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
@ -835,18 +867,18 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
"SELECT id, name, host, path FROM moz_cookies "
"ORDER BY name ASC, host ASC, path ASC, expiry ASC"),
getter_AddRefs(select));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
nsCOMPtr<mozIStorageStatement> deleteExpired;
rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_cookies WHERE id = :id"),
getter_AddRefs(deleteExpired));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Read the first row.
PRBool hasResult;
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (hasResult) {
nsCString name1, host1, path1;
@ -859,7 +891,7 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
while (1) {
// Read the second row.
rv = select->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
if (!hasResult)
break;
@ -879,7 +911,7 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
NS_ASSERT_SUCCESS(rv);
rv = deleteExpired->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Make the second row the first for the next iteration.
@ -893,25 +925,25 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
// Add the creationTime column to the table.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_cookies ADD creationTime INTEGER"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Copy the id of each row into the new creationTime column.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_cookies SET creationTime = "
"(SELECT id WHERE id = moz_cookies.id)"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Create a unique index on (name, host, path) to allow fast lookup.
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE UNIQUE INDEX moz_uniqueid "
"ON moz_cookies (name, host, path)"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// Fall through to the next upgrade.
// No more upgrades. Update the schema version.
rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
case COOKIES_SCHEMA_VERSION:
break;
@ -926,7 +958,7 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
// re-set the schema version in the db, in case the checks succeed (if
// they don't, we're dropping the table anyway).
rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
// fall through to downgrade check
@ -960,10 +992,10 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
// our columns aren't there - drop the table!
rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE moz_cookies"));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
}
break;
}
@ -1006,23 +1038,23 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
":isHttpOnly"
")"),
getter_AddRefs(mDefaultDBState->stmtInsert));
NS_ENSURE_SUCCESS(rv, rv);
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));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"UPDATE moz_cookies SET lastAccessed = :lastAccessed "
"WHERE name = :name AND host = :host AND path = :path"),
getter_AddRefs(mDefaultDBState->stmtUpdate));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// if we deleted a corrupt db, don't attempt to import - return now
if (aDeleteExistingDB)
return NS_OK;
if (aRecreateDB)
return RESULT_OK;
// check whether to import or just read in the db
if (tableExists)
@ -1031,20 +1063,19 @@ nsCookieService::TryInitDB(PRBool aDeleteExistingDB)
nsCOMPtr<nsIFile> oldCookieFile;
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(oldCookieFile));
if (NS_FAILED(rv)) return rv;
if (NS_FAILED(rv)) return RESULT_OK;
// Import cookies, and clean up the old file regardless of success or failure.
// Note that we have to switch out our DBState temporarily, in case we're in
// private browsing mode; otherwise ImportCookies() won't be happy.
DBState* initialState = mDBState;
mDBState = mDefaultDBState;
oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName));
rv = ImportCookies(oldCookieFile);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_FILE_NOT_FOUND)
return NS_OK;
return rv;
}
// we're done importing - delete the old cookie file
ImportCookies(oldCookieFile);
oldCookieFile->Remove(PR_FALSE);
return NS_OK;
mDBState = initialState;
return RESULT_OK;
}
// Sets the schema version and creates the moz_cookies table.
@ -1553,7 +1584,8 @@ nsCookieService::Remove(const nsACString &aHost,
* private file I/O functions
******************************************************************************/
nsresult
// Begin an asynchronous read from the database.
OpenDBResult
nsCookieService::Read()
{
// Set up a statement for the read. Note that our query specifies that
@ -1573,7 +1605,7 @@ nsCookieService::Read()
"baseDomain "
"FROM moz_cookies "
"WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Set up a statement to delete any rows with a NULL 'baseDomain'
// column. This takes care of any cookies set by browsers that don't
@ -1583,7 +1615,7 @@ nsCookieService::Read()
rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_cookies WHERE baseDomain ISNULL"),
getter_AddRefs(stmtDeleteNull));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
// Init our readSet hash and execute the statements. Note that, after this
// point, we cannot fail without altering the cleanup code in InitDBStates()
@ -1600,7 +1632,7 @@ nsCookieService::Read()
getter_AddRefs(handle));
NS_ASSERT_SUCCESS(rv);
return NS_OK;
return RESULT_OK;
}
// Extract data from a single result row and create an nsCookie.
@ -1705,14 +1737,11 @@ mozIStorageConnection*
nsCookieService::GetSyncDBConn()
{
NS_ASSERTION(!mDefaultDBState->syncConn, "already have sync db connection");
NS_ASSERTION(mDefaultDBState->cookieFile, "no cookie file");
// 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->OpenUnsharedDatabase(cookieFile,
mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile,
getter_AddRefs(mDefaultDBState->syncConn));
if (!mDefaultDBState->syncConn) {
NS_ERROR("can't open sync db connection");
@ -3571,3 +3600,4 @@ nsCookieService::UpdateCookieInList(nsCookie *aCookie,
NS_ASSERT_SUCCESS(rv);
}
}

Просмотреть файл

@ -161,6 +161,7 @@ struct DBState
nsTHashtable<nsCookieEntry> hostTable;
PRUint32 cookieCount;
PRInt64 cookieOldestTime;
nsCOMPtr<nsIFile> cookieFile;
nsCOMPtr<mozIStorageConnection> dbConn;
nsCOMPtr<mozIStorageAsyncStatement> stmtInsert;
nsCOMPtr<mozIStorageAsyncStatement> stmtDelete;
@ -202,6 +203,14 @@ enum CookieStatus
STATUS_REJECTED_WITH_ERROR
};
// Result codes for TryInitDB() and Read().
enum OpenDBResult
{
RESULT_OK,
RESULT_RETRY,
RESULT_FAILURE
};
/******************************************************************************
* nsCookieService:
* class declaration
@ -228,11 +237,11 @@ class nsCookieService : public nsICookieService
protected:
void PrefChanged(nsIPrefBranch *aPrefBranch);
void InitDBStates();
nsresult TryInitDB(PRBool aDeleteExistingDB);
OpenDBResult TryInitDB(bool aDeleteExistingDB);
nsresult CreateTable();
void CloseDBStates();
void CloseDefaultDBConnection();
nsresult Read();
OpenDBResult Read();
template<class T> nsCookie* GetCookieFromRow(T &aRow);
void AsyncReadComplete();
void CancelAsyncRead(PRBool aPurgeReadSet);