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 "content/browser/appcache/appcache_database.h"
6
7#include "base/auto_reset.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/files/file_util.h"
11#include "base/logging.h"
12#include "base/strings/utf_string_conversions.h"
13#include "content/browser/appcache/appcache_entry.h"
14#include "content/browser/appcache/appcache_histograms.h"
15#include "sql/connection.h"
16#include "sql/error_delegate_util.h"
17#include "sql/meta_table.h"
18#include "sql/statement.h"
19#include "sql/transaction.h"
20
21namespace content {
22
23// Schema -------------------------------------------------------------------
24namespace {
25
26#if defined(APPCACHE_USE_SIMPLE_CACHE)
27const int kCurrentVersion = 6;
28const int kCompatibleVersion = 6;
29#else
30const int kCurrentVersion = 5;
31const int kCompatibleVersion = 5;
32#endif
33
34// A mechanism to run experiments that may affect in data being persisted
35// in different ways such that when the experiment is toggled on/off via
36// cmd line flags, the database gets reset. The active flags are stored at
37// the time of database creation and compared when reopening. If different
38// the database is reset.
39const char kExperimentFlagsKey[] = "ExperimentFlags";
40
41const char kGroupsTable[] = "Groups";
42const char kCachesTable[] = "Caches";
43const char kEntriesTable[] = "Entries";
44const char kNamespacesTable[] = "Namespaces";
45const char kOnlineWhiteListsTable[] = "OnlineWhiteLists";
46const char kDeletableResponseIdsTable[] = "DeletableResponseIds";
47
48struct TableInfo {
49  const char* table_name;
50  const char* columns;
51};
52
53struct IndexInfo {
54  const char* index_name;
55  const char* table_name;
56  const char* columns;
57  bool unique;
58};
59
60const TableInfo kTables[] = {
61  { kGroupsTable,
62    "(group_id INTEGER PRIMARY KEY,"
63    " origin TEXT,"
64    " manifest_url TEXT,"
65    " creation_time INTEGER,"
66    " last_access_time INTEGER)" },
67
68  { kCachesTable,
69    "(cache_id INTEGER PRIMARY KEY,"
70    " group_id INTEGER,"
71    " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
72    " update_time INTEGER,"
73    " cache_size INTEGER)" },  // intentionally not normalized
74
75  { kEntriesTable,
76    "(cache_id INTEGER,"
77    " url TEXT,"
78    " flags INTEGER,"
79    " response_id INTEGER,"
80    " response_size INTEGER)" },
81
82  { kNamespacesTable,
83    "(cache_id INTEGER,"
84    " origin TEXT,"  // intentionally not normalized
85    " type INTEGER,"
86    " namespace_url TEXT,"
87    " target_url TEXT,"
88    " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
89
90  { kOnlineWhiteListsTable,
91    "(cache_id INTEGER,"
92    " namespace_url TEXT,"
93    " is_pattern INTEGER CHECK(is_pattern IN (0, 1)))" },
94
95  { kDeletableResponseIdsTable,
96    "(response_id INTEGER NOT NULL)" },
97};
98
99const IndexInfo kIndexes[] = {
100  { "GroupsOriginIndex",
101    kGroupsTable,
102    "(origin)",
103    false },
104
105  { "GroupsManifestIndex",
106    kGroupsTable,
107    "(manifest_url)",
108    true },
109
110  { "CachesGroupIndex",
111    kCachesTable,
112    "(group_id)",
113    false },
114
115  { "EntriesCacheIndex",
116    kEntriesTable,
117    "(cache_id)",
118    false },
119
120  { "EntriesCacheAndUrlIndex",
121    kEntriesTable,
122    "(cache_id, url)",
123    true },
124
125  { "EntriesResponseIdIndex",
126    kEntriesTable,
127    "(response_id)",
128    true },
129
130  { "NamespacesCacheIndex",
131    kNamespacesTable,
132    "(cache_id)",
133    false },
134
135  { "NamespacesOriginIndex",
136    kNamespacesTable,
137    "(origin)",
138    false },
139
140  { "NamespacesCacheAndUrlIndex",
141    kNamespacesTable,
142    "(cache_id, namespace_url)",
143    true },
144
145  { "OnlineWhiteListCacheIndex",
146    kOnlineWhiteListsTable,
147    "(cache_id)",
148    false },
149
150  { "DeletableResponsesIdIndex",
151    kDeletableResponseIdsTable,
152    "(response_id)",
153    true },
154};
155
156const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
157const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
158
159bool CreateTable(sql::Connection* db, const TableInfo& info) {
160  std::string sql("CREATE TABLE ");
161  sql += info.table_name;
162  sql += info.columns;
163  return db->Execute(sql.c_str());
164}
165
166bool CreateIndex(sql::Connection* db, const IndexInfo& info) {
167  std::string sql;
168  if (info.unique)
169    sql += "CREATE UNIQUE INDEX ";
170  else
171    sql += "CREATE INDEX ";
172  sql += info.index_name;
173  sql += " ON ";
174  sql += info.table_name;
175  sql += info.columns;
176  return db->Execute(sql.c_str());
177}
178
179std::string GetActiveExperimentFlags() {
180  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
181          kEnableExecutableHandlers))
182    return std::string("executableHandlersEnabled");
183  return std::string();
184}
185
186}  // anon namespace
187
188// AppCacheDatabase ----------------------------------------------------------
189
190AppCacheDatabase::GroupRecord::GroupRecord()
191    : group_id(0) {
192}
193
194AppCacheDatabase::GroupRecord::~GroupRecord() {
195}
196
197AppCacheDatabase::NamespaceRecord::NamespaceRecord()
198    : cache_id(0) {
199}
200
201AppCacheDatabase::NamespaceRecord::~NamespaceRecord() {
202}
203
204
205AppCacheDatabase::AppCacheDatabase(const base::FilePath& path)
206    : db_file_path_(path),
207      is_disabled_(false),
208      is_recreating_(false),
209      was_corruption_detected_(false) {
210}
211
212AppCacheDatabase::~AppCacheDatabase() {
213}
214
215void AppCacheDatabase::Disable() {
216  VLOG(1) << "Disabling appcache database.";
217  is_disabled_ = true;
218  ResetConnectionAndTables();
219}
220
221int64 AppCacheDatabase::GetOriginUsage(const GURL& origin) {
222  std::vector<CacheRecord> records;
223  if (!FindCachesForOrigin(origin, &records))
224    return 0;
225
226  int64 origin_usage = 0;
227  std::vector<CacheRecord>::const_iterator iter = records.begin();
228  while (iter != records.end()) {
229    origin_usage += iter->cache_size;
230    ++iter;
231  }
232  return origin_usage;
233}
234
235bool AppCacheDatabase::GetAllOriginUsage(std::map<GURL, int64>* usage_map) {
236  std::set<GURL> origins;
237  if (!FindOriginsWithGroups(&origins))
238    return false;
239  for (std::set<GURL>::const_iterator origin = origins.begin();
240       origin != origins.end(); ++origin) {
241    (*usage_map)[*origin] = GetOriginUsage(*origin);
242  }
243  return true;
244}
245
246bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
247  DCHECK(origins && origins->empty());
248  if (!LazyOpen(false))
249    return false;
250
251  const char* kSql =
252      "SELECT DISTINCT(origin) FROM Groups";
253
254  sql::Statement statement(db_->GetUniqueStatement(kSql));
255
256  while (statement.Step())
257    origins->insert(GURL(statement.ColumnString(0)));
258
259  return statement.Succeeded();
260}
261
262bool AppCacheDatabase::FindLastStorageIds(
263    int64* last_group_id, int64* last_cache_id, int64* last_response_id,
264    int64* last_deletable_response_rowid) {
265  DCHECK(last_group_id && last_cache_id && last_response_id &&
266         last_deletable_response_rowid);
267
268  *last_group_id = 0;
269  *last_cache_id = 0;
270  *last_response_id = 0;
271  *last_deletable_response_rowid = 0;
272
273  if (!LazyOpen(false))
274    return false;
275
276  const char* kMaxGroupIdSql = "SELECT MAX(group_id) FROM Groups";
277  const char* kMaxCacheIdSql = "SELECT MAX(cache_id) FROM Caches";
278  const char* kMaxResponseIdFromEntriesSql =
279      "SELECT MAX(response_id) FROM Entries";
280  const char* kMaxResponseIdFromDeletablesSql =
281      "SELECT MAX(response_id) FROM DeletableResponseIds";
282  const char* kMaxDeletableResponseRowIdSql =
283      "SELECT MAX(rowid) FROM DeletableResponseIds";
284  int64 max_group_id;
285  int64 max_cache_id;
286  int64 max_response_id_from_entries;
287  int64 max_response_id_from_deletables;
288  int64 max_deletable_response_rowid;
289  if (!RunUniqueStatementWithInt64Result(kMaxGroupIdSql, &max_group_id) ||
290      !RunUniqueStatementWithInt64Result(kMaxCacheIdSql, &max_cache_id) ||
291      !RunUniqueStatementWithInt64Result(kMaxResponseIdFromEntriesSql,
292                                         &max_response_id_from_entries) ||
293      !RunUniqueStatementWithInt64Result(kMaxResponseIdFromDeletablesSql,
294                                         &max_response_id_from_deletables) ||
295      !RunUniqueStatementWithInt64Result(kMaxDeletableResponseRowIdSql,
296                                         &max_deletable_response_rowid)) {
297    return false;
298  }
299
300  *last_group_id = max_group_id;
301  *last_cache_id = max_cache_id;
302  *last_response_id = std::max(max_response_id_from_entries,
303                               max_response_id_from_deletables);
304  *last_deletable_response_rowid = max_deletable_response_rowid;
305  return true;
306}
307
308bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
309  DCHECK(record);
310  if (!LazyOpen(false))
311    return false;
312
313  const char* kSql =
314      "SELECT group_id, origin, manifest_url,"
315      "       creation_time, last_access_time"
316      "  FROM Groups WHERE group_id = ?";
317
318  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
319
320  statement.BindInt64(0, group_id);
321  if (!statement.Step())
322    return false;
323
324  ReadGroupRecord(statement, record);
325  DCHECK(record->group_id == group_id);
326  return true;
327}
328
329bool AppCacheDatabase::FindGroupForManifestUrl(
330    const GURL& manifest_url, GroupRecord* record) {
331  DCHECK(record);
332  if (!LazyOpen(false))
333    return false;
334
335  const char* kSql =
336      "SELECT group_id, origin, manifest_url,"
337      "       creation_time, last_access_time"
338      "  FROM Groups WHERE manifest_url = ?";
339
340  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
341  statement.BindString(0, manifest_url.spec());
342
343  if (!statement.Step())
344    return false;
345
346  ReadGroupRecord(statement, record);
347  DCHECK(record->manifest_url == manifest_url);
348  return true;
349}
350
351bool AppCacheDatabase::FindGroupsForOrigin(
352    const GURL& origin, std::vector<GroupRecord>* records) {
353  DCHECK(records && records->empty());
354  if (!LazyOpen(false))
355    return false;
356
357  const char* kSql =
358      "SELECT group_id, origin, manifest_url,"
359      "       creation_time, last_access_time"
360      "   FROM Groups WHERE origin = ?";
361
362  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
363  statement.BindString(0, origin.spec());
364
365  while (statement.Step()) {
366    records->push_back(GroupRecord());
367    ReadGroupRecord(statement, &records->back());
368    DCHECK(records->back().origin == origin);
369  }
370
371  return statement.Succeeded();
372}
373
374bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
375  DCHECK(record);
376  if (!LazyOpen(false))
377    return false;
378
379  const char* kSql =
380      "SELECT g.group_id, g.origin, g.manifest_url,"
381      "       g.creation_time, g.last_access_time"
382      "  FROM Groups g, Caches c"
383      "  WHERE c.cache_id = ? AND c.group_id = g.group_id";
384
385  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
386  statement.BindInt64(0, cache_id);
387
388  if (!statement.Step())
389    return false;
390
391  ReadGroupRecord(statement, record);
392  return true;
393}
394
395bool AppCacheDatabase::UpdateGroupLastAccessTime(
396    int64 group_id, base::Time time) {
397  if (!LazyOpen(true))
398    return false;
399
400  const char* kSql =
401      "UPDATE Groups SET last_access_time = ? WHERE group_id = ?";
402
403  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
404  statement.BindInt64(0, time.ToInternalValue());
405  statement.BindInt64(1, group_id);
406
407  return statement.Run() && db_->GetLastChangeCount();
408}
409
410bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
411  if (!LazyOpen(true))
412    return false;
413
414  const char* kSql =
415      "INSERT INTO Groups"
416      "  (group_id, origin, manifest_url, creation_time, last_access_time)"
417      "  VALUES(?, ?, ?, ?, ?)";
418
419  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
420  statement.BindInt64(0, record->group_id);
421  statement.BindString(1, record->origin.spec());
422  statement.BindString(2, record->manifest_url.spec());
423  statement.BindInt64(3, record->creation_time.ToInternalValue());
424  statement.BindInt64(4, record->last_access_time.ToInternalValue());
425
426  return statement.Run();
427}
428
429bool AppCacheDatabase::DeleteGroup(int64 group_id) {
430  if (!LazyOpen(false))
431    return false;
432
433  const char* kSql =
434      "DELETE FROM Groups WHERE group_id = ?";
435
436  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
437  statement.BindInt64(0, group_id);
438
439  return statement.Run();
440}
441
442bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
443  DCHECK(record);
444  if (!LazyOpen(false))
445    return false;
446
447  const char* kSql =
448      "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
449      " FROM Caches WHERE cache_id = ?";
450
451  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
452  statement.BindInt64(0, cache_id);
453
454  if (!statement.Step())
455    return false;
456
457  ReadCacheRecord(statement, record);
458  return true;
459}
460
461bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
462  DCHECK(record);
463  if (!LazyOpen(false))
464    return false;
465
466  const char* kSql =
467      "SELECT cache_id, group_id, online_wildcard, update_time, cache_size"
468      "  FROM Caches WHERE group_id = ?";
469
470  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
471  statement.BindInt64(0, group_id);
472
473  if (!statement.Step())
474    return false;
475
476  ReadCacheRecord(statement, record);
477  return true;
478}
479
480bool AppCacheDatabase::FindCachesForOrigin(
481    const GURL& origin, std::vector<CacheRecord>* records) {
482  DCHECK(records);
483  std::vector<GroupRecord> group_records;
484  if (!FindGroupsForOrigin(origin, &group_records))
485    return false;
486
487  CacheRecord cache_record;
488  std::vector<GroupRecord>::const_iterator iter = group_records.begin();
489  while (iter != group_records.end()) {
490    if (FindCacheForGroup(iter->group_id, &cache_record))
491      records->push_back(cache_record);
492    ++iter;
493  }
494  return true;
495}
496
497bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
498  if (!LazyOpen(true))
499    return false;
500
501  const char* kSql =
502      "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
503      "                    update_time, cache_size)"
504      "  VALUES(?, ?, ?, ?, ?)";
505
506  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
507  statement.BindInt64(0, record->cache_id);
508  statement.BindInt64(1, record->group_id);
509  statement.BindBool(2, record->online_wildcard);
510  statement.BindInt64(3, record->update_time.ToInternalValue());
511  statement.BindInt64(4, record->cache_size);
512
513  return statement.Run();
514}
515
516bool AppCacheDatabase::DeleteCache(int64 cache_id) {
517  if (!LazyOpen(false))
518    return false;
519
520  const char* kSql =
521      "DELETE FROM Caches WHERE cache_id = ?";
522
523  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
524  statement.BindInt64(0, cache_id);
525
526  return statement.Run();
527}
528
529bool AppCacheDatabase::FindEntriesForCache(
530    int64 cache_id, std::vector<EntryRecord>* records) {
531  DCHECK(records && records->empty());
532  if (!LazyOpen(false))
533    return false;
534
535  const char* kSql =
536      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
537      "  WHERE cache_id = ?";
538
539  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
540  statement.BindInt64(0, cache_id);
541
542  while (statement.Step()) {
543    records->push_back(EntryRecord());
544    ReadEntryRecord(statement, &records->back());
545    DCHECK(records->back().cache_id == cache_id);
546  }
547
548  return statement.Succeeded();
549}
550
551bool AppCacheDatabase::FindEntriesForUrl(
552    const GURL& url, std::vector<EntryRecord>* records) {
553  DCHECK(records && records->empty());
554  if (!LazyOpen(false))
555    return false;
556
557  const char* kSql =
558      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
559      "  WHERE url = ?";
560
561  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
562  statement.BindString(0, url.spec());
563
564  while (statement.Step()) {
565    records->push_back(EntryRecord());
566    ReadEntryRecord(statement, &records->back());
567    DCHECK(records->back().url == url);
568  }
569
570  return statement.Succeeded();
571}
572
573bool AppCacheDatabase::FindEntry(
574    int64 cache_id, const GURL& url, EntryRecord* record) {
575  DCHECK(record);
576  if (!LazyOpen(false))
577    return false;
578
579  const char* kSql =
580      "SELECT cache_id, url, flags, response_id, response_size FROM Entries"
581      "  WHERE cache_id = ? AND url = ?";
582
583  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
584  statement.BindInt64(0, cache_id);
585  statement.BindString(1, url.spec());
586
587  if (!statement.Step())
588    return false;
589
590  ReadEntryRecord(statement, record);
591  DCHECK(record->cache_id == cache_id);
592  DCHECK(record->url == url);
593  return true;
594}
595
596bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
597  if (!LazyOpen(true))
598    return false;
599
600  const char* kSql =
601      "INSERT INTO Entries (cache_id, url, flags, response_id, response_size)"
602      "  VALUES(?, ?, ?, ?, ?)";
603
604  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
605  statement.BindInt64(0, record->cache_id);
606  statement.BindString(1, record->url.spec());
607  statement.BindInt(2, record->flags);
608  statement.BindInt64(3, record->response_id);
609  statement.BindInt64(4, record->response_size);
610
611  return statement.Run();
612}
613
614bool AppCacheDatabase::InsertEntryRecords(
615    const std::vector<EntryRecord>& records) {
616  if (records.empty())
617    return true;
618  sql::Transaction transaction(db_.get());
619  if (!transaction.Begin())
620    return false;
621  std::vector<EntryRecord>::const_iterator iter = records.begin();
622  while (iter != records.end()) {
623    if (!InsertEntry(&(*iter)))
624      return false;
625    ++iter;
626  }
627  return transaction.Commit();
628}
629
630bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
631  if (!LazyOpen(false))
632    return false;
633
634  const char* kSql =
635      "DELETE FROM Entries WHERE cache_id = ?";
636
637  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
638  statement.BindInt64(0, cache_id);
639
640  return statement.Run();
641}
642
643bool AppCacheDatabase::AddEntryFlags(
644    const GURL& entry_url, int64 cache_id, int additional_flags) {
645  if (!LazyOpen(false))
646    return false;
647
648  const char* kSql =
649      "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
650
651  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
652  statement.BindInt(0, additional_flags);
653  statement.BindInt64(1, cache_id);
654  statement.BindString(2, entry_url.spec());
655
656  return statement.Run() && db_->GetLastChangeCount();
657}
658
659bool AppCacheDatabase::FindNamespacesForOrigin(
660    const GURL& origin,
661    std::vector<NamespaceRecord>* intercepts,
662    std::vector<NamespaceRecord>* fallbacks) {
663  DCHECK(intercepts && intercepts->empty());
664  DCHECK(fallbacks && fallbacks->empty());
665  if (!LazyOpen(false))
666    return false;
667
668  const char* kSql =
669      "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
670      "  FROM Namespaces WHERE origin = ?";
671
672  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
673  statement.BindString(0, origin.spec());
674
675  ReadNamespaceRecords(&statement, intercepts, fallbacks);
676
677  return statement.Succeeded();
678}
679
680bool AppCacheDatabase::FindNamespacesForCache(
681    int64 cache_id,
682    std::vector<NamespaceRecord>* intercepts,
683    std::vector<NamespaceRecord>* fallbacks) {
684  DCHECK(intercepts && intercepts->empty());
685  DCHECK(fallbacks && fallbacks->empty());
686  if (!LazyOpen(false))
687    return false;
688
689  const char* kSql =
690      "SELECT cache_id, origin, type, namespace_url, target_url, is_pattern"
691      "  FROM Namespaces WHERE cache_id = ?";
692
693  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
694  statement.BindInt64(0, cache_id);
695
696  ReadNamespaceRecords(&statement, intercepts, fallbacks);
697
698  return statement.Succeeded();
699}
700
701bool AppCacheDatabase::InsertNamespace(
702    const NamespaceRecord* record) {
703  if (!LazyOpen(true))
704    return false;
705
706  const char* kSql =
707      "INSERT INTO Namespaces"
708      "  (cache_id, origin, type, namespace_url, target_url, is_pattern)"
709      "  VALUES (?, ?, ?, ?, ?, ?)";
710
711  // Note: quick and dirty storage for the 'executable' bit w/o changing
712  // schemas, we use the high bit of 'type' field.
713  int type_with_executable_bit = record->namespace_.type;
714  if (record->namespace_.is_executable) {
715    type_with_executable_bit |= 0x8000000;
716    DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
717        kEnableExecutableHandlers));
718  }
719
720  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
721  statement.BindInt64(0, record->cache_id);
722  statement.BindString(1, record->origin.spec());
723  statement.BindInt(2, type_with_executable_bit);
724  statement.BindString(3, record->namespace_.namespace_url.spec());
725  statement.BindString(4, record->namespace_.target_url.spec());
726  statement.BindBool(5, record->namespace_.is_pattern);
727  return statement.Run();
728}
729
730bool AppCacheDatabase::InsertNamespaceRecords(
731    const std::vector<NamespaceRecord>& records) {
732  if (records.empty())
733    return true;
734  sql::Transaction transaction(db_.get());
735  if (!transaction.Begin())
736    return false;
737  std::vector<NamespaceRecord>::const_iterator iter = records.begin();
738  while (iter != records.end()) {
739    if (!InsertNamespace(&(*iter)))
740      return false;
741    ++iter;
742  }
743  return transaction.Commit();
744}
745
746bool AppCacheDatabase::DeleteNamespacesForCache(int64 cache_id) {
747  if (!LazyOpen(false))
748    return false;
749
750  const char* kSql =
751      "DELETE FROM Namespaces WHERE cache_id = ?";
752
753  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
754  statement.BindInt64(0, cache_id);
755
756  return statement.Run();
757}
758
759bool AppCacheDatabase::FindOnlineWhiteListForCache(
760    int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
761  DCHECK(records && records->empty());
762  if (!LazyOpen(false))
763    return false;
764
765  const char* kSql =
766      "SELECT cache_id, namespace_url, is_pattern FROM OnlineWhiteLists"
767      "  WHERE cache_id = ?";
768
769  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
770  statement.BindInt64(0, cache_id);
771
772  while (statement.Step()) {
773    records->push_back(OnlineWhiteListRecord());
774    this->ReadOnlineWhiteListRecord(statement, &records->back());
775    DCHECK(records->back().cache_id == cache_id);
776  }
777  return statement.Succeeded();
778}
779
780bool AppCacheDatabase::InsertOnlineWhiteList(
781    const OnlineWhiteListRecord* record) {
782  if (!LazyOpen(true))
783    return false;
784
785  const char* kSql =
786      "INSERT INTO OnlineWhiteLists (cache_id, namespace_url, is_pattern)"
787      "  VALUES (?, ?, ?)";
788
789  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
790  statement.BindInt64(0, record->cache_id);
791  statement.BindString(1, record->namespace_url.spec());
792  statement.BindBool(2, record->is_pattern);
793
794  return statement.Run();
795}
796
797bool AppCacheDatabase::InsertOnlineWhiteListRecords(
798    const std::vector<OnlineWhiteListRecord>& records) {
799  if (records.empty())
800    return true;
801  sql::Transaction transaction(db_.get());
802  if (!transaction.Begin())
803    return false;
804  std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
805  while (iter != records.end()) {
806    if (!InsertOnlineWhiteList(&(*iter)))
807      return false;
808    ++iter;
809  }
810  return transaction.Commit();
811}
812
813bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
814  if (!LazyOpen(false))
815    return false;
816
817  const char* kSql =
818      "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
819
820  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
821  statement.BindInt64(0, cache_id);
822
823  return statement.Run();
824}
825
826bool AppCacheDatabase::GetDeletableResponseIds(
827    std::vector<int64>* response_ids, int64 max_rowid, int limit) {
828  if (!LazyOpen(false))
829    return false;
830
831  const char* kSql =
832      "SELECT response_id FROM DeletableResponseIds "
833      "  WHERE rowid <= ?"
834      "  LIMIT ?";
835
836  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
837  statement.BindInt64(0, max_rowid);
838  statement.BindInt64(1, limit);
839
840  while (statement.Step())
841    response_ids->push_back(statement.ColumnInt64(0));
842  return statement.Succeeded();
843}
844
845bool AppCacheDatabase::InsertDeletableResponseIds(
846    const std::vector<int64>& response_ids) {
847  const char* kSql =
848      "INSERT INTO DeletableResponseIds (response_id) VALUES (?)";
849  return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
850}
851
852bool AppCacheDatabase::DeleteDeletableResponseIds(
853    const std::vector<int64>& response_ids) {
854  const char* kSql =
855      "DELETE FROM DeletableResponseIds WHERE response_id = ?";
856  return RunCachedStatementWithIds(SQL_FROM_HERE, kSql, response_ids);
857}
858
859bool AppCacheDatabase::RunCachedStatementWithIds(
860    const sql::StatementID& statement_id, const char* sql,
861    const std::vector<int64>& ids) {
862  DCHECK(sql);
863  if (!LazyOpen(true))
864    return false;
865
866  sql::Transaction transaction(db_.get());
867  if (!transaction.Begin())
868    return false;
869
870  sql::Statement statement(db_->GetCachedStatement(statement_id, sql));
871
872  std::vector<int64>::const_iterator iter = ids.begin();
873  while (iter != ids.end()) {
874    statement.BindInt64(0, *iter);
875    if (!statement.Run())
876      return false;
877    statement.Reset(true);
878    ++iter;
879  }
880
881  return transaction.Commit();
882}
883
884bool AppCacheDatabase::RunUniqueStatementWithInt64Result(
885    const char* sql, int64* result) {
886  DCHECK(sql);
887  sql::Statement statement(db_->GetUniqueStatement(sql));
888  if (!statement.Step()) {
889    return false;
890  }
891  *result = statement.ColumnInt64(0);
892  return true;
893}
894
895bool AppCacheDatabase::FindResponseIdsForCacheHelper(
896    int64 cache_id, std::vector<int64>* ids_vector,
897    std::set<int64>* ids_set) {
898  DCHECK(ids_vector || ids_set);
899  DCHECK(!(ids_vector && ids_set));
900  if (!LazyOpen(false))
901    return false;
902
903  const char* kSql =
904      "SELECT response_id FROM Entries WHERE cache_id = ?";
905
906  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
907
908  statement.BindInt64(0, cache_id);
909  while (statement.Step()) {
910    int64 id = statement.ColumnInt64(0);
911    if (ids_set)
912      ids_set->insert(id);
913    else
914      ids_vector->push_back(id);
915  }
916
917  return statement.Succeeded();
918}
919
920void AppCacheDatabase::ReadGroupRecord(
921    const sql::Statement& statement, GroupRecord* record) {
922  record->group_id = statement.ColumnInt64(0);
923  record->origin = GURL(statement.ColumnString(1));
924  record->manifest_url = GURL(statement.ColumnString(2));
925  record->creation_time =
926      base::Time::FromInternalValue(statement.ColumnInt64(3));
927  record->last_access_time =
928      base::Time::FromInternalValue(statement.ColumnInt64(4));
929}
930
931void AppCacheDatabase::ReadCacheRecord(
932    const sql::Statement& statement, CacheRecord* record) {
933  record->cache_id = statement.ColumnInt64(0);
934  record->group_id = statement.ColumnInt64(1);
935  record->online_wildcard = statement.ColumnBool(2);
936  record->update_time =
937      base::Time::FromInternalValue(statement.ColumnInt64(3));
938  record->cache_size = statement.ColumnInt64(4);
939}
940
941void AppCacheDatabase::ReadEntryRecord(
942    const sql::Statement& statement, EntryRecord* record) {
943  record->cache_id = statement.ColumnInt64(0);
944  record->url = GURL(statement.ColumnString(1));
945  record->flags = statement.ColumnInt(2);
946  record->response_id = statement.ColumnInt64(3);
947  record->response_size = statement.ColumnInt64(4);
948}
949
950void AppCacheDatabase::ReadNamespaceRecords(
951      sql::Statement* statement,
952      NamespaceRecordVector* intercepts,
953      NamespaceRecordVector* fallbacks) {
954  while (statement->Step()) {
955    AppCacheNamespaceType type = static_cast<AppCacheNamespaceType>(
956        statement->ColumnInt(2));
957    NamespaceRecordVector* records =
958        (type == APPCACHE_FALLBACK_NAMESPACE) ? fallbacks : intercepts;
959    records->push_back(NamespaceRecord());
960    ReadNamespaceRecord(statement, &records->back());
961  }
962}
963
964void AppCacheDatabase::ReadNamespaceRecord(
965    const sql::Statement* statement, NamespaceRecord* record) {
966  record->cache_id = statement->ColumnInt64(0);
967  record->origin = GURL(statement->ColumnString(1));
968  int type_with_executable_bit = statement->ColumnInt(2);
969  record->namespace_.namespace_url = GURL(statement->ColumnString(3));
970  record->namespace_.target_url = GURL(statement->ColumnString(4));
971  record->namespace_.is_pattern = statement->ColumnBool(5);
972
973  // Note: quick and dirty storage for the 'executable' bit w/o changing
974  // schemas, we use the high bit of 'type' field.
975  record->namespace_.type = static_cast<AppCacheNamespaceType>
976      (type_with_executable_bit & 0x7ffffff);
977  record->namespace_.is_executable =
978      (type_with_executable_bit & 0x80000000) != 0;
979  DCHECK(!record->namespace_.is_executable ||
980      base::CommandLine::ForCurrentProcess()->HasSwitch(
981          kEnableExecutableHandlers));
982}
983
984void AppCacheDatabase::ReadOnlineWhiteListRecord(
985    const sql::Statement& statement, OnlineWhiteListRecord* record) {
986  record->cache_id = statement.ColumnInt64(0);
987  record->namespace_url = GURL(statement.ColumnString(1));
988  record->is_pattern = statement.ColumnBool(2);
989}
990
991bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
992  if (db_)
993    return true;
994
995  // If we tried and failed once, don't try again in the same session
996  // to avoid creating an incoherent mess on disk.
997  if (is_disabled_)
998    return false;
999
1000  // Avoid creating a database at all if we can.
1001  bool use_in_memory_db = db_file_path_.empty();
1002  if (!create_if_needed &&
1003      (use_in_memory_db || !base::PathExists(db_file_path_))) {
1004    return false;
1005  }
1006
1007  db_.reset(new sql::Connection);
1008  meta_table_.reset(new sql::MetaTable);
1009
1010  db_->set_histogram_tag("AppCache");
1011
1012  bool opened = false;
1013  if (use_in_memory_db) {
1014    opened = db_->OpenInMemory();
1015  } else if (!base::CreateDirectory(db_file_path_.DirName())) {
1016    LOG(ERROR) << "Failed to create appcache directory.";
1017  } else {
1018    opened = db_->Open(db_file_path_);
1019    if (opened)
1020      db_->Preload();
1021  }
1022
1023  if (!opened || !db_->QuickIntegrityCheck() || !EnsureDatabaseVersion()) {
1024    LOG(ERROR) << "Failed to open the appcache database.";
1025    AppCacheHistograms::CountInitResult(
1026        AppCacheHistograms::SQL_DATABASE_ERROR);
1027
1028    // We're unable to open the database. This is a fatal error
1029    // which we can't recover from. We try to handle it by deleting
1030    // the existing appcache data and starting with a clean slate in
1031    // this browser session.
1032    if (!use_in_memory_db && DeleteExistingAndCreateNewDatabase())
1033      return true;
1034
1035    Disable();
1036    return false;
1037  }
1038
1039  AppCacheHistograms::CountInitResult(AppCacheHistograms::INIT_OK);
1040  was_corruption_detected_ = false;
1041  db_->set_error_callback(
1042      base::Bind(&AppCacheDatabase::OnDatabaseError, base::Unretained(this)));
1043  return true;
1044}
1045
1046bool AppCacheDatabase::EnsureDatabaseVersion() {
1047  if (!sql::MetaTable::DoesTableExist(db_.get()))
1048    return CreateSchema();
1049
1050  if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1051    return false;
1052
1053  if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
1054    LOG(WARNING) << "AppCache database is too new.";
1055    return false;
1056  }
1057
1058  std::string stored_flags;
1059  meta_table_->GetValue(kExperimentFlagsKey, &stored_flags);
1060  if (stored_flags != GetActiveExperimentFlags())
1061    return false;
1062
1063  if (meta_table_->GetVersionNumber() < kCurrentVersion)
1064    return UpgradeSchema();
1065
1066#ifndef NDEBUG
1067  DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
1068  for (int i = 0; i < kTableCount; ++i) {
1069    DCHECK(db_->DoesTableExist(kTables[i].table_name));
1070  }
1071  for (int i = 0; i < kIndexCount; ++i) {
1072    DCHECK(db_->DoesIndexExist(kIndexes[i].index_name));
1073  }
1074#endif
1075
1076  return true;
1077}
1078
1079bool AppCacheDatabase::CreateSchema() {
1080  sql::Transaction transaction(db_.get());
1081  if (!transaction.Begin())
1082    return false;
1083
1084  if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
1085    return false;
1086
1087  if (!meta_table_->SetValue(kExperimentFlagsKey,
1088                             GetActiveExperimentFlags())) {
1089    return false;
1090  }
1091
1092  for (int i = 0; i < kTableCount; ++i) {
1093    if (!CreateTable(db_.get(), kTables[i]))
1094      return false;
1095  }
1096
1097  for (int i = 0; i < kIndexCount; ++i) {
1098    if (!CreateIndex(db_.get(), kIndexes[i]))
1099      return false;
1100  }
1101
1102  return transaction.Commit();
1103}
1104
1105bool AppCacheDatabase::UpgradeSchema() {
1106#if defined(APPCACHE_USE_SIMPLE_CACHE)
1107  return DeleteExistingAndCreateNewDatabase();
1108#else
1109  if (meta_table_->GetVersionNumber() == 3) {
1110    // version 3 was pre 12/17/2011
1111    DCHECK_EQ(strcmp(kNamespacesTable, kTables[3].table_name), 0);
1112    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[6].table_name), 0);
1113    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[7].table_name), 0);
1114    DCHECK_EQ(strcmp(kNamespacesTable, kIndexes[8].table_name), 0);
1115
1116    const TableInfo kNamespaceTable_v4 = {
1117        kNamespacesTable,
1118        "(cache_id INTEGER,"
1119        " origin TEXT,"  // intentionally not normalized
1120        " type INTEGER,"
1121        " namespace_url TEXT,"
1122        " target_url TEXT)"
1123    };
1124
1125    // Migrate from the old FallbackNameSpaces to the newer Namespaces table,
1126    // but without the is_pattern column added in v5.
1127    sql::Transaction transaction(db_.get());
1128    if (!transaction.Begin() ||
1129        !CreateTable(db_.get(), kNamespaceTable_v4)) {
1130      return false;
1131    }
1132
1133    // Move data from the old table to the new table, setting the
1134    // 'type' for all current records to the value for
1135    // APPCACHE_FALLBACK_NAMESPACE.
1136    DCHECK_EQ(0, static_cast<int>(APPCACHE_FALLBACK_NAMESPACE));
1137    if (!db_->Execute(
1138            "INSERT INTO Namespaces"
1139            "  SELECT cache_id, origin, 0, namespace_url, fallback_entry_url"
1140            "  FROM FallbackNameSpaces")) {
1141      return false;
1142    }
1143
1144    // Drop the old table, indexes on that table are also removed by this.
1145    if (!db_->Execute("DROP TABLE FallbackNameSpaces"))
1146      return false;
1147
1148    // Create new indexes.
1149    if (!CreateIndex(db_.get(), kIndexes[6]) ||
1150        !CreateIndex(db_.get(), kIndexes[7]) ||
1151        !CreateIndex(db_.get(), kIndexes[8])) {
1152      return false;
1153    }
1154
1155    meta_table_->SetVersionNumber(4);
1156    meta_table_->SetCompatibleVersionNumber(4);
1157    if (!transaction.Commit())
1158      return false;
1159  }
1160
1161  if (meta_table_->GetVersionNumber() == 4) {
1162    // version 4 pre 3/30/2013
1163    // Add the is_pattern column to the Namespaces and OnlineWhitelists tables.
1164    DCHECK_EQ(strcmp(kNamespacesTable, "Namespaces"), 0);
1165    sql::Transaction transaction(db_.get());
1166    if (!transaction.Begin())
1167      return false;
1168    if (!db_->Execute(
1169            "ALTER TABLE Namespaces ADD COLUMN"
1170            "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1171      return false;
1172    }
1173    if (!db_->Execute(
1174            "ALTER TABLE OnlineWhitelists ADD COLUMN"
1175            "  is_pattern INTEGER CHECK(is_pattern IN (0, 1))")) {
1176      return false;
1177    }
1178    meta_table_->SetVersionNumber(5);
1179    meta_table_->SetCompatibleVersionNumber(5);
1180    return transaction.Commit();
1181  }
1182
1183  // If there is no upgrade path for the version on disk to the current
1184  // version, nuke everything and start over.
1185  return DeleteExistingAndCreateNewDatabase();
1186#endif
1187}
1188
1189void AppCacheDatabase::ResetConnectionAndTables() {
1190  meta_table_.reset();
1191  db_.reset();
1192}
1193
1194bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
1195  DCHECK(!db_file_path_.empty());
1196  DCHECK(base::PathExists(db_file_path_));
1197  VLOG(1) << "Deleting existing appcache data and starting over.";
1198
1199  ResetConnectionAndTables();
1200
1201  // This also deletes the disk cache data.
1202  base::FilePath directory = db_file_path_.DirName();
1203  if (!base::DeleteFile(directory, true))
1204    return false;
1205
1206  // Make sure the steps above actually deleted things.
1207  if (base::PathExists(directory))
1208    return false;
1209
1210  if (!base::CreateDirectory(directory))
1211    return false;
1212
1213  // So we can't go recursive.
1214  if (is_recreating_)
1215    return false;
1216
1217  base::AutoReset<bool> auto_reset(&is_recreating_, true);
1218  return LazyOpen(true);
1219}
1220
1221void AppCacheDatabase::OnDatabaseError(int err, sql::Statement* stmt) {
1222  was_corruption_detected_ |= sql::IsErrorCatastrophic(err);
1223  if (!db_->ShouldIgnoreSqliteError(err))
1224    DLOG(ERROR) << db_->GetErrorMessage();
1225  // TODO: Maybe use non-catostrophic errors to trigger a full integrity check?
1226}
1227
1228}  // namespace content
1229