summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-11 02:17:51 +0000
committerpaulg@google.com <paulg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-11 02:17:51 +0000
commitc3ff8949df1905e2c0bffe32527a4b11de212ccc (patch)
treeffb88432b79882716a8779aee7a15efec6f3c65e
parent95218fbcd0b0e804175ada19cd8f95f5818242ba (diff)
downloadchromium_src-c3ff8949df1905e2c0bffe32527a4b11de212ccc.zip
chromium_src-c3ff8949df1905e2c0bffe32527a4b11de212ccc.tar.gz
chromium_src-c3ff8949df1905e2c0bffe32527a4b11de212ccc.tar.bz2
Add support for 256 bit full hashes to the new implementation.
This CL replaces the following, which seems to have become inaccessible: http://codereview.chromium.org/9202/ Review URL: http://codereview.chromium.org/10402 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5159 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/safe_browsing/protocol_manager.cc13
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database.h32
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_bloom.cc699
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_bloom.h104
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_impl.cc25
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_impl.h21
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_impl_unittest.cc230
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_database_unittest.cc489
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.cc18
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.h15
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_util.cc3
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_util.h4
-rw-r--r--chrome/chrome.xcodeproj/project.pbxproj2
-rw-r--r--chrome/test/unit/unit_tests.scons1
-rw-r--r--chrome/test/unit/unittests.vcproj4
15 files changed, 1045 insertions, 615 deletions
diff --git a/chrome/browser/safe_browsing/protocol_manager.cc b/chrome/browser/safe_browsing/protocol_manager.cc
index 073dc2e..1e760c6 100644
--- a/chrome/browser/safe_browsing/protocol_manager.cc
+++ b/chrome/browser/safe_browsing/protocol_manager.cc
@@ -272,6 +272,7 @@ bool SafeBrowsingProtocolManager::HandleServiceResponse(const GURL& url,
}
last_update_ = Time::Now();
+ sb_service_->UpdateStarted();
if (update_state_ == FIRST_REQUEST)
update_state_ = SECOND_REQUEST;
@@ -477,22 +478,22 @@ void SafeBrowsingProtocolManager::OnGetChunksComplete(
bool found_phishing = false;
for (size_t i = 0; i < lists.size(); ++i) {
list_data.append(FormatList(lists[i], use_mac));
- if (lists[i].name == "goog-phish-shavar")
+ if (lists[i].name == safe_browsing_util::kPhishingList)
found_phishing = true;
- if (lists[i].name == "goog-malware-shavar")
+ if (lists[i].name == safe_browsing_util::kMalwareList)
found_malware = true;
}
// If we have an empty database, let the server know we want data for these
// lists.
if (!found_phishing)
- list_data.append(FormatList(SBListChunkRanges("goog-phish-shavar"),
- use_mac));
+ list_data.append(FormatList(
+ SBListChunkRanges(safe_browsing_util::kPhishingList), use_mac));
if (!found_malware)
- list_data.append(FormatList(SBListChunkRanges("goog-malware-shavar"),
- use_mac));
+ list_data.append(FormatList(
+ SBListChunkRanges(safe_browsing_util::kMalwareList), use_mac));
std::string url = StringPrintf(kSbUpdateUrl,
kSbClientName,
diff --git a/chrome/browser/safe_browsing/safe_browsing_database.h b/chrome/browser/safe_browsing/safe_browsing_database.h
index f4c692a..9e07297 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database.h
+++ b/chrome/browser/safe_browsing/safe_browsing_database.h
@@ -6,9 +6,11 @@
#define CHROME_BROWSER_SAFE_BROWSING_SAFE_BROWSING_DATABASE_H_
#include <deque>
+#include <list>
#include <string>
#include <vector>
+#include "base/hash_tables.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/task.h"
@@ -80,7 +82,10 @@ class SafeBrowsingDatabase {
// Called when the user's machine has resumed from a lower power state.
virtual void HandleResume() = 0;
- virtual void UpdateFinished(bool update_succeeded) { }
+ virtual void UpdateStarted() {}
+ virtual void UpdateFinished(bool update_succeeded) {}
+
+ virtual std::wstring filename() const { return filename_; }
protected:
static std::wstring BloomFilterFilename(const std::wstring& db_filename);
@@ -98,8 +103,31 @@ class SafeBrowsingDatabase {
virtual void BuildBloomFilter() = 0;
// Measuring false positive rate. Call this each time we look in the filter.
- virtual void IncrementBloomFilterReadCount() {};
+ virtual void IncrementBloomFilterReadCount() {}
+
+ // Full hash cache support.
+ friend class SafeBrowsingDatabase_HashCaching_Test;
+
+ typedef struct HashCacheEntry {
+ SBFullHash full_hash;
+ int list_id;
+ int add_chunk_id;
+ int sub_chunk_id;
+ base::Time received;
+ } HashCacheEntry;
+
+ typedef std::list<HashCacheEntry> HashList;
+ typedef base::hash_map<SBPrefix, HashList> HashCache;
+
+ scoped_ptr<HashCache> hash_cache_;
+ HashCache* hash_cache() { return hash_cache_.get(); }
+
+ // Cache of prefixes that returned empty results (no full hash match).
+ typedef std::set<SBPrefix> PrefixCache;
+ PrefixCache prefix_miss_cache_;
+ PrefixCache* prefix_miss_cache() { return &prefix_miss_cache_; }
+ std::wstring filename_;
std::wstring bloom_filter_filename_;
scoped_refptr<BloomFilter> bloom_filter_;
};
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_bloom.cc b/chrome/browser/safe_browsing/safe_browsing_database_bloom.cc
index 041e1bf..e20fa87 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database_bloom.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_database_bloom.cc
@@ -10,6 +10,7 @@
#include "base/message_loop.h"
#include "base/platform_thread.h"
#include "base/sha2.h"
+#include "base/stats_counters.h"
#include "base/string_util.h"
#include "chrome/browser/safe_browsing/bloom_filter.h"
#include "chrome/browser/safe_browsing/chunk_range.h"
@@ -101,6 +102,8 @@ bool SafeBrowsingDatabaseBloom::Open() {
statement_cache_.reset(new SqliteStatementCache(db_));
+ hash_cache_.reset(new HashCache);
+
return true;
}
@@ -127,7 +130,7 @@ bool SafeBrowsingDatabaseBloom::CreateTables() {
return false;
}
- // Store 32 sub prefixes here.
+ // Store 32 bit sub prefixes here.
if (sqlite3_exec(db_, "CREATE TABLE sub_prefix ("
"chunk INTEGER,"
"add_chunk INTEGER,"
@@ -136,55 +139,22 @@ bool SafeBrowsingDatabaseBloom::CreateTables() {
return false;
}
- // TODO(paulg): Store 256 bit add full prefixes and GetHash results here.
- // 'receive_time' is used for testing the staleness of GetHash results.
- // if (sqlite3_exec(db_, "CREATE TABLE full_add_prefix ("
- // "chunk INTEGER,"
- // "prefix INTEGER,"
- // "receive_time INTEGER,"
- // "full_prefix BLOB)",
- // NULL, NULL, NULL) != SQLITE_OK) {
- // return false;
- // }
-
- // TODO(paulg): These tables are going to contain very few entries, so we
- // need to measure if it's worth keeping an index on them.
- // sqlite3_exec(db_,
- // "CREATE INDEX full_add_prefix_chunk ON full_prefix(chunk)",
- // NULL, NULL, NULL);
- // sqlite3_exec(db_,
- // "CREATE INDEX full_add_prefix_prefix ON full_prefix(prefix)",
- // NULL, NULL, NULL);
-
- // TODO(paulg): Store 256 bit sub full prefixes here.
- // if (sqlite3_exec(db_, "CREATE TABLE full_sub_prefix ("
- // "chunk INTEGER,"
- // "add_chunk INTEGER,"
- // "prefix INTEGER,"
- // "receive_time INTEGER,"
- // "full_prefix BLOB)",
- // NULL, NULL, NULL) != SQLITE_OK) {
- // return false;
- // }
-
- // TODO(paulg): These tables are going to contain very few entries, so we
- // need to measure if it's worth keeping an index on them.
- // sqlite3_exec(
- // db_,
- // "CREATE INDEX full_sub_prefix_chunk ON full_sub_prefix(chunk)",
- // NULL, NULL, NULL);
- // sqlite3_exec(
- // db_,
- // "CREATE INDEX full_sub_prefix_add_chunk ON full_sub_prefix(add_chunk)",
- // NULL, NULL, NULL);
- // sqlite3_exec(
- // db_,
- // "CREATE INDEX full_sub_prefix_prefix ON full_sub_prefix(prefix)",
- // NULL, NULL, NULL);
-
- if (sqlite3_exec(db_, "CREATE TABLE list_names ("
- "id INTEGER PRIMARY KEY AUTOINCREMENT,"
- "name TEXT)",
+ // Store 256 bit add full hashes (and GetHash results) here.
+ if (sqlite3_exec(db_, "CREATE TABLE add_full_hash ("
+ "chunk INTEGER,"
+ "prefix INTEGER,"
+ "receive_time INTEGER,"
+ "full_hash BLOB)",
+ NULL, NULL, NULL) != SQLITE_OK) {
+ return false;
+ }
+
+ // Store 256 bit sub full hashes here.
+ if (sqlite3_exec(db_, "CREATE TABLE sub_full_hash ("
+ "chunk INTEGER,"
+ "add_chunk INTEGER,"
+ "prefix INTEGER,"
+ "full_hash BLOB)",
NULL, NULL, NULL) != SQLITE_OK) {
return false;
}
@@ -233,9 +203,11 @@ bool SafeBrowsingDatabaseBloom::CreateTables() {
// database thread. Any URLs that the service needs to check when this is
// running are queued up and run once the reset is done.
bool SafeBrowsingDatabaseBloom::ResetDatabase() {
- hash_cache_.clear();
+ hash_cache_->clear();
ClearUpdateCaches();
+ insert_transaction_.reset();
+
bool rv = Close();
DCHECK(rv);
@@ -256,7 +228,7 @@ bool SafeBrowsingDatabaseBloom::ResetDatabase() {
bool SafeBrowsingDatabaseBloom::CheckCompatibleVersion() {
SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
- "PRAGMA user_version");
+ "PRAGMA user_version");
if (!statement.is_valid()) {
NOTREACHED();
return false;
@@ -300,10 +272,11 @@ bool SafeBrowsingDatabaseBloom::ContainsUrl(
std::vector<std::string> paths;
safe_browsing_util::GeneratePathsToCheck(url, &paths);
- // Grab a reference to the existing filter so that it isn't deleted on us if
- // a update is just about to finish.
- scoped_refptr<BloomFilter> filter = bloom_filter_;
- if (!filter.get())
+ // Lock the bloom filter and cache so that they aren't deleted on us if an
+ // update is just about to finish.
+ AutoLock lock(lookup_lock_);
+
+ if (!bloom_filter_.get())
return false;
// TODO(erikkay): This may wind up being too many hashes on a complex page.
@@ -317,7 +290,7 @@ bool SafeBrowsingDatabaseBloom::ContainsUrl(
sizeof(SBFullHash));
SBPrefix prefix;
memcpy(&prefix, &full_hash, sizeof(SBPrefix));
- if (filter->Exists(prefix))
+ if (bloom_filter_->Exists(prefix))
prefix_hits->push_back(prefix);
}
}
@@ -365,7 +338,7 @@ void SafeBrowsingDatabaseBloom::InsertChunks(const std::string& list_name,
if (chunks->empty())
return;
- int list_id = GetListID(list_name);
+ int list_id = GetListId(list_name);
ChunkType chunk_type = chunks->front().is_add ? ADD_CHUNK : SUB_CHUNK;
while (!chunks->empty()) {
@@ -383,9 +356,9 @@ void SafeBrowsingDatabaseBloom::InsertChunks(const std::string& list_name,
entry->set_list_id(list_id);
if (chunk_type == ADD_CHUNK) {
entry->set_chunk_id(chunk_id);
- AddEntry(host, entry);
+ InsertAdd(host, entry);
} else {
- AddSub(chunk_id, host, entry);
+ InsertSub(chunk_id, host, entry);
}
entry->Destroy();
@@ -408,36 +381,59 @@ void SafeBrowsingDatabaseBloom::InsertChunks(const std::string& list_name,
chunk_inserted_callback_->Run();
}
+void SafeBrowsingDatabaseBloom::UpdateStarted() {
+ DCHECK(insert_transaction_.get() == NULL);
+ insert_transaction_.reset(new SQLTransaction(db_));
+ if (insert_transaction_->Begin() != SQLITE_OK) {
+ // TODO(paulg): We should abort the update, and possibly enter back off mode
+ // if we have a problem with the database.
+ DCHECK(false) << "Safe browsing database couldn't start transaction";
+ insert_transaction_.reset();
+ }
+}
+
void SafeBrowsingDatabaseBloom::UpdateFinished(bool update_succeeded) {
if (update_succeeded)
BuildBloomFilter();
+ insert_transaction_.reset();
+
// We won't need the chunk caches until the next update (which will read them
// from the database), so free their memory as they may contain thousands of
// entries.
ClearUpdateCaches();
}
-void SafeBrowsingDatabaseBloom::AddEntry(SBPrefix host, SBEntry* entry) {
+void SafeBrowsingDatabaseBloom::InsertAdd(SBPrefix host, SBEntry* entry) {
STATS_COUNTER(L"SB.HostInsert", 1);
+ int encoded = EncodeChunkId(entry->chunk_id(), entry->list_id());
+
if (entry->type() == SBEntry::ADD_FULL_HASH) {
- // TODO(erikkay, paulg): Add the full 256 bit prefix to the full_prefix
- // table AND insert its 32 bit prefix into the regular prefix table.
+ base::Time receive_time = base::Time::Now();
+ for (int i = 0; i < entry->prefix_count(); ++i) {
+ SBPrefix prefix;
+ SBFullHash full_hash = entry->FullHashAt(i);
+ memcpy(&prefix, full_hash.full_hash, sizeof(SBPrefix));
+ InsertAddPrefix(prefix, encoded);
+ InsertAddFullHash(prefix, encoded, receive_time, full_hash);
+ }
return;
}
- int encoded = EncodeChunkId(entry->chunk_id(), entry->list_id());
+
+ // This entry contains only regular (32 bit) prefixes.
int count = entry->prefix_count();
if (count == 0) {
- AddPrefix(host, encoded);
+ InsertAddPrefix(host, encoded);
} else {
for (int i = 0; i < count; i++) {
SBPrefix prefix = entry->PrefixAt(i);
- AddPrefix(prefix, encoded);
+ InsertAddPrefix(prefix, encoded);
}
}
}
-void SafeBrowsingDatabaseBloom::AddPrefix(SBPrefix prefix, int encoded_chunk) {
+void SafeBrowsingDatabaseBloom::InsertAddPrefix(SBPrefix prefix,
+ int encoded_chunk) {
STATS_COUNTER(L"SB.PrefixAdd", 1);
std::string sql = "INSERT INTO add_prefix (chunk, prefix) VALUES (?, ?)";
SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_, sql.c_str());
@@ -454,27 +450,27 @@ void SafeBrowsingDatabaseBloom::AddPrefix(SBPrefix prefix, int encoded_chunk) {
} else {
DCHECK(rv == SQLITE_DONE);
}
- bloom_filter_->Insert(prefix);
add_count_++;
}
-void SafeBrowsingDatabaseBloom::AddFullPrefix(SBPrefix prefix,
- int encoded_chunk,
- SBFullHash full_prefix) {
+void SafeBrowsingDatabaseBloom::InsertAddFullHash(SBPrefix prefix,
+ int encoded_chunk,
+ base::Time receive_time,
+ SBFullHash full_prefix) {
STATS_COUNTER(L"SB.PrefixAddFull", 1);
- std::string sql = "INSERT INTO full_add_prefix "
- "(chunk, prefix, receive_time, full_prefix) "
- "VALUES (?, ?, ?)";
+ std::string sql = "INSERT INTO add_full_hash "
+ "(chunk, prefix, receive_time, full_hash) "
+ "VALUES (?,?,?,?)";
SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_, sql.c_str());
if (!statement.is_valid()) {
NOTREACHED();
return;
}
+
statement->bind_int(0, encoded_chunk);
statement->bind_int(1, prefix);
- // TODO(paulg): Add receive_time and full_prefix.
- // statement->bind_int64(2, receive_time);
- // statement->bind_blob(3, full_prefix);
+ statement->bind_int64(2, receive_time.ToTimeT());
+ statement->bind_blob(3, full_prefix.full_hash, sizeof(SBFullHash));
int rv = statement->step();
statement->reset();
if (rv == SQLITE_CORRUPT) {
@@ -484,30 +480,40 @@ void SafeBrowsingDatabaseBloom::AddFullPrefix(SBPrefix prefix,
}
}
-void SafeBrowsingDatabaseBloom::AddSub(
+void SafeBrowsingDatabaseBloom::InsertSub(
int chunk_id, SBPrefix host, SBEntry* entry) {
STATS_COUNTER(L"SB.HostDelete", 1);
- if (entry->type() == SBEntry::SUB_FULL_HASH) {
- // TODO(erikkay)
- return;
- }
-
int encoded = EncodeChunkId(chunk_id, entry->list_id());
- int encoded_add = EncodeChunkId(entry->chunk_id(), entry->list_id());
- int count = entry->prefix_count();
- if (count == 0) {
- AddSubPrefix(host, encoded, encoded_add);
+ int encoded_add;
+
+ if (entry->type() == SBEntry::SUB_FULL_HASH) {
+ for (int i = 0; i < entry->prefix_count(); ++i) {
+ SBPrefix prefix;
+ SBFullHash full_hash = entry->FullHashAt(i);
+ memcpy(&prefix, full_hash.full_hash, sizeof(SBPrefix));
+ encoded_add = EncodeChunkId(entry->ChunkIdAtPrefix(i), entry->list_id());
+ InsertSubPrefix(prefix, encoded, encoded_add);
+ InsertSubFullHash(prefix, encoded, encoded_add, full_hash, false);
+ }
} else {
- for (int i = 0; i < count; i++) {
- SBPrefix prefix = entry->PrefixAt(i);
- AddSubPrefix(prefix, encoded, encoded_add);
+ // We have prefixes.
+ int count = entry->prefix_count();
+ if (count == 0) {
+ encoded_add = EncodeChunkId(entry->chunk_id(), entry->list_id());
+ InsertSubPrefix(host, encoded, encoded_add);
+ } else {
+ for (int i = 0; i < count; i++) {
+ SBPrefix prefix = entry->PrefixAt(i);
+ encoded_add = EncodeChunkId(entry->ChunkIdAtPrefix(i), entry->list_id());
+ InsertSubPrefix(prefix, encoded, encoded_add);
+ }
}
}
}
-void SafeBrowsingDatabaseBloom::AddSubPrefix(SBPrefix prefix,
- int encoded_chunk,
- int encoded_add_chunk) {
+void SafeBrowsingDatabaseBloom::InsertSubPrefix(SBPrefix prefix,
+ int encoded_chunk,
+ int encoded_add_chunk) {
STATS_COUNTER(L"SB.PrefixSub", 1);
std::string sql =
"INSERT INTO sub_prefix (chunk, add_chunk, prefix) VALUES (?,?,?)";
@@ -528,14 +534,20 @@ void SafeBrowsingDatabaseBloom::AddSubPrefix(SBPrefix prefix,
}
}
-void SafeBrowsingDatabaseBloom::SubFullPrefix(SBPrefix prefix,
- int encoded_chunk,
- int encoded_add_chunk,
- SBFullHash full_prefix) {
+void SafeBrowsingDatabaseBloom::InsertSubFullHash(SBPrefix prefix,
+ int encoded_chunk,
+ int encoded_add_chunk,
+ SBFullHash full_prefix,
+ bool use_temp_table) {
STATS_COUNTER(L"SB.PrefixSubFull", 1);
- std::string sql = "INSERT INTO full_sub_prefix "
- "(chunk, add_chunk, prefix, receive_time, full_prefix) "
- "VALUES (?,?,?,?)";
+ std::string sql = "INSERT INTO ";
+ if (use_temp_table) {
+ sql += "sub_full_tmp";
+ } else {
+ sql += "sub_full_hash";
+ }
+ sql += " (chunk, add_chunk, prefix, full_hash) VALUES (?,?,?,?)";
+
SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_, sql.c_str());
if (!statement.is_valid()) {
NOTREACHED();
@@ -544,9 +556,7 @@ void SafeBrowsingDatabaseBloom::SubFullPrefix(SBPrefix prefix,
statement->bind_int(0, encoded_chunk);
statement->bind_int(1, encoded_add_chunk);
statement->bind_int(2, prefix);
- // TODO(paulg): Add receive_time and full_prefix.
- // statement->bind_int64(3, receive_time);
- // statement->bind_blob(4, full_prefix);
+ statement->bind_blob(3, full_prefix.full_hash, sizeof(SBFullHash));
int rv = statement->step();
statement->reset();
if (rv == SQLITE_CORRUPT) {
@@ -556,10 +566,22 @@ void SafeBrowsingDatabaseBloom::SubFullPrefix(SBPrefix prefix,
}
}
-// TODO(paulg): Look for a less expensive way to maintain add_count_.
+void SafeBrowsingDatabaseBloom::ReadFullHash(SqliteCompiledStatement& statement,
+ int column,
+ SBFullHash* full_hash) {
+ DCHECK(full_hash);
+ std::vector<unsigned char> blob;
+ statement->column_blob_as_vector(column, &blob);
+ DCHECK(blob.size() == sizeof(SBFullHash));
+ memcpy(full_hash->full_hash, &blob[0], sizeof(SBFullHash));
+}
+
+// TODO(paulg): Look for a less expensive way to maintain add_count_? If we move
+// to a native file format, we can just cache the count in the file and not have
+// to scan at all.
int SafeBrowsingDatabaseBloom::GetAddPrefixCount() {
SQLITE_UNIQUE_STATEMENT(count, *statement_cache_,
- "SELECT count(*) FROM add_prefix");
+ "SELECT count(*) FROM add_prefix");
if (!count.is_valid()) {
NOTREACHED();
return 0;
@@ -579,7 +601,7 @@ void SafeBrowsingDatabaseBloom::DeleteChunks(
if (chunk_deletes->empty())
return;
- int list_id = GetListID(chunk_deletes->front().list_name);
+ int list_id = GetListId(chunk_deletes->front().list_name);
for (size_t i = 0; i < chunk_deletes->size(); ++i) {
const SBChunkDelete& chunk = (*chunk_deletes)[i];
@@ -640,100 +662,43 @@ void SafeBrowsingDatabaseBloom::GetChunkIds(
void SafeBrowsingDatabaseBloom::GetListsInfo(
std::vector<SBListChunkRanges>* lists) {
DCHECK(lists);
+ lists->clear();
ReadChunkNumbers();
- lists->clear();
- SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
- "SELECT name,id FROM list_names");
- if (!statement.is_valid()) {
- NOTREACHED();
- return;
- }
-
- std::vector<std::vector<int> > chunks;
- while (true) {
- int rv = statement->step();
- if (rv != SQLITE_ROW) {
- if (rv == SQLITE_CORRUPT)
- HandleCorruptDatabase();
+ lists->push_back(SBListChunkRanges(safe_browsing_util::kMalwareList));
+ GetChunkIds(MALWARE, ADD_CHUNK, &lists->back().adds);
+ GetChunkIds(MALWARE, SUB_CHUNK, &lists->back().subs);
- break;
- }
- int list_id = statement->column_int(1);
- lists->push_back(SBListChunkRanges(statement->column_string(0)));
- std::vector<int> c;
- chunks.push_back(c);
- GetChunkIds(list_id, ADD_CHUNK, &lists->back().adds);
- GetChunkIds(list_id, SUB_CHUNK, &lists->back().subs);
- }
+ lists->push_back(SBListChunkRanges(safe_browsing_util::kPhishingList));
+ GetChunkIds(PHISH, ADD_CHUNK, &lists->back().adds);
+ GetChunkIds(PHISH, SUB_CHUNK, &lists->back().subs);
return;
}
-int SafeBrowsingDatabaseBloom::AddList(const std::string& name) {
- SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
- "INSERT INTO list_names"
- "(id,name)"
- "VALUES (NULL,?)");
- if (!statement.is_valid()) {
- NOTREACHED();
- return 0;
- }
-
- statement->bind_string(0, name);
- int rv = statement->step();
- if (rv != SQLITE_DONE) {
- if (rv == SQLITE_CORRUPT) {
- HandleCorruptDatabase();
- } else {
- NOTREACHED();
- }
-
- return 0;
- }
-
- return static_cast<int>(sqlite3_last_insert_rowid(db_));
-}
-
-int SafeBrowsingDatabaseBloom::GetListID(const std::string& name) {
- SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
- "SELECT id FROM list_names WHERE name=?");
- if (!statement.is_valid()) {
- NOTREACHED();
- return 0;
- }
-
- statement->bind_string(0, name);
- int result = statement->step();
- if (result == SQLITE_ROW)
- return statement->column_int(0);
+/* static */
+int SafeBrowsingDatabaseBloom::GetListId(const std::string& name) {
+ if (name == safe_browsing_util::kMalwareList)
+ return MALWARE;
+ else if (name == safe_browsing_util::kPhishingList)
+ return PHISH;
- if (result == SQLITE_CORRUPT)
- HandleCorruptDatabase();
-
- // There isn't an existing entry so add one.
- return AddList(name);
+ NOTREACHED();
+ return -1;
}
-std::string SafeBrowsingDatabaseBloom::GetListName(int id) {
- SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
- "SELECT name FROM list_names WHERE id=?");
- if (!statement.is_valid()) {
- NOTREACHED();
- return 0;
- }
-
- statement->bind_int(0, id);
- int result = statement->step();
- if (result != SQLITE_ROW) {
- if (result == SQLITE_CORRUPT)
- HandleCorruptDatabase();
-
- return std::string();
+/* static */
+std::string SafeBrowsingDatabaseBloom::GetListName(int list_id) {
+ switch (list_id) {
+ case MALWARE:
+ return safe_browsing_util::kMalwareList;
+ case PHISH:
+ return safe_browsing_util::kPhishingList;
+ default:
+ NOTREACHED();
+ return "";
}
-
- return statement->column_string(0);
}
void SafeBrowsingDatabaseBloom::ReadChunkNumbers() {
@@ -863,11 +828,11 @@ int SafeBrowsingDatabaseBloom::PairCompare(const void* arg1, const void* arg2) {
return delta;
}
-bool SafeBrowsingDatabaseBloom::BuildAddList(SBPair* adds) {
+bool SafeBrowsingDatabaseBloom::BuildAddPrefixList(SBPair* adds) {
// Read add_prefix into memory and sort it.
STATS_COUNTER(L"SB.HostSelectForBloomFilter", 1);
SQLITE_UNIQUE_STATEMENT(add_prefix, *statement_cache_,
- "SELECT chunk, prefix FROM add_prefix");
+ "SELECT chunk, prefix FROM add_prefix");
if (!add_prefix.is_valid()) {
NOTREACHED();
return false;
@@ -894,11 +859,14 @@ bool SafeBrowsingDatabaseBloom::BuildAddList(SBPair* adds) {
return true;
}
-bool SafeBrowsingDatabaseBloom::RemoveSubs(SBPair* adds,
- std::vector<bool>* adds_removed) {
+bool SafeBrowsingDatabaseBloom::RemoveSubs(
+ SBPair* adds, std::vector<bool>* adds_removed,
+ HashCache* add_cache, HashCache* sub_cache) {
+ DCHECK(add_cache && sub_cache);
+
// Read through sub_prefix and zero out add_prefix entries that match.
SQLITE_UNIQUE_STATEMENT(sub_prefix, *statement_cache_,
- "SELECT chunk, add_chunk, prefix FROM sub_prefix");
+ "SELECT chunk, add_chunk, prefix FROM sub_prefix");
if (!sub_prefix.is_valid()) {
NOTREACHED();
return false;
@@ -916,6 +884,16 @@ bool SafeBrowsingDatabaseBloom::RemoveSubs(SBPair* adds,
return false;
}
+ // Create a temporary sub full hash table, similar to the above prefix table.
+ if (sqlite3_exec(db_, "CREATE TABLE sub_full_tmp ("
+ "chunk INTEGER,"
+ "add_chunk INTEGER,"
+ "prefix INTEGER,"
+ "full_hash BLOB)",
+ NULL, NULL, NULL) != SQLITE_OK) {
+ return false;
+ }
+
SQLITE_UNIQUE_STATEMENT(
sub_prefix_tmp,
*statement_cache_,
@@ -945,17 +923,21 @@ bool SafeBrowsingDatabaseBloom::RemoveSubs(SBPair* adds,
if (sub_del_cache_.find(sub_chunk) != sub_del_cache_.end())
continue;
- void* match = bsearch(&sub, adds, add_count_, sizeof(SBPair),
+ void* match = bsearch(&sub, adds, add_count_, sizeof(SBPair),
&SafeBrowsingDatabaseBloom::PairCompare);
if (match) {
SBPair* subbed = reinterpret_cast<SBPair*>(match);
(*adds_removed)[subbed - adds] = true;
+ // Remove any GetHash results (full hashes) that match this sub, as well
+ // as removing any full subs we may have received.
+ ClearCachedEntry(sub.prefix, sub.chunk_id, add_cache);
+ ClearCachedEntry(sub.prefix, sub.chunk_id, sub_cache);
} else {
// This sub_prefix entry did not match any add, so we keep it around.
sub_prefix_tmp->bind_int(0, sub_chunk);
sub_prefix_tmp->bind_int(1, sub.chunk_id);
sub_prefix_tmp->bind_int(2, sub.prefix);
- int rv = sub_prefix_tmp->step();
+ rv = sub_prefix_tmp->step();
if (rv == SQLITE_CORRUPT) {
HandleCorruptDatabase();
return false;
@@ -964,7 +946,7 @@ bool SafeBrowsingDatabaseBloom::RemoveSubs(SBPair* adds,
sub_prefix_tmp->reset();
}
}
-
+
return true;
}
@@ -996,8 +978,7 @@ bool SafeBrowsingDatabaseBloom::UpdateTables() {
}
DCHECK(rv == SQLITE_DONE);
- // Now blow away add_prefix and re-write it from our in-memory data,
- // building the new bloom filter as we go.
+ // Now blow away add_prefix. We will write the new values out later.
SQLITE_UNIQUE_STATEMENT(del_add, *statement_cache_, "DELETE FROM add_prefix");
if (!del_add.is_valid()) {
NOTREACHED();
@@ -1010,6 +991,48 @@ bool SafeBrowsingDatabaseBloom::UpdateTables() {
}
DCHECK(rv == SQLITE_DONE);
+ // Delete the old sub_full_hash table and rename the temp full hash table.
+ SQLITE_UNIQUE_STATEMENT(del_full_sub, *statement_cache_,
+ "DROP TABLE sub_full_hash");
+ if (!del_full_sub.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+
+ rv = del_full_sub->step();
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ DCHECK(rv == SQLITE_DONE);
+
+ SQLITE_UNIQUE_STATEMENT(rename_full_sub, *statement_cache_,
+ "ALTER TABLE sub_full_tmp RENAME TO sub_full_hash");
+ if (!rename_full_sub.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+ rv = rename_full_sub->step();
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ DCHECK(rv == SQLITE_DONE);
+
+ // Blow away all the full adds. We will write the new values out later.
+ SQLITE_UNIQUE_STATEMENT(del_full_add, *statement_cache_,
+ "DELETE FROM add_full_hash");
+ if (!del_full_add.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+ rv = del_full_add->step();
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ DCHECK(rv == SQLITE_DONE);
+
return true;
}
@@ -1064,11 +1087,158 @@ bool SafeBrowsingDatabaseBloom::WritePrefixes(
return true;
}
+void SafeBrowsingDatabaseBloom::WriteFullHashes(HashCache* hash_cache,
+ bool is_add) {
+ DCHECK(hash_cache);
+ HashCache::iterator it = hash_cache->begin();
+ for (; it != hash_cache->end(); ++it) {
+ const HashList& entries = it->second;
+ WriteFullHashList(entries, is_add);
+ }
+}
+
+void SafeBrowsingDatabaseBloom::WriteFullHashList(const HashList& hash_list,
+ bool is_add) {
+ HashList::const_iterator lit = hash_list.begin();
+ for (; lit != hash_list.end(); ++lit) {
+ const HashCacheEntry& entry = *lit;
+ SBPrefix prefix;
+ memcpy(&prefix, entry.full_hash.full_hash, sizeof(SBPrefix));
+ if (is_add) {
+ if (add_del_cache_.find(entry.add_chunk_id) == add_del_cache_.end()) {
+ InsertAddFullHash(prefix, entry.add_chunk_id,
+ entry.received, entry.full_hash);
+ }
+ } else {
+ if (sub_del_cache_.find(entry.sub_chunk_id) == sub_del_cache_.end()) {
+ InsertSubFullHash(prefix, entry.sub_chunk_id,
+ entry.add_chunk_id, entry.full_hash, true);
+ }
+ }
+ }
+}
+
+bool SafeBrowsingDatabaseBloom::BuildAddFullHashCache(HashCache* add_cache) {
+ add_cache->clear();
+
+ // Read all full add entries to the cache.
+ SQLITE_UNIQUE_STATEMENT(
+ full_add_entry,
+ *statement_cache_,
+ "SELECT chunk, prefix, receive_time, full_hash FROM add_full_hash");
+ if (!full_add_entry.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+
+ int rv;
+ while (true) {
+ rv = full_add_entry->step();
+ if (rv != SQLITE_ROW) {
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ break;
+ }
+ HashCacheEntry entry;
+ entry.add_chunk_id = full_add_entry->column_int(0);
+ if (add_del_cache_.find(entry.add_chunk_id) != add_del_cache_.end())
+ continue; // This entry's chunk was deleted so we skip it.
+ SBPrefix prefix = full_add_entry->column_int(1);
+ entry.received = base::Time::FromTimeT(full_add_entry->column_int64(2));
+ int chunk, list_id;
+ DecodeChunkId(entry.add_chunk_id, &chunk, &list_id);
+ entry.list_id = list_id;
+ ReadFullHash(full_add_entry, 3, &entry.full_hash);
+ HashList& entries = (*add_cache)[prefix];
+ entries.push_back(entry);
+ }
+
+ // Clear the full add table.
+ SQLITE_UNIQUE_STATEMENT(full_add_drop, *statement_cache_,
+ "DELETE FROM add_full_hash");
+ if (!full_add_drop.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+ rv = full_add_drop->step();
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ DCHECK(rv == SQLITE_DONE);
+
+ return true;
+}
+
+bool SafeBrowsingDatabaseBloom::BuildSubFullHashCache(HashCache* sub_cache) {
+ sub_cache->clear();
+
+ // Read all full sub entries to the cache.
+ SQLITE_UNIQUE_STATEMENT(
+ full_sub_entry,
+ *statement_cache_,
+ "SELECT chunk, add_chunk, prefix, full_hash FROM sub_full_hash");
+ if (!full_sub_entry.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+
+ int rv;
+ while (true) {
+ rv = full_sub_entry->step();
+ if (rv != SQLITE_ROW) {
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ break;
+ }
+ HashCacheEntry entry;
+ entry.sub_chunk_id = full_sub_entry->column_int(0);
+ if (sub_del_cache_.find(entry.sub_chunk_id) != sub_del_cache_.end())
+ continue; // This entry's chunk was deleted so we skip it.
+ entry.add_chunk_id = full_sub_entry->column_int(1);
+ SBPrefix prefix = full_sub_entry->column_int(2);
+ int chunk, list_id;
+ DecodeChunkId(entry.add_chunk_id, &chunk, &list_id);
+ entry.list_id = list_id;
+ ReadFullHash(full_sub_entry, 3, &entry.full_hash);
+ HashList& entries = (*sub_cache)[prefix];
+ entries.push_back(entry);
+ }
+
+ // Clear the full sub table.
+ SQLITE_UNIQUE_STATEMENT(full_sub_drop, *statement_cache_,
+ "DELETE FROM sub_full_hash");
+ if (!full_sub_drop.is_valid()) {
+ NOTREACHED();
+ return false;
+ }
+ rv = full_sub_drop->step();
+ if (rv == SQLITE_CORRUPT) {
+ HandleCorruptDatabase();
+ return false;
+ }
+ DCHECK(rv == SQLITE_DONE);
+
+ return true;
+}
+
// TODO(erikkay): should we call WaitAfterResume() inside any of the loops here?
// This is a pretty fast operation and it would be nice to let it finish.
void SafeBrowsingDatabaseBloom::BuildBloomFilter() {
Time before = Time::Now();
+ // Get all the pending GetHash results and write them to disk.
+ HashList pending_hashes;
+ {
+ AutoLock lock(lookup_lock_);
+ pending_hashes.swap(pending_full_hashes_);
+ }
+ WriteFullHashList(pending_hashes, true);
+
add_count_ = GetAddPrefixCount();
if (add_count_ == 0) {
bloom_filter_ = NULL;
@@ -1078,16 +1248,18 @@ void SafeBrowsingDatabaseBloom::BuildBloomFilter() {
scoped_array<SBPair> adds_array(new SBPair[add_count_]);
SBPair* adds = adds_array.get();
- if (!BuildAddList(adds))
+ if (!BuildAddPrefixList(adds))
return;
- // Protect the remaining operations on the database with a transaction.
- scoped_ptr<SQLTransaction> update_transaction;
- update_transaction.reset(new SQLTransaction(db_));
- if (update_transaction->Begin() != SQLITE_OK) {
- NOTREACHED();
+ // Build the full add cache, which includes full hash updates and GetHash
+ // results. Subs may remove some of these entries.
+ scoped_ptr<HashCache> add_cache(new HashCache);
+ if (!BuildAddFullHashCache(add_cache.get()))
+ return;
+
+ scoped_ptr<HashCache> sub_cache(new HashCache);
+ if (!BuildSubFullHashCache(sub_cache.get()))
return;
- }
// Used to track which adds have been subbed out. The vector<bool> is actually
// a bitvector so the size is as small as we can get.
@@ -1095,22 +1267,34 @@ void SafeBrowsingDatabaseBloom::BuildBloomFilter() {
adds_removed.resize(add_count_, false);
// Flag any add as removed if there is a matching sub.
- if (!RemoveSubs(adds, &adds_removed))
+ if (!RemoveSubs(adds, &adds_removed, add_cache.get(), sub_cache.get()))
return;
// Prepare the database for writing out our remaining add and sub prefixes.
if (!UpdateTables())
return;
- // Write out the remaining adds to the filter and database.
+ // Write out the remaining add prefixes to the filter and database.
int new_count;
BloomFilter* filter;
if (!WritePrefixes(adds, adds_removed, &new_count, &filter))
return;
- // If there were any matching subs, the size will be smaller.
- add_count_ = new_count;
- bloom_filter_ = filter;
+ // Write out the remaining full hash adds and subs to the database.
+ WriteFullHashes(add_cache.get(), true);
+ WriteFullHashes(sub_cache.get(), false);
+
+ // Save the chunk numbers we've received to the database for reporting in
+ // future update requests.
+ if (!WriteChunkNumbers())
+ return;
+
+ // Commit all the changes to the database.
+ int rv = insert_transaction_->Commit();
+ if (rv != SQLITE_OK) {
+ NOTREACHED() << "SafeBrowsing update transaction failed to commit.";
+ return;
+ }
TimeDelta bloom_gen = Time::Now() - before;
SB_DLOG(INFO) << "SafeBrowsingDatabaseImpl built bloom filter in "
@@ -1118,13 +1302,14 @@ void SafeBrowsingDatabaseBloom::BuildBloomFilter() {
<< " ms total. prefix count: "<< add_count_;
UMA_HISTOGRAM_LONG_TIMES(L"SB.BuildBloom", bloom_gen);
- // Save the chunk numbers we've received to the database for reporting in
- // future update requests.
- if (!WriteChunkNumbers())
- return;
-
- // We've made it this far without errors, so commit the changes.
- update_transaction->Commit();
+ // Swap in the newly built filter and cache. If there were any matching subs,
+ // the size (add_count_) will be smaller.
+ {
+ AutoLock lock(lookup_lock_);
+ add_count_ = new_count;
+ bloom_filter_ = filter;
+ hash_cache_.swap(add_cache);
+ }
// Persist the bloom filter to disk.
WriteBloomFilter();
@@ -1140,13 +1325,17 @@ void SafeBrowsingDatabaseBloom::GetCachedFullHashes(
for (std::vector<SBPrefix>::const_iterator it = prefix_hits->begin();
it != prefix_hits->end(); ++it) {
- HashCache::iterator hit = hash_cache_.find(*it);
- if (hit != hash_cache_.end()) {
+ HashCache::iterator hit = hash_cache_->find(*it);
+ if (hit != hash_cache_->end()) {
HashList& entries = hit->second;
HashList::iterator eit = entries.begin();
while (eit != entries.end()) {
// An entry is valid if we've received an update in the past 45 minutes,
// or if this particular GetHash was received in the past 45 minutes.
+ // If an entry is does not meet the time criteria above, we are not
+ // allowed to use it since it might have become stale. We keep it
+ // around, though, and may be able to use it in the future once we
+ // receive the next update (that doesn't sub it).
if (max_age < last_update || eit->received > max_age) {
SBFullHashResult full_hash;
memcpy(&full_hash.hash.full_hash,
@@ -1155,15 +1344,12 @@ void SafeBrowsingDatabaseBloom::GetCachedFullHashes(
full_hash.list_name = GetListName(eit->list_id);
full_hash.add_chunk_id = eit->add_chunk_id;
full_hits->push_back(full_hash);
- ++eit;
- } else {
- // Evict the expired entry.
- eit = entries.erase(eit);
}
+ ++eit;
}
if (entries.empty())
- hash_cache_.erase(hit);
+ hash_cache_->erase(hit);
}
}
}
@@ -1171,6 +1357,8 @@ void SafeBrowsingDatabaseBloom::GetCachedFullHashes(
void SafeBrowsingDatabaseBloom::CacheHashResults(
const std::vector<SBPrefix>& prefixes,
const std::vector<SBFullHashResult>& full_hits) {
+ AutoLock lock(lookup_lock_);
+
if (full_hits.empty()) {
// These prefixes returned no results, so we store them in order to prevent
// asking for them again. We flush this cache at the next update.
@@ -1185,51 +1373,44 @@ void SafeBrowsingDatabaseBloom::CacheHashResults(
for (std::vector<SBFullHashResult>::const_iterator it = full_hits.begin();
it != full_hits.end(); ++it) {
SBPrefix prefix;
- memcpy(&prefix, &it->hash.full_hash, sizeof(prefix));
- HashList& entries = hash_cache_[prefix];
+ memcpy(&prefix, &it->hash.full_hash, sizeof(SBPrefix));
+ HashList& entries = (*hash_cache_)[prefix];
HashCacheEntry entry;
entry.received = now;
- entry.list_id = GetListID(it->list_name);
- entry.add_chunk_id = it->add_chunk_id;
+ entry.list_id = GetListId(it->list_name);
+ entry.add_chunk_id = EncodeChunkId(it->add_chunk_id, entry.list_id);
memcpy(&entry.full_hash, &it->hash.full_hash, sizeof(SBFullHash));
entries.push_back(entry);
- }
-}
-void SafeBrowsingDatabaseBloom::ClearCachedHashes(const SBEntry* entry) {
- for (int i = 0; i < entry->prefix_count(); ++i) {
- SBPrefix prefix;
- if (entry->type() == SBEntry::SUB_FULL_HASH)
- memcpy(&prefix, &entry->FullHashAt(i), sizeof(SBPrefix));
- else
- prefix = entry->PrefixAt(i);
-
- HashCache::iterator it = hash_cache_.find(prefix);
- if (it != hash_cache_.end())
- hash_cache_.erase(it);
+ // Also push a copy to the pending write queue.
+ pending_full_hashes_.push_back(entry);
}
}
-// This clearing algorithm is a little inefficient, but we don't expect there to
-// be too many entries for this to matter. Also, this runs as a background task
-// during an update, so no user action is blocking on it.
-void SafeBrowsingDatabaseBloom::ClearCachedHashesForChunk(int list_id,
- int add_chunk_id) {
- HashCache::iterator it = hash_cache_.begin();
- while (it != hash_cache_.end()) {
- HashList& entries = it->second;
- HashList::iterator eit = entries.begin();
- while (eit != entries.end()) {
- if (eit->list_id == list_id && eit->add_chunk_id == add_chunk_id)
- eit = entries.erase(eit);
- else
- ++eit;
+bool SafeBrowsingDatabaseBloom::ClearCachedEntry(SBPrefix prefix,
+ int add_chunk,
+ HashCache* hash_cache) {
+ bool match = false;
+ HashCache::iterator it = hash_cache->find(prefix);
+ if (it == hash_cache->end())
+ return match;
+
+ HashList& entries = it->second;
+ HashList::iterator lit = entries.begin();
+ while (lit != entries.end()) {
+ HashCacheEntry& entry = *lit;
+ if (entry.add_chunk_id == add_chunk) {
+ lit = entries.erase(lit);
+ match = true;
+ continue;
}
- if (entries.empty())
- hash_cache_.erase(it++);
- else
- ++it;
+ ++lit;
}
+
+ if (entries.empty())
+ hash_cache->erase(it);
+
+ return match;
}
void SafeBrowsingDatabaseBloom::HandleCorruptDatabase() {
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_bloom.h b/chrome/browser/safe_browsing/safe_browsing_database_bloom.h
index 9569a43..9cebde6 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database_bloom.h
+++ b/chrome/browser/safe_browsing/safe_browsing_database_bloom.h
@@ -13,6 +13,7 @@
#include <vector>
#include "base/hash_tables.h"
+#include "base/lock.h"
#include "base/scoped_ptr.h"
#include "base/task.h"
#include "base/time.h"
@@ -57,7 +58,7 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// Returns the lists and their add/sub chunks.
virtual void GetListsInfo(std::vector<SBListChunkRanges>* lists);
- // Does nothing in this implementation. Operations in this class are
+ // Does nothing in this implementation. Operations in this class are
// always synchronous.
virtual void SetSynchronous();
@@ -71,7 +72,9 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// Called when the user's machine has resumed from a lower power state.
virtual void HandleResume();
+ virtual void UpdateStarted();
virtual void UpdateFinished(bool update_succeeded);
+
virtual bool NeedToCheckUrl(const GURL& url);
private:
@@ -107,15 +110,14 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// the given list and chunk type.
void GetChunkIds(int list_id, ChunkType type, std::string* list);
- // Adds the given list to the database. Returns its row id.
- int AddList(const std::string& name);
-
- // Given a list name, returns its internal id. If we haven't seen it before,
- // an id is created and stored in the database. On error, returns 0.
- int GetListID(const std::string& name);
-
- // Given a list id, returns its name.
- std::string GetListName(int id);
+ // Converts between the SafeBrowsing list names and their enumerated value.
+ // If the list names change, both of these methods must be updated.
+ enum ListType {
+ MALWARE = 0,
+ PHISH = 1,
+ };
+ static int GetListId(const std::string& name);
+ static std::string GetListName(int list_id);
// Generate a bloom filter.
virtual void BuildBloomFilter();
@@ -128,11 +130,19 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
static int PairCompare(const void* arg1, const void* arg2);
- bool BuildAddList(SBPair* adds);
- bool RemoveSubs(SBPair* adds, std::vector<bool>* adds_removed);
+ bool BuildAddPrefixList(SBPair* adds);
+ bool BuildAddFullHashCache(HashCache* add_cache);
+ bool BuildSubFullHashCache(HashCache* sub_cache);
+ bool RemoveSubs(SBPair* adds,
+ std::vector<bool>* adds_removed,
+ HashCache* add_cache,
+ HashCache* sub_cache);
+
bool UpdateTables();
bool WritePrefixes(SBPair* adds, const std::vector<bool>& adds_removed,
int* new_add_count, BloomFilter** filter);
+ void WriteFullHashes(HashCache* hash_cache, bool is_add);
+ void WriteFullHashList(const HashList& hash_list, bool is_add);
// Looks up any cached full hashes we may have.
void GetCachedFullHashes(const std::vector<SBPrefix>* prefix_hits,
@@ -140,10 +150,7 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
base::Time last_update);
// Remove cached entries that have prefixes contained in the entry.
- void ClearCachedHashes(const SBEntry* entry);
-
- // Remove all GetHash entries that match the list and chunk id from an AddDel.
- void ClearCachedHashesForChunk(int list_id, int add_chunk_id);
+ bool ClearCachedEntry(SBPrefix, int add_chunk_id, HashCache* hash_cache);
void HandleCorruptDatabase();
void OnHandleCorruptDatabase();
@@ -156,18 +163,32 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// flag. This method should be called periodically inside of busy disk loops.
void WaitAfterResume();
- void AddEntry(SBPrefix host, SBEntry* entry);
- void AddPrefix(SBPrefix prefix, int encoded_chunk);
- void AddSub(int chunk, SBPrefix host, SBEntry* entry);
- void AddSubPrefix(SBPrefix prefix, int encoded_chunk, int encoded_add_chunk);
+ // Adding add entries to the database.
+ void InsertAdd(SBPrefix host, SBEntry* entry);
+ void InsertAddPrefix(SBPrefix prefix, int encoded_chunk);
+ void InsertAddFullHash(SBPrefix prefix,
+ int encoded_chunk,
+ base::Time received_time,
+ SBFullHash full_prefix);
+
+ // Adding sub entries to the database.
+ void InsertSub(int chunk, SBPrefix host, SBEntry* entry);
+ void InsertSubPrefix(SBPrefix prefix,
+ int encoded_chunk,
+ int encoded_add_chunk);
+ void InsertSubFullHash(SBPrefix prefix,
+ int encoded_chunk,
+ int encoded_add_chunk,
+ SBFullHash full_prefix,
+ bool use_temp_table);
+
+ // Used for reading full hashes from the database.
+ void ReadFullHash(SqliteCompiledStatement& statement,
+ int column,
+ SBFullHash* full_hash);
+
+ // Returns the number of chunk + prefix pairs in the add prefix table.
int GetAddPrefixCount();
- void AddFullPrefix(SBPrefix prefix,
- int encoded_chunk,
- SBFullHash full_prefix);
- void SubFullPrefix(SBPrefix prefix,
- int encoded_chunk,
- int encoded_add_chunk,
- SBFullHash full_prefix);
// Reads and writes chunk numbers to and from persistent store.
void ReadChunkNumbers();
@@ -178,7 +199,6 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// Encode the list id in the lower bit of the chunk.
static inline int EncodeChunkId(int chunk, int list_id) {
- list_id--;
DCHECK(list_id == 0 || list_id == 1);
chunk = chunk << 1;
chunk |= list_id;
@@ -187,7 +207,7 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// Split an encoded chunk id and return the original chunk id and list id.
static inline void DecodeChunkId(int encoded, int* chunk, int* list_id) {
- *list_id = 1 + (encoded & 0x1);
+ *list_id = encoded & 0x1;
*chunk = encoded >> 1;
}
@@ -200,8 +220,6 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// True iff the database has been opened successfully.
bool init_;
- std::wstring filename_;
-
// Called after an add/sub chunk is processed.
scoped_ptr<Callback0::Type> chunk_inserted_callback_;
@@ -211,21 +229,6 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// Used to schedule resuming from a lower power state.
ScopedRunnableMethodFactory<SafeBrowsingDatabaseBloom> resume_factory_;
- // Used for caching GetHash results.
- typedef struct HashCacheEntry {
- SBFullHash full_hash;
- int list_id;
- int add_chunk_id;
- base::Time received;
- } HashCacheEntry;
-
- typedef std::list<HashCacheEntry> HashList;
- typedef base::hash_map<SBPrefix, HashList> HashCache;
- HashCache hash_cache_;
-
- // Cache of prefixes that returned empty results (no full hash match).
- std::set<SBPrefix> prefix_miss_cache_;
-
// Caches for all of the existing add and sub chunks.
std::set<int> add_chunk_cache_;
std::set<int> sub_chunk_cache_;
@@ -243,6 +246,15 @@ class SafeBrowsingDatabaseBloom : public SafeBrowsingDatabase {
// it's presumably going to be pretty busy.
bool did_resume_;
+ // Transaction for protecting database integrity during updates.
+ scoped_ptr<SQLTransaction> insert_transaction_;
+
+ // Lock for protecting access to the bloom filter and hash cache.
+ Lock lookup_lock_;
+
+ // A store for GetHash results that have not yet been written to the database.
+ HashList pending_full_hashes_;
+
DISALLOW_COPY_AND_ASSIGN(SafeBrowsingDatabaseBloom);
};
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_impl.cc b/chrome/browser/safe_browsing/safe_browsing_database_impl.cc
index 6414a0e..34adc83 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database_impl.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_database_impl.cc
@@ -109,6 +109,7 @@ bool SafeBrowsingDatabaseImpl::Init(const std::wstring& filename,
init_ = true;
chunk_inserted_callback_.reset(chunk_inserted_callback);
+
return true;
}
@@ -129,6 +130,8 @@ bool SafeBrowsingDatabaseImpl::Open() {
bloom_read_factory_.RevokeAll();
bloom_write_factory_.RevokeAll();
+ hash_cache_.reset(new HashCache);
+
return true;
}
@@ -216,7 +219,7 @@ bool SafeBrowsingDatabaseImpl::CreateTables() {
// The SafeBrowsing service assumes this operation is synchronous.
bool SafeBrowsingDatabaseImpl::ResetDatabase() {
- hash_cache_.clear();
+ hash_cache_->clear();
prefix_miss_cache_.clear();
bool rv = Close();
@@ -1091,8 +1094,8 @@ void SafeBrowsingDatabaseImpl::GetCachedFullHashes(
for (std::vector<SBPrefix>::const_iterator it = prefix_hits->begin();
it != prefix_hits->end(); ++it) {
- HashCache::iterator hit = hash_cache_.find(*it);
- if (hit != hash_cache_.end()) {
+ HashCache::iterator hit = hash_cache_->find(*it);
+ if (hit != hash_cache_->end()) {
HashList& entries = hit->second;
HashList::iterator eit = entries.begin();
while (eit != entries.end()) {
@@ -1114,7 +1117,7 @@ void SafeBrowsingDatabaseImpl::GetCachedFullHashes(
}
if (entries.empty())
- hash_cache_.erase(hit);
+ hash_cache_->erase(hit);
}
}
}
@@ -1137,7 +1140,7 @@ void SafeBrowsingDatabaseImpl::CacheHashResults(
it != full_hits.end(); ++it) {
SBPrefix prefix;
memcpy(&prefix, &it->hash.full_hash, sizeof(prefix));
- HashList& entries = hash_cache_[prefix];
+ HashList& entries = (*hash_cache_)[prefix];
HashCacheEntry entry;
entry.received = now;
entry.list_id = GetListID(it->list_name);
@@ -1155,9 +1158,9 @@ void SafeBrowsingDatabaseImpl::ClearCachedHashes(const SBEntry* entry) {
else
prefix = entry->PrefixAt(i);
- HashCache::iterator it = hash_cache_.find(prefix);
- if (it != hash_cache_.end())
- hash_cache_.erase(it);
+ HashCache::iterator it = hash_cache_->find(prefix);
+ if (it != hash_cache_->end())
+ hash_cache_->erase(it);
}
}
@@ -1166,8 +1169,8 @@ void SafeBrowsingDatabaseImpl::ClearCachedHashes(const SBEntry* entry) {
// during an update, so no user action is blocking on it.
void SafeBrowsingDatabaseImpl::ClearCachedHashesForChunk(int list_id,
int add_chunk_id) {
- HashCache::iterator it = hash_cache_.begin();
- while (it != hash_cache_.end()) {
+ HashCache::iterator it = hash_cache_->begin();
+ while (it != hash_cache_->end()) {
HashList& entries = it->second;
HashList::iterator eit = entries.begin();
while (eit != entries.end()) {
@@ -1177,7 +1180,7 @@ void SafeBrowsingDatabaseImpl::ClearCachedHashesForChunk(int list_id,
++eit;
}
if (entries.empty())
- hash_cache_.erase(it++);
+ hash_cache_->erase(it++);
else
++it;
}
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_impl.h b/chrome/browser/safe_browsing/safe_browsing_database_impl.h
index 4b47859..a69b419 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database_impl.h
+++ b/chrome/browser/safe_browsing/safe_browsing_database_impl.h
@@ -21,8 +21,6 @@
#include "chrome/common/sqlite_compiled_statement.h"
#include "chrome/common/sqlite_utils.h"
-//class BloomFilter;
-
// The reference implementation database using SQLite.
class SafeBrowsingDatabaseImpl : public SafeBrowsingDatabase {
public:
@@ -71,8 +69,6 @@ class SafeBrowsingDatabaseImpl : public SafeBrowsingDatabase {
virtual void HandleResume();
private:
- friend class SafeBrowsingDatabaseImpl_HashCaching_Test;
-
// Opens the database.
bool Open();
@@ -212,8 +208,6 @@ class SafeBrowsingDatabaseImpl : public SafeBrowsingDatabase {
// True iff the database has been opened successfully.
bool init_;
- std::wstring filename_;
-
// Controls whether database writes are done synchronously in one go or
// asynchronously in small chunks.
bool asynchronous_;
@@ -259,21 +253,6 @@ class SafeBrowsingDatabaseImpl : public SafeBrowsingDatabase {
// Used to schedule resuming from a lower power state.
ScopedRunnableMethodFactory<SafeBrowsingDatabaseImpl> resume_factory_;
- // Used for caching GetHash results.
- typedef struct HashCacheEntry {
- SBFullHash full_hash;
- int list_id;
- int add_chunk_id;
- base::Time received;
- } HashCacheEntry;
-
- typedef std::list<HashCacheEntry> HashList;
- typedef base::hash_map<SBPrefix, HashList> HashCache;
- HashCache hash_cache_;
-
- // Cache of prefixes that returned empty results (no full hash match).
- std::set<SBPrefix> prefix_miss_cache_;
-
// The amount of time, in milliseconds, to wait before the next disk write.
int disk_delay_;
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_impl_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_database_impl_unittest.cc
deleted file mode 100644
index 614fa1b..0000000
--- a/chrome/browser/safe_browsing/safe_browsing_database_impl_unittest.cc
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2006-2008 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.
-//
-// Unit tests for the SafeBrowsing storage system (specific to the
-// SafeBrowsingDatabaseImpl implementation).
-
-#include "base/file_util.h"
-#include "base/logging.h"
-#include "base/path_service.h"
-#include "base/process_util.h"
-#include "base/sha2.h"
-#include "base/stats_counters.h"
-#include "base/string_util.h"
-#include "base/time.h"
-#include "chrome/browser/safe_browsing/protocol_parser.h"
-#include "chrome/browser/safe_browsing/safe_browsing_database_impl.h"
-#include "googleurl/src/gurl.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::Time;
-using base::TimeDelta;
-
-namespace {
- SBPrefix Sha256Prefix(const std::string& str) {
- SBPrefix hash;
- base::SHA256HashString(str, &hash, sizeof(hash));
- return hash;
- }
-
-// Helper function to do an AddDel or SubDel command.
-void DelChunk(SafeBrowsingDatabase* db,
- const std::string& list,
- int chunk_id,
- bool is_sub_del) {
- std::vector<SBChunkDelete>* deletes = new std::vector<SBChunkDelete>;
- SBChunkDelete chunk_delete;
- chunk_delete.list_name = list;
- chunk_delete.is_sub_del = is_sub_del;
- chunk_delete.chunk_del.push_back(ChunkRange(chunk_id));
- deletes->push_back(chunk_delete);
- db->DeleteChunks(deletes);
-}
-
-void AddDelChunk(SafeBrowsingDatabase* db,
- const std::string& list,
- int chunk_id) {
- DelChunk(db, list, chunk_id, false);
-}
-
-}
-
-// Utility function for setting up the database for the caching test.
-void PopulateDatabaseForCacheTest(SafeBrowsingDatabase* database) {
- // Add a simple chunk with one hostkey and cache it.
- SBChunkHost host;
- host.host = Sha256Prefix("www.evil.com/");
- host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 2);
- host.entry->set_chunk_id(1);
- host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/phishing.html"));
- host.entry->SetPrefixAt(1, Sha256Prefix("www.evil.com/malware.html"));
-
- SBChunk chunk;
- chunk.chunk_number = 1;
- chunk.is_add = true;
- chunk.hosts.push_back(host);
-
- std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
- chunks->push_back(chunk);
- database->InsertChunks("goog-malware-shavar", chunks);
-
- // Add the GetHash results to the cache.
- SBFullHashResult full_hash;
- base::SHA256HashString("www.evil.com/phishing.html",
- &full_hash.hash, sizeof(SBFullHash));
- full_hash.list_name = "goog-malware-shavar";
- full_hash.add_chunk_id = 1;
-
- std::vector<SBFullHashResult> results;
- results.push_back(full_hash);
-
- base::SHA256HashString("www.evil.com/malware.html",
- &full_hash.hash, sizeof(SBFullHash));
- results.push_back(full_hash);
-
- std::vector<SBPrefix> prefixes;
- database->CacheHashResults(prefixes, results);
-}
-
-TEST(SafeBrowsingDatabaseImpl, HashCaching) {
- std::wstring filename;
- PathService::Get(base::DIR_TEMP, &filename);
- filename.push_back(file_util::kPathSeparator);
- filename.append(L"SafeBrowsingTestDatabase");
- file_util::Delete(filename, false); // In case it existed from a previous run.
-
- SafeBrowsingDatabaseImpl database;
- database.SetSynchronous();
- EXPECT_TRUE(database.Init(filename, NULL));
-
- PopulateDatabaseForCacheTest(&database);
-
- // We should have both full hashes in the cache.
- EXPECT_EQ(database.hash_cache_.size(), 2U);
-
- // Test the cache lookup for the first prefix.
- std::string list;
- std::vector<SBPrefix> prefixes;
- std::vector<SBFullHashResult> full_hashes;
- database.ContainsUrl(GURL("http://www.evil.com/phishing.html"),
- &list, &prefixes, &full_hashes, Time::Now());
- EXPECT_EQ(full_hashes.size(), 1U);
-
- SBFullHashResult full_hash;
- base::SHA256HashString("www.evil.com/phishing.html",
- &full_hash.hash, sizeof(SBFullHash));
- EXPECT_EQ(memcmp(&full_hashes[0].hash,
- &full_hash.hash, sizeof(SBFullHash)), 0);
-
- prefixes.clear();
- full_hashes.clear();
-
- // Test the cache lookup for the second prefix.
- database.ContainsUrl(GURL("http://www.evil.com/malware.html"),
- &list, &prefixes, &full_hashes, Time::Now());
- EXPECT_EQ(full_hashes.size(), 1U);
- base::SHA256HashString("www.evil.com/malware.html",
- &full_hash.hash, sizeof(SBFullHash));
- EXPECT_EQ(memcmp(&full_hashes[0].hash,
- &full_hash.hash, sizeof(SBFullHash)), 0);
-
- prefixes.clear();
- full_hashes.clear();
-
- // Test removing a prefix via a sub chunk.
- SBChunkHost host;
- host.host = Sha256Prefix("www.evil.com/");
- host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 2);
- host.entry->set_chunk_id(1);
- host.entry->SetChunkIdAtPrefix(0, 1);
- host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/phishing.html"));
-
- SBChunk chunk;
- chunk.chunk_number = 2;
- chunk.is_add = false;
- chunk.hosts.clear();
- chunk.hosts.push_back(host);
- std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
- chunks->push_back(chunk);
- database.InsertChunks("goog-malware-shavar", chunks);
-
- // This prefix should still be there.
- database.ContainsUrl(GURL("http://www.evil.com/malware.html"),
- &list, &prefixes, &full_hashes, Time::Now());
- EXPECT_EQ(full_hashes.size(), 1U);
- base::SHA256HashString("www.evil.com/malware.html",
- &full_hash.hash, sizeof(SBFullHash));
- EXPECT_EQ(memcmp(&full_hashes[0].hash,
- &full_hash.hash, sizeof(SBFullHash)), 0);
-
- prefixes.clear();
- full_hashes.clear();
-
- // This prefix should be gone.
- database.ContainsUrl(GURL("http://www.evil.com/phishing.html"),
- &list, &prefixes, &full_hashes, Time::Now());
- EXPECT_EQ(full_hashes.size(), 0U);
-
- prefixes.clear();
- full_hashes.clear();
-
- // Test that an AddDel for the original chunk removes the last cached entry.
- AddDelChunk(&database, "goog-malware-shavar", 1);
- database.ContainsUrl(GURL("http://www.evil.com/malware.html"),
- &list, &prefixes, &full_hashes, Time::Now());
- EXPECT_EQ(full_hashes.size(), 0U);
- EXPECT_EQ(database.hash_cache_.size(), 0U);
-
- prefixes.clear();
- full_hashes.clear();
-
- // Test that the cache won't return expired values. First we have to adjust
- // the cached entries' received time to make them older, since the database
- // cache insert uses Time::Now(). First, store some entries.
- PopulateDatabaseForCacheTest(&database);
- EXPECT_EQ(database.hash_cache_.size(), 2U);
-
- // Now adjust one of the entries times to be in the past.
- Time expired = Time::Now() - TimeDelta::FromMinutes(60);
- SBPrefix key;
- memcpy(&key, &full_hash.hash, sizeof(SBPrefix));
- SafeBrowsingDatabaseImpl::HashList& entries = database.hash_cache_[key];
- SafeBrowsingDatabaseImpl::HashCacheEntry entry = entries.front();
- entries.pop_front();
- entry.received = expired;
- entries.push_back(entry);
-
- database.ContainsUrl(GURL("http://www.evil.com/malware.html"),
- &list, &prefixes, &full_hashes, expired);
- EXPECT_EQ(full_hashes.size(), 0U);
-
- // Expired entry was dumped.
- EXPECT_EQ(database.hash_cache_.size(), 1U);
-
- // This entry should still exist.
- database.ContainsUrl(GURL("http://www.evil.com/phishing.html"),
- &list, &prefixes, &full_hashes, expired);
- EXPECT_EQ(full_hashes.size(), 1U);
-
-
- // Testing prefix miss caching. First, we clear out the existing database,
- // Since PopulateDatabaseForCacheTest() doesn't handle adding duplicate
- // chunks.
- AddDelChunk(&database, "goog-malware-shavar", 1);
-
- std::vector<SBPrefix> prefix_misses;
- std::vector<SBFullHashResult> empty_full_hash;
- prefix_misses.push_back(Sha256Prefix("http://www.bad.com/malware.html"));
- prefix_misses.push_back(Sha256Prefix("http://www.bad.com/phishing.html"));
- database.CacheHashResults(prefix_misses, empty_full_hash);
-
- // Prefixes with no full results are misses.
- EXPECT_EQ(database.prefix_miss_cache_.size(), 2U);
-
- // Update the database.
- PopulateDatabaseForCacheTest(&database);
-
- // Prefix miss cache should be cleared.
- EXPECT_EQ(database.prefix_miss_cache_.size(), 0U);
-}
diff --git a/chrome/browser/safe_browsing/safe_browsing_database_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_database_unittest.cc
index 43249a5..cf187a2 100644
--- a/chrome/browser/safe_browsing/safe_browsing_database_unittest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_database_unittest.cc
@@ -4,6 +4,7 @@
//
// Unit tests for the SafeBrowsing storage system.
+#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
@@ -14,12 +15,14 @@
#include "base/time.h"
#include "chrome/browser/safe_browsing/protocol_parser.h"
#include "chrome/browser/safe_browsing/safe_browsing_database.h"
+#include "chrome/common/chrome_switches.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Time;
static const wchar_t kSafeBrowsingTestDatabase[] = L"SafeBrowsingTestDatabase";
+static const wchar_t kBloomSuffix[] = L" Bloom";
namespace {
SBPrefix Sha256Prefix(const std::string& str) {
@@ -65,7 +68,9 @@ namespace {
SafeBrowsingDatabase* SetupTestDatabase() {
std::wstring filename = GetTestDatabaseName();
+
// In case it existed from a previous run.
+ file_util::Delete(filename + kBloomSuffix, false);
file_util::Delete(filename, false);
SafeBrowsingDatabase* database = SafeBrowsingDatabase::Create();
@@ -76,12 +81,150 @@ namespace {
}
void TearDownTestDatabase(SafeBrowsingDatabase* database) {
- file_util::Delete(GetTestDatabaseName(), false);
+ std::wstring filename = database->filename();
delete database;
+ file_util::Delete(filename, false);
+ file_util::Delete(filename + L" Filter", false);
}
} // namespace
+// Tests retrieving list name information.
+TEST(SafeBrowsingDatabase, ListName) {
+ SafeBrowsingDatabase* database = SetupTestDatabase();
+
+ // Insert some malware add chunks.
+ SBChunkHost host;
+ host.host = Sha256Prefix("www.evil.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1);
+ host.entry->set_chunk_id(1);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/malware.html"));
+ SBChunk chunk;
+ chunk.chunk_number = 1;
+ chunk.is_add = true;
+ chunk.hosts.push_back(host);
+ std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+
+ host.host = Sha256Prefix("www.foo.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1);
+ host.entry->set_chunk_id(2);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.foo.com/malware.html"));
+ chunk.chunk_number = 2;
+ chunk.is_add = true;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+
+ host.host = Sha256Prefix("www.whatever.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1);
+ host.entry->set_chunk_id(3);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.whatever.com/malware.html"));
+ chunk.chunk_number = 3;
+ chunk.is_add = true;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ std::vector<SBListChunkRanges> lists;
+ database->GetListsInfo(&lists);
+
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
+ EXPECT_EQ(lists[0].adds, "1-3");
+ EXPECT_TRUE(lists[0].subs.empty());
+ lists.clear();
+
+ // Insert a malware sub chunk.
+ host.host = Sha256Prefix("www.subbed.com/");
+ host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
+ host.entry->set_chunk_id(7);
+ host.entry->SetChunkIdAtPrefix(0, 19);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.subbed.com/notevil1.html"));
+ chunk.chunk_number = 7;
+ chunk.is_add = false;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ database->GetListsInfo(&lists);
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
+ EXPECT_EQ(lists[0].adds, "1-3");
+ EXPECT_EQ(lists[0].subs, "7");
+ if (lists.size() == 2) {
+ // Old style database won't have the second entry since it creates the lists
+ // when it receives an update containing that list. The new bloom filter
+ // based database has these values hard coded.
+ EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList);
+ EXPECT_TRUE(lists[1].adds.empty());
+ EXPECT_TRUE(lists[1].subs.empty());
+ }
+ lists.clear();
+
+ // Add a phishing add chunk.
+ host.host = Sha256Prefix("www.evil.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1);
+ host.entry->set_chunk_id(47);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/phishing.html"));
+ chunk.chunk_number = 47;
+ chunk.is_add = true;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+
+ // Insert some phishing sub chunks.
+ host.host = Sha256Prefix("www.phishy.com/");
+ host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
+ host.entry->set_chunk_id(200);
+ host.entry->SetChunkIdAtPrefix(0, 1999);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.phishy.com/notevil1.html"));
+ chunk.chunk_number = 200;
+ chunk.is_add = false;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+
+ host.host = Sha256Prefix("www.phishy2.com/");
+ host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
+ host.entry->set_chunk_id(201);
+ host.entry->SetChunkIdAtPrefix(0, 1999);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.phishy2.com/notevil1.html"));
+ chunk.chunk_number = 201;
+ chunk.is_add = false;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+ database->UpdateFinished(true);
+
+ database->GetListsInfo(&lists);
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
+ EXPECT_EQ(lists[0].adds, "1-3");
+ EXPECT_EQ(lists[0].subs, "7");
+ EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList);
+ EXPECT_EQ(lists[1].adds, "47");
+ EXPECT_EQ(lists[1].subs, "200-201");
+
+ TearDownTestDatabase(database);
+}
+
// Checks database reading and writing.
TEST(SafeBrowsingDatabase, Database) {
SafeBrowsingDatabase* database = SetupTestDatabase();
@@ -101,7 +244,8 @@ TEST(SafeBrowsingDatabase, Database) {
std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
// Add another chunk with two different hostkeys.
host.host = Sha256Prefix("www.evil.com/");
@@ -125,7 +269,7 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
// and a chunk with an IP-based host
host.host = Sha256Prefix("192.168.0.1/");
@@ -139,14 +283,13 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
// Make sure they were added correctly.
std::vector<SBListChunkRanges> lists;
database->GetListsInfo(&lists);
- EXPECT_EQ(lists.size(), 1U);
- EXPECT_EQ(lists[0].name, "goog-malware");
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
EXPECT_EQ(lists[0].adds, "1-3");
EXPECT_TRUE(lists[0].subs.empty());
@@ -208,7 +351,8 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
EXPECT_TRUE(database->ContainsUrl(GURL("http://www.evil.com/phishing.html"),
@@ -235,12 +379,12 @@ TEST(SafeBrowsingDatabase, Database) {
&full_hashes, now));
database->GetListsInfo(&lists);
- EXPECT_EQ(lists.size(), 1U);
- EXPECT_EQ(lists[0].name, "goog-malware");
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
EXPECT_EQ(lists[0].subs, "4");
// Test removing all the prefixes from an add chunk.
- AddDelChunk(database, "goog-malware", 2);
+ database->UpdateStarted();
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 2);
database->UpdateFinished(true);
EXPECT_FALSE(database->ContainsUrl(GURL("http://www.evil.com/notevil2.html"),
@@ -256,8 +400,7 @@ TEST(SafeBrowsingDatabase, Database) {
&full_hashes, now));
database->GetListsInfo(&lists);
- EXPECT_EQ(lists.size(), 1U);
- EXPECT_EQ(lists[0].name, "goog-malware");
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
EXPECT_EQ(lists[0].adds, "1,3");
EXPECT_EQ(lists[0].subs, "4");
@@ -276,19 +419,19 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
// Now remove the dummy entry. If there are any problems with the
// transactions, asserts will fire.
- AddDelChunk(database, "goog-malware", 44);
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 44);
// Test the subdel command.
- SubDelChunk(database, "goog-malware", 4);
+ SubDelChunk(database, safe_browsing_util::kMalwareList, 4);
database->UpdateFinished(true);
database->GetListsInfo(&lists);
- EXPECT_EQ(lists.size(), 1U);
- EXPECT_EQ(lists[0].name, "goog-malware");
+ EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
EXPECT_EQ(lists[0].adds, "1,3");
EXPECT_EQ(lists[0].subs, "");
@@ -308,7 +451,8 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
EXPECT_FALSE(database->ContainsUrl(
@@ -328,7 +472,8 @@ TEST(SafeBrowsingDatabase, Database) {
chunks = new std::deque<SBChunk>;
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
EXPECT_FALSE(database->ContainsUrl(
@@ -373,7 +518,8 @@ TEST(SafeBrowsingDatabase, ZeroSizeChunk) {
chunk.hosts.push_back(host);
chunks->push_back(chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
// Add an empty ADD and SUB chunk.
@@ -386,12 +532,13 @@ TEST(SafeBrowsingDatabase, ZeroSizeChunk) {
empty_chunk.is_add = true;
chunks = new std::deque<SBChunk>;
chunks->push_back(empty_chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
chunks = new std::deque<SBChunk>;
empty_chunk.chunk_number = 7;
empty_chunk.is_add = false;
chunks->push_back(empty_chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
list_chunks_empty.clear();
@@ -427,7 +574,8 @@ TEST(SafeBrowsingDatabase, ZeroSizeChunk) {
empty_chunk.is_add = true;
chunks->push_back(empty_chunk);
- database->InsertChunks("goog-malware", chunks);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
database->UpdateFinished(true);
const Time now = Time::Now();
@@ -447,14 +595,16 @@ TEST(SafeBrowsingDatabase, ZeroSizeChunk) {
EXPECT_EQ(list_chunks_empty[0].subs, "7");
// Handle AddDel and SubDel commands for empty chunks.
- AddDelChunk(database, "goog-malware", 21);
+ database->UpdateStarted();
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 21);
database->UpdateFinished(true);
list_chunks_empty.clear();
database->GetListsInfo(&list_chunks_empty);
EXPECT_EQ(list_chunks_empty[0].adds, "1,10,19-20,22");
EXPECT_EQ(list_chunks_empty[0].subs, "7");
- SubDelChunk(database, "goog-malware", 7);
+ database->UpdateStarted();
+ SubDelChunk(database, safe_browsing_util::kMalwareList, 7);
database->UpdateFinished(true);
list_chunks_empty.clear();
database->GetListsInfo(&list_chunks_empty);
@@ -464,6 +614,290 @@ TEST(SafeBrowsingDatabase, ZeroSizeChunk) {
TearDownTestDatabase(database);
}
+// Utility function for setting up the database for the caching test.
+void PopulateDatabaseForCacheTest(SafeBrowsingDatabase* database) {
+ // Add a simple chunk with one hostkey and cache it.
+ SBChunkHost host;
+ host.host = Sha256Prefix("www.evil.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 2);
+ host.entry->set_chunk_id(1);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/phishing.html"));
+ host.entry->SetPrefixAt(1, Sha256Prefix("www.evil.com/malware.html"));
+
+ SBChunk chunk;
+ chunk.chunk_number = 1;
+ chunk.is_add = true;
+ chunk.hosts.push_back(host);
+
+ std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ // Add the GetHash results to the cache.
+ SBFullHashResult full_hash;
+ base::SHA256HashString("www.evil.com/phishing.html",
+ &full_hash.hash, sizeof(SBFullHash));
+ full_hash.list_name = safe_browsing_util::kMalwareList;
+ full_hash.add_chunk_id = 1;
+
+ std::vector<SBFullHashResult> results;
+ results.push_back(full_hash);
+
+ base::SHA256HashString("www.evil.com/malware.html",
+ &full_hash.hash, sizeof(SBFullHash));
+ results.push_back(full_hash);
+
+ std::vector<SBPrefix> prefixes;
+ database->CacheHashResults(prefixes, results);
+}
+
+TEST(SafeBrowsingDatabase, HashCaching) {
+ SafeBrowsingDatabase* database = SetupTestDatabase();
+
+ PopulateDatabaseForCacheTest(database);
+
+ // We should have both full hashes in the cache.
+ SafeBrowsingDatabase::HashCache* hash_cache = database->hash_cache();
+ EXPECT_EQ(hash_cache->size(), 2U);
+
+ // Test the cache lookup for the first prefix.
+ std::string list;
+ std::vector<SBPrefix> prefixes;
+ std::vector<SBFullHashResult> full_hashes;
+ database->ContainsUrl(GURL("http://www.evil.com/phishing.html"),
+ &list, &prefixes, &full_hashes, Time::Now());
+ EXPECT_EQ(full_hashes.size(), 1U);
+
+ SBFullHashResult full_hash;
+ base::SHA256HashString("www.evil.com/phishing.html",
+ &full_hash.hash, sizeof(SBFullHash));
+ EXPECT_EQ(memcmp(&full_hashes[0].hash,
+ &full_hash.hash, sizeof(SBFullHash)), 0);
+
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test the cache lookup for the second prefix.
+ database->ContainsUrl(GURL("http://www.evil.com/malware.html"),
+ &list, &prefixes, &full_hashes, Time::Now());
+ EXPECT_EQ(full_hashes.size(), 1U);
+ base::SHA256HashString("www.evil.com/malware.html",
+ &full_hash.hash, sizeof(SBFullHash));
+ EXPECT_EQ(memcmp(&full_hashes[0].hash,
+ &full_hash.hash, sizeof(SBFullHash)), 0);
+
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test removing a prefix via a sub chunk.
+ SBChunkHost host;
+ host.host = Sha256Prefix("www.evil.com/");
+ host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
+ host.entry->set_chunk_id(1);
+ host.entry->SetChunkIdAtPrefix(0, 1);
+ host.entry->SetPrefixAt(0, Sha256Prefix("www.evil.com/phishing.html"));
+
+ SBChunk chunk;
+ chunk.chunk_number = 2;
+ chunk.is_add = false;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ std::deque<SBChunk>* chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ // This prefix should still be there.
+ database->ContainsUrl(GURL("http://www.evil.com/malware.html"),
+ &list, &prefixes, &full_hashes, Time::Now());
+ EXPECT_EQ(full_hashes.size(), 1U);
+ base::SHA256HashString("www.evil.com/malware.html",
+ &full_hash.hash, sizeof(SBFullHash));
+ EXPECT_EQ(memcmp(&full_hashes[0].hash,
+ &full_hash.hash, sizeof(SBFullHash)), 0);
+
+ prefixes.clear();
+ full_hashes.clear();
+
+ // This prefix should be gone.
+ database->ContainsUrl(GURL("http://www.evil.com/phishing.html"),
+ &list, &prefixes, &full_hashes, Time::Now());
+ EXPECT_EQ(full_hashes.size(), 0U);
+
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test that an AddDel for the original chunk removes the last cached entry.
+ database->UpdateStarted();
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 1);
+ database->UpdateFinished(true);
+ database->ContainsUrl(GURL("http://www.evil.com/malware.html"),
+ &list, &prefixes, &full_hashes, Time::Now());
+ EXPECT_EQ(full_hashes.size(), 0U);
+ EXPECT_EQ(database->hash_cache()->size(), 0U);
+
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test that the cache won't return expired values. First we have to adjust
+ // the cached entries' received time to make them older, since the database
+ // cache insert uses Time::Now(). First, store some entries.
+ PopulateDatabaseForCacheTest(database);
+ hash_cache = database->hash_cache();
+ EXPECT_EQ(hash_cache->size(), 2U);
+
+ // Now adjust one of the entries times to be in the past.
+ base::Time expired = base::Time::Now() - base::TimeDelta::FromMinutes(60);
+ SBPrefix key;
+ memcpy(&key, &full_hash.hash, sizeof(SBPrefix));
+ SafeBrowsingDatabase::HashList& entries = (*hash_cache)[key];
+ SafeBrowsingDatabase::HashCacheEntry entry = entries.front();
+ entries.pop_front();
+ entry.received = expired;
+ entries.push_back(entry);
+
+ database->ContainsUrl(GURL("http://www.evil.com/malware.html"),
+ &list, &prefixes, &full_hashes, expired);
+ EXPECT_EQ(full_hashes.size(), 0U);
+
+ // This entry should still exist.
+ database->ContainsUrl(GURL("http://www.evil.com/phishing.html"),
+ &list, &prefixes, &full_hashes, expired);
+ EXPECT_EQ(full_hashes.size(), 1U);
+
+
+ // Testing prefix miss caching. First, we clear out the existing database,
+ // Since PopulateDatabaseForCacheTest() doesn't handle adding duplicate
+ // chunks.
+ database->UpdateStarted();
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 1);
+ database->UpdateFinished(true);
+
+ std::vector<SBPrefix> prefix_misses;
+ std::vector<SBFullHashResult> empty_full_hash;
+ prefix_misses.push_back(Sha256Prefix("http://www.bad.com/malware.html"));
+ prefix_misses.push_back(Sha256Prefix("http://www.bad.com/phishing.html"));
+ database->CacheHashResults(prefix_misses, empty_full_hash);
+
+ // Prefixes with no full results are misses.
+ EXPECT_EQ(database->prefix_miss_cache()->size(), 2U);
+
+ // Update the database.
+ PopulateDatabaseForCacheTest(database);
+
+ // Prefix miss cache should be cleared.
+ EXPECT_EQ(database->prefix_miss_cache()->size(), 0U);
+
+ list.clear();
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test receiving a full add chunk. The old implementation doesn't support
+ // this test, so we bail here.
+ if (!CommandLine().HasSwitch(switches::kUseNewSafeBrowsing)) {
+ TearDownTestDatabase(database);
+ return;
+ }
+
+ host.host = Sha256Prefix("www.fullevil.com/");
+ host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 2);
+ host.entry->set_chunk_id(20);
+ SBFullHash full_add1;
+ base::SHA256HashString("www.fullevil.com/bad1.html",
+ &full_add1.full_hash, sizeof(SBFullHash));
+ host.entry->SetFullHashAt(0, full_add1);
+ SBFullHash full_add2;
+ base::SHA256HashString("www.fullevil.com/bad2.html",
+ &full_add2.full_hash, sizeof(SBFullHash));
+ host.entry->SetFullHashAt(1, full_add2);
+
+ chunk.chunk_number = 20;
+ chunk.is_add = true;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ EXPECT_TRUE(database->ContainsUrl(GURL("http://www.fullevil.com/bad1.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+ EXPECT_EQ(full_hashes.size(), 1U);
+ EXPECT_EQ(0, memcmp(full_hashes[0].hash.full_hash,
+ full_add1.full_hash,
+ sizeof(SBFullHash)));
+ list.clear();
+ prefixes.clear();
+ full_hashes.clear();
+
+ EXPECT_TRUE(database->ContainsUrl(GURL("http://www.fullevil.com/bad2.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+ EXPECT_EQ(full_hashes.size(), 1U);
+ EXPECT_EQ(0, memcmp(full_hashes[0].hash.full_hash,
+ full_add2.full_hash,
+ sizeof(SBFullHash)));
+ list.clear();
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Test receiving a full sub chunk, which will remove one of the full adds.
+ host.host = Sha256Prefix("www.fullevil.com/");
+ host.entry = SBEntry::Create(SBEntry::SUB_FULL_HASH, 1);
+ host.entry->set_chunk_id(200);
+ host.entry->SetChunkIdAtPrefix(0, 20);
+ SBFullHash full_sub;
+ base::SHA256HashString("www.fullevil.com/bad1.html",
+ &full_sub.full_hash, sizeof(SBFullHash));
+ host.entry->SetFullHashAt(0, full_sub);
+
+ chunk.chunk_number = 200;
+ chunk.is_add = false;
+ chunk.hosts.clear();
+ chunk.hosts.push_back(host);
+ chunks = new std::deque<SBChunk>;
+ chunks->push_back(chunk);
+ database->UpdateStarted();
+ database->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+ database->UpdateFinished(true);
+
+ EXPECT_FALSE(database->ContainsUrl(GURL("http://www.fullevil.com/bad1.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+ EXPECT_EQ(full_hashes.size(), 0U);
+
+ // There should be one remaining full add.
+ EXPECT_TRUE(database->ContainsUrl(GURL("http://www.fullevil.com/bad2.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+ EXPECT_EQ(full_hashes.size(), 1U);
+ EXPECT_EQ(0, memcmp(full_hashes[0].hash.full_hash,
+ full_add2.full_hash,
+ sizeof(SBFullHash)));
+ list.clear();
+ prefixes.clear();
+ full_hashes.clear();
+
+ // Now test an AddDel for the remaining full add.
+ database->UpdateStarted();
+ AddDelChunk(database, safe_browsing_util::kMalwareList, 20);
+ database->UpdateFinished(true);
+
+ EXPECT_FALSE(database->ContainsUrl(GURL("http://www.fullevil.com/bad1.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+ EXPECT_FALSE(database->ContainsUrl(GURL("http://www.fullevil.com/bad2.html"),
+ &list, &prefixes, &full_hashes,
+ Time::Now()));
+
+ TearDownTestDatabase(database);
+}
+
void PrintStat(const wchar_t* name) {
#if defined(OS_WIN)
int value = StatsTable::current()->GetCounterValue(name);
@@ -518,8 +952,9 @@ void PeformUpdate(const std::wstring& initial_db,
process_util::ProcessMetrics::CreateProcessMetrics(handle));
CHECK(metric->GetIOCounters(&before));
- database->DeleteChunks(deletes);
+ database->UpdateStarted();
+ database->DeleteChunks(deletes);
for (size_t i = 0; i < chunks.size(); ++i)
database->InsertChunks(chunks[i].listname, chunks[i].chunks);
@@ -682,7 +1117,7 @@ TEST(SafeBrowsingDatabase, DISABLED_DatabaseOldLotsofDeletesIO) {
std::vector<SBChunkDelete>* deletes = new std::vector<SBChunkDelete>;
SBChunkDelete del;
del.is_sub_del = false;
- del.list_name = "goog-malware-shavar";
+ del.list_name = safe_browsing_util::kMalwareList;
del.chunk_del.push_back(ChunkRange(3539, 3579));
deletes->push_back(del);
PeformUpdate(GetOldSafeBrowsingPath(), chunks, deletes);
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index 1382a5f..5a2f257 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -202,8 +202,8 @@ bool SafeBrowsingService::CheckUrlNew(const GURL& url, Client* client) {
std::string list;
std::vector<SBPrefix> prefix_hits;
std::vector<SBFullHashResult> full_hits;
- bool prefix_match = database_->ContainsUrl(url, &list, &prefix_hits,
- &full_hits,
+ bool prefix_match = database_->ContainsUrl(url, &list, &prefix_hits,
+ &full_hits,
protocol_manager_->last_update());
if (!prefix_match)
return true; // URL is okay.
@@ -415,6 +415,7 @@ void SafeBrowsingService::HandleGetHashResults(
db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
this, &SafeBrowsingService::CacheHashResults, prefixes, full_hashes));
} else if (database_) {
+ // Cache the GetHash results in memory:
database_->CacheHashResults(prefixes, full_hashes);
}
}
@@ -464,6 +465,13 @@ void SafeBrowsingService::GetAllChunks() {
this, &SafeBrowsingService::GetAllChunksFromDatabase));
}
+void SafeBrowsingService::UpdateStarted() {
+ DCHECK(MessageLoop::current() == io_loop_);
+ DCHECK(enabled_);
+ db_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &SafeBrowsingService::DatabaseUpdateStarted));
+}
+
void SafeBrowsingService::UpdateFinished(bool update_succeeded) {
DCHECK(MessageLoop::current() == io_loop_);
DCHECK(enabled_);
@@ -471,6 +479,12 @@ void SafeBrowsingService::UpdateFinished(bool update_succeeded) {
this, &SafeBrowsingService::DatabaseUpdateFinished, update_succeeded));
}
+void SafeBrowsingService::DatabaseUpdateStarted() {
+ DCHECK(MessageLoop::current() == db_thread_->message_loop());
+ if (GetDatabase())
+ GetDatabase()->UpdateStarted();
+}
+
void SafeBrowsingService::DatabaseUpdateFinished(bool update_succeeded) {
DCHECK(MessageLoop::current() == db_thread_->message_loop());
if (GetDatabase())
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h
index 8a87726..d85dd89 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -59,9 +59,9 @@ class SafeBrowsingService
GURL url;
bool proceed;
UrlCheckResult result;
- Client* client;
- int render_process_host_id;
- int render_view_id;
+ Client* client;
+ int render_process_host_id;
+ int render_view_id;
ResourceType::Type resource_type;
};
@@ -135,6 +135,9 @@ class SafeBrowsingService
void HandleChunk(const std::string& list, std::deque<SBChunk>* chunks);
void HandleChunkDelete(std::vector<SBChunkDelete>* chunk_deletes);
void GetAllChunks();
+
+ // Methods called to indicate the beginning and end of a complete update.
+ void UpdateStarted();
void UpdateFinished(bool update_succeeded);
// The blocking page on the UI thread has completed.
@@ -177,6 +180,9 @@ class SafeBrowsingService
// threadsafe.
SafeBrowsingDatabase* GetDatabase();
+ // Release the final reference to the database on the db thread.
+ void ReleaseDatabase(SafeBrowsingDatabase* database);
+
// Called on the database thread to check a url.
void CheckDatabase(SafeBrowsingCheck* info, base::Time last_update);
@@ -203,6 +209,7 @@ class SafeBrowsingService
void NotifyClientBlockingComplete(Client* client, bool proceed);
+ void DatabaseUpdateStarted();
void DatabaseUpdateFinished(bool update_succeeded);
void Start();
@@ -215,7 +222,7 @@ class SafeBrowsingService
// Runs on the io thread when the reset is complete.
void OnResetComplete();
- // Store the results of a GetHash request. Runs on the database thread.
+ // Store in-memory the GetHash response. Runs on the database thread.
void CacheHashResults(const std::vector<SBPrefix>& prefixes,
const std::vector<SBFullHashResult>& full_hashes);
diff --git a/chrome/browser/safe_browsing/safe_browsing_util.cc b/chrome/browser/safe_browsing/safe_browsing_util.cc
index b7cf098..7b529fa 100644
--- a/chrome/browser/safe_browsing/safe_browsing_util.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_util.cc
@@ -25,6 +25,9 @@ static const char kReportParams[] = "?tpl=generic&continue=%s&url=%s";
namespace safe_browsing_util {
+const char kMalwareList[] = "goog-malware-shavar";
+const char kPhishingList[] = "goog-phish-shavar";
+
void GenerateHostsToCheck(const GURL& url, std::vector<std::string>* hosts) {
// Per Safe Browsing Protocol 2 spec, first we try the host. Then we try up
// to 4 hostnames starting with the last 5 components and successively
diff --git a/chrome/browser/safe_browsing/safe_browsing_util.h b/chrome/browser/safe_browsing/safe_browsing_util.h
index 3d8cfb7..a034b73 100644
--- a/chrome/browser/safe_browsing/safe_browsing_util.h
+++ b/chrome/browser/safe_browsing/safe_browsing_util.h
@@ -273,6 +273,10 @@ class SBHostInfo {
namespace safe_browsing_util {
+// SafeBrowsing list names.
+extern const char kMalwareList[];
+extern const char kPhishingList[];
+
void FreeChunks(std::deque<SBChunk>* chunks);
// Given a URL, returns all the hosts we need to check. They are returned
diff --git a/chrome/chrome.xcodeproj/project.pbxproj b/chrome/chrome.xcodeproj/project.pbxproj
index cf149ce..53bf20f 100644
--- a/chrome/chrome.xcodeproj/project.pbxproj
+++ b/chrome/chrome.xcodeproj/project.pbxproj
@@ -1031,7 +1031,6 @@
4D7BFAE00E9D49DE009A6919 /* safe_browsing_database_bloom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = safe_browsing_database_bloom.h; sourceTree = "<group>"; };
4D7BFAE10E9D49DE009A6919 /* safe_browsing_database_impl.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = safe_browsing_database_impl.cc; sourceTree = "<group>"; };
4D7BFAE20E9D49DE009A6919 /* safe_browsing_database_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = safe_browsing_database_impl.h; sourceTree = "<group>"; };
- 4D7BFAE30E9D49DE009A6919 /* safe_browsing_database_impl_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = safe_browsing_database_impl_unittest.cc; sourceTree = "<group>"; };
4D7BFAE40E9D49DE009A6919 /* safe_browsing_database_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = safe_browsing_database_unittest.cc; sourceTree = "<group>"; };
4D7BFAE50E9D49DE009A6919 /* safe_browsing_service.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = safe_browsing_service.cc; sourceTree = "<group>"; };
4D7BFAE60E9D49DE009A6919 /* safe_browsing_service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = safe_browsing_service.h; sourceTree = "<group>"; };
@@ -1945,7 +1944,6 @@
4D7BFAE00E9D49DE009A6919 /* safe_browsing_database_bloom.h */,
4D7BFAE10E9D49DE009A6919 /* safe_browsing_database_impl.cc */,
4D7BFAE20E9D49DE009A6919 /* safe_browsing_database_impl.h */,
- 4D7BFAE30E9D49DE009A6919 /* safe_browsing_database_impl_unittest.cc */,
4D7BFAE40E9D49DE009A6919 /* safe_browsing_database_unittest.cc */,
4D7BFAE50E9D49DE009A6919 /* safe_browsing_service.cc */,
4D7BFAE60E9D49DE009A6919 /* safe_browsing_service.h */,
diff --git a/chrome/test/unit/unit_tests.scons b/chrome/test/unit/unit_tests.scons
index 3129c16..407fdaa 100644
--- a/chrome/test/unit/unit_tests.scons
+++ b/chrome/test/unit/unit_tests.scons
@@ -126,7 +126,6 @@ if env['PLATFORM'] in ('posix', 'win32'):
'$CHROME_DIR/browser/safe_browsing/bloom_filter_unittest.cc',
'$CHROME_DIR/browser/safe_browsing/chunk_range_unittest.cc',
'$CHROME_DIR/browser/safe_browsing/safe_browsing_database_unittest.cc',
- '$CHROME_DIR/browser/safe_browsing/safe_browsing_database_impl_unittest.cc',
'$CHROME_DIR/browser/safe_browsing/protocol_parser_unittest.cc',
'$CHROME_DIR/browser/safe_browsing/safe_browsing_util_unittest.cc',
'$CHROME_DIR/common/animation_unittest.cc',
diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj
index d39295b..0b131c0 100644
--- a/chrome/test/unit/unittests.vcproj
+++ b/chrome/test/unit/unittests.vcproj
@@ -814,10 +814,6 @@
>
</File>
<File
- RelativePath="..\..\browser\safe_browsing\safe_browsing_database_impl_unittest.cc"
- >
- </File>
- <File
RelativePath="..\..\browser\safe_browsing\safe_browsing_database_unittest.cc"
>
</File>