| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/notifications/notification_message_filter.h" |
| |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/notifications/notification_event_dispatcher_impl.h" |
| #include "content/browser/notifications/notification_id_generator.h" |
| #include "content/browser/notifications/platform_notification_context_impl.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/common/platform_notification_messages.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/notification_database_data.h" |
| #include "content/public/browser/platform_notification_service.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "third_party/WebKit/public/platform/modules/notifications/WebNotificationConstants.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const int kMinimumVibrationDurationMs = 1; // 1 millisecond |
| const int kMaximumVibrationDurationMs = 10000; // 10 seconds |
| |
| PlatformNotificationData SanitizeNotificationData( |
| const PlatformNotificationData& notification_data) { |
| PlatformNotificationData sanitized_data = notification_data; |
| |
| // Make sure that the vibration values are within reasonable bounds. |
| for (int& pattern : sanitized_data.vibration_pattern) { |
| pattern = std::min(kMaximumVibrationDurationMs, |
| std::max(kMinimumVibrationDurationMs, pattern)); |
| } |
| |
| // Ensure there aren't more actions than supported. |
| if (sanitized_data.actions.size() > blink::kWebNotificationMaxActions) |
| sanitized_data.actions.resize(blink::kWebNotificationMaxActions); |
| |
| return sanitized_data; |
| } |
| |
| // Returns true when |resources| looks ok, false otherwise. |
| bool ValidateNotificationResources(const NotificationResources& resources) { |
| if (!resources.image.drawsNothing() && |
| !base::FeatureList::IsEnabled(features::kNotificationContentImage)) { |
| return false; |
| } |
| if (resources.image.width() > blink::kWebNotificationMaxImageWidthPx || |
| resources.image.height() > blink::kWebNotificationMaxImageHeightPx) { |
| return false; |
| } |
| if (resources.notification_icon.width() > |
| blink::kWebNotificationMaxIconSizePx || |
| resources.notification_icon.height() > |
| blink::kWebNotificationMaxIconSizePx) { |
| return false; |
| } |
| if (resources.badge.width() > blink::kWebNotificationMaxBadgeSizePx || |
| resources.badge.height() > blink::kWebNotificationMaxBadgeSizePx) { |
| return false; |
| } |
| for (const auto& action_icon : resources.action_icons) { |
| if (action_icon.width() > blink::kWebNotificationMaxActionIconSizePx || |
| action_icon.height() > blink::kWebNotificationMaxActionIconSizePx) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| NotificationMessageFilter::NotificationMessageFilter( |
| int process_id, |
| PlatformNotificationContextImpl* notification_context, |
| ResourceContext* resource_context, |
| const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context, |
| BrowserContext* browser_context) |
| : BrowserMessageFilter(PlatformNotificationMsgStart), |
| process_id_(process_id), |
| non_persistent__notification_shown_(false), |
| notification_context_(notification_context), |
| resource_context_(resource_context), |
| service_worker_context_(service_worker_context), |
| browser_context_(browser_context), |
| weak_factory_io_(this) {} |
| |
| NotificationMessageFilter::~NotificationMessageFilter() = default; |
| |
| void NotificationMessageFilter::OnDestruct() const { |
| if (non_persistent__notification_shown_) { |
| NotificationEventDispatcherImpl* event_dispatcher = |
| NotificationEventDispatcherImpl::GetInstance(); |
| DCHECK(event_dispatcher); |
| event_dispatcher->RendererGone(process_id_); |
| } |
| BrowserThread::DeleteOnIOThread::Destruct(this); |
| } |
| |
| bool NotificationMessageFilter::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(NotificationMessageFilter, message) |
| IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_Show, |
| OnShowPlatformNotification) |
| IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_ShowPersistent, |
| OnShowPersistentNotification) |
| IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_GetNotifications, |
| OnGetNotifications) |
| IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_Close, |
| OnClosePlatformNotification) |
| IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_ClosePersistent, |
| OnClosePersistentNotification) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void NotificationMessageFilter::OverrideThreadForMessage( |
| const IPC::Message& message, |
| BrowserThread::ID* thread) { |
| if (message.type() == PlatformNotificationHostMsg_Show::ID || |
| message.type() == PlatformNotificationHostMsg_Close::ID) |
| *thread = BrowserThread::UI; |
| } |
| |
| void NotificationMessageFilter::OnShowPlatformNotification( |
| int request_id, |
| const GURL& origin, |
| const PlatformNotificationData& notification_data, |
| const NotificationResources& notification_resources) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!RenderProcessHost::FromID(process_id_)) |
| return; |
| |
| if (!ValidateNotificationResources(notification_resources)) { |
| bad_message::ReceivedBadMessage(this, bad_message::NMF_INVALID_ARGUMENT); |
| return; |
| } |
| |
| PlatformNotificationService* service = |
| GetContentClient()->browser()->GetPlatformNotificationService(); |
| DCHECK(service); |
| |
| if (!VerifyNotificationPermissionGranted(service, origin)) |
| return; |
| |
| std::string notification_id = |
| GetNotificationIdGenerator()->GenerateForNonPersistentNotification( |
| origin, notification_data.tag, request_id, process_id_); |
| NotificationEventDispatcherImpl* event_dispatcher = |
| NotificationEventDispatcherImpl::GetInstance(); |
| non_persistent__notification_shown_ = true; |
| event_dispatcher->RegisterNonPersistentNotification(notification_id, |
| process_id_, request_id); |
| |
| service->DisplayNotification(browser_context_, notification_id, origin, |
| SanitizeNotificationData(notification_data), |
| notification_resources); |
| } |
| |
| void NotificationMessageFilter::OnShowPersistentNotification( |
| int request_id, |
| int64_t service_worker_registration_id, |
| const GURL& origin, |
| const PlatformNotificationData& notification_data, |
| const NotificationResources& notification_resources) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (GetPermissionForOriginOnIO(origin) != |
| blink::mojom::PermissionStatus::GRANTED) { |
| // We can't assume that the renderer is compromised at this point because |
| // it's possible for the user to revoke an origin's permission between the |
| // time where a website requests the notification to be shown and the call |
| // arriving in the message filter. |
| return; |
| } |
| |
| if (!ValidateNotificationResources(notification_resources)) { |
| bad_message::ReceivedBadMessage(this, bad_message::NMF_INVALID_ARGUMENT); |
| return; |
| } |
| |
| NotificationDatabaseData database_data; |
| database_data.origin = origin; |
| database_data.service_worker_registration_id = service_worker_registration_id; |
| |
| PlatformNotificationData sanitized_notification_data = |
| SanitizeNotificationData(notification_data); |
| database_data.notification_data = sanitized_notification_data; |
| |
| notification_context_->WriteNotificationData( |
| origin, database_data, |
| base::Bind(&NotificationMessageFilter::DidWritePersistentNotificationData, |
| weak_factory_io_.GetWeakPtr(), request_id, |
| service_worker_registration_id, origin, |
| sanitized_notification_data, notification_resources)); |
| } |
| |
| void NotificationMessageFilter::DidWritePersistentNotificationData( |
| int request_id, |
| int64_t service_worker_registration_id, |
| const GURL& origin, |
| const PlatformNotificationData& notification_data, |
| const NotificationResources& notification_resources, |
| bool success, |
| const std::string& notification_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!success) { |
| Send(new PlatformNotificationMsg_DidShowPersistent(request_id, false)); |
| return; |
| } |
| |
| // Get the service worker scope. |
| service_worker_context_->FindReadyRegistrationForId( |
| service_worker_registration_id, origin, |
| base::Bind(&NotificationMessageFilter::DidFindServiceWorkerRegistration, |
| weak_factory_io_.GetWeakPtr(), request_id, origin, |
| notification_data, notification_resources, notification_id)); |
| } |
| |
| void NotificationMessageFilter::DidFindServiceWorkerRegistration( |
| int request_id, |
| const GURL& origin, |
| const PlatformNotificationData& notification_data, |
| const NotificationResources& notification_resources, |
| const std::string& notification_id, |
| content::ServiceWorkerStatusCode service_worker_status, |
| scoped_refptr<content::ServiceWorkerRegistration> registration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (service_worker_status != SERVICE_WORKER_OK) { |
| Send(new PlatformNotificationMsg_DidShowPersistent(request_id, false)); |
| LOG(ERROR) << "Registration not found for " << origin.spec(); |
| // TODO(peter): Add UMA to track how often this occurs. |
| return; |
| } |
| |
| PlatformNotificationService* service = |
| GetContentClient()->browser()->GetPlatformNotificationService(); |
| DCHECK(service); |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce( |
| &PlatformNotificationService::DisplayPersistentNotification, |
| base::Unretained(service), // The service is a singleton. |
| browser_context_, notification_id, registration->pattern(), origin, |
| notification_data, notification_resources)); |
| |
| Send(new PlatformNotificationMsg_DidShowPersistent(request_id, true)); |
| } |
| |
| void NotificationMessageFilter::OnGetNotifications( |
| int request_id, |
| int64_t service_worker_registration_id, |
| const GURL& origin, |
| const std::string& filter_tag) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (GetPermissionForOriginOnIO(origin) != |
| blink::mojom::PermissionStatus::GRANTED) { |
| // No permission has been granted for the given origin. It is harmless to |
| // try to get notifications without permission, so return an empty vector |
| // indicating that no (accessible) notifications exist at this time. |
| Send(new PlatformNotificationMsg_DidGetNotifications( |
| request_id, std::vector<PersistentNotificationInfo>())); |
| return; |
| } |
| |
| notification_context_->ReadAllNotificationDataForServiceWorkerRegistration( |
| origin, service_worker_registration_id, |
| base::Bind(&NotificationMessageFilter::DidGetNotifications, |
| weak_factory_io_.GetWeakPtr(), request_id, filter_tag)); |
| } |
| |
| void NotificationMessageFilter::DidGetNotifications( |
| int request_id, |
| const std::string& filter_tag, |
| bool success, |
| const std::vector<NotificationDatabaseData>& notifications) { |
| std::vector<PersistentNotificationInfo> persistent_notifications; |
| for (const NotificationDatabaseData& database_data : notifications) { |
| if (!filter_tag.empty()) { |
| const std::string& tag = database_data.notification_data.tag; |
| if (tag != filter_tag) |
| continue; |
| } |
| |
| persistent_notifications.push_back(std::make_pair( |
| database_data.notification_id, database_data.notification_data)); |
| } |
| |
| Send(new PlatformNotificationMsg_DidGetNotifications( |
| request_id, persistent_notifications)); |
| } |
| |
| void NotificationMessageFilter::OnClosePlatformNotification( |
| const GURL& origin, |
| const std::string& tag, |
| int request_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!RenderProcessHost::FromID(process_id_)) |
| return; |
| |
| std::string notification_id = |
| GetNotificationIdGenerator()->GenerateForNonPersistentNotification( |
| origin, tag, request_id, process_id_); |
| |
| PlatformNotificationService* service = |
| GetContentClient()->browser()->GetPlatformNotificationService(); |
| DCHECK(service); |
| |
| service->CloseNotification(browser_context_, notification_id); |
| |
| NotificationEventDispatcherImpl::GetInstance() |
| ->DispatchNonPersistentCloseEvent(notification_id); |
| } |
| |
| void NotificationMessageFilter::OnClosePersistentNotification( |
| const GURL& origin, |
| const std::string& tag, |
| const std::string& notification_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (GetPermissionForOriginOnIO(origin) != |
| blink::mojom::PermissionStatus::GRANTED) { |
| return; |
| } |
| |
| PlatformNotificationService* service = |
| GetContentClient()->browser()->GetPlatformNotificationService(); |
| DCHECK(service); |
| |
| // There's no point in waiting until the database data has been removed before |
| // closing the notification presented to the user. Post that task immediately. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&PlatformNotificationService::ClosePersistentNotification, |
| base::Unretained(service), // The service is a singleton. |
| browser_context_, notification_id)); |
| |
| notification_context_->DeleteNotificationData( |
| notification_id, origin, |
| base::Bind( |
| &NotificationMessageFilter::DidDeletePersistentNotificationData, |
| weak_factory_io_.GetWeakPtr())); |
| } |
| |
| void NotificationMessageFilter::DidDeletePersistentNotificationData( |
| bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // TODO(peter): Consider feeding back to the renderer that the notification |
| // has been closed. |
| } |
| |
| blink::mojom::PermissionStatus |
| NotificationMessageFilter::GetPermissionForOriginOnIO( |
| const GURL& origin) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| PlatformNotificationService* service = |
| GetContentClient()->browser()->GetPlatformNotificationService(); |
| if (!service) |
| return blink::mojom::PermissionStatus::DENIED; |
| |
| return service->CheckPermissionOnIOThread(resource_context_, origin, |
| process_id_); |
| } |
| |
| bool NotificationMessageFilter::VerifyNotificationPermissionGranted( |
| PlatformNotificationService* service, |
| const GURL& origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| blink::mojom::PermissionStatus permission_status = |
| service->CheckPermissionOnUIThread(browser_context_, origin, process_id_); |
| |
| // We can't assume that the renderer is compromised at this point because |
| // it's possible for the user to revoke an origin's permission between the |
| // time where a website requests the notification to be shown and the call |
| // arriving in the message filter. |
| |
| return permission_status == blink::mojom::PermissionStatus::GRANTED; |
| } |
| |
| NotificationIdGenerator* NotificationMessageFilter::GetNotificationIdGenerator() |
| const { |
| return notification_context_->notification_id_generator(); |
| } |
| |
| } // namespace content |