| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/content_settings/renderer/content_settings_agent_impl.h" |
| |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings.mojom.h" |
| #include "components/content_settings/core/common/content_settings_pattern.h" |
| #include "components/content_settings/core/common/content_settings_utils.h" |
| #include "content/public/child/child_thread.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/origin_util.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" |
| #include "third_party/blink/public/platform/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/platform/url_conversion.h" |
| #include "third_party/blink/public/platform/web_security_origin.h" |
| #include "third_party/blink/public/platform/web_url.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_local_frame_client.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_constants.h" |
| |
| using blink::WebDocument; |
| using blink::WebFrame; |
| using blink::WebLocalFrame; |
| using blink::WebSecurityOrigin; |
| using blink::WebString; |
| using blink::WebURL; |
| using blink::WebView; |
| |
| namespace content_settings { |
| namespace { |
| bool IsFrameWithOpaqueOrigin(WebFrame* frame) { |
| // Storage access is keyed off the top origin and the frame's origin. |
| // It will be denied any opaque origins so have this method to return early |
| // instead of making a Sync IPC call. |
| return frame->GetSecurityOrigin().IsOpaque() || |
| frame->Top()->GetSecurityOrigin().IsOpaque(); |
| } |
| |
| } // namespace |
| |
| ContentSettingsAgentImpl::Delegate::~Delegate() = default; |
| |
| bool ContentSettingsAgentImpl::Delegate::IsFrameAllowlistedForStorageAccess( |
| blink::WebFrame* frame) const { |
| return false; |
| } |
| |
| bool ContentSettingsAgentImpl::Delegate::IsSchemeAllowlisted( |
| const std::string& scheme) { |
| return false; |
| } |
| |
| bool ContentSettingsAgentImpl::Delegate::AllowReadFromClipboard() { |
| return false; |
| } |
| |
| bool ContentSettingsAgentImpl::Delegate::AllowWriteToClipboard() { |
| return false; |
| } |
| |
| ContentSettingsAgentImpl::ContentSettingsAgentImpl( |
| content::RenderFrame* render_frame, |
| std::unique_ptr<Delegate> delegate) |
| : content::RenderFrameObserver(render_frame), |
| content::RenderFrameObserverTracker<ContentSettingsAgentImpl>( |
| render_frame), |
| delegate_(std::move(delegate)) { |
| DCHECK(delegate_); |
| ClearBlockedContentSettings(); |
| render_frame->GetWebFrame()->SetContentSettingsClient(this); |
| |
| render_frame->GetAssociatedInterfaceRegistry() |
| ->AddInterface<mojom::ContentSettingsAgent>(base::BindRepeating( |
| &ContentSettingsAgentImpl::OnContentSettingsAgentRequest, |
| base::Unretained(this))); |
| |
| content::RenderFrame* main_frame = render_frame->GetMainRenderFrame(); |
| // TODO(nasko): The main frame is not guaranteed to be in the same process |
| // with this frame with --site-per-process. This code needs to be updated |
| // to handle this case. See https://crbug.com/496670. |
| if (main_frame && main_frame != render_frame) { |
| // Copy all the settings from the main render frame to avoid race conditions |
| // when initializing this data. See https://crbug.com/333308. |
| ContentSettingsAgentImpl* parent = |
| ContentSettingsAgentImpl::Get(main_frame); |
| allow_running_insecure_content_ = parent->allow_running_insecure_content_; |
| } |
| } |
| |
| ContentSettingsAgentImpl::~ContentSettingsAgentImpl() = default; |
| |
| mojom::ContentSettingsManager& |
| ContentSettingsAgentImpl::GetContentSettingsManager() { |
| if (!content_settings_manager_) |
| BindContentSettingsManager(&content_settings_manager_); |
| return *content_settings_manager_; |
| } |
| |
| void ContentSettingsAgentImpl::DidBlockContentType( |
| ContentSettingsType settings_type) { |
| bool newly_blocked = content_blocked_.insert(settings_type).second; |
| if (newly_blocked) |
| GetContentSettingsManager().OnContentBlocked( |
| render_frame()->GetWebFrame()->GetLocalFrameToken(), settings_type); |
| } |
| |
| namespace { |
| template <typename URL> |
| ContentSetting GetContentSettingFromRules( |
| const ContentSettingsForOneType& rules, |
| const URL& secondary_url) { |
| // If there is only one rule, it's the default rule and we don't need to match |
| // the patterns. |
| if (rules.size() == 1) { |
| DCHECK(rules[0].primary_pattern == ContentSettingsPattern::Wildcard()); |
| DCHECK(rules[0].secondary_pattern == ContentSettingsPattern::Wildcard()); |
| return rules[0].GetContentSetting(); |
| } |
| // The primary pattern has already been matched in the browser process (see |
| // PageSpecificContentSettings::WebContentsHandler::ReadyToCommitNavigation), |
| // and the rules received by the renderer are a subset of the existing rules |
| // that have the correct primary pattern. So we only need to check the |
| // secondary pattern below. |
| const GURL& secondary_gurl = secondary_url; |
| for (const auto& rule : rules) { |
| if (rule.secondary_pattern.Matches(secondary_gurl)) { |
| return rule.GetContentSetting(); |
| } |
| } |
| NOTREACHED(); |
| } |
| } // namespace |
| |
| void ContentSettingsAgentImpl::BindContentSettingsManager( |
| mojo::Remote<mojom::ContentSettingsManager>* manager) { |
| DCHECK(!*manager); |
| content::ChildThread::Get()->BindHostReceiver( |
| manager->BindNewPipeAndPassReceiver()); |
| } |
| |
| void ContentSettingsAgentImpl::DidCommitProvisionalLoad( |
| ui::PageTransition transition) { |
| // This entire method will be removed soon. https://crbug.com/40282541. |
| // Clear "block" flags for the new page. This needs to happen before any of |
| // `allowScript()`, `allowScriptFromSource()`, or |
| // `allowPlugins()` is called for the new page so that these functions can |
| // correctly detect that a piece of content flipped from "not blocked" to |
| // "blocked". |
| ClearBlockedContentSettings(); |
| |
| blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| if (frame->Parent()) |
| return; // Not a top-level navigation. |
| |
| #if DCHECK_IS_ON() |
| GURL url = frame->GetDocument().Url(); |
| // If we start failing this DCHECK, please makes sure we don't regress |
| // this bug: http://code.google.com/p/chromium/issues/detail?id=79304 |
| DCHECK(frame->GetDocument().GetSecurityOrigin().ToString() == "null" || |
| !url.SchemeIs(url::kDataScheme)); |
| #endif |
| } |
| |
| void ContentSettingsAgentImpl::OnDestruct() { |
| delete this; |
| } |
| |
| void ContentSettingsAgentImpl::SetAllowRunningInsecureContent() { |
| allow_running_insecure_content_ = true; |
| |
| // Reload if we are the main frame. |
| blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| if (!frame->Parent()) |
| frame->StartReload(blink::WebFrameLoadType::kReload); |
| } |
| |
| void ContentSettingsAgentImpl::SendRendererContentSettingRules( |
| const RendererContentSettingRules& renderer_settings) { |
| content_setting_rules_ = std::make_unique<RendererContentSettingRules>( |
| std::move(renderer_settings)); |
| } |
| |
| void ContentSettingsAgentImpl::OnContentSettingsAgentRequest( |
| mojo::PendingAssociatedReceiver<mojom::ContentSettingsAgent> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| mojom::ContentSettingsManager::StorageType |
| ContentSettingsAgentImpl::ConvertToMojoStorageType(StorageType storage_type) { |
| switch (storage_type) { |
| case StorageType::kIndexedDB: |
| return mojom::ContentSettingsManager::StorageType::INDEXED_DB; |
| case StorageType::kCacheStorage: |
| return mojom::ContentSettingsManager::StorageType::CACHE; |
| case StorageType::kWebLocks: |
| return mojom::ContentSettingsManager::StorageType::WEB_LOCKS; |
| case StorageType::kFileSystem: |
| return mojom::ContentSettingsManager::StorageType::FILE_SYSTEM; |
| case StorageType::kLocalStorage: |
| return mojom::ContentSettingsManager::StorageType::LOCAL_STORAGE; |
| case StorageType::kSessionStorage: |
| return mojom::ContentSettingsManager::StorageType::SESSION_STORAGE; |
| } |
| } |
| |
| void ContentSettingsAgentImpl::AllowStorageAccess( |
| StorageType storage_type, |
| base::OnceCallback<void(bool)> callback) { |
| WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| if (delegate_->IsFrameAllowlistedForStorageAccess(frame)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| if (IsFrameWithOpaqueOrigin(frame)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| StoragePermissionsKey key(url::Origin(frame->GetSecurityOrigin()), |
| storage_type); |
| const auto permissions = cached_storage_permissions_.find(key); |
| if (permissions != cached_storage_permissions_.end()) { |
| std::move(callback).Run(permissions->second); |
| return; |
| } |
| |
| // Passing the `cache_storage_permissions_` ref to the callback is safe here |
| // as the mojo::Remote is owned by `this` and won't invoke the callback if |
| // `this` (and in turn `cache_storage_permissions_`) is destroyed. |
| base::OnceCallback<void(bool)> new_cb = base::BindOnce( |
| [](base::OnceCallback<void(bool)> original_cb, StoragePermissionsKey key, |
| base::flat_map<StoragePermissionsKey, bool>& cache_map, bool result) { |
| cache_map[key] = result; |
| std::move(original_cb).Run(result); |
| }, |
| std::move(callback), key, std::ref(cached_storage_permissions_)); |
| |
| GetContentSettingsManager().AllowStorageAccess( |
| frame->GetLocalFrameToken(), ConvertToMojoStorageType(storage_type), |
| frame->GetSecurityOrigin(), frame->GetDocument().SiteForCookies(), |
| frame->GetDocument().TopFrameOrigin(), std::move(new_cb)); |
| } |
| |
| bool ContentSettingsAgentImpl::AllowStorageAccessSync( |
| StorageType storage_type) { |
| WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| if (delegate_->IsFrameAllowlistedForStorageAccess(frame)) { |
| return true; |
| } |
| |
| if (IsFrameWithOpaqueOrigin(frame)) { |
| return false; |
| } |
| |
| StoragePermissionsKey key(url::Origin(frame->GetSecurityOrigin()), |
| storage_type); |
| const auto permissions = cached_storage_permissions_.find(key); |
| if (permissions != cached_storage_permissions_.end()) |
| return permissions->second; |
| |
| bool result = false; |
| GetContentSettingsManager().AllowStorageAccess( |
| frame->GetLocalFrameToken(), ConvertToMojoStorageType(storage_type), |
| frame->GetSecurityOrigin(), frame->GetDocument().SiteForCookies(), |
| frame->GetDocument().TopFrameOrigin(), &result); |
| cached_storage_permissions_[key] = result; |
| return result; |
| } |
| |
| bool ContentSettingsAgentImpl::AllowReadFromClipboard() { |
| return delegate_->AllowReadFromClipboard(); |
| } |
| |
| bool ContentSettingsAgentImpl::AllowWriteToClipboard() { |
| return delegate_->AllowWriteToClipboard(); |
| } |
| |
| bool ContentSettingsAgentImpl::AllowRunningInsecureContent( |
| bool allowed_per_settings, |
| const blink::WebURL& resource_url) { |
| if (allowed_per_settings || allow_running_insecure_content_) |
| return true; |
| |
| if (content_setting_rules_) { |
| ContentSetting setting = GetContentSettingFromRules( |
| content_setting_rules_->mixed_content_rules, GURL()); |
| if (setting == CONTENT_SETTING_ALLOW) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ContentSettingsAgentImpl::ShouldAutoupgradeMixedContent() { |
| if (content_setting_rules_) { |
| auto setting = GetContentSettingFromRules( |
| content_setting_rules_->mixed_content_rules, GURL()); |
| return setting != CONTENT_SETTING_ALLOW; |
| } |
| return true; |
| } |
| |
| RendererContentSettingRules* |
| ContentSettingsAgentImpl::GetRendererContentSettingRules() { |
| return content_setting_rules_.get(); |
| } |
| |
| void ContentSettingsAgentImpl::SetRendererContentSettingRulesForTest( |
| const RendererContentSettingRules& rules) { |
| content_setting_rules_ = std::make_unique<RendererContentSettingRules>(rules); |
| } |
| |
| void ContentSettingsAgentImpl::DidNotAllowScript() { |
| DidBlockContentType(ContentSettingsType::JAVASCRIPT); |
| } |
| |
| void ContentSettingsAgentImpl::DidNotAllowImage() { |
| DidBlockContentType(ContentSettingsType::IMAGES); |
| } |
| |
| void ContentSettingsAgentImpl::ClearBlockedContentSettings() { |
| content_blocked_.clear(); |
| cached_storage_permissions_.clear(); |
| } |
| |
| } // namespace content_settings |