blob: 6d6115ceebb66c69de789cb383dbd0fa06bea94a [file] [log] [blame]
// 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