/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_antitrackingservice_h
#define mozilla_antitrackingservice_h

#include "nsString.h"
#include "mozilla/ContentBlockingNotifier.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_privacy.h"

#include "nsIUrlClassifierFeature.h"

class nsIChannel;
class nsICookieJarSettings;
class nsIPermission;
class nsIPrincipal;
class nsIURI;
class nsPIDOMWindowInner;
class nsPIDOMWindowOuter;

namespace mozilla {

class OriginAttributes;

namespace dom {
class BrowsingContext;
class ContentParent;
class Document;
}  // namespace dom

class StorageAccessAPIHelper final {
 public:
  enum StorageAccessPromptChoices { eAllow, eAllowAutoGrant };

  // Grant the permission for aOrigin to have access to the first party storage.
  // This method can handle 2 different scenarios:
  // - aParentContext is a 3rd party context, it opens an aOrigin window and the
  //   user interacts with it. We want to grant the permission at the
  //   combination: top-level + aParentWindow + aOrigin.
  //   Ex: example.net loads an iframe tracker.com, which opens a popup
  //   tracker.prg and the user interacts with it. tracker.org is allowed if
  //   loaded by tracker.com when loaded by example.net.
  // - aParentContext is a first party context and a 3rd party resource
  // (probably
  //   becuase of a script) opens a popup and the user interacts with it. We
  //   want to grant the permission for the 3rd party context to have access to
  //   the first party stoage when loaded in aParentWindow.
  //   Ex: example.net import tracker.com/script.js which does opens a popup and
  //   the user interacts with it. tracker.com is allowed when loaded by
  //   example.net.
  typedef MozPromise<int, bool, true> StorageAccessPermissionGrantPromise;
  typedef std::function<RefPtr<StorageAccessPermissionGrantPromise>()>
      PerformPermissionGrant;
  [[nodiscard]] static RefPtr<StorageAccessPermissionGrantPromise>
  AllowAccessFor(
      nsIPrincipal* aPrincipal, dom::BrowsingContext* aParentContext,
      ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason,
      const PerformPermissionGrant& aPerformFinalChecks = nullptr);

  // This function handles tasks that have to be done in the process
  // of the window that we just grant permission for.
  static void OnAllowAccessFor(
      dom::BrowsingContext* aParentContext, const nsACString& aTrackingOrigin,
      uint32_t aCookieBehavior,
      ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason);

  // For IPC only.
  typedef MozPromise<nsresult, bool, true> ParentAccessGrantPromise;
  static RefPtr<ParentAccessGrantPromise> SaveAccessForOriginOnParentProcess(
      nsIPrincipal* aParentPrincipal, nsIPrincipal* aTrackingPrincipal,
      int aAllowMode, bool aFrameOnly,
      uint64_t aExpirationTime =
          StaticPrefs::privacy_restrict3rdpartystorage_expiration());

  static RefPtr<ParentAccessGrantPromise> SaveAccessForOriginOnParentProcess(
      uint64_t aTopLevelWindowId, dom::BrowsingContext* aParentContext,
      nsIPrincipal* aTrackingPrincipal, int aAllowMode, bool aFrameOnly,
      uint64_t aExpirationTime =
          StaticPrefs::privacy_restrict3rdpartystorage_expiration());

  // This function checks if the document has explicit permission either to
  // allow or deny access to cookies. This may be because of the "cookie"
  // permission or because the domain is on the ContentBlockingAllowList
  // e.g. because the user flipped the sheild.
  // This returns:
  //   Some(true) if unpartitioned cookies will be permitted
  //   Some(false) if unpartitioned cookies will be blocked
  //   None if it is not clear from permission alone what to do
  static Maybe<bool> CheckCookiesPermittedDecidesStorageAccessAPI(
      nsICookieJarSettings* aCookieJarSettings,
      nsIPrincipal* aRequestingPrincipal);

  // Calls CheckCookiesPermittedDecidesStorageAccessAPI in the Content Parent
  // using aBrowsingContext's Top's Window Global's CookieJarSettings.
  static RefPtr<MozPromise<Maybe<bool>, nsresult, true>>
  AsyncCheckCookiesPermittedDecidesStorageAccessAPI(
      dom::BrowsingContext* aBrowsingContext,
      nsIPrincipal* aRequestingPrincipal);

  // This function checks if the browser settings give explicit permission
  // either to allow or deny access to cookies. This only checks the
  // cookieBehavior setting. This requires an additional bool to indicate
  // whether or not the context considered is third-party. This returns:
  //   Some(true) if unpartitioned cookies will be permitted
  //   Some(false) if unpartitioned cookies will be blocked
  //   None if it is not clear from settings alone what to do
  static Maybe<bool> CheckBrowserSettingsDecidesStorageAccessAPI(
      nsICookieJarSettings* aCookieJarSettings, bool aThirdParty,
      bool aOnRejectForeignAllowlist, bool aIsOnThirdPartySkipList,
      bool aIsThirdPartyTracker);

  // This function checks if the document's context (like if it is third-party
  // or an iframe) gives an answer of how a the StorageAccessAPI call, that is
  // meant to be called by an embedded third party, should return.
  // This requires an argument that allows some checks to be run only if the
  // caller of this function is performing a request for storage access.
  // This returns:
  //   Some(true) if the calling context has access to cookies if it is not
  //              disallowed by the browser settings and cookie permissions
  //   Some(false) if the calling context should not have access to cookies if
  //               it is not expressly allowed by the browser settings and
  //               cookie permissions
  //   None if the calling context does not determine the document's access to
  //        unpartitioned cookies
  static Maybe<bool> CheckCallingContextDecidesStorageAccessAPI(
      dom::Document* aDocument, bool aRequestingStorageAccess);

  // This function checks if the document's context (like if it is third-party
  // or an iframe) gives an answer of how a the StorageAccessAPI call that is
  // meant to be called in a top-level context, should return.
  // This returns:
  //   Some(true) if the calling context indicates calls to the top-level
  //              API must resolve if it is not
  //              disallowed by the browser settings and cookie permissions
  //   Some(false) if the calling context must reject when calling top level
  //               portions of the API if it is not expressly allowed by the
  //               browser settings and cookie permissions
  //   None if the calling context does not determine the outcome of the
  //        document's use of the top-level portions of the Storage Access API.
  static Maybe<bool> CheckSameSiteCallingContextDecidesStorageAccessAPI(
      dom::Document* aDocument, bool aRequireUserActivation);

  // This function checks if the document has already been granted or denied
  // access to its unpartitioned cookies by the StorageAccessAPI
  // This returns:
  //   Some(true) if the document has been granted access by the Storage Access
  //              API before
  //   Some(false) if the document has been denied access by the Storage Access
  //               API before
  //   None if the document has not been granted or denied access by the Storage
  //        Access API before
  static Maybe<bool> CheckExistingPermissionDecidesStorageAccessAPI(
      dom::Document* aDocument, bool aRequestingStorageAccess);

  // This function performs the asynchronous portion of checking if requests
  // for storage access will be successful or not. This includes calling
  // Document member functions that creating a permission prompt request and
  // trying to perform an "autogrant" if aRequireGrant is true.
  // This will return a promise whose values correspond to those of a
  // ContentBlocking::AllowAccessFor call that ends the function.
  static RefPtr<StorageAccessPermissionGrantPromise>
  RequestStorageAccessAsyncHelper(
      dom::Document* aDocument, nsPIDOMWindowInner* aInnerWindow,
      dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal,
      bool aHasUserInteraction, bool aFrameOnly,
      ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier,
      bool aRequireGrant);

 private:
  friend class dom::ContentParent;
  // This should be running either in the parent process or in the child
  // processes with an in-process browsing context.
  [[nodiscard]] static RefPtr<StorageAccessPermissionGrantPromise>
  CompleteAllowAccessFor(
      dom::BrowsingContext* aParentContext, uint64_t aTopLevelWindowId,
      nsIPrincipal* aTrackingPrincipal, const nsACString& aTrackingOrigin,
      uint32_t aCookieBehavior,
      ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason,
      const PerformPermissionGrant& aPerformFinalChecks = nullptr);

  static void UpdateAllowAccessOnCurrentProcess(
      dom::BrowsingContext* aParentContext, const nsACString& aTrackingOrigin);

  static void UpdateAllowAccessOnParentProcess(
      dom::BrowsingContext* aParentContext, const nsACString& aTrackingOrigin);
};

}  // namespace mozilla

#endif  // mozilla_antitrackingservice_h
