// Copyright 2015 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 "libcef/browser/permissions/permission_manager.h" #include "libcef/browser/browser_context.h" #include "libcef/browser/permissions/permission_util.h" #include "base/callback.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "content/public/browser/permission_type.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" using blink::mojom::PermissionStatus; using content::PermissionType; namespace { // Helper method to convert ContentSetting to PermissionStatus. PermissionStatus ContentSettingToPermissionStatus(ContentSetting setting) { switch (setting) { case CONTENT_SETTING_ALLOW: case CONTENT_SETTING_SESSION_ONLY: return PermissionStatus::GRANTED; case CONTENT_SETTING_BLOCK: return PermissionStatus::DENIED; case CONTENT_SETTING_ASK: return PermissionStatus::ASK; case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT: case CONTENT_SETTING_DEFAULT: case CONTENT_SETTING_NUM_SETTINGS: break; } NOTREACHED(); return PermissionStatus::DENIED; } // Helper method to convert PermissionStatus to ContentSetting. ContentSetting PermissionStatusToContentSetting(PermissionStatus status) { switch (status) { case PermissionStatus::GRANTED: return CONTENT_SETTING_ALLOW; case PermissionStatus::DENIED: return CONTENT_SETTING_BLOCK; case PermissionStatus::ASK: return CONTENT_SETTING_ASK; } NOTREACHED(); return CONTENT_SETTING_BLOCK; } // Wrap a callback taking a PermissionStatus to pass it as a callback taking a // ContentSetting. void ContentSettingToPermissionStatusCallbackWrapper( const base::Callback& callback, ContentSetting setting) { callback.Run(ContentSettingToPermissionStatus(setting)); } // Returns whether the permission has a constant PermissionStatus value (i.e. // always approved or always denied). bool IsConstantPermission(PermissionType type) { switch (type) { case PermissionType::MIDI: return true; default: return false; } } void PermissionRequestResponseCallbackWrapper( const base::Callback& callback, const std::vector& vector) { DCHECK_EQ(vector.size(), 1ul); callback.Run(vector[0]); } // Function used for handling permission types which do not change their // value i.e. they are always approved or always denied etc. // CONTENT_SETTING_DEFAULT is returned if the permission needs further handling. // This function should only be called when IsConstantPermission has returned // true for the PermissionType. ContentSetting GetContentSettingForConstantPermission(PermissionType type) { DCHECK(IsConstantPermission(type)); switch (type) { case PermissionType::MIDI: return CONTENT_SETTING_ALLOW; default: return CONTENT_SETTING_DEFAULT; } } PermissionStatus GetPermissionStatusForConstantPermission(PermissionType type) { return ContentSettingToPermissionStatus( GetContentSettingForConstantPermission(type)); } } // anonymous namespace class CefPermissionManager::PendingRequest { public: PendingRequest(content::RenderFrameHost* render_frame_host, const std::vector permissions, const base::Callback&)>& callback) : render_process_id_(render_frame_host->GetProcess()->GetID()), render_frame_id_(render_frame_host->GetRoutingID()), callback_(callback), permissions_(permissions), results_(permissions.size(), PermissionStatus::DENIED), remaining_results_(permissions.size()) { } void SetPermissionStatus(int permission_id, PermissionStatus status) { DCHECK(!IsComplete()); results_[permission_id] = status; --remaining_results_; } bool IsComplete() const { return remaining_results_ == 0; } int render_process_id() const { return render_process_id_; } int render_frame_id() const { return render_frame_id_; } const base::Callback&)> callback() const { return callback_; } std::vector permissions() const { return permissions_; } std::vector results() const { return results_; } private: int render_process_id_; int render_frame_id_; const base::Callback&)> callback_; std::vector permissions_; std::vector results_; size_t remaining_results_; }; struct CefPermissionManager::Subscription { PermissionType permission; GURL requesting_origin; GURL embedding_origin; base::Callback callback; ContentSetting current_value; }; CefPermissionManager::CefPermissionManager(CefBrowserContext* profile) : profile_(profile), context_(profile), weak_ptr_factory_(this) { } CefPermissionManager::~CefPermissionManager() { if (!subscriptions_.IsEmpty()) profile_->GetHostContentSettingsMap()->RemoveObserver(this); } int CefPermissionManager::RequestPermission( PermissionType permission, content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, bool user_gesture, const base::Callback& callback) { return RequestPermissions( std::vector(1, permission), render_frame_host, requesting_origin, user_gesture, base::Bind(&PermissionRequestResponseCallbackWrapper, callback)); } int CefPermissionManager::RequestPermissions( const std::vector& permissions, content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, bool user_gesture, const base::Callback&)>& callback) { if (permissions.empty()) { callback.Run(std::vector()); return kNoPendingOperation; } content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin(); std::unique_ptr pending_request = base::MakeUnique( render_frame_host, permissions, callback); int request_id = pending_requests_.Add(std::move(pending_request)); const PermissionRequestID request(render_frame_host, request_id); for (size_t i = 0; i < permissions.size(); ++i) { const PermissionType permission = permissions[i]; if (IsConstantPermission(permission) || !context_.SupportsPermission(permission)) { OnPermissionsRequestResponseStatus(request_id, i, GetPermissionStatus(permission, requesting_origin, embedding_origin)); continue; } context_.RequestPermission( permission, web_contents, request, requesting_origin, base::Bind(&ContentSettingToPermissionStatusCallbackWrapper, base::Bind( &CefPermissionManager::OnPermissionsRequestResponseStatus, weak_ptr_factory_.GetWeakPtr(), request_id, i))); } // The request might have been resolved already. if (!pending_requests_.Lookup(request_id)) return kNoPendingOperation; return request_id; } void CefPermissionManager::OnPermissionsRequestResponseStatus( int request_id, int permission_id, PermissionStatus status) { PendingRequest* pending_request = pending_requests_.Lookup(request_id); pending_request->SetPermissionStatus(permission_id, status); if (!pending_request->IsComplete()) return; pending_request->callback().Run(pending_request->results()); pending_requests_.Remove(request_id); } void CefPermissionManager::CancelPermissionRequest(int request_id) { PendingRequest* pending_request = pending_requests_.Lookup(request_id); if (!pending_request) return; content::RenderFrameHost* render_frame_host = content::RenderFrameHost::FromID(pending_request->render_process_id(), pending_request->render_frame_id()); DCHECK(render_frame_host); content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); DCHECK(web_contents); const PermissionRequestID request(pending_request->render_process_id(), pending_request->render_frame_id(), request_id); for (PermissionType permission : pending_request->permissions()) { if (!context_.SupportsPermission(permission)) continue; context_.CancelPermissionRequest(permission, web_contents, request); } pending_requests_.Remove(request_id); } void CefPermissionManager::ResetPermission(PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin) { if (!context_.SupportsPermission(permission)) return; context_.ResetPermission(permission, requesting_origin, embedding_origin); } PermissionStatus CefPermissionManager::GetPermissionStatus( PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin) { if (IsConstantPermission(permission)) return GetPermissionStatusForConstantPermission(permission); if (!context_.SupportsPermission(permission)) return PermissionStatus::DENIED; return ContentSettingToPermissionStatus( context_.GetPermissionStatus(permission, requesting_origin, embedding_origin)); } int CefPermissionManager::SubscribePermissionStatusChange( PermissionType permission, const GURL& requesting_origin, const GURL& embedding_origin, const base::Callback& callback) { if (subscriptions_.IsEmpty()) profile_->GetHostContentSettingsMap()->AddObserver(this); std::unique_ptr subscription = base::MakeUnique(); subscription->permission = permission; subscription->requesting_origin = requesting_origin; subscription->embedding_origin = embedding_origin; subscription->callback = callback; subscription->current_value = PermissionStatusToContentSetting( GetPermissionStatus(permission, subscription->requesting_origin, subscription->embedding_origin)); return subscriptions_.Add(std::move(subscription)); } void CefPermissionManager::UnsubscribePermissionStatusChange( int subscription_id) { // Whether |subscription_id| is known will be checked by the Remove() call. subscriptions_.Remove(subscription_id); if (subscriptions_.IsEmpty()) profile_->GetHostContentSettingsMap()->RemoveObserver(this); } void CefPermissionManager::OnContentSettingChanged( const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern, ContentSettingsType content_type, std::string resource_identifier) { std::list callbacks; for (SubscriptionsMap::iterator iter(&subscriptions_); !iter.IsAtEnd(); iter.Advance()) { Subscription* subscription = iter.GetCurrentValue(); if (permission_util::PermissionTypeToContentSetting( subscription->permission) != content_type) { continue; } if (primary_pattern.IsValid() && !primary_pattern.Matches(subscription->requesting_origin)) continue; if (secondary_pattern.IsValid() && !secondary_pattern.Matches(subscription->embedding_origin)) continue; ContentSetting new_value = PermissionStatusToContentSetting( GetPermissionStatus(subscription->permission, subscription->requesting_origin, subscription->embedding_origin)); if (subscription->current_value == new_value) continue; subscription->current_value = new_value; // Add the callback to |callbacks| which will be run after the loop to // prevent re-entrance issues. callbacks.push_back( base::Bind(subscription->callback, ContentSettingToPermissionStatus(new_value))); } for (const auto& callback : callbacks) callback.Run(); }