1 // Copyright 2015 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/notifications/notification_database.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/files/file_util.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "content/browser/notifications/notification_database_conversions.h"
16 #include "content/public/browser/browser_task_traits.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_database_data.h"
19 #include "storage/common/database/database_identifier.h"
20 #include "third_party/blink/public/common/notifications/notification_resources.h"
21 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
22 #include "third_party/leveldatabase/env_chromium.h"
23 #include "third_party/leveldatabase/leveldb_chrome.h"
24 #include "third_party/leveldatabase/src/include/leveldb/db.h"
25 #include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
26 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
27 #include "url/gurl.h"
28 
29 // Notification LevelDB database schema (in alphabetized order)
30 // =======================
31 //
32 // key: "DATA:" <origin identifier> '\x00' <notification_id>
33 // value: String containing the NotificationDatabaseDataProto protocol buffer
34 //        in serialized form.
35 //
36 // key: "RESOURCES:" <origin identifier> '\x00' <notification_id>
37 // value: String containing the NotificationDatabaseResourcesProto protocol
38 //        buffer in serialized form.
39 
40 namespace content {
41 namespace {
42 
43 // Keys of the fields defined in the database.
44 const char kDataKeyPrefix[] = "DATA:";
45 const char kResourcesKeyPrefix[] = "RESOURCES:";
46 
47 // Separates the components of compound keys.
48 const char kNotificationKeySeparator = '\x00';
49 
50 // Converts the LevelDB |status| to one of the notification database's values.
LevelDBStatusToNotificationDatabaseStatus(const leveldb::Status & status)51 NotificationDatabase::Status LevelDBStatusToNotificationDatabaseStatus(
52     const leveldb::Status& status) {
53   if (status.ok())
54     return NotificationDatabase::STATUS_OK;
55   else if (status.IsNotFound())
56     return NotificationDatabase::STATUS_ERROR_NOT_FOUND;
57   else if (status.IsCorruption())
58     return NotificationDatabase::STATUS_ERROR_CORRUPTED;
59   else if (status.IsIOError())
60     return NotificationDatabase::STATUS_IO_ERROR;
61   else if (status.IsNotSupportedError())
62     return NotificationDatabase::STATUS_NOT_SUPPORTED;
63   else if (status.IsInvalidArgument())
64     return NotificationDatabase::STATUS_INVALID_ARGUMENT;
65 
66   return NotificationDatabase::STATUS_ERROR_FAILED;
67 }
68 
69 // Creates a prefix for the data entries based on |origin|.
CreateDataPrefix(const GURL & origin)70 std::string CreateDataPrefix(const GURL& origin) {
71   if (!origin.is_valid())
72     return kDataKeyPrefix;
73 
74   return base::StringPrintf("%s%s%c", kDataKeyPrefix,
75                             storage::GetIdentifierFromOrigin(origin).c_str(),
76                             kNotificationKeySeparator);
77 }
78 
79 // Creates the compound data key in which notification data is stored.
CreateDataKey(const GURL & origin,const std::string & notification_id)80 std::string CreateDataKey(const GURL& origin,
81                           const std::string& notification_id) {
82   DCHECK(origin.is_valid());
83   DCHECK(!notification_id.empty());
84 
85   return CreateDataPrefix(origin) + notification_id;
86 }
87 
88 // Creates a prefix for the resources entries based on |origin|.
CreateResourcesPrefix(const GURL & origin)89 std::string CreateResourcesPrefix(const GURL& origin) {
90   if (!origin.is_valid())
91     return kResourcesKeyPrefix;
92 
93   return base::StringPrintf("%s%s%c", kResourcesKeyPrefix,
94                             storage::GetIdentifierFromOrigin(origin).c_str(),
95                             kNotificationKeySeparator);
96 }
97 
98 // Creates the compound data key in which notification resources are stored.
CreateResourcesKey(const GURL & origin,const std::string & notification_id)99 std::string CreateResourcesKey(const GURL& origin,
100                                const std::string& notification_id) {
101   DCHECK(origin.is_valid());
102   DCHECK(!notification_id.empty());
103 
104   return CreateResourcesPrefix(origin) + notification_id;
105 }
106 
107 // Deserializes data in |serialized_data| to |notification_database_data|.
108 // Will return if the deserialization was successful.
DeserializedNotificationData(const std::string & serialized_data,NotificationDatabaseData * notification_database_data)109 NotificationDatabase::Status DeserializedNotificationData(
110     const std::string& serialized_data,
111     NotificationDatabaseData* notification_database_data) {
112   DCHECK(notification_database_data);
113   if (DeserializeNotificationDatabaseData(serialized_data,
114                                           notification_database_data)) {
115     return NotificationDatabase::STATUS_OK;
116   }
117 
118   DLOG(ERROR) << "Unable to deserialize a notification's data.";
119   return NotificationDatabase::STATUS_ERROR_CORRUPTED;
120 }
121 
122 // Deserializes resources in |serialized_resources| to |notification_resources|.
123 // Will return if the deserialization was successful.
DeserializedNotificationResources(const std::string & serialized_resources,blink::NotificationResources * notification_resources)124 NotificationDatabase::Status DeserializedNotificationResources(
125     const std::string& serialized_resources,
126     blink::NotificationResources* notification_resources) {
127   DCHECK(notification_resources);
128   if (DeserializeNotificationDatabaseResources(serialized_resources,
129                                                notification_resources)) {
130     return NotificationDatabase::STATUS_OK;
131   }
132 
133   DLOG(ERROR) << "Unable to deserialize a notification's resources.";
134   return NotificationDatabase::STATUS_ERROR_CORRUPTED;
135 }
136 
137 // Updates the time of the last click on the notification, and the first if
138 // necessary.
UpdateNotificationTimestamps(NotificationDatabaseData * data)139 void UpdateNotificationTimestamps(NotificationDatabaseData* data) {
140   base::TimeDelta delta = base::Time::Now() - data->creation_time_millis;
141   if (!data->time_until_first_click_millis.has_value())
142     data->time_until_first_click_millis = delta;
143   data->time_until_last_click_millis = delta;
144 }
145 
146 }  // namespace
147 
NotificationDatabase(const base::FilePath & path,UkmCallback callback)148 NotificationDatabase::NotificationDatabase(const base::FilePath& path,
149                                            UkmCallback callback)
150     : path_(path), record_notification_to_ukm_callback_(std::move(callback)) {}
151 
~NotificationDatabase()152 NotificationDatabase::~NotificationDatabase() {
153   DCHECK(sequence_checker_.CalledOnValidSequence());
154 }
155 
Open(bool create_if_missing)156 NotificationDatabase::Status NotificationDatabase::Open(
157     bool create_if_missing) {
158   DCHECK(sequence_checker_.CalledOnValidSequence());
159   DCHECK_EQ(State::UNINITIALIZED, state_);
160 
161   if (!create_if_missing) {
162     if (IsInMemoryDatabase() || !base::PathExists(path_) ||
163         base::IsDirectoryEmpty(path_)) {
164       return NotificationDatabase::STATUS_ERROR_NOT_FOUND;
165     }
166   }
167 
168   filter_policy_.reset(leveldb::NewBloomFilterPolicy(10));
169 
170   leveldb_env::Options options;
171   options.create_if_missing = create_if_missing;
172   options.paranoid_checks = true;
173   options.filter_policy = filter_policy_.get();
174   options.block_cache = leveldb_chrome::GetSharedWebBlockCache();
175   if (IsInMemoryDatabase()) {
176     env_ = leveldb_chrome::NewMemEnv("notification");
177     options.env = env_.get();
178   }
179 
180   Status status = LevelDBStatusToNotificationDatabaseStatus(
181       leveldb_env::OpenDB(options, path_.AsUTF8Unsafe(), &db_));
182   if (status == STATUS_OK)
183     state_ = State::INITIALIZED;
184 
185   return status;
186 }
187 
ReadNotificationData(const std::string & notification_id,const GURL & origin,NotificationDatabaseData * notification_database_data) const188 NotificationDatabase::Status NotificationDatabase::ReadNotificationData(
189     const std::string& notification_id,
190     const GURL& origin,
191     NotificationDatabaseData* notification_database_data) const {
192   DCHECK(sequence_checker_.CalledOnValidSequence());
193   DCHECK_EQ(State::INITIALIZED, state_);
194   DCHECK(!notification_id.empty());
195   DCHECK(origin.is_valid());
196   DCHECK(notification_database_data);
197 
198   std::string key = CreateDataKey(origin, notification_id);
199   std::string serialized_data;
200 
201   Status status = LevelDBStatusToNotificationDatabaseStatus(
202       db_->Get(leveldb::ReadOptions(), key, &serialized_data));
203   if (status != STATUS_OK)
204     return status;
205 
206   return DeserializedNotificationData(serialized_data,
207                                       notification_database_data);
208 }
209 
ReadNotificationResources(const std::string & notification_id,const GURL & origin,blink::NotificationResources * notification_resources) const210 NotificationDatabase::Status NotificationDatabase::ReadNotificationResources(
211     const std::string& notification_id,
212     const GURL& origin,
213     blink::NotificationResources* notification_resources) const {
214   DCHECK(sequence_checker_.CalledOnValidSequence());
215   DCHECK_EQ(State::INITIALIZED, state_);
216   DCHECK(!notification_id.empty());
217   DCHECK(origin.is_valid());
218   DCHECK(notification_resources);
219 
220   std::string key = CreateResourcesKey(origin, notification_id);
221   std::string serialized_resources;
222 
223   Status status = LevelDBStatusToNotificationDatabaseStatus(
224       db_->Get(leveldb::ReadOptions(), key, &serialized_resources));
225   if (status != STATUS_OK)
226     return status;
227 
228   return DeserializedNotificationResources(serialized_resources,
229                                            notification_resources);
230 }
231 
232 NotificationDatabase::Status
ReadNotificationDataAndRecordInteraction(const std::string & notification_id,const GURL & origin,PlatformNotificationContext::Interaction interaction,NotificationDatabaseData * notification_database_data)233 NotificationDatabase::ReadNotificationDataAndRecordInteraction(
234     const std::string& notification_id,
235     const GURL& origin,
236     PlatformNotificationContext::Interaction interaction,
237     NotificationDatabaseData* notification_database_data) {
238   Status status =
239       ReadNotificationData(notification_id, origin, notification_database_data);
240   if (status != STATUS_OK)
241     return status;
242 
243   // Update the appropriate fields for UKM logging purposes.
244   switch (interaction) {
245     case PlatformNotificationContext::Interaction::CLOSED:
246       notification_database_data->closed_reason =
247           NotificationDatabaseData::ClosedReason::USER;
248       notification_database_data->time_until_close_millis =
249           base::Time::Now() - notification_database_data->creation_time_millis;
250       break;
251     case PlatformNotificationContext::Interaction::NONE:
252       break;
253     case PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED:
254       notification_database_data->num_action_button_clicks += 1;
255       UpdateNotificationTimestamps(notification_database_data);
256       break;
257     case PlatformNotificationContext::Interaction::CLICKED:
258       notification_database_data->num_clicks += 1;
259       UpdateNotificationTimestamps(notification_database_data);
260       break;
261   }
262 
263   // Write the changed values to the database.
264   status = WriteNotificationData(origin, *notification_database_data);
265   UMA_HISTOGRAM_ENUMERATION(
266       "Notifications.Database.ReadResultRecordInteraction", status,
267       NotificationDatabase::STATUS_COUNT);
268   return status;
269 }
270 
ForEachNotificationData(ReadAllNotificationsCallback callback) const271 NotificationDatabase::Status NotificationDatabase::ForEachNotificationData(
272     ReadAllNotificationsCallback callback) const {
273   return ForEachNotificationDataInternal(
274       GURL() /* origin */, blink::mojom::kInvalidServiceWorkerRegistrationId,
275       std::move(callback));
276 }
277 
278 NotificationDatabase::Status
ForEachNotificationDataForServiceWorkerRegistration(const GURL & origin,int64_t service_worker_registration_id,ReadAllNotificationsCallback callback) const279 NotificationDatabase::ForEachNotificationDataForServiceWorkerRegistration(
280     const GURL& origin,
281     int64_t service_worker_registration_id,
282     ReadAllNotificationsCallback callback) const {
283   return ForEachNotificationDataInternal(origin, service_worker_registration_id,
284                                          std::move(callback));
285 }
286 
287 NotificationDatabase::Status
ReadAllNotificationDataForOrigin(const GURL & origin,std::vector<NotificationDatabaseData> * notification_data_vector) const288 NotificationDatabase::ReadAllNotificationDataForOrigin(
289     const GURL& origin,
290     std::vector<NotificationDatabaseData>* notification_data_vector) const {
291   return ReadAllNotificationDataInternal(
292       origin, blink::mojom::kInvalidServiceWorkerRegistrationId,
293       notification_data_vector);
294 }
295 
296 NotificationDatabase::Status
ReadAllNotificationDataForServiceWorkerRegistration(const GURL & origin,int64_t service_worker_registration_id,std::vector<NotificationDatabaseData> * notification_data_vector) const297 NotificationDatabase::ReadAllNotificationDataForServiceWorkerRegistration(
298     const GURL& origin,
299     int64_t service_worker_registration_id,
300     std::vector<NotificationDatabaseData>* notification_data_vector) const {
301   return ReadAllNotificationDataInternal(origin, service_worker_registration_id,
302                                          notification_data_vector);
303 }
304 
WriteNotificationData(const GURL & origin,const NotificationDatabaseData & notification_data)305 NotificationDatabase::Status NotificationDatabase::WriteNotificationData(
306     const GURL& origin,
307     const NotificationDatabaseData& notification_data) {
308   DCHECK(sequence_checker_.CalledOnValidSequence());
309   DCHECK_EQ(State::INITIALIZED, state_);
310   DCHECK(origin.is_valid());
311 
312   const std::string& notification_id = notification_data.notification_id;
313   DCHECK(!notification_id.empty());
314 
315   std::string serialized_data;
316   if (!SerializeNotificationDatabaseData(notification_data, &serialized_data)) {
317     DLOG(ERROR) << "Unable to serialize data for a notification belonging "
318                 << "to: " << origin;
319     return STATUS_ERROR_FAILED;
320   }
321 
322   leveldb::WriteBatch batch;
323   batch.Put(CreateDataKey(origin, notification_id), serialized_data);
324 
325   if (notification_data.notification_resources.has_value()) {
326     std::string serialized_resources;
327     if (!SerializeNotificationDatabaseResources(
328             notification_data.notification_resources.value(),
329             &serialized_resources)) {
330       DLOG(ERROR) << "Unable to serialize resources for a notification "
331                   << "belonging to: " << origin;
332       return STATUS_ERROR_FAILED;
333     }
334 
335     batch.Put(CreateResourcesKey(origin, notification_id),
336               serialized_resources);
337   }
338 
339   return LevelDBStatusToNotificationDatabaseStatus(
340       db_->Write(leveldb::WriteOptions(), &batch));
341 }
342 
DeleteNotificationData(const std::string & notification_id,const GURL & origin)343 NotificationDatabase::Status NotificationDatabase::DeleteNotificationData(
344     const std::string& notification_id,
345     const GURL& origin) {
346   DCHECK(sequence_checker_.CalledOnValidSequence());
347   DCHECK_EQ(State::INITIALIZED, state_);
348   DCHECK(!notification_id.empty());
349   DCHECK(origin.is_valid());
350 
351   NotificationDatabaseData data;
352   Status status = ReadNotificationData(notification_id, origin, &data);
353   if (status == STATUS_OK && record_notification_to_ukm_callback_) {
354     GetUIThreadTaskRunner({})->PostTask(
355         FROM_HERE, base::BindOnce(record_notification_to_ukm_callback_, data));
356   }
357 
358   leveldb::WriteBatch batch;
359   batch.Delete(CreateDataKey(origin, notification_id));
360   batch.Delete(CreateResourcesKey(origin, notification_id));
361 
362   return LevelDBStatusToNotificationDatabaseStatus(
363       db_->Write(leveldb::WriteOptions(), &batch));
364 }
365 
DeleteNotificationResources(const std::string & notification_id,const GURL & origin)366 NotificationDatabase::Status NotificationDatabase::DeleteNotificationResources(
367     const std::string& notification_id,
368     const GURL& origin) {
369   DCHECK(sequence_checker_.CalledOnValidSequence());
370   DCHECK_EQ(State::INITIALIZED, state_);
371   DCHECK(!notification_id.empty());
372   DCHECK(origin.is_valid());
373 
374   std::string key = CreateResourcesKey(origin, notification_id);
375   return LevelDBStatusToNotificationDatabaseStatus(
376       db_->Delete(leveldb::WriteOptions(), key));
377 }
378 
379 NotificationDatabase::Status
DeleteAllNotificationDataForOrigin(const GURL & origin,const std::string & tag,std::set<std::string> * deleted_notification_ids)380 NotificationDatabase::DeleteAllNotificationDataForOrigin(
381     const GURL& origin,
382     const std::string& tag,
383     std::set<std::string>* deleted_notification_ids) {
384   return DeleteAllNotificationDataInternal(
385       origin, tag, blink::mojom::kInvalidServiceWorkerRegistrationId,
386       deleted_notification_ids);
387 }
388 
389 NotificationDatabase::Status
DeleteAllNotificationDataForServiceWorkerRegistration(const GURL & origin,int64_t service_worker_registration_id,std::set<std::string> * deleted_notification_ids)390 NotificationDatabase::DeleteAllNotificationDataForServiceWorkerRegistration(
391     const GURL& origin,
392     int64_t service_worker_registration_id,
393     std::set<std::string>* deleted_notification_ids) {
394   return DeleteAllNotificationDataInternal(origin, "" /* tag */,
395                                            service_worker_registration_id,
396                                            deleted_notification_ids);
397 }
398 
Destroy()399 NotificationDatabase::Status NotificationDatabase::Destroy() {
400   DCHECK(sequence_checker_.CalledOnValidSequence());
401 
402   leveldb_env::Options options;
403   if (IsInMemoryDatabase()) {
404     if (!env_)
405       return STATUS_OK;  // The database has not been initialized.
406 
407     options.env = env_.get();
408   }
409 
410   state_ = State::DISABLED;
411   db_.reset();
412 
413   return LevelDBStatusToNotificationDatabaseStatus(
414       leveldb::DestroyDB(path_.AsUTF8Unsafe(), options));
415 }
416 
417 NotificationDatabase::Status
ReadAllNotificationDataInternal(const GURL & origin,int64_t service_worker_registration_id,std::vector<NotificationDatabaseData> * notification_data_vector) const418 NotificationDatabase::ReadAllNotificationDataInternal(
419     const GURL& origin,
420     int64_t service_worker_registration_id,
421     std::vector<NotificationDatabaseData>* notification_data_vector) const {
422   DCHECK(sequence_checker_.CalledOnValidSequence());
423   DCHECK(notification_data_vector);
424 
425   return ForEachNotificationDataInternal(
426       origin, service_worker_registration_id,
427       base::BindRepeating(
428           [](std::vector<NotificationDatabaseData>* datas,
429              const NotificationDatabaseData& data) { datas->push_back(data); },
430           notification_data_vector));
431 }
432 
433 NotificationDatabase::Status
ForEachNotificationDataInternal(const GURL & origin,int64_t service_worker_registration_id,ReadAllNotificationsCallback callback) const434 NotificationDatabase::ForEachNotificationDataInternal(
435     const GURL& origin,
436     int64_t service_worker_registration_id,
437     ReadAllNotificationsCallback callback) const {
438   DCHECK(sequence_checker_.CalledOnValidSequence());
439 
440   const std::string prefix = CreateDataPrefix(origin);
441 
442   leveldb::Slice prefix_slice(prefix);
443 
444   NotificationDatabaseData notification_database_data;
445   std::unique_ptr<leveldb::Iterator> iter(
446       db_->NewIterator(leveldb::ReadOptions()));
447   for (iter->Seek(prefix_slice); iter->Valid(); iter->Next()) {
448     if (!iter->key().starts_with(prefix_slice))
449       break;
450 
451     Status status = DeserializedNotificationData(iter->value().ToString(),
452                                                  &notification_database_data);
453     if (status != STATUS_OK)
454       return status;
455 
456     if (service_worker_registration_id !=
457             blink::mojom::kInvalidServiceWorkerRegistrationId &&
458         notification_database_data.service_worker_registration_id !=
459             service_worker_registration_id) {
460       continue;
461     }
462 
463     callback.Run(notification_database_data);
464   }
465 
466   return LevelDBStatusToNotificationDatabaseStatus(iter->status());
467 }
468 
469 NotificationDatabase::Status
DeleteAllNotificationDataInternal(const GURL & origin,const std::string & tag,int64_t service_worker_registration_id,std::set<std::string> * deleted_notification_ids)470 NotificationDatabase::DeleteAllNotificationDataInternal(
471     const GURL& origin,
472     const std::string& tag,
473     int64_t service_worker_registration_id,
474     std::set<std::string>* deleted_notification_ids) {
475   DCHECK(sequence_checker_.CalledOnValidSequence());
476   DCHECK(deleted_notification_ids);
477   DCHECK(origin.is_valid());
478 
479   const std::string prefix = CreateDataPrefix(origin);
480 
481   leveldb::Slice prefix_slice(prefix);
482   leveldb::WriteBatch batch;
483   bool did_delete = false;
484 
485   NotificationDatabaseData notification_database_data;
486   std::unique_ptr<leveldb::Iterator> iter(
487       db_->NewIterator(leveldb::ReadOptions()));
488   for (iter->Seek(prefix_slice); iter->Valid(); iter->Next()) {
489     if (!iter->key().starts_with(prefix_slice))
490       break;
491 
492     Status status = DeserializedNotificationData(iter->value().ToString(),
493                                                  &notification_database_data);
494     if (status != STATUS_OK)
495       return status;
496 
497     if (!tag.empty() &&
498         notification_database_data.notification_data.tag != tag) {
499       continue;
500     }
501 
502     if (service_worker_registration_id !=
503             blink::mojom::kInvalidServiceWorkerRegistrationId &&
504         notification_database_data.service_worker_registration_id !=
505             service_worker_registration_id) {
506       continue;
507     }
508 
509     if (record_notification_to_ukm_callback_) {
510       GetUIThreadTaskRunner({})->PostTask(
511           FROM_HERE, base::BindOnce(record_notification_to_ukm_callback_,
512                                     notification_database_data));
513     }
514 
515     std::string notification_id = notification_database_data.notification_id;
516     DCHECK(!notification_id.empty());
517 
518     batch.Delete(iter->key());
519     batch.Delete(CreateResourcesKey(origin, notification_id));
520     did_delete = true;
521 
522     deleted_notification_ids->insert(notification_id);
523   }
524 
525   if (!did_delete)
526     return STATUS_OK;
527 
528   return LevelDBStatusToNotificationDatabaseStatus(
529       db_->Write(leveldb::WriteOptions(), &batch));
530 }
531 
532 }  // namespace content
533