Upstream version 10.39.225.0
[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/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/error_delegate_util.h"
14 #include "sql/statement.h"
15 #include "sql/transaction.h"
16 #include "storage/browser/quota/special_storage_policy.h"
17 #include "url/gurl.h"
18
19 namespace content {
20
21 static const char kWebRTCIdentityStoreDBName[] = "webrtc_identity_store";
22
23 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
24     FILE_PATH_LITERAL("WebRTCIdentityStore");
25
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;
36
37     if (!db->Execute("DROP TABLE webrtc_identity_store"))
38       return false;
39   }
40
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)");
50 }
51
52 struct WebRTCIdentityStoreBackend::IdentityKey {
53   IdentityKey(const GURL& origin, const std::string& identity_name)
54       : origin(origin), identity_name(identity_name) {}
55
56   bool operator<(const IdentityKey& other) const {
57     return origin < other.origin ||
58            (origin == other.origin && identity_name < other.identity_name);
59   }
60
61   GURL origin;
62   std::string identity_name;
63 };
64
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()) {}
73
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) {}
82
83   std::string common_name;
84   std::string certificate;
85   std::string private_key;
86   int64 creation_time;
87 };
88
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) {}
98
99   ~PendingFindRequest() {}
100
101   GURL origin;
102   std::string identity_name;
103   std::string common_name;
104   FindIdentityCallback callback;
105 };
106
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);
119   }
120
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);
130
131   void SetValidityPeriodForTesting(base::TimeDelta validity_period) {
132     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
133     DCHECK(!db_.get());
134     validity_period_ = validity_period;
135   }
136
137  private:
138   friend class base::RefCountedThreadSafe<SqlLiteStorage>;
139
140   enum OperationType {
141     ADD_IDENTITY,
142     DELETE_IDENTITY
143   };
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) {}
153
154     OperationType type;
155     GURL origin;
156     std::string identity_name;
157     Identity identity;
158   };
159   typedef ScopedVector<PendingOperation> PendingOperationList;
160
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();
168
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_;
176
177   DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
178 };
179
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)) {
187 }
188
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;
197
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;
204
205     DCHECK_EQ(state_, NOT_STARTED);
206
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;
221     }
222     // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
223   }
224
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));
238     }
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);
242   }
243
244   return BrowserThread::PostTask(
245       BrowserThread::IO,
246       FROM_HERE,
247       base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
248 }
249
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;
258
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);
263
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;
273   }
274   identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
275
276   BrowserThread::PostTask(BrowserThread::DB,
277                           FROM_HERE,
278                           base::Bind(&SqlLiteStorage::AddIdentity,
279                                      sql_lite_storage_,
280                                      origin,
281                                      identity_name,
282                                      identity));
283 }
284
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;
292   }
293
294   if (state_ == CLOSED)
295     return;
296
297   state_ = CLOSED;
298   BrowserThread::PostTask(
299       BrowserThread::DB,
300       FROM_HERE,
301       base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
302 }
303
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;
310
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;
319     }
320   }
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);
328 }
329
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));
340 }
341
342 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
343
344 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
345   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
346
347   if (state_ != LOADING)
348     return;
349
350   DVLOG(3) << "WebRTC identity store has loaded.";
351
352   state_ = LOADED;
353   identities_.swap(*out_map);
354
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];
361   }
362   pending_find_requests_.clear();
363 }
364
365 //
366 // Implementation of SqlLiteStorage.
367 //
368
369 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
370   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
371   DCHECK(!db_.get());
372
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;
379   }
380
381   db_.reset(new sql::Connection());
382
383   db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
384
385   if (!db_->Open(path_)) {
386     DVLOG(2) << "Unable to open DB.";
387     db_.reset();
388     return;
389   }
390
391   if (!InitDB(db_.get())) {
392     DVLOG(2) << "Unable to init DB.";
393     db_.reset();
394     return;
395   }
396
397   db_->Preload();
398
399   // Delete expired identities.
400   DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
401
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());
408
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);
420   }
421 }
422
423 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
424   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
425   Commit();
426   db_.reset();
427 }
428
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;
436
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;
442   }
443   BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
444 }
445
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);
454 }
455
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;
462
463   // Commit pending operations first.
464   Commit();
465
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());
471
472   del_stmt.BindInt64(0, delete_begin.ToInternalValue());
473   del_stmt.BindInt64(1, delete_end.ToInternalValue());
474
475   sql::Transaction transaction(db_.get());
476   if (!transaction.Begin()) {
477     DVLOG(2) << "Failed to begin the transaction.";
478     return;
479   }
480
481   if (!del_stmt.Run()) {
482     DVLOG(2) << "Failed to run the delete statement.";
483     return;
484   }
485
486   if (!transaction.Commit())
487     DVLOG(2) << "Failed to commit the transaction.";
488 }
489
490 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
491     int error,
492     sql::Statement* stmt) {
493   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
494
495   db_->RazeAndClose();
496   // It's not safe to reset |db_| here.
497 }
498
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;
510
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));
514
515   pending_operations_.push_back(operation.release());
516
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));
528   }
529 }
530
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;
536
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       " (?,?,?,?,?,?)"));
543
544   CHECK(add_stmt.is_valid());
545
546   sql::Statement del_stmt(db_->GetCachedStatement(
547       SQL_FROM_HERE,
548       "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
549
550   CHECK(del_stmt.is_valid());
551
552   sql::Transaction transaction(db_.get());
553   if (!transaction.Begin()) {
554     DVLOG(2) << "Failed to begin the transaction.";
555     return;
556   }
557
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);
562
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;
581         }
582         break;
583       }
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;
591         }
592         break;
593
594       default:
595         NOTREACHED();
596         break;
597     }
598   }
599
600   if (!transaction.Commit())
601     DVLOG(2) << "Failed to commit the transaction.";
602 }
603
604 }  // namespace content