// Copyright 2021 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/permissions/permission_util.h"

#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "build/build_config.h"
#include "components/content_settings/core/common/content_settings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
#include "url/gurl.h"
#include "url/origin.h"

using blink::mojom::PermissionDescriptorPtr;

namespace content {

#if BUILDFLAG(IS_ANDROID)
namespace {
constexpr const char* kIsFileURLHistogram =
    "Permissions.GetLastCommittedOriginAsURL.IsFileURL";
}
#endif

PermissionOption PermissionUtil::ToPermissionOption(
    blink::mojom::PermissionStatus permission_status) {
  switch (permission_status) {
    case blink::mojom::PermissionStatus::GRANTED:
      return PermissionOption::kAllowed;
    case blink::mojom::PermissionStatus::DENIED:
      return PermissionOption::kDenied;
    case blink::mojom::PermissionStatus::ASK:
      return PermissionOption::kAsk;
  }
  NOTREACHED();
}

blink::mojom::PermissionStatusWithDetailsPtr
PermissionUtil::ToPermissionStatusWithDetails(
    blink::mojom::PermissionName permission_name,
    PermissionResult result) {
  blink::mojom::PermissionStatus status = result.status;
  blink::mojom::PermissionDetailsPtr details;

  if (permission_name == blink::mojom::PermissionName::GEOLOCATION) {
    GeolocationSetting* geolocation_setting =
        result.retrieved_permission_setting
            ? std::get_if<GeolocationSetting>(
                  &result.retrieved_permission_setting.value())
            : nullptr;
    if (status == blink::mojom::PermissionStatus::GRANTED &&
        geolocation_setting) {
      details = blink::mojom::PermissionDetails::NewGeolocationAccuracy(
          geolocation_setting->precise == PermissionOption::kAllowed
              ? blink::mojom::GeolocationAccuracy::kPrecise
              : blink::mojom::GeolocationAccuracy::kApproximate);
    }
  }
  return blink::mojom::PermissionStatusWithDetails::New(status,
                                                        std::move(details));
}

// Due to dependency issues, this method is duplicated from
// components/permissions/permission_util.cc.
GURL PermissionUtil::GetLastCommittedOriginAsURL(
    content::RenderFrameHost* render_frame_host) {
  CHECK(render_frame_host);

  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(render_frame_host);
#if BUILDFLAG(IS_ANDROID)
  // If `allow_universal_access_from_file_urls` flag is enabled, a file:/// can
  // change its url via history.pushState/replaceState to any other url,
  // including about:blank. To avoid user confusion we should always use a
  // visible url, in other words `GetLastCommittedURL`.
  if (web_contents->GetOrCreateWebPreferences()
          .allow_universal_access_from_file_urls &&
      render_frame_host->GetLastCommittedOrigin().GetURL().SchemeIsFile()) {
    base::UmaHistogramBoolean(kIsFileURLHistogram, true);
    return render_frame_host->GetLastCommittedURL().DeprecatedGetOriginAsURL();
  } else {
    base::UmaHistogramBoolean(kIsFileURLHistogram, false);
  }
#endif

  GURL origin = render_frame_host->GetLastCommittedOrigin().GetURL();
  if (origin.is_empty() && render_frame_host->IsInPrimaryMainFrame()) {
    if (!web_contents->GetVisibleURL().is_empty()) {
      origin = web_contents->GetVisibleURL().DeprecatedGetOriginAsURL();
    }
  }
  return origin;
}

bool PermissionUtil::IsDomainOverride(
    const PermissionDescriptorPtr& descriptor) {
  return descriptor->extension &&
         descriptor->extension->is_top_level_storage_access();
}

const url::Origin& PermissionUtil::ExtractDomainOverride(
    const PermissionDescriptorPtr& descriptor) {
  const blink::mojom::TopLevelStorageAccessPermissionDescriptorPtr&
      override_descriptor =
          descriptor->extension->get_top_level_storage_access();
  return override_descriptor->requestedOrigin;
}

bool PermissionUtil::ValidateDomainOverride(
    const std::vector<blink::mojom::PermissionDescriptorPtr>& types,
    RenderFrameHost* rfh,
    const blink::mojom::PermissionDescriptorPtr& descriptor) {
  if (types.size() > 1) {
    // Requests with domain overrides must be requested individually.
    return false;
  }
  if (!rfh || !rfh->IsInPrimaryMainFrame()) {
    // Requests with domain overrides must be requested from a top-level
    // browsing context.
    return false;
  }

  const url::Origin& overridden_origin =
      PermissionUtil::ExtractDomainOverride(descriptor);

  if (rfh->GetLastCommittedOrigin().IsSameOriginWith(overridden_origin)) {
    // In case `overridden_origin` equals
    // `render_frame_host->GetLastCommittedOrigin()` then you should use
    // `GetPermissionStatusForCurrentDocument`.
    return false;
  }

  return true;
}

bool PermissionUtil::IsDevicePermission(
    const blink::mojom::PermissionDescriptorPtr& descriptor) {
  return descriptor->name == blink::mojom::PermissionName::VIDEO_CAPTURE ||
         descriptor->name == blink::mojom::PermissionName::AUDIO_CAPTURE ||
         descriptor->name == blink::mojom::PermissionName::GEOLOCATION ||
         descriptor->name == blink::mojom::PermissionName::AR ||
         descriptor->name == blink::mojom::PermissionName::VR ||
         descriptor->name == blink::mojom::PermissionName::HAND_TRACKING;
}

bool PermissionUtil::IsEmbeddablePermission(
    const blink::mojom::PermissionDescriptorPtr& descriptor) {
  return descriptor->name == blink::mojom::PermissionName::VIDEO_CAPTURE ||
         descriptor->name == blink::mojom::PermissionName::AUDIO_CAPTURE ||
         descriptor->name == blink::mojom::PermissionName::GEOLOCATION ||
         descriptor->name == blink::mojom::PermissionName::WEB_APP_INSTALLATION;
}

}  // namespace content
