| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/push_messaging/push_messaging_manager.h" |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/service_worker/service_worker_context_core.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/child_process_host.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "content/public/browser/permission_request_description.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/common/content_switches.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" |
| #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom.h" |
| #include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Chrome currently does not support the Push API in incognito. |
| const char kIncognitoPushUnsupportedMessage[] = |
| "Chrome currently does not support the Push API in incognito mode " |
| "(https://crbug.com/401439). There is deliberately no way to " |
| "feature-detect this, since incognito mode needs to be undetectable by " |
| "websites."; |
| |
| // These UMA methods are called from the SW and/or UI threads. Racey but ok, see |
| // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/FNzZRJtN2aw/Aw0CWAXJJ1kJ |
| void RecordRegistrationStatus(blink::mojom::PushRegistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UMA_HISTOGRAM_ENUMERATION("PushMessaging.RegistrationStatus", status); |
| } |
| void RecordUnregistrationStatus(blink::mojom::PushUnregistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationStatus", status); |
| } |
| void RecordGetRegistrationStatus( |
| blink::mojom::PushGetRegistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UMA_HISTOGRAM_ENUMERATION("PushMessaging.GetRegistrationStatus", status); |
| } |
| |
| const char* PushUnregistrationStatusToString( |
| blink::mojom::PushUnregistrationStatus status) { |
| switch (status) { |
| case blink::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED: |
| return "Unregistration successful - from push service"; |
| |
| case blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED: |
| return "Unregistration successful - was not registered"; |
| |
| case blink::mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR: |
| return "Unregistration pending - a network error occurred, but it will " |
| "be retried until it succeeds"; |
| |
| case blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER: |
| return "Unregistration failed - no Service Worker"; |
| |
| case blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE: |
| return "Unregistration failed - push service not available"; |
| |
| case blink::mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR: |
| return "Unregistration pending - a push service error occurred, but it " |
| "will be retried until it succeeds"; |
| |
| case blink::mojom::PushUnregistrationStatus::STORAGE_ERROR: |
| return "Unregistration failed - storage error"; |
| |
| case blink::mojom::PushUnregistrationStatus::NETWORK_ERROR: |
| return "Unregistration failed - could not connect to push server"; |
| } |
| NOTREACHED(); |
| } |
| |
| // Returns application_server_key if non-empty, otherwise checks if |
| // stored_sender_id may be used as a fallback and if so, returns |
| // stored_sender_id instead. |
| // |
| // This is in order to support the legacy way of subscribing from a service |
| // worker (first subscribe from the document using a gcm_sender_id set in the |
| // manifest, and then subscribe from the service worker with no key). |
| // |
| // An empty string will be returned if application_server_key is empty and the |
| // fallback is not a numeric gcm sender id. |
| std::string FixSenderInfo(const std::string& application_server_key, |
| const std::string& stored_sender_id) { |
| if (!application_server_key.empty()) |
| return application_server_key; |
| if (base::ContainsOnlyChars(stored_sender_id, "0123456789")) |
| return stored_sender_id; |
| return std::string(); |
| } |
| |
| bool IsRequestFromDocument(int render_frame_id) { |
| return render_frame_id != ChildProcessHost::kInvalidUniqueID; |
| } |
| |
| } // namespace |
| |
| struct PushMessagingManager::RegisterData { |
| RegisterData() = default; |
| RegisterData(RegisterData&& other) = default; |
| |
| blink::StorageKey requesting_storage_key{}; |
| int64_t service_worker_registration_id{0}; |
| std::optional<std::string> existing_subscription_id; |
| blink::mojom::PushSubscriptionOptionsPtr options; |
| SubscribeCallback callback; |
| |
| // True if the call to register was made with a user gesture. |
| bool user_gesture; |
| }; |
| |
| PushMessagingManager::PushMessagingManager( |
| RenderProcessHost& render_process_host, |
| int render_frame_id, |
| scoped_refptr<ServiceWorkerContextWrapper> service_worker_context) |
| : render_process_host_(render_process_host), |
| render_frame_id_(render_frame_id), |
| service_worker_context_(std::move(service_worker_context)), |
| is_incognito_( |
| render_process_host_->GetBrowserContext()->IsOffTheRecord()), |
| service_available_(!!GetService()) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| PushMessagingManager::~PushMessagingManager() {} |
| |
| void PushMessagingManager::AddPushMessagingReceiver( |
| mojo::PendingReceiver<blink::mojom::PushMessaging> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| // Subscribe methods, merged in order of use. |
| // ----------------------------------------------------------------------------- |
| |
| void PushMessagingManager::Subscribe( |
| int64_t service_worker_registration_id, |
| blink::mojom::PushSubscriptionOptionsPtr options, |
| bool user_gesture, |
| SubscribeCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(options); |
| |
| RegisterData data; |
| |
| data.service_worker_registration_id = service_worker_registration_id; |
| data.callback = std::move(callback); |
| data.options = std::move(options); |
| data.user_gesture = user_gesture; |
| |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration = |
| service_worker_context_->GetLiveRegistration( |
| data.service_worker_registration_id); |
| if (!service_worker_registration || |
| !service_worker_registration->active_version()) { |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::NO_SERVICE_WORKER); |
| return; |
| } |
| |
| // The renderer should have checked and disallowed the request for fenced |
| // frames and thrown an exception in blink::PushManager. Report a bad message |
| // if the renderer if the renderer side check didn't happen for some reason. |
| if (service_worker_registration->ancestor_frame_type() == |
| blink::mojom::AncestorFrameType::kFencedFrame) { |
| bad_message::ReceivedBadMessage(render_process_host_->GetDeprecatedID(), |
| bad_message::PMM_SUBSCRIBE_IN_FENCED_FRAME); |
| return; |
| } |
| |
| const blink::StorageKey& storage_key = service_worker_registration->key(); |
| |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin( |
| render_process_host_->GetDeprecatedID(), storage_key.origin())) { |
| bad_message::ReceivedBadMessage(&*render_process_host_, |
| bad_message::PMM_SUBSCRIBE_INVALID_ORIGIN); |
| return; |
| } |
| |
| data.requesting_storage_key = storage_key; |
| |
| DCHECK(!(data.options->application_server_key.empty() && |
| IsRequestFromDocument(render_frame_id_))); |
| |
| int64_t registration_id = data.service_worker_registration_id; |
| service_worker_context_->GetRegistrationUserData( |
| registration_id, |
| {kPushRegistrationIdServiceWorkerKey, kPushSenderIdServiceWorkerKey}, |
| base::BindOnce(&PushMessagingManager::DidCheckForExistingRegistration, |
| weak_factory_.GetWeakPtr(), std::move(data))); |
| } |
| |
| void PushMessagingManager::DidCheckForExistingRegistration( |
| RegisterData data, |
| const std::vector<std::string>& subscription_id_and_sender_id, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Validate the stored subscription against the subscription request made by |
| // the developer. The authorized entity must match. |
| if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) { |
| DCHECK_EQ(2u, subscription_id_and_sender_id.size()); |
| |
| const std::string& subscription_id = subscription_id_and_sender_id[0]; |
| const std::string& stored_sender_id = subscription_id_and_sender_id[1]; |
| |
| const std::string application_server_key_string( |
| data.options->application_server_key.begin(), |
| data.options->application_server_key.end()); |
| |
| std::string fixed_sender_id( |
| FixSenderInfo(application_server_key_string, stored_sender_id)); |
| if (fixed_sender_id.empty()) { |
| SendSubscriptionError(std::move(data), |
| blink::mojom::PushRegistrationStatus::NO_SENDER_ID); |
| return; |
| } |
| |
| if (fixed_sender_id != stored_sender_id) { |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::SENDER_ID_MISMATCH); |
| return; |
| } |
| |
| data.existing_subscription_id = subscription_id; |
| } |
| |
| // TODO(peter): Handle failures other than |
| // blink::ServiceWorkerStatusCode::kErrorNotFound by rejecting |
| // the subscription algorithm instead of trying to subscribe. |
| |
| if (!data.options->application_server_key.empty()) { |
| Register(std::move(data)); |
| } else { |
| // No |application_server_key| was provided by the developer. Fall back to |
| // checking whether a previous subscription did identify a sender. |
| int64_t registration_id = data.service_worker_registration_id; |
| service_worker_context_->GetRegistrationUserData( |
| registration_id, {kPushSenderIdServiceWorkerKey}, |
| base::BindOnce(&PushMessagingManager::DidGetSenderIdFromStorage, |
| weak_factory_.GetWeakPtr(), std::move(data))); |
| } |
| } |
| |
| void PushMessagingManager::DidGetSenderIdFromStorage( |
| RegisterData data, |
| const std::vector<std::string>& stored_sender_id, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) { |
| SendSubscriptionError(std::move(data), |
| blink::mojom::PushRegistrationStatus::NO_SENDER_ID); |
| return; |
| } |
| DCHECK_EQ(1u, stored_sender_id.size()); |
| // We should only be here because no sender info was supplied to subscribe(). |
| DCHECK(data.options->application_server_key.empty()); |
| |
| const std::string application_server_key_string( |
| std::string(data.options->application_server_key.begin(), |
| data.options->application_server_key.end())); |
| std::string fixed_sender_id( |
| FixSenderInfo(application_server_key_string, stored_sender_id[0])); |
| if (fixed_sender_id.empty()) { |
| SendSubscriptionError(std::move(data), |
| blink::mojom::PushRegistrationStatus::NO_SENDER_ID); |
| return; |
| } |
| data.options->application_server_key = |
| std::vector<uint8_t>(fixed_sender_id.begin(), fixed_sender_id.end()); |
| Register(std::move(data)); |
| } |
| |
| void PushMessagingManager::Register(PushMessagingManager::RegisterData data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| PushMessagingService* push_service = GetService(); |
| if (!push_service) { |
| if (!is_incognito_) { |
| // This might happen if InstanceIDProfileService::IsInstanceIDEnabled |
| // returns false because the Instance ID kill switch was enabled. |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE); |
| } else { |
| // Prevent websites from detecting incognito mode, by emulating what would |
| // have happened if we had a PushMessagingService available. |
| if (!IsRequestFromDocument(render_frame_id_) || |
| !data.options->user_visible_only) { |
| // Throw a permission denied error under the same circumstances. |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED); |
| } else { |
| RenderFrameHostImpl* render_frame_host_impl = |
| RenderFrameHostImpl::FromID(render_process_host_->GetDeprecatedID(), |
| render_frame_id_); |
| if (render_frame_host_impl) { |
| render_frame_host_impl->AddMessageToConsole( |
| blink::mojom::ConsoleMessageLevel::kError, |
| kIncognitoPushUnsupportedMessage); |
| |
| // Request notifications permission (which will fail, since |
| // notifications aren't supported in incognito), so the website can't |
| // detect whether incognito is active. |
| bool user_gesture = data.user_gesture; |
| |
| DCHECK_EQ(data.requesting_storage_key, |
| render_frame_host_impl->GetStorageKey()); |
| |
| render_frame_host_impl->GetBrowserContext() |
| ->GetPermissionController() |
| ->RequestPermissionFromCurrentDocument( |
| render_frame_host_impl, |
| PermissionRequestDescription( |
| PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS), |
| user_gesture), |
| base::BindOnce( |
| &PushMessagingManager::DidRequestPermissionInIncognito, |
| AsWeakPtr(), std::move(data))); |
| } |
| } |
| } |
| return; |
| } |
| |
| int64_t registration_id = data.service_worker_registration_id; |
| url::Origin requesting_origin = data.requesting_storage_key.origin(); |
| bool user_gesture = data.user_gesture; |
| |
| auto options = data.options->Clone(); |
| if (IsRequestFromDocument(render_frame_id_)) { |
| push_service->SubscribeFromDocument( |
| requesting_origin.GetURL(), registration_id, |
| render_process_host_->GetDeprecatedID(), render_frame_id_, |
| std::move(options), user_gesture, |
| base::BindOnce(&PushMessagingManager::DidRegister, AsWeakPtr(), |
| std::move(data))); |
| } else { |
| push_service->SubscribeFromWorker( |
| requesting_origin.GetURL(), registration_id, |
| render_process_host_->GetDeprecatedID(), std::move(options), |
| base::BindOnce(&PushMessagingManager::DidRegister, AsWeakPtr(), |
| std::move(data))); |
| } |
| } |
| |
| void PushMessagingManager::DidRequestPermissionInIncognito( |
| RegisterData data, |
| PermissionResult permission_result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Notification permission should always be denied in incognito. |
| DCHECK_EQ(blink::mojom::PermissionStatus::DENIED, permission_result.status); |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED); |
| } |
| |
| // TODO(crbug.com/40139581): Handle expiration_time that is passed from push |
| // service check if |expiration_time| is valid before saving it in |data| and |
| // passing it back in SendSubscriptionSuccess. |
| void PushMessagingManager::DidRegister( |
| RegisterData data, |
| const std::string& push_subscription_id, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth, |
| blink::mojom::PushRegistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // TODO(crbug.com/41275327): Handle the case where |push_subscription_id| and |
| // |data.existing_subscription_id| are not the same. Right now we just |
| // override the old subscription ID and encryption information. |
| const bool subscription_changed = |
| data.existing_subscription_id.has_value() && |
| data.existing_subscription_id.value() != push_subscription_id; |
| |
| if (status == |
| blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE) { |
| PersistRegistration( |
| std::move(data), push_subscription_id, endpoint, expiration_time, |
| p256dh, auth, |
| subscription_changed |
| ? blink::mojom::PushRegistrationStatus:: |
| SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE |
| : blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE); |
| } else { |
| // TODO(crbug.com/41275327): for invalid |expiration_time| send a |
| // subscription error with a new PushRegistrationStatus |
| SendSubscriptionError(std::move(data), status); |
| } |
| } |
| |
| void PushMessagingManager::PersistRegistration( |
| RegisterData data, |
| const std::string& push_subscription_id, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth, |
| blink::mojom::PushRegistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| blink::StorageKey storage_key = data.requesting_storage_key; |
| int64_t registration_id = data.service_worker_registration_id; |
| std::string application_server_key( |
| std::string(data.options->application_server_key.begin(), |
| data.options->application_server_key.end())); |
| |
| service_worker_context_->StoreRegistrationUserData( |
| registration_id, std::move(storage_key), |
| {{kPushRegistrationIdServiceWorkerKey, push_subscription_id}, |
| {kPushSenderIdServiceWorkerKey, application_server_key}}, |
| base::BindOnce(&PushMessagingManager::DidPersistRegistration, |
| weak_factory_.GetWeakPtr(), std::move(data), endpoint, |
| expiration_time, p256dh, auth, status)); |
| } |
| |
| void PushMessagingManager::DidPersistRegistration( |
| RegisterData data, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth, |
| blink::mojom::PushRegistrationStatus push_registration_status, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) { |
| SendSubscriptionSuccess(std::move(data), push_registration_status, endpoint, |
| expiration_time, p256dh, auth); |
| } else { |
| // TODO(johnme): Unregister, so PushMessagingServiceImpl can decrease count. |
| SendSubscriptionError(std::move(data), |
| blink::mojom::PushRegistrationStatus::STORAGE_ERROR); |
| } |
| } |
| |
| void PushMessagingManager::SendSubscriptionError( |
| RegisterData data, |
| blink::mojom::PushRegistrationStatus status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(data.callback).Run(status, nullptr /* subscription */); |
| RecordRegistrationStatus(status); |
| } |
| |
| void PushMessagingManager::SendSubscriptionSuccess( |
| RegisterData data, |
| blink::mojom::PushRegistrationStatus status, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!service_available_) { |
| // This shouldn't be possible in incognito mode, since we've already checked |
| // that we have an existing registration. Hence it's ok to throw an error. |
| DCHECK(!is_incognito_); |
| SendSubscriptionError( |
| std::move(data), |
| blink::mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE); |
| return; |
| } |
| |
| std::move(data.callback) |
| .Run(status, blink::mojom::PushSubscription::New( |
| endpoint, expiration_time, std::move(data.options), |
| p256dh, auth)); |
| |
| RecordRegistrationStatus(status); |
| } |
| |
| // Unsubscribe methods, merged in order of use. |
| // ----------------------------------------------------------------------------- |
| |
| void PushMessagingManager::Unsubscribe(int64_t service_worker_registration_id, |
| UnsubscribeCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration = |
| service_worker_context_->GetLiveRegistration( |
| service_worker_registration_id); |
| if (!service_worker_registration) { |
| DidUnregister(std::move(callback), |
| blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER); |
| return; |
| } |
| |
| const url::Origin& origin = service_worker_registration->key().origin(); |
| |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin( |
| render_process_host_->GetDeprecatedID(), origin)) { |
| bad_message::ReceivedBadMessage( |
| &*render_process_host_, bad_message::PMM_UNSUBSCRIBE_INVALID_ORIGIN); |
| return; |
| } |
| |
| service_worker_context_->GetRegistrationUserData( |
| service_worker_registration_id, {kPushSenderIdServiceWorkerKey}, |
| base::BindOnce(&PushMessagingManager::UnsubscribeHavingGottenSenderId, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| service_worker_registration_id, origin)); |
| } |
| |
| void PushMessagingManager::UnsubscribeHavingGottenSenderId( |
| UnsubscribeCallback callback, |
| int64_t service_worker_registration_id, |
| const url::Origin& requesting_origin, |
| const std::vector<std::string>& sender_ids, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| std::string sender_id; |
| if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) { |
| DCHECK_EQ(1u, sender_ids.size()); |
| sender_id = sender_ids[0]; |
| } |
| |
| PushMessagingService* push_service = GetService(); |
| if (!push_service) { |
| // This shouldn't be possible in incognito mode, since we've already checked |
| // that we have an existing registration. Hence it's ok to throw an error. |
| DCHECK(!is_incognito_); |
| DidUnregister( |
| std::move(callback), |
| blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE); |
| return; |
| } |
| |
| push_service->Unsubscribe( |
| blink::mojom::PushUnregistrationReason::JAVASCRIPT_API, |
| requesting_origin.GetURL(), service_worker_registration_id, sender_id, |
| base::BindOnce(&PushMessagingManager::DidUnregister, AsWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void PushMessagingManager::DidUnregister( |
| UnsubscribeCallback callback, |
| blink::mojom::PushUnregistrationStatus unregistration_status) { |
| // Only called from SW thread, but would be safe to call from UI thread. |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| switch (unregistration_status) { |
| case blink::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED: |
| case blink::mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR: |
| case blink::mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR: |
| std::move(callback).Run(blink::mojom::PushErrorType::NONE, |
| true /* did_unsubscribe */, |
| std::nullopt /* error_message */); |
| break; |
| case blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED: |
| std::move(callback).Run(blink::mojom::PushErrorType::NONE, |
| false /* did_unsubscribe */, |
| std::nullopt /* error_message */); |
| break; |
| case blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER: |
| case blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE: |
| case blink::mojom::PushUnregistrationStatus::STORAGE_ERROR: |
| std::move(callback).Run(blink::mojom::PushErrorType::ABORT, false, |
| std::string(PushUnregistrationStatusToString( |
| unregistration_status)) /* error_message */); |
| break; |
| case blink::mojom::PushUnregistrationStatus::NETWORK_ERROR: |
| NOTREACHED(); |
| } |
| RecordUnregistrationStatus(unregistration_status); |
| } |
| |
| // GetSubscription methods, merged in order of use. |
| // ----------------------------------------------------------------------------- |
| |
| void PushMessagingManager::GetSubscription( |
| int64_t service_worker_registration_id, |
| GetSubscriptionCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| service_worker_context_->GetLiveRegistration( |
| service_worker_registration_id); |
| if (registration) { |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin( |
| render_process_host_->GetDeprecatedID(), |
| registration->key().origin())) { |
| bad_message::ReceivedBadMessage( |
| &*render_process_host_, |
| bad_message::PMM_GET_SUBSCRIPTION_INVALID_ORIGIN); |
| return; |
| } |
| } |
| |
| service_worker_context_->GetRegistrationUserData( |
| service_worker_registration_id, |
| {kPushRegistrationIdServiceWorkerKey, kPushSenderIdServiceWorkerKey}, |
| base::BindOnce(&PushMessagingManager::DidGetSubscription, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| service_worker_registration_id)); |
| } |
| |
| void PushMessagingManager::DidGetSubscription( |
| GetSubscriptionCallback callback, |
| int64_t service_worker_registration_id, |
| const std::vector<std::string>& |
| push_subscription_id_and_application_server_key, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| blink::mojom::PushGetRegistrationStatus get_status = |
| blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR; |
| switch (service_worker_status) { |
| case blink::ServiceWorkerStatusCode::kOk: { |
| DCHECK_EQ(2u, push_subscription_id_and_application_server_key.size()); |
| const std::string& push_subscription_id = |
| push_subscription_id_and_application_server_key[0]; |
| const std::string& application_server_key = |
| push_subscription_id_and_application_server_key[1]; |
| |
| if (!service_available_) { |
| // Return not found in incognito mode, so websites can't detect it. |
| get_status = is_incognito_ ? blink::mojom::PushGetRegistrationStatus:: |
| INCOGNITO_REGISTRATION_NOT_FOUND |
| : blink::mojom::PushGetRegistrationStatus:: |
| SERVICE_NOT_AVAILABLE; |
| break; |
| } |
| |
| scoped_refptr<ServiceWorkerRegistration> registration = |
| service_worker_context_->GetLiveRegistration( |
| service_worker_registration_id); |
| if (!registration) { |
| get_status = |
| blink::mojom::PushGetRegistrationStatus::NO_LIVE_SERVICE_WORKER; |
| break; |
| } |
| |
| const url::Origin& origin = registration->key().origin(); |
| |
| GetSubscriptionInfo( |
| origin, service_worker_registration_id, application_server_key, |
| push_subscription_id, |
| base::BindOnce(&PushMessagingManager::GetSubscriptionDidGetInfo, |
| AsWeakPtr(), std::move(callback), origin, |
| service_worker_registration_id, |
| application_server_key)); |
| |
| return; |
| } |
| case blink::ServiceWorkerStatusCode::kErrorNotFound: { |
| get_status = |
| blink::mojom::PushGetRegistrationStatus::REGISTRATION_NOT_FOUND; |
| break; |
| } |
| case blink::ServiceWorkerStatusCode::kErrorFailed: { |
| get_status = blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR; |
| break; |
| } |
| case blink::ServiceWorkerStatusCode::kErrorAbort: |
| case blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed: |
| case blink::ServiceWorkerStatusCode::kErrorProcessNotFound: |
| case blink::ServiceWorkerStatusCode::kErrorExists: |
| case blink::ServiceWorkerStatusCode::kErrorInstallWorkerFailed: |
| case blink::ServiceWorkerStatusCode::kErrorActivateWorkerFailed: |
| case blink::ServiceWorkerStatusCode::kErrorIpcFailed: |
| case blink::ServiceWorkerStatusCode::kErrorNetwork: |
| case blink::ServiceWorkerStatusCode::kErrorSecurity: |
| case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected: |
| case blink::ServiceWorkerStatusCode::kErrorState: |
| case blink::ServiceWorkerStatusCode::kErrorTimeout: |
| case blink::ServiceWorkerStatusCode::kErrorScriptEvaluateFailed: |
| case blink::ServiceWorkerStatusCode::kErrorDiskCache: |
| case blink::ServiceWorkerStatusCode::kErrorRedundant: |
| case blink::ServiceWorkerStatusCode::kErrorDisallowed: |
| case blink::ServiceWorkerStatusCode::kErrorInvalidArguments: |
| case blink::ServiceWorkerStatusCode::kErrorStorageDisconnected: |
| case blink::ServiceWorkerStatusCode::kErrorStorageDataCorrupted: { |
| DUMP_WILL_BE_NOTREACHED() |
| << "Got unexpected error code: " |
| << static_cast<uint32_t>(service_worker_status) << " " |
| << blink::ServiceWorkerStatusToString(service_worker_status); |
| get_status = blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR; |
| break; |
| } |
| } |
| std::move(callback).Run(get_status, nullptr /* subscription */); |
| RecordGetRegistrationStatus(get_status); |
| } |
| |
| void PushMessagingManager::GetSubscriptionDidGetInfo( |
| GetSubscriptionCallback callback, |
| const url::Origin& origin, |
| int64_t service_worker_registration_id, |
| const std::string& application_server_key, |
| bool is_valid, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (is_valid) { |
| auto options = blink::mojom::PushSubscriptionOptions::New(); |
| |
| // Chrome rejects subscription requests with userVisibleOnly false, so it |
| // must have been true. TODO(harkness): If Chrome starts accepting silent |
| // push subscriptions with userVisibleOnly false, the bool will need to be |
| // stored. |
| options->user_visible_only = true; |
| options->application_server_key = std::vector<uint8_t>( |
| application_server_key.begin(), application_server_key.end()); |
| |
| blink::mojom::PushGetRegistrationStatus status = |
| blink::mojom::PushGetRegistrationStatus::SUCCESS; |
| |
| std::move(callback).Run(status, blink::mojom::PushSubscription::New( |
| endpoint, expiration_time, |
| std::move(options), p256dh, auth)); |
| |
| RecordGetRegistrationStatus(status); |
| } else { |
| PushMessagingService* push_service = GetService(); |
| if (!push_service) { |
| // Shouldn't be possible to have a stored push subscription in a profile |
| // with no push service, but this case can occur when the renderer is |
| // shutting down. |
| std::move(callback).Run( |
| blink::mojom::PushGetRegistrationStatus::RENDERER_SHUTDOWN, |
| nullptr /* subscription */); |
| return; |
| } |
| |
| // Uh-oh! Although there was a cached subscription in the Service Worker |
| // database, it did not have matching counterparts in the |
| // PushMessagingAppIdentifier map and/or GCM Store. Unsubscribe to fix this |
| // inconsistency. |
| blink::mojom::PushGetRegistrationStatus status = |
| blink::mojom::PushGetRegistrationStatus::STORAGE_CORRUPT; |
| |
| push_service->Unsubscribe( |
| blink::mojom::PushUnregistrationReason:: |
| GET_SUBSCRIPTION_STORAGE_CORRUPT, |
| origin.GetURL(), service_worker_registration_id, application_server_key, |
| base::BindOnce(&PushMessagingManager::GetSubscriptionDidUnsubscribe, |
| AsWeakPtr(), std::move(callback), status)); |
| |
| RecordGetRegistrationStatus(status); |
| } |
| } |
| |
| void PushMessagingManager::GetSubscriptionDidUnsubscribe( |
| GetSubscriptionCallback callback, |
| blink::mojom::PushGetRegistrationStatus get_status, |
| blink::mojom::PushUnregistrationStatus unsubscribe_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(get_status, nullptr /* subscription */); |
| } |
| |
| void PushMessagingManager::GetSubscriptionInfo( |
| const url::Origin& origin, |
| int64_t service_worker_registration_id, |
| const std::string& sender_id, |
| const std::string& push_subscription_id, |
| PushMessagingService::SubscriptionInfoCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| PushMessagingService* push_service = GetService(); |
| if (!push_service) { |
| std::move(callback).Run(false /* is_valid */, GURL() /* endpoint */, |
| std::nullopt /* expiration_time */, |
| std::vector<uint8_t>() /* p256dh */, |
| std::vector<uint8_t>() /* auth */); |
| return; |
| } |
| |
| push_service->GetSubscriptionInfo(origin.GetURL(), |
| service_worker_registration_id, sender_id, |
| push_subscription_id, std::move(callback)); |
| } |
| |
| PushMessagingService* PushMessagingManager::GetService() { |
| return render_process_host_->GetBrowserContext()->GetPushMessagingService(); |
| } |
| |
| } // namespace content |