IndexedDBFactory now ForceCloses databases.
[chromium-blink-merge.git] / content / browser / media / webrtc_identity_store_backend.cc
blobd599dcda742d08d3ffb80598acabdf432bb5a3d0
1 // Copyright 2013 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.
5 #include "content/browser/media/webrtc_identity_store_backend.h"
7 #include "base/file_util.h"
8 #include "base/files/file_path.h"
9 #include "content/public/browser/browser_thread.h"
10 #include "net/base/net_errors.h"
11 #include "sql/error_delegate_util.h"
12 #include "sql/statement.h"
13 #include "sql/transaction.h"
14 #include "url/gurl.h"
15 #include "webkit/browser/quota/special_storage_policy.h"
17 namespace content {
19 static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store";
21 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
22 FILE_PATH_LITERAL("WebRTCIdentityStore");
24 // Initializes the identity table, returning true on success.
25 static bool InitDB(sql::Connection* db) {
26 if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
27 if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
28 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
29 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
30 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
31 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
32 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
33 return true;
34 if (!db->Execute("DROP TABLE webrtc_identity_store"))
35 return false;
38 return db->Execute(
39 "CREATE TABLE webrtc_identity_store"
40 " ("
41 "origin TEXT NOT NULL,"
42 "identity_name TEXT NOT NULL,"
43 "common_name TEXT NOT NULL,"
44 "certificate BLOB NOT NULL,"
45 "private_key BLOB NOT NULL,"
46 "creation_time INTEGER)");
49 struct WebRTCIdentityStoreBackend::IdentityKey {
50 IdentityKey(const GURL& origin, const std::string& identity_name)
51 : origin(origin), identity_name(identity_name) {}
53 bool operator<(const IdentityKey& other) const {
54 return origin < other.origin ||
55 (origin == other.origin && identity_name < other.identity_name);
58 GURL origin;
59 std::string identity_name;
62 struct WebRTCIdentityStoreBackend::Identity {
63 Identity(const std::string& common_name,
64 const std::string& certificate,
65 const std::string& private_key)
66 : common_name(common_name),
67 certificate(certificate),
68 private_key(private_key),
69 creation_time(base::Time::Now().ToInternalValue()) {}
71 Identity(const std::string& common_name,
72 const std::string& certificate,
73 const std::string& private_key,
74 int64 creation_time)
75 : common_name(common_name),
76 certificate(certificate),
77 private_key(private_key),
78 creation_time(creation_time) {}
80 std::string common_name;
81 std::string certificate;
82 std::string private_key;
83 int64 creation_time;
86 struct WebRTCIdentityStoreBackend::PendingFindRequest {
87 PendingFindRequest(const GURL& origin,
88 const std::string& identity_name,
89 const std::string& common_name,
90 const FindIdentityCallback& callback)
91 : origin(origin),
92 identity_name(identity_name),
93 common_name(common_name),
94 callback(callback) {}
96 ~PendingFindRequest() {}
98 GURL origin;
99 std::string identity_name;
100 std::string common_name;
101 FindIdentityCallback callback;
104 // The class encapsulates the database operations. All members except ctor and
105 // dtor should be accessed on the DB thread.
106 // It can be created/destroyed on any thread.
107 class WebRTCIdentityStoreBackend::SqlLiteStorage
108 : public base::RefCountedThreadSafe<SqlLiteStorage> {
109 public:
110 SqlLiteStorage(base::TimeDelta validity_period,
111 const base::FilePath& path,
112 quota::SpecialStoragePolicy* policy)
113 : validity_period_(validity_period), special_storage_policy_(policy) {
114 if (!path.empty())
115 path_ = path.Append(kWebRTCIdentityStoreDirectory);
118 void Load(IdentityMap* out_map);
119 void Close();
120 void AddIdentity(const GURL& origin,
121 const std::string& identity_name,
122 const Identity& identity);
123 void DeleteIdentity(const GURL& origin,
124 const std::string& identity_name,
125 const Identity& identity);
126 void DeleteBetween(base::Time delete_begin, base::Time delete_end);
128 void SetValidityPeriodForTesting(base::TimeDelta validity_period) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
130 DCHECK(!db_.get());
131 validity_period_ = validity_period;
134 private:
135 friend class base::RefCountedThreadSafe<SqlLiteStorage>;
137 enum OperationType {
138 ADD_IDENTITY,
139 DELETE_IDENTITY
141 struct PendingOperation {
142 PendingOperation(OperationType type,
143 const GURL& origin,
144 const std::string& identity_name,
145 const Identity& identity)
146 : type(type),
147 origin(origin),
148 identity_name(identity_name),
149 identity(identity) {}
151 OperationType type;
152 GURL origin;
153 std::string identity_name;
154 Identity identity;
156 typedef std::vector<PendingOperation*> PendingOperationList;
158 virtual ~SqlLiteStorage() {}
159 void OnDatabaseError(int error, sql::Statement* stmt);
160 void BatchOperation(OperationType type,
161 const GURL& origin,
162 const std::string& identity_name,
163 const Identity& identity);
164 void Commit();
166 base::TimeDelta validity_period_;
167 // The file path of the DB. Empty if temporary.
168 base::FilePath path_;
169 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
170 scoped_ptr<sql::Connection> db_;
171 // Batched DB operations pending to commit.
172 PendingOperationList pending_operations_;
174 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
177 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
178 const base::FilePath& path,
179 quota::SpecialStoragePolicy* policy,
180 base::TimeDelta validity_period)
181 : validity_period_(validity_period),
182 state_(NOT_STARTED),
183 sql_lite_storage_(new SqlLiteStorage(validity_period, path, policy)) {}
185 bool WebRTCIdentityStoreBackend::FindIdentity(
186 const GURL& origin,
187 const std::string& identity_name,
188 const std::string& common_name,
189 const FindIdentityCallback& callback) {
190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
191 if (state_ == CLOSED)
192 return false;
194 if (state_ != LOADED) {
195 // Queues the request to wait for the DB to load.
196 pending_find_requests_.push_back(
197 new PendingFindRequest(origin, identity_name, common_name, callback));
198 if (state_ == LOADING)
199 return true;
201 DCHECK_EQ(state_, NOT_STARTED);
203 // Kick off loading the DB.
204 scoped_ptr<IdentityMap> out_map(new IdentityMap());
205 base::Closure task(
206 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
207 // |out_map| will be NULL after this call.
208 if (BrowserThread::PostTaskAndReply(
209 BrowserThread::DB,
210 FROM_HERE,
211 task,
212 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
213 this,
214 base::Passed(&out_map)))) {
215 state_ = LOADING;
216 return true;
218 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
221 IdentityKey key(origin, identity_name);
222 IdentityMap::iterator iter = identities_.find(key);
223 if (iter != identities_.end() && iter->second.common_name == common_name) {
224 base::TimeDelta age = base::Time::Now() - base::Time::FromInternalValue(
225 iter->second.creation_time);
226 if (age < validity_period_) {
227 // Identity found.
228 return BrowserThread::PostTask(BrowserThread::IO,
229 FROM_HERE,
230 base::Bind(callback,
231 net::OK,
232 iter->second.certificate,
233 iter->second.private_key));
235 // Removes the expired identity from the in-memory cache. The copy in the
236 // database will be removed on the next load.
237 identities_.erase(iter);
240 return BrowserThread::PostTask(
241 BrowserThread::IO,
242 FROM_HERE,
243 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
246 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
247 const std::string& identity_name,
248 const std::string& common_name,
249 const std::string& certificate,
250 const std::string& private_key) {
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
252 if (state_ == CLOSED)
253 return;
255 // If there is an existing identity for the same origin and identity_name,
256 // delete it.
257 IdentityKey key(origin, identity_name);
258 Identity identity(common_name, certificate, private_key);
260 if (identities_.find(key) != identities_.end()) {
261 if (!BrowserThread::PostTask(BrowserThread::DB,
262 FROM_HERE,
263 base::Bind(&SqlLiteStorage::DeleteIdentity,
264 sql_lite_storage_,
265 origin,
266 identity_name,
267 identities_.find(key)->second)))
268 return;
270 identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
272 BrowserThread::PostTask(BrowserThread::DB,
273 FROM_HERE,
274 base::Bind(&SqlLiteStorage::AddIdentity,
275 sql_lite_storage_,
276 origin,
277 identity_name,
278 identity));
281 void WebRTCIdentityStoreBackend::Close() {
282 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
283 BrowserThread::PostTask(
284 BrowserThread::IO,
285 FROM_HERE,
286 base::Bind(&WebRTCIdentityStoreBackend::Close, this));
287 return;
290 if (state_ == CLOSED)
291 return;
293 state_ = CLOSED;
294 BrowserThread::PostTask(
295 BrowserThread::DB,
296 FROM_HERE,
297 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
300 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
301 base::Time delete_end,
302 const base::Closure& callback) {
303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
304 if (state_ == CLOSED)
305 return;
307 // Delete the in-memory cache.
308 IdentityMap::iterator it = identities_.begin();
309 while (it != identities_.end()) {
310 if (it->second.creation_time >= delete_begin.ToInternalValue() &&
311 it->second.creation_time <= delete_end.ToInternalValue()) {
312 identities_.erase(it++);
313 } else {
314 ++it;
317 BrowserThread::PostTaskAndReply(BrowserThread::DB,
318 FROM_HERE,
319 base::Bind(&SqlLiteStorage::DeleteBetween,
320 sql_lite_storage_,
321 delete_begin,
322 delete_end),
323 callback);
326 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
327 base::TimeDelta validity_period) {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
329 validity_period_ = validity_period;
330 BrowserThread::PostTask(
331 BrowserThread::DB,
332 FROM_HERE,
333 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting,
334 sql_lite_storage_,
335 validity_period));
338 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
340 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
343 if (state_ != LOADING)
344 return;
346 DVLOG(2) << "WebRTC identity store has loaded.";
348 state_ = LOADED;
349 identities_.swap(*out_map);
351 for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
352 FindIdentity(pending_find_requests_[i]->origin,
353 pending_find_requests_[i]->identity_name,
354 pending_find_requests_[i]->common_name,
355 pending_find_requests_[i]->callback);
356 delete pending_find_requests_[i];
358 pending_find_requests_.clear();
362 // Implementation of SqlLiteStorage.
365 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
366 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
367 DCHECK(!db_.get());
369 // Ensure the parent directory for storing certs is created before reading
370 // from it.
371 const base::FilePath dir = path_.DirName();
372 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) {
373 DLOG(ERROR) << "Unable to open DB file path.";
374 return;
377 db_.reset(new sql::Connection());
379 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
381 if (!db_->Open(path_)) {
382 DLOG(ERROR) << "Unable to open DB.";
383 db_.reset();
384 return;
387 if (!InitDB(db_.get())) {
388 DLOG(ERROR) << "Unable to init DB.";
389 db_.reset();
390 return;
393 db_->Preload();
395 // Delete expired identities.
396 DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
398 // Slurp all the identities into the out_map.
399 sql::Statement stmt(db_->GetUniqueStatement(
400 "SELECT origin, identity_name, common_name, "
401 "certificate, private_key, creation_time "
402 "FROM webrtc_identity_store"));
403 CHECK(stmt.is_valid());
405 while (stmt.Step()) {
406 IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
407 std::string common_name(stmt.ColumnString(2));
408 std::string cert, private_key;
409 stmt.ColumnBlobAsString(3, &cert);
410 stmt.ColumnBlobAsString(4, &private_key);
411 int64 creation_time = stmt.ColumnInt64(5);
412 std::pair<IdentityMap::iterator, bool> result =
413 out_map->insert(std::pair<IdentityKey, Identity>(
414 key, Identity(common_name, cert, private_key, creation_time)));
415 DCHECK(result.second);
419 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
421 Commit();
422 db_.reset();
425 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
426 const GURL& origin,
427 const std::string& identity_name,
428 const Identity& identity) {
429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
430 if (!db_.get())
431 return;
433 // Do not add for session only origins.
434 if (special_storage_policy_.get() &&
435 !special_storage_policy_->IsStorageProtected(origin) &&
436 special_storage_policy_->IsStorageSessionOnly(origin)) {
437 return;
439 BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
442 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
443 const GURL& origin,
444 const std::string& identity_name,
445 const Identity& identity) {
446 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
447 if (!db_.get())
448 return;
449 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
452 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
453 base::Time delete_begin,
454 base::Time delete_end) {
455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
456 if (!db_.get())
457 return;
459 // Commit pending operations first.
460 Commit();
462 sql::Statement del_stmt(db_->GetCachedStatement(
463 SQL_FROM_HERE,
464 "DELETE FROM webrtc_identity_store"
465 " WHERE creation_time >= ? AND creation_time <= ?"));
466 CHECK(del_stmt.is_valid());
468 del_stmt.BindInt64(0, delete_begin.ToInternalValue());
469 del_stmt.BindInt64(1, delete_end.ToInternalValue());
471 sql::Transaction transaction(db_.get());
472 if (!transaction.Begin()) {
473 DLOG(ERROR) << "Failed to begin the transaction.";
474 return;
477 CHECK(del_stmt.Run());
478 transaction.Commit();
481 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
482 int error,
483 sql::Statement* stmt) {
484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
485 if (!sql::IsErrorCatastrophic(error))
486 return;
487 db_->RazeAndClose();
490 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
491 OperationType type,
492 const GURL& origin,
493 const std::string& identity_name,
494 const Identity& identity) {
495 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
496 // Commit every 30 seconds.
497 static const base::TimeDelta kCommitInterval(
498 base::TimeDelta::FromSeconds(30));
499 // Commit right away if we have more than 512 outstanding operations.
500 static const size_t kCommitAfterBatchSize = 512;
502 // We do a full copy of the cert here, and hopefully just here.
503 scoped_ptr<PendingOperation> operation(
504 new PendingOperation(type, origin, identity_name, identity));
506 pending_operations_.push_back(operation.release());
508 if (pending_operations_.size() == 1) {
509 // We've gotten our first entry for this batch, fire off the timer.
510 BrowserThread::PostDelayedTask(BrowserThread::DB,
511 FROM_HERE,
512 base::Bind(&SqlLiteStorage::Commit, this),
513 kCommitInterval);
514 } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
515 // We've reached a big enough batch, fire off a commit now.
516 BrowserThread::PostTask(BrowserThread::DB,
517 FROM_HERE,
518 base::Bind(&SqlLiteStorage::Commit, this));
522 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
524 // Maybe an old timer fired or we are already Close()'ed.
525 if (!db_.get() || pending_operations_.empty())
526 return;
528 sql::Statement add_stmt(db_->GetCachedStatement(
529 SQL_FROM_HERE,
530 "INSERT INTO webrtc_identity_store "
531 "(origin, identity_name, common_name, certificate,"
532 " private_key, creation_time) VALUES"
533 " (?,?,?,?,?,?)"));
535 CHECK(add_stmt.is_valid());
537 sql::Statement del_stmt(db_->GetCachedStatement(
538 SQL_FROM_HERE,
539 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
541 CHECK(del_stmt.is_valid());
543 sql::Transaction transaction(db_.get());
544 if (!transaction.Begin()) {
545 DLOG(ERROR) << "Failed to begin the transaction.";
546 return;
549 for (PendingOperationList::iterator it = pending_operations_.begin();
550 it != pending_operations_.end();
551 ++it) {
552 scoped_ptr<PendingOperation> po(*it);
553 switch (po->type) {
554 case ADD_IDENTITY: {
555 add_stmt.Reset(true);
556 add_stmt.BindString(0, po->origin.spec());
557 add_stmt.BindString(1, po->identity_name);
558 add_stmt.BindString(2, po->identity.common_name);
559 const std::string& cert = po->identity.certificate;
560 add_stmt.BindBlob(3, cert.data(), cert.size());
561 const std::string& private_key = po->identity.private_key;
562 add_stmt.BindBlob(4, private_key.data(), private_key.size());
563 add_stmt.BindInt64(5, po->identity.creation_time);
564 CHECK(add_stmt.Run());
565 break;
567 case DELETE_IDENTITY:
568 del_stmt.Reset(true);
569 del_stmt.BindString(0, po->origin.spec());
570 del_stmt.BindString(1, po->identity_name);
571 CHECK(del_stmt.Run());
572 break;
574 default:
575 NOTREACHED();
576 break;
579 transaction.Commit();
580 pending_operations_.clear();
583 } // namespace content