Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / browser / media / webrtc_identity_store_backend.cc
blob42e725865461195b4b959ade9f6083aae37e4d23
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 "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/error_delegate_util.h"
14 #include "sql/statement.h"
15 #include "sql/transaction.h"
16 #include "url/gurl.h"
17 #include "webkit/browser/quota/special_storage_policy.h"
19 namespace content {
21 static const char kWebRTCIdentityStoreDBName[] = "webrtc_identity_store";
23 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
24 FILE_PATH_LITERAL("WebRTCIdentityStore");
26 // Initializes the identity table, returning true on success.
27 static bool InitDB(sql::Connection* db) {
28 if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
29 if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
30 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
31 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
32 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
33 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
34 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
35 return true;
37 if (!db->Execute("DROP TABLE webrtc_identity_store"))
38 return false;
41 return db->Execute(
42 "CREATE TABLE webrtc_identity_store"
43 " ("
44 "origin TEXT NOT NULL,"
45 "identity_name TEXT NOT NULL,"
46 "common_name TEXT NOT NULL,"
47 "certificate BLOB NOT NULL,"
48 "private_key BLOB NOT NULL,"
49 "creation_time INTEGER)");
52 struct WebRTCIdentityStoreBackend::IdentityKey {
53 IdentityKey(const GURL& origin, const std::string& identity_name)
54 : origin(origin), identity_name(identity_name) {}
56 bool operator<(const IdentityKey& other) const {
57 return origin < other.origin ||
58 (origin == other.origin && identity_name < other.identity_name);
61 GURL origin;
62 std::string identity_name;
65 struct WebRTCIdentityStoreBackend::Identity {
66 Identity(const std::string& common_name,
67 const std::string& certificate,
68 const std::string& private_key)
69 : common_name(common_name),
70 certificate(certificate),
71 private_key(private_key),
72 creation_time(base::Time::Now().ToInternalValue()) {}
74 Identity(const std::string& common_name,
75 const std::string& certificate,
76 const std::string& private_key,
77 int64 creation_time)
78 : common_name(common_name),
79 certificate(certificate),
80 private_key(private_key),
81 creation_time(creation_time) {}
83 std::string common_name;
84 std::string certificate;
85 std::string private_key;
86 int64 creation_time;
89 struct WebRTCIdentityStoreBackend::PendingFindRequest {
90 PendingFindRequest(const GURL& origin,
91 const std::string& identity_name,
92 const std::string& common_name,
93 const FindIdentityCallback& callback)
94 : origin(origin),
95 identity_name(identity_name),
96 common_name(common_name),
97 callback(callback) {}
99 ~PendingFindRequest() {}
101 GURL origin;
102 std::string identity_name;
103 std::string common_name;
104 FindIdentityCallback callback;
107 // The class encapsulates the database operations. All members except ctor and
108 // dtor should be accessed on the DB thread.
109 // It can be created/destroyed on any thread.
110 class WebRTCIdentityStoreBackend::SqlLiteStorage
111 : public base::RefCountedThreadSafe<SqlLiteStorage> {
112 public:
113 SqlLiteStorage(base::TimeDelta validity_period,
114 const base::FilePath& path,
115 storage::SpecialStoragePolicy* policy)
116 : validity_period_(validity_period), special_storage_policy_(policy) {
117 if (!path.empty())
118 path_ = path.Append(kWebRTCIdentityStoreDirectory);
121 void Load(IdentityMap* out_map);
122 void Close();
123 void AddIdentity(const GURL& origin,
124 const std::string& identity_name,
125 const Identity& identity);
126 void DeleteIdentity(const GURL& origin,
127 const std::string& identity_name,
128 const Identity& identity);
129 void DeleteBetween(base::Time delete_begin, base::Time delete_end);
131 void SetValidityPeriodForTesting(base::TimeDelta validity_period) {
132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
133 DCHECK(!db_.get());
134 validity_period_ = validity_period;
137 private:
138 friend class base::RefCountedThreadSafe<SqlLiteStorage>;
140 enum OperationType {
141 ADD_IDENTITY,
142 DELETE_IDENTITY
144 struct PendingOperation {
145 PendingOperation(OperationType type,
146 const GURL& origin,
147 const std::string& identity_name,
148 const Identity& identity)
149 : type(type),
150 origin(origin),
151 identity_name(identity_name),
152 identity(identity) {}
154 OperationType type;
155 GURL origin;
156 std::string identity_name;
157 Identity identity;
159 typedef ScopedVector<PendingOperation> PendingOperationList;
161 virtual ~SqlLiteStorage() {}
162 void OnDatabaseError(int error, sql::Statement* stmt);
163 void BatchOperation(OperationType type,
164 const GURL& origin,
165 const std::string& identity_name,
166 const Identity& identity);
167 void Commit();
169 base::TimeDelta validity_period_;
170 // The file path of the DB. Empty if temporary.
171 base::FilePath path_;
172 scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
173 scoped_ptr<sql::Connection> db_;
174 // Batched DB operations pending to commit.
175 PendingOperationList pending_operations_;
177 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
180 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
181 const base::FilePath& path,
182 storage::SpecialStoragePolicy* policy,
183 base::TimeDelta validity_period)
184 : validity_period_(validity_period),
185 state_(NOT_STARTED),
186 sql_lite_storage_(new SqlLiteStorage(validity_period, path, policy)) {
189 bool WebRTCIdentityStoreBackend::FindIdentity(
190 const GURL& origin,
191 const std::string& identity_name,
192 const std::string& common_name,
193 const FindIdentityCallback& callback) {
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
195 if (state_ == CLOSED)
196 return false;
198 if (state_ != LOADED) {
199 // Queues the request to wait for the DB to load.
200 pending_find_requests_.push_back(
201 new PendingFindRequest(origin, identity_name, common_name, callback));
202 if (state_ == LOADING)
203 return true;
205 DCHECK_EQ(state_, NOT_STARTED);
207 // Kick off loading the DB.
208 scoped_ptr<IdentityMap> out_map(new IdentityMap());
209 base::Closure task(
210 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
211 // |out_map| will be NULL after this call.
212 if (BrowserThread::PostTaskAndReply(
213 BrowserThread::DB,
214 FROM_HERE,
215 task,
216 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
217 this,
218 base::Passed(&out_map)))) {
219 state_ = LOADING;
220 return true;
222 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
225 IdentityKey key(origin, identity_name);
226 IdentityMap::iterator iter = identities_.find(key);
227 if (iter != identities_.end() && iter->second.common_name == common_name) {
228 base::TimeDelta age = base::Time::Now() - base::Time::FromInternalValue(
229 iter->second.creation_time);
230 if (age < validity_period_) {
231 // Identity found.
232 return BrowserThread::PostTask(BrowserThread::IO,
233 FROM_HERE,
234 base::Bind(callback,
235 net::OK,
236 iter->second.certificate,
237 iter->second.private_key));
239 // Removes the expired identity from the in-memory cache. The copy in the
240 // database will be removed on the next load.
241 identities_.erase(iter);
244 return BrowserThread::PostTask(
245 BrowserThread::IO,
246 FROM_HERE,
247 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
250 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
251 const std::string& identity_name,
252 const std::string& common_name,
253 const std::string& certificate,
254 const std::string& private_key) {
255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
256 if (state_ == CLOSED)
257 return;
259 // If there is an existing identity for the same origin and identity_name,
260 // delete it.
261 IdentityKey key(origin, identity_name);
262 Identity identity(common_name, certificate, private_key);
264 if (identities_.find(key) != identities_.end()) {
265 if (!BrowserThread::PostTask(BrowserThread::DB,
266 FROM_HERE,
267 base::Bind(&SqlLiteStorage::DeleteIdentity,
268 sql_lite_storage_,
269 origin,
270 identity_name,
271 identities_.find(key)->second)))
272 return;
274 identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
276 BrowserThread::PostTask(BrowserThread::DB,
277 FROM_HERE,
278 base::Bind(&SqlLiteStorage::AddIdentity,
279 sql_lite_storage_,
280 origin,
281 identity_name,
282 identity));
285 void WebRTCIdentityStoreBackend::Close() {
286 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
287 BrowserThread::PostTask(
288 BrowserThread::IO,
289 FROM_HERE,
290 base::Bind(&WebRTCIdentityStoreBackend::Close, this));
291 return;
294 if (state_ == CLOSED)
295 return;
297 state_ = CLOSED;
298 BrowserThread::PostTask(
299 BrowserThread::DB,
300 FROM_HERE,
301 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
304 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
305 base::Time delete_end,
306 const base::Closure& callback) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
308 if (state_ == CLOSED)
309 return;
311 // Delete the in-memory cache.
312 IdentityMap::iterator it = identities_.begin();
313 while (it != identities_.end()) {
314 if (it->second.creation_time >= delete_begin.ToInternalValue() &&
315 it->second.creation_time <= delete_end.ToInternalValue()) {
316 identities_.erase(it++);
317 } else {
318 ++it;
321 BrowserThread::PostTaskAndReply(BrowserThread::DB,
322 FROM_HERE,
323 base::Bind(&SqlLiteStorage::DeleteBetween,
324 sql_lite_storage_,
325 delete_begin,
326 delete_end),
327 callback);
330 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
331 base::TimeDelta validity_period) {
332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
333 validity_period_ = validity_period;
334 BrowserThread::PostTask(
335 BrowserThread::DB,
336 FROM_HERE,
337 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting,
338 sql_lite_storage_,
339 validity_period));
342 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
344 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
347 if (state_ != LOADING)
348 return;
350 DVLOG(3) << "WebRTC identity store has loaded.";
352 state_ = LOADED;
353 identities_.swap(*out_map);
355 for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
356 FindIdentity(pending_find_requests_[i]->origin,
357 pending_find_requests_[i]->identity_name,
358 pending_find_requests_[i]->common_name,
359 pending_find_requests_[i]->callback);
360 delete pending_find_requests_[i];
362 pending_find_requests_.clear();
366 // Implementation of SqlLiteStorage.
369 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
371 DCHECK(!db_.get());
373 // Ensure the parent directory for storing certs is created before reading
374 // from it.
375 const base::FilePath dir = path_.DirName();
376 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) {
377 DVLOG(2) << "Unable to open DB file path.";
378 return;
381 db_.reset(new sql::Connection());
383 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
385 if (!db_->Open(path_)) {
386 DVLOG(2) << "Unable to open DB.";
387 db_.reset();
388 return;
391 if (!InitDB(db_.get())) {
392 DVLOG(2) << "Unable to init DB.";
393 db_.reset();
394 return;
397 db_->Preload();
399 // Delete expired identities.
400 DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
402 // Slurp all the identities into the out_map.
403 sql::Statement stmt(db_->GetUniqueStatement(
404 "SELECT origin, identity_name, common_name, "
405 "certificate, private_key, creation_time "
406 "FROM webrtc_identity_store"));
407 CHECK(stmt.is_valid());
409 while (stmt.Step()) {
410 IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
411 std::string common_name(stmt.ColumnString(2));
412 std::string cert, private_key;
413 stmt.ColumnBlobAsString(3, &cert);
414 stmt.ColumnBlobAsString(4, &private_key);
415 int64 creation_time = stmt.ColumnInt64(5);
416 std::pair<IdentityMap::iterator, bool> result =
417 out_map->insert(std::pair<IdentityKey, Identity>(
418 key, Identity(common_name, cert, private_key, creation_time)));
419 DCHECK(result.second);
423 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
425 Commit();
426 db_.reset();
429 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
430 const GURL& origin,
431 const std::string& identity_name,
432 const Identity& identity) {
433 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
434 if (!db_.get())
435 return;
437 // Do not add for session only origins.
438 if (special_storage_policy_.get() &&
439 !special_storage_policy_->IsStorageProtected(origin) &&
440 special_storage_policy_->IsStorageSessionOnly(origin)) {
441 return;
443 BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
446 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
447 const GURL& origin,
448 const std::string& identity_name,
449 const Identity& identity) {
450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
451 if (!db_.get())
452 return;
453 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
456 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
457 base::Time delete_begin,
458 base::Time delete_end) {
459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
460 if (!db_.get())
461 return;
463 // Commit pending operations first.
464 Commit();
466 sql::Statement del_stmt(db_->GetCachedStatement(
467 SQL_FROM_HERE,
468 "DELETE FROM webrtc_identity_store"
469 " WHERE creation_time >= ? AND creation_time <= ?"));
470 CHECK(del_stmt.is_valid());
472 del_stmt.BindInt64(0, delete_begin.ToInternalValue());
473 del_stmt.BindInt64(1, delete_end.ToInternalValue());
475 sql::Transaction transaction(db_.get());
476 if (!transaction.Begin()) {
477 DVLOG(2) << "Failed to begin the transaction.";
478 return;
481 if (!del_stmt.Run()) {
482 DVLOG(2) << "Failed to run the delete statement.";
483 return;
486 if (!transaction.Commit())
487 DVLOG(2) << "Failed to commit the transaction.";
490 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
491 int error,
492 sql::Statement* stmt) {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
495 db_->RazeAndClose();
496 // It's not safe to reset |db_| here.
499 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
500 OperationType type,
501 const GURL& origin,
502 const std::string& identity_name,
503 const Identity& identity) {
504 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
505 // Commit every 30 seconds.
506 static const base::TimeDelta kCommitInterval(
507 base::TimeDelta::FromSeconds(30));
508 // Commit right away if we have more than 512 outstanding operations.
509 static const size_t kCommitAfterBatchSize = 512;
511 // We do a full copy of the cert here, and hopefully just here.
512 scoped_ptr<PendingOperation> operation(
513 new PendingOperation(type, origin, identity_name, identity));
515 pending_operations_.push_back(operation.release());
517 if (pending_operations_.size() == 1) {
518 // We've gotten our first entry for this batch, fire off the timer.
519 BrowserThread::PostDelayedTask(BrowserThread::DB,
520 FROM_HERE,
521 base::Bind(&SqlLiteStorage::Commit, this),
522 kCommitInterval);
523 } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
524 // We've reached a big enough batch, fire off a commit now.
525 BrowserThread::PostTask(BrowserThread::DB,
526 FROM_HERE,
527 base::Bind(&SqlLiteStorage::Commit, this));
531 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
532 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
533 // Maybe an old timer fired or we are already Close()'ed.
534 if (!db_.get() || pending_operations_.empty())
535 return;
537 sql::Statement add_stmt(db_->GetCachedStatement(
538 SQL_FROM_HERE,
539 "INSERT INTO webrtc_identity_store "
540 "(origin, identity_name, common_name, certificate,"
541 " private_key, creation_time) VALUES"
542 " (?,?,?,?,?,?)"));
544 CHECK(add_stmt.is_valid());
546 sql::Statement del_stmt(db_->GetCachedStatement(
547 SQL_FROM_HERE,
548 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
550 CHECK(del_stmt.is_valid());
552 sql::Transaction transaction(db_.get());
553 if (!transaction.Begin()) {
554 DVLOG(2) << "Failed to begin the transaction.";
555 return;
558 // Swaps |pending_operations_| into a temporary list to make sure
559 // |pending_operations_| is always cleared in case of DB errors.
560 PendingOperationList pending_operations_copy;
561 pending_operations_.swap(pending_operations_copy);
563 for (PendingOperationList::const_iterator it =
564 pending_operations_copy.begin();
565 it != pending_operations_copy.end();
566 ++it) {
567 switch ((*it)->type) {
568 case ADD_IDENTITY: {
569 add_stmt.Reset(true);
570 add_stmt.BindString(0, (*it)->origin.spec());
571 add_stmt.BindString(1, (*it)->identity_name);
572 add_stmt.BindString(2, (*it)->identity.common_name);
573 const std::string& cert = (*it)->identity.certificate;
574 add_stmt.BindBlob(3, cert.data(), cert.size());
575 const std::string& private_key = (*it)->identity.private_key;
576 add_stmt.BindBlob(4, private_key.data(), private_key.size());
577 add_stmt.BindInt64(5, (*it)->identity.creation_time);
578 if (!add_stmt.Run()) {
579 DVLOG(2) << "Failed to add the identity to DB.";
580 return;
582 break;
584 case DELETE_IDENTITY:
585 del_stmt.Reset(true);
586 del_stmt.BindString(0, (*it)->origin.spec());
587 del_stmt.BindString(1, (*it)->identity_name);
588 if (!del_stmt.Run()) {
589 DVLOG(2) << "Failed to delete the identity from DB.";
590 return;
592 break;
594 default:
595 NOTREACHED();
596 break;
600 if (!transaction.Commit())
601 DVLOG(2) << "Failed to commit the transaction.";
604 } // namespace content