| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/favicon/core/favicon_database.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <bit> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/debug/alias.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/database_utils/upper_bound_string.h" |
| #include "components/database_utils/url_converter.h" |
| #include "components/favicon_base/favicon_types.h" |
| #include "sql/recovery.h" |
| #include "sql/statement.h" |
| #include "sql/transaction.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_APPLE) |
| #include "base/apple/backup_util.h" |
| #endif |
| |
| namespace favicon { |
| |
| // Description of database tables: |
| // |
| // icon_mapping |
| // id Unique ID. |
| // page_url Page URL which has one or more associated favicons. |
| // icon_id The ID of favicon that this mapping maps to. |
| // page_url_type The type of the `page_url`. This is computed when the |
| // entry is added. This may differ from the current type of |
| // the `page_url`. For example if a site moves, or sign-in |
| // state changes, the current page_url_type of the page may |
| // differ from what is stored in the row. See `PageUrlType` |
| // for valid values. By default this is `kRegularPage`. |
| // |
| // favicons This table associates a row to each favicon for a |
| // `page_url` in the `icon_mapping` table. This is the |
| // default favicon `page_url`/favicon.ico plus any favicons |
| // associated via <link rel="icon_type" href="url">. |
| // The `id` matches the `icon_id` field in the appropriate |
| // row in the icon_mapping table. |
| // |
| // id Unique ID. |
| // url The URL at which the favicon file is located. |
| // icon_type The type of the favicon specified in the rel attribute of |
| // the link tag. The kFavicon type is used for the default |
| // favicon.ico favicon. |
| // |
| // favicon_bitmaps This table contains the PNG encoded bitmap data of the |
| // favicons. There is a separate row for every size in a |
| // multi resolution bitmap. The bitmap data is associated |
| // to the favicon via the `icon_id` field which matches |
| // the `id` field in the appropriate row in the `favicons` |
| // table. |
| // |
| // id Unique ID. |
| // icon_id The ID of the favicon that the bitmap is associated to. |
| // last_updated The time at which this favicon was inserted into the |
| // table. This is used to determine if it needs to be |
| // redownloaded from the web. Value 0 denotes that the bitmap |
| // has been explicitly expired. |
| // This is used only for ON_VISIT icons, for ON_DEMAND the |
| // value is always 0. |
| // image_data PNG encoded data of the favicon. |
| // width Pixel width of `image_data`. |
| // height Pixel height of `image_data`. |
| // last_requested The time at which this bitmap was last requested. This |
| // entry is non-zero iff the bitmap is of type ON_DEMAND. |
| // This info is used for clearing old ON_DEMAND bitmaps. |
| // (On-demand bitmaps cannot get cleared along with expired |
| // visits in history DB because there is no corresponding |
| // visit.) |
| |
| namespace { |
| |
| // For this database, schema migrations are deprecated after two |
| // years. This means that the oldest non-deprecated version should be |
| // two years old or greater (thus the migrations to get there are |
| // older). Databases containing deprecated versions will be cleared |
| // at startup. Since this database is a cache, losing old data is not |
| // fatal (in fact, very old data may be expired immediately at startup |
| // anyhow). |
| |
| // TODO(ckitagawa): Add commit hash after landing. |
| // Version 9: <TODO>/r6208170 by ckitagawa@chromium.org on 2025-01-28 |
| // Version 8: 982ef2c1/r323176 by rogerm@chromium.org on 2015-03-31 |
| // Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01 (depr.) |
| // Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 (depr.) |
| // Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12 (deprecated) |
| // Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 (deprecated) |
| // Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated) |
| |
| // Version number of the database. |
| // NOTE(shess): When changing the version, add a new golden file for |
| // the new version and a test to verify that Init() works with it. |
| const int kCurrentVersionNumber = 9; |
| const int kCompatibleVersionNumber = 9; |
| const int kDeprecatedVersionNumber = 7; // and earlier. |
| |
| // NOTE(shess): Schema modifications must consider initial creation in |
| // `InitImpl()` and history pruning in `RetainDataForPageUrls()`. |
| bool InitTables(sql::Database* db) { |
| static const char kIconMappingSql[] = |
| "CREATE TABLE IF NOT EXISTS icon_mapping" |
| "(" |
| "id INTEGER PRIMARY KEY," |
| "page_url LONGVARCHAR NOT NULL," |
| "icon_id INTEGER," |
| "page_url_type INTEGER DEFAULT 0" |
| ")"; |
| if (!db->Execute(kIconMappingSql)) |
| return false; |
| |
| static const char kFaviconsSql[] = |
| "CREATE TABLE IF NOT EXISTS favicons" |
| "(" |
| "id INTEGER PRIMARY KEY," |
| "url LONGVARCHAR NOT NULL," |
| // default icon_type kFavicon to be consistent with past migration. |
| "icon_type INTEGER DEFAULT 1" |
| ")"; |
| if (!db->Execute(kFaviconsSql)) |
| return false; |
| |
| static const char kFaviconBitmapsSql[] = |
| "CREATE TABLE IF NOT EXISTS favicon_bitmaps" |
| "(" |
| "id INTEGER PRIMARY KEY," |
| "icon_id INTEGER NOT NULL," |
| "last_updated INTEGER DEFAULT 0," |
| "image_data BLOB," |
| "width INTEGER DEFAULT 0," |
| "height INTEGER DEFAULT 0," |
| // This field is at the end so that fresh tables and migrated tables have |
| // the same layout. |
| "last_requested INTEGER DEFAULT 0" |
| ")"; |
| if (!db->Execute(kFaviconBitmapsSql)) |
| return false; |
| |
| return true; |
| } |
| |
| // NOTE(shess): Schema modifications must consider initial creation in |
| // `InitImpl()` and history pruning in `RetainDataForPageUrls()`. |
| bool InitIndices(sql::Database* db) { |
| static const char kIconMappingUrlIndexSql[] = |
| "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" |
| " ON icon_mapping(page_url)"; |
| static const char kIconMappingIdIndexSql[] = |
| "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" |
| " ON icon_mapping(icon_id)"; |
| if (!db->Execute(kIconMappingUrlIndexSql) || |
| !db->Execute(kIconMappingIdIndexSql)) { |
| return false; |
| } |
| |
| static const char kFaviconsIndexSql[] = |
| "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; |
| if (!db->Execute(kFaviconsIndexSql)) |
| return false; |
| |
| static const char kFaviconBitmapsIndexSql[] = |
| "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " |
| "favicon_bitmaps(icon_id)"; |
| if (!db->Execute(kFaviconBitmapsIndexSql)) |
| return false; |
| |
| return true; |
| } |
| |
| void DatabaseErrorCallback(sql::Database* db, |
| int extended_error, |
| sql::Statement* stmt) { |
| // TODO(shess): Assert that this is running on a safe thread. |
| // AFAICT, should be the history thread, but at this level I can't |
| // see how to reach that. |
| |
| // Attempt to recover a corrupt database, if it is eligible to be recovered. |
| if (sql::Recovery::RecoverIfPossible( |
| db, extended_error, |
| sql::Recovery::Strategy::kRecoverWithMetaVersionOrRaze)) { |
| // Recovery was attempted. The database handle has been poisoned and the |
| // error callback has been reset. |
| |
| // TODO(shess): Is it possible/likely to have broken foreign-key |
| // issues with the tables? |
| // - icon_mapping.icon_id maps to no favicons.id |
| // - favicon_bitmaps.icon_id maps to no favicons.id |
| // - favicons.id is referenced by no icon_mapping.icon_id |
| // - favicons.id is referenced by no favicon_bitmaps.icon_id |
| // This step is possibly not worth the effort necessary to develop |
| // and sequence the statements, as it is basically a form of garbage |
| // collection. |
| |
| // Signal the test-expectation framework that the error was handled. |
| std::ignore = sql::Database::IsExpectedSqliteError(extended_error); |
| return; |
| } |
| |
| // The default handling is to log an error on debug and to ignore on release. |
| if (!sql::Database::IsExpectedSqliteError(extended_error)) { |
| DLOG(ERROR) << db->GetErrorMessage(); |
| } |
| } |
| |
| } // namespace |
| |
| FaviconDatabase::IconMappingEnumerator::IconMappingEnumerator() = default; |
| |
| FaviconDatabase::IconMappingEnumerator::~IconMappingEnumerator() = default; |
| |
| bool FaviconDatabase::IconMappingEnumerator::GetNextIconMapping( |
| IconMapping* icon_mapping) { |
| if (!statement_.Step()) |
| return false; |
| FillIconMapping(GURL(statement_.ColumnStringView(4)), statement_, |
| icon_mapping); |
| return true; |
| } |
| |
| FaviconDatabase::FaviconDatabase() |
| : db_(sql::DatabaseOptions() |
| .set_preload(true) |
| // Favicons db only stores favicons, so we don't need that big a |
| // page size or cache. |
| .set_page_size(2048) |
| .set_cache_size(32), |
| /*tag=*/"Thumbnail") {} |
| |
| FaviconDatabase::~FaviconDatabase() { |
| // The DBCloseScoper will delete the DB and the cache. |
| } |
| |
| sql::InitStatus FaviconDatabase::Init(const base::FilePath& db_name) { |
| // TODO(shess): Consider separating database open from schema setup. |
| // With that change, this code could Raze() from outside the |
| // transaction, rather than needing RazeAndPoison() in InitImpl(). |
| |
| // Retry failed setup in case the recovery system fixed things. |
| const size_t kAttempts = 2; |
| |
| sql::InitStatus status = sql::INIT_FAILURE; |
| for (size_t i = 0; i < kAttempts; ++i) { |
| status = InitImpl(db_name); |
| if (status == sql::INIT_OK) |
| return status; |
| |
| meta_table_.Reset(); |
| db_.Close(); |
| } |
| return status; |
| } |
| |
| void FaviconDatabase::ComputeDatabaseMetrics() { |
| // Calculate the size of the favicon database. |
| sql::Statement page_count( |
| db_.GetCachedStatement(SQL_FROM_HERE, "PRAGMA page_count")); |
| int64_t page_count_bytes = page_count.Step() ? page_count.ColumnInt64(0) : 0; |
| sql::Statement page_size( |
| db_.GetCachedStatement(SQL_FROM_HERE, "PRAGMA page_size")); |
| int64_t page_size_bytes = page_size.Step() ? page_size.ColumnInt64(0) : 0; |
| int size_mb = |
| static_cast<int>((page_count_bytes * page_size_bytes) / (1024 * 1024)); |
| UMA_HISTOGRAM_MEMORY_MB("History.FaviconDatabaseSizeMB", size_mb); |
| } |
| |
| void FaviconDatabase::BeginTransaction() { |
| db_.BeginTransactionDeprecated(); |
| } |
| |
| void FaviconDatabase::CommitTransaction() { |
| db_.CommitTransactionDeprecated(); |
| } |
| |
| void FaviconDatabase::RollbackTransaction() { |
| db_.RollbackTransactionDeprecated(); |
| } |
| |
| void FaviconDatabase::Vacuum() { |
| DCHECK(db_.transaction_nesting() == 0) |
| << "Can not have a transaction when vacuuming."; |
| std::ignore = db_.Execute("VACUUM"); |
| } |
| |
| void FaviconDatabase::TrimMemory() { |
| db_.TrimMemory(); |
| } |
| |
| std::map<favicon_base::FaviconID, IconMappingsForExpiry> |
| FaviconDatabase::GetOldOnDemandFavicons(base::Time threshold) { |
| // Restrict to on-demand bitmaps (i.e. with last_requested != 0). |
| // This is called rarely during history expiration cleanup and hence not worth |
| // caching. |
| sql::Statement old_icons(db_.GetUniqueStatement( |
| "SELECT favicons.id, favicons.url, icon_mapping.page_url " |
| "FROM favicons " |
| "JOIN favicon_bitmaps ON (favicon_bitmaps.icon_id = favicons.id) " |
| "JOIN icon_mapping ON (icon_mapping.icon_id = favicon_bitmaps.icon_id) " |
| "WHERE (favicon_bitmaps.last_requested > 0 AND " |
| " favicon_bitmaps.last_requested < ?)")); |
| old_icons.BindTime(0, threshold); |
| |
| std::map<favicon_base::FaviconID, IconMappingsForExpiry> icon_mappings; |
| |
| while (old_icons.Step()) { |
| favicon_base::FaviconID id = old_icons.ColumnInt64(0); |
| icon_mappings[id].icon_url = GURL(old_icons.ColumnStringView(1)); |
| icon_mappings[id].page_urls.push_back(GURL(old_icons.ColumnStringView(2))); |
| } |
| |
| return icon_mappings; |
| } |
| |
| bool FaviconDatabase::GetFaviconBitmapIDSizes( |
| favicon_base::FaviconID icon_id, |
| std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { |
| DCHECK(icon_id); |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!bitmap_id_sizes) |
| return result; |
| |
| FaviconBitmapIDSize bitmap_id_size; |
| bitmap_id_size.bitmap_id = statement.ColumnInt64(0); |
| bitmap_id_size.pixel_size = |
| gfx::Size(statement.ColumnInt(1), statement.ColumnInt(2)); |
| bitmap_id_sizes->push_back(bitmap_id_size); |
| } |
| return result; |
| } |
| |
| bool FaviconDatabase::GetFaviconBitmaps( |
| favicon_base::FaviconID icon_id, |
| std::vector<FaviconBitmap>* favicon_bitmaps) { |
| DCHECK(icon_id); |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT id, last_updated, image_data, width, height, last_requested " |
| "FROM favicon_bitmaps WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!favicon_bitmaps) |
| return result; |
| |
| FaviconBitmap favicon_bitmap; |
| favicon_bitmap.bitmap_id = statement.ColumnInt64(0); |
| favicon_bitmap.icon_id = icon_id; |
| favicon_bitmap.last_updated = statement.ColumnTime(1); |
| if (std::vector<uint8_t> bitmap_data_blob = statement.ColumnBlobAsVector(2); |
| !bitmap_data_blob.empty()) { |
| favicon_bitmap.bitmap_data = base::MakeRefCounted<base::RefCountedBytes>( |
| std::move(bitmap_data_blob)); |
| } |
| favicon_bitmap.pixel_size = |
| gfx::Size(statement.ColumnInt(3), statement.ColumnInt(4)); |
| favicon_bitmap.last_requested = statement.ColumnTime(5); |
| favicon_bitmaps->push_back(favicon_bitmap); |
| } |
| return result; |
| } |
| |
| bool FaviconDatabase::GetFaviconBitmap( |
| FaviconBitmapID bitmap_id, |
| base::Time* last_updated, |
| base::Time* last_requested, |
| scoped_refptr<base::RefCountedMemory>* png_icon_data, |
| gfx::Size* pixel_size) { |
| DCHECK(bitmap_id); |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT last_updated, image_data, width, height, last_requested " |
| "FROM favicon_bitmaps WHERE id=?")); |
| statement.BindInt64(0, bitmap_id); |
| |
| if (!statement.Step()) |
| return false; |
| |
| if (last_updated) { |
| *last_updated = statement.ColumnTime(0); |
| } |
| |
| if (png_icon_data) { |
| std::vector<uint8_t> png_data_blob = statement.ColumnBlobAsVector(1); |
| if (!png_data_blob.empty()) |
| *png_icon_data = |
| base::MakeRefCounted<base::RefCountedBytes>(std::move(png_data_blob)); |
| } |
| |
| if (pixel_size) { |
| *pixel_size = gfx::Size(statement.ColumnInt(2), statement.ColumnInt(3)); |
| } |
| |
| if (last_requested) { |
| *last_requested = statement.ColumnTime(4); |
| } |
| |
| return true; |
| } |
| |
| FaviconBitmapID FaviconDatabase::AddFaviconBitmap( |
| favicon_base::FaviconID icon_id, |
| scoped_refptr<base::RefCountedMemory> icon_data, |
| FaviconBitmapType type, |
| base::Time time, |
| const gfx::Size& pixel_size) { |
| DCHECK(icon_id); |
| |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, " |
| "last_requested, width, height) VALUES (?, ?, ?, ?, ?, ?)")); |
| |
| statement.BindInt64(0, icon_id); |
| if (icon_data.get() && icon_data->size()) { |
| statement.BindBlob(1, std::move(icon_data)); |
| } else { |
| statement.BindNull(1); |
| } |
| |
| // On-visit bitmaps: |
| // - keep track of last_updated: last write time is used for expiration; |
| // - always have last_requested==0: no need to keep track of last read time. |
| type == ON_VISIT ? statement.BindTime(2, time) : statement.BindInt64(2, 0); |
| // On-demand bitmaps: |
| // - always have last_updated==0: last write time is not stored as they are |
| // always expired and thus ready to be replaced by ON_VISIT icons; |
| // - keep track of last_requested: last read time is used for cache eviction. |
| type == ON_DEMAND ? statement.BindTime(3, time) : statement.BindInt64(3, 0); |
| |
| statement.BindInt(4, pixel_size.width()); |
| statement.BindInt(5, pixel_size.height()); |
| |
| if (!statement.Run()) |
| return 0; |
| return db_.GetLastInsertRowId(); |
| } |
| |
| bool FaviconDatabase::SetFaviconBitmap( |
| FaviconBitmapID bitmap_id, |
| scoped_refptr<base::RefCountedMemory> bitmap_data, |
| base::Time time) { |
| DCHECK(bitmap_id); |
| // By updating last_updated timestamp, we assume the icon is of type ON_VISIT. |
| // If it is ON_DEMAND, reset last_requested to 0 and thus silently change the |
| // type to ON_VISIT. |
| sql::Statement statement( |
| db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET image_data=?, " |
| "last_updated=?, last_requested=? WHERE id=?")); |
| if (bitmap_data.get() && bitmap_data->size()) { |
| statement.BindBlob(0, std::move(bitmap_data)); |
| } else { |
| statement.BindNull(0); |
| } |
| statement.BindTime(1, time); |
| statement.BindInt64(2, 0); |
| statement.BindInt64(3, bitmap_id); |
| |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::SetFaviconBitmapLastUpdateTime(FaviconBitmapID bitmap_id, |
| base::Time time) { |
| DCHECK(bitmap_id); |
| // By updating last_updated timestamp, we assume the icon is of type ON_VISIT. |
| // If it is ON_DEMAND, reset last_requested to 0 and thus silently change the |
| // type to ON_VISIT. |
| sql::Statement statement( |
| db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_updated=?, " |
| "last_requested=? WHERE id=?")); |
| statement.BindTime(0, time); |
| statement.BindInt64(1, 0); |
| statement.BindInt64(2, bitmap_id); |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::SetFaviconsOutOfDateBetween(base::Time begin, |
| base::Time end) { |
| if (end.is_null()) |
| end = base::Time::Max(); |
| sql::Statement statement( |
| db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_updated=0 " |
| "WHERE last_updated>=? AND last_updated<?")); |
| statement.BindTime(0, begin); |
| statement.BindTime(1, end); |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::TouchOnDemandFavicon(const GURL& icon_url, |
| base::Time time) { |
| // Look up the icon ids for the url. |
| sql::Statement id_statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "SELECT id FROM favicons WHERE url=?")); |
| id_statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url)); |
| |
| base::Time max_time = time - base::Days(kFaviconUpdateLastRequestedAfterDays); |
| |
| while (id_statement.Step()) { |
| favicon_base::FaviconID icon_id = id_statement.ColumnInt64(0); |
| |
| // Update the time only for ON_DEMAND bitmaps (i.e. with last_requested > |
| // 0). For performance reasons, update the time only if the currently stored |
| // time is old enough (UPDATEs where the WHERE condition does not match any |
| // entries are way faster than UPDATEs that really change some data). |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_requested=? WHERE icon_id=? AND " |
| "last_requested>0 AND last_requested<=?")); |
| statement.BindTime(0, time); |
| statement.BindInt64(1, icon_id); |
| statement.BindTime(2, max_time); |
| if (!statement.Run()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool FaviconDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "DELETE FROM favicon_bitmaps WHERE id=?")); |
| statement.BindInt64(0, bitmap_id); |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::SetFaviconOutOfDate(favicon_base::FaviconID icon_id) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); |
| statement.BindInt64(0, 0); |
| statement.BindInt64(1, icon_id); |
| |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::GetFaviconLastUpdatedTime(favicon_base::FaviconID icon_id, |
| base::Time* last_updated) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT MAX(last_updated) FROM favicon_bitmaps WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| if (!statement.Step()) |
| return false; |
| |
| // Return false also if there there is no bitmap with `icon_id`. |
| if (statement.GetColumnType(0) == sql::ColumnType::kNull) |
| return false; |
| |
| if (last_updated) { |
| *last_updated = statement.ColumnTime(0); |
| } |
| return true; |
| } |
| |
| favicon_base::FaviconID FaviconDatabase::GetFaviconIDForFaviconURL( |
| const GURL& icon_url, |
| favicon_base::IconType icon_type, |
| const url::Origin& page_origin) { |
| // Look to see if there even is any relevant cached entry. |
| auto const icon_id = GetFaviconIDForFaviconURL(icon_url, icon_type); |
| if (!icon_id) { |
| return icon_id; |
| } |
| |
| // Check existing mappings to see if any are for the same origin. |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "SELECT page_url FROM icon_mapping WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| while (statement.Step()) { |
| const auto candidate_origin = |
| url::Origin::Create(GURL(statement.ColumnStringView(0))); |
| if (candidate_origin == page_origin) { |
| return icon_id; |
| } |
| } |
| |
| // Act as if there is no entry in the cache if no mapping exists. |
| return 0; |
| } |
| |
| favicon_base::FaviconID FaviconDatabase::GetFaviconIDForFaviconURL( |
| const GURL& icon_url, |
| favicon_base::IconType icon_type) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "SELECT id FROM favicons WHERE url=? AND icon_type=?")); |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url)); |
| statement.BindInt(1, ToPersistedIconType(icon_type)); |
| |
| if (!statement.Step()) |
| return 0; // not cached |
| |
| return statement.ColumnInt64(0); |
| } |
| |
| bool FaviconDatabase::GetFaviconHeader(favicon_base::FaviconID icon_id, |
| GURL* icon_url, |
| favicon_base::IconType* icon_type) { |
| DCHECK(icon_id); |
| |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "SELECT url, icon_type FROM favicons WHERE id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| if (!statement.Step()) |
| return false; // No entry for the id. |
| |
| if (icon_url) |
| *icon_url = GURL(statement.ColumnStringView(0)); |
| if (icon_type) |
| *icon_type = FromPersistedIconType(statement.ColumnInt(1)); |
| |
| return true; |
| } |
| |
| favicon_base::FaviconID FaviconDatabase::AddFavicon( |
| const GURL& icon_url, |
| favicon_base::IconType icon_type) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url)); |
| statement.BindInt(1, ToPersistedIconType(icon_type)); |
| |
| if (!statement.Run()) |
| return 0; |
| return db_.GetLastInsertRowId(); |
| } |
| |
| favicon_base::FaviconID FaviconDatabase::AddFavicon( |
| const GURL& icon_url, |
| favicon_base::IconType icon_type, |
| scoped_refptr<base::RefCountedMemory> icon_data, |
| FaviconBitmapType type, |
| base::Time time, |
| const gfx::Size& pixel_size) { |
| favicon_base::FaviconID icon_id = AddFavicon(icon_url, icon_type); |
| if (!icon_id || !AddFaviconBitmap(icon_id, std::move(icon_data), type, time, |
| pixel_size)) { |
| return 0; |
| } |
| |
| return icon_id; |
| } |
| |
| bool FaviconDatabase::DeleteFavicon(favicon_base::FaviconID id) { |
| sql::Statement statement; |
| statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM favicons WHERE id = ?")); |
| statement.BindInt64(0, id); |
| if (!statement.Run()) |
| return false; |
| |
| statement.Assign(db_.GetCachedStatement( |
| SQL_FROM_HERE, "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); |
| statement.BindInt64(0, id); |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::GetIconMappingsForPageURL( |
| const GURL& page_url, |
| const favicon_base::IconTypeSet& required_icon_types, |
| std::vector<IconMapping>* filtered_mapping_data) { |
| std::vector<IconMapping> mapping_data; |
| if (!GetIconMappingsForPageURL(page_url, &mapping_data)) |
| return false; |
| |
| bool result = false; |
| for (auto m = mapping_data.begin(); m != mapping_data.end(); ++m) { |
| if (required_icon_types.count(m->icon_type) != 0) { |
| result = true; |
| if (!filtered_mapping_data) |
| return result; |
| |
| filtered_mapping_data->push_back(*m); |
| } |
| } |
| return result; |
| } |
| |
| bool FaviconDatabase::GetIconMappingsForPageURL( |
| const GURL& page_url, |
| std::vector<IconMapping>* mapping_data) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " |
| "favicons.url, icon_mapping.page_url_type " |
| "FROM icon_mapping " |
| "INNER JOIN favicons " |
| "ON icon_mapping.icon_id = favicons.id " |
| "WHERE icon_mapping.page_url=? " |
| "ORDER BY favicons.icon_type DESC")); |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url)); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!mapping_data) |
| return result; |
| |
| IconMapping icon_mapping; |
| FillIconMapping(page_url, statement, &icon_mapping); |
| mapping_data->push_back(icon_mapping); |
| } |
| return result; |
| } |
| |
| std::optional<GURL> FaviconDatabase::FindBestPageURLForHost( |
| const GURL& url, |
| const favicon_base::IconTypeSet& required_icon_types) { |
| if (url.GetHost().empty()) { |
| return std::nullopt; |
| } |
| |
| // This query prioritizes PageUrlType::kRegular over PageUrlType::kRedirect. |
| // If PageUrlType is ever changed the ORDER BY clause for page_url_type may |
| // need to be revised. |
| CHECK_EQ(PageUrlType::kRedirect, PageUrlType::kMaxValue); |
| sql::Statement statement( |
| db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT icon_mapping.page_url, favicons.icon_type " |
| "FROM icon_mapping " |
| "INNER JOIN favicons " |
| "ON icon_mapping.icon_id = favicons.id " |
| "WHERE (page_url >= ? AND page_url < ?) " |
| "OR (page_url >= ? AND page_url < ?) " |
| "ORDER BY icon_mapping.page_url_type ASC, " |
| "favicons.icon_type DESC")); |
| |
| // This is an optimization to avoid using the LIKE operator which can be |
| // expensive. This statement finds all rows where page_url starts from either |
| // "http://<host>/" or "https://<host>/". |
| std::string http_prefix = |
| base::StringPrintf("http://%s/", url.GetHost().c_str()); |
| statement.BindString(0, http_prefix); |
| statement.BindString(1, database_utils::UpperBoundString(http_prefix)); |
| std::string https_prefix = |
| base::StringPrintf("https://%s/", url.GetHost().c_str()); |
| statement.BindString(2, https_prefix); |
| statement.BindString(3, database_utils::UpperBoundString(https_prefix)); |
| |
| while (statement.Step()) { |
| favicon_base::IconType icon_type = |
| FaviconDatabase::FromPersistedIconType(statement.ColumnInt(1)); |
| |
| if (required_icon_types.count(icon_type) != 0) |
| return std::make_optional(GURL(statement.ColumnStringView(0))); |
| } |
| return std::nullopt; |
| } |
| |
| IconMappingID FaviconDatabase::AddIconMapping(const GURL& page_url, |
| favicon_base::FaviconID icon_id, |
| PageUrlType page_url_type) { |
| static const char kSql[] = |
| "INSERT INTO icon_mapping (page_url, icon_id, page_url_type) " |
| "VALUES (?, ?, ?)"; |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url)); |
| statement.BindInt64(1, icon_id); |
| statement.BindInt64(2, ToPersistedPageUrlType(page_url_type)); |
| |
| if (!statement.Run()) { |
| return 0; |
| } |
| |
| return db_.GetLastInsertRowId(); |
| } |
| |
| bool FaviconDatabase::DeleteIconMappings(const GURL& page_url) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "DELETE FROM icon_mapping WHERE page_url = ?")); |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url)); |
| |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::DeleteIconMappingsForFaviconId( |
| favicon_base::FaviconID id) { |
| // This is called rarely during history expiration cleanup and hence not |
| // worth caching. |
| sql::Statement statement( |
| db_.GetUniqueStatement("DELETE FROM icon_mapping WHERE icon_id=?")); |
| statement.BindInt64(0, id); |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::DeleteIconMapping(IconMappingID mapping_id) { |
| sql::Statement statement(db_.GetCachedStatement( |
| SQL_FROM_HERE, "DELETE FROM icon_mapping WHERE id=?")); |
| statement.BindInt64(0, mapping_id); |
| |
| return statement.Run(); |
| } |
| |
| bool FaviconDatabase::HasMappingFor(favicon_base::FaviconID id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT id FROM icon_mapping " |
| "WHERE icon_id=?")); |
| statement.BindInt64(0, id); |
| |
| return statement.Step(); |
| } |
| |
| std::vector<favicon_base::FaviconID> |
| FaviconDatabase::GetFaviconsLastUpdatedBefore(base::Time time, int max_count) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT icon_id " |
| "FROM favicon_bitmaps " |
| "WHERE last_updated < ? " |
| "ORDER BY last_updated ASC " |
| "LIMIT ?")); |
| statement.BindTime(0, time); |
| statement.BindInt64( |
| 1, max_count == 0 ? std::numeric_limits<int64_t>::max() : max_count); |
| std::vector<favicon_base::FaviconID> ids; |
| while (statement.Step()) |
| ids.push_back(statement.ColumnInt64(0)); |
| return ids; |
| } |
| |
| bool FaviconDatabase::InitIconMappingEnumerator( |
| favicon_base::IconType type, |
| IconMappingEnumerator* enumerator) { |
| DCHECK(!enumerator->statement_.is_valid()); |
| enumerator->statement_.Assign(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " |
| "favicons.url, icon_mapping.page_url " |
| "FROM icon_mapping JOIN favicons ON (" |
| "icon_mapping.icon_id = favicons.id) " |
| "WHERE favicons.icon_type = ?")); |
| enumerator->statement_.BindInt(0, ToPersistedIconType(type)); |
| return enumerator->statement_.is_valid(); |
| } |
| |
| bool FaviconDatabase::RetainDataForPageUrls( |
| const std::vector<GURL>& urls_to_keep) { |
| sql::Transaction transaction(&db_); |
| if (!transaction.Begin()) |
| return false; |
| |
| // Populate temp.retained_urls with `urls_to_keep`. |
| { |
| static const char kCreateRetainedUrls[] = |
| "CREATE TEMP TABLE retained_urls (url LONGVARCHAR PRIMARY KEY)"; |
| if (!db_.Execute(kCreateRetainedUrls)) |
| return false; |
| |
| static const char kRetainedUrlSql[] = |
| "INSERT OR IGNORE INTO temp.retained_urls (url) VALUES (?)"; |
| sql::Statement statement(db_.GetUniqueStatement(kRetainedUrlSql)); |
| for (const GURL& url : urls_to_keep) { |
| statement.BindString(0, database_utils::GurlToDatabaseUrl(url)); |
| if (!statement.Run()) |
| return false; |
| statement.Reset(true); |
| } |
| } |
| |
| // temp.icon_id_mapping generates new icon ids as consecutive |
| // integers starting from 1, and maps them to the old icon ids. |
| { |
| static const char kIconMappingCreate[] = |
| "CREATE TEMP TABLE icon_id_mapping " |
| "(" |
| "new_icon_id INTEGER PRIMARY KEY," |
| "old_icon_id INTEGER NOT NULL UNIQUE" |
| ")"; |
| if (!db_.Execute(kIconMappingCreate)) |
| return false; |
| |
| // Insert the icon ids for retained urls, skipping duplicates. |
| static const char kIconMappingSql[] = |
| "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " |
| "SELECT icon_id FROM icon_mapping " |
| "JOIN temp.retained_urls " |
| "ON (temp.retained_urls.url = icon_mapping.page_url)"; |
| if (!db_.Execute(kIconMappingSql)) |
| return false; |
| } |
| |
| static const char kRenameIconMappingTable[] = |
| "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; |
| static const char kCopyIconMapping[] = |
| "INSERT INTO icon_mapping (page_url, icon_id, page_url_type) " |
| "SELECT temp.retained_urls.url, mapping.new_icon_id, old.page_url_type " |
| "FROM temp.retained_urls " |
| "JOIN old_icon_mapping AS old " |
| "ON (temp.retained_urls.url = old.page_url) " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.icon_id = mapping.old_icon_id)"; |
| static const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; |
| |
| static const char kRenameFaviconsTable[] = |
| "ALTER TABLE favicons RENAME TO old_favicons"; |
| static const char kCopyFavicons[] = |
| "INSERT INTO favicons (id, url, icon_type) " |
| "SELECT mapping.new_icon_id, old.url, old.icon_type " |
| "FROM old_favicons AS old " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.id = mapping.old_icon_id)"; |
| static const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; |
| |
| // Set the retained favicon bitmaps to be expired (last_updated == 0). |
| // The user may be deleting their favicon bitmaps because the favicon bitmaps |
| // are incorrect. Expiring a favicon bitmap causes it to be redownloaded when |
| // the user visits a page associated with the favicon bitmap. See |
| // crbug.com/474421 for an example of a bug which caused favicon bitmaps to |
| // become incorrect. |
| static const char kRenameFaviconBitmapsTable[] = |
| "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; |
| static const char kCopyFaviconBitmaps[] = |
| "INSERT INTO favicon_bitmaps " |
| " (icon_id, last_updated, image_data, width, height, last_requested) " |
| "SELECT mapping.new_icon_id, 0, old.image_data, old.width, old.height," |
| " old.last_requested " |
| "FROM old_favicon_bitmaps AS old " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.icon_id = mapping.old_icon_id)"; |
| static const char kDropOldFaviconBitmapsTable[] = |
| "DROP TABLE old_favicon_bitmaps"; |
| |
| // Rename existing tables to new location. |
| if (!db_.Execute(kRenameIconMappingTable) || |
| !db_.Execute(kRenameFaviconsTable) || |
| !db_.Execute(kRenameFaviconBitmapsTable)) { |
| return false; |
| } |
| |
| // Initialize the replacement tables. At this point the old indices |
| // still exist (pointing to the old_* tables), so do not initialize |
| // the indices. |
| if (!InitTables(&db_)) |
| return false; |
| |
| // Copy all of the data over. |
| if (!db_.Execute(kCopyIconMapping) || !db_.Execute(kCopyFavicons) || |
| !db_.Execute(kCopyFaviconBitmaps)) { |
| return false; |
| } |
| |
| // Drop the old_* tables, which also drops the indices. |
| if (!db_.Execute(kDropOldIconMappingTable) || |
| !db_.Execute(kDropOldFaviconsTable) || |
| !db_.Execute(kDropOldFaviconBitmapsTable)) { |
| return false; |
| } |
| |
| // Recreate the indices. |
| // TODO(shess): UNIQUE indices could fail due to duplication. This |
| // could happen in case of corruption. |
| if (!InitIndices(&db_)) |
| return false; |
| |
| static const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; |
| static const char kRetainedUrlsDrop[] = "DROP TABLE temp.retained_urls"; |
| if (!db_.Execute(kIconMappingDrop) || !db_.Execute(kRetainedUrlsDrop)) |
| return false; |
| |
| return transaction.Commit(); |
| } |
| |
| // static |
| int FaviconDatabase::ToPersistedIconType(favicon_base::IconType icon_type) { |
| if (icon_type == favicon_base::IconType::kInvalid) |
| return 0; |
| |
| return 1 << (static_cast<int>(icon_type) - 1); |
| } |
| |
| // static |
| favicon_base::IconType FaviconDatabase::FromPersistedIconType(int icon_type) { |
| if (icon_type == 0) |
| return favicon_base::IconType::kInvalid; |
| |
| int val = std::bit_width<uint32_t>(icon_type); |
| if (val > static_cast<int>(favicon_base::IconType::kMax)) |
| return favicon_base::IconType::kInvalid; |
| |
| return static_cast<favicon_base::IconType>(val); |
| } |
| |
| // static |
| int FaviconDatabase::ToPersistedPageUrlType(PageUrlType page_url_type) { |
| return static_cast<int>(page_url_type); |
| } |
| |
| // static |
| PageUrlType FaviconDatabase::FromPersistedPageUrlType(int page_url_type) { |
| return static_cast<PageUrlType>(page_url_type); |
| } |
| |
| // static |
| void FaviconDatabase::FillIconMapping(const GURL& page_url, |
| sql::Statement& statement, |
| IconMapping* icon_mapping) { |
| icon_mapping->mapping_id = statement.ColumnInt64(0); |
| icon_mapping->icon_id = statement.ColumnInt64(1); |
| icon_mapping->icon_type = FromPersistedIconType(statement.ColumnInt(2)); |
| icon_mapping->icon_url = GURL(statement.ColumnStringView(3)); |
| icon_mapping->page_url = page_url; |
| icon_mapping->page_url_type = |
| FromPersistedPageUrlType(statement.ColumnInt64(4)); |
| } |
| |
| sql::InitStatus FaviconDatabase::OpenDatabase(sql::Database* db, |
| const base::FilePath& db_name) { |
| // `OpenDatabase()` may be called repeatedly on the same `db`. Ensure that we |
| // don't attempt to overwrite an existing error callback. |
| if (!db_.has_error_callback()) { |
| db->set_error_callback(base::BindRepeating(&DatabaseErrorCallback, db)); |
| } |
| return db->Open(db_name) ? sql::INIT_OK : sql::INIT_FAILURE; |
| } |
| |
| sql::InitStatus FaviconDatabase::InitImpl(const base::FilePath& db_name) { |
| sql::InitStatus status = OpenDatabase(&db_, db_name); |
| if (status != sql::INIT_OK) |
| return status; |
| |
| // Clear databases which are too old to process. |
| DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber); |
| if (sql::MetaTable::RazeIfIncompatible( |
| &db_, /*lowest_supported_version=*/kDeprecatedVersionNumber + 1, |
| kCurrentVersionNumber) == sql::RazeIfIncompatibleResult::kFailed) { |
| return sql::INIT_FAILURE; |
| } |
| |
| // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and |
| // 25. Future versions are not destroyed because that could lead to |
| // data loss if the profile is opened by a later channel, but |
| // perhaps a heuristic like >kCurrentVersionNumber+3 could be used. |
| |
| // Scope initialization in a transaction so we can't be partially initialized. |
| sql::Transaction transaction(&db_); |
| if (!transaction.Begin()) |
| return sql::INIT_FAILURE; |
| |
| // TODO(shess): Failing Begin() implies that something serious is |
| // wrong with the database. Raze() may be in order. |
| |
| #if BUILDFLAG(IS_APPLE) |
| // Exclude the favicons file from backups. |
| base::apple::SetBackupExclusion(db_name); |
| #endif |
| |
| // thumbnails table has been obsolete for a long time, remove any detritus. |
| std::ignore = db_.Execute("DROP TABLE IF EXISTS thumbnails"); |
| |
| // At some point, operations involving temporary tables weren't done |
| // atomically and users have been stranded. Drop those tables and |
| // move on. |
| // TODO(shess): Prove it? Audit all cases and see if it's possible |
| // that this implies non-atomic update, and should thus be handled |
| // via the corruption handler. |
| std::ignore = db_.Execute("DROP TABLE IF EXISTS temp_favicons"); |
| std::ignore = db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps"); |
| std::ignore = db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping"); |
| |
| // Create the tables. |
| if (!meta_table_.Init(&db_, kCurrentVersionNumber, |
| kCompatibleVersionNumber) || |
| !InitTables(&db_) || !InitIndices(&db_)) { |
| return sql::INIT_FAILURE; |
| } |
| |
| // Version check. We should not encounter a database too old for us to handle |
| // in the wild, so we try to continue in that case. |
| if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { |
| LOG(WARNING) << "Favicon database is too new."; |
| return sql::INIT_TOO_NEW; |
| } |
| |
| int cur_version = meta_table_.GetVersionNumber(); |
| |
| if (cur_version == 8) { |
| ++cur_version; |
| if (!UpgradeToVersion9()) { |
| return CantUpgradeToVersion(cur_version); |
| } |
| } |
| |
| LOG_IF(WARNING, cur_version < kCurrentVersionNumber) |
| << "Favicon database version " << cur_version << " is too old to handle."; |
| |
| // Initialization is complete. |
| if (!transaction.Commit()) { |
| LOG(ERROR) << "Favicon init transaction commit failure."; |
| return sql::INIT_FAILURE; |
| } |
| |
| // Raze the database if the structure of the favicons database is not what |
| // it should be. This error cannot be detected via the SQL error code because |
| // the error code for running SQL statements against a database with missing |
| // columns is SQLITE_ERROR which is not unique enough to act upon. |
| // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. |
| // (crbug.com/166453) |
| if (IsFaviconDBStructureIncorrect()) { |
| LOG(ERROR) << "Raze because of invalid favicon db structure."; |
| |
| db_.RazeAndPoison(); |
| return sql::INIT_FAILURE; |
| } |
| |
| return sql::INIT_OK; |
| } |
| |
| sql::InitStatus FaviconDatabase::CantUpgradeToVersion(int cur_version) { |
| LOG(WARNING) << "Unable to update to favicon database to version " |
| << cur_version << "."; |
| db_.Close(); |
| return sql::INIT_FAILURE; |
| } |
| |
| bool FaviconDatabase::UpgradeToVersion9() { |
| // Add the page_url_type column to the icon_mapping table. |
| static const char kIconMappingAddPageUrlTypeSql[] = |
| "ALTER TABLE icon_mapping ADD COLUMN page_url_type INTEGER DEFAULT 0"; |
| if (!db_.Execute(kIconMappingAddPageUrlTypeSql)) { |
| return false; |
| } |
| |
| return meta_table_.SetVersionNumber(9) && |
| meta_table_.SetCompatibleVersionNumber( |
| std::min(9, kCompatibleVersionNumber)); |
| } |
| |
| bool FaviconDatabase::IsFaviconDBStructureIncorrect() { |
| return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); |
| } |
| |
| } // namespace favicon |