1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
|
// 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);
std::vector<uint8_t> bitmap_data_blob;
statement.ColumnBlobAsVector(2, &bitmap_data_blob);
if (!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, &png_data_blob);
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.host().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.host().c_str());
statement.BindString(0, http_prefix);
statement.BindString(1, database_utils::UpperBoundString(http_prefix));
std::string https_prefix =
base::StringPrintf("https://%s/", url.host().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
|