// 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 "content/browser/mojo_binder_policy_applier.h"

#include <string_view>

#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "content/public/browser/mojo_binder_policy_map.h"
#include "mojo/public/cpp/bindings/message.h"

namespace {

// TODO(crbug.com/40196368): It is not sustainable to maintain a list.
// An ideal solution should:
// 1. Show a pre-submit warning if a frame-scoped interface is specified with
//    kDefer but declares synchronous methods.
// 2. When an interface that can make sync IPC is registered with BinderMap,
//    change its policy to kCancel by default.
// 3. Bind these receivers to a generic implementation, and terminate the
//    execution context if it receives a synchronous message.
// Stores the list of interface names that declare sync methods.
constexpr auto kSyncMethodInterfaces = base::MakeFixedFlatSet<std::string_view>(
    {"blink.mojom.NotificationService"});

}  // namespace

namespace content {

MojoBinderPolicyApplier::MojoBinderPolicyApplier(
    const MojoBinderPolicyMapImpl* policy_map,
    base::OnceCallback<void(const std::string& interface_name)> cancel_callback)
    : policy_map_(*policy_map), cancel_callback_(std::move(cancel_callback)) {}

MojoBinderPolicyApplier::~MojoBinderPolicyApplier() = default;

// static
std::unique_ptr<MojoBinderPolicyApplier>
MojoBinderPolicyApplier::CreateForSameOriginPrerendering(
    base::OnceCallback<void(const std::string& interface_name)>
        cancel_callback) {
  return std::make_unique<MojoBinderPolicyApplier>(
      MojoBinderPolicyMapImpl::GetInstanceForSameOriginPrerendering(),
      std::move(cancel_callback));
}

// static
std::unique_ptr<MojoBinderPolicyApplier>
MojoBinderPolicyApplier::CreateForPreview(
    base::OnceCallback<void(const std::string& interface_name)>
        cancel_callback) {
  return std::make_unique<MojoBinderPolicyApplier>(
      MojoBinderPolicyMapImpl::GetInstanceForPreview(),
      std::move(cancel_callback));
}

void MojoBinderPolicyApplier::ApplyPolicyToNonAssociatedBinder(
    const std::string& interface_name,
    base::OnceClosure binder_callback) {
  if (mode_ == Mode::kGrantAll) {
    std::move(binder_callback).Run();
    return;
  }
  const MojoBinderNonAssociatedPolicy policy =
      GetNonAssociatedMojoBinderPolicy(interface_name);

  // Run in the kPrepareToGrantAll mode before the renderer sends back a
  // DidCommitActivation. In this mode, MojoBinderPolicyApplier loosens
  // policies, but still defers binders to ensure that the renderer does not
  // receive unexpected messages before CommitActivation arrives.
  if (mode_ == Mode::kPrepareToGrantAll) {
    switch (policy) {
      case MojoBinderNonAssociatedPolicy::kGrant:
      // Grant these two kinds of interfaces because:
      // - kCancel and kUnexpected interfaces may have sync methods, so grant
      // them to avoid deadlocks.
      // - Renderer might request these interfaces during the prerenderingchange
      // event, because from the page's point of view it is no longer
      // prerendering.
      case MojoBinderNonAssociatedPolicy::kCancel:
      case MojoBinderNonAssociatedPolicy::kUnexpected:
        std::move(binder_callback).Run();
        break;
      case MojoBinderNonAssociatedPolicy::kDefer:
        if (base::Contains(kSyncMethodInterfaces, interface_name)) {
          std::move(binder_callback).Run();
        } else {
          deferred_binders_.push_back(std::move(binder_callback));
        }
        break;
    }
    return;
  }

  DCHECK_EQ(mode_, Mode::kEnforce);
  switch (policy) {
    case MojoBinderNonAssociatedPolicy::kGrant:
      std::move(binder_callback).Run();
      break;
    case MojoBinderNonAssociatedPolicy::kCancel:
      if (cancel_callback_) {
        std::move(cancel_callback_).Run(interface_name);
      }
      break;
    case MojoBinderNonAssociatedPolicy::kDefer:
      if (base::Contains(kSyncMethodInterfaces, interface_name)) {
        deferred_sync_binders_.push_back(std::move(binder_callback));
      } else {
        deferred_binders_.push_back(std::move(binder_callback));
      }
      break;
    case MojoBinderNonAssociatedPolicy::kUnexpected:
      mojo::ReportBadMessage("MBPA_BAD_INTERFACE: " + interface_name);
      if (cancel_callback_) {
        std::move(cancel_callback_).Run(interface_name);
      }
      break;
  }
}

bool MojoBinderPolicyApplier::ApplyPolicyToAssociatedBinder(
    const std::string& interface_name) {
  MojoBinderAssociatedPolicy policy = MojoBinderAssociatedPolicy::kCancel;
  switch (mode_) {
    // Always allow binders to run.
    case Mode::kGrantAll:
    case Mode::kPrepareToGrantAll:
      return true;
    case Mode::kEnforce:
      policy = policy_map_->GetAssociatedMojoBinderPolicy(
          interface_name, MojoBinderAssociatedPolicy::kCancel);
      if (policy != MojoBinderAssociatedPolicy::kGrant) {
        if (cancel_callback_)
          std::move(cancel_callback_).Run(interface_name);
        return false;
      }
  }
  return true;
}

void MojoBinderPolicyApplier::PrepareToGrantAll() {
  DCHECK_EQ(mode_, Mode::kEnforce);

  // The remote side would think its status has changed after the browser
  // executes this method, so it is safe to send some synchronous method, so the
  // browser side should make the IPC pipeline ready.
  for (auto& deferred_binder : deferred_sync_binders_) {
    std::move(deferred_binder).Run();
  }
  deferred_sync_binders_.clear();

  mode_ = Mode::kPrepareToGrantAll;
}

void MojoBinderPolicyApplier::GrantAll() {
  DCHECK_NE(mode_, Mode::kGrantAll);

  // Check that we are in a Mojo message dispatch, since the deferred binders
  // might call mojo::ReportBadMessage().
  //
  // TODO(crbug.com/40185437): Give the deferred_binders_ a
  // BadMessageCallback and forbid them from using mojo::ReportBadMessage()
  // directly. We are currently in the message stack of one of the PageBroadcast
  // Mojo callbacks handled by RenderViewHost, so if a binder calls
  // mojo::ReportBadMessage() it kills possibly the wrong renderer. Even if we
  // only run the binders associated with the RVH for each message per-RVH,
  // there are still subtle problems with running all these callbacks at once:
  // for example, mojo::GetMessageCallback()/mojo::ReportBadMessage() can only
  // be called once per message dispatch.
  DCHECK(mojo::IsInMessageDispatch());

  mode_ = Mode::kGrantAll;

  // It's safe to iterate over `deferred_binders_` because no more callbacks
  // will be added to it once `grant_all_` is true."
  for (auto& deferred_binder : deferred_binders_)
    std::move(deferred_binder).Run();
  deferred_binders_.clear();
}

void MojoBinderPolicyApplier::DropDeferredBinders() {
  deferred_binders_.clear();
}

MojoBinderNonAssociatedPolicy
MojoBinderPolicyApplier::GetNonAssociatedMojoBinderPolicy(
    const std::string& interface_name) const {
  return policy_map_->GetNonAssociatedMojoBinderPolicy(interface_name,
                                                       default_policy_);
}

}  // namespace content