// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/chrome_content_settings_agent_delegate.h"

#include "build/build_config.h"
#include "pdf/buildflags.h"

// TODO(b/197163596): Remove File Manager constants
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/webui/file_manager/url_constants.h"
#endif
#include "base/containers/contains.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/web/web_local_frame.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/dispatcher.h"
#include "extensions/renderer/renderer_extension_registry.h"
#endif

#if BUILDFLAG(ENABLE_PDF)
#include "components/pdf/common/pdf_util.h"
#include "third_party/blink/public/web/web_frame.h"
#include "url/origin.h"
#endif

ChromeContentSettingsAgentDelegate::ChromeContentSettingsAgentDelegate(
    content::RenderFrame* render_frame)
    : RenderFrameObserver(render_frame),
      RenderFrameObserverTracker<ChromeContentSettingsAgentDelegate>(
          render_frame),
      render_frame_(render_frame) {
  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) {
    auto* parent = ChromeContentSettingsAgentDelegate::Get(main_frame);
    temporarily_allowed_plugins_ = parent->temporarily_allowed_plugins_;
  }
}

ChromeContentSettingsAgentDelegate::~ChromeContentSettingsAgentDelegate() =
    default;

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
void ChromeContentSettingsAgentDelegate::SetExtensionDispatcher(
    extensions::Dispatcher* extension_dispatcher) {
  DCHECK(!extension_dispatcher_)
      << "SetExtensionDispatcher() should only be called once.";
  extension_dispatcher_ = extension_dispatcher;
}
#endif

bool ChromeContentSettingsAgentDelegate::IsPluginTemporarilyAllowed(
    const std::string& identifier) {
  // If the empty string is in here, it means all plugins are allowed.
  // TODO(bauerb): Remove this once we only pass in explicit identifiers.
  return base::Contains(temporarily_allowed_plugins_, identifier) ||
         base::Contains(temporarily_allowed_plugins_, std::string());
}

void ChromeContentSettingsAgentDelegate::AllowPluginTemporarily(
    const std::string& identifier) {
  temporarily_allowed_plugins_.insert(identifier);
}

bool ChromeContentSettingsAgentDelegate::IsFrameAllowlistedForStorageAccess(
    blink::WebFrame* frame) const {
#if BUILDFLAG(ENABLE_PDF)
  // Allow the Chrome PDF Viewer's extension frame to access storage. This is
  // needed when a data: URL navigates to or embeds a PDF. Normally, data: URLs
  // are opaque and shouldn't be able to access storage. However, the Chrome PDF
  // viewer is an internal use case and does not need to adhere to the web spec.

  // The origin should match the PDF extension's origin. A PDF extension frame
  // should always have a parent (the PDF embedder frame).
  if (IsPdfExtensionOrigin(url::Origin(frame->GetSecurityOrigin())) &&
      frame->Parent()) {
    return true;
  }
#endif
  return false;
}

bool ChromeContentSettingsAgentDelegate::IsSchemeAllowlisted(
    const std::string& scheme) {
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  return scheme == extensions::kExtensionScheme
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
         || scheme == extensions::kArkwebExtensionScheme
#endif
      ;
#else
  return false;
#endif
}

bool ChromeContentSettingsAgentDelegate::AllowReadFromClipboard() {
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  extensions::ScriptContext* current_context =
      extension_dispatcher_->script_context_set().GetCurrent();
  if (current_context &&
      current_context->HasAPIPermission(
          extensions::mojom::APIPermissionID::kClipboardRead)) {
    return true;
  }

  if (IsAllowListedSystemWebApp()) {
    return true;
  }
#endif
  return false;
}

bool ChromeContentSettingsAgentDelegate::AllowWriteToClipboard() {
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // All blessed extension pages could historically write to the clipboard, so
  // preserve that for compatibility.
  extensions::ScriptContext* current_context =
      extension_dispatcher_->script_context_set().GetCurrent();
  if (current_context) {
    if (current_context->effective_context_type() ==
            extensions::mojom::ContextType::kPrivilegedExtension &&
        !current_context->IsForServiceWorker()) {
      return true;
    }
    if (current_context->HasAPIPermission(
            extensions::mojom::APIPermissionID::kClipboardWrite)) {
      return true;
    }
  }
#endif
  return false;
}

void ChromeContentSettingsAgentDelegate::DidCommitProvisionalLoad(
    ui::PageTransition transition) {
  if (render_frame()->GetWebFrame()->Parent())
    return;

  temporarily_allowed_plugins_.clear();
}

void ChromeContentSettingsAgentDelegate::OnDestruct() {}

bool ChromeContentSettingsAgentDelegate::IsPlatformApp() {
#if BUILDFLAG(ENABLE_EXTENSIONS)
  blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
  blink::WebSecurityOrigin origin = frame->GetDocument().GetSecurityOrigin();
  const extensions::Extension* extension = GetExtension(origin);
  return extension && extension->is_platform_app();
#else
  return false;
#endif
}

bool ChromeContentSettingsAgentDelegate::IsAllowListedSystemWebApp() {
#if BUILDFLAG(IS_CHROMEOS)
  blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
  blink::WebSecurityOrigin origin = frame->GetDocument().GetSecurityOrigin();
  // TODO(crbug.com/1233395): Migrate Files SWA to Clipboard API and remove this
  // allow-list.
  if (origin.Protocol().Ascii() == ::content::kChromeUIScheme &&
      origin.Host().Utf8() == ::ash::file_manager::kChromeUIFileManagerHost) {
    return true;
  }
#endif
  return false;
}

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
const extensions::Extension* ChromeContentSettingsAgentDelegate::GetExtension(
    const blink::WebSecurityOrigin& origin) const {
  if (origin.Protocol().Ascii() != extensions::kExtensionScheme
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
    && origin.Protocol().Ascii() != extensions::kArkwebExtensionScheme
#endif
)
    return nullptr;

  const std::string extension_id = origin.Host().Utf8().data();
  if (!extension_dispatcher_->IsExtensionActive(extension_id))
    return nullptr;

  return extensions::RendererExtensionRegistry::Get()->GetByID(extension_id);
}
#endif