sqlite_persistent_cookie_store.cc revision 9aa5f8b435409e063e76e6e4f12f5c4e85997e0a
1// Copyright (c) 2010 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/net/sqlite_persistent_cookie_store.h"
6
7#include <list>
8
9#include "app/sql/meta_table.h"
10#include "app/sql/statement.h"
11#include "app/sql/transaction.h"
12#include "base/basictypes.h"
13#include "base/file_path.h"
14#include "base/file_util.h"
15#include "base/logging.h"
16#include "base/metrics/histogram.h"
17#include "base/ref_counted.h"
18#include "base/scoped_ptr.h"
19#include "base/string_util.h"
20#include "base/threading/thread.h"
21#ifndef ANDROID
22#include "chrome/browser/browser_thread.h"
23#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
24#endif
25
26#ifdef ANDROID
27base::Thread* getDbThread()
28{
29  static base::Thread* dbThread = NULL;
30  if (dbThread && dbThread->IsRunning())
31    return dbThread;
32
33  if (!dbThread)
34    dbThread = new base::Thread("db");
35
36  if (!dbThread)
37    return NULL;
38
39  base::Thread::Options options;
40  options.message_loop_type = MessageLoop::TYPE_DEFAULT;
41  if (!dbThread->StartWithOptions(options)) {
42    delete dbThread;
43    dbThread = NULL;
44  }
45  return dbThread;
46}
47#endif
48
49using base::Time;
50
51// This class is designed to be shared between any calling threads and the
52// database thread.  It batches operations and commits them on a timer.
53class SQLitePersistentCookieStore::Backend
54    : public base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend> {
55 public:
56  explicit Backend(const FilePath& path)
57      : path_(path),
58        db_(NULL),
59        num_pending_(0),
60        clear_local_state_on_exit_(false)
61#if defined(ANDROID)
62        , cookie_count_(0)
63#endif
64  {
65  }
66
67  // Creates or load the SQLite database.
68  bool Load(std::vector<net::CookieMonster::CanonicalCookie*>* cookies);
69
70  // Batch a cookie addition.
71  void AddCookie(const net::CookieMonster::CanonicalCookie& cc);
72
73  // Batch a cookie access time update.
74  void UpdateCookieAccessTime(const net::CookieMonster::CanonicalCookie& cc);
75
76  // Batch a cookie deletion.
77  void DeleteCookie(const net::CookieMonster::CanonicalCookie& cc);
78
79  // Commit pending operations as soon as possible.
80  void Flush(Task* completion_task);
81
82  // Commit any pending operations and close the database.  This must be called
83  // before the object is destructed.
84  void Close();
85
86  void SetClearLocalStateOnExit(bool clear_local_state);
87
88#if defined(ANDROID)
89  int get_cookie_count() const { return cookie_count_; }
90  void set_cookie_count(int count) { cookie_count_ = count; }
91#endif
92
93 private:
94  friend class base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend>;
95
96  // You should call Close() before destructing this object.
97  ~Backend() {
98    DCHECK(!db_.get()) << "Close should have already been called.";
99    DCHECK(num_pending_ == 0 && pending_.empty());
100  }
101
102  // Database upgrade statements.
103  bool EnsureDatabaseVersion();
104
105  class PendingOperation {
106   public:
107    typedef enum {
108      COOKIE_ADD,
109      COOKIE_UPDATEACCESS,
110      COOKIE_DELETE,
111    } OperationType;
112
113    PendingOperation(OperationType op,
114                     const net::CookieMonster::CanonicalCookie& cc)
115        : op_(op), cc_(cc) { }
116
117    OperationType op() const { return op_; }
118    const net::CookieMonster::CanonicalCookie& cc() const { return cc_; }
119
120   private:
121    OperationType op_;
122    net::CookieMonster::CanonicalCookie cc_;
123  };
124
125 private:
126  // Batch a cookie operation (add or delete)
127  void BatchOperation(PendingOperation::OperationType op,
128                      const net::CookieMonster::CanonicalCookie& cc);
129  // Commit our pending operations to the database.
130#if defined(ANDROID)
131  void Commit(Task* completion_task);
132#else
133  void Commit();
134#endif
135  // Close() executed on the background thread.
136  void InternalBackgroundClose();
137
138  FilePath path_;
139  scoped_ptr<sql::Connection> db_;
140  sql::MetaTable meta_table_;
141
142  typedef std::list<PendingOperation*> PendingOperationsList;
143  PendingOperationsList pending_;
144  PendingOperationsList::size_type num_pending_;
145  // True if the persistent store should be deleted upon destruction.
146  bool clear_local_state_on_exit_;
147  // Guard |pending_|, |num_pending_| and |clear_local_state_on_exit_|.
148  Lock lock_;
149
150#if defined(ANDROID)
151  // Number of cookies that have actually been saved. Updated during Commit().
152  volatile int cookie_count_;
153#endif
154
155  DISALLOW_COPY_AND_ASSIGN(Backend);
156};
157
158// Version number of the database. In version 4, we migrated the time epoch.
159// If you open the DB with an older version on Mac or Linux, the times will
160// look wonky, but the file will likely be usable. On Windows version 3 and 4
161// are the same.
162//
163// Version 3 updated the database to include the last access time, so we can
164// expire them in decreasing order of use when we've reached the maximum
165// number of cookies.
166static const int kCurrentVersionNumber = 4;
167static const int kCompatibleVersionNumber = 3;
168
169namespace {
170
171// Initializes the cookies table, returning true on success.
172bool InitTable(sql::Connection* db) {
173  if (!db->DoesTableExist("cookies")) {
174    if (!db->Execute("CREATE TABLE cookies ("
175                     "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY,"
176                     "host_key TEXT NOT NULL,"
177                     "name TEXT NOT NULL,"
178                     "value TEXT NOT NULL,"
179                     "path TEXT NOT NULL,"
180#if defined(ANDROID)
181                     // On some mobile platforms, we persist session cookies
182                     // because the OS can kill the browser during a session.
183                     // If so, expires_utc is set to 0. When the field is read
184                     // into a Time object, Time::is_null() will return true.
185#else
186                     // We only store persistent, so we know it expires
187#endif
188                     "expires_utc INTEGER NOT NULL,"
189                     "secure INTEGER NOT NULL,"
190                     "httponly INTEGER NOT NULL,"
191                     "last_access_utc INTEGER NOT NULL)"))
192      return false;
193  }
194
195  // Try to create the index every time. Older versions did not have this index,
196  // so we want those people to get it. Ignore errors, since it may exist.
197#ifdef ANDROID
198  db->Execute(
199      "CREATE INDEX IF NOT EXISTS cookie_times ON cookies (creation_utc)");
200#else
201  db->Execute("CREATE INDEX cookie_times ON cookies (creation_utc)");
202#endif
203  return true;
204}
205
206}  // namespace
207
208bool SQLitePersistentCookieStore::Backend::Load(
209    std::vector<net::CookieMonster::CanonicalCookie*>* cookies) {
210  // This function should be called only once per instance.
211  DCHECK(!db_.get());
212
213  db_.reset(new sql::Connection);
214  if (!db_->Open(path_)) {
215    NOTREACHED() << "Unable to open cookie DB.";
216    db_.reset();
217    return false;
218  }
219
220#ifndef ANDROID
221  // GetErrorHandlerForCookieDb is defined in sqlite_diagnostics.h
222  // which we do not currently include on Android
223  db_->set_error_delegate(GetErrorHandlerForCookieDb());
224#endif
225
226  if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
227    NOTREACHED() << "Unable to open cookie DB.";
228    db_.reset();
229    return false;
230  }
231
232  db_->Preload();
233
234  // Slurp all the cookies into the out-vector.
235  sql::Statement smt(db_->GetUniqueStatement(
236      "SELECT creation_utc, host_key, name, value, path, expires_utc, secure, "
237      "httponly, last_access_utc FROM cookies"));
238  if (!smt) {
239    NOTREACHED() << "select statement prep failed";
240    db_.reset();
241    return false;
242  }
243
244  while (smt.Step()) {
245#if defined(ANDROID)
246    base::Time expires = Time::FromInternalValue(smt.ColumnInt64(5));
247#endif
248    scoped_ptr<net::CookieMonster::CanonicalCookie> cc(
249        new net::CookieMonster::CanonicalCookie(
250            smt.ColumnString(2),                            // name
251            smt.ColumnString(3),                            // value
252            smt.ColumnString(1),                            // domain
253            smt.ColumnString(4),                            // path
254            smt.ColumnInt(6) != 0,                          // secure
255            smt.ColumnInt(7) != 0,                          // httponly
256            Time::FromInternalValue(smt.ColumnInt64(0)),    // creation_utc
257            Time::FromInternalValue(smt.ColumnInt64(8)),    // last_access_utc
258#if defined(ANDROID)
259            !expires.is_null(),                             // has_expires
260            expires));                                      // expires_utc
261#else
262            true,                                           // has_expires
263            Time::FromInternalValue(smt.ColumnInt64(5))));  // expires_utc
264#endif
265    DLOG_IF(WARNING,
266            cc->CreationDate() > Time::Now()) << L"CreationDate too recent";
267    cookies->push_back(cc.release());
268  }
269
270#ifdef ANDROID
271  set_cookie_count(cookies->size());
272#endif
273
274  return true;
275}
276
277bool SQLitePersistentCookieStore::Backend::EnsureDatabaseVersion() {
278  // Version check.
279  if (!meta_table_.Init(
280      db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
281    return false;
282  }
283
284  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
285    LOG(WARNING) << "Cookie database is too new.";
286    return false;
287  }
288
289  int cur_version = meta_table_.GetVersionNumber();
290  if (cur_version == 2) {
291    sql::Transaction transaction(db_.get());
292    if (!transaction.Begin())
293      return false;
294    if (!db_->Execute("ALTER TABLE cookies ADD COLUMN last_access_utc "
295                     "INTEGER DEFAULT 0") ||
296        !db_->Execute("UPDATE cookies SET last_access_utc = creation_utc")) {
297      LOG(WARNING) << "Unable to update cookie database to version 3.";
298      return false;
299    }
300    ++cur_version;
301    meta_table_.SetVersionNumber(cur_version);
302    meta_table_.SetCompatibleVersionNumber(
303        std::min(cur_version, kCompatibleVersionNumber));
304    transaction.Commit();
305  }
306
307  if (cur_version == 3) {
308    // The time epoch changed for Mac & Linux in this version to match Windows.
309    // This patch came after the main epoch change happened, so some
310    // developers have "good" times for cookies added by the more recent
311    // versions. So we have to be careful to only update times that are under
312    // the old system (which will appear to be from before 1970 in the new
313    // system). The magic number used below is 1970 in our time units.
314    sql::Transaction transaction(db_.get());
315    transaction.Begin();
316#if !defined(OS_WIN)
317    db_->Execute(
318        "UPDATE cookies "
319        "SET creation_utc = creation_utc + 11644473600000000 "
320        "WHERE rowid IN "
321        "(SELECT rowid FROM cookies WHERE "
322          "creation_utc > 0 AND creation_utc < 11644473600000000)");
323    db_->Execute(
324        "UPDATE cookies "
325        "SET expires_utc = expires_utc + 11644473600000000 "
326        "WHERE rowid IN "
327        "(SELECT rowid FROM cookies WHERE "
328          "expires_utc > 0 AND expires_utc < 11644473600000000)");
329    db_->Execute(
330        "UPDATE cookies "
331        "SET last_access_utc = last_access_utc + 11644473600000000 "
332        "WHERE rowid IN "
333        "(SELECT rowid FROM cookies WHERE "
334          "last_access_utc > 0 AND last_access_utc < 11644473600000000)");
335#endif
336    ++cur_version;
337    meta_table_.SetVersionNumber(cur_version);
338    transaction.Commit();
339  }
340
341  // Put future migration cases here.
342
343  // When the version is too old, we just try to continue anyway, there should
344  // not be a released product that makes a database too old for us to handle.
345  LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
346      "Cookie database version " << cur_version << " is too old to handle.";
347
348  return true;
349}
350
351void SQLitePersistentCookieStore::Backend::AddCookie(
352    const net::CookieMonster::CanonicalCookie& cc) {
353  BatchOperation(PendingOperation::COOKIE_ADD, cc);
354}
355
356void SQLitePersistentCookieStore::Backend::UpdateCookieAccessTime(
357    const net::CookieMonster::CanonicalCookie& cc) {
358  BatchOperation(PendingOperation::COOKIE_UPDATEACCESS, cc);
359}
360
361void SQLitePersistentCookieStore::Backend::DeleteCookie(
362    const net::CookieMonster::CanonicalCookie& cc) {
363  BatchOperation(PendingOperation::COOKIE_DELETE, cc);
364}
365
366void SQLitePersistentCookieStore::Backend::BatchOperation(
367    PendingOperation::OperationType op,
368    const net::CookieMonster::CanonicalCookie& cc) {
369  // Commit every 30 seconds.
370  static const int kCommitIntervalMs = 30 * 1000;
371  // Commit right away if we have more than 512 outstanding operations.
372  static const size_t kCommitAfterBatchSize = 512;
373#ifndef ANDROID
374  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
375#endif
376
377  // We do a full copy of the cookie here, and hopefully just here.
378  scoped_ptr<PendingOperation> po(new PendingOperation(op, cc));
379
380  PendingOperationsList::size_type num_pending;
381  {
382    AutoLock locked(lock_);
383    pending_.push_back(po.release());
384    num_pending = ++num_pending_;
385  }
386
387#ifdef ANDROID
388  if (!getDbThread())
389    return;
390  MessageLoop* loop = getDbThread()->message_loop();
391#endif
392
393  if (num_pending == 1) {
394    // We've gotten our first entry for this batch, fire off the timer.
395#ifdef ANDROID
396    loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(
397        this, &Backend::Commit, static_cast<Task*>(NULL)), kCommitIntervalMs);
398#else
399    BrowserThread::PostDelayedTask(
400        BrowserThread::DB, FROM_HERE,
401        NewRunnableMethod(this, &Backend::Commit), kCommitIntervalMs);
402#endif
403  } else if (num_pending == kCommitAfterBatchSize) {
404    // We've reached a big enough batch, fire off a commit now.
405#ifdef ANDROID
406    loop->PostTask(FROM_HERE, NewRunnableMethod(
407        this, &Backend::Commit, static_cast<Task*>(NULL)));
408#else
409    BrowserThread::PostTask(
410        BrowserThread::DB, FROM_HERE,
411        NewRunnableMethod(this, &Backend::Commit));
412#endif
413  }
414}
415
416#if defined(ANDROID)
417void SQLitePersistentCookieStore::Backend::Commit(Task* completion_task) {
418#else
419void SQLitePersistentCookieStore::Backend::Commit() {
420  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
421#endif
422
423#if defined(ANDROID)
424  if (completion_task) {
425    // We post this task to the current thread, so it won't run until we exit.
426    MessageLoop::current()->PostTask(FROM_HERE, completion_task);
427  }
428#endif
429
430  PendingOperationsList ops;
431  {
432    AutoLock locked(lock_);
433    pending_.swap(ops);
434    num_pending_ = 0;
435  }
436
437  // Maybe an old timer fired or we are already Close()'ed.
438  if (!db_.get() || ops.empty())
439    return;
440
441  sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
442      "INSERT INTO cookies (creation_utc, host_key, name, value, path, "
443      "expires_utc, secure, httponly, last_access_utc) "
444      "VALUES (?,?,?,?,?,?,?,?,?)"));
445  if (!add_smt) {
446    NOTREACHED();
447    return;
448  }
449
450  sql::Statement update_access_smt(db_->GetCachedStatement(SQL_FROM_HERE,
451      "UPDATE cookies SET last_access_utc=? WHERE creation_utc=?"));
452  if (!update_access_smt) {
453    NOTREACHED();
454    return;
455  }
456
457  sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
458                         "DELETE FROM cookies WHERE creation_utc=?"));
459  if (!del_smt) {
460    NOTREACHED();
461    return;
462  }
463
464  sql::Transaction transaction(db_.get());
465  if (!transaction.Begin()) {
466    NOTREACHED();
467    return;
468  }
469#if defined(ANDROID)
470  int cookie_delta = 0;
471#endif
472  for (PendingOperationsList::iterator it = ops.begin();
473       it != ops.end(); ++it) {
474    // Free the cookies as we commit them to the database.
475    scoped_ptr<PendingOperation> po(*it);
476    switch (po->op()) {
477      case PendingOperation::COOKIE_ADD:
478#if defined(ANDROID)
479        ++cookie_delta;
480#endif
481        add_smt.Reset();
482        add_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue());
483        add_smt.BindString(1, po->cc().Domain());
484        add_smt.BindString(2, po->cc().Name());
485        add_smt.BindString(3, po->cc().Value());
486        add_smt.BindString(4, po->cc().Path());
487        add_smt.BindInt64(5, po->cc().ExpiryDate().ToInternalValue());
488        add_smt.BindInt(6, po->cc().IsSecure());
489        add_smt.BindInt(7, po->cc().IsHttpOnly());
490        add_smt.BindInt64(8, po->cc().LastAccessDate().ToInternalValue());
491        if (!add_smt.Run())
492          NOTREACHED() << "Could not add a cookie to the DB.";
493        break;
494
495      case PendingOperation::COOKIE_UPDATEACCESS:
496        update_access_smt.Reset();
497        update_access_smt.BindInt64(0,
498            po->cc().LastAccessDate().ToInternalValue());
499        update_access_smt.BindInt64(1,
500            po->cc().CreationDate().ToInternalValue());
501        if (!update_access_smt.Run())
502          NOTREACHED() << "Could not update cookie last access time in the DB.";
503        break;
504
505      case PendingOperation::COOKIE_DELETE:
506#if defined(ANDROID)
507        --cookie_delta;
508#endif
509        del_smt.Reset();
510        del_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue());
511        if (!del_smt.Run())
512          NOTREACHED() << "Could not delete a cookie from the DB.";
513        break;
514
515      default:
516        NOTREACHED();
517        break;
518    }
519  }
520  bool succeeded = transaction.Commit();
521#if defined(ANDROID)
522  if (succeeded)
523      cookie_count_ += cookie_delta;
524#endif
525  UMA_HISTOGRAM_ENUMERATION("Cookie.BackingStoreUpdateResults",
526                            succeeded ? 0 : 1, 2);
527}
528
529void SQLitePersistentCookieStore::Backend::Flush(Task* completion_task) {
530#if defined(ANDROID)
531    if (!getDbThread()) {
532      if (completion_task)
533        MessageLoop::current()->PostTask(FROM_HERE, completion_task);
534      return;
535    }
536
537    MessageLoop* loop = getDbThread()->message_loop();
538    loop->PostTask(FROM_HERE, NewRunnableMethod(
539        this, &Backend::Commit, completion_task));
540#else
541  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
542  BrowserThread::PostTask(
543      BrowserThread::DB, FROM_HERE, NewRunnableMethod(this, &Backend::Commit));
544  if (completion_task) {
545    // We want the completion task to run immediately after Commit() returns.
546    // Posting it from here means there is less chance of another task getting
547    // onto the message queue first, than if we posted it from Commit() itself.
548    BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, completion_task);
549  }
550#endif
551}
552
553// Fire off a close message to the background thread.  We could still have a
554// pending commit timer that will be holding a reference on us, but if/when
555// this fires we will already have been cleaned up and it will be ignored.
556void SQLitePersistentCookieStore::Backend::Close() {
557#ifndef ANDROID
558  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
559#endif
560
561#ifdef ANDROID
562  if (!getDbThread())
563    return;
564
565  MessageLoop* loop = getDbThread()->message_loop();
566  loop->PostTask(FROM_HERE,
567      NewRunnableMethod(this, &Backend::InternalBackgroundClose));
568#else
569  // Must close the backend on the background thread.
570  BrowserThread::PostTask(
571      BrowserThread::DB, FROM_HERE,
572      NewRunnableMethod(this, &Backend::InternalBackgroundClose));
573#endif
574}
575
576void SQLitePersistentCookieStore::Backend::InternalBackgroundClose() {
577#ifndef ANDROID
578  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
579#endif
580  // Commit any pending operations
581#if defined(ANDROID)
582  Commit(NULL);
583#else
584  Commit();
585#endif
586
587  db_.reset();
588
589  if (clear_local_state_on_exit_)
590    file_util::Delete(path_, false);
591}
592
593void SQLitePersistentCookieStore::Backend::SetClearLocalStateOnExit(
594    bool clear_local_state) {
595  AutoLock locked(lock_);
596  clear_local_state_on_exit_ = clear_local_state;
597}
598SQLitePersistentCookieStore::SQLitePersistentCookieStore(const FilePath& path)
599    : backend_(new Backend(path)) {
600}
601
602SQLitePersistentCookieStore::~SQLitePersistentCookieStore() {
603  if (backend_.get()) {
604    backend_->Close();
605    // Release our reference, it will probably still have a reference if the
606    // background thread has not run Close() yet.
607    backend_ = NULL;
608  }
609}
610
611bool SQLitePersistentCookieStore::Load(
612    std::vector<net::CookieMonster::CanonicalCookie*>* cookies) {
613  return backend_->Load(cookies);
614}
615
616void SQLitePersistentCookieStore::AddCookie(
617    const net::CookieMonster::CanonicalCookie& cc) {
618  if (backend_.get())
619    backend_->AddCookie(cc);
620}
621
622void SQLitePersistentCookieStore::UpdateCookieAccessTime(
623    const net::CookieMonster::CanonicalCookie& cc) {
624  if (backend_.get())
625    backend_->UpdateCookieAccessTime(cc);
626}
627
628void SQLitePersistentCookieStore::DeleteCookie(
629    const net::CookieMonster::CanonicalCookie& cc) {
630  if (backend_.get())
631    backend_->DeleteCookie(cc);
632}
633
634void SQLitePersistentCookieStore::SetClearLocalStateOnExit(
635    bool clear_local_state) {
636  if (backend_.get())
637    backend_->SetClearLocalStateOnExit(clear_local_state);
638}
639
640void SQLitePersistentCookieStore::Flush(Task* completion_task) {
641  if (backend_.get())
642    backend_->Flush(completion_task);
643  else if (completion_task)
644    MessageLoop::current()->PostTask(FROM_HERE, completion_task);
645}
646
647#if defined(ANDROID)
648int SQLitePersistentCookieStore::GetCookieCount() {
649  int result = backend_ ? backend_->get_cookie_count() : 0;
650  return result;
651}
652#endif
653