// 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 "components/embedder_support/content_settings_utils.h"

#include "base/feature_list.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/cookie_settings_base.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/features.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace embedder_support {

using StorageType =
    content_settings::mojom::ContentSettingsManager::StorageType;

namespace {

//  If storage partitioning is active, third-party partitioned storage is
//  allowed by default, and access is only blocked due to general third-party
//  cookie blocking (and not due to a user specified pattern) then storage
//  access can be allowed.
bool PartitionedStorageByDefaultAllowed(
    const content_settings::CookieSettingsBase::CookieSettingWithMetadata&
        cookie_settings) {
  return base::FeatureList::IsEnabled(
             net::features::kThirdPartyStoragePartitioning) &&
         base::FeatureList::IsEnabled(
             net::features::kThirdPartyPartitionedStorageAllowedByDefault) &&
         cookie_settings.BlockedByThirdPartyCookieBlocking();
}

bool AllowWorkerStorageAccess(
    StorageType storage_type,
    const GURL& url,
    const std::vector<content::GlobalRenderFrameHostId>& render_frames,
    const content_settings::CookieSettings* cookie_settings,
    const blink::StorageKey& storage_key) {
  // TODO(crbug.com/40247160): Consider whether the following check should
  // somehow determine real CookieSettingOverrides rather than default to none.
  content_settings::CookieSettingsBase::CookieSettingWithMetadata
      cookie_settings_metadata;

  bool allow = cookie_settings->IsFullCookieAccessAllowed(
      url, net::SiteForCookies::FromUrl(url), url::Origin::Create(url),
      net::CookieSettingOverrides(), storage_key.ToCookiePartitionKey(),
      &cookie_settings_metadata);

  if (!allow && PartitionedStorageByDefaultAllowed(cookie_settings_metadata)) {
    allow = true;
  }

  // Allow storage when --test-third-party-cookie-phaseout is used, but ensure
  // that only partitioned storage is available. This developer flag is meant to
  // simulate Chrome's behavior when 3P cookies are turned down to help
  // developers test their site.
  if (!allow && net::cookie_util::IsForceThirdPartyCookieBlockingEnabled()) {
    allow = true;
  }

  for (const auto& it : render_frames) {
    auto* rfh = content::RenderFrameHost::FromID(it);
    if (!rfh) {
      continue;
    }
    content_settings::PageSpecificContentSettings::StorageAccessed(
        storage_type, it, rfh->GetStorageKey(), !allow);
  }

  return allow;
}
}  // namespace

content::AllowServiceWorkerResult AllowServiceWorker(
    const GURL& scope,
    const net::SiteForCookies& site_for_cookies,
    const std::optional<url::Origin>& top_frame_origin,
    const blink::StorageKey& storage_key,
    const content_settings::CookieSettings* cookie_settings,
    const HostContentSettingsMap* settings_map) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // TODO(crbug.com/40847840): Remove this check once we figure out what is
  // wrong.
  DCHECK(settings_map);
  GURL first_party_url = top_frame_origin ? top_frame_origin->GetURL() : GURL();
  // Check if JavaScript is allowed.
  content_settings::SettingInfo info;
  ContentSetting setting = settings_map->GetContentSetting(
      first_party_url, first_party_url, ContentSettingsType::JAVASCRIPT, &info);
  bool allow_javascript = setting == CONTENT_SETTING_ALLOW;

  // We need to manually create a cookie_partition_key without a nonce since
  // nonced contexts (e.g. FencedFrames or Credentialless iFrames) do not have
  // unpartitioned cookie access, BUT may allow Service Workers. This nonceless
  // cookie_partition_key allows this function to return the correct result for
  // such contexts.
  std::optional<const net::CookiePartitionKey> cookie_partition_key =
      net::CookiePartitionKey::FromStorageKeyComponents(
          storage_key.top_level_site(),
          net::CookiePartitionKey::BoolToAncestorChainBit(
              storage_key.IsThirdPartyContext()),
          /*nonce=*/std::nullopt);

  // Check if cookies are allowed. Storage Access API grants and Top-Level
  // Storage Access API grants may only be considered if storage is partitioned
  // (or if Storage Access API is intended to grant access to storage - which is
  // a deviation from the spec, but at least one embedder wants that ability).
  // TODO(crbug.com/40247160): Consider whether the following check should
  // also consider the third-party cookie user bypass override.
  content_settings::CookieSettingsBase::CookieSettingWithMetadata
      cookie_settings_metadata;

  bool allow_cookies = cookie_settings->IsFullCookieAccessAllowed(
      scope, site_for_cookies, top_frame_origin, net::CookieSettingOverrides(),
      cookie_partition_key,

      &cookie_settings_metadata);

  if (!allow_cookies &&
      PartitionedStorageByDefaultAllowed(cookie_settings_metadata)) {
    allow_cookies = true;
  }

  // Allow storage when --test-third-party-cookie-phaseout is used, but ensure
  // that only partitioned storage is available. This developer flag is meant to
  // simulate Chrome's behavior when 3P cookies are turned down to help
  // developers test their site.
  if (!allow_cookies &&
      net::cookie_util::IsForceThirdPartyCookieBlockingEnabled()) {
    allow_cookies = true;
  }

  return content::AllowServiceWorkerResult::FromPolicy(!allow_javascript,
                                                       !allow_cookies);
}

bool AllowSharedWorker(
    const GURL& worker_url,
    const net::SiteForCookies& site_for_cookies,
    const std::optional<url::Origin>& top_frame_origin,
    const std::string& name,
    const blink::StorageKey& storage_key,
    const blink::mojom::SharedWorkerSameSiteCookies same_site_cookies,
    int render_process_id,
    int render_frame_id,
    const content_settings::CookieSettings* cookie_settings) {
  content_settings::CookieSettingsBase::CookieSettingWithMetadata
      cookie_settings_metadata;

  // We need to manually create a cookie_partition_key without a nonce since
  // nonced contexts (e.g. FencedFrames or Credentialless iFrames) do not have
  // unpartitioned cookie access, BUT may allow Shared Workers. This nonceless
  // cookie_partition_key allows this function to return the correct result for
  // such contexts.
  std::optional<const net::CookiePartitionKey> cookie_partition_key =
      net::CookiePartitionKey::FromStorageKeyComponents(
          storage_key.top_level_site(),
          net::CookiePartitionKey::BoolToAncestorChainBit(
              storage_key.IsThirdPartyContext()),
          /*nonce=*/std::nullopt);

  bool allow = cookie_settings->IsFullCookieAccessAllowed(
      worker_url, site_for_cookies, top_frame_origin,
      net::CookieSettingOverrides(), cookie_partition_key,
      &cookie_settings_metadata);

  if (!allow && PartitionedStorageByDefaultAllowed(cookie_settings_metadata)) {
    allow = true;
  }

  // Allow storage when --test-third-party-cookie-phaseout is used, but ensure
  // that only partitioned storage is available. This developer flag is meant to
  // simulate Chrome's behavior when 3P cookies are turned down to help
  // developers test their site.
  if (!allow && net::cookie_util::IsForceThirdPartyCookieBlockingEnabled()) {
    allow = true;
  }

  content_settings::PageSpecificContentSettings::SharedWorkerAccessed(
      render_process_id, render_frame_id, worker_url, name, storage_key,
      same_site_cookies, !allow);
  return allow;
}

bool AllowWorkerFileSystem(
    const GURL& url,
    const std::vector<content::GlobalRenderFrameHostId>& render_frames,
    const content_settings::CookieSettings* cookie_settings,
    const blink::StorageKey& storage_key) {
  return AllowWorkerStorageAccess(StorageType::FILE_SYSTEM, url, render_frames,
                                  cookie_settings, storage_key);
}

bool AllowWorkerIndexedDB(
    const GURL& url,
    const std::vector<content::GlobalRenderFrameHostId>& render_frames,
    const content_settings::CookieSettings* cookie_settings,
    const blink::StorageKey& storage_key) {
  return AllowWorkerStorageAccess(StorageType::INDEXED_DB, url, render_frames,
                                  cookie_settings, storage_key);
}

bool AllowWorkerCacheStorage(
    const GURL& url,
    const std::vector<content::GlobalRenderFrameHostId>& render_frames,
    const content_settings::CookieSettings* cookie_settings,
    const blink::StorageKey& storage_key) {
  return AllowWorkerStorageAccess(StorageType::CACHE, url, render_frames,
                                  cookie_settings, storage_key);
}

bool AllowWorkerWebLocks(
    const GURL& url,
    const content_settings::CookieSettings* cookie_settings,
    const blink::StorageKey& storage_key) {
  return AllowWorkerStorageAccess(StorageType::WEB_LOCKS, url, {},
                                  cookie_settings, storage_key);
}

}  // namespace embedder_support
