This source file includes following definitions.
- BindFilePath
- ColumnFilePath
- BindFilePath
- ColumnFilePath
- StateToInt
- IntToState
- DangerTypeToInt
- IntToDangerType
- in_progress_entry_cleanup_completed_
- EnsureColumnExists
- MigrateDownloadsState
- MigrateDownloadsReasonPathsAndDangerType
- MigrateReferrer
- MigrateDownloadedByExtension
- MigrateDownloadValidators
- InitDownloadTable
- GetNextDownloadId
- DropDownloadTable
- QueryDownloads
- UpdateDownload
- EnsureInProgressEntriesCleanedUp
- CreateDownload
- RemoveDownload
- RemoveDownloadURLs
- CountDownloads
#include "chrome/browser/history/download_database.h"
#include <limits>
#include <string>
#include <vector>
#include "base/debug/alias.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/history/download_row.h"
#include "chrome/browser/history/history_types.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_item.h"
#include "sql/statement.h"
using content::DownloadItem;
namespace history {
namespace {
enum DroppedReason {
DROPPED_REASON_BAD_STATE = 0,
DROPPED_REASON_BAD_DANGER_TYPE = 1,
DROPPED_REASON_BAD_ID = 2,
DROPPED_REASON_DUPLICATE_ID = 3,
DROPPED_REASON_MAX
};
static const char kSchema[] =
"CREATE TABLE downloads ("
"id INTEGER PRIMARY KEY,"
"current_path LONGVARCHAR NOT NULL,"
"target_path LONGVARCHAR NOT NULL,"
"start_time INTEGER NOT NULL,"
"received_bytes INTEGER NOT NULL,"
"total_bytes INTEGER NOT NULL,"
"state INTEGER NOT NULL,"
"danger_type INTEGER NOT NULL, "
"interrupt_reason INTEGER NOT NULL,"
"end_time INTEGER NOT NULL,"
"opened INTEGER NOT NULL,"
"referrer VARCHAR NOT NULL,"
"by_ext_id VARCHAR NOT NULL,"
"by_ext_name VARCHAR NOT NULL,"
"etag VARCHAR NOT NULL,"
"last_modified VARCHAR NOT NULL)";
static const char kUrlChainSchema[] =
"CREATE TABLE downloads_url_chains ("
"id INTEGER NOT NULL,"
"chain_index INTEGER NOT NULL,"
"url LONGVARCHAR NOT NULL, "
"PRIMARY KEY (id, chain_index) )";
#if defined(OS_POSIX)
void BindFilePath(sql::Statement& statement, const base::FilePath& path,
int col) {
statement.BindString(col, path.value());
}
base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
return base::FilePath(statement.ColumnString(col));
}
#else
void BindFilePath(sql::Statement& statement, const base::FilePath& path,
int col) {
statement.BindString16(col, path.value());
}
base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
return base::FilePath(statement.ColumnString16(col));
}
#endif
}
const int DownloadDatabase::kStateInvalid = -1;
const int DownloadDatabase::kStateInProgress = 0;
const int DownloadDatabase::kStateComplete = 1;
const int DownloadDatabase::kStateCancelled = 2;
const int DownloadDatabase::kStateBug140687 = 3;
const int DownloadDatabase::kStateInterrupted = 4;
const int DownloadDatabase::kDangerTypeInvalid = -1;
const int DownloadDatabase::kDangerTypeNotDangerous = 0;
const int DownloadDatabase::kDangerTypeDangerousFile = 1;
const int DownloadDatabase::kDangerTypeDangerousUrl = 2;
const int DownloadDatabase::kDangerTypeDangerousContent = 3;
const int DownloadDatabase::kDangerTypeMaybeDangerousContent = 4;
const int DownloadDatabase::kDangerTypeUncommonContent = 5;
const int DownloadDatabase::kDangerTypeUserValidated = 6;
const int DownloadDatabase::kDangerTypeDangerousHost = 7;
const int DownloadDatabase::kDangerTypePotentiallyUnwanted = 8;
int DownloadDatabase::StateToInt(DownloadItem::DownloadState state) {
switch (state) {
case DownloadItem::IN_PROGRESS: return DownloadDatabase::kStateInProgress;
case DownloadItem::COMPLETE: return DownloadDatabase::kStateComplete;
case DownloadItem::CANCELLED: return DownloadDatabase::kStateCancelled;
case DownloadItem::INTERRUPTED: return DownloadDatabase::kStateInterrupted;
case DownloadItem::MAX_DOWNLOAD_STATE:
NOTREACHED();
return DownloadDatabase::kStateInvalid;
}
NOTREACHED();
return DownloadDatabase::kStateInvalid;
}
DownloadItem::DownloadState DownloadDatabase::IntToState(int state) {
switch (state) {
case DownloadDatabase::kStateInProgress: return DownloadItem::IN_PROGRESS;
case DownloadDatabase::kStateComplete: return DownloadItem::COMPLETE;
case DownloadDatabase::kStateCancelled: return DownloadItem::CANCELLED;
case DownloadDatabase::kStateInterrupted: return DownloadItem::INTERRUPTED;
default: return DownloadItem::MAX_DOWNLOAD_STATE;
}
}
int DownloadDatabase::DangerTypeToInt(content::DownloadDangerType danger_type) {
switch (danger_type) {
case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
return DownloadDatabase::kDangerTypeNotDangerous;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
return DownloadDatabase::kDangerTypeDangerousFile;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
return DownloadDatabase::kDangerTypeDangerousUrl;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
return DownloadDatabase::kDangerTypeDangerousContent;
case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
return DownloadDatabase::kDangerTypeMaybeDangerousContent;
case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
return DownloadDatabase::kDangerTypeUncommonContent;
case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
return DownloadDatabase::kDangerTypeUserValidated;
case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
return DownloadDatabase::kDangerTypeDangerousHost;
case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
return DownloadDatabase::kDangerTypePotentiallyUnwanted;
case content::DOWNLOAD_DANGER_TYPE_MAX:
NOTREACHED();
return DownloadDatabase::kDangerTypeInvalid;
}
NOTREACHED();
return DownloadDatabase::kDangerTypeInvalid;
}
content::DownloadDangerType DownloadDatabase::IntToDangerType(int danger_type) {
switch (danger_type) {
case DownloadDatabase::kDangerTypeNotDangerous:
return content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
case DownloadDatabase::kDangerTypeDangerousFile:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
case DownloadDatabase::kDangerTypeDangerousUrl:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL;
case DownloadDatabase::kDangerTypeDangerousContent:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT;
case DownloadDatabase::kDangerTypeMaybeDangerousContent:
return content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT;
case DownloadDatabase::kDangerTypeUncommonContent:
return content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT;
case DownloadDatabase::kDangerTypeUserValidated:
return content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
case DownloadDatabase::kDangerTypeDangerousHost:
return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST;
case DownloadDatabase::kDangerTypePotentiallyUnwanted:
return content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED;
default:
return content::DOWNLOAD_DANGER_TYPE_MAX;
}
}
DownloadDatabase::DownloadDatabase()
: owning_thread_set_(false),
owning_thread_(0),
in_progress_entry_cleanup_completed_(false) {
}
DownloadDatabase::~DownloadDatabase() {
}
bool DownloadDatabase::EnsureColumnExists(
const std::string& name, const std::string& type) {
std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type;
return GetDB().DoesColumnExist("downloads", name.c_str()) ||
GetDB().Execute(add_col.c_str());
}
bool DownloadDatabase::MigrateDownloadsState() {
sql::Statement statement(GetDB().GetUniqueStatement(
"UPDATE downloads SET state=? WHERE state=?"));
statement.BindInt(0, kStateInterrupted);
statement.BindInt(1, kStateBug140687);
return statement.Run();
}
bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() {
if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp"))
return false;
if (!GetDB().Execute(kSchema))
return false;
sql::Statement statement_populate(GetDB().GetUniqueStatement(
"INSERT INTO downloads "
"( id, current_path, target_path, start_time, received_bytes, "
" total_bytes, state, danger_type, interrupt_reason, end_time, opened, "
" referrer, by_ext_id, by_ext_name, etag, last_modified ) "
"SELECT id, full_path, full_path, "
" CASE start_time WHEN 0 THEN 0 ELSE "
" (start_time + 11644473600) * 1000000 END, "
" received_bytes, total_bytes, "
" state, ?, ?, "
" CASE end_time WHEN 0 THEN 0 ELSE "
" (end_time + 11644473600) * 1000000 END, "
" opened, \"\", \"\", \"\", \"\", \"\" "
"FROM downloads_tmp"));
statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE);
statement_populate.BindInt(1, kDangerTypeNotDangerous);
if (!statement_populate.Run())
return false;
if (!GetDB().Execute(kUrlChainSchema))
return false;
if (!GetDB().Execute("INSERT INTO downloads_url_chains "
" ( id, chain_index, url) "
" SELECT id, 0, url from downloads_tmp"))
return false;
if (!GetDB().Execute("DROP TABLE downloads_tmp"))
return false;
return true;
}
bool DownloadDatabase::MigrateReferrer() {
return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::MigrateDownloadedByExtension() {
return EnsureColumnExists("by_ext_id", "VARCHAR NOT NULL DEFAULT \"\"") &&
EnsureColumnExists("by_ext_name", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::MigrateDownloadValidators() {
return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") &&
EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\"");
}
bool DownloadDatabase::InitDownloadTable() {
if (GetDB().DoesTableExist("downloads")) {
return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") &&
EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0");
} else {
return (!GetDB().DoesTableExist("downloads_url_chain") &&
GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema));
}
}
uint32 DownloadDatabase::GetNextDownloadId() {
sql::Statement select_max_id(GetDB().GetUniqueStatement(
"SELECT max(id) FROM downloads"));
bool result = select_max_id.Step();
DCHECK(result);
return 1 + static_cast<uint32>(std::max(
static_cast<int64>(content::DownloadItem::kInvalidId),
select_max_id.ColumnInt64(0)));
}
bool DownloadDatabase::DropDownloadTable() {
return GetDB().Execute("DROP TABLE downloads");
}
void DownloadDatabase::QueryDownloads(
std::vector<DownloadRow>* results) {
EnsureInProgressEntriesCleanedUp();
results->clear();
std::set<uint32> ids;
std::map<uint32, DownloadRow*> info_map;
sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT id, current_path, target_path, start_time, received_bytes, "
"total_bytes, state, danger_type, interrupt_reason, end_time, opened, "
"referrer, by_ext_id, by_ext_name, etag, last_modified "
"FROM downloads ORDER BY start_time"));
while (statement_main.Step()) {
scoped_ptr<DownloadRow> info(new DownloadRow());
int column = 0;
int64 signed_id = statement_main.ColumnInt64(column++);
info->id = static_cast<uint32>(signed_id);
info->current_path = ColumnFilePath(statement_main, column++);
info->target_path = ColumnFilePath(statement_main, column++);
info->start_time = base::Time::FromInternalValue(
statement_main.ColumnInt64(column++));
info->received_bytes = statement_main.ColumnInt64(column++);
info->total_bytes = statement_main.ColumnInt64(column++);
int state = statement_main.ColumnInt(column++);
info->state = IntToState(state);
if (info->state == DownloadItem::MAX_DOWNLOAD_STATE)
UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state);
info->danger_type = IntToDangerType(statement_main.ColumnInt(column++));
info->interrupt_reason = static_cast<content::DownloadInterruptReason>(
statement_main.ColumnInt(column++));
info->end_time = base::Time::FromInternalValue(
statement_main.ColumnInt64(column++));
info->opened = statement_main.ColumnInt(column++) != 0;
info->referrer_url = GURL(statement_main.ColumnString(column++));
info->by_ext_id = statement_main.ColumnString(column++);
info->by_ext_name = statement_main.ColumnString(column++);
info->etag = statement_main.ColumnString(column++);
info->last_modified = statement_main.ColumnString(column++);
DroppedReason dropped_reason = DROPPED_REASON_MAX;
if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId)) {
dropped_reason = DROPPED_REASON_BAD_ID;
} else if (!ids.insert(info->id).second) {
dropped_reason = DROPPED_REASON_DUPLICATE_ID;
NOTREACHED() << info->id;
} else if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) {
dropped_reason = DROPPED_REASON_BAD_STATE;
} else if (info->danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
dropped_reason = DROPPED_REASON_BAD_DANGER_TYPE;
}
if (dropped_reason != DROPPED_REASON_MAX) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseRecordDropped",
dropped_reason,
DROPPED_REASON_MAX + 1);
} else {
DCHECK(!ContainsKey(info_map, info->id));
uint32 id = info->id;
info_map[id] = info.release();
}
}
sql::Statement statement_chain(GetDB().GetCachedStatement(
SQL_FROM_HERE,
"SELECT id, chain_index, url FROM downloads_url_chains "
"ORDER BY id, chain_index"));
while (statement_chain.Step()) {
int column = 0;
int64 signed_id = statement_chain.ColumnInt64(column++);
int chain_index = statement_chain.ColumnInt(column++);
if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId))
continue;
uint32 id = static_cast<uint32>(signed_id);
DCHECK(ContainsKey(info_map, id));
if (!ContainsKey(info_map, id))
continue;
int current_chain_size = info_map[id]->url_chain.size();
std::vector<GURL>* url_chain(&info_map[id]->url_chain);
DCHECK_EQ(chain_index, current_chain_size);
while (current_chain_size < chain_index) {
url_chain->push_back(GURL());
current_chain_size++;
}
if (current_chain_size > chain_index)
continue;
url_chain->push_back(GURL(statement_chain.ColumnString(2)));
}
for (std::map<uint32, DownloadRow*>::iterator
it = info_map.begin(); it != info_map.end(); ++it) {
DownloadRow* row = it->second;
bool empty_url_chain = row->url_chain.empty();
UMA_HISTOGRAM_BOOLEAN("Download.DatabaseEmptyUrlChain", empty_url_chain);
if (empty_url_chain) {
RemoveDownload(row->id);
} else {
results->push_back(*row);
}
delete row;
it->second = NULL;
}
}
bool DownloadDatabase::UpdateDownload(const DownloadRow& data) {
EnsureInProgressEntriesCleanedUp();
DCHECK_NE(content::DownloadItem::kInvalidId, data.id);
int state = StateToInt(data.state);
if (state == kStateInvalid) {
NOTREACHED();
return false;
}
int danger_type = DangerTypeToInt(data.danger_type);
if (danger_type == kDangerTypeInvalid) {
NOTREACHED();
return false;
}
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"UPDATE downloads "
"SET current_path=?, target_path=?, received_bytes=?, state=?, "
"danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, "
"opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? "
"WHERE id=?"));
int column = 0;
BindFilePath(statement, data.current_path, column++);
BindFilePath(statement, data.target_path, column++);
statement.BindInt64(column++, data.received_bytes);
statement.BindInt(column++, state);
statement.BindInt(column++, danger_type);
statement.BindInt(column++, static_cast<int>(data.interrupt_reason));
statement.BindInt64(column++, data.end_time.ToInternalValue());
statement.BindInt64(column++, data.total_bytes);
statement.BindInt(column++, (data.opened ? 1 : 0));
statement.BindString(column++, data.by_ext_id);
statement.BindString(column++, data.by_ext_name);
statement.BindString(column++, data.etag);
statement.BindString(column++, data.last_modified);
statement.BindInt(column++, data.id);
return statement.Run();
}
void DownloadDatabase::EnsureInProgressEntriesCleanedUp() {
if (in_progress_entry_cleanup_completed_)
return;
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"UPDATE downloads SET state=?, interrupt_reason=? WHERE state=?"));
statement.BindInt(0, kStateInterrupted);
statement.BindInt(1, content::DOWNLOAD_INTERRUPT_REASON_CRASH);
statement.BindInt(2, kStateInProgress);
statement.Run();
in_progress_entry_cleanup_completed_ = true;
}
bool DownloadDatabase::CreateDownload(const DownloadRow& info) {
DCHECK_NE(content::DownloadItem::kInvalidId, info.id);
EnsureInProgressEntriesCleanedUp();
if (info.url_chain.empty())
return false;
int state = StateToInt(info.state);
if (state == kStateInvalid)
return false;
int danger_type = DangerTypeToInt(info.danger_type);
if (danger_type == kDangerTypeInvalid)
return false;
{
sql::Statement statement_insert(GetDB().GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO downloads "
"(id, current_path, target_path, start_time, "
" received_bytes, total_bytes, state, danger_type, interrupt_reason, "
" end_time, opened, referrer, by_ext_id, by_ext_name, etag, "
" last_modified) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
int column = 0;
statement_insert.BindInt(column++, info.id);
BindFilePath(statement_insert, info.current_path, column++);
BindFilePath(statement_insert, info.target_path, column++);
statement_insert.BindInt64(column++, info.start_time.ToInternalValue());
statement_insert.BindInt64(column++, info.received_bytes);
statement_insert.BindInt64(column++, info.total_bytes);
statement_insert.BindInt(column++, state);
statement_insert.BindInt(column++, danger_type);
statement_insert.BindInt(column++, info.interrupt_reason);
statement_insert.BindInt64(column++, info.end_time.ToInternalValue());
statement_insert.BindInt(column++, info.opened ? 1 : 0);
statement_insert.BindString(column++, info.referrer_url.spec());
statement_insert.BindString(column++, info.by_ext_id);
statement_insert.BindString(column++, info.by_ext_name);
statement_insert.BindString(column++, info.etag);
statement_insert.BindString(column++, info.last_modified);
if (!statement_insert.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainInsertError",
GetDB().GetErrorCode() & 0xff, 50);
return false;
}
}
{
sql::Statement count_urls(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT count(*) FROM downloads_url_chains WHERE id=?"));
count_urls.BindInt(0, info.id);
if (count_urls.Step()) {
bool corrupt_urls = count_urls.ColumnInt(0) > 0;
UMA_HISTOGRAM_BOOLEAN("Download.DatabaseCorruptUrls", corrupt_urls);
if (corrupt_urls) {
RemoveDownloadURLs(info.id);
}
}
}
sql::Statement statement_insert_chain(
GetDB().GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO downloads_url_chains "
"(id, chain_index, url) "
"VALUES (?, ?, ?)"));
for (size_t i = 0; i < info.url_chain.size(); ++i) {
statement_insert_chain.BindInt(0, info.id);
statement_insert_chain.BindInt(1, i);
statement_insert_chain.BindString(2, info.url_chain[i].spec());
if (!statement_insert_chain.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainInsertError",
GetDB().GetErrorCode() & 0xff, 50);
RemoveDownload(info.id);
return false;
}
statement_insert_chain.Reset(true);
}
return true;
}
void DownloadDatabase::RemoveDownload(uint32 id) {
EnsureInProgressEntriesCleanedUp();
sql::Statement downloads_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM downloads WHERE id=?"));
downloads_statement.BindInt(0, id);
if (!downloads_statement.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainDeleteError",
GetDB().GetErrorCode() & 0xff, 50);
return;
}
RemoveDownloadURLs(id);
}
void DownloadDatabase::RemoveDownloadURLs(uint32 id) {
sql::Statement urlchain_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM downloads_url_chains WHERE id=?"));
urlchain_statement.BindInt(0, id);
if (!urlchain_statement.Run()) {
UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainDeleteError",
GetDB().GetErrorCode() & 0xff, 50);
}
}
size_t DownloadDatabase::CountDownloads() {
EnsureInProgressEntriesCleanedUp();
sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
"SELECT count(*) from downloads"));
statement.Step();
return statement.ColumnInt(0);
}
}