diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-22 22:02:38 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-22 22:02:38 +0000 |
commit | 08b1f75f74bdf7769e3a8ac2d587c9729353c1d8 (patch) | |
tree | 7c05052cb83a88ad8c4a35c3ccf94d6dcf5e2b96 /webkit/browser/database | |
parent | 910b52bcf700ef412da46a15e85ea9a80b2b3f75 (diff) | |
download | chromium_src-08b1f75f74bdf7769e3a8ac2d587c9729353c1d8.zip chromium_src-08b1f75f74bdf7769e3a8ac2d587c9729353c1d8.tar.gz chromium_src-08b1f75f74bdf7769e3a8ac2d587c9729353c1d8.tar.bz2 |
Move webkit/database to webkit/browser and webkit/common. Also move content::WebDatabaseObserverImpl to the new common_child library.
- just moving the files in the source repository
- and fixing up complilation guards
- and fixing up include paths throughout
- and fixing up DEPs files as needed
Note: Everything in /webkit is still being built into the same old webkit_storage target
for now, new actual build target(s) will come later.
BUG=239109
R=jamesr@chromium.org, kinuko@chromium.org, piman@chromium.org, thestig@chromium.org
Review URL: https://codereview.chromium.org/15367010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@201619 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/browser/database')
-rw-r--r-- | webkit/browser/database/database_quota_client.cc | 224 | ||||
-rw-r--r-- | webkit/browser/database/database_quota_client.h | 55 | ||||
-rw-r--r-- | webkit/browser/database/database_quota_client_unittest.cc | 292 | ||||
-rw-r--r-- | webkit/browser/database/database_tracker.cc | 878 | ||||
-rw-r--r-- | webkit/browser/database/database_tracker.h | 312 | ||||
-rw-r--r-- | webkit/browser/database/database_tracker_unittest.cc | 855 | ||||
-rw-r--r-- | webkit/browser/database/database_util.cc | 85 | ||||
-rw-r--r-- | webkit/browser/database/database_util.h | 38 | ||||
-rw-r--r-- | webkit/browser/database/database_util_unittest.cc | 74 | ||||
-rw-r--r-- | webkit/browser/database/databases_table.cc | 148 | ||||
-rw-r--r-- | webkit/browser/database/databases_table.h | 53 | ||||
-rw-r--r-- | webkit/browser/database/databases_table_unittest.cc | 156 | ||||
-rw-r--r-- | webkit/browser/database/vfs_backend.cc | 167 | ||||
-rw-r--r-- | webkit/browser/database/vfs_backend.h | 44 | ||||
-rw-r--r-- | webkit/browser/database/webkit_browser_database.gypi | 20 |
15 files changed, 3401 insertions, 0 deletions
diff --git a/webkit/browser/database/database_quota_client.cc b/webkit/browser/database/database_quota_client.cc new file mode 100644 index 0000000..a1ea780 --- /dev/null +++ b/webkit/browser/database/database_quota_client.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/database/database_quota_client.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop_proxy.h" +#include "base/task_runner_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "webkit/base/origin_url_conversions.h" +#include "webkit/browser/database/database_tracker.h" +#include "webkit/browser/database/database_util.h" + +using quota::QuotaClient; + +namespace webkit_database { + +namespace { + +int64 GetOriginUsageOnDBThread( + DatabaseTracker* db_tracker, + const GURL& origin_url) { + OriginInfo info; + if (db_tracker->GetOriginInfo( + webkit_base::GetOriginIdentifierFromURL(origin_url), &info)) + return info.TotalSize(); + return 0; +} + +void GetOriginsOnDBThread( + DatabaseTracker* db_tracker, + std::set<GURL>* origins_ptr) { + std::vector<base::string16> origin_identifiers; + if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) { + for (std::vector<base::string16>::const_iterator iter = + origin_identifiers.begin(); + iter != origin_identifiers.end(); ++iter) { + GURL origin = webkit_base::GetOriginURLFromIdentifier(*iter); + origins_ptr->insert(origin); + } + } +} + +void GetOriginsForHostOnDBThread( + DatabaseTracker* db_tracker, + std::set<GURL>* origins_ptr, + const std::string& host) { + std::vector<base::string16> origin_identifiers; + if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) { + for (std::vector<base::string16>::const_iterator iter = + origin_identifiers.begin(); + iter != origin_identifiers.end(); ++iter) { + GURL origin = webkit_base::GetOriginURLFromIdentifier(*iter); + if (host == net::GetHostOrSpecFromURL(origin)) + origins_ptr->insert(origin); + } + } +} + +void DidGetOrigins( + const QuotaClient::GetOriginsCallback& callback, + std::set<GURL>* origins_ptr, + quota::StorageType type) { + callback.Run(*origins_ptr, type); +} + +void DidDeleteOriginData( + base::SingleThreadTaskRunner* original_task_runner, + const QuotaClient::DeletionCallback& callback, + int result) { + if (result == net::ERR_IO_PENDING) { + // The callback will be invoked via + // DatabaseTracker::ScheduleDatabasesForDeletion. + return; + } + + quota::QuotaStatusCode status; + if (result == net::OK) + status = quota::kQuotaStatusOk; + else + status = quota::kQuotaStatusUnknown; + + if (original_task_runner->BelongsToCurrentThread()) + callback.Run(status); + else + original_task_runner->PostTask(FROM_HERE, base::Bind(callback, status)); +} + +} // namespace + +DatabaseQuotaClient::DatabaseQuotaClient( + base::MessageLoopProxy* db_tracker_thread, + DatabaseTracker* db_tracker) + : db_tracker_thread_(db_tracker_thread), db_tracker_(db_tracker) { +} + +DatabaseQuotaClient::~DatabaseQuotaClient() { + if (db_tracker_thread_ && + !db_tracker_thread_->RunsTasksOnCurrentThread() && + db_tracker_) { + DatabaseTracker* tracker = db_tracker_; + tracker->AddRef(); + db_tracker_ = NULL; + if (!db_tracker_thread_->ReleaseSoon(FROM_HERE, tracker)) + tracker->Release(); + } +} + +QuotaClient::ID DatabaseQuotaClient::id() const { + return kDatabase; +} + +void DatabaseQuotaClient::OnQuotaManagerDestroyed() { + delete this; +} + +void DatabaseQuotaClient::GetOriginUsage( + const GURL& origin_url, + quota::StorageType type, + const GetUsageCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != quota::kStorageTypeTemporary) { + callback.Run(0); + return; + } + + base::PostTaskAndReplyWithResult( + db_tracker_thread_, + FROM_HERE, + base::Bind(&GetOriginUsageOnDBThread, + db_tracker_, + origin_url), + callback); +} + +void DatabaseQuotaClient::GetOriginsForType( + quota::StorageType type, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != quota::kStorageTypeTemporary) { + callback.Run(std::set<GURL>(), type); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + db_tracker_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsOnDBThread, + db_tracker_, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr), + type)); +} + +void DatabaseQuotaClient::GetOriginsForHost( + quota::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != quota::kStorageTypeTemporary) { + callback.Run(std::set<GURL>(), type); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + db_tracker_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForHostOnDBThread, + db_tracker_, + base::Unretained(origins_ptr), + host), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr), + type)); +} + +void DatabaseQuotaClient::DeleteOriginData( + const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now, so nothing to delete. + if (type != quota::kStorageTypeTemporary) { + callback.Run(quota::kQuotaStatusOk); + return; + } + + base::Callback<void(int)> delete_callback = + base::Bind(&DidDeleteOriginData, + base::MessageLoopProxy::current(), + callback); + + PostTaskAndReplyWithResult( + db_tracker_thread_, + FROM_HERE, + base::Bind(&DatabaseTracker::DeleteDataForOrigin, + db_tracker_, + webkit_base::GetOriginIdentifierFromURL(origin), + delete_callback), + delete_callback); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/database_quota_client.h b/webkit/browser/database/database_quota_client.h new file mode 100644 index 0000000..c90c0d5b --- /dev/null +++ b/webkit/browser/database/database_quota_client.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ +#define WEBKIT_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ + +#include <set> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/message_loop_proxy.h" +#include "webkit/quota/quota_client.h" +#include "webkit/quota/quota_types.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace webkit_database { + +class DatabaseTracker; + +// A QuotaClient implementation to integrate WebSQLDatabases +// with the quota management system. This interface is used +// on the IO thread by the quota manager. +class WEBKIT_STORAGE_EXPORT_PRIVATE DatabaseQuotaClient + : public quota::QuotaClient { + public: + DatabaseQuotaClient( + base::MessageLoopProxy* tracker_thread, + DatabaseTracker* tracker); + virtual ~DatabaseQuotaClient(); + + // QuotaClient method overrides + virtual ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin_url, + quota::StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType(quota::StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost(quota::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData(const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) OVERRIDE; + private: + scoped_refptr<base::MessageLoopProxy> db_tracker_thread_; + scoped_refptr<DatabaseTracker> db_tracker_; // only used on its thread + + DISALLOW_COPY_AND_ASSIGN(DatabaseQuotaClient); +}; + +} // namespace webkit_database + +#endif // WEBKIT_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ diff --git a/webkit/browser/database/database_quota_client_unittest.cc b/webkit/browser/database/database_quota_client_unittest.cc new file mode 100644 index 0000000..8e508ac --- /dev/null +++ b/webkit/browser/database/database_quota_client_unittest.cc @@ -0,0 +1,292 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/utf_string_conversions.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/base/origin_url_conversions.h" +#include "webkit/browser/database/database_quota_client.h" +#include "webkit/browser/database/database_tracker.h" +#include "webkit/browser/database/database_util.h" + +namespace webkit_database { + +// Declared to shorten the line lengths. +static const quota::StorageType kTemp = quota::kStorageTypeTemporary; +static const quota::StorageType kPerm = quota::kStorageTypePersistent; + +// Mock tracker class the mocks up those methods of the tracker +// that are used by the QuotaClient. +class MockDatabaseTracker : public DatabaseTracker { + public: + MockDatabaseTracker() + : DatabaseTracker(base::FilePath(), false, NULL, NULL, NULL), + delete_called_count_(0), + async_delete_(false) {} + + virtual bool GetOriginInfo( + const base::string16& origin_identifier, + OriginInfo* info) OVERRIDE { + std::map<GURL, MockOriginInfo>::const_iterator found = + mock_origin_infos_.find( + webkit_base::GetOriginURLFromIdentifier(origin_identifier)); + if (found == mock_origin_infos_.end()) + return false; + *info = OriginInfo(found->second); + return true; + } + + virtual bool GetAllOriginIdentifiers( + std::vector<base::string16>* origins_identifiers) OVERRIDE { + std::map<GURL, MockOriginInfo>::const_iterator iter; + for (iter = mock_origin_infos_.begin(); + iter != mock_origin_infos_.end(); + ++iter) { + origins_identifiers->push_back(iter->second.GetOrigin()); + } + return true; + } + + virtual bool GetAllOriginsInfo( + std::vector<OriginInfo>* origins_info) OVERRIDE { + std::map<GURL, MockOriginInfo>::const_iterator iter; + for (iter = mock_origin_infos_.begin(); + iter != mock_origin_infos_.end(); + ++iter) { + origins_info->push_back(OriginInfo(iter->second)); + } + return true; + } + + virtual int DeleteDataForOrigin( + const base::string16& origin_id, + const net::CompletionCallback& callback) OVERRIDE { + ++delete_called_count_; + if (async_delete()) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockDatabaseTracker::AsyncDeleteDataForOrigin, this, + callback)); + return net::ERR_IO_PENDING; + } + return net::OK; + } + + void AsyncDeleteDataForOrigin(const net::CompletionCallback& callback) { + callback.Run(net::OK); + } + + void AddMockDatabase(const GURL& origin, const char* name, int size) { + MockOriginInfo& info = mock_origin_infos_[origin]; + info.set_origin(webkit_base::GetOriginIdentifierFromURL(origin)); + info.AddMockDatabase(ASCIIToUTF16(name), size); + } + + int delete_called_count() { return delete_called_count_; } + bool async_delete() { return async_delete_; } + void set_async_delete(bool async) { async_delete_ = async; } + + protected: + virtual ~MockDatabaseTracker() {} + + private: + class MockOriginInfo : public OriginInfo { + public: + void set_origin(const base::string16& origin_id) { + origin_ = origin_id; + } + + void AddMockDatabase(const base::string16& name, int size) { + EXPECT_TRUE(database_info_.find(name) == database_info_.end()); + database_info_[name].first = size; + total_size_ += size; + } + }; + + int delete_called_count_; + bool async_delete_; + std::map<GURL, MockOriginInfo> mock_origin_infos_; +}; + + +// Base class for our test fixtures. +class DatabaseQuotaClientTest : public testing::Test { + public: + const GURL kOriginA; + const GURL kOriginB; + const GURL kOriginOther; + + DatabaseQuotaClientTest() + : kOriginA("http://host"), + kOriginB("http://host:8000"), + kOriginOther("http://other"), + usage_(0), + mock_tracker_(new MockDatabaseTracker), + weak_factory_(this) { + } + + int64 GetOriginUsage( + quota::QuotaClient* client, + const GURL& origin, + quota::StorageType type) { + usage_ = 0; + client->GetOriginUsage( + origin, type, + base::Bind(&DatabaseQuotaClientTest::OnGetOriginUsageComplete, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return usage_; + } + + const std::set<GURL>& GetOriginsForType( + quota::QuotaClient* client, + quota::StorageType type) { + origins_.clear(); + client->GetOriginsForType( + type, + base::Bind(&DatabaseQuotaClientTest::OnGetOriginsComplete, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return origins_; + } + + const std::set<GURL>& GetOriginsForHost( + quota::QuotaClient* client, + quota::StorageType type, + const std::string& host) { + origins_.clear(); + client->GetOriginsForHost( + type, host, + base::Bind(&DatabaseQuotaClientTest::OnGetOriginsComplete, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return origins_; + } + + bool DeleteOriginData( + quota::QuotaClient* client, + quota::StorageType type, + const GURL& origin) { + delete_status_ = quota::kQuotaStatusUnknown; + client->DeleteOriginData( + origin, type, + base::Bind(&DatabaseQuotaClientTest::OnDeleteOriginDataComplete, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return delete_status_ == quota::kQuotaStatusOk; + } + + MockDatabaseTracker* mock_tracker() { return mock_tracker_.get(); } + + + private: + void OnGetOriginUsageComplete(int64 usage) { + usage_ = usage; + } + + void OnGetOriginsComplete(const std::set<GURL>& origins, + quota::StorageType type) { + origins_ = origins; + type_ = type; + } + + void OnDeleteOriginDataComplete(quota::QuotaStatusCode status) { + delete_status_ = status; + } + + base::MessageLoop message_loop_; + int64 usage_; + std::set<GURL> origins_; + quota::StorageType type_; + quota::QuotaStatusCode delete_status_; + scoped_refptr<MockDatabaseTracker> mock_tracker_; + base::WeakPtrFactory<DatabaseQuotaClientTest> weak_factory_; +}; + + +TEST_F(DatabaseQuotaClientTest, GetOriginUsage) { + DatabaseQuotaClient client( + base::MessageLoopProxy::current(), + mock_tracker()); + + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kTemp)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm)); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm)); + + EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kTemp)); +} + +TEST_F(DatabaseQuotaClientTest, GetOriginsForHost) { + DatabaseQuotaClient client( + base::MessageLoopProxy::current(), + mock_tracker()); + + EXPECT_EQ(kOriginA.host(), kOriginB.host()); + EXPECT_NE(kOriginA.host(), kOriginOther.host()); + + std::set<GURL> origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_TRUE(origins.empty()); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_EQ(origins.size(), 1ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + + mock_tracker()->AddMockDatabase(kOriginB, "barDB", 1000); + origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_EQ(origins.size(), 2ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + EXPECT_TRUE(origins.find(kOriginB) != origins.end()); + + EXPECT_TRUE(GetOriginsForHost(&client, kPerm, kOriginA.host()).empty()); + EXPECT_TRUE(GetOriginsForHost(&client, kTemp, kOriginOther.host()).empty()); +} + +TEST_F(DatabaseQuotaClientTest, GetOriginsForType) { + DatabaseQuotaClient client( + base::MessageLoopProxy::current(), + mock_tracker()); + + EXPECT_TRUE(GetOriginsForType(&client, kTemp).empty()); + EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty()); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + std::set<GURL> origins = GetOriginsForType(&client, kTemp); + EXPECT_EQ(origins.size(), 1ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + + EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty()); +} + +TEST_F(DatabaseQuotaClientTest, DeleteOriginData) { + DatabaseQuotaClient client( + base::MessageLoopProxy::current(), + mock_tracker()); + + // Perm deletions are short circuited in the Client and + // should not reach the DatabaseTracker. + EXPECT_TRUE(DeleteOriginData(&client, kPerm, kOriginA)); + EXPECT_EQ(0, mock_tracker()->delete_called_count()); + + mock_tracker()->set_async_delete(false); + EXPECT_TRUE(DeleteOriginData(&client, kTemp, kOriginA)); + EXPECT_EQ(1, mock_tracker()->delete_called_count()); + + mock_tracker()->set_async_delete(true); + EXPECT_TRUE(DeleteOriginData(&client, kTemp, kOriginA)); + EXPECT_EQ(2, mock_tracker()->delete_called_count()); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/database_tracker.cc b/webkit/browser/database/database_tracker.cc new file mode 100644 index 0000000..effe322 --- /dev/null +++ b/webkit/browser/database/database_tracker.cc @@ -0,0 +1,878 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/database/database_tracker.h" + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/transaction.h" +#include "third_party/sqlite/sqlite3.h" +#include "webkit/base/origin_url_conversions.h" +#include "webkit/browser/database/database_quota_client.h" +#include "webkit/browser/database/database_util.h" +#include "webkit/browser/database/databases_table.h" +#include "webkit/quota/quota_manager.h" +#include "webkit/quota/special_storage_policy.h" + +namespace webkit_database { + +const base::FilePath::CharType kDatabaseDirectoryName[] = + FILE_PATH_LITERAL("databases"); +const base::FilePath::CharType kIncognitoDatabaseDirectoryName[] = + FILE_PATH_LITERAL("databases-incognito"); +const base::FilePath::CharType kTrackerDatabaseFileName[] = + FILE_PATH_LITERAL("Databases.db"); +static const int kCurrentVersion = 2; +static const int kCompatibleVersion = 1; + +const base::FilePath::CharType kTemporaryDirectoryPrefix[] = + FILE_PATH_LITERAL("DeleteMe"); +const base::FilePath::CharType kTemporaryDirectoryPattern[] = + FILE_PATH_LITERAL("DeleteMe*"); + +OriginInfo::OriginInfo() + : total_size_(0) {} + +OriginInfo::OriginInfo(const OriginInfo& origin_info) + : origin_(origin_info.origin_), + total_size_(origin_info.total_size_), + database_info_(origin_info.database_info_) {} + +OriginInfo::~OriginInfo() {} + +void OriginInfo::GetAllDatabaseNames( + std::vector<base::string16>* databases) const { + for (DatabaseInfoMap::const_iterator it = database_info_.begin(); + it != database_info_.end(); it++) { + databases->push_back(it->first); + } +} + +int64 OriginInfo::GetDatabaseSize(const base::string16& database_name) const { + DatabaseInfoMap::const_iterator it = database_info_.find(database_name); + if (it != database_info_.end()) + return it->second.first; + return 0; +} + +base::string16 OriginInfo::GetDatabaseDescription( + const base::string16& database_name) const { + DatabaseInfoMap::const_iterator it = database_info_.find(database_name); + if (it != database_info_.end()) + return it->second.second; + return base::string16(); +} + +OriginInfo::OriginInfo(const base::string16& origin, int64 total_size) + : origin_(origin), total_size_(total_size) {} + +DatabaseTracker::DatabaseTracker( + const base::FilePath& profile_path, + bool is_incognito, + quota::SpecialStoragePolicy* special_storage_policy, + quota::QuotaManagerProxy* quota_manager_proxy, + base::MessageLoopProxy* db_tracker_thread) + : is_initialized_(false), + is_incognito_(is_incognito), + force_keep_session_state_(false), + shutting_down_(false), + profile_path_(profile_path), + db_dir_(is_incognito_ ? + profile_path_.Append(kIncognitoDatabaseDirectoryName) : + profile_path_.Append(kDatabaseDirectoryName)), + db_(new sql::Connection()), + databases_table_(NULL), + meta_table_(NULL), + special_storage_policy_(special_storage_policy), + quota_manager_proxy_(quota_manager_proxy), + db_tracker_thread_(db_tracker_thread), + incognito_origin_directories_generator_(0) { + if (quota_manager_proxy) { + quota_manager_proxy->RegisterClient( + new DatabaseQuotaClient(db_tracker_thread, this)); + } +} + +DatabaseTracker::~DatabaseTracker() { + DCHECK(dbs_to_be_deleted_.empty()); + DCHECK(deletion_callbacks_.empty()); +} + +void DatabaseTracker::DatabaseOpened(const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16& database_description, + int64 estimated_size, + int64* database_size) { + if (shutting_down_ || !LazyInit()) { + *database_size = 0; + return; + } + + if (quota_manager_proxy_) + quota_manager_proxy_->NotifyStorageAccessed( + quota::QuotaClient::kDatabase, + webkit_base::GetOriginURLFromIdentifier(origin_identifier), + quota::kStorageTypeTemporary); + + InsertOrUpdateDatabaseDetails(origin_identifier, database_name, + database_description, estimated_size); + if (database_connections_.AddConnection(origin_identifier, database_name)) { + *database_size = SeedOpenDatabaseInfo(origin_identifier, + database_name, + database_description); + return; + } + *database_size = UpdateOpenDatabaseInfoAndNotify(origin_identifier, + database_name, + &database_description); +} + +void DatabaseTracker::DatabaseModified(const base::string16& origin_identifier, + const base::string16& database_name) { + if (!LazyInit()) + return; + UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name); +} + +void DatabaseTracker::DatabaseClosed(const base::string16& origin_identifier, + const base::string16& database_name) { + if (database_connections_.IsEmpty()) { + DCHECK(!is_initialized_); + return; + } + + // We call NotifiyStorageAccessed when a db is opened and also when + // closed because we don't call it for read while open. + if (quota_manager_proxy_) + quota_manager_proxy_->NotifyStorageAccessed( + quota::QuotaClient::kDatabase, + webkit_base::GetOriginURLFromIdentifier(origin_identifier), + quota::kStorageTypeTemporary); + + UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name); + if (database_connections_.RemoveConnection(origin_identifier, database_name)) + DeleteDatabaseIfNeeded(origin_identifier, database_name); +} + +void DatabaseTracker::HandleSqliteError( + const base::string16& origin_identifier, + const base::string16& database_name, + int error) { + // We only handle errors that indicate corruption and we + // do so with a heavy hand, we delete it. Any renderers/workers + // with this database open will receive a message to close it + // immediately, once all have closed, the files will be deleted. + // In the interim, all attempts to open a new connection to that + // database will fail. + // Note: the client-side filters out all but these two errors as + // a small optimization, see WebDatabaseObserverImpl::HandleSqliteError. + if (error == SQLITE_CORRUPT || error == SQLITE_NOTADB) { + DeleteDatabase(origin_identifier, database_name, + net::CompletionCallback()); + } +} + +void DatabaseTracker::CloseDatabases(const DatabaseConnections& connections) { + if (database_connections_.IsEmpty()) { + DCHECK(!is_initialized_ || connections.IsEmpty()); + return; + } + + // When being closed by this route, there's a chance that + // the tracker missed some DatabseModified calls. This method is used + // when a renderer crashes to cleanup its open resources. + // We need to examine what we have in connections for the + // size of each open databases and notify any differences between the + // actual file sizes now. + std::vector<std::pair<base::string16, base::string16> > open_dbs; + connections.ListConnections(&open_dbs); + for (std::vector<std::pair<base::string16, base::string16> >::iterator it = + open_dbs.begin(); it != open_dbs.end(); ++it) + UpdateOpenDatabaseSizeAndNotify(it->first, it->second); + + std::vector<std::pair<base::string16, base::string16> > closed_dbs; + database_connections_.RemoveConnections(connections, &closed_dbs); + for (std::vector<std::pair<base::string16, base::string16> >::iterator it = + closed_dbs.begin(); it != closed_dbs.end(); ++it) { + DeleteDatabaseIfNeeded(it->first, it->second); + } +} + +void DatabaseTracker::DeleteDatabaseIfNeeded( + const base::string16& origin_identifier, + const base::string16& database_name) { + DCHECK(!database_connections_.IsDatabaseOpened(origin_identifier, + database_name)); + if (IsDatabaseScheduledForDeletion(origin_identifier, database_name)) { + DeleteClosedDatabase(origin_identifier, database_name); + dbs_to_be_deleted_[origin_identifier].erase(database_name); + if (dbs_to_be_deleted_[origin_identifier].empty()) + dbs_to_be_deleted_.erase(origin_identifier); + + PendingDeletionCallbacks::iterator callback = deletion_callbacks_.begin(); + while (callback != deletion_callbacks_.end()) { + DatabaseSet::iterator found_origin = + callback->second.find(origin_identifier); + if (found_origin != callback->second.end()) { + std::set<base::string16>& databases = found_origin->second; + databases.erase(database_name); + if (databases.empty()) { + callback->second.erase(found_origin); + if (callback->second.empty()) { + net::CompletionCallback cb = callback->first; + cb.Run(net::OK); + callback = deletion_callbacks_.erase(callback); + continue; + } + } + } + + ++callback; + } + } +} + +void DatabaseTracker::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void DatabaseTracker::RemoveObserver(Observer* observer) { + // When we remove a listener, we do not know which cached information + // is still needed and which information can be discarded. So we just + // clear all caches and re-populate them as needed. + observers_.RemoveObserver(observer); + ClearAllCachedOriginInfo(); +} + +void DatabaseTracker::CloseTrackerDatabaseAndClearCaches() { + ClearAllCachedOriginInfo(); + + if (!is_incognito_) { + meta_table_.reset(NULL); + databases_table_.reset(NULL); + db_->Close(); + is_initialized_ = false; + } +} + +base::string16 DatabaseTracker::GetOriginDirectory( + const base::string16& origin_identifier) { + if (!is_incognito_) + return origin_identifier; + + OriginDirectoriesMap::const_iterator it = + incognito_origin_directories_.find(origin_identifier); + if (it != incognito_origin_directories_.end()) + return it->second; + + base::string16 origin_directory = + base::IntToString16(incognito_origin_directories_generator_++); + incognito_origin_directories_[origin_identifier] = origin_directory; + return origin_directory; +} + +base::FilePath DatabaseTracker::GetFullDBFilePath( + const base::string16& origin_identifier, + const base::string16& database_name) { + DCHECK(!origin_identifier.empty()); + if (!LazyInit()) + return base::FilePath(); + + int64 id = databases_table_->GetDatabaseID( + origin_identifier, database_name); + if (id < 0) + return base::FilePath(); + + base::FilePath file_name = base::FilePath::FromWStringHack( + UTF8ToWide(base::Int64ToString(id))); + return db_dir_.Append(base::FilePath::FromWStringHack( + UTF16ToWide(GetOriginDirectory(origin_identifier)))).Append(file_name); +} + +bool DatabaseTracker::GetOriginInfo(const base::string16& origin_identifier, + OriginInfo* info) { + DCHECK(info); + CachedOriginInfo* cached_info = GetCachedOriginInfo(origin_identifier); + if (!cached_info) + return false; + *info = OriginInfo(*cached_info); + return true; +} + +bool DatabaseTracker::GetAllOriginIdentifiers( + std::vector<base::string16>* origin_identifiers) { + DCHECK(origin_identifiers); + DCHECK(origin_identifiers->empty()); + if (!LazyInit()) + return false; + return databases_table_->GetAllOrigins(origin_identifiers); +} + +bool DatabaseTracker::GetAllOriginsInfo( + std::vector<OriginInfo>* origins_info) { + DCHECK(origins_info); + DCHECK(origins_info->empty()); + + std::vector<base::string16> origins; + if (!GetAllOriginIdentifiers(&origins)) + return false; + + for (std::vector<base::string16>::const_iterator it = origins.begin(); + it != origins.end(); it++) { + CachedOriginInfo* origin_info = GetCachedOriginInfo(*it); + if (!origin_info) { + // Restore 'origins_info' to its initial state. + origins_info->clear(); + return false; + } + origins_info->push_back(OriginInfo(*origin_info)); + } + + return true; +} + +bool DatabaseTracker::DeleteClosedDatabase( + const base::string16& origin_identifier, + const base::string16& database_name) { + if (!LazyInit()) + return false; + + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(origin_identifier, database_name)) + return false; + + int64 db_file_size = quota_manager_proxy_ ? + GetDBFileSize(origin_identifier, database_name) : 0; + + // Try to delete the file on the hard drive. + base::FilePath db_file = GetFullDBFilePath(origin_identifier, database_name); + if (file_util::PathExists(db_file) && !file_util::Delete(db_file, false)) + return false; + + // Also delete any orphaned journal file. + DCHECK(db_file.Extension().empty()); + file_util::Delete(db_file.InsertBeforeExtensionASCII( + DatabaseUtil::kJournalFileSuffix), false); + + if (quota_manager_proxy_ && db_file_size) + quota_manager_proxy_->NotifyStorageModified( + quota::QuotaClient::kDatabase, + webkit_base::GetOriginURLFromIdentifier(origin_identifier), + quota::kStorageTypeTemporary, + -db_file_size); + + // Clean up the main database and invalidate the cached record. + databases_table_->DeleteDatabaseDetails(origin_identifier, database_name); + origins_info_map_.erase(origin_identifier); + + std::vector<DatabaseDetails> details; + if (databases_table_->GetAllDatabaseDetailsForOrigin( + origin_identifier, &details) && details.empty()) { + // Try to delete the origin in case this was the last database. + DeleteOrigin(origin_identifier, false); + } + return true; +} + +bool DatabaseTracker::DeleteOrigin(const base::string16& origin_identifier, + bool force) { + if (!LazyInit()) + return false; + + // Check if any database in this origin is opened by any renderer. + if (database_connections_.IsOriginUsed(origin_identifier) && !force) + return false; + + int64 deleted_size = 0; + if (quota_manager_proxy_) { + CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier); + if (origin_info) + deleted_size = origin_info->TotalSize(); + } + + origins_info_map_.erase(origin_identifier); + base::FilePath origin_dir = db_dir_.Append(base::FilePath::FromWStringHack( + UTF16ToWide(origin_identifier))); + + // Create a temporary directory to move possibly still existing databases to, + // as we can't delete the origin directory on windows if it contains opened + // files. + base::FilePath new_origin_dir; + file_util::CreateTemporaryDirInDir(db_dir_, + kTemporaryDirectoryPrefix, + &new_origin_dir); + file_util::FileEnumerator databases( + origin_dir, + false, + file_util::FileEnumerator::FILES); + for (base::FilePath database = databases.Next(); !database.empty(); + database = databases.Next()) { + base::FilePath new_file = new_origin_dir.Append(database.BaseName()); + file_util::Move(database, new_file); + } + file_util::Delete(origin_dir, true); + file_util::Delete(new_origin_dir, true); // might fail on windows. + + databases_table_->DeleteOrigin(origin_identifier); + + if (quota_manager_proxy_ && deleted_size) { + quota_manager_proxy_->NotifyStorageModified( + quota::QuotaClient::kDatabase, + webkit_base::GetOriginURLFromIdentifier(origin_identifier), + quota::kStorageTypeTemporary, + -deleted_size); + } + + return true; +} + +bool DatabaseTracker::IsDatabaseScheduledForDeletion( + const base::string16& origin_identifier, + const base::string16& database_name) { + DatabaseSet::iterator it = dbs_to_be_deleted_.find(origin_identifier); + if (it == dbs_to_be_deleted_.end()) + return false; + + std::set<base::string16>& databases = it->second; + return (databases.find(database_name) != databases.end()); +} + +bool DatabaseTracker::LazyInit() { + if (!is_initialized_ && !shutting_down_) { + DCHECK(!db_->is_open()); + DCHECK(!databases_table_.get()); + DCHECK(!meta_table_.get()); + + // If there are left-over directories from failed deletion attempts, clean + // them up. + if (file_util::DirectoryExists(db_dir_)) { + file_util::FileEnumerator directories( + db_dir_, + false, + file_util::FileEnumerator::DIRECTORIES, + kTemporaryDirectoryPattern); + for (base::FilePath directory = directories.Next(); !directory.empty(); + directory = directories.Next()) { + file_util::Delete(directory, true); + } + } + + // If the tracker database exists, but it's corrupt or doesn't + // have a meta table, delete the database directory. + const base::FilePath kTrackerDatabaseFullPath = + db_dir_.Append(base::FilePath(kTrackerDatabaseFileName)); + if (file_util::DirectoryExists(db_dir_) && + file_util::PathExists(kTrackerDatabaseFullPath) && + (!db_->Open(kTrackerDatabaseFullPath) || + !sql::MetaTable::DoesTableExist(db_.get()))) { + db_->Close(); + if (!file_util::Delete(db_dir_, true)) + return false; + } + + db_->set_histogram_tag("DatabaseTracker"); + + databases_table_.reset(new DatabasesTable(db_.get())); + meta_table_.reset(new sql::MetaTable()); + + is_initialized_ = + file_util::CreateDirectory(db_dir_) && + (db_->is_open() || + (is_incognito_ ? db_->OpenInMemory() : + db_->Open(kTrackerDatabaseFullPath))) && + UpgradeToCurrentVersion(); + if (!is_initialized_) { + databases_table_.reset(NULL); + meta_table_.reset(NULL); + db_->Close(); + } + } + return is_initialized_; +} + +bool DatabaseTracker::UpgradeToCurrentVersion() { + sql::Transaction transaction(db_.get()); + if (!transaction.Begin() || + !meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion) || + (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) || + !databases_table_->Init()) + return false; + + if (meta_table_->GetVersionNumber() < kCurrentVersion) + meta_table_->SetVersionNumber(kCurrentVersion); + + return transaction.Commit(); +} + +void DatabaseTracker::InsertOrUpdateDatabaseDetails( + const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16& database_description, + int64 estimated_size) { + DatabaseDetails details; + if (!databases_table_->GetDatabaseDetails( + origin_identifier, database_name, &details)) { + details.origin_identifier = origin_identifier; + details.database_name = database_name; + details.description = database_description; + details.estimated_size = estimated_size; + databases_table_->InsertDatabaseDetails(details); + } else if ((details.description != database_description) || + (details.estimated_size != estimated_size)) { + details.description = database_description; + details.estimated_size = estimated_size; + databases_table_->UpdateDatabaseDetails(details); + } +} + +void DatabaseTracker::ClearAllCachedOriginInfo() { + origins_info_map_.clear(); +} + +DatabaseTracker::CachedOriginInfo* DatabaseTracker::MaybeGetCachedOriginInfo( + const base::string16& origin_identifier, bool create_if_needed) { + if (!LazyInit()) + return NULL; + + // Populate the cache with data for this origin if needed. + if (origins_info_map_.find(origin_identifier) == origins_info_map_.end()) { + if (!create_if_needed) + return NULL; + + std::vector<DatabaseDetails> details; + if (!databases_table_->GetAllDatabaseDetailsForOrigin( + origin_identifier, &details)) { + return NULL; + } + + CachedOriginInfo& origin_info = origins_info_map_[origin_identifier]; + origin_info.SetOrigin(origin_identifier); + for (std::vector<DatabaseDetails>::const_iterator it = details.begin(); + it != details.end(); it++) { + int64 db_file_size; + if (database_connections_.IsDatabaseOpened( + origin_identifier, it->database_name)) { + db_file_size = database_connections_.GetOpenDatabaseSize( + origin_identifier, it->database_name); + } else { + db_file_size = GetDBFileSize(origin_identifier, it->database_name); + } + origin_info.SetDatabaseSize(it->database_name, db_file_size); + origin_info.SetDatabaseDescription(it->database_name, it->description); + } + } + + return &origins_info_map_[origin_identifier]; +} + +int64 DatabaseTracker::GetDBFileSize(const base::string16& origin_identifier, + const base::string16& database_name) { + base::FilePath db_file_name = GetFullDBFilePath(origin_identifier, + database_name); + int64 db_file_size = 0; + if (!file_util::GetFileSize(db_file_name, &db_file_size)) + db_file_size = 0; + return db_file_size; +} + +int64 DatabaseTracker::SeedOpenDatabaseInfo( + const base::string16& origin_id, const base::string16& name, + const base::string16& description) { + DCHECK(database_connections_.IsDatabaseOpened(origin_id, name)); + int64 size = GetDBFileSize(origin_id, name); + database_connections_.SetOpenDatabaseSize(origin_id, name, size); + CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false); + if (info) { + info->SetDatabaseSize(name, size); + info->SetDatabaseDescription(name, description); + } + return size; +} + +int64 DatabaseTracker::UpdateOpenDatabaseInfoAndNotify( + const base::string16& origin_id, const base::string16& name, + const base::string16* opt_description) { + DCHECK(database_connections_.IsDatabaseOpened(origin_id, name)); + int64 new_size = GetDBFileSize(origin_id, name); + int64 old_size = database_connections_.GetOpenDatabaseSize(origin_id, name); + CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false); + if (info && opt_description) + info->SetDatabaseDescription(name, *opt_description); + if (old_size != new_size) { + database_connections_.SetOpenDatabaseSize(origin_id, name, new_size); + if (info) + info->SetDatabaseSize(name, new_size); + if (quota_manager_proxy_) + quota_manager_proxy_->NotifyStorageModified( + quota::QuotaClient::kDatabase, + webkit_base::GetOriginURLFromIdentifier(origin_id), + quota::kStorageTypeTemporary, + new_size - old_size); + FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseSizeChanged( + origin_id, name, new_size)); + } + return new_size; +} + +void DatabaseTracker::ScheduleDatabaseForDeletion( + const base::string16& origin_identifier, + const base::string16& database_name) { + DCHECK(database_connections_.IsDatabaseOpened(origin_identifier, + database_name)); + dbs_to_be_deleted_[origin_identifier].insert(database_name); + FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseScheduledForDeletion( + origin_identifier, database_name)); +} + +void DatabaseTracker::ScheduleDatabasesForDeletion( + const DatabaseSet& databases, + const net::CompletionCallback& callback) { + DCHECK(!databases.empty()); + + if (!callback.is_null()) + deletion_callbacks_.push_back(std::make_pair(callback, databases)); + for (DatabaseSet::const_iterator ori = databases.begin(); + ori != databases.end(); ++ori) { + for (std::set<base::string16>::const_iterator db = ori->second.begin(); + db != ori->second.end(); ++db) + ScheduleDatabaseForDeletion(ori->first, *db); + } +} + +int DatabaseTracker::DeleteDatabase(const base::string16& origin_identifier, + const base::string16& database_name, + const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + if (database_connections_.IsDatabaseOpened(origin_identifier, + database_name)) { + if (!callback.is_null()) { + DatabaseSet set; + set[origin_identifier].insert(database_name); + deletion_callbacks_.push_back(std::make_pair(callback, set)); + } + ScheduleDatabaseForDeletion(origin_identifier, database_name); + return net::ERR_IO_PENDING; + } + DeleteClosedDatabase(origin_identifier, database_name); + return net::OK; +} + +int DatabaseTracker::DeleteDataModifiedSince( + const base::Time& cutoff, + const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + DatabaseSet to_be_deleted; + + std::vector<base::string16> origins_identifiers; + if (!databases_table_->GetAllOrigins(&origins_identifiers)) + return net::ERR_FAILED; + int rv = net::OK; + for (std::vector<base::string16>::const_iterator ori = + origins_identifiers.begin(); + ori != origins_identifiers.end(); ++ori) { + if (special_storage_policy_.get() && + special_storage_policy_->IsStorageProtected( + webkit_base::GetOriginURLFromIdentifier(*ori))) { + continue; + } + + std::vector<DatabaseDetails> details; + if (!databases_table_->GetAllDatabaseDetailsForOrigin(*ori, &details)) + rv = net::ERR_FAILED; + for (std::vector<DatabaseDetails>::const_iterator db = details.begin(); + db != details.end(); ++db) { + base::FilePath db_file = GetFullDBFilePath(*ori, db->database_name); + base::PlatformFileInfo file_info; + file_util::GetFileInfo(db_file, &file_info); + if (file_info.last_modified < cutoff) + continue; + + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(*ori, db->database_name)) + to_be_deleted[*ori].insert(db->database_name); + else + DeleteClosedDatabase(*ori, db->database_name); + } + } + + if (rv != net::OK) + return rv; + + if (!to_be_deleted.empty()) { + ScheduleDatabasesForDeletion(to_be_deleted, callback); + return net::ERR_IO_PENDING; + } + return net::OK; +} + +int DatabaseTracker::DeleteDataForOrigin( + const base::string16& origin, const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + DatabaseSet to_be_deleted; + + std::vector<DatabaseDetails> details; + if (!databases_table_->GetAllDatabaseDetailsForOrigin(origin, &details)) + return net::ERR_FAILED; + for (std::vector<DatabaseDetails>::const_iterator db = details.begin(); + db != details.end(); ++db) { + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(origin, db->database_name)) + to_be_deleted[origin].insert(db->database_name); + else + DeleteClosedDatabase(origin, db->database_name); + } + + if (!to_be_deleted.empty()) { + ScheduleDatabasesForDeletion(to_be_deleted, callback); + return net::ERR_IO_PENDING; + } + return net::OK; +} + +void DatabaseTracker::GetIncognitoFileHandle( + const base::string16& vfs_file_name, + base::PlatformFile* file_handle) const { + DCHECK(is_incognito_); + FileHandlesMap::const_iterator it = + incognito_file_handles_.find(vfs_file_name); + if (it != incognito_file_handles_.end()) + *file_handle = it->second; + else + *file_handle = base::kInvalidPlatformFileValue; +} + +void DatabaseTracker::SaveIncognitoFileHandle( + const base::string16& vfs_file_name, + const base::PlatformFile& file_handle) { + DCHECK(is_incognito_); + DCHECK(incognito_file_handles_.find(vfs_file_name) == + incognito_file_handles_.end()); + if (file_handle != base::kInvalidPlatformFileValue) + incognito_file_handles_[vfs_file_name] = file_handle; +} + +bool DatabaseTracker::CloseIncognitoFileHandle( + const base::string16& vfs_file_name) { + DCHECK(is_incognito_); + DCHECK(incognito_file_handles_.find(vfs_file_name) != + incognito_file_handles_.end()); + + bool handle_closed = false; + FileHandlesMap::iterator it = incognito_file_handles_.find(vfs_file_name); + if (it != incognito_file_handles_.end()) { + handle_closed = base::ClosePlatformFile(it->second); + if (handle_closed) + incognito_file_handles_.erase(it); + } + return handle_closed; +} + +bool DatabaseTracker::HasSavedIncognitoFileHandle( + const base::string16& vfs_file_name) const { + return (incognito_file_handles_.find(vfs_file_name) != + incognito_file_handles_.end()); +} + +void DatabaseTracker::DeleteIncognitoDBDirectory() { + shutting_down_ = true; + is_initialized_ = false; + + for (FileHandlesMap::iterator it = incognito_file_handles_.begin(); + it != incognito_file_handles_.end(); it++) + base::ClosePlatformFile(it->second); + + base::FilePath incognito_db_dir = + profile_path_.Append(kIncognitoDatabaseDirectoryName); + if (file_util::DirectoryExists(incognito_db_dir)) + file_util::Delete(incognito_db_dir, true); +} + +void DatabaseTracker::ClearSessionOnlyOrigins() { + shutting_down_ = true; + + bool has_session_only_databases = + special_storage_policy_.get() && + special_storage_policy_->HasSessionOnlyOrigins(); + + // Clearing only session-only databases, and there are none. + if (!has_session_only_databases) + return; + + if (!LazyInit()) + return; + + std::vector<base::string16> origin_identifiers; + GetAllOriginIdentifiers(&origin_identifiers); + + for (std::vector<base::string16>::iterator origin = + origin_identifiers.begin(); + origin != origin_identifiers.end(); ++origin) { + GURL origin_url = webkit_base::GetOriginURLFromIdentifier(*origin); + if (!special_storage_policy_->IsStorageSessionOnly(origin_url)) + continue; + if (special_storage_policy_->IsStorageProtected(origin_url)) + continue; + webkit_database::OriginInfo origin_info; + std::vector<base::string16> databases; + GetOriginInfo(*origin, &origin_info); + origin_info.GetAllDatabaseNames(&databases); + + for (std::vector<base::string16>::iterator database = databases.begin(); + database != databases.end(); ++database) { + base::PlatformFile file_handle = base::CreatePlatformFile( + GetFullDBFilePath(*origin, *database), + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_SHARE_DELETE | + base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_READ, + NULL, NULL); + base::ClosePlatformFile(file_handle); + } + DeleteOrigin(*origin, true); + } +} + + +void DatabaseTracker::Shutdown() { + DCHECK(db_tracker_thread_.get()); + DCHECK(db_tracker_thread_->BelongsToCurrentThread()); + if (shutting_down_) { + NOTREACHED(); + return; + } + if (is_incognito_) + DeleteIncognitoDBDirectory(); + else if (!force_keep_session_state_) + ClearSessionOnlyOrigins(); +} + +void DatabaseTracker::SetForceKeepSessionState() { + DCHECK(db_tracker_thread_.get()); + if (!db_tracker_thread_->BelongsToCurrentThread()) { + db_tracker_thread_->PostTask( + FROM_HERE, + base::Bind(&DatabaseTracker::SetForceKeepSessionState, this)); + return; + } + force_keep_session_state_ = true; +} + +} // namespace webkit_database diff --git a/webkit/browser/database/database_tracker.h b/webkit/browser/database/database_tracker.h new file mode 100644 index 0000000..b2f922c --- /dev/null +++ b/webkit/browser/database/database_tracker.h @@ -0,0 +1,312 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_DATABASE_DATABASE_TRACKER_H_ +#define WEBKIT_BROWSER_DATABASE_DATABASE_TRACKER_H_ + +#include <map> +#include <set> +#include <utility> + +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/platform_file.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/time.h" +#include "net/base/completion_callback.h" +#include "webkit/common/database/database_connections.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class MessageLoopProxy; +} + +namespace sql { +class Connection; +class MetaTable; +} + +namespace quota { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace webkit_database { + +WEBKIT_STORAGE_EXPORT extern const base::FilePath::CharType + kDatabaseDirectoryName[]; +WEBKIT_STORAGE_EXPORT extern const base::FilePath::CharType + kTrackerDatabaseFileName[]; + +class DatabasesTable; + +// This class is used to store information about all databases in an origin. +class WEBKIT_STORAGE_EXPORT OriginInfo { + public: + OriginInfo(); + OriginInfo(const OriginInfo& origin_info); + ~OriginInfo(); + + const base::string16& GetOrigin() const { return origin_; } + int64 TotalSize() const { return total_size_; } + void GetAllDatabaseNames(std::vector<base::string16>* databases) const; + int64 GetDatabaseSize(const base::string16& database_name) const; + base::string16 GetDatabaseDescription( + const base::string16& database_name) const; + + protected: + typedef std::map<base::string16, std::pair<int64, base::string16> > + DatabaseInfoMap; + + OriginInfo(const base::string16& origin, int64 total_size); + + base::string16 origin_; + int64 total_size_; + DatabaseInfoMap database_info_; +}; + +// This class manages the main database and keeps track of open databases. +// +// The data in this class is not thread-safe, so all methods of this class +// should be called on the same thread. The only exceptions are the ctor(), +// the dtor() and the database_directory() and quota_manager_proxy() getters. +// +// Furthermore, some methods of this class have to read/write data from/to +// the disk. Therefore, in a multi-threaded application, all methods of this +// class should be called on the thread dedicated to file operations (file +// thread in the browser process, for example), if such a thread exists. +class WEBKIT_STORAGE_EXPORT DatabaseTracker + : public base::RefCountedThreadSafe<DatabaseTracker> { + public: + class Observer { + public: + virtual void OnDatabaseSizeChanged(const base::string16& origin_identifier, + const base::string16& database_name, + int64 database_size) = 0; + virtual void OnDatabaseScheduledForDeletion( + const base::string16& origin_identifier, + const base::string16& database_name) = 0; + + protected: + virtual ~Observer() {} + }; + + DatabaseTracker(const base::FilePath& profile_path, + bool is_incognito, + quota::SpecialStoragePolicy* special_storage_policy, + quota::QuotaManagerProxy* quota_manager_proxy, + base::MessageLoopProxy* db_tracker_thread); + + void DatabaseOpened(const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16& database_details, + int64 estimated_size, + int64* database_size); + void DatabaseModified(const base::string16& origin_identifier, + const base::string16& database_name); + void DatabaseClosed(const base::string16& origin_identifier, + const base::string16& database_name); + void HandleSqliteError(const base::string16& origin_identifier, + const base::string16& database_name, + int error); + + void CloseDatabases(const DatabaseConnections& connections); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + void CloseTrackerDatabaseAndClearCaches(); + + const base::FilePath& DatabaseDirectory() const { return db_dir_; } + base::FilePath GetFullDBFilePath(const base::string16& origin_identifier, + const base::string16& database_name); + + // virtual for unit-testing only + virtual bool GetOriginInfo(const base::string16& origin_id, OriginInfo* info); + virtual bool GetAllOriginIdentifiers(std::vector<base::string16>* origin_ids); + virtual bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info); + + // Safe to call on any thread. + quota::QuotaManagerProxy* quota_manager_proxy() const { + return quota_manager_proxy_.get(); + } + + bool IsDatabaseScheduledForDeletion(const base::string16& origin_identifier, + const base::string16& database_name); + + // Deletes a single database. Returns net::OK on success, net::FAILED on + // failure, or net::ERR_IO_PENDING and |callback| is invoked upon completion, + // if non-NULL. + int DeleteDatabase(const base::string16& origin_identifier, + const base::string16& database_name, + const net::CompletionCallback& callback); + + // Delete any databases that have been touched since the cutoff date that's + // supplied, omitting any that match IDs within |protected_origins|. + // Returns net::OK on success, net::FAILED if not all databases could be + // deleted, and net::ERR_IO_PENDING and |callback| is invoked upon completion, + // if non-NULL. Protected origins, according the the SpecialStoragePolicy, + // are not deleted by this method. + int DeleteDataModifiedSince(const base::Time& cutoff, + const net::CompletionCallback& callback); + + // Delete all databases that belong to the given origin. Returns net::OK on + // success, net::FAILED if not all databases could be deleted, and + // net::ERR_IO_PENDING and |callback| is invoked upon completion, if non-NULL. + // virtual for unit testing only + virtual int DeleteDataForOrigin(const base::string16& origin_identifier, + const net::CompletionCallback& callback); + + bool IsIncognitoProfile() const { return is_incognito_; } + + void GetIncognitoFileHandle(const base::string16& vfs_file_path, + base::PlatformFile* file_handle) const; + void SaveIncognitoFileHandle(const base::string16& vfs_file_path, + const base::PlatformFile& file_handle); + bool CloseIncognitoFileHandle(const base::string16& vfs_file_path); + bool HasSavedIncognitoFileHandle(const base::string16& vfs_file_path) const; + + // Shutdown the database tracker, deleting database files if the tracker is + // used for an incognito profile. + void Shutdown(); + // Disables the exit-time deletion of session-only data. + void SetForceKeepSessionState(); + + private: + friend class base::RefCountedThreadSafe<DatabaseTracker>; + friend class MockDatabaseTracker; // for testing + + typedef std::map<base::string16, std::set<base::string16> > DatabaseSet; + typedef std::vector<std::pair<net::CompletionCallback, DatabaseSet> > + PendingDeletionCallbacks; + typedef std::map<base::string16, base::PlatformFile> FileHandlesMap; + typedef std::map<base::string16, base::string16> OriginDirectoriesMap; + + class CachedOriginInfo : public OriginInfo { + public: + CachedOriginInfo() : OriginInfo(base::string16(), 0) {} + void SetOrigin(const base::string16& origin) { origin_ = origin; } + void SetDatabaseSize(const base::string16& database_name, int64 new_size) { + int64 old_size = 0; + if (database_info_.find(database_name) != database_info_.end()) + old_size = database_info_[database_name].first; + database_info_[database_name].first = new_size; + if (new_size != old_size) + total_size_ += new_size - old_size; + } + void SetDatabaseDescription(const base::string16& database_name, + const base::string16& description) { + database_info_[database_name].second = description; + } + }; + + // virtual for unit-testing only. + virtual ~DatabaseTracker(); + + // Deletes the directory that stores all DBs in incognito mode, if it exists. + void DeleteIncognitoDBDirectory(); + + // Deletes session-only databases. Blocks databases from being created/opened. + void ClearSessionOnlyOrigins(); + + bool DeleteClosedDatabase(const base::string16& origin_identifier, + const base::string16& database_name); + + // Delete all files belonging to the given origin given that no database + // connections within this origin are open, or if |force| is true, delete + // the meta data and rename the associated directory. + bool DeleteOrigin(const base::string16& origin_identifier, bool force); + void DeleteDatabaseIfNeeded(const base::string16& origin_identifier, + const base::string16& database_name); + + bool LazyInit(); + bool UpgradeToCurrentVersion(); + void InsertOrUpdateDatabaseDetails(const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16& database_details, + int64 estimated_size); + + void ClearAllCachedOriginInfo(); + CachedOriginInfo* MaybeGetCachedOriginInfo( + const base::string16& origin_identifier, + bool create_if_needed); + CachedOriginInfo* GetCachedOriginInfo( + const base::string16& origin_identifier) { + return MaybeGetCachedOriginInfo(origin_identifier, true); + } + + int64 GetDBFileSize(const base::string16& origin_identifier, + const base::string16& database_name); + int64 SeedOpenDatabaseInfo(const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16& description); + int64 UpdateOpenDatabaseInfoAndNotify(const base::string16& origin_identifier, + const base::string16& database_name, + const base::string16* opt_description); + int64 UpdateOpenDatabaseSizeAndNotify(const base::string16& origin_identifier, + const base::string16& database_name) { + return UpdateOpenDatabaseInfoAndNotify( + origin_identifier, database_name, NULL); + } + + + void ScheduleDatabaseForDeletion(const base::string16& origin_identifier, + const base::string16& database_name); + // Schedule a set of open databases for deletion. If non-null, callback is + // invoked upon completion. + void ScheduleDatabasesForDeletion(const DatabaseSet& databases, + const net::CompletionCallback& callback); + + // Returns the directory where all DB files for the given origin are stored. + base::string16 GetOriginDirectory(const base::string16& origin_identifier); + + bool is_initialized_; + const bool is_incognito_; + bool force_keep_session_state_; + bool shutting_down_; + const base::FilePath profile_path_; + const base::FilePath db_dir_; + scoped_ptr<sql::Connection> db_; + scoped_ptr<DatabasesTable> databases_table_; + scoped_ptr<sql::MetaTable> meta_table_; + ObserverList<Observer, true> observers_; + std::map<base::string16, CachedOriginInfo> origins_info_map_; + DatabaseConnections database_connections_; + + // The set of databases that should be deleted but are still opened + DatabaseSet dbs_to_be_deleted_; + PendingDeletionCallbacks deletion_callbacks_; + + // Apps and Extensions can have special rights. + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; + + scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + + // The database tracker thread we're supposed to run file IO on. + scoped_refptr<base::MessageLoopProxy> db_tracker_thread_; + + // When in incognito mode, store a DELETE_ON_CLOSE handle to each + // main DB and journal file that was accessed. When the incognito profile + // goes away (or when the browser crashes), all these handles will be + // closed, and the files will be deleted. + FileHandlesMap incognito_file_handles_; + + // In a non-incognito profile, all DBs in an origin are stored in a directory + // named after the origin. In an incognito profile though, we do not want the + // directory structure to reveal the origins visited by the user (in case the + // browser process crashes and those directories are not deleted). So we use + // this map to assign directory names that do not reveal this information. + OriginDirectoriesMap incognito_origin_directories_; + int incognito_origin_directories_generator_; + + FRIEND_TEST_ALL_PREFIXES(DatabaseTracker, TestHelper); +}; + +} // namespace webkit_database + +#endif // WEBKIT_BROWSER_DATABASE_DATABASE_TRACKER_H_ diff --git a/webkit/browser/database/database_tracker_unittest.cc b/webkit/browser/database/database_tracker_unittest.cc new file mode 100644 index 0000000..32bf570 --- /dev/null +++ b/webkit/browser/database/database_tracker_unittest.cc @@ -0,0 +1,855 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" +#include "webkit/base/origin_url_conversions.h" +#include "webkit/browser/database/database_tracker.h" +#include "webkit/quota/mock_special_storage_policy.h" +#include "webkit/quota/quota_manager.h" + +namespace { + +const char kOrigin1Url[] = "http://origin1"; +const char kOrigin2Url[] = "http://protected_origin2"; + +class TestObserver : public webkit_database::DatabaseTracker::Observer { + public: + TestObserver() + : new_notification_received_(false), + observe_size_changes_(true), + observe_scheduled_deletions_(true) { + } + TestObserver(bool observe_size_changes, bool observe_scheduled_deletions) + : new_notification_received_(false), + observe_size_changes_(observe_size_changes), + observe_scheduled_deletions_(observe_scheduled_deletions) { + } + + virtual ~TestObserver() {} + virtual void OnDatabaseSizeChanged(const base::string16& origin_identifier, + const base::string16& database_name, + int64 database_size) OVERRIDE { + if (!observe_size_changes_) + return; + new_notification_received_ = true; + origin_identifier_ = origin_identifier; + database_name_ = database_name; + database_size_ = database_size; + } + virtual void OnDatabaseScheduledForDeletion( + const base::string16& origin_identifier, + const base::string16& database_name) OVERRIDE { + if (!observe_scheduled_deletions_) + return; + new_notification_received_ = true; + origin_identifier_ = origin_identifier; + database_name_ = database_name; + } + bool DidReceiveNewNotification() { + bool temp_new_notification_received = new_notification_received_; + new_notification_received_ = false; + return temp_new_notification_received; + } + base::string16 GetNotificationOriginIdentifier() { + return origin_identifier_; + } + base::string16 GetNotificationDatabaseName() { return database_name_; } + int64 GetNotificationDatabaseSize() { return database_size_; } + + private: + bool new_notification_received_; + bool observe_size_changes_; + bool observe_scheduled_deletions_; + base::string16 origin_identifier_; + base::string16 database_name_; + int64 database_size_; +}; + +void CheckNotificationReceived(TestObserver* observer, + const base::string16& expected_origin_identifier, + const base::string16& expected_database_name, + int64 expected_database_size) { + EXPECT_TRUE(observer->DidReceiveNewNotification()); + EXPECT_EQ(expected_origin_identifier, + observer->GetNotificationOriginIdentifier()); + EXPECT_EQ(expected_database_name, + observer->GetNotificationDatabaseName()); + EXPECT_EQ(expected_database_size, + observer->GetNotificationDatabaseSize()); +} + +class TestQuotaManagerProxy : public quota::QuotaManagerProxy { + public: + TestQuotaManagerProxy() + : QuotaManagerProxy(NULL, NULL), + registered_client_(NULL) { + } + + virtual void RegisterClient(quota::QuotaClient* client) OVERRIDE { + EXPECT_FALSE(registered_client_); + registered_client_ = client; + } + + virtual void NotifyStorageAccessed(quota::QuotaClient::ID client_id, + const GURL& origin, + quota::StorageType type) OVERRIDE { + EXPECT_EQ(quota::QuotaClient::kDatabase, client_id); + EXPECT_EQ(quota::kStorageTypeTemporary, type); + accesses_[origin] += 1; + } + + virtual void NotifyStorageModified(quota::QuotaClient::ID client_id, + const GURL& origin, + quota::StorageType type, + int64 delta) OVERRIDE { + EXPECT_EQ(quota::QuotaClient::kDatabase, client_id); + EXPECT_EQ(quota::kStorageTypeTemporary, type); + modifications_[origin].first += 1; + modifications_[origin].second += delta; + } + + // Not needed for our tests. + virtual void NotifyOriginInUse(const GURL& origin) OVERRIDE {} + virtual void NotifyOriginNoLongerInUse(const GURL& origin) OVERRIDE {} + + void SimulateQuotaManagerDestroyed() { + if (registered_client_) { + registered_client_->OnQuotaManagerDestroyed(); + registered_client_ = NULL; + } + } + + bool WasAccessNotified(const GURL& origin) { + return accesses_[origin] != 0; + } + + bool WasModificationNotified(const GURL& origin, int64 amount) { + return modifications_[origin].first != 0 && + modifications_[origin].second == amount; + } + + void reset() { + accesses_.clear(); + modifications_.clear(); + } + + quota::QuotaClient* registered_client_; + + // Map from origin to count of access notifications. + std::map<GURL, int> accesses_; + + // Map from origin to <count, sum of deltas> + std::map<GURL, std::pair<int, int64> > modifications_; + + protected: + virtual ~TestQuotaManagerProxy() { + EXPECT_FALSE(registered_client_); + } +}; + + +bool EnsureFileOfSize(const base::FilePath& file_path, int64 length) { + base::PlatformFileError error_code(base::PLATFORM_FILE_ERROR_FAILED); + base::PlatformFile file = + base::CreatePlatformFile( + file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE, + NULL, + &error_code); + if (error_code != base::PLATFORM_FILE_OK) + return false; + if (!base::TruncatePlatformFile(file, length)) + error_code = base::PLATFORM_FILE_ERROR_FAILED; + base::ClosePlatformFile(file); + return error_code == base::PLATFORM_FILE_OK; +} + +} // namespace + +namespace webkit_database { + +// We declare a helper class, and make it a friend of DatabaseTracker using +// the FRIEND_TEST() macro, and we implement all tests we want to run as +// static methods of this class. Then we make our TEST() targets call these +// static functions. This allows us to run each test in normal mode and +// incognito mode without writing the same code twice. +class DatabaseTracker_TestHelper_Test { + public: + static void TestDeleteOpenDatabase(bool incognito_mode) { + // Initialize the tracker database. + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy = + new quota::MockSpecialStoragePolicy; + special_storage_policy->AddProtected(GURL(kOrigin2Url)); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), incognito_mode, + special_storage_policy, NULL, NULL)); + + // Create and open three databases. + int64 database_size = 0; + const base::string16 kOrigin1 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin1Url)); + const base::string16 kOrigin2 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin2Url)); + const base::string16 kDB1 = ASCIIToUTF16("db1"); + const base::string16 kDB2 = ASCIIToUTF16("db2"); + const base::string16 kDB3 = ASCIIToUTF16("db3"); + const base::string16 kDescription = ASCIIToUTF16("database_description"); + + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, + &database_size); + tracker->DatabaseOpened(kOrigin2, kDB3, kDescription, 0, + &database_size); + + EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide( + tracker->GetOriginDirectory(kOrigin1)))))); + EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide( + tracker->GetOriginDirectory(kOrigin2)))))); + EXPECT_EQ(1, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1)); + EXPECT_EQ(2, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin2, kDB2), "aa", 2)); + EXPECT_EQ(3, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin2, kDB3), "aaa", 3)); + tracker->DatabaseModified(kOrigin1, kDB1); + tracker->DatabaseModified(kOrigin2, kDB2); + tracker->DatabaseModified(kOrigin2, kDB3); + + // Delete db1. Should also delete origin1. + TestObserver observer; + tracker->AddObserver(&observer); + net::TestCompletionCallback callback; + int result = tracker->DeleteDatabase(kOrigin1, kDB1, callback.callback()); + EXPECT_EQ(net::ERR_IO_PENDING, result); + ASSERT_FALSE(callback.have_result()); + EXPECT_TRUE(observer.DidReceiveNewNotification()); + EXPECT_EQ(kOrigin1, observer.GetNotificationOriginIdentifier()); + EXPECT_EQ(kDB1, observer.GetNotificationDatabaseName()); + tracker->DatabaseClosed(kOrigin1, kDB1); + result = callback.GetResult(result); + EXPECT_EQ(net::OK, result); + EXPECT_FALSE(file_util::PathExists(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide(kOrigin1))))); + + // Recreate db1. + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide( + tracker->GetOriginDirectory(kOrigin1)))))); + EXPECT_EQ(1, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1)); + tracker->DatabaseModified(kOrigin1, kDB1); + + // Setup file modification times. db1 and db2 are modified now, db3 three + // days ago. + EXPECT_TRUE(file_util::SetLastModifiedTime( + tracker->GetFullDBFilePath(kOrigin1, kDB1), base::Time::Now())); + EXPECT_TRUE(file_util::SetLastModifiedTime( + tracker->GetFullDBFilePath(kOrigin2, kDB2), base::Time::Now())); + base::Time three_days_ago = base::Time::Now(); + three_days_ago -= base::TimeDelta::FromDays(3); + EXPECT_TRUE(file_util::SetLastModifiedTime( + tracker->GetFullDBFilePath(kOrigin2, kDB3), three_days_ago)); + + // Delete databases modified since yesterday. db2 is whitelisted. + base::Time yesterday = base::Time::Now(); + yesterday -= base::TimeDelta::FromDays(1); + result = tracker->DeleteDataModifiedSince( + yesterday, callback.callback()); + EXPECT_EQ(net::ERR_IO_PENDING, result); + ASSERT_FALSE(callback.have_result()); + EXPECT_TRUE(observer.DidReceiveNewNotification()); + tracker->DatabaseClosed(kOrigin1, kDB1); + tracker->DatabaseClosed(kOrigin2, kDB2); + result = callback.GetResult(result); + EXPECT_EQ(net::OK, result); + EXPECT_FALSE(file_util::PathExists(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide(kOrigin1))))); + EXPECT_TRUE( + file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2))); + EXPECT_TRUE( + file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB3))); + + tracker->DatabaseClosed(kOrigin2, kDB3); + tracker->RemoveObserver(&observer); + } + + static void TestDatabaseTracker(bool incognito_mode) { + // Initialize the tracker database. + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy = + new quota::MockSpecialStoragePolicy; + special_storage_policy->AddProtected(GURL(kOrigin2Url)); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), incognito_mode, + special_storage_policy, NULL, NULL)); + + // Add two observers. + TestObserver observer1; + TestObserver observer2; + tracker->AddObserver(&observer1); + tracker->AddObserver(&observer2); + + // Open three new databases. + int64 database_size = 0; + const base::string16 kOrigin1 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin1Url)); + const base::string16 kOrigin2 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin2Url)); + const base::string16 kDB1 = ASCIIToUTF16("db1"); + const base::string16 kDB2 = ASCIIToUTF16("db2"); + const base::string16 kDB3 = ASCIIToUTF16("db3"); + const base::string16 kDescription = ASCIIToUTF16("database_description"); + + // Get the info for kOrigin1 and kOrigin2 + DatabaseTracker::CachedOriginInfo* origin1_info = + tracker->GetCachedOriginInfo(kOrigin1); + DatabaseTracker::CachedOriginInfo* origin2_info = + tracker->GetCachedOriginInfo(kOrigin1); + EXPECT_TRUE(origin1_info); + EXPECT_TRUE(origin2_info); + + + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + + // Write some data to each file and check that the listeners are + // called with the appropriate values. + EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide( + tracker->GetOriginDirectory(kOrigin1)))))); + EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append( + base::FilePath::FromWStringHack(UTF16ToWide( + tracker->GetOriginDirectory(kOrigin2)))))); + EXPECT_EQ(1, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1)); + EXPECT_EQ(2, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin2, kDB2), "aa", 2)); + EXPECT_EQ(4, file_util::WriteFile( + tracker->GetFullDBFilePath(kOrigin1, kDB3), "aaaa", 4)); + tracker->DatabaseModified(kOrigin1, kDB1); + CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1); + CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1); + tracker->DatabaseModified(kOrigin2, kDB2); + CheckNotificationReceived(&observer1, kOrigin2, kDB2, 2); + CheckNotificationReceived(&observer2, kOrigin2, kDB2, 2); + tracker->DatabaseModified(kOrigin1, kDB3); + CheckNotificationReceived(&observer1, kOrigin1, kDB3, 4); + CheckNotificationReceived(&observer2, kOrigin1, kDB3, 4); + + // Close all databases + tracker->DatabaseClosed(kOrigin1, kDB1); + tracker->DatabaseClosed(kOrigin2, kDB2); + tracker->DatabaseClosed(kOrigin1, kDB3); + + // Open an existing database and check the reported size + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_EQ(1, database_size); + tracker->DatabaseClosed(kOrigin1, kDB1); + + // Remove an observer; this should clear all caches. + tracker->RemoveObserver(&observer2); + + // Close the tracker database and clear all caches. + // Then make sure that DatabaseOpened() still returns the correct result. + tracker->CloseTrackerDatabaseAndClearCaches(); + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_EQ(1, database_size); + tracker->DatabaseClosed(kOrigin1, kDB1); + + // Remove all observers. + tracker->RemoveObserver(&observer1); + + // Trying to delete a database in use should fail + tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0, + &database_size); + EXPECT_FALSE(tracker->DeleteClosedDatabase(kOrigin1, kDB3)); + origin1_info = tracker->GetCachedOriginInfo(kOrigin1); + EXPECT_TRUE(origin1_info); + EXPECT_EQ(4, origin1_info->GetDatabaseSize(kDB3)); + tracker->DatabaseClosed(kOrigin1, kDB3); + + // Delete a database and make sure the space used by that origin is updated + EXPECT_TRUE(tracker->DeleteClosedDatabase(kOrigin1, kDB3)); + origin1_info = tracker->GetCachedOriginInfo(kOrigin1); + EXPECT_TRUE(origin1_info); + EXPECT_EQ(1, origin1_info->GetDatabaseSize(kDB1)); + EXPECT_EQ(0, origin1_info->GetDatabaseSize(kDB3)); + + // Get all data for all origins + std::vector<OriginInfo> origins_info; + EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info)); + EXPECT_EQ(size_t(2), origins_info.size()); + EXPECT_EQ(kOrigin1, origins_info[0].GetOrigin()); + EXPECT_EQ(1, origins_info[0].TotalSize()); + EXPECT_EQ(1, origins_info[0].GetDatabaseSize(kDB1)); + EXPECT_EQ(0, origins_info[0].GetDatabaseSize(kDB3)); + + EXPECT_EQ(kOrigin2, origins_info[1].GetOrigin()); + EXPECT_EQ(2, origins_info[1].TotalSize()); + + // Trying to delete an origin with databases in use should fail + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_FALSE(tracker->DeleteOrigin(kOrigin1, false)); + origin1_info = tracker->GetCachedOriginInfo(kOrigin1); + EXPECT_TRUE(origin1_info); + EXPECT_EQ(1, origin1_info->GetDatabaseSize(kDB1)); + tracker->DatabaseClosed(kOrigin1, kDB1); + + // Delete an origin that doesn't have any database in use + EXPECT_TRUE(tracker->DeleteOrigin(kOrigin1, false)); + origins_info.clear(); + EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info)); + EXPECT_EQ(size_t(1), origins_info.size()); + EXPECT_EQ(kOrigin2, origins_info[0].GetOrigin()); + + origin1_info = tracker->GetCachedOriginInfo(kOrigin1); + EXPECT_TRUE(origin1_info); + EXPECT_EQ(0, origin1_info->TotalSize()); + } + + static void DatabaseTrackerQuotaIntegration() { + const GURL kOrigin(kOrigin1Url); + const base::string16 kOriginId = + webkit_base::GetOriginIdentifierFromURL(kOrigin); + const base::string16 kName = ASCIIToUTF16("name"); + const base::string16 kDescription = ASCIIToUTF16("description"); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + // Initialize the tracker with a QuotaManagerProxy + scoped_refptr<TestQuotaManagerProxy> test_quota_proxy( + new TestQuotaManagerProxy); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), false /* incognito */, + NULL, test_quota_proxy, NULL)); + EXPECT_TRUE(test_quota_proxy->registered_client_); + + // Create a database and modify it a couple of times, close it, + // then delete it. Observe the tracker notifies accordingly. + + int64 database_size = 0; + tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, + &database_size); + EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin)); + test_quota_proxy->reset(); + + base::FilePath db_file(tracker->GetFullDBFilePath(kOriginId, kName)); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 10)); + tracker->DatabaseModified(kOriginId, kName); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 10)); + test_quota_proxy->reset(); + + EXPECT_TRUE(EnsureFileOfSize(db_file, 100)); + tracker->DatabaseModified(kOriginId, kName); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 90)); + test_quota_proxy->reset(); + + tracker->DatabaseClosed(kOriginId, kName); + EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin)); + EXPECT_EQ(net::OK, tracker->DeleteDatabase( + kOriginId, kName, net::CompletionCallback())); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, -100)); + test_quota_proxy->reset(); + + // Create a database and modify it, try to delete it while open, + // then close it (at which time deletion will actually occur). + // Observe the tracker notifies accordingly. + + tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, + &database_size); + EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin)); + test_quota_proxy->reset(); + + db_file = tracker->GetFullDBFilePath(kOriginId, kName); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 100)); + tracker->DatabaseModified(kOriginId, kName); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 100)); + test_quota_proxy->reset(); + + EXPECT_EQ(net::ERR_IO_PENDING, + tracker->DeleteDatabase(kOriginId, kName, + net::CompletionCallback())); + EXPECT_FALSE(test_quota_proxy->WasModificationNotified(kOrigin, -100)); + + tracker->DatabaseClosed(kOriginId, kName); + EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin)); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, -100)); + test_quota_proxy->reset(); + + // Create a database and up the file size without telling + // the tracker about the modification, than simulate a + // a renderer crash. + // Observe the tracker notifies accordingly. + + tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, + &database_size); + EXPECT_TRUE(test_quota_proxy->WasAccessNotified(kOrigin)); + test_quota_proxy->reset(); + db_file = tracker->GetFullDBFilePath(kOriginId, kName); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 100)); + DatabaseConnections crashed_renderer_connections; + crashed_renderer_connections.AddConnection(kOriginId, kName); + EXPECT_FALSE(test_quota_proxy->WasModificationNotified(kOrigin, 100)); + tracker->CloseDatabases(crashed_renderer_connections); + EXPECT_TRUE(test_quota_proxy->WasModificationNotified(kOrigin, 100)); + + // Cleanup. + crashed_renderer_connections.RemoveAllConnections(); + test_quota_proxy->SimulateQuotaManagerDestroyed(); + } + + static void DatabaseTrackerClearSessionOnlyDatabasesOnExit() { + int64 database_size = 0; + const base::string16 kOrigin1 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin1Url)); + const base::string16 kOrigin2 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin2Url)); + const base::string16 kDB1 = ASCIIToUTF16("db1"); + const base::string16 kDB2 = ASCIIToUTF16("db2"); + const base::string16 kDescription = ASCIIToUTF16("database_description"); + + // Initialize the tracker database. + base::MessageLoop message_loop; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath origin1_db_dir; + base::FilePath origin2_db_dir; + { + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy = + new quota::MockSpecialStoragePolicy; + special_storage_policy->AddSessionOnly(GURL(kOrigin2Url)); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker( + temp_dir.path(), false, special_storage_policy, NULL, + base::MessageLoopProxy::current())); + + // Open two new databases. + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + + // Write some data to each file. + base::FilePath db_file; + db_file = tracker->GetFullDBFilePath(kOrigin1, kDB1); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 1)); + + db_file = tracker->GetFullDBFilePath(kOrigin2, kDB2); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 2)); + + // Store the origin database directories as long as they still exist. + origin1_db_dir = tracker->GetFullDBFilePath(kOrigin1, kDB1).DirName(); + origin2_db_dir = tracker->GetFullDBFilePath(kOrigin2, kDB2).DirName(); + + tracker->DatabaseModified(kOrigin1, kDB1); + tracker->DatabaseModified(kOrigin2, kDB2); + + // Close all databases. + tracker->DatabaseClosed(kOrigin1, kDB1); + tracker->DatabaseClosed(kOrigin2, kDB2); + + tracker->Shutdown(); + } + + // At this point, the database tracker should be gone. Create a new one. + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), false, NULL, NULL, NULL)); + + // Get all data for all origins. + std::vector<OriginInfo> origins_info; + EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info)); + // kOrigin1 was not session-only, so it survived. kOrigin2 was session-only + // and it got deleted. + EXPECT_EQ(size_t(1), origins_info.size()); + EXPECT_EQ(kOrigin1, origins_info[0].GetOrigin()); + EXPECT_TRUE( + file_util::PathExists(tracker->GetFullDBFilePath(kOrigin1, kDB1))); + EXPECT_EQ(base::FilePath(), tracker->GetFullDBFilePath(kOrigin2, kDB2)); + + // The origin directory of kOrigin1 remains, but the origin directory of + // kOrigin2 is deleted. + EXPECT_TRUE(file_util::PathExists(origin1_db_dir)); + EXPECT_FALSE(file_util::PathExists(origin2_db_dir)); + } + + static void DatabaseTrackerSetForceKeepSessionState() { + int64 database_size = 0; + const base::string16 kOrigin1 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin1Url)); + const base::string16 kOrigin2 = + webkit_base::GetOriginIdentifierFromURL(GURL(kOrigin2Url)); + const base::string16 kDB1 = ASCIIToUTF16("db1"); + const base::string16 kDB2 = ASCIIToUTF16("db2"); + const base::string16 kDescription = ASCIIToUTF16("database_description"); + + // Initialize the tracker database. + base::MessageLoop message_loop; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath origin1_db_dir; + base::FilePath origin2_db_dir; + { + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy = + new quota::MockSpecialStoragePolicy; + special_storage_policy->AddSessionOnly(GURL(kOrigin2Url)); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker( + temp_dir.path(), false, special_storage_policy, NULL, + base::MessageLoopProxy::current())); + tracker->SetForceKeepSessionState(); + + // Open two new databases. + tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + + // Write some data to each file. + base::FilePath db_file; + db_file = tracker->GetFullDBFilePath(kOrigin1, kDB1); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 1)); + + db_file = tracker->GetFullDBFilePath(kOrigin2, kDB2); + EXPECT_TRUE(file_util::CreateDirectory(db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(db_file, 2)); + + // Store the origin database directories as long as they still exist. + origin1_db_dir = tracker->GetFullDBFilePath(kOrigin1, kDB1).DirName(); + origin2_db_dir = tracker->GetFullDBFilePath(kOrigin2, kDB2).DirName(); + + tracker->DatabaseModified(kOrigin1, kDB1); + tracker->DatabaseModified(kOrigin2, kDB2); + + // Close all databases. + tracker->DatabaseClosed(kOrigin1, kDB1); + tracker->DatabaseClosed(kOrigin2, kDB2); + + tracker->Shutdown(); + } + + // At this point, the database tracker should be gone. Create a new one. + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), false, NULL, NULL, NULL)); + + // Get all data for all origins. + std::vector<OriginInfo> origins_info; + EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info)); + // No origins were deleted. + EXPECT_EQ(size_t(2), origins_info.size()); + EXPECT_TRUE( + file_util::PathExists(tracker->GetFullDBFilePath(kOrigin1, kDB1))); + EXPECT_TRUE( + file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2))); + + EXPECT_TRUE(file_util::PathExists(origin1_db_dir)); + EXPECT_TRUE(file_util::PathExists(origin2_db_dir)); + } + + static void EmptyDatabaseNameIsValid() { + const GURL kOrigin(kOrigin1Url); + const base::string16 kOriginId = + webkit_base::GetOriginIdentifierFromURL(kOrigin); + const base::string16 kEmptyName; + const base::string16 kDescription(ASCIIToUTF16("description")); + const base::string16 kChangedDescription( + ASCIIToUTF16("changed_description")); + + // Initialize a tracker database, no need to put it on disk. + const bool kUseInMemoryTrackerDatabase = true; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), kUseInMemoryTrackerDatabase, + NULL, NULL, NULL)); + + // Starts off with no databases. + std::vector<OriginInfo> infos; + EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos)); + EXPECT_TRUE(infos.empty()); + + // Create a db with an empty name. + int64 database_size = -1; + tracker->DatabaseOpened(kOriginId, kEmptyName, kDescription, 0, + &database_size); + EXPECT_EQ(0, database_size); + tracker->DatabaseModified(kOriginId, kEmptyName); + EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos)); + EXPECT_EQ(1u, infos.size()); + EXPECT_EQ(kDescription, infos[0].GetDatabaseDescription(kEmptyName)); + EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kEmptyName).empty()); + tracker->DatabaseOpened(kOriginId, kEmptyName, kChangedDescription, 0, + &database_size); + infos.clear(); + EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos)); + EXPECT_EQ(1u, infos.size()); + EXPECT_EQ(kChangedDescription, infos[0].GetDatabaseDescription(kEmptyName)); + tracker->DatabaseClosed(kOriginId, kEmptyName); + tracker->DatabaseClosed(kOriginId, kEmptyName); + + // Deleting it should return to the initial state. + EXPECT_EQ(net::OK, tracker->DeleteDatabase(kOriginId, kEmptyName, + net::CompletionCallback())); + infos.clear(); + EXPECT_TRUE(tracker->GetAllOriginsInfo(&infos)); + EXPECT_TRUE(infos.empty()); + } + + static void HandleSqliteError() { + const GURL kOrigin(kOrigin1Url); + const base::string16 kOriginId = + webkit_base::GetOriginIdentifierFromURL(kOrigin); + const base::string16 kName(ASCIIToUTF16("name")); + const base::string16 kDescription(ASCIIToUTF16("description")); + + // Initialize a tracker database, no need to put it on disk. + const bool kUseInMemoryTrackerDatabase = true; + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + scoped_refptr<DatabaseTracker> tracker( + new DatabaseTracker(temp_dir.path(), kUseInMemoryTrackerDatabase, + NULL, NULL, NULL)); + + // Setup to observe OnScheduledForDelete notifications. + TestObserver observer(false, true); + tracker->AddObserver(&observer); + + // Verify does no harm when there is no such database. + tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT); + EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName)); + EXPECT_FALSE(observer.DidReceiveNewNotification()); + + // -------------------------------------------------------- + // Create a record of a database in the tracker db and create + // a spoof_db_file on disk in the expected location. + int64 database_size = 0; + tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, + &database_size); + base::FilePath spoof_db_file = tracker->GetFullDBFilePath(kOriginId, kName); + EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kName).empty()); + EXPECT_TRUE(file_util::CreateDirectory(spoof_db_file.DirName())); + EXPECT_TRUE(EnsureFileOfSize(spoof_db_file, 1)); + + // Verify does no harm with a non-error is reported. + tracker->HandleSqliteError(kOriginId, kName, SQLITE_OK); + EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName)); + EXPECT_FALSE(observer.DidReceiveNewNotification()); + + // Verify that with a connection open, the db is scheduled for deletion, + // but that the file still exists. + tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT); + EXPECT_TRUE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName)); + EXPECT_TRUE(observer.DidReceiveNewNotification()); + EXPECT_TRUE(file_util::PathExists(spoof_db_file)); + + // Verify that once closed, the file is deleted and the record in the + // tracker db is removed. + tracker->DatabaseClosed(kOriginId, kName); + EXPECT_FALSE(file_util::PathExists(spoof_db_file)); + EXPECT_TRUE(tracker->GetFullDBFilePath(kOriginId, kName).empty()); + + // -------------------------------------------------------- + // Create another record of a database in the tracker db and create + // a spoof_db_file on disk in the expected location. + tracker->DatabaseOpened(kOriginId, kName, kDescription, 0, + &database_size); + base::FilePath spoof_db_file2 = tracker->GetFullDBFilePath(kOriginId, kName); + EXPECT_FALSE(tracker->GetFullDBFilePath(kOriginId, kName).empty()); + EXPECT_NE(spoof_db_file, spoof_db_file2); + EXPECT_TRUE(file_util::CreateDirectory(spoof_db_file2.DirName())); + EXPECT_TRUE(EnsureFileOfSize(spoof_db_file2, 1)); + + // Verify that with no connection open, the db is deleted immediately. + tracker->DatabaseClosed(kOriginId, kName); + tracker->HandleSqliteError(kOriginId, kName, SQLITE_CORRUPT); + EXPECT_FALSE(tracker->IsDatabaseScheduledForDeletion(kOriginId, kName)); + EXPECT_FALSE(observer.DidReceiveNewNotification()); + EXPECT_TRUE(tracker->GetFullDBFilePath(kOriginId, kName).empty()); + EXPECT_FALSE(file_util::PathExists(spoof_db_file2)); + + tracker->RemoveObserver(&observer); + } +}; + +TEST(DatabaseTrackerTest, DeleteOpenDatabase) { + DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(false); +} + +TEST(DatabaseTrackerTest, DeleteOpenDatabaseIncognitoMode) { + DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(true); +} + +TEST(DatabaseTrackerTest, DatabaseTracker) { + DatabaseTracker_TestHelper_Test::TestDatabaseTracker(false); +} + +TEST(DatabaseTrackerTest, DatabaseTrackerIncognitoMode) { + DatabaseTracker_TestHelper_Test::TestDatabaseTracker(true); +} + +TEST(DatabaseTrackerTest, DatabaseTrackerQuotaIntegration) { + // There is no difference in behavior between incognito and not. + DatabaseTracker_TestHelper_Test::DatabaseTrackerQuotaIntegration(); +} + +TEST(DatabaseTrackerTest, DatabaseTrackerClearSessionOnlyDatabasesOnExit) { + // Only works for regular mode. + DatabaseTracker_TestHelper_Test:: + DatabaseTrackerClearSessionOnlyDatabasesOnExit(); +} + +TEST(DatabaseTrackerTest, DatabaseTrackerSetForceKeepSessionState) { + // Only works for regular mode. + DatabaseTracker_TestHelper_Test::DatabaseTrackerSetForceKeepSessionState(); +} + +TEST(DatabaseTrackerTest, EmptyDatabaseNameIsValid) { + DatabaseTracker_TestHelper_Test::EmptyDatabaseNameIsValid(); +} + +TEST(DatabaseTrackerTest, HandleSqliteError) { + DatabaseTracker_TestHelper_Test::HandleSqliteError(); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/database_util.cc b/webkit/browser/database/database_util.cc new file mode 100644 index 0000000..6c98d4e --- /dev/null +++ b/webkit/browser/database/database_util.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/database/database_util.h" + +#include "base/basictypes.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" +#include "webkit/browser/database/database_tracker.h" +#include "webkit/browser/database/vfs_backend.h" + +namespace webkit_database { + +const char DatabaseUtil::kJournalFileSuffix[] = "-journal"; + +bool DatabaseUtil::CrackVfsFileName(const base::string16& vfs_file_name, + base::string16* origin_identifier, + base::string16* database_name, + base::string16* sqlite_suffix) { + // 'vfs_file_name' is of the form <origin_identifier>/<db_name>#<suffix>. + // <suffix> is optional. + DCHECK(!vfs_file_name.empty()); + size_t first_slash_index = vfs_file_name.find('/'); + size_t last_pound_index = vfs_file_name.rfind('#'); + // '/' and '#' must be present in the string. Also, the string cannot start + // with a '/' (origin_identifier cannot be empty) and '/' must come before '#' + if ((first_slash_index == base::string16::npos) || + (last_pound_index == base::string16::npos) || + (first_slash_index == 0) || + (first_slash_index > last_pound_index)) { + return false; + } + + if (origin_identifier) + *origin_identifier = vfs_file_name.substr(0, first_slash_index); + if (database_name) { + *database_name = vfs_file_name.substr( + first_slash_index + 1, last_pound_index - first_slash_index - 1); + } + if (sqlite_suffix) { + *sqlite_suffix = vfs_file_name.substr( + last_pound_index + 1, vfs_file_name.length() - last_pound_index - 1); + } + return true; +} + +base::FilePath DatabaseUtil::GetFullFilePathForVfsFile( + DatabaseTracker* db_tracker, const base::string16& vfs_file_name) { + base::string16 origin_identifier; + base::string16 database_name; + base::string16 sqlite_suffix; + if (!CrackVfsFileName(vfs_file_name, &origin_identifier, + &database_name, &sqlite_suffix)) { + return base::FilePath(); // invalid vfs_file_name + } + + base::FilePath full_path = db_tracker->GetFullDBFilePath( + origin_identifier, database_name); + if (!full_path.empty() && !sqlite_suffix.empty()) { + DCHECK(full_path.Extension().empty()); + full_path = full_path.InsertBeforeExtensionASCII( + UTF16ToASCII(sqlite_suffix)); + } + // Watch out for directory traversal attempts from a compromised renderer. + if (full_path.value().find(FILE_PATH_LITERAL("..")) != + base::FilePath::StringType::npos) + return base::FilePath(); + return full_path; +} + +bool DatabaseUtil::IsValidOriginIdentifier( + const base::string16& origin_identifier) { + base::string16 dotdot = ASCIIToUTF16(".."); + char16 forbidden[] = {'\\', '/', '\0'}; + + base::string16::size_type pos = origin_identifier.find(dotdot); + if (pos == base::string16::npos) + pos = origin_identifier.find_first_of(forbidden, 0, arraysize(forbidden)); + + return pos == base::string16::npos; +} + +} // namespace webkit_database diff --git a/webkit/browser/database/database_util.h b/webkit/browser/database/database_util.h new file mode 100644 index 0000000..547e0a2 --- /dev/null +++ b/webkit/browser/database/database_util.h @@ -0,0 +1,38 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_DATABASE_DATABASE_UTIL_H_ +#define WEBKIT_BROWSER_DATABASE_DATABASE_UTIL_H_ + +#include "base/string16.h" +#include "googleurl/src/gurl.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class FilePath; +} + +namespace webkit_database { + +class DatabaseTracker; + +class WEBKIT_STORAGE_EXPORT DatabaseUtil { + public: + static const char kJournalFileSuffix[]; + + // Extract various information from a database vfs_file_name. All return + // parameters are optional. + static bool CrackVfsFileName(const base::string16& vfs_file_name, + base::string16* origin_identifier, + base::string16* database_name, + base::string16* sqlite_suffix); + static base::FilePath GetFullFilePathForVfsFile( + DatabaseTracker* db_tracker, + const base::string16& vfs_file_name); + static bool IsValidOriginIdentifier(const base::string16& origin_identifier); +}; + +} // namespace webkit_database + +#endif // WEBKIT_BROWSER_DATABASE_DATABASE_UTIL_H_ diff --git a/webkit/browser/database/database_util_unittest.cc b/webkit/browser/database/database_util_unittest.cc new file mode 100644 index 0000000..649b58a --- /dev/null +++ b/webkit/browser/database/database_util_unittest.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/string_piece.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/base/origin_url_conversions.h" +#include "webkit/browser/database/database_util.h" + +using webkit_database::DatabaseUtil; + +static void TestVfsFilePath(bool expected_result, + const char* vfs_file_name, + const char* expected_origin_identifier = "", + const char* expected_database_name = "", + const char* expected_sqlite_suffix = "") { + base::string16 origin_identifier; + base::string16 database_name; + base::string16 sqlite_suffix; + EXPECT_EQ(expected_result, + DatabaseUtil::CrackVfsFileName(ASCIIToUTF16(vfs_file_name), + &origin_identifier, + &database_name, + &sqlite_suffix)); + EXPECT_EQ(ASCIIToUTF16(expected_origin_identifier), origin_identifier); + EXPECT_EQ(ASCIIToUTF16(expected_database_name), database_name); + EXPECT_EQ(ASCIIToUTF16(expected_sqlite_suffix), sqlite_suffix); +} + +static GURL ToAndFromOriginIdentifier(const GURL origin_url) { + base::string16 id = webkit_base::GetOriginIdentifierFromURL(origin_url); + return webkit_base::GetOriginURLFromIdentifier(id); +} + +static void TestValidOriginIdentifier(bool expected_result, + const base::StringPiece id) { + EXPECT_EQ(expected_result, + DatabaseUtil::IsValidOriginIdentifier(ASCIIToUTF16(id))); +} + +namespace webkit_database { + +// Test DatabaseUtil::CrackVfsFilePath on various inputs. +TEST(DatabaseUtilTest, CrackVfsFilePathTest) { + TestVfsFilePath(true, "origin/#", "origin", "", ""); + TestVfsFilePath(true, "origin/#suffix", "origin", "", "suffix"); + TestVfsFilePath(true, "origin/db_name#", "origin", "db_name", ""); + TestVfsFilePath(true, "origin/db_name#suffix", "origin", "db_name", "suffix"); + TestVfsFilePath(false, "origindb_name#"); + TestVfsFilePath(false, "origindb_name#suffix"); + TestVfsFilePath(false, "origin/db_name"); + TestVfsFilePath(false, "origin#db_name/suffix"); + TestVfsFilePath(false, "/db_name#"); + TestVfsFilePath(false, "/db_name#suffix"); +} + +TEST(DatabaseUtilTest, OriginIdentifiers) { + const GURL kFileOrigin(GURL("file:///").GetOrigin()); + const GURL kHttpOrigin(GURL("http://bar/").GetOrigin()); + EXPECT_EQ(kFileOrigin, ToAndFromOriginIdentifier(kFileOrigin)); + EXPECT_EQ(kHttpOrigin, ToAndFromOriginIdentifier(kHttpOrigin)); +} + +TEST(DatabaseUtilTest, IsValidOriginIdentifier) { + TestValidOriginIdentifier(true, "http_bar_0"); + TestValidOriginIdentifier(true, ""); + TestValidOriginIdentifier(false, "bad..id"); + TestValidOriginIdentifier(false, "bad/id"); + TestValidOriginIdentifier(false, "bad\\id"); + TestValidOriginIdentifier(false, base::StringPiece("bad\0id", 6)); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/databases_table.cc b/webkit/browser/database/databases_table.cc new file mode 100644 index 0000000..45c76ec --- /dev/null +++ b/webkit/browser/database/databases_table.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/database/databases_table.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "sql/statement.h" + +namespace webkit_database { + +DatabaseDetails::DatabaseDetails() : estimated_size(0) { } + +DatabaseDetails::~DatabaseDetails() {} + +bool DatabasesTable::Init() { + // 'Databases' schema: + // id A unique ID assigned to each database + // origin The originto which the database belongs. This is a + // string that can be used as part of a file name + // (http_webkit.org_0, for example). + // name The database name. + // description A short description of the database. + // estimated_size The estimated size of the database. + return db_->DoesTableExist("Databases") || + (db_->Execute( + "CREATE TABLE Databases (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "origin TEXT NOT NULL, " + "name TEXT NOT NULL, " + "description TEXT NOT NULL, " + "estimated_size INTEGER NOT NULL)") && + db_->Execute( + "CREATE INDEX origin_index ON Databases (origin)") && + db_->Execute( + "CREATE UNIQUE INDEX unique_index ON Databases (origin, name)")); +} + +int64 DatabasesTable::GetDatabaseID(const base::string16& origin_identifier, + const base::string16& database_name) { + sql::Statement select_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT id FROM Databases WHERE origin = ? AND name = ?")); + select_statement.BindString16(0, origin_identifier); + select_statement.BindString16(1, database_name); + + if (select_statement.Step()) { + return select_statement.ColumnInt64(0); + } + + return -1; +} + +bool DatabasesTable::GetDatabaseDetails(const base::string16& origin_identifier, + const base::string16& database_name, + DatabaseDetails* details) { + DCHECK(details); + sql::Statement select_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT description, estimated_size FROM Databases " + "WHERE origin = ? AND name = ?")); + select_statement.BindString16(0, origin_identifier); + select_statement.BindString16(1, database_name); + + if (select_statement.Step()) { + details->origin_identifier = origin_identifier; + details->database_name = database_name; + details->description = select_statement.ColumnString16(0); + details->estimated_size = select_statement.ColumnInt64(1); + return true; + } + + return false; +} + +bool DatabasesTable::InsertDatabaseDetails(const DatabaseDetails& details) { + sql::Statement insert_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "INSERT INTO Databases (origin, name, description, " + "estimated_size) VALUES (?, ?, ?, ?)")); + insert_statement.BindString16(0, details.origin_identifier); + insert_statement.BindString16(1, details.database_name); + insert_statement.BindString16(2, details.description); + insert_statement.BindInt64(3, details.estimated_size); + + return insert_statement.Run(); +} + +bool DatabasesTable::UpdateDatabaseDetails(const DatabaseDetails& details) { + sql::Statement update_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "UPDATE Databases SET description = ?, " + "estimated_size = ? WHERE origin = ? AND name = ?")); + update_statement.BindString16(0, details.description); + update_statement.BindInt64(1, details.estimated_size); + update_statement.BindString16(2, details.origin_identifier); + update_statement.BindString16(3, details.database_name); + + return (update_statement.Run() && db_->GetLastChangeCount()); +} + +bool DatabasesTable::DeleteDatabaseDetails( + const base::string16& origin_identifier, + const base::string16& database_name) { + sql::Statement delete_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ? AND name = ?")); + delete_statement.BindString16(0, origin_identifier); + delete_statement.BindString16(1, database_name); + + return (delete_statement.Run() && db_->GetLastChangeCount()); +} + +bool DatabasesTable::GetAllOrigins(std::vector<base::string16>* origins) { + sql::Statement statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT DISTINCT origin FROM Databases ORDER BY origin")); + + while (statement.Step()) + origins->push_back(statement.ColumnString16(0)); + + return statement.Succeeded(); +} + +bool DatabasesTable::GetAllDatabaseDetailsForOrigin( + const base::string16& origin_identifier, + std::vector<DatabaseDetails>* details_vector) { + sql::Statement statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT name, description, estimated_size " + "FROM Databases WHERE origin = ? ORDER BY name")); + statement.BindString16(0, origin_identifier); + + while (statement.Step()) { + DatabaseDetails details; + details.origin_identifier = origin_identifier; + details.database_name = statement.ColumnString16(0); + details.description = statement.ColumnString16(1); + details.estimated_size = statement.ColumnInt64(2); + details_vector->push_back(details); + } + + return statement.Succeeded(); +} + +bool DatabasesTable::DeleteOrigin(const base::string16& origin_identifier) { + sql::Statement delete_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ?")); + delete_statement.BindString16(0, origin_identifier); + + return (delete_statement.Run() && db_->GetLastChangeCount()); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/databases_table.h b/webkit/browser/database/databases_table.h new file mode 100644 index 0000000..6fa4c08 --- /dev/null +++ b/webkit/browser/database/databases_table.h @@ -0,0 +1,53 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_DATABASE_DATABASES_TABLE_H_ +#define WEBKIT_BROWSER_DATABASE_DATABASES_TABLE_H_ + +#include <vector> + +#include "base/string16.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace sql { +class Connection; +} + +namespace webkit_database { + +struct WEBKIT_STORAGE_EXPORT_PRIVATE DatabaseDetails { + DatabaseDetails(); + ~DatabaseDetails(); + + base::string16 origin_identifier; + base::string16 database_name; + base::string16 description; + int64 estimated_size; +}; + +class WEBKIT_STORAGE_EXPORT_PRIVATE DatabasesTable { + public: + explicit DatabasesTable(sql::Connection* db) : db_(db) { } + + bool Init(); + int64 GetDatabaseID(const base::string16& origin_identifier, + const base::string16& database_name); + bool GetDatabaseDetails(const base::string16& origin_identifier, + const base::string16& database_name, + DatabaseDetails* details); + bool InsertDatabaseDetails(const DatabaseDetails& details); + bool UpdateDatabaseDetails(const DatabaseDetails& details); + bool DeleteDatabaseDetails(const base::string16& origin_identifier, + const base::string16& database_name); + bool GetAllOrigins(std::vector<base::string16>* origins); + bool GetAllDatabaseDetailsForOrigin(const base::string16& origin_identifier, + std::vector<DatabaseDetails>* details); + bool DeleteOrigin(const base::string16& origin_identifier); + private: + sql::Connection* db_; +}; + +} // namespace webkit_database + +#endif // WEBKIT_BROWSER_DATABASE_DATABASES_TABLE_H_ diff --git a/webkit/browser/database/databases_table_unittest.cc b/webkit/browser/database/databases_table_unittest.cc new file mode 100644 index 0000000..30a7975 --- /dev/null +++ b/webkit/browser/database/databases_table_unittest.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/database/databases_table.h" + +namespace { + +class TestErrorDelegate : public sql::ErrorDelegate { + public: + TestErrorDelegate() {} + virtual ~TestErrorDelegate() {} + + virtual int OnError(int error, + sql::Connection* connection, + sql::Statement* stmt) OVERRIDE { + return error; + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestErrorDelegate); +}; + +} // namespace + +namespace webkit_database { + +static void CheckDetailsAreEqual(const DatabaseDetails& d1, + const DatabaseDetails& d2) { + EXPECT_EQ(d1.origin_identifier, d2.origin_identifier); + EXPECT_EQ(d1.database_name, d2.database_name); + EXPECT_EQ(d1.description, d2.description); + EXPECT_EQ(d1.estimated_size, d2.estimated_size); +} + +static bool DatabasesTableIsEmpty(sql::Connection* db) { + sql::Statement statement(db->GetCachedStatement( + SQL_FROM_HERE, "SELECT COUNT(*) FROM Databases")); + return (statement.is_valid() && statement.Step() && !statement.ColumnInt(0)); +} + +TEST(DatabasesTableTest, TestIt) { + // Initialize the 'Databases' table. + sql::Connection db; + + // Set an error delegate that will make all operations return false on error. + db.set_error_delegate(new TestErrorDelegate()); + + // Initialize the temp dir and the 'Databases' table. + EXPECT_TRUE(db.OpenInMemory()); + DatabasesTable databases_table(&db); + EXPECT_TRUE(databases_table.Init()); + + // The 'Databases' table should be empty. + EXPECT_TRUE(DatabasesTableIsEmpty(&db)); + + // Create the details for a databases. + DatabaseDetails details_in1; + DatabaseDetails details_out1; + details_in1.origin_identifier = ASCIIToUTF16("origin1"); + details_in1.database_name = ASCIIToUTF16("db1"); + details_in1.description = ASCIIToUTF16("description_db1"); + details_in1.estimated_size = 100; + + // Updating details for this database should fail. + EXPECT_FALSE(databases_table.UpdateDatabaseDetails(details_in1)); + EXPECT_FALSE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, + details_in1.database_name, + &details_out1)); + + // Inserting details for this database should pass. + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in1)); + EXPECT_TRUE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, + details_in1.database_name, + &details_out1)); + EXPECT_EQ(1, databases_table.GetDatabaseID(details_in1.origin_identifier, + details_in1.database_name)); + + // Check that the details were correctly written to the database. + CheckDetailsAreEqual(details_in1, details_out1); + + // Check that inserting a duplicate row fails. + EXPECT_FALSE(databases_table.InsertDatabaseDetails(details_in1)); + + // Insert details for another database with the same origin. + DatabaseDetails details_in2; + details_in2.origin_identifier = ASCIIToUTF16("origin1"); + details_in2.database_name = ASCIIToUTF16("db2"); + details_in2.description = ASCIIToUTF16("description_db2"); + details_in2.estimated_size = 200; + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in2)); + EXPECT_EQ(2, databases_table.GetDatabaseID(details_in2.origin_identifier, + details_in2.database_name)); + + // Insert details for a third database with a different origin. + DatabaseDetails details_in3; + details_in3.origin_identifier = ASCIIToUTF16("origin2"); + details_in3.database_name = ASCIIToUTF16("db3"); + details_in3.description = ASCIIToUTF16("description_db3"); + details_in3.estimated_size = 300; + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in3)); + EXPECT_EQ(3, databases_table.GetDatabaseID(details_in3.origin_identifier, + details_in3.database_name)); + + // There should be no database with origin "origin3". + std::vector<DatabaseDetails> details_out_origin3; + EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOrigin( + ASCIIToUTF16("origin3"), &details_out_origin3)); + EXPECT_TRUE(details_out_origin3.empty()); + + // There should be only two databases with origin "origin1". + std::vector<DatabaseDetails> details_out_origin1; + EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOrigin( + details_in1.origin_identifier, &details_out_origin1)); + EXPECT_EQ(size_t(2), details_out_origin1.size()); + CheckDetailsAreEqual(details_in1, details_out_origin1[0]); + CheckDetailsAreEqual(details_in2, details_out_origin1[1]); + + // Get the list of all origins: should be "origin1" and "origin2". + std::vector<base::string16> origins_out; + EXPECT_TRUE(databases_table.GetAllOrigins(&origins_out)); + EXPECT_EQ(size_t(2), origins_out.size()); + EXPECT_EQ(details_in1.origin_identifier, origins_out[0]); + EXPECT_EQ(details_in3.origin_identifier, origins_out[1]); + + // Delete an origin and check that it's no longer in the table. + origins_out.clear(); + EXPECT_TRUE(databases_table.DeleteOrigin(details_in3.origin_identifier)); + EXPECT_TRUE(databases_table.GetAllOrigins(&origins_out)); + EXPECT_EQ(size_t(1), origins_out.size()); + EXPECT_EQ(details_in1.origin_identifier, origins_out[0]); + + // Deleting an origin that doesn't have any record in this table should fail. + EXPECT_FALSE(databases_table.DeleteOrigin(ASCIIToUTF16("unknown_origin"))); + + // Delete the details for 'db1' and check that they're no longer there. + EXPECT_TRUE(databases_table.DeleteDatabaseDetails( + details_in1.origin_identifier, details_in1.database_name)); + EXPECT_FALSE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, + details_in1.database_name, + &details_out1)); + + // Check that trying to delete a record that doesn't exist fails. + EXPECT_FALSE(databases_table.DeleteDatabaseDetails( + ASCIIToUTF16("unknown_origin"), ASCIIToUTF16("unknown_database"))); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/vfs_backend.cc b/webkit/browser/database/vfs_backend.cc new file mode 100644 index 0000000..041c44b --- /dev/null +++ b/webkit/browser/database/vfs_backend.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/database/vfs_backend.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "third_party/sqlite/sqlite3.h" + +namespace webkit_database { + +static const int kFileTypeMask = 0x00007F00; + +// static +bool VfsBackend::OpenTypeIsReadWrite(int desired_flags) { + return (desired_flags & SQLITE_OPEN_READWRITE) != 0; +} + +// static +bool VfsBackend::OpenFileFlagsAreConsistent(int desired_flags) { + const int file_type = desired_flags & kFileTypeMask; + const bool is_exclusive = (desired_flags & SQLITE_OPEN_EXCLUSIVE) != 0; + const bool is_delete = (desired_flags & SQLITE_OPEN_DELETEONCLOSE) != 0; + const bool is_create = (desired_flags & SQLITE_OPEN_CREATE) != 0; + const bool is_read_only = (desired_flags & SQLITE_OPEN_READONLY) != 0; + const bool is_read_write = (desired_flags & SQLITE_OPEN_READWRITE) != 0; + + // All files should be opened either read-write or read-only, but not both. + if (is_read_only == is_read_write) + return false; + + // If a new file is created, it must also be writable. + if (is_create && !is_read_write) + return false; + + // If we're accessing an existing file, we cannot give exclusive access, and + // we can't delete it. + // Normally, we'd also check that 'is_delete' is false for a main DB, main + // journal or master journal file; however, when in incognito mode, we use + // the SQLITE_OPEN_DELETEONCLOSE flag when opening those files too and keep + // an open handle to them for as long as the incognito profile is around. + if ((is_exclusive || is_delete) && !is_create) + return false; + + // Make sure we're opening the DB directory or that a file type is set. + return (file_type == SQLITE_OPEN_MAIN_DB) || + (file_type == SQLITE_OPEN_TEMP_DB) || + (file_type == SQLITE_OPEN_MAIN_JOURNAL) || + (file_type == SQLITE_OPEN_TEMP_JOURNAL) || + (file_type == SQLITE_OPEN_SUBJOURNAL) || + (file_type == SQLITE_OPEN_MASTER_JOURNAL) || + (file_type == SQLITE_OPEN_TRANSIENT_DB); +} + +// static +void VfsBackend::OpenFile(const base::FilePath& file_path, + int desired_flags, + base::PlatformFile* file_handle) { + DCHECK(!file_path.empty()); + + // Verify the flags for consistency and create the database + // directory if it doesn't exist. + if (!OpenFileFlagsAreConsistent(desired_flags) || + !file_util::CreateDirectory(file_path.DirName())) + return; + + int flags = 0; + flags |= base::PLATFORM_FILE_READ; + if (desired_flags & SQLITE_OPEN_READWRITE) + flags |= base::PLATFORM_FILE_WRITE; + + if (!(desired_flags & SQLITE_OPEN_MAIN_DB)) { + flags |= base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_EXCLUSIVE_WRITE; + } + + flags |= ((desired_flags & SQLITE_OPEN_CREATE) ? + base::PLATFORM_FILE_OPEN_ALWAYS : base::PLATFORM_FILE_OPEN); + + if (desired_flags & SQLITE_OPEN_EXCLUSIVE) { + flags |= base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_EXCLUSIVE_WRITE; + } + + if (desired_flags & SQLITE_OPEN_DELETEONCLOSE) { + flags |= base::PLATFORM_FILE_TEMPORARY | base::PLATFORM_FILE_HIDDEN | + base::PLATFORM_FILE_DELETE_ON_CLOSE; + } + + // This flag will allow us to delete the file later on from the browser + // process. + flags |= base::PLATFORM_FILE_SHARE_DELETE; + + // Try to open/create the DB file. + *file_handle = + base::CreatePlatformFile(file_path, flags, NULL, NULL); +} + +// static +void VfsBackend::OpenTempFileInDirectory( + const base::FilePath& dir_path, + int desired_flags, + base::PlatformFile* file_handle) { + // We should be able to delete temp files when they're closed + // and create them as needed + if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE) || + !(desired_flags & SQLITE_OPEN_CREATE)) { + return; + } + + // Get a unique temp file name in the database directory. + base::FilePath temp_file_path; + if (!file_util::CreateTemporaryFileInDir(dir_path, &temp_file_path)) + return; + + OpenFile(temp_file_path, desired_flags, file_handle); +} + +// static +int VfsBackend::DeleteFile(const base::FilePath& file_path, bool sync_dir) { + if (!file_util::PathExists(file_path)) + return SQLITE_OK; + if (!file_util::Delete(file_path, false)) + return SQLITE_IOERR_DELETE; + + int error_code = SQLITE_OK; +#if defined(OS_POSIX) + if (sync_dir) { + base::PlatformFile dir_fd = base::CreatePlatformFile( + file_path.DirName(), base::PLATFORM_FILE_READ, NULL, NULL); + if (dir_fd == base::kInvalidPlatformFileValue) { + error_code = SQLITE_CANTOPEN; + } else { + if (fsync(dir_fd)) + error_code = SQLITE_IOERR_DIR_FSYNC; + base::ClosePlatformFile(dir_fd); + } + } +#endif + return error_code; +} + +// static +uint32 VfsBackend::GetFileAttributes(const base::FilePath& file_path) { +#if defined(OS_WIN) + uint32 attributes = ::GetFileAttributes(file_path.value().c_str()); +#elif defined(OS_POSIX) + uint32 attributes = 0; + if (!access(file_path.value().c_str(), R_OK)) + attributes |= static_cast<uint32>(R_OK); + if (!access(file_path.value().c_str(), W_OK)) + attributes |= static_cast<uint32>(W_OK); + if (!attributes) + attributes = -1; +#endif + return attributes; +} + +// static +int64 VfsBackend::GetFileSize(const base::FilePath& file_path) { + int64 size = 0; + return (file_util::GetFileSize(file_path, &size) ? size : 0); +} + +} // namespace webkit_database diff --git a/webkit/browser/database/vfs_backend.h b/webkit/browser/database/vfs_backend.h new file mode 100644 index 0000000..e5d3374 --- /dev/null +++ b/webkit/browser/database/vfs_backend.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_DATABASE_VFS_BACKEND_H_ +#define WEBKIT_BROWSER_DATABASE_VFS_BACKEND_H_ + +#include "base/platform_file.h" +#include "base/process.h" +#include "base/string16.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class FilePath; +} + +namespace webkit_database { + +class WEBKIT_STORAGE_EXPORT VfsBackend { + public: + static void OpenFile(const base::FilePath& file_path, + int desired_flags, + base::PlatformFile* file_handle); + + static void OpenTempFileInDirectory(const base::FilePath& dir_path, + int desired_flags, + base::PlatformFile* file_handle); + + static int DeleteFile(const base::FilePath& file_path, bool sync_dir); + + static uint32 GetFileAttributes(const base::FilePath& file_path); + + static int64 GetFileSize(const base::FilePath& file_path); + + // Used to make decisions in the DatabaseDispatcherHost. + static bool OpenTypeIsReadWrite(int desired_flags); + + private: + static bool OpenFileFlagsAreConsistent(int desired_flags); +}; + +} // namespace webkit_database + +#endif // WEBKIT_BROWSER_DATABASE_VFS_BACKEND_H_ diff --git a/webkit/browser/database/webkit_browser_database.gypi b/webkit/browser/database/webkit_browser_database.gypi new file mode 100644 index 0000000..0e4e9ad --- /dev/null +++ b/webkit/browser/database/webkit_browser_database.gypi @@ -0,0 +1,20 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'webkit_browser_database_sources': [ + '../browser/database/databases_table.cc', + '../browser/database/databases_table.h', + '../browser/database/database_quota_client.cc', + '../browser/database/database_quota_client.h', + '../browser/database/database_tracker.cc', + '../browser/database/database_tracker.h', + '../browser/database/database_util.cc', + '../browser/database/database_util.h', + '../browser/database/vfs_backend.cc', + '../browser/database/vfs_backend.h', + ], + }, +} |