Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / media / webrtc_identity_store_backend.cc
blob59ee8167e0738a3242f2a1a6f629ae8b1b389ff9
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/files/file_path.h"
8 #include "base/files/file_util.h"
9 #include "base/memory/scoped_vector.h"
10 #include "base/strings/string_util.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "net/base/net_errors.h"
13 #include "sql/statement.h"
14 #include "sql/transaction.h"
15 #include "storage/browser/quota/special_storage_policy.h"
16 #include "url/gurl.h"
18 namespace content {
20 static const char kWebRTCIdentityStoreDBName[] = "webrtc_identity_store";
22 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
23 FILE_PATH_LITERAL("WebRTCIdentityStore");
25 // Initializes the identity table, returning true on success.
26 static bool InitDB(sql::Connection* db) {
27 if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
28 if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
29 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
30 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
31 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
32 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
33 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
34 return true;
36 if (!db->Execute("DROP TABLE webrtc_identity_store"))
37 return false;
40 return db->Execute(
41 "CREATE TABLE webrtc_identity_store"
42 " ("
43 "origin TEXT NOT NULL,"
44 "identity_name TEXT NOT NULL,"
45 "common_name TEXT NOT NULL,"
46 "certificate BLOB NOT NULL,"
47 "private_key BLOB NOT NULL,"
48 "creation_time INTEGER)");
51 struct WebRTCIdentityStoreBackend::IdentityKey {
52 IdentityKey(const GURL& origin, const std::string& identity_name)
53 : origin(origin), identity_name(identity_name) {}
55 bool operator<(const IdentityKey& other) const {
56 return origin < other.origin ||
57 (origin == other.origin && identity_name < other.identity_name);
60 GURL origin;
61 std::string identity_name;
64 struct WebRTCIdentityStoreBackend::Identity {
65 Identity(const std::string& common_name,
66 const std::string& certificate,
67 const std::string& private_key)
68 : common_name(common_name),
69 certificate(certificate),
70 private_key(private_key),
71 creation_time(base::Time::Now().ToInternalValue()) {}
73 Identity(const std::string& common_name,
74 const std::string& certificate,
75 const std::string& private_key,
76 int64 creation_time)
77 : common_name(common_name),
78 certificate(certificate),
79 private_key(private_key),
80 creation_time(creation_time) {}
82 std::string common_name;
83 std::string certificate;
84 std::string private_key;
85 int64 creation_time;
88 struct WebRTCIdentityStoreBackend::PendingFindRequest {
89 PendingFindRequest(const GURL& origin,
90 const std::string& identity_name,
91 const std::string& common_name,
92 const FindIdentityCallback& callback)
93 : origin(origin),
94 identity_name(identity_name),
95 common_name(common_name),
96 callback(callback) {}
98 ~PendingFindRequest() {}
100 GURL origin;
101 std::string identity_name;
102 std::string common_name;
103 FindIdentityCallback callback;
106 // The class encapsulates the database operations. All members except ctor and
107 // dtor should be accessed on the DB thread.
108 // It can be created/destroyed on any thread.
109 class WebRTCIdentityStoreBackend::SqlLiteStorage
110 : public base::RefCountedThreadSafe<SqlLiteStorage> {
111 public:
112 SqlLiteStorage(base::TimeDelta validity_period,
113 const base::FilePath& path,
114 storage::SpecialStoragePolicy* policy)
115 : validity_period_(validity_period), special_storage_policy_(policy) {
116 if (!path.empty())
117 path_ = path.Append(kWebRTCIdentityStoreDirectory);
120 void Load(IdentityMap* out_map);
121 void Close();
122 void AddIdentity(const GURL& origin,
123 const std::string& identity_name,
124 const Identity& identity);
125 void DeleteIdentity(const GURL& origin,
126 const std::string& identity_name,
127 const Identity& identity);
128 void DeleteBetween(base::Time delete_begin, base::Time delete_end);
130 void SetValidityPeriodForTesting(base::TimeDelta validity_period) {
131 DCHECK_CURRENTLY_ON(BrowserThread::DB);
132 DCHECK(!db_.get());
133 validity_period_ = validity_period;
136 private:
137 friend class base::RefCountedThreadSafe<SqlLiteStorage>;
139 enum OperationType {
140 ADD_IDENTITY,
141 DELETE_IDENTITY
143 struct PendingOperation {
144 PendingOperation(OperationType type,
145 const GURL& origin,
146 const std::string& identity_name,
147 const Identity& identity)
148 : type(type),
149 origin(origin),
150 identity_name(identity_name),
151 identity(identity) {}
153 OperationType type;
154 GURL origin;
155 std::string identity_name;
156 Identity identity;
158 typedef ScopedVector<PendingOperation> PendingOperationList;
160 virtual ~SqlLiteStorage() {}
161 void OnDatabaseError(int error, sql::Statement* stmt);
162 void BatchOperation(OperationType type,
163 const GURL& origin,
164 const std::string& identity_name,
165 const Identity& identity);
166 void Commit();
168 base::TimeDelta validity_period_;
169 // The file path of the DB. Empty if temporary.
170 base::FilePath path_;
171 scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
172 scoped_ptr<sql::Connection> db_;
173 // Batched DB operations pending to commit.
174 PendingOperationList pending_operations_;
176 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
179 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
180 const base::FilePath& path,
181 storage::SpecialStoragePolicy* policy,
182 base::TimeDelta validity_period)
183 : validity_period_(validity_period),
184 state_(NOT_STARTED),
185 sql_lite_storage_(new SqlLiteStorage(validity_period, path, policy)) {
188 bool WebRTCIdentityStoreBackend::FindIdentity(
189 const GURL& origin,
190 const std::string& identity_name,
191 const std::string& common_name,
192 const FindIdentityCallback& callback) {
193 DCHECK_CURRENTLY_ON(BrowserThread::IO);
194 if (state_ == CLOSED)
195 return false;
197 if (state_ != LOADED) {
198 // Queues the request to wait for the DB to load.
199 pending_find_requests_.push_back(
200 new PendingFindRequest(origin, identity_name, common_name, callback));
201 if (state_ == LOADING)
202 return true;
204 DCHECK_EQ(state_, NOT_STARTED);
206 // Kick off loading the DB.
207 scoped_ptr<IdentityMap> out_map(new IdentityMap());
208 base::Closure task(
209 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
210 // |out_map| will be NULL after this call.
211 if (BrowserThread::PostTaskAndReply(
212 BrowserThread::DB,
213 FROM_HERE,
214 task,
215 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
216 this,
217 base::Passed(&out_map)))) {
218 state_ = LOADING;
219 return true;
221 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
224 IdentityKey key(origin, identity_name);
225 IdentityMap::iterator iter = identities_.find(key);
226 if (iter != identities_.end() && iter->second.common_name == common_name) {
227 base::TimeDelta age = base::Time::Now() - base::Time::FromInternalValue(
228 iter->second.creation_time);
229 if (age < validity_period_) {
230 // Identity found.
231 return BrowserThread::PostTask(BrowserThread::IO,
232 FROM_HERE,
233 base::Bind(callback,
234 net::OK,
235 iter->second.certificate,
236 iter->second.private_key));
238 // Removes the expired identity from the in-memory cache. The copy in the
239 // database will be removed on the next load.
240 identities_.erase(iter);
243 return BrowserThread::PostTask(
244 BrowserThread::IO,
245 FROM_HERE,
246 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
249 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
250 const std::string& identity_name,
251 const std::string& common_name,
252 const std::string& certificate,
253 const std::string& private_key) {
254 DCHECK_CURRENTLY_ON(BrowserThread::IO);
255 if (state_ == CLOSED)
256 return;
258 // If there is an existing identity for the same origin and identity_name,
259 // delete it.
260 IdentityKey key(origin, identity_name);
261 Identity identity(common_name, certificate, private_key);
263 if (identities_.find(key) != identities_.end()) {
264 if (!BrowserThread::PostTask(BrowserThread::DB,
265 FROM_HERE,
266 base::Bind(&SqlLiteStorage::DeleteIdentity,
267 sql_lite_storage_,
268 origin,
269 identity_name,
270 identities_.find(key)->second)))
271 return;
273 identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
275 BrowserThread::PostTask(BrowserThread::DB,
276 FROM_HERE,
277 base::Bind(&SqlLiteStorage::AddIdentity,
278 sql_lite_storage_,
279 origin,
280 identity_name,
281 identity));
284 void WebRTCIdentityStoreBackend::Close() {
285 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
286 BrowserThread::PostTask(
287 BrowserThread::IO,
288 FROM_HERE,
289 base::Bind(&WebRTCIdentityStoreBackend::Close, this));
290 return;
293 if (state_ == CLOSED)
294 return;
296 state_ = CLOSED;
297 BrowserThread::PostTask(
298 BrowserThread::DB,
299 FROM_HERE,
300 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
303 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
304 base::Time delete_end,
305 const base::Closure& callback) {
306 DCHECK_CURRENTLY_ON(BrowserThread::IO);
307 if (state_ == CLOSED)
308 return;
310 // Delete the in-memory cache.
311 IdentityMap::iterator it = identities_.begin();
312 while (it != identities_.end()) {
313 if (it->second.creation_time >= delete_begin.ToInternalValue() &&
314 it->second.creation_time <= delete_end.ToInternalValue()) {
315 identities_.erase(it++);
316 } else {
317 ++it;
320 BrowserThread::PostTaskAndReply(BrowserThread::DB,
321 FROM_HERE,
322 base::Bind(&SqlLiteStorage::DeleteBetween,
323 sql_lite_storage_,
324 delete_begin,
325 delete_end),
326 callback);
329 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
330 base::TimeDelta validity_period) {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO);
332 validity_period_ = validity_period;
333 BrowserThread::PostTask(
334 BrowserThread::DB,
335 FROM_HERE,
336 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting,
337 sql_lite_storage_,
338 validity_period));
341 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
343 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
344 DCHECK_CURRENTLY_ON(BrowserThread::IO);
346 if (state_ != LOADING)
347 return;
349 DVLOG(3) << "WebRTC identity store has loaded.";
351 state_ = LOADED;
352 identities_.swap(*out_map);
354 for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
355 FindIdentity(pending_find_requests_[i]->origin,
356 pending_find_requests_[i]->identity_name,
357 pending_find_requests_[i]->common_name,
358 pending_find_requests_[i]->callback);
359 delete pending_find_requests_[i];
361 pending_find_requests_.clear();
365 // Implementation of SqlLiteStorage.
368 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
369 DCHECK_CURRENTLY_ON(BrowserThread::DB);
370 DCHECK(!db_.get());
372 // Ensure the parent directory for storing certs is created before reading
373 // from it.
374 const base::FilePath dir = path_.DirName();
375 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) {
376 DVLOG(2) << "Unable to open DB file path.";
377 return;
380 db_.reset(new sql::Connection());
382 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
384 if (!db_->Open(path_)) {
385 DVLOG(2) << "Unable to open DB.";
386 db_.reset();
387 return;
390 if (!InitDB(db_.get())) {
391 DVLOG(2) << "Unable to init DB.";
392 db_.reset();
393 return;
396 db_->Preload();
398 // Delete expired identities.
399 DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
401 // Slurp all the identities into the out_map.
402 sql::Statement stmt(db_->GetUniqueStatement(
403 "SELECT origin, identity_name, common_name, "
404 "certificate, private_key, creation_time "
405 "FROM webrtc_identity_store"));
406 CHECK(stmt.is_valid());
408 while (stmt.Step()) {
409 IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
410 std::string common_name(stmt.ColumnString(2));
411 std::string cert, private_key;
412 stmt.ColumnBlobAsString(3, &cert);
413 stmt.ColumnBlobAsString(4, &private_key);
414 int64 creation_time = stmt.ColumnInt64(5);
415 std::pair<IdentityMap::iterator, bool> result =
416 out_map->insert(std::pair<IdentityKey, Identity>(
417 key, Identity(common_name, cert, private_key, creation_time)));
418 DCHECK(result.second);
422 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
423 DCHECK_CURRENTLY_ON(BrowserThread::DB);
424 Commit();
425 db_.reset();
428 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
429 const GURL& origin,
430 const std::string& identity_name,
431 const Identity& identity) {
432 DCHECK_CURRENTLY_ON(BrowserThread::DB);
433 if (!db_.get())
434 return;
436 // Do not add for session only origins.
437 if (special_storage_policy_.get() &&
438 !special_storage_policy_->IsStorageProtected(origin) &&
439 special_storage_policy_->IsStorageSessionOnly(origin)) {
440 return;
442 BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
445 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
446 const GURL& origin,
447 const std::string& identity_name,
448 const Identity& identity) {
449 DCHECK_CURRENTLY_ON(BrowserThread::DB);
450 if (!db_.get())
451 return;
452 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
455 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
456 base::Time delete_begin,
457 base::Time delete_end) {
458 DCHECK_CURRENTLY_ON(BrowserThread::DB);
459 if (!db_.get())
460 return;
462 // Commit pending operations first.
463 Commit();
465 sql::Statement del_stmt(db_->GetCachedStatement(
466 SQL_FROM_HERE,
467 "DELETE FROM webrtc_identity_store"
468 " WHERE creation_time >= ? AND creation_time <= ?"));
469 CHECK(del_stmt.is_valid());
471 del_stmt.BindInt64(0, delete_begin.ToInternalValue());
472 del_stmt.BindInt64(1, delete_end.ToInternalValue());
474 sql::Transaction transaction(db_.get());
475 if (!transaction.Begin()) {
476 DVLOG(2) << "Failed to begin the transaction.";
477 return;
480 if (!del_stmt.Run()) {
481 DVLOG(2) << "Failed to run the delete statement.";
482 return;
485 if (!transaction.Commit())
486 DVLOG(2) << "Failed to commit the transaction.";
489 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
490 int error,
491 sql::Statement* stmt) {
492 DCHECK_CURRENTLY_ON(BrowserThread::DB);
494 db_->RazeAndClose();
495 // It's not safe to reset |db_| here.
498 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
499 OperationType type,
500 const GURL& origin,
501 const std::string& identity_name,
502 const Identity& identity) {
503 DCHECK_CURRENTLY_ON(BrowserThread::DB);
504 // Commit every 30 seconds.
505 static const base::TimeDelta kCommitInterval(
506 base::TimeDelta::FromSeconds(30));
507 // Commit right away if we have more than 512 outstanding operations.
508 static const size_t kCommitAfterBatchSize = 512;
510 // We do a full copy of the cert here, and hopefully just here.
511 scoped_ptr<PendingOperation> operation(
512 new PendingOperation(type, origin, identity_name, identity));
514 pending_operations_.push_back(operation.release());
516 if (pending_operations_.size() == 1) {
517 // We've gotten our first entry for this batch, fire off the timer.
518 BrowserThread::PostDelayedTask(BrowserThread::DB,
519 FROM_HERE,
520 base::Bind(&SqlLiteStorage::Commit, this),
521 kCommitInterval);
522 } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
523 // We've reached a big enough batch, fire off a commit now.
524 BrowserThread::PostTask(BrowserThread::DB,
525 FROM_HERE,
526 base::Bind(&SqlLiteStorage::Commit, this));
530 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
531 DCHECK_CURRENTLY_ON(BrowserThread::DB);
532 // Maybe an old timer fired or we are already Close()'ed.
533 if (!db_.get() || pending_operations_.empty())
534 return;
536 sql::Statement add_stmt(db_->GetCachedStatement(
537 SQL_FROM_HERE,
538 "INSERT INTO webrtc_identity_store "
539 "(origin, identity_name, common_name, certificate,"
540 " private_key, creation_time) VALUES"
541 " (?,?,?,?,?,?)"));
543 CHECK(add_stmt.is_valid());
545 sql::Statement del_stmt(db_->GetCachedStatement(
546 SQL_FROM_HERE,
547 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
549 CHECK(del_stmt.is_valid());
551 sql::Transaction transaction(db_.get());
552 if (!transaction.Begin()) {
553 DVLOG(2) << "Failed to begin the transaction.";
554 return;
557 // Swaps |pending_operations_| into a temporary list to make sure
558 // |pending_operations_| is always cleared in case of DB errors.
559 PendingOperationList pending_operations_copy;
560 pending_operations_.swap(pending_operations_copy);
562 for (PendingOperationList::const_iterator it =
563 pending_operations_copy.begin();
564 it != pending_operations_copy.end();
565 ++it) {
566 switch ((*it)->type) {
567 case ADD_IDENTITY: {
568 add_stmt.Reset(true);
569 add_stmt.BindString(0, (*it)->origin.spec());
570 add_stmt.BindString(1, (*it)->identity_name);
571 add_stmt.BindString(2, (*it)->identity.common_name);
572 const std::string& cert = (*it)->identity.certificate;
573 add_stmt.BindBlob(3, cert.data(), cert.size());
574 const std::string& private_key = (*it)->identity.private_key;
575 add_stmt.BindBlob(4, private_key.data(), private_key.size());
576 add_stmt.BindInt64(5, (*it)->identity.creation_time);
577 if (!add_stmt.Run()) {
578 DVLOG(2) << "Failed to add the identity to DB.";
579 return;
581 break;
583 case DELETE_IDENTITY:
584 del_stmt.Reset(true);
585 del_stmt.BindString(0, (*it)->origin.spec());
586 del_stmt.BindString(1, (*it)->identity_name);
587 if (!del_stmt.Run()) {
588 DVLOG(2) << "Failed to delete the identity from DB.";
589 return;
591 break;
593 default:
594 NOTREACHED();
595 break;
599 if (!transaction.Commit())
600 DVLOG(2) << "Failed to commit the transaction.";
603 } // namespace content