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