Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/history/download_database.h"
      6 
      7 #include <limits>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/debug/alias.h"
     12 #include "base/files/file_path.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/metrics/histogram.h"
     15 #include "base/stl_util.h"
     16 #include "base/strings/stringprintf.h"
     17 #include "base/strings/utf_string_conversions.h"
     18 #include "base/time/time.h"
     19 #include "build/build_config.h"
     20 #include "chrome/browser/history/download_row.h"
     21 #include "components/history/core/browser/history_types.h"
     22 #include "content/public/browser/download_interrupt_reasons.h"
     23 #include "content/public/browser/download_item.h"
     24 #include "sql/statement.h"
     25 
     26 using content::DownloadItem;
     27 
     28 namespace history {
     29 
     30 namespace {
     31 
     32 // Reason for dropping a particular record.
     33 enum DroppedReason {
     34   DROPPED_REASON_BAD_STATE = 0,
     35   DROPPED_REASON_BAD_DANGER_TYPE = 1,
     36   DROPPED_REASON_BAD_ID = 2,
     37   DROPPED_REASON_DUPLICATE_ID = 3,
     38   DROPPED_REASON_MAX
     39 };
     40 
     41 #if defined(OS_POSIX)
     42 
     43 // Binds/reads the given file path to the given column of the given statement.
     44 void BindFilePath(sql::Statement& statement, const base::FilePath& path,
     45                   int col) {
     46   statement.BindString(col, path.value());
     47 }
     48 base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
     49   return base::FilePath(statement.ColumnString(col));
     50 }
     51 
     52 #else
     53 
     54 // See above.
     55 void BindFilePath(sql::Statement& statement, const base::FilePath& path,
     56                   int col) {
     57   statement.BindString16(col, path.value());
     58 }
     59 base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
     60   return base::FilePath(statement.ColumnString16(col));
     61 }
     62 
     63 #endif
     64 
     65 }  // namespace
     66 
     67 // These constants and the transformation functions below are used to allow
     68 // DownloadItem::DownloadState and DownloadDangerType to change without
     69 // breaking the database schema.
     70 // They guarantee that the values of the |state| field in the database are one
     71 // of the values returned by StateToInt, and that the values of the |state|
     72 // field of the DownloadRows returned by QueryDownloads() are one of the values
     73 // returned by IntToState().
     74 const int DownloadDatabase::kStateInvalid = -1;
     75 const int DownloadDatabase::kStateInProgress = 0;
     76 const int DownloadDatabase::kStateComplete = 1;
     77 const int DownloadDatabase::kStateCancelled = 2;
     78 const int DownloadDatabase::kStateBug140687 = 3;
     79 const int DownloadDatabase::kStateInterrupted = 4;
     80 
     81 const int DownloadDatabase::kDangerTypeInvalid = -1;
     82 const int DownloadDatabase::kDangerTypeNotDangerous = 0;
     83 const int DownloadDatabase::kDangerTypeDangerousFile = 1;
     84 const int DownloadDatabase::kDangerTypeDangerousUrl = 2;
     85 const int DownloadDatabase::kDangerTypeDangerousContent = 3;
     86 const int DownloadDatabase::kDangerTypeMaybeDangerousContent = 4;
     87 const int DownloadDatabase::kDangerTypeUncommonContent = 5;
     88 const int DownloadDatabase::kDangerTypeUserValidated = 6;
     89 const int DownloadDatabase::kDangerTypeDangerousHost = 7;
     90 const int DownloadDatabase::kDangerTypePotentiallyUnwanted = 8;
     91 
     92 int DownloadDatabase::StateToInt(DownloadItem::DownloadState state) {
     93   switch (state) {
     94     case DownloadItem::IN_PROGRESS: return DownloadDatabase::kStateInProgress;
     95     case DownloadItem::COMPLETE: return DownloadDatabase::kStateComplete;
     96     case DownloadItem::CANCELLED: return DownloadDatabase::kStateCancelled;
     97     case DownloadItem::INTERRUPTED: return DownloadDatabase::kStateInterrupted;
     98     case DownloadItem::MAX_DOWNLOAD_STATE:
     99       NOTREACHED();
    100       return DownloadDatabase::kStateInvalid;
    101   }
    102   NOTREACHED();
    103   return DownloadDatabase::kStateInvalid;
    104 }
    105 
    106 DownloadItem::DownloadState DownloadDatabase::IntToState(int state) {
    107   switch (state) {
    108     case DownloadDatabase::kStateInProgress: return DownloadItem::IN_PROGRESS;
    109     case DownloadDatabase::kStateComplete: return DownloadItem::COMPLETE;
    110     case DownloadDatabase::kStateCancelled: return DownloadItem::CANCELLED;
    111     // We should not need kStateBug140687 here because MigrateDownloadsState()
    112     // is called in HistoryDatabase::Init().
    113     case DownloadDatabase::kStateInterrupted: return DownloadItem::INTERRUPTED;
    114     default: return DownloadItem::MAX_DOWNLOAD_STATE;
    115   }
    116 }
    117 
    118 int DownloadDatabase::DangerTypeToInt(content::DownloadDangerType danger_type) {
    119   switch (danger_type) {
    120     case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
    121       return DownloadDatabase::kDangerTypeNotDangerous;
    122     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
    123       return DownloadDatabase::kDangerTypeDangerousFile;
    124     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
    125       return DownloadDatabase::kDangerTypeDangerousUrl;
    126     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
    127       return DownloadDatabase::kDangerTypeDangerousContent;
    128     case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
    129       return DownloadDatabase::kDangerTypeMaybeDangerousContent;
    130     case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
    131       return DownloadDatabase::kDangerTypeUncommonContent;
    132     case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
    133       return DownloadDatabase::kDangerTypeUserValidated;
    134     case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
    135       return DownloadDatabase::kDangerTypeDangerousHost;
    136     case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
    137       return DownloadDatabase::kDangerTypePotentiallyUnwanted;
    138     case content::DOWNLOAD_DANGER_TYPE_MAX:
    139       NOTREACHED();
    140       return DownloadDatabase::kDangerTypeInvalid;
    141   }
    142   NOTREACHED();
    143   return DownloadDatabase::kDangerTypeInvalid;
    144 }
    145 
    146 content::DownloadDangerType DownloadDatabase::IntToDangerType(int danger_type) {
    147   switch (danger_type) {
    148     case DownloadDatabase::kDangerTypeNotDangerous:
    149       return content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS;
    150     case DownloadDatabase::kDangerTypeDangerousFile:
    151       return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
    152     case DownloadDatabase::kDangerTypeDangerousUrl:
    153       return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL;
    154     case DownloadDatabase::kDangerTypeDangerousContent:
    155       return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT;
    156     case DownloadDatabase::kDangerTypeMaybeDangerousContent:
    157       return content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT;
    158     case DownloadDatabase::kDangerTypeUncommonContent:
    159       return content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT;
    160     case DownloadDatabase::kDangerTypeUserValidated:
    161       return content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED;
    162     case DownloadDatabase::kDangerTypeDangerousHost:
    163       return content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST;
    164     case DownloadDatabase::kDangerTypePotentiallyUnwanted:
    165       return content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED;
    166     default:
    167       return content::DOWNLOAD_DANGER_TYPE_MAX;
    168   }
    169 }
    170 
    171 DownloadDatabase::DownloadDatabase()
    172     : owning_thread_set_(false),
    173       owning_thread_(0),
    174       in_progress_entry_cleanup_completed_(false) {
    175 }
    176 
    177 DownloadDatabase::~DownloadDatabase() {
    178 }
    179 
    180 bool DownloadDatabase::EnsureColumnExists(
    181     const std::string& name, const std::string& type) {
    182   std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type;
    183   return GetDB().DoesColumnExist("downloads", name.c_str()) ||
    184          GetDB().Execute(add_col.c_str());
    185 }
    186 
    187 bool DownloadDatabase::MigrateMimeType() {
    188   return EnsureColumnExists("mime_type", "VARCHAR(255) NOT NULL"
    189                             " DEFAULT \"\"") &&
    190          EnsureColumnExists("original_mime_type", "VARCHAR(255) NOT NULL"
    191                             " DEFAULT \"\"");
    192 }
    193 
    194 bool DownloadDatabase::MigrateDownloadsState() {
    195   sql::Statement statement(GetDB().GetUniqueStatement(
    196       "UPDATE downloads SET state=? WHERE state=?"));
    197   statement.BindInt(0, kStateInterrupted);
    198   statement.BindInt(1, kStateBug140687);
    199   return statement.Run();
    200 }
    201 
    202 bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() {
    203   // We need to rename the table and copy back from it because SQLite
    204   // provides no way to rename or delete a column.
    205   if (!GetDB().Execute("ALTER TABLE downloads RENAME TO downloads_tmp"))
    206     return false;
    207 
    208   const char kReasonPathDangerSchema[] =
    209       "CREATE TABLE downloads ("
    210       "id INTEGER PRIMARY KEY,"
    211       "current_path LONGVARCHAR NOT NULL,"
    212       "target_path LONGVARCHAR NOT NULL,"
    213       "start_time INTEGER NOT NULL,"
    214       "received_bytes INTEGER NOT NULL,"
    215       "total_bytes INTEGER NOT NULL,"
    216       "state INTEGER NOT NULL,"
    217       "danger_type INTEGER NOT NULL,"
    218       "interrupt_reason INTEGER NOT NULL,"
    219       "end_time INTEGER NOT NULL,"
    220       "opened INTEGER NOT NULL)";
    221 
    222   static const char kReasonPathDangerUrlChainSchema[] =
    223       "CREATE TABLE downloads_url_chains ("
    224       "id INTEGER NOT NULL,"                // downloads.id.
    225       "chain_index INTEGER NOT NULL,"       // Index of url in chain
    226                                             // 0 is initial target,
    227                                             // MAX is target after redirects.
    228       "url LONGVARCHAR NOT NULL, "          // URL.
    229       "PRIMARY KEY (id, chain_index) )";
    230 
    231 
    232   // Recreate main table.
    233   if (!GetDB().Execute(kReasonPathDangerSchema))
    234     return false;
    235 
    236   // Populate it.  As we do so, we transform the time values from time_t
    237   // (seconds since 1/1/1970 UTC), to our internal measure (microseconds
    238   // since the Windows Epoch).  Note that this is dependent on the
    239   // internal representation of base::Time and needs to change if that changes.
    240   sql::Statement statement_populate(GetDB().GetUniqueStatement(
    241       "INSERT INTO downloads "
    242       "( id, current_path, target_path, start_time, received_bytes, "
    243       "  total_bytes, state, danger_type, interrupt_reason, end_time, opened ) "
    244       "SELECT id, full_path, full_path, "
    245       "       CASE start_time WHEN 0 THEN 0 ELSE "
    246       "            (start_time + 11644473600) * 1000000 END, "
    247       "       received_bytes, total_bytes, "
    248       "       state, ?, ?, "
    249       "       CASE end_time WHEN 0 THEN 0 ELSE "
    250       "            (end_time + 11644473600) * 1000000 END, "
    251       "       opened "
    252       "FROM downloads_tmp"));
    253   statement_populate.BindInt(0, content::DOWNLOAD_INTERRUPT_REASON_NONE);
    254   statement_populate.BindInt(1, kDangerTypeNotDangerous);
    255   if (!statement_populate.Run())
    256     return false;
    257 
    258   // Create new chain table and populate it.
    259   if (!GetDB().Execute(kReasonPathDangerUrlChainSchema))
    260     return false;
    261 
    262   if (!GetDB().Execute("INSERT INTO downloads_url_chains "
    263                        "  ( id, chain_index, url) "
    264                        "  SELECT id, 0, url from downloads_tmp"))
    265     return false;
    266 
    267   // Get rid of temporary table.
    268   if (!GetDB().Execute("DROP TABLE downloads_tmp"))
    269     return false;
    270 
    271   return true;
    272 }
    273 
    274 bool DownloadDatabase::MigrateReferrer() {
    275   return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\"");
    276 }
    277 
    278 bool DownloadDatabase::MigrateDownloadedByExtension() {
    279   return EnsureColumnExists("by_ext_id", "VARCHAR NOT NULL DEFAULT \"\"") &&
    280          EnsureColumnExists("by_ext_name", "VARCHAR NOT NULL DEFAULT \"\"");
    281 }
    282 
    283 bool DownloadDatabase::MigrateDownloadValidators() {
    284   return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") &&
    285          EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\"");
    286 }
    287 
    288 bool DownloadDatabase::InitDownloadTable() {
    289   const char kSchema[] =
    290       "CREATE TABLE downloads ("
    291       "id INTEGER PRIMARY KEY,"             // Primary key.
    292       "current_path LONGVARCHAR NOT NULL,"  // Current disk location
    293       "target_path LONGVARCHAR NOT NULL,"   // Final disk location
    294       "start_time INTEGER NOT NULL,"        // When the download was started.
    295       "received_bytes INTEGER NOT NULL,"    // Total size downloaded.
    296       "total_bytes INTEGER NOT NULL,"       // Total size of the download.
    297       "state INTEGER NOT NULL,"             // 1=complete, 4=interrupted
    298       "danger_type INTEGER NOT NULL,"       // Danger type, validated.
    299       "interrupt_reason INTEGER NOT NULL,"  // content::DownloadInterruptReason
    300       "end_time INTEGER NOT NULL,"          // When the download completed.
    301       "opened INTEGER NOT NULL,"            // 1 if it has ever been opened
    302                                             // else 0
    303       "referrer VARCHAR NOT NULL,"          // HTTP Referrer
    304       "by_ext_id VARCHAR NOT NULL,"         // ID of extension that started the
    305                                             // download
    306       "by_ext_name VARCHAR NOT NULL,"       // name of extension
    307       "etag VARCHAR NOT NULL,"              // ETag
    308       "last_modified VARCHAR NOT NULL,"     // Last-Modified header
    309       "mime_type VARCHAR(255) NOT NULL,"    // MIME type.
    310       "original_mime_type VARCHAR(255) NOT NULL)";  // Original MIME type.
    311 
    312   const char kUrlChainSchema[] =
    313       "CREATE TABLE downloads_url_chains ("
    314       "id INTEGER NOT NULL,"                // downloads.id.
    315       "chain_index INTEGER NOT NULL,"       // Index of url in chain
    316                                             // 0 is initial target,
    317                                             // MAX is target after redirects.
    318       "url LONGVARCHAR NOT NULL, "          // URL.
    319       "PRIMARY KEY (id, chain_index) )";
    320 
    321   if (GetDB().DoesTableExist("downloads")) {
    322     return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") &&
    323            EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0");
    324   } else {
    325     // If the "downloads" table doesn't exist, the downloads_url_chain
    326     // table better not.
    327     return (!GetDB().DoesTableExist("downloads_url_chain") &&
    328             GetDB().Execute(kSchema) && GetDB().Execute(kUrlChainSchema));
    329   }
    330 }
    331 
    332 uint32 DownloadDatabase::GetNextDownloadId() {
    333   sql::Statement select_max_id(GetDB().GetUniqueStatement(
    334       "SELECT max(id) FROM downloads"));
    335   bool result = select_max_id.Step();
    336   DCHECK(result);
    337   // If there are zero records in the downloads table, then max(id) will return
    338   // 0 = kInvalidId, so GetNextDownloadId() will set *id = kInvalidId + 1.
    339   // If there is at least one record but all of the |id|s are <= kInvalidId,
    340   // then max(id) will return <= kInvalidId, so GetNextDownloadId should return
    341   // kInvalidId + 1. Note that any records with |id <= kInvalidId| will be
    342   // dropped in QueryDownloads()
    343   // SQLITE doesn't have unsigned integers.
    344   return 1 + static_cast<uint32>(std::max(
    345       static_cast<int64>(content::DownloadItem::kInvalidId),
    346       select_max_id.ColumnInt64(0)));
    347 }
    348 
    349 bool DownloadDatabase::DropDownloadTable() {
    350   return GetDB().Execute("DROP TABLE downloads");
    351 }
    352 
    353 void DownloadDatabase::QueryDownloads(
    354     std::vector<DownloadRow>* results) {
    355   EnsureInProgressEntriesCleanedUp();
    356 
    357   results->clear();
    358   std::set<uint32> ids;
    359 
    360   std::map<uint32, DownloadRow*> info_map;
    361 
    362   sql::Statement statement_main(GetDB().GetCachedStatement(SQL_FROM_HERE,
    363       "SELECT id, current_path, target_path, "
    364       "mime_type, original_mime_type, "
    365       "start_time, received_bytes, "
    366       "total_bytes, state, danger_type, interrupt_reason, end_time, opened, "
    367       "referrer, by_ext_id, by_ext_name, etag, last_modified "
    368       "FROM downloads ORDER BY start_time"));
    369 
    370   while (statement_main.Step()) {
    371     scoped_ptr<DownloadRow> info(new DownloadRow());
    372     int column = 0;
    373 
    374     // SQLITE does not have unsigned integers, so explicitly handle negative
    375     // |id|s instead of casting them to very large uint32s, which would break
    376     // the max(id) logic in GetNextDownloadId().
    377     int64 signed_id = statement_main.ColumnInt64(column++);
    378     info->id = static_cast<uint32>(signed_id);
    379     info->current_path = ColumnFilePath(statement_main, column++);
    380     info->target_path = ColumnFilePath(statement_main, column++);
    381     info->mime_type = statement_main.ColumnString(column++);
    382     info->original_mime_type = statement_main.ColumnString(column++);
    383     info->start_time = base::Time::FromInternalValue(
    384         statement_main.ColumnInt64(column++));
    385     info->received_bytes = statement_main.ColumnInt64(column++);
    386     info->total_bytes = statement_main.ColumnInt64(column++);
    387     int state = statement_main.ColumnInt(column++);
    388     info->state = IntToState(state);
    389     if (info->state == DownloadItem::MAX_DOWNLOAD_STATE)
    390       UMA_HISTOGRAM_COUNTS("Download.DatabaseInvalidState", state);
    391     info->danger_type = IntToDangerType(statement_main.ColumnInt(column++));
    392     info->interrupt_reason = static_cast<content::DownloadInterruptReason>(
    393         statement_main.ColumnInt(column++));
    394     info->end_time = base::Time::FromInternalValue(
    395         statement_main.ColumnInt64(column++));
    396     info->opened = statement_main.ColumnInt(column++) != 0;
    397     info->referrer_url = GURL(statement_main.ColumnString(column++));
    398     info->by_ext_id = statement_main.ColumnString(column++);
    399     info->by_ext_name = statement_main.ColumnString(column++);
    400     info->etag = statement_main.ColumnString(column++);
    401     info->last_modified = statement_main.ColumnString(column++);
    402 
    403     // If the record is corrupted, note that and drop it.
    404     // http://crbug.com/251269
    405     DroppedReason dropped_reason = DROPPED_REASON_MAX;
    406     if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId)) {
    407       // SQLITE doesn't have unsigned integers.
    408       dropped_reason = DROPPED_REASON_BAD_ID;
    409     } else if (!ids.insert(info->id).second) {
    410       dropped_reason = DROPPED_REASON_DUPLICATE_ID;
    411       NOTREACHED() << info->id;
    412     } else if (info->state == DownloadItem::MAX_DOWNLOAD_STATE) {
    413       dropped_reason = DROPPED_REASON_BAD_STATE;
    414     } else if (info->danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
    415       dropped_reason = DROPPED_REASON_BAD_DANGER_TYPE;
    416     }
    417     if (dropped_reason != DROPPED_REASON_MAX) {
    418       UMA_HISTOGRAM_ENUMERATION("Download.DatabaseRecordDropped",
    419                                 dropped_reason,
    420                                 DROPPED_REASON_MAX + 1);
    421     } else {
    422       DCHECK(!ContainsKey(info_map, info->id));
    423       uint32 id = info->id;
    424       info_map[id] = info.release();
    425     }
    426   }
    427 
    428   sql::Statement statement_chain(GetDB().GetCachedStatement(
    429       SQL_FROM_HERE,
    430       "SELECT id, chain_index, url FROM downloads_url_chains "
    431       "ORDER BY id, chain_index"));
    432 
    433   while (statement_chain.Step()) {
    434     int column = 0;
    435     // See the comment above about SQLITE lacking unsigned integers.
    436     int64 signed_id = statement_chain.ColumnInt64(column++);
    437     int chain_index = statement_chain.ColumnInt(column++);
    438 
    439     if (signed_id <= static_cast<int64>(content::DownloadItem::kInvalidId))
    440       continue;
    441     uint32 id = static_cast<uint32>(signed_id);
    442 
    443     // Note that these DCHECKs may trip as a result of corrupted databases.
    444     // We have them because in debug builds the chances are higher there's
    445     // an actual bug than that the database is corrupt, but we handle the
    446     // DB corruption case in production code.
    447 
    448     // Confirm the id has already been seen--if it hasn't, discard the
    449     // record.
    450     DCHECK(ContainsKey(info_map, id));
    451     if (!ContainsKey(info_map, id))
    452       continue;
    453 
    454     // Confirm all previous URLs in the chain have already been seen;
    455     // if not, fill in with null or discard record.
    456     int current_chain_size = info_map[id]->url_chain.size();
    457     std::vector<GURL>* url_chain(&info_map[id]->url_chain);
    458     DCHECK_EQ(chain_index, current_chain_size);
    459     while (current_chain_size < chain_index) {
    460       url_chain->push_back(GURL());
    461       current_chain_size++;
    462     }
    463     if (current_chain_size > chain_index)
    464       continue;
    465 
    466     // Save the record.
    467     url_chain->push_back(GURL(statement_chain.ColumnString(2)));
    468   }
    469 
    470   for (std::map<uint32, DownloadRow*>::iterator
    471            it = info_map.begin(); it != info_map.end(); ++it) {
    472     DownloadRow* row = it->second;
    473     bool empty_url_chain = row->url_chain.empty();
    474     UMA_HISTOGRAM_BOOLEAN("Download.DatabaseEmptyUrlChain", empty_url_chain);
    475     if (empty_url_chain) {
    476       RemoveDownload(row->id);
    477     } else {
    478       // Copy the contents of the stored info.
    479       results->push_back(*row);
    480     }
    481     delete row;
    482     it->second = NULL;
    483   }
    484 }
    485 
    486 bool DownloadDatabase::UpdateDownload(const DownloadRow& data) {
    487   EnsureInProgressEntriesCleanedUp();
    488 
    489   DCHECK_NE(content::DownloadItem::kInvalidId, data.id);
    490   int state = StateToInt(data.state);
    491   if (state == kStateInvalid) {
    492     NOTREACHED();
    493     return false;
    494   }
    495   int danger_type = DangerTypeToInt(data.danger_type);
    496   if (danger_type == kDangerTypeInvalid) {
    497     NOTREACHED();
    498     return false;
    499   }
    500 
    501   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    502       "UPDATE downloads "
    503       "SET current_path=?, target_path=?, "
    504       "mime_type=?, original_mime_type=?, "
    505       "received_bytes=?, state=?, "
    506       "danger_type=?, interrupt_reason=?, end_time=?, total_bytes=?, "
    507       "opened=?, by_ext_id=?, by_ext_name=?, etag=?, last_modified=? "
    508       "WHERE id=?"));
    509   int column = 0;
    510   BindFilePath(statement, data.current_path, column++);
    511   BindFilePath(statement, data.target_path, column++);
    512   statement.BindString(column++, data.mime_type);
    513   statement.BindString(column++, data.original_mime_type);
    514   statement.BindInt64(column++, data.received_bytes);
    515   statement.BindInt(column++, state);
    516   statement.BindInt(column++, danger_type);
    517   statement.BindInt(column++, static_cast<int>(data.interrupt_reason));
    518   statement.BindInt64(column++, data.end_time.ToInternalValue());
    519   statement.BindInt64(column++, data.total_bytes);
    520   statement.BindInt(column++, (data.opened ? 1 : 0));
    521   statement.BindString(column++, data.by_ext_id);
    522   statement.BindString(column++, data.by_ext_name);
    523   statement.BindString(column++, data.etag);
    524   statement.BindString(column++, data.last_modified);
    525   statement.BindInt(column++, data.id);
    526 
    527   return statement.Run();
    528 }
    529 
    530 void DownloadDatabase::EnsureInProgressEntriesCleanedUp() {
    531   if (in_progress_entry_cleanup_completed_)
    532     return;
    533 
    534   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    535       "UPDATE downloads SET state=?, interrupt_reason=? WHERE state=?"));
    536   statement.BindInt(0, kStateInterrupted);
    537   statement.BindInt(1, content::DOWNLOAD_INTERRUPT_REASON_CRASH);
    538   statement.BindInt(2, kStateInProgress);
    539 
    540   statement.Run();
    541   in_progress_entry_cleanup_completed_ = true;
    542 }
    543 
    544 bool DownloadDatabase::CreateDownload(const DownloadRow& info) {
    545   DCHECK_NE(content::DownloadItem::kInvalidId, info.id);
    546   EnsureInProgressEntriesCleanedUp();
    547 
    548   if (info.url_chain.empty())
    549     return false;
    550 
    551   int state = StateToInt(info.state);
    552   if (state == kStateInvalid)
    553     return false;
    554 
    555   int danger_type = DangerTypeToInt(info.danger_type);
    556   if (danger_type == kDangerTypeInvalid)
    557     return false;
    558 
    559   {
    560     sql::Statement statement_insert(GetDB().GetCachedStatement(
    561         SQL_FROM_HERE,
    562         "INSERT INTO downloads "
    563         "(id, current_path, target_path, "
    564         " mime_type, original_mime_type, "
    565         " start_time, "
    566         " received_bytes, total_bytes, state, danger_type, interrupt_reason, "
    567         " end_time, opened, referrer, by_ext_id, by_ext_name, etag, "
    568         " last_modified) "
    569         "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    570 
    571     int column = 0;
    572     statement_insert.BindInt(column++, info.id);
    573     BindFilePath(statement_insert, info.current_path, column++);
    574     BindFilePath(statement_insert, info.target_path, column++);
    575     statement_insert.BindString(column++, info.mime_type);
    576     statement_insert.BindString(column++, info.original_mime_type);
    577     statement_insert.BindInt64(column++, info.start_time.ToInternalValue());
    578     statement_insert.BindInt64(column++, info.received_bytes);
    579     statement_insert.BindInt64(column++, info.total_bytes);
    580     statement_insert.BindInt(column++, state);
    581     statement_insert.BindInt(column++, danger_type);
    582     statement_insert.BindInt(column++, info.interrupt_reason);
    583     statement_insert.BindInt64(column++, info.end_time.ToInternalValue());
    584     statement_insert.BindInt(column++, info.opened ? 1 : 0);
    585     statement_insert.BindString(column++, info.referrer_url.spec());
    586     statement_insert.BindString(column++, info.by_ext_id);
    587     statement_insert.BindString(column++, info.by_ext_name);
    588     statement_insert.BindString(column++, info.etag);
    589     statement_insert.BindString(column++, info.last_modified);
    590     if (!statement_insert.Run()) {
    591       // GetErrorCode() returns a bitmask where the lower byte is a more general
    592       // code and the upper byte is a more specific code. In order to save
    593       // memory, take the general code, of which there are fewer than 50. See
    594       // also sql/connection.cc
    595       // http://www.sqlite.org/c3ref/c_abort_rollback.html
    596       UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainInsertError",
    597                                 GetDB().GetErrorCode() & 0xff, 50);
    598       return false;
    599     }
    600   }
    601 
    602   {
    603     sql::Statement count_urls(GetDB().GetCachedStatement(SQL_FROM_HERE,
    604         "SELECT count(*) FROM downloads_url_chains WHERE id=?"));
    605     count_urls.BindInt(0, info.id);
    606     if (count_urls.Step()) {
    607       bool corrupt_urls = count_urls.ColumnInt(0) > 0;
    608       UMA_HISTOGRAM_BOOLEAN("Download.DatabaseCorruptUrls", corrupt_urls);
    609       if (corrupt_urls) {
    610         // There should not be any URLs in downloads_url_chains for this
    611         // info.id.  If there are, we don't want them to interfere with
    612         // inserting the correct URLs, so just remove them.
    613         RemoveDownloadURLs(info.id);
    614       }
    615     }
    616   }
    617 
    618   sql::Statement statement_insert_chain(
    619       GetDB().GetCachedStatement(SQL_FROM_HERE,
    620                                  "INSERT INTO downloads_url_chains "
    621                                  "(id, chain_index, url) "
    622                                  "VALUES (?, ?, ?)"));
    623   for (size_t i = 0; i < info.url_chain.size(); ++i) {
    624     statement_insert_chain.BindInt(0, info.id);
    625     statement_insert_chain.BindInt(1, i);
    626     statement_insert_chain.BindString(2, info.url_chain[i].spec());
    627     if (!statement_insert_chain.Run()) {
    628       UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainInsertError",
    629                                 GetDB().GetErrorCode() & 0xff, 50);
    630       RemoveDownload(info.id);
    631       return false;
    632     }
    633     statement_insert_chain.Reset(true);
    634   }
    635   return true;
    636 }
    637 
    638 void DownloadDatabase::RemoveDownload(uint32 id) {
    639   EnsureInProgressEntriesCleanedUp();
    640 
    641   sql::Statement downloads_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    642       "DELETE FROM downloads WHERE id=?"));
    643   downloads_statement.BindInt(0, id);
    644   if (!downloads_statement.Run()) {
    645     UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainDeleteError",
    646                               GetDB().GetErrorCode() & 0xff, 50);
    647     return;
    648   }
    649   RemoveDownloadURLs(id);
    650 }
    651 
    652 void DownloadDatabase::RemoveDownloadURLs(uint32 id) {
    653   sql::Statement urlchain_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    654       "DELETE FROM downloads_url_chains WHERE id=?"));
    655   urlchain_statement.BindInt(0, id);
    656   if (!urlchain_statement.Run()) {
    657     UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainDeleteError",
    658                               GetDB().GetErrorCode() & 0xff, 50);
    659   }
    660 }
    661 
    662 size_t DownloadDatabase::CountDownloads() {
    663   EnsureInProgressEntriesCleanedUp();
    664 
    665   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    666       "SELECT count(*) from downloads"));
    667   statement.Step();
    668   return statement.ColumnInt(0);
    669 }
    670 
    671 }  // namespace history
    672