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 "components/history/core/browser/download_database.h"
6 
7 #include <inttypes.h>
8 
9 #include <limits>
10 #include <memory>
11 #include <string>
12 #include <vector>
13 
14 #include "base/debug/alias.h"
15 #include "base/files/file_path.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/rand_util.h"
18 #include "base/stl_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "build/build_config.h"
23 #include "components/history/core/browser/download_constants.h"
24 #include "components/history/core/browser/download_row.h"
25 #include "components/history/core/browser/download_slice_info.h"
26 #include "components/history/core/browser/history_types.h"
27 #include "sql/statement.h"
28 
29 namespace history {
30 
31 namespace {
32 
33 const char kDownloadsTable[] = "downloads";
34 
35 const char kDownloadsSlicesTable[] = "downloads_slices";
36 
37 // Reason for dropping a particular record. Used for UMA.
38 enum DroppedReason {
39   DROPPED_REASON_BAD_STATE = 0,
40   DROPPED_REASON_BAD_DANGER_TYPE = 1,
41   DROPPED_REASON_BAD_ID = 2,
42   DROPPED_REASON_DUPLICATE_ID = 3,
43   DROPPED_REASON_MAX
44 };
45 
46 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
47 
48 // Binds/reads the given file path to the given column of the given statement.
BindFilePath(sql::Statement & statement,const base::FilePath & path,int col)49 void BindFilePath(sql::Statement& statement,
50                   const base::FilePath& path,
51                   int col) {
52   statement.BindString(col, path.value());
53 }
ColumnFilePath(sql::Statement & statement,int col)54 base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
55   return base::FilePath(statement.ColumnString(col));
56 }
57 
58 #else
59 
60 // See above.
BindFilePath(sql::Statement & statement,const base::FilePath & path,int col)61 void BindFilePath(sql::Statement& statement,
62                   const base::FilePath& path,
63                   int col) {
64   statement.BindString16(col, path.value());
65 }
ColumnFilePath(sql::Statement & statement,int col)66 base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
67   return base::FilePath(statement.ColumnString16(col));
68 }
69 
70 #endif
71 
72 }  // namespace
73 
DownloadDatabase(DownloadInterruptReason download_interrupt_reason_none,DownloadInterruptReason download_interrupt_reason_crash)74 DownloadDatabase::DownloadDatabase(
75     DownloadInterruptReason download_interrupt_reason_none,
76     DownloadInterruptReason download_interrupt_reason_crash)
77     : owning_thread_set_(false),
78       owning_thread_(0),
79       in_progress_entry_cleanup_completed_(false),
80       download_interrupt_reason_none_(download_interrupt_reason_none),
81       download_interrupt_reason_crash_(download_interrupt_reason_crash) {
82 }
83 
~DownloadDatabase()84 DownloadDatabase::~DownloadDatabase() {
85 }
86 
EnsureColumnExists(const std::string & name,const std::string & type)87 bool DownloadDatabase::EnsureColumnExists(const std::string& name,
88                                           const std::string& type) {
89   return EnsureColumnExistsInTable(kDownloadsTable, name, type);
90 }
91 
EnsureColumnExistsInTable(const std::string & table,const std::string & name,const std::string & type)92 bool DownloadDatabase::EnsureColumnExistsInTable(const std::string& table,
93                                                  const std::string& name,
94                                                  const std::string& type) {
95   std::string add_col =
96       "ALTER TABLE " + table + " ADD COLUMN " + name + " " + type;
97   return GetDB().DoesColumnExist(table.c_str(), name.c_str()) ||
98          GetDB().Execute(add_col.c_str());
99 }
100 
MigrateMimeType()101 bool DownloadDatabase::MigrateMimeType() {
102   return EnsureColumnExists("mime_type", "VARCHAR(255) NOT NULL"
103                             " DEFAULT \"\"") &&
104          EnsureColumnExists("original_mime_type", "VARCHAR(255) NOT NULL"
105                             " DEFAULT \"\"");
106 }
107 
MigrateDownloadsState()108 bool DownloadDatabase::MigrateDownloadsState() {
109   sql::Statement statement(GetDB().GetUniqueStatement(
110       base::StringPrintf("UPDATE %s SET state=? WHERE state=?", kDownloadsTable)
111           .c_str()));
112   statement.BindInt(0, DownloadStateToInt(DownloadState::INTERRUPTED));
113   statement.BindInt(1, DownloadStateToInt(DownloadState::BUG_140687));
114   return statement.Run();
115 }
116 
MigrateDownloadsReasonPathsAndDangerType()117 bool DownloadDatabase::MigrateDownloadsReasonPathsAndDangerType() {
118   // We need to rename the table and copy back from it because SQLite
119   // provides no way to rename or delete a column.
120   if (!GetDB().Execute(
121           base::StringPrintf("ALTER TABLE %s RENAME TO downloads_tmp",
122                              kDownloadsTable)
123               .c_str()))
124     return false;
125 
126   const std::string kReasonPathDangerSchema = base::StringPrintf(
127       "CREATE TABLE %s ("
128       "id INTEGER PRIMARY KEY,"
129       "current_path LONGVARCHAR NOT NULL,"
130       "target_path LONGVARCHAR NOT NULL,"
131       "start_time INTEGER NOT NULL,"
132       "received_bytes INTEGER NOT NULL,"
133       "total_bytes INTEGER NOT NULL,"
134       "state INTEGER NOT NULL,"
135       "danger_type INTEGER NOT NULL,"
136       "interrupt_reason INTEGER NOT NULL,"
137       "end_time INTEGER NOT NULL,"
138       "opened INTEGER NOT NULL)",
139       kDownloadsTable);
140 
141   static const char kReasonPathDangerUrlChainSchema[] =
142       "CREATE TABLE downloads_url_chains ("
143       "id INTEGER NOT NULL,"                // downloads.id.
144       "chain_index INTEGER NOT NULL,"       // Index of url in chain
145                                             // 0 is initial target,
146                                             // MAX is target after redirects.
147       "url LONGVARCHAR NOT NULL, "          // URL.
148       "PRIMARY KEY (id, chain_index) )";
149 
150 
151   // Recreate main table.
152   if (!GetDB().Execute(kReasonPathDangerSchema.c_str()))
153     return false;
154 
155   // Populate it.  As we do so, we transform the time values from time_t
156   // (seconds since 1/1/1970 UTC), to our internal measure (microseconds
157   // since the Windows Epoch).  Note that this is dependent on the
158   // internal representation of base::Time and needs to change if that changes.
159   sql::Statement statement_populate(GetDB().GetUniqueStatement(
160       base::StringPrintf(
161           "INSERT INTO %s "
162           "( id, current_path, target_path, start_time, received_bytes, "
163           "  total_bytes, state, danger_type, interrupt_reason, end_time, "
164           "opened ) "
165           "SELECT id, full_path, full_path, "
166           "       CASE start_time WHEN 0 THEN 0 ELSE "
167           "            (start_time + 11644473600) * 1000000 END, "
168           "       received_bytes, total_bytes, "
169           "       state, ?, ?, "
170           "       CASE end_time WHEN 0 THEN 0 ELSE "
171           "            (end_time + 11644473600) * 1000000 END, "
172           "       opened "
173           "FROM downloads_tmp",
174           kDownloadsTable)
175           .c_str()));
176   statement_populate.BindInt(
177       0, DownloadInterruptReasonToInt(download_interrupt_reason_none_));
178   statement_populate.BindInt(
179       1, DownloadDangerTypeToInt(DownloadDangerType::NOT_DANGEROUS));
180   if (!statement_populate.Run())
181     return false;
182 
183   // Create new chain table and populate it.
184   if (!GetDB().Execute(kReasonPathDangerUrlChainSchema))
185     return false;
186 
187   if (!GetDB().Execute("INSERT INTO downloads_url_chains "
188                        "  ( id, chain_index, url) "
189                        "  SELECT id, 0, url from downloads_tmp"))
190     return false;
191 
192   // Get rid of temporary table.
193   if (!GetDB().Execute("DROP TABLE downloads_tmp"))
194     return false;
195 
196   return true;
197 }
198 
MigrateReferrer()199 bool DownloadDatabase::MigrateReferrer() {
200   return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\"");
201 }
202 
MigrateDownloadedByExtension()203 bool DownloadDatabase::MigrateDownloadedByExtension() {
204   return EnsureColumnExists("by_ext_id", "VARCHAR NOT NULL DEFAULT \"\"") &&
205          EnsureColumnExists("by_ext_name", "VARCHAR NOT NULL DEFAULT \"\"");
206 }
207 
MigrateDownloadValidators()208 bool DownloadDatabase::MigrateDownloadValidators() {
209   return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") &&
210          EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\"");
211 }
212 
MigrateHashHttpMethodAndGenerateGuids()213 bool DownloadDatabase::MigrateHashHttpMethodAndGenerateGuids() {
214   if (!EnsureColumnExists("guid", "VARCHAR NOT NULL DEFAULT ''") ||
215       !EnsureColumnExists("hash", "BLOB NOT NULL DEFAULT X''") ||
216       !EnsureColumnExists("http_method", "VARCHAR NOT NULL DEFAULT ''"))
217     return false;
218 
219   // Generate GUIDs for each download. GUIDs based on random data should conform
220   // with RFC 4122 section 4.4. Given the following field layout (based on RFC
221   // 4122):
222   //
223   //  0                   1                   2                   3
224   //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
225   //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
226   //  |                          time_low                             |
227   //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
228   //  |       time_mid                |         time_hi_and_version   |
229   //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
230   //  |clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
231   //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
232   //  |                         node (2-5)                            |
233   //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
234   //
235   // * Bits 4-7 of time_hi_and_version should be set to 0b0100 == 4
236   // * Bits 6-7 of clk_seq_hi_res should be set to 0b10
237   // * All other bits should be random or pseudorandom.
238   //
239   // We are going to take the liberty of setting time_low to the 32-bit download
240   // ID. That will guarantee that no two randomly generated GUIDs will collide
241   // even if the 90 bits of entropy doesn't save us.
242   //
243   // Translated to the canonical string representation, the GUID is generated
244   // thusly:
245   //
246   //    XXXXXXXX-RRRR-4RRR-yRRR-RRRRRRRRRRRR
247   //    \__  __/ \___________  ____________/
248   //       \/                \/
249   //       |          R = random hex digit.
250   //       |          y = one of {'8','9','A','B'} selected randomly.
251   //       |          4 = the character '4'.
252   //       |
253   //       Hex representation of 32-bit download ID.
254   //
255   // This GUID generation scheme is only used for migrated download rows and
256   // assumes that the likelihood of a collision with a GUID generated via
257   // base::GenerateGUID() will be vanishingly small.
258   //
259   // A previous version of this code generated GUIDs that used random bits for
260   // all but the first 32-bits. I.e. the scheme didn't respect the 6 fixed bits
261   // as prescribed for type 4 GUIDs. The resulting GUIDs are not believed to
262   // have an elevated risk of collision with GUIDs generated via
263   // base::GenerateGUID() and are considered valid by all known consumers. Hence
264   // no additional migration logic is being introduced to fix those GUIDs.
265   sql::Statement select(GetDB().GetUniqueStatement(
266       base::StringPrintf("SELECT id FROM %s", kDownloadsTable).c_str()));
267   sql::Statement update(GetDB().GetUniqueStatement(
268       base::StringPrintf("UPDATE %s SET guid = ? WHERE id = ?", kDownloadsTable)
269           .c_str()));
270   while (select.Step()) {
271     int id = select.ColumnInt(0);
272     uint64_t r1 = base::RandUint64();
273     uint64_t r2 = base::RandUint64();
274     std::string guid = base::StringPrintf(
275         "%08" PRIX32 "-%04" PRIX64 "-4%03" PRIX64 "-%04" PRIX64 "-%012" PRIX64,
276         id, r1 >> 48,
277         (r1 >> 36) & 0xfff,
278         ((8 | ((r1 >> 34) & 3)) << 12) | ((r1 >> 22) & 0xfff),
279         r2 & 0xffffffffffff);
280     update.BindString(0, guid);
281     update.BindInt(1, id);
282     if (!update.Run())
283       return false;
284     update.Reset(true);
285   }
286   return true;
287 }
288 
MigrateDownloadTabUrl()289 bool DownloadDatabase::MigrateDownloadTabUrl() {
290   return EnsureColumnExists("tab_url", "VARCHAR NOT NULL DEFAULT ''") &&
291          EnsureColumnExists("tab_referrer_url", "VARCHAR NOT NULL DEFAULT ''");
292 }
293 
MigrateDownloadSiteInstanceUrl()294 bool DownloadDatabase::MigrateDownloadSiteInstanceUrl() {
295   return EnsureColumnExists("site_url", "VARCHAR NOT NULL DEFAULT ''");
296 }
297 
MigrateDownloadLastAccessTime()298 bool DownloadDatabase::MigrateDownloadLastAccessTime() {
299   return EnsureColumnExists("last_access_time", "INTEGER NOT NULL DEFAULT 0");
300 }
301 
MigrateDownloadTransient()302 bool DownloadDatabase::MigrateDownloadTransient() {
303   return EnsureColumnExists("transient", "INTEGER NOT NULL DEFAULT 0");
304 }
305 
MigrateDownloadSliceFinished()306 bool DownloadDatabase::MigrateDownloadSliceFinished() {
307   return EnsureColumnExistsInTable(kDownloadsSlicesTable, "finished",
308                                    "INTEGER NOT NULL DEFAULT 0");
309 }
310 
InitDownloadTable()311 bool DownloadDatabase::InitDownloadTable() {
312   const std::string kSchema = base::StringPrintf(
313       "CREATE TABLE %s ("
314       "id INTEGER PRIMARY KEY,"             // Primary key.
315       "guid VARCHAR NOT NULL,"              // GUID.
316       "current_path LONGVARCHAR NOT NULL,"  // Current disk location
317       "target_path LONGVARCHAR NOT NULL,"   // Final disk location
318       "start_time INTEGER NOT NULL,"        // When the download was started.
319       "received_bytes INTEGER NOT NULL,"    // Total size downloaded.
320       "total_bytes INTEGER NOT NULL,"       // Total size of the download.
321       "state INTEGER NOT NULL,"             // 1=complete, 4=interrupted
322       "danger_type INTEGER NOT NULL,"       // Danger type, validated.
323       "interrupt_reason INTEGER NOT NULL,"  // DownloadInterruptReason
324       "hash BLOB NOT NULL,"                 // Raw SHA-256 hash of contents.
325       "end_time INTEGER NOT NULL,"          // When the download completed.
326       "opened INTEGER NOT NULL,"            // 1 if it has ever been opened
327                                             // else 0
328       "last_access_time INTEGER NOT NULL,"  // The last time it was
329                                             // accessed.
330       "transient INTEGER NOT NULL,"         // 1 if it is transient, else 0.
331       "referrer VARCHAR NOT NULL,"          // HTTP Referrer
332       "site_url VARCHAR NOT NULL,"          // Site URL for initiating site
333                                             // instance.
334       "tab_url VARCHAR NOT NULL,"           // Tab URL for initiator.
335       "tab_referrer_url VARCHAR NOT NULL,"  // Tag referrer URL for
336                                             // initiator.
337       "http_method VARCHAR NOT NULL,"       // HTTP method.
338       "by_ext_id VARCHAR NOT NULL,"         // ID of extension that started the
339                                             // download
340       "by_ext_name VARCHAR NOT NULL,"       // name of extension
341       "etag VARCHAR NOT NULL,"              // ETag
342       "last_modified VARCHAR NOT NULL,"     // Last-Modified header
343       "mime_type VARCHAR(255) NOT NULL,"    // MIME type.
344       "original_mime_type VARCHAR(255) NOT NULL)"  // Original MIME type.
345       ,
346       kDownloadsTable);
347 
348   const char kUrlChainSchema[] =
349       "CREATE TABLE downloads_url_chains ("
350       "id INTEGER NOT NULL,"                // downloads.id.
351       "chain_index INTEGER NOT NULL,"       // Index of url in chain
352                                             // 0 is initial target,
353                                             // MAX is target after redirects.
354       "url LONGVARCHAR NOT NULL, "          // URL.
355       "PRIMARY KEY (id, chain_index) )";
356 
357   const std::string kSlicesSchema = base::StringPrintf(
358       "CREATE TABLE %s ("
359       "download_id INTEGER NOT NULL,"         // downloads.id.
360       "offset INTEGER NOT NULL,"              // Offset in the target file.
361       "received_bytes INTEGER NOT NULL,"      // Total bytes downloaded.
362       "finished INTEGER NOT NULL DEFAULT 0,"  // If the slice is finished.
363       "PRIMARY KEY (download_id, offset) )",
364       kDownloadsSlicesTable);
365 
366   bool ret;
367   if (GetDB().DoesTableExist(kDownloadsTable)) {
368     // If the "downloads" table exists, "downloads_url_chain" might not be there
369     // as it is introduced in version 24. A migration function will be run to
370     // create it later.
371     ret = EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") &&
372         EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0");
373   } else {
374     // If the "downloads" table doesn't exist, neither should the
375     // "downloads_url_chain" table.
376     ret = !GetDB().DoesTableExist("downloads_url_chain");
377     // Recreate the "downloads" and "downloads_url_chain" table.
378     ret = ret && GetDB().Execute(kSchema.c_str()) &&
379           GetDB().Execute(kUrlChainSchema);
380   }
381 
382   // Making sure the "downloads_slices" table is created as it is introduced in
383   // version 33. This table doesn't require migration of existing tables.
384   return ret && (GetDB().DoesTableExist(kDownloadsSlicesTable) ||
385                  GetDB().Execute(kSlicesSchema.c_str()));
386 }
387 
GetNextDownloadId()388 uint32_t DownloadDatabase::GetNextDownloadId() {
389   sql::Statement select_max_id(GetDB().GetUniqueStatement(
390       base::StringPrintf("SELECT max(id) FROM %s", kDownloadsTable).c_str()));
391   bool result = select_max_id.Step();
392   DCHECK(result);
393   // If there are zero records in the downloads table, then max(id) will
394   // return 0 = kInvalidDownloadId, so GetNextDownloadId() will set
395   // *id = kInvalidDownloadId + 1.
396   //
397   // If there is at least one record but all of the |id|s are
398   // <= kInvalidDownloadId, then max(id) will return <= kInvalidDownloadId,
399   // so GetNextDownloadId() should return kInvalidDownloadId + 1.
400   //
401   // Note that any records with |id <= kInvalidDownloadId| will be dropped in
402   // QueryDownloads().
403   //
404   // SQLITE doesn't have unsigned integers.
405   return 1 + static_cast<uint32_t>(
406                  std::max(static_cast<int64_t>(kInvalidDownloadId),
407                           select_max_id.ColumnInt64(0)));
408 }
409 
DropDownloadTable()410 bool DownloadDatabase::DropDownloadTable() {
411   return GetDB().Execute(
412       base::StringPrintf("DROP TABLE %s", kDownloadsTable).c_str());
413 }
414 
QueryDownloads(std::vector<DownloadRow> * results)415 void DownloadDatabase::QueryDownloads(std::vector<DownloadRow>* results) {
416   EnsureInProgressEntriesCleanedUp();
417 
418   results->clear();
419   std::set<uint32_t> ids;
420 
421   DownloadRowMap info_map;
422 
423   sql::Statement statement_main(GetDB().GetCachedStatement(
424       SQL_FROM_HERE,
425       base::StringPrintf(
426           "SELECT id, guid, current_path, target_path, mime_type, "
427           "original_mime_type, start_time, received_bytes, total_bytes, state, "
428           "danger_type, interrupt_reason, hash, end_time, opened, "
429           "last_access_time, transient, referrer, site_url, tab_url, "
430           "tab_referrer_url, http_method, by_ext_id, by_ext_name, etag, "
431           "last_modified FROM %s ORDER BY start_time",
432           kDownloadsTable)
433           .c_str()));
434 
435   while (statement_main.Step()) {
436     std::unique_ptr<DownloadRow> info(new DownloadRow());
437     int column = 0;
438 
439     // SQLITE does not have unsigned integers, so explicitly handle negative
440     // |id|s instead of casting them to very large uint32s, which would break
441     // the max(id) logic in GetNextDownloadId().
442     int64_t signed_id = statement_main.ColumnInt64(column++);
443     bool valid = ConvertIntToDownloadId(signed_id, &(info->id));
444     info->guid = statement_main.ColumnString(column++);
445     info->current_path = ColumnFilePath(statement_main, column++);
446     info->target_path = ColumnFilePath(statement_main, column++);
447     info->mime_type = statement_main.ColumnString(column++);
448     info->original_mime_type = statement_main.ColumnString(column++);
449     info->start_time =
450         base::Time::FromInternalValue(statement_main.ColumnInt64(column++));
451     info->received_bytes = statement_main.ColumnInt64(column++);
452     info->total_bytes = statement_main.ColumnInt64(column++);
453     int state = statement_main.ColumnInt(column++);
454     info->state = IntToDownloadState(state);
455     info->danger_type =
456         IntToDownloadDangerType(statement_main.ColumnInt(column++));
457     info->interrupt_reason =
458         IntToDownloadInterruptReason(statement_main.ColumnInt(column++));
459     statement_main.ColumnBlobAsString(column++, &info->hash);
460     info->end_time =
461         base::Time::FromInternalValue(statement_main.ColumnInt64(column++));
462     info->opened = statement_main.ColumnInt(column++) != 0;
463     info->last_access_time =
464         base::Time::FromInternalValue(statement_main.ColumnInt64(column++));
465     info->transient = statement_main.ColumnInt(column++) != 0;
466     info->referrer_url = GURL(statement_main.ColumnString(column++));
467     info->site_url = GURL(statement_main.ColumnString(column++));
468     info->tab_url = GURL(statement_main.ColumnString(column++));
469     info->tab_referrer_url = GURL(statement_main.ColumnString(column++));
470     info->http_method = statement_main.ColumnString(column++);
471     info->by_ext_id = statement_main.ColumnString(column++);
472     info->by_ext_name = statement_main.ColumnString(column++);
473     info->etag = statement_main.ColumnString(column++);
474     info->last_modified = statement_main.ColumnString(column++);
475 
476     // If the record is corrupted, note that and drop it.
477     // http://crbug.com/251269
478     DroppedReason dropped_reason = DROPPED_REASON_MAX;
479     if (!valid) {
480       // SQLITE doesn't have unsigned integers.
481       dropped_reason = DROPPED_REASON_BAD_ID;
482     } else if (!ids.insert(info->id).second) {
483       dropped_reason = DROPPED_REASON_DUPLICATE_ID;
484       NOTREACHED() << info->id;
485     } else if (info->state == DownloadState::INVALID) {
486       dropped_reason = DROPPED_REASON_BAD_STATE;
487     } else if (info->danger_type == DownloadDangerType::INVALID) {
488       dropped_reason = DROPPED_REASON_BAD_DANGER_TYPE;
489     }
490     if (dropped_reason == DROPPED_REASON_MAX) {
491       DCHECK(!base::Contains(info_map, info->id));
492       uint32_t id = info->id;
493       info_map[id] = info.release();
494     }
495   }
496 
497   sql::Statement statement_chain(GetDB().GetCachedStatement(
498       SQL_FROM_HERE,
499       "SELECT id, chain_index, url FROM downloads_url_chains "
500       "ORDER BY id, chain_index"));
501 
502   while (statement_chain.Step()) {
503     int column = 0;
504     // See the comment above about SQLITE lacking unsigned integers.
505     int64_t signed_id = statement_chain.ColumnInt64(column++);
506     int chain_index = statement_chain.ColumnInt(column++);
507 
508     DownloadId id = kInvalidDownloadId;
509     if (!ConvertIntToDownloadId(signed_id, &id))
510       continue;
511 
512     // Confirm the id has already been seen--if it hasn't, discard the
513     // record.
514     if (!base::Contains(info_map, id))
515       continue;
516 
517     // Confirm all previous URLs in the chain have already been seen;
518     // if not, fill in with null or discard record.
519     int current_chain_size = static_cast<int>(info_map[id]->url_chain.size());
520     std::vector<GURL>* url_chain(&info_map[id]->url_chain);
521     // Note that this DCHECK may trip as a result of corrupted databases.
522     // It exists because in debug builds the chances are higher there's
523     // an actual bug than that the database is corrupt. The DB corruption
524     // case in handled in production code.
525     DCHECK_EQ(chain_index, current_chain_size);
526     while (current_chain_size < chain_index) {
527       url_chain->push_back(GURL());
528       current_chain_size++;
529     }
530     if (current_chain_size > chain_index)
531       continue;
532 
533     // Save the record.
534     url_chain->push_back(GURL(statement_chain.ColumnString(2)));
535   }
536 
537   QueryDownloadSlices(&info_map);
538 
539   for (auto it = info_map.begin(); it != info_map.end(); ++it) {
540     DownloadRow* row = it->second;
541     bool empty_url_chain = row->url_chain.empty();
542     UMA_HISTOGRAM_BOOLEAN("Download.DatabaseEmptyUrlChain", empty_url_chain);
543     if (empty_url_chain) {
544       RemoveDownload(row->id);
545     } else {
546       // Copy the contents of the stored info.
547       results->push_back(*row);
548     }
549     delete row;
550     it->second = NULL;
551   }
552 }
553 
UpdateDownload(const DownloadRow & data)554 bool DownloadDatabase::UpdateDownload(const DownloadRow& data) {
555   // UpdateDownload() is called fairly frequently.
556   EnsureInProgressEntriesCleanedUp();
557 
558   DCHECK_NE(kInvalidDownloadId, data.id);
559   if (data.state == DownloadState::INVALID) {
560     NOTREACHED();
561     return false;
562   }
563   if (data.danger_type == DownloadDangerType::INVALID) {
564     NOTREACHED();
565     return false;
566   }
567 
568   sql::Statement statement(GetDB().GetCachedStatement(
569       SQL_FROM_HERE,
570       base::StringPrintf("UPDATE %s "
571                          "SET current_path=?, target_path=?, "
572                          "mime_type=?, original_mime_type=?, "
573                          "received_bytes=?, state=?, "
574                          "danger_type=?, interrupt_reason=?, hash=?, "
575                          "end_time=?, total_bytes=?, "
576                          "opened=?, last_access_time=?, transient=?, "
577                          "by_ext_id=?, by_ext_name=?, "
578                          "etag=?, last_modified=? WHERE id=?",
579                          kDownloadsTable)
580           .c_str()));
581   int column = 0;
582   BindFilePath(statement, data.current_path, column++);
583   BindFilePath(statement, data.target_path, column++);
584   statement.BindString(column++, data.mime_type);
585   statement.BindString(column++, data.original_mime_type);
586   statement.BindInt64(column++, data.received_bytes);
587   statement.BindInt(column++, DownloadStateToInt(data.state));
588   statement.BindInt(column++, DownloadDangerTypeToInt(data.danger_type));
589   statement.BindInt(column++,
590                     DownloadInterruptReasonToInt(data.interrupt_reason));
591   statement.BindBlob(column++, data.hash.data(), data.hash.size());
592   statement.BindInt64(column++, data.end_time.ToInternalValue());
593   statement.BindInt64(column++, data.total_bytes);
594   statement.BindInt(column++, (data.opened ? 1 : 0));
595   statement.BindInt64(column++, data.last_access_time.ToInternalValue());
596   statement.BindInt(column++, (data.transient ? 1 : 0));
597   statement.BindString(column++, data.by_ext_id);
598   statement.BindString(column++, data.by_ext_name);
599   statement.BindString(column++, data.etag);
600   statement.BindString(column++, data.last_modified);
601   statement.BindInt64(column++, DownloadIdToInt(data.id));
602 
603   if (!statement.Run())
604     return false;
605 
606   if (data.download_slice_info.size() == 0) {
607     RemoveDownloadSlices(data.id);
608   } else {
609     for (size_t i = 0; i < data.download_slice_info.size(); ++i) {
610       if (!CreateOrUpdateDownloadSlice(data.download_slice_info[i]))
611         return false;
612     }
613   }
614 
615   return true;
616 }
617 
EnsureInProgressEntriesCleanedUp()618 void DownloadDatabase::EnsureInProgressEntriesCleanedUp() {
619   if (in_progress_entry_cleanup_completed_)
620     return;
621 
622   sql::Statement statement(GetDB().GetCachedStatement(
623       SQL_FROM_HERE,
624       base::StringPrintf(
625           "UPDATE %s SET state=?, interrupt_reason=? WHERE state=?",
626           kDownloadsTable)
627           .c_str()));
628   statement.BindInt(0, DownloadStateToInt(DownloadState::INTERRUPTED));
629   statement.BindInt(
630       1, DownloadInterruptReasonToInt(download_interrupt_reason_crash_));
631   statement.BindInt(2, DownloadStateToInt(DownloadState::IN_PROGRESS));
632 
633   statement.Run();
634   in_progress_entry_cleanup_completed_ = true;
635 }
636 
CreateDownload(const DownloadRow & info)637 bool DownloadDatabase::CreateDownload(const DownloadRow& info) {
638   DCHECK_NE(kInvalidDownloadId, info.id);
639   DCHECK(!info.guid.empty());
640   EnsureInProgressEntriesCleanedUp();
641 
642   if (info.url_chain.empty())
643     return false;
644 
645   if (info.state == DownloadState::INVALID)
646     return false;
647 
648   if (info.danger_type == DownloadDangerType::INVALID)
649     return false;
650 
651   {
652     sql::Statement statement_insert(GetDB().GetCachedStatement(
653         SQL_FROM_HERE,
654         base::StringPrintf(
655             "INSERT INTO %s "
656             "(id, guid, current_path, target_path, mime_type, "
657             "original_mime_type, "
658             " start_time, received_bytes, total_bytes, state, danger_type, "
659             " interrupt_reason, hash, end_time, opened, last_access_time, "
660             "transient, referrer, site_url, tab_url, tab_referrer_url, "
661             "http_method, "
662             " by_ext_id, by_ext_name, etag, last_modified) "
663             "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
664             "        ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
665             "        ?, ?, ?, ?, ?, ?)",
666             kDownloadsTable)
667             .c_str()));
668 
669     int column = 0;
670     statement_insert.BindInt64(column++, DownloadIdToInt(info.id));
671     statement_insert.BindString(column++, info.guid);
672     BindFilePath(statement_insert, info.current_path, column++);
673     BindFilePath(statement_insert, info.target_path, column++);
674     statement_insert.BindString(column++, info.mime_type);
675     statement_insert.BindString(column++, info.original_mime_type);
676     statement_insert.BindInt64(column++, info.start_time.ToInternalValue());
677     statement_insert.BindInt64(column++, info.received_bytes);
678     statement_insert.BindInt64(column++, info.total_bytes);
679     statement_insert.BindInt(column++, DownloadStateToInt(info.state));
680     statement_insert.BindInt(column++,
681                              DownloadDangerTypeToInt(info.danger_type));
682     statement_insert.BindInt(
683         column++, DownloadInterruptReasonToInt(info.interrupt_reason));
684     statement_insert.BindBlob(column++, info.hash.data(), info.hash.size());
685     statement_insert.BindInt64(column++, info.end_time.ToInternalValue());
686     statement_insert.BindInt(column++, info.opened ? 1 : 0);
687     statement_insert.BindInt64(column++,
688                                info.last_access_time.ToInternalValue());
689     statement_insert.BindInt(column++, info.transient ? 1 : 0);
690     statement_insert.BindString(column++, info.referrer_url.spec());
691     statement_insert.BindString(column++, info.site_url.spec());
692     statement_insert.BindString(column++, info.tab_url.spec());
693     statement_insert.BindString(column++, info.tab_referrer_url.spec());
694     statement_insert.BindString(column++, info.http_method);
695     statement_insert.BindString(column++, info.by_ext_id);
696     statement_insert.BindString(column++, info.by_ext_name);
697     statement_insert.BindString(column++, info.etag);
698     statement_insert.BindString(column++, info.last_modified);
699     if (!statement_insert.Run()) {
700       // GetErrorCode() returns a bitmask where the lower byte is a more general
701       // code and the upper byte is a more specific code. In order to save
702       // memory, take the general code, of which there are fewer than 50. See
703       // also sql/connection.cc
704       // http://www.sqlite.org/c3ref/c_abort_rollback.html
705       UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainInsertError",
706                                 GetDB().GetErrorCode() & 0xff, 50);
707       return false;
708     }
709   }
710 
711   {
712     sql::Statement count_urls(GetDB().GetCachedStatement(SQL_FROM_HERE,
713         "SELECT count(*) FROM downloads_url_chains WHERE id=?"));
714     count_urls.BindInt64(0, info.id);
715     if (count_urls.Step()) {
716       bool corrupt_urls = count_urls.ColumnInt(0) > 0;
717       if (corrupt_urls) {
718         // There should not be any URLs in downloads_url_chains for this
719         // info.id.  If there are, we don't want them to interfere with
720         // inserting the correct URLs, so just remove them.
721         RemoveDownloadURLs(info.id);
722       }
723     }
724   }
725 
726   sql::Statement statement_insert_chain(
727       GetDB().GetCachedStatement(SQL_FROM_HERE,
728                                  "INSERT INTO downloads_url_chains "
729                                  "(id, chain_index, url) "
730                                  "VALUES (?, ?, ?)"));
731   for (size_t i = 0; i < info.url_chain.size(); ++i) {
732     statement_insert_chain.BindInt64(0, info.id);
733     statement_insert_chain.BindInt(1, static_cast<int>(i));
734     statement_insert_chain.BindString(2, info.url_chain[i].spec());
735     if (!statement_insert_chain.Run()) {
736       UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainInsertError",
737                                 GetDB().GetErrorCode() & 0xff, 50);
738       RemoveDownload(info.id);
739       return false;
740     }
741     statement_insert_chain.Reset(true);
742   }
743 
744   for (size_t i = 0; i < info.download_slice_info.size(); ++i) {
745     if (!CreateOrUpdateDownloadSlice(info.download_slice_info[i])) {
746       RemoveDownload(info.id);
747       return false;
748     }
749   }
750 
751   return true;
752 }
753 
RemoveDownload(DownloadId id)754 void DownloadDatabase::RemoveDownload(DownloadId id) {
755   EnsureInProgressEntriesCleanedUp();
756 
757   sql::Statement downloads_statement(GetDB().GetCachedStatement(
758       SQL_FROM_HERE,
759       base::StringPrintf("DELETE FROM %s WHERE id=?", kDownloadsTable)
760           .c_str()));
761   downloads_statement.BindInt64(0, id);
762   if (!downloads_statement.Run()) {
763     UMA_HISTOGRAM_ENUMERATION("Download.DatabaseMainDeleteError",
764                               GetDB().GetErrorCode() & 0xff, 50);
765     return;
766   }
767   RemoveDownloadURLs(id);
768   RemoveDownloadSlices(id);
769 }
770 
RemoveDownloadURLs(DownloadId id)771 void DownloadDatabase::RemoveDownloadURLs(DownloadId id) {
772   sql::Statement urlchain_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
773       "DELETE FROM downloads_url_chains WHERE id=?"));
774   urlchain_statement.BindInt64(0, id);
775   if (!urlchain_statement.Run()) {
776     UMA_HISTOGRAM_ENUMERATION("Download.DatabaseURLChainDeleteError",
777                               GetDB().GetErrorCode() & 0xff, 50);
778   }
779 }
780 
CountDownloads()781 size_t DownloadDatabase::CountDownloads() {
782   EnsureInProgressEntriesCleanedUp();
783 
784   sql::Statement statement(GetDB().GetCachedStatement(
785       SQL_FROM_HERE,
786       base::StringPrintf("SELECT count(*) from %s", kDownloadsTable).c_str()));
787   statement.Step();
788   return statement.ColumnInt(0);
789 }
790 
CreateOrUpdateDownloadSlice(const DownloadSliceInfo & info)791 bool DownloadDatabase::CreateOrUpdateDownloadSlice(
792     const DownloadSliceInfo& info) {
793   // If the slice has no data, there is no need to insert it into the db. Note
794   // that for each slice, |received_bytes| can only go up. So if a slice is
795   // already in the db, its |received_bytes| should always be larger than 0.
796   if (info.received_bytes == 0)
797     return true;
798   sql::Statement statement_replace(GetDB().GetCachedStatement(
799       SQL_FROM_HERE,
800       base::StringPrintf("REPLACE INTO %s "
801                          "(download_id, offset, received_bytes, finished) "
802                          "VALUES (?, ?, ?, ?)",
803                          kDownloadsSlicesTable)
804           .c_str()));
805   int column = 0;
806   statement_replace.BindInt64(column++, info.download_id);
807   statement_replace.BindInt64(column++, info.offset);
808   statement_replace.BindInt64(column++, info.received_bytes);
809   statement_replace.BindInt64(column++, (info.finished ? 1 : 0));
810   return statement_replace.Run();
811 }
812 
RemoveDownloadSlices(DownloadId id)813 void DownloadDatabase::RemoveDownloadSlices(DownloadId id) {
814   sql::Statement statement_delete(GetDB().GetCachedStatement(
815       SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE download_id=?",
816                                         kDownloadsSlicesTable)
817                          .c_str()));
818   statement_delete.BindInt64(0, id);
819   statement_delete.Run();
820 }
821 
QueryDownloadSlices(DownloadRowMap * download_row_map)822 void DownloadDatabase::QueryDownloadSlices(DownloadRowMap* download_row_map) {
823   DCHECK(download_row_map);
824   sql::Statement statement_query(GetDB().GetCachedStatement(
825       SQL_FROM_HERE,
826       base::StringPrintf("SELECT download_id, offset, received_bytes, finished "
827                          "FROM %s "
828                          "ORDER BY download_id, offset",
829                          kDownloadsSlicesTable)
830           .c_str()));
831 
832   while (statement_query.Step()) {
833     int column = 0;
834     DownloadSliceInfo info;
835     // Convert signed integer from sqlite to unsigned DownloadId.
836     int64_t signed_id = statement_query.ColumnInt64(column++);
837     bool success = ConvertIntToDownloadId(signed_id, &info.download_id);
838     DCHECK(success) << "Invalid download ID found in downloads_slices table "
839                     << signed_id;
840     info.offset = statement_query.ColumnInt64(column++);
841     info.received_bytes = statement_query.ColumnInt64(column++);
842     info.finished = static_cast<bool>(statement_query.ColumnInt64(column++));
843 
844     // Confirm the download_id has already been seen--if it hasn't, discard the
845     // record.
846     auto it = download_row_map->find(info.download_id);
847     bool found = (it != download_row_map->end());
848     if (!found) {
849       RemoveDownloadSlices(info.download_id);
850       continue;
851     }
852     it->second->download_slice_info.push_back(info);
853   }
854 }
855 
856 }  // namespace history
857