• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
BindFilePath(sql::Statement & statement,const base::FilePath & path,int col)44 void BindFilePath(sql::Statement& statement, const base::FilePath& path,
45                   int col) {
46   statement.BindString(col, path.value());
47 }
ColumnFilePath(sql::Statement & statement,int col)48 base::FilePath ColumnFilePath(sql::Statement& statement, int col) {
49   return base::FilePath(statement.ColumnString(col));
50 }
51 
52 #else
53 
54 // See above.
BindFilePath(sql::Statement & statement,const base::FilePath & path,int col)55 void BindFilePath(sql::Statement& statement, const base::FilePath& path,
56                   int col) {
57   statement.BindString16(col, path.value());
58 }
ColumnFilePath(sql::Statement & statement,int col)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 
StateToInt(DownloadItem::DownloadState state)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 
IntToState(int state)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 
DangerTypeToInt(content::DownloadDangerType danger_type)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 
IntToDangerType(int danger_type)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 
DownloadDatabase()171 DownloadDatabase::DownloadDatabase()
172     : owning_thread_set_(false),
173       owning_thread_(0),
174       in_progress_entry_cleanup_completed_(false) {
175 }
176 
~DownloadDatabase()177 DownloadDatabase::~DownloadDatabase() {
178 }
179 
EnsureColumnExists(const std::string & name,const std::string & type)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 
MigrateMimeType()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 
MigrateDownloadsState()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 
MigrateDownloadsReasonPathsAndDangerType()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 
MigrateReferrer()274 bool DownloadDatabase::MigrateReferrer() {
275   return EnsureColumnExists("referrer", "VARCHAR NOT NULL DEFAULT \"\"");
276 }
277 
MigrateDownloadedByExtension()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 
MigrateDownloadValidators()283 bool DownloadDatabase::MigrateDownloadValidators() {
284   return EnsureColumnExists("etag", "VARCHAR NOT NULL DEFAULT \"\"") &&
285          EnsureColumnExists("last_modified", "VARCHAR NOT NULL DEFAULT \"\"");
286 }
287 
InitDownloadTable()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 
GetNextDownloadId()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 
DropDownloadTable()349 bool DownloadDatabase::DropDownloadTable() {
350   return GetDB().Execute("DROP TABLE downloads");
351 }
352 
QueryDownloads(std::vector<DownloadRow> * results)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 
UpdateDownload(const DownloadRow & data)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 
EnsureInProgressEntriesCleanedUp()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 
CreateDownload(const DownloadRow & info)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 
RemoveDownload(uint32 id)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 
RemoveDownloadURLs(uint32 id)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 
CountDownloads()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