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 ¬ification_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 ¬ification_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