| // 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 "android_webview/browser/aw_permission_manager.h" |
| |
| #include <string> |
| |
| #include "android_webview/browser/aw_browser_permission_request_delegate.h" |
| #include "base/callback.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/logging.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 content::PermissionStatus; |
| using content::PermissionType; |
| |
| namespace android_webview { |
| |
| class LastRequestResultCache { |
| public: |
| LastRequestResultCache() = default; |
| |
| void SetResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin, |
| PermissionStatus status) { |
| DCHECK(status == content::PERMISSION_STATUS_GRANTED || |
| status == content::PERMISSION_STATUS_DENIED); |
| |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| DLOG(WARNING) << "Not caching result because of empty origin."; |
| return; |
| } |
| |
| if (!requesting_origin.is_valid()) { |
| NOTREACHED() << requesting_origin.possibly_invalid_spec(); |
| return; |
| } |
| if (!embedding_origin.is_valid()) { |
| NOTREACHED() << embedding_origin.possibly_invalid_spec(); |
| return; |
| } |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| // Other permissions are not cached. |
| return; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| if (key.empty()) { |
| NOTREACHED(); |
| // Never store an empty key because it could inadvertently be used for |
| // another combination. |
| return; |
| } |
| pmi_result_cache_[key] = status; |
| } |
| |
| PermissionStatus GetResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) const { |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| return content::PERMISSION_STATUS_ASK; |
| } |
| |
| DCHECK(requesting_origin.is_valid()) |
| << requesting_origin.possibly_invalid_spec(); |
| DCHECK(embedding_origin.is_valid()) |
| << embedding_origin.possibly_invalid_spec(); |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| NOTREACHED() << "Results are only cached for PROTECTED_MEDIA_IDENTIFIER"; |
| return content::PERMISSION_STATUS_ASK; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| StatusMap::const_iterator it = pmi_result_cache_.find(key); |
| if (it == pmi_result_cache_.end()) { |
| DLOG(WARNING) << "GetResult() called for uncached origins: " << key; |
| return content::PERMISSION_STATUS_ASK; |
| } |
| |
| DCHECK(!key.empty()); |
| return it->second; |
| } |
| |
| void ClearResult(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| // TODO(ddorwin): We should be denying empty origins at a higher level. |
| if (requesting_origin.is_empty() || embedding_origin.is_empty()) { |
| return; |
| } |
| |
| DCHECK(requesting_origin.is_valid()) |
| << requesting_origin.possibly_invalid_spec(); |
| DCHECK(embedding_origin.is_valid()) |
| << embedding_origin.possibly_invalid_spec(); |
| |
| |
| if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| // Other permissions are not cached, so nothing to clear. |
| return; |
| } |
| |
| std::string key = GetCacheKey(requesting_origin, embedding_origin); |
| pmi_result_cache_.erase(key); |
| } |
| |
| private: |
| // Returns a concatenation of the origins to be used as the index. |
| // Returns the empty string if either origin is invalid or empty. |
| static std::string GetCacheKey(const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| const std::string& requesting = requesting_origin.spec(); |
| const std::string& embedding = embedding_origin.spec(); |
| if (requesting.empty() || embedding.empty()) |
| return std::string(); |
| return requesting + "," + embedding; |
| } |
| |
| using StatusMap = base::hash_map<std::string, PermissionStatus>; |
| StatusMap pmi_result_cache_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LastRequestResultCache); |
| }; |
| |
| struct AwPermissionManager::PendingRequest { |
| public: |
| PendingRequest(PermissionType permission, |
| GURL requesting_origin, |
| GURL embedding_origin, |
| content::RenderFrameHost* render_frame_host, |
| const base::Callback<void(PermissionStatus)>& callback) |
| : permission(permission), |
| requesting_origin(requesting_origin), |
| embedding_origin(embedding_origin), |
| render_process_id(render_frame_host->GetProcess()->GetID()), |
| render_frame_id(render_frame_host->GetRoutingID()), |
| callback(callback) { |
| } |
| |
| ~PendingRequest() = default; |
| |
| PermissionType permission; |
| GURL requesting_origin; |
| GURL embedding_origin; |
| int render_process_id; |
| int render_frame_id; |
| base::Callback<void(PermissionStatus)> callback; |
| }; |
| |
| AwPermissionManager::AwPermissionManager() |
| : content::PermissionManager(), |
| result_cache_(new LastRequestResultCache), |
| weak_ptr_factory_(this) { |
| } |
| |
| AwPermissionManager::~AwPermissionManager() { |
| } |
| |
| int AwPermissionManager::RequestPermission( |
| PermissionType permission, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin, |
| bool user_gesture, |
| const base::Callback<void(PermissionStatus)>& callback) { |
| int render_process_id = render_frame_host->GetProcess()->GetID(); |
| int render_frame_id = render_frame_host->GetRoutingID(); |
| AwBrowserPermissionRequestDelegate* delegate = |
| AwBrowserPermissionRequestDelegate::FromID(render_process_id, |
| render_frame_id); |
| if (!delegate) { |
| DVLOG(0) << "Dropping permission request for " |
| << static_cast<int>(permission); |
| callback.Run(content::PERMISSION_STATUS_DENIED); |
| return kNoPendingOperation; |
| } |
| |
| // Do not delegate any requests which are already pending. |
| bool should_delegate_request = true; |
| for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| if (permission == it.GetCurrentValue()->permission) { |
| should_delegate_request = false; |
| break; |
| } |
| } |
| |
| const GURL& embedding_origin = |
| content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetLastCommittedURL().GetOrigin(); |
| |
| int request_id = kNoPendingOperation; |
| switch (permission) { |
| case PermissionType::GEOLOCATION: |
| request_id = pending_requests_.Add(new PendingRequest( |
| permission, requesting_origin, |
| embedding_origin, render_frame_host, |
| callback)); |
| if (should_delegate_request) { |
| delegate->RequestGeolocationPermission( |
| requesting_origin, |
| base::Bind(&OnRequestResponse, |
| weak_ptr_factory_.GetWeakPtr(), request_id, |
| callback)); |
| } |
| break; |
| case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| request_id = pending_requests_.Add(new PendingRequest( |
| permission, requesting_origin, |
| embedding_origin, render_frame_host, |
| callback)); |
| if (should_delegate_request) { |
| delegate->RequestProtectedMediaIdentifierPermission( |
| requesting_origin, |
| base::Bind(&OnRequestResponse, |
| weak_ptr_factory_.GetWeakPtr(), request_id, |
| callback)); |
| } |
| break; |
| case PermissionType::MIDI_SYSEX: |
| request_id = pending_requests_.Add(new PendingRequest( |
| permission, requesting_origin, |
| embedding_origin, render_frame_host, |
| callback)); |
| if (should_delegate_request) { |
| delegate->RequestMIDISysexPermission( |
| requesting_origin, |
| base::Bind(&OnRequestResponse, |
| weak_ptr_factory_.GetWeakPtr(), request_id, |
| callback)); |
| } |
| break; |
| case PermissionType::AUDIO_CAPTURE: |
| case PermissionType::VIDEO_CAPTURE: |
| case PermissionType::NOTIFICATIONS: |
| case PermissionType::PUSH_MESSAGING: |
| case PermissionType::DURABLE_STORAGE: |
| NOTIMPLEMENTED() << "RequestPermission is not implemented for " |
| << static_cast<int>(permission); |
| callback.Run(content::PERMISSION_STATUS_DENIED); |
| break; |
| case PermissionType::MIDI: |
| callback.Run(content::PERMISSION_STATUS_GRANTED); |
| break; |
| case PermissionType::NUM: |
| NOTREACHED() << "PermissionType::NUM was not expected here."; |
| callback.Run(content::PERMISSION_STATUS_DENIED); |
| break; |
| } |
| return request_id; |
| } |
| |
| int AwPermissionManager::RequestPermissions( |
| const std::vector<PermissionType>& permissions, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& requesting_origin, |
| bool user_gesture, |
| const base::Callback<void( |
| const std::vector<PermissionStatus>&)>& callback) { |
| NOTIMPLEMENTED() << "RequestPermissions has not been implemented in WebView"; |
| |
| std::vector<PermissionStatus> result(permissions.size()); |
| const GURL& embedding_origin = |
| content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetLastCommittedURL().GetOrigin(); |
| |
| for (PermissionType type : permissions) { |
| result.push_back(GetPermissionStatus( |
| type, requesting_origin, embedding_origin)); |
| } |
| |
| callback.Run(result); |
| return kNoPendingOperation; |
| } |
| |
| // static |
| void AwPermissionManager::OnRequestResponse( |
| const base::WeakPtr<AwPermissionManager>& manager, |
| int request_id, |
| const base::Callback<void(PermissionStatus)>& callback, |
| bool allowed) { |
| PermissionStatus status = allowed ? content::PERMISSION_STATUS_GRANTED |
| : content::PERMISSION_STATUS_DENIED; |
| if (manager.get()) { |
| PendingRequest* pending_request = |
| manager->pending_requests_.Lookup(request_id); |
| |
| for (PendingRequestsMap::Iterator<PendingRequest> it( |
| &manager->pending_requests_); |
| !it.IsAtEnd(); it.Advance()) { |
| if (pending_request->permission == it.GetCurrentValue()->permission && |
| it.GetCurrentKey() != request_id) { |
| it.GetCurrentValue()->callback.Run(status); |
| manager->pending_requests_.Remove(it.GetCurrentKey()); |
| } |
| } |
| |
| manager->result_cache_->SetResult( |
| pending_request->permission, |
| pending_request->requesting_origin, |
| pending_request->embedding_origin, |
| status); |
| manager->pending_requests_.Remove(request_id); |
| } |
| callback.Run(status); |
| } |
| |
| void AwPermissionManager::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); |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(render_frame_host); |
| DCHECK(web_contents); |
| |
| // The caller is canceling (presumably) the most recent request. Assuming the |
| // request did not complete, the user did not respond to the requset. |
| // Thus, assume we do not know the result. |
| const GURL& embedding_origin = web_contents |
| ->GetLastCommittedURL().GetOrigin(); |
| result_cache_->ClearResult( |
| pending_request->permission, |
| pending_request->requesting_origin, |
| embedding_origin); |
| |
| AwBrowserPermissionRequestDelegate* delegate = |
| AwBrowserPermissionRequestDelegate::FromID( |
| pending_request->render_process_id, |
| pending_request->render_frame_id); |
| if (!delegate) { |
| pending_requests_.Remove(request_id); |
| return; |
| } |
| |
| switch (pending_request->permission) { |
| case PermissionType::GEOLOCATION: |
| delegate->CancelGeolocationPermissionRequests( |
| pending_request->requesting_origin); |
| break; |
| case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| delegate->CancelProtectedMediaIdentifierPermissionRequests( |
| pending_request->requesting_origin); |
| break; |
| case PermissionType::MIDI_SYSEX: |
| delegate->CancelMIDISysexPermissionRequests( |
| pending_request->requesting_origin); |
| break; |
| case PermissionType::NOTIFICATIONS: |
| case PermissionType::PUSH_MESSAGING: |
| case PermissionType::DURABLE_STORAGE: |
| case PermissionType::AUDIO_CAPTURE: |
| case PermissionType::VIDEO_CAPTURE: |
| NOTIMPLEMENTED() << "CancelPermission not implemented for " |
| << static_cast<int>(pending_request->permission); |
| break; |
| case PermissionType::MIDI: |
| // There is nothing to cancel so this is simply ignored. |
| break; |
| case PermissionType::NUM: |
| NOTREACHED() << "PermissionType::NUM was not expected here."; |
| break; |
| } |
| |
| pending_requests_.Remove(request_id); |
| } |
| |
| void AwPermissionManager::ResetPermission(PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| result_cache_->ClearResult(permission, requesting_origin, embedding_origin); |
| } |
| |
| PermissionStatus AwPermissionManager::GetPermissionStatus( |
| PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| // Method is called outside the Permissions API only for this permission. |
| if (permission == PermissionType::PROTECTED_MEDIA_IDENTIFIER) { |
| return result_cache_->GetResult(permission, requesting_origin, |
| embedding_origin); |
| } else if (permission == PermissionType::MIDI) { |
| return content::PERMISSION_STATUS_GRANTED; |
| } |
| |
| return content::PERMISSION_STATUS_DENIED; |
| } |
| |
| void AwPermissionManager::RegisterPermissionUsage( |
| PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin) { |
| } |
| |
| int AwPermissionManager::SubscribePermissionStatusChange( |
| PermissionType permission, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin, |
| const base::Callback<void(PermissionStatus)>& callback) { |
| return kNoPendingOperation; |
| } |
| |
| void AwPermissionManager::UnsubscribePermissionStatusChange( |
| int subscription_id) { |
| } |
| |
| } // namespace android_webview |