- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / media / webrtc_identity_store_backend.cc
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.
4
5 #include "content/browser/media/webrtc_identity_store_backend.h"
6
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"
16
17 namespace content {
18
19 static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store";
20
21 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
22     FILE_PATH_LITERAL("WebRTCIdentityStore");
23
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;
36   }
37
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)");
47 }
48
49 struct WebRTCIdentityStoreBackend::IdentityKey {
50   IdentityKey(const GURL& origin, const std::string& identity_name)
51       : origin(origin), identity_name(identity_name) {}
52
53   bool operator<(const IdentityKey& other) const {
54     return origin < other.origin ||
55            (origin == other.origin && identity_name < other.identity_name);
56   }
57
58   GURL origin;
59   std::string identity_name;
60 };
61
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()) {}
70
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) {}
79
80   std::string common_name;
81   std::string certificate;
82   std::string private_key;
83   int64 creation_time;
84 };
85
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) {}
95
96   ~PendingFindRequest() {}
97
98   GURL origin;
99   std::string identity_name;
100   std::string common_name;
101   FindIdentityCallback callback;
102 };
103
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);
116   }
117
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);
127
128   void SetValidityPeriodForTesting(base::TimeDelta validity_period) {
129     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
130     DCHECK(!db_.get());
131     validity_period_ = validity_period;
132   }
133
134  private:
135   friend class base::RefCountedThreadSafe<SqlLiteStorage>;
136
137   enum OperationType {
138     ADD_IDENTITY,
139     DELETE_IDENTITY
140   };
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) {}
150
151     OperationType type;
152     GURL origin;
153     std::string identity_name;
154     Identity identity;
155   };
156   typedef std::vector<PendingOperation*> PendingOperationList;
157
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();
165
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_;
173
174   DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
175 };
176
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)) {}
184
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;
193
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;
200
201     DCHECK_EQ(state_, NOT_STARTED);
202
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;
217     }
218     // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
219   }
220
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));
234     }
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);
238   }
239
240   return BrowserThread::PostTask(
241       BrowserThread::IO,
242       FROM_HERE,
243       base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
244 }
245
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;
254
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);
259
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;
269   }
270   identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
271
272   BrowserThread::PostTask(BrowserThread::DB,
273                           FROM_HERE,
274                           base::Bind(&SqlLiteStorage::AddIdentity,
275                                      sql_lite_storage_,
276                                      origin,
277                                      identity_name,
278                                      identity));
279 }
280
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;
288   }
289
290   if (state_ == CLOSED)
291     return;
292
293   state_ = CLOSED;
294   BrowserThread::PostTask(
295       BrowserThread::DB,
296       FROM_HERE,
297       base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
298 }
299
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;
306
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;
315     }
316   }
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);
324 }
325
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));
336 }
337
338 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
339
340 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
341   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
342
343   if (state_ != LOADING)
344     return;
345
346   DVLOG(2) << "WebRTC identity store has loaded.";
347
348   state_ = LOADED;
349   identities_.swap(*out_map);
350
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];
357   }
358   pending_find_requests_.clear();
359 }
360
361 //
362 // Implementation of SqlLiteStorage.
363 //
364
365 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
366   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
367   DCHECK(!db_.get());
368
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) && !file_util::CreateDirectory(dir)) {
373     DLOG(ERROR) << "Unable to open DB file path.";
374     return;
375   }
376
377   db_.reset(new sql::Connection());
378
379   db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
380
381   if (!db_->Open(path_)) {
382     DLOG(ERROR) << "Unable to open DB.";
383     db_.reset();
384     return;
385   }
386
387   if (!InitDB(db_.get())) {
388     DLOG(ERROR) << "Unable to init DB.";
389     db_.reset();
390     return;
391   }
392
393   db_->Preload();
394
395   // Delete expired identities.
396   DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
397
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());
404
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);
416   }
417 }
418
419 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
420   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
421   Commit();
422   db_.reset();
423 }
424
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;
432
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;
438   }
439   BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
440 }
441
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);
450 }
451
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;
458
459   // Commit pending operations first.
460   Commit();
461
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());
467
468   del_stmt.BindInt64(0, delete_begin.ToInternalValue());
469   del_stmt.BindInt64(1, delete_end.ToInternalValue());
470
471   sql::Transaction transaction(db_.get());
472   if (!transaction.Begin()) {
473     DLOG(ERROR) << "Failed to begin the transaction.";
474     return;
475   }
476
477   CHECK(del_stmt.Run());
478   transaction.Commit();
479 }
480
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();
488 }
489
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;
501
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));
505
506   pending_operations_.push_back(operation.release());
507
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));
519   }
520 }
521
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;
527
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       " (?,?,?,?,?,?)"));
534
535   CHECK(add_stmt.is_valid());
536
537   sql::Statement del_stmt(db_->GetCachedStatement(
538       SQL_FROM_HERE,
539       "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
540
541   CHECK(del_stmt.is_valid());
542
543   sql::Transaction transaction(db_.get());
544   if (!transaction.Begin()) {
545     DLOG(ERROR) << "Failed to begin the transaction.";
546     return;
547   }
548
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;
566       }
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;
573
574       default:
575         NOTREACHED();
576         break;
577     }
578   }
579   transaction.Commit();
580   pending_operations_.clear();
581 }
582
583 }  // namespace content