Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(520)

Side by Side Diff: chrome/common/net/cookie_monster_sqlite.cc

Issue 201099: Convert the sqlite cookie database and web database to use the new sqlite... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/common/net/cookie_monster_sqlite.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 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/common/net/cookie_monster_sqlite.h"
6
7 #include <list>
8
9 #include "base/basictypes.h"
10 #include "base/logging.h"
11 #include "base/ref_counted.h"
12 #include "base/string_util.h"
13 #include "base/thread.h"
14 #include "chrome/common/sqlite_compiled_statement.h"
15 #include "chrome/common/sqlite_utils.h"
16
17 using base::Time;
18
19 // This class is designed to be shared between any calling threads and the
20 // database thread. It batches operations and commits them on a timer.
21 class SQLitePersistentCookieStore::Backend
22 : public base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend> {
23 public:
24 // The passed database pointer must be already-initialized. This object will
25 // take ownership.
26 explicit Backend(sqlite3* db, MessageLoop* loop)
27 : db_(db),
28 background_loop_(loop),
29 cache_(new SqliteStatementCache(db)),
30 num_pending_(0) {
31 DCHECK(db_) << "Database must exist.";
32 }
33
34 // You should call Close() before destructing this object.
35 ~Backend() {
36 DCHECK(!db_) << "Close should have already been called.";
37 DCHECK(num_pending_ == 0 && pending_.empty());
38 }
39
40 // Batch a cookie addition.
41 void AddCookie(const std::string& key,
42 const net::CookieMonster::CanonicalCookie& cc);
43
44 // Batch a cookie access time update.
45 void UpdateCookieAccessTime(const net::CookieMonster::CanonicalCookie& cc);
46
47 // Batch a cookie deletion.
48 void DeleteCookie(const net::CookieMonster::CanonicalCookie& cc);
49
50 // Commit any pending operations and close the database. This must be called
51 // before the object is destructed.
52 void Close();
53
54 private:
55 class PendingOperation {
56 public:
57 typedef enum {
58 COOKIE_ADD,
59 COOKIE_UPDATEACCESS,
60 COOKIE_DELETE,
61 } OperationType;
62
63 PendingOperation(OperationType op,
64 const std::string& key,
65 const net::CookieMonster::CanonicalCookie& cc)
66 : op_(op), key_(key), cc_(cc) { }
67
68 OperationType op() const { return op_; }
69 const std::string& key() const { return key_; }
70 const net::CookieMonster::CanonicalCookie& cc() const { return cc_; }
71
72 private:
73 OperationType op_;
74 std::string key_; // Only used for OP_ADD
75 net::CookieMonster::CanonicalCookie cc_;
76 };
77
78 private:
79 // Batch a cookie operation (add or delete)
80 void BatchOperation(PendingOperation::OperationType op,
81 const std::string& key,
82 const net::CookieMonster::CanonicalCookie& cc);
83 // Commit our pending operations to the database.
84 void Commit();
85 // Close() executed on the background thread.
86 void InternalBackgroundClose();
87
88 sqlite3* db_;
89 MessageLoop* background_loop_;
90 SqliteStatementCache* cache_;
91
92 typedef std::list<PendingOperation*> PendingOperationsList;
93 PendingOperationsList pending_;
94 PendingOperationsList::size_type num_pending_;
95 Lock pending_lock_; // Guard pending_ and num_pending_
96
97 DISALLOW_COPY_AND_ASSIGN(Backend);
98 };
99
100 void SQLitePersistentCookieStore::Backend::AddCookie(
101 const std::string& key,
102 const net::CookieMonster::CanonicalCookie& cc) {
103 BatchOperation(PendingOperation::COOKIE_ADD, key, cc);
104 }
105
106 void SQLitePersistentCookieStore::Backend::UpdateCookieAccessTime(
107 const net::CookieMonster::CanonicalCookie& cc) {
108 BatchOperation(PendingOperation::COOKIE_UPDATEACCESS, std::string(), cc);
109 }
110
111 void SQLitePersistentCookieStore::Backend::DeleteCookie(
112 const net::CookieMonster::CanonicalCookie& cc) {
113 BatchOperation(PendingOperation::COOKIE_DELETE, std::string(), cc);
114 }
115
116 void SQLitePersistentCookieStore::Backend::BatchOperation(
117 PendingOperation::OperationType op,
118 const std::string& key,
119 const net::CookieMonster::CanonicalCookie& cc) {
120 // Commit every 30 seconds.
121 static const int kCommitIntervalMs = 30 * 1000;
122 // Commit right away if we have more than 512 outstanding operations.
123 static const size_t kCommitAfterBatchSize = 512;
124 DCHECK(MessageLoop::current() != background_loop_);
125
126 // We do a full copy of the cookie here, and hopefully just here.
127 scoped_ptr<PendingOperation> po(new PendingOperation(op, key, cc));
128 CHECK(po.get());
129
130 PendingOperationsList::size_type num_pending;
131 {
132 AutoLock locked(pending_lock_);
133 pending_.push_back(po.release());
134 num_pending = ++num_pending_;
135 }
136
137 // TODO(abarth): What if the DB thread is being destroyed on the UI thread?
138 if (num_pending == 1) {
139 // We've gotten our first entry for this batch, fire off the timer.
140 background_loop_->PostDelayedTask(FROM_HERE,
141 NewRunnableMethod(this, &Backend::Commit), kCommitIntervalMs);
142 } else if (num_pending == kCommitAfterBatchSize) {
143 // We've reached a big enough batch, fire off a commit now.
144 background_loop_->PostTask(FROM_HERE,
145 NewRunnableMethod(this, &Backend::Commit));
146 }
147 }
148
149 void SQLitePersistentCookieStore::Backend::Commit() {
150 DCHECK(MessageLoop::current() == background_loop_);
151 PendingOperationsList ops;
152 {
153 AutoLock locked(pending_lock_);
154 pending_.swap(ops);
155 num_pending_ = 0;
156 }
157
158 // Maybe an old timer fired or we are already Close()'ed.
159 if (!db_ || ops.empty())
160 return;
161
162 SQLITE_UNIQUE_STATEMENT(add_smt, *cache_,
163 "INSERT INTO cookies (creation_utc, host_key, name, value, path, "
164 "expires_utc, secure, httponly, last_access_utc) "
165 "VALUES (?,?,?,?,?,?,?,?,?)");
166 if (!add_smt.is_valid()) {
167 NOTREACHED();
168 return;
169 }
170
171 SQLITE_UNIQUE_STATEMENT(update_access_smt, *cache_,
172 "UPDATE cookies SET last_access_utc=? "
173 "WHERE creation_utc=?");
174 if (!update_access_smt.is_valid()) {
175 NOTREACHED();
176 return;
177 }
178
179 SQLITE_UNIQUE_STATEMENT(del_smt, *cache_,
180 "DELETE FROM cookies WHERE creation_utc=?");
181 if (!del_smt.is_valid()) {
182 NOTREACHED();
183 return;
184 }
185
186 SQLTransaction transaction(db_);
187 transaction.Begin();
188 for (PendingOperationsList::iterator it = ops.begin();
189 it != ops.end(); ++it) {
190 // Free the cookies as we commit them to the database.
191 scoped_ptr<PendingOperation> po(*it);
192 switch (po->op()) {
193 case PendingOperation::COOKIE_ADD:
194 add_smt->reset();
195 add_smt->bind_int64(0, po->cc().CreationDate().ToInternalValue());
196 add_smt->bind_string(1, po->key());
197 add_smt->bind_string(2, po->cc().Name());
198 add_smt->bind_string(3, po->cc().Value());
199 add_smt->bind_string(4, po->cc().Path());
200 add_smt->bind_int64(5, po->cc().ExpiryDate().ToInternalValue());
201 add_smt->bind_int(6, po->cc().IsSecure());
202 add_smt->bind_int(7, po->cc().IsHttpOnly());
203 add_smt->bind_int64(8, po->cc().LastAccessDate().ToInternalValue());
204 if (add_smt->step() != SQLITE_DONE) {
205 NOTREACHED() << "Could not add a cookie to the DB.";
206 }
207 break;
208
209 case PendingOperation::COOKIE_UPDATEACCESS:
210 update_access_smt->reset();
211 update_access_smt->bind_int64(0,
212 po->cc().LastAccessDate().ToInternalValue());
213 update_access_smt->bind_int64(1,
214 po->cc().CreationDate().ToInternalValue());
215 if (update_access_smt->step() != SQLITE_DONE) {
216 NOTREACHED() << "Could not update cookie last access time in the DB.";
217 }
218 break;
219
220 case PendingOperation::COOKIE_DELETE:
221 del_smt->reset();
222 del_smt->bind_int64(0, po->cc().CreationDate().ToInternalValue());
223 if (del_smt->step() != SQLITE_DONE) {
224 NOTREACHED() << "Could not delete a cookie from the DB.";
225 }
226 break;
227
228 default:
229 NOTREACHED();
230 break;
231 }
232 }
233 transaction.Commit();
234 }
235
236 // Fire off a close message to the background thread. We could still have a
237 // pending commit timer that will be holding a reference on us, but if/when
238 // this fires we will already have been cleaned up and it will be ignored.
239 void SQLitePersistentCookieStore::Backend::Close() {
240 DCHECK(MessageLoop::current() != background_loop_);
241 // Must close the backend on the background thread.
242 // TODO(abarth): What if the DB thread is being destroyed on the UI thread?
243 background_loop_->PostTask(FROM_HERE,
244 NewRunnableMethod(this, &Backend::InternalBackgroundClose));
245 }
246
247 void SQLitePersistentCookieStore::Backend::InternalBackgroundClose() {
248 DCHECK(MessageLoop::current() == background_loop_);
249 // Commit any pending operations
250 Commit();
251 // We must destroy the cache before closing the database.
252 delete cache_;
253 cache_ = NULL;
254 sqlite3_close(db_);
255 db_ = NULL;
256 }
257
258 SQLitePersistentCookieStore::SQLitePersistentCookieStore(
259 const std::wstring& path, MessageLoop* background_loop)
260 : path_(path),
261 background_loop_(background_loop) {
262 DCHECK(background_loop) << "SQLitePersistentCookieStore needs a MessageLoop";
263 }
264
265 SQLitePersistentCookieStore::~SQLitePersistentCookieStore() {
266 if (backend_.get()) {
267 backend_->Close();
268 // Release our reference, it will probably still have a reference if the
269 // background thread has not run Close() yet.
270 backend_ = NULL;
271 }
272 }
273
274 // Version number of the database. In version 4, we migrated the time epoch.
275 // If you open the DB with an older version on Mac or Linux, the times will
276 // look wonky, but the file will likely be usable. On Windows version 3 and 4
277 // are the same.
278 //
279 // Version 3 updated the database to include the last access time, so we can
280 // expire them in decreasing order of use when we've reached the maximum
281 // number of cookies.
282 static const int kCurrentVersionNumber = 4;
283 static const int kCompatibleVersionNumber = 3;
284
285 namespace {
286
287 // Initializes the cookies table, returning true on success.
288 bool InitTable(sqlite3* db) {
289 if (!DoesSqliteTableExist(db, "cookies")) {
290 if (sqlite3_exec(db, "CREATE TABLE cookies ("
291 "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY,"
292 "host_key TEXT NOT NULL,"
293 "name TEXT NOT NULL,"
294 "value TEXT NOT NULL,"
295 "path TEXT NOT NULL,"
296 // We only store persistent, so we know it expires
297 "expires_utc INTEGER NOT NULL,"
298 "secure INTEGER NOT NULL,"
299 "httponly INTEGER NOT NULL,"
300 "last_access_utc INTEGER NOT NULL)",
301 NULL, NULL, NULL) != SQLITE_OK)
302 return false;
303 }
304
305 // Try to create the index every time. Older versions did not have this index,
306 // so we want those people to get it. Ignore errors, since it may exist.
307 sqlite3_exec(db, "CREATE INDEX cookie_times ON cookies (creation_utc)",
308 NULL, NULL, NULL);
309
310 return true;
311 }
312
313 } // namespace
314
315 bool SQLitePersistentCookieStore::Load(
316 std::vector<net::CookieMonster::KeyedCanonicalCookie>* cookies) {
317 DCHECK(!path_.empty());
318 sqlite3* db;
319 if (sqlite3_open(WideToUTF8(path_).c_str(), &db) != SQLITE_OK) {
320 NOTREACHED() << "Unable to open cookie DB.";
321 return false;
322 }
323
324 if (!EnsureDatabaseVersion(db) || !InitTable(db)) {
325 NOTREACHED() << "Unable to initialize cookie DB.";
326 sqlite3_close(db);
327 return false;
328 }
329
330 MetaTableHelper::PrimeCache(std::string(), db);
331
332 // Slurp all the cookies into the out-vector.
333 SQLStatement smt;
334 if (smt.prepare(db,
335 "SELECT creation_utc, host_key, name, value, path, expires_utc, secure, "
336 "httponly, last_access_utc FROM cookies") != SQLITE_OK) {
337 NOTREACHED() << "select statement prep failed";
338 sqlite3_close(db);
339 return false;
340 }
341
342 while (smt.step() == SQLITE_ROW) {
343 std::string key = smt.column_string(1);
344 scoped_ptr<net::CookieMonster::CanonicalCookie> cc(
345 new net::CookieMonster::CanonicalCookie(
346 smt.column_string(2), // name
347 smt.column_string(3), // value
348 smt.column_string(4), // path
349 smt.column_int(6) != 0, // secure
350 smt.column_int(7) != 0, // httponly
351 Time::FromInternalValue(smt.column_int64(0)), // creation_utc
352 Time::FromInternalValue(smt.column_int64(8)), // last_access_utc
353 true, // has_expires
354 Time::FromInternalValue(smt.column_int64(5)))); // expires_utc
355 // Memory allocation failed.
356 if (!cc.get())
357 break;
358
359 DLOG_IF(WARNING,
360 cc->CreationDate() > Time::Now()) << L"CreationDate too recent";
361 cookies->push_back(
362 net::CookieMonster::KeyedCanonicalCookie(smt.column_string(1),
363 cc.release()));
364 }
365
366 // Create the backend, this will take ownership of the db pointer.
367 backend_ = new Backend(db, background_loop_);
368
369 return true;
370 }
371
372 bool SQLitePersistentCookieStore::EnsureDatabaseVersion(sqlite3* db) {
373 // Version check.
374 if (!meta_table_.Init(std::string(), kCurrentVersionNumber,
375 kCompatibleVersionNumber, db))
376 return false;
377
378 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
379 LOG(WARNING) << "Cookie database is too new.";
380 return false;
381 }
382
383 int cur_version = meta_table_.GetVersionNumber();
384 if (cur_version == 2) {
385 SQLTransaction transaction(db);
386 transaction.Begin();
387 if ((sqlite3_exec(db,
388 "ALTER TABLE cookies ADD COLUMN last_access_utc "
389 "INTEGER DEFAULT 0", NULL, NULL, NULL) != SQLITE_OK) ||
390 (sqlite3_exec(db,
391 "UPDATE cookies SET last_access_utc = creation_utc",
392 NULL, NULL, NULL) != SQLITE_OK)) {
393 LOG(WARNING) << "Unable to update cookie database to version 3.";
394 return false;
395 }
396 ++cur_version;
397 meta_table_.SetVersionNumber(cur_version);
398 meta_table_.SetCompatibleVersionNumber(
399 std::min(cur_version, kCompatibleVersionNumber));
400 transaction.Commit();
401 }
402
403 if (cur_version == 3) {
404 // The time epoch changed for Mac & Linux in this version to match Windows.
405 // This patch came after the main epoch change happened, so some
406 // developers have "good" times for cookies added by the more recent
407 // versions. So we have to be careful to only update times that are under
408 // the old system (which will appear to be from before 1970 in the new
409 // system). The magic number used below is 1970 in our time units.
410 SQLTransaction transaction(db);
411 transaction.Begin();
412 #if !defined(OS_WIN)
413 sqlite3_exec(db,
414 "UPDATE cookies "
415 "SET creation_utc = creation_utc + 11644473600000000 "
416 "WHERE rowid IN "
417 "(SELECT rowid FROM cookies WHERE "
418 "creation_utc > 0 AND creation_utc < 11644473600000000)",
419 NULL, NULL, NULL);
420 sqlite3_exec(db,
421 "UPDATE cookies "
422 "SET expires_utc = expires_utc + 11644473600000000 "
423 "WHERE rowid IN "
424 "(SELECT rowid FROM cookies WHERE "
425 "expires_utc > 0 AND expires_utc < 11644473600000000)",
426 NULL, NULL, NULL);
427 sqlite3_exec(db,
428 "UPDATE cookies "
429 "SET last_access_utc = last_access_utc + 11644473600000000 "
430 "WHERE rowid IN "
431 "(SELECT rowid FROM cookies WHERE "
432 "last_access_utc > 0 AND last_access_utc < 11644473600000000)",
433 NULL, NULL, NULL);
434 #endif
435 ++cur_version;
436 meta_table_.SetVersionNumber(cur_version);
437 transaction.Commit();
438 }
439
440 // Put future migration cases here.
441
442 // When the version is too old, we just try to continue anyway, there should
443 // not be a released product that makes a database too old for us to handle.
444 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
445 "Cookie database version " << cur_version << " is too old to handle.";
446
447 return true;
448 }
449
450 void SQLitePersistentCookieStore::AddCookie(
451 const std::string& key,
452 const net::CookieMonster::CanonicalCookie& cc) {
453 if (backend_.get())
454 backend_->AddCookie(key, cc);
455 }
456
457 void SQLitePersistentCookieStore::UpdateCookieAccessTime(
458 const net::CookieMonster::CanonicalCookie& cc) {
459 if (backend_.get())
460 backend_->UpdateCookieAccessTime(cc);
461 }
462
463 void SQLitePersistentCookieStore::DeleteCookie(
464 const net::CookieMonster::CanonicalCookie& cc) {
465 if (backend_.get())
466 backend_->DeleteCookie(cc);
467 }
OLDNEW
« no previous file with comments | « chrome/common/net/cookie_monster_sqlite.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698