// Copyright 2012 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/custom_handlers/protocol_handler_registry.h"

#include <stddef.h>

#include <algorithm>
#include <string_view>
#include <utility>

#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "build/build_config.h"
#include "components/custom_handlers/pref_names.h"
#include "components/custom_handlers/protocol_handler.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/child_process_security_policy.h"
#include "url/url_util.h"

using content::BrowserThread;
using content::ChildProcessSecurityPolicy;

namespace custom_handlers {

namespace {

const ProtocolHandler& LookupHandler(
    const ProtocolHandlerRegistry::ProtocolHandlerMap& handler_map,
    std::string_view scheme) {
  auto p = handler_map.find(scheme);

  if (p != handler_map.end())
    return p->second;

  return ProtocolHandler::EmptyProtocolHandler();
}

GURL TranslateUrl(
    const ProtocolHandlerRegistry::ProtocolHandlerMap& handler_map,
    const GURL& url) {
  const ProtocolHandler& handler = LookupHandler(handler_map, url.scheme());
  if (handler.IsEmpty())
    return GURL();

  GURL translated_url(handler.TranslateUrl(url));
  if (!translated_url.is_valid())
    return GURL();

  return translated_url;
}

}  // namespace

// ProtocolHandlerRegistry -----------------------------------------------------

std::unique_ptr<ProtocolHandlerRegistry> ProtocolHandlerRegistry::Create(
    PrefService* prefs,
    std::unique_ptr<Delegate> delegate,
    bool is_off_the_record) {
  auto registry = std::make_unique<ProtocolHandlerRegistry>(
      prefs, std::move(delegate), is_off_the_record);

  // If installing defaults, they must be installed prior calling
  // InitProtocolSettings.
  registry->InstallPredefinedHandlers();

  registry->InitProtocolSettings();

  return registry;
}

ProtocolHandlerRegistry::ProtocolHandlerRegistry(
    PrefService* prefs,
    std::unique_ptr<Delegate> delegate,
    bool is_off_the_record)
    : prefs_(prefs),
      delegate_(std::move(delegate)),
      enabled_(true),
      is_loading_(false),
      is_loaded_(false),
      is_off_the_record_(is_off_the_record) {}

bool ProtocolHandlerRegistry::IsHandlerAccessible(
    const ProtocolHandler& handler) const {
  return !is_off_the_record_ || handler.is_allowed_in_incognito();
}

bool ProtocolHandlerRegistry::SilentlyHandleRegisterHandlerRequest(
    const ProtocolHandler& handler) {
  if (handler.IsEmpty() || !CanSchemeBeOverridden(handler.protocol()))
    return true;

  if (!enabled() || IsRegistered(handler) || HasIgnoredEquivalent(handler))
    return true;

  if (AttemptReplace(handler))
    return true;

  return false;
}

void ProtocolHandlerRegistry::OnAcceptRegisterProtocolHandler(
    const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // A handler explicitly accepted in an incognito session is usable there.
  // Extension handlers are exempt: their incognito access is governed by the
  // extension's incognito permission toggle, not by where they're accepted.
  ProtocolHandler effective_handler = handler;
  if (is_off_the_record_ && !handler.IsExtensionHandler()) {
    effective_handler.set_is_allowed_in_incognito(true);
  }
  if (!RegisterProtocolHandler(effective_handler, USER)) {
    return;
  }
  SetDefault(effective_handler);
  Save();
  NotifyChanged();
}

void ProtocolHandlerRegistry::OnDenyRegisterProtocolHandler(
    const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  RegisterProtocolHandler(handler, USER);
  Save();
  NotifyChanged();
}

void ProtocolHandlerRegistry::OnIgnoreRegisterProtocolHandler(
    const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  IgnoreProtocolHandler(handler, USER);
  Save();
  NotifyChanged();
}

static bool CanReplaceHandler(const ProtocolHandler& handler1,
                              const ProtocolHandler& handler2) {
  if (!handler1.IsSameOrigin(handler2)) {
    return false;
  }

  // Extension handlers must not override WebAPI or PWAs registrations.
  if (handler1.IsExtensionHandler()) {
    return handler2.IsExtensionHandler();
  }

  return true;
}

bool ProtocolHandlerRegistry::AttemptReplace(const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Refuse destructive removals if the new handler isn't accessible. Only
  // OnAcceptRegisterProtocolHandler auto-promotes the OTR flag; the silent
  // path here must not.
  if (!IsHandlerAccessible(handler)) {
    return false;
  }
  ProtocolHandler old_default = GetHandlerForInternal(handler.protocol());
  bool make_new_handler_default = CanReplaceHandler(handler, old_default);
  ProtocolHandlerList to_replace(GetReplacedHandlers(handler));
  if (to_replace.empty())
    return false;
  for (const auto& replaced_handler : to_replace) {
    RemoveHandler(replaced_handler);
  }
  if (make_new_handler_default) {
    OnAcceptRegisterProtocolHandler(handler);
  } else {
    InsertHandler(handler);
    NotifyChanged();
  }
  return true;
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetReplacedHandlers(
    const ProtocolHandler& handler) const {
  ProtocolHandlerList replaced_handlers;
  const ProtocolHandlerList* handlers = GetHandlerList(handler.protocol());
  if (handlers) {
    std::ranges::copy_if(*handlers, std::back_inserter(replaced_handlers),
                         [&](const ProtocolHandler& old_handler) {
                           return CanReplaceHandler(handler, old_handler);
                         });
  }
  return replaced_handlers;
}

void ProtocolHandlerRegistry::ClearDefault(const std::string& scheme) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // TODO(jfernandez): If we want to use std::string_view as map's key for
  // erasing, we would need to adapt the ProtocolHandlerMap, or just use the
  // iterator got from find(scheme).
  default_handlers_.erase(scheme);
  Save();
  NotifyChanged();
}

bool ProtocolHandlerRegistry::IsDefault(const ProtocolHandler& handler) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return GetHandlerForInternal(handler.protocol()) == handler;
}

void ProtocolHandlerRegistry::InstallPredefinedHandlers() {
  for (const auto& [scheme, handler] : url::GetPredefinedHandlerSchemes()) {
    ProtocolHandler ph =
        ProtocolHandler::CreateProtocolHandler(scheme, GURL(handler));
    ph.set_is_allowed_in_incognito(true);
    AddPredefinedHandler(ph);
  }
}

void ProtocolHandlerRegistry::InitProtocolSettings() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Any further default additions to the table will get rejected from now on.
  is_loaded_ = true;
  is_loading_ = true;

  if (!prefs_) {
    is_loading_ = false;
    return;
  }

  if (prefs_->HasPrefPath(prefs::kCustomHandlersEnabled)) {
    if (prefs_->GetBoolean(prefs::kCustomHandlersEnabled)) {
      Enable();
    } else {
      Disable();
    }
  }

  RegisterProtocolHandlersFromPref(prefs::kPolicyRegisteredProtocolHandlers,
                                   POLICY);
  RegisterProtocolHandlersFromPref(prefs::kRegisteredProtocolHandlers, USER);
  IgnoreProtocolHandlersFromPref(prefs::kPolicyIgnoredProtocolHandlers, POLICY);
  IgnoreProtocolHandlersFromPref(prefs::kIgnoredProtocolHandlers, USER);

  is_loading_ = false;

  // For each default protocol handler, check that we are still registered
  // with the OS as the default application.
  if (delegate_->ShouldRemoveHandlersNotInOS()) {
    for (const auto& [protocol, handler] : default_handlers_) {
      delegate_->CheckDefaultClientWithOS(
          protocol, GetDefaultWebClientCallback(protocol));
    }
  }
}

int ProtocolHandlerRegistry::GetHandlerIndex(std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandler& candidate = GetHandlerForInternal(scheme);
  if (candidate.IsEmpty())
    return -1;
  const ProtocolHandlerList* handlers = GetHandlerList(scheme);
  if (!handlers)
    return -1;

  int i = 0;
  for (const auto& handler : *handlers) {
    if (handler == candidate)
      return i;
    i++;
  }
  return -1;
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetHandlersFor(std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto p = protocol_handlers_.find(scheme);
  if (p == protocol_handlers_.end()) {
    return ProtocolHandlerList();
  }
  return p->second;
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetUserDefinedHandlers(base::Time begin,
                                                base::Time end) const {
  ProtocolHandlerRegistry::ProtocolHandlerList result;
  for (const auto& [protocol, handlers_list] : user_protocol_handlers_) {
    for (const ProtocolHandler& handler : handlers_list) {
      if (std::ranges::contains(predefined_protocol_handlers_, handler))
        continue;
      if (begin <= handler.last_modified() && handler.last_modified() < end)
        result.push_back(handler);
    }
  }
  return result;
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetUserIgnoredHandlers(base::Time begin,
                                                base::Time end) const {
  ProtocolHandlerRegistry::ProtocolHandlerList result;
  for (const ProtocolHandler& handler : user_ignored_protocol_handlers_) {
    if (begin <= handler.last_modified() && handler.last_modified() < end)
      result.push_back(handler);
  }
  return result;
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetExtensionProtocolHandlers(
    std::optional<std::string> extension_id) {
  ProtocolHandlerRegistry::ProtocolHandlerList result;
  for (const auto& [protocol, handlers_list] : user_protocol_handlers_) {
    std::ranges::copy_if(
        handlers_list, std::back_inserter(result),
        [&](const ProtocolHandler& handler) {
          return handler.extension_id() &&
                 (!extension_id || handler.extension_id() == extension_id);
        });
  }
  return result;
}

void ProtocolHandlerRegistry::ClearUserDefinedHandlers(base::Time begin,
                                                       base::Time end,
                                                       bool save) {
  for (const ProtocolHandler& handler : GetUserDefinedHandlers(begin, end))
    RemoveHandler(handler, save);

  for (const ProtocolHandler& handler : GetUserIgnoredHandlers(begin, end))
    RemoveIgnoredHandler(handler, save);
}

ProtocolHandlerRegistry::ProtocolHandlerList
ProtocolHandlerRegistry::GetIgnoredHandlers() {
  return ignored_protocol_handlers_;
}

void ProtocolHandlerRegistry::GetRegisteredProtocols(
    std::vector<std::string>* output) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  for (const auto& [protocol, handlers_list] : protocol_handlers_) {
    if (!handlers_list.empty())
      output->push_back(protocol);
  }
}

bool ProtocolHandlerRegistry::CanSchemeBeOverridden(
    std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandlerList* handlers = GetHandlerList(scheme);
  // If we already have a handler for this scheme, we can add more.
  if (handlers != nullptr && !handlers->empty()) {
    return true;
  }
  // Don't override a scheme if it already has an external handler.
  return !delegate_->IsExternalHandlerRegistered(
      static_cast<std::string>(scheme));
}

bool ProtocolHandlerRegistry::IsRegistered(
    const ProtocolHandler& handler) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandlerList* handlers = GetHandlerList(handler.protocol());
  if (!handlers) {
    return false;
  }
  return std::ranges::contains(*handlers, handler);
}

bool ProtocolHandlerRegistry::IsRegisteredByUser(
    const ProtocolHandler& handler) {
  return HandlerExists(handler, &user_protocol_handlers_);
}

bool ProtocolHandlerRegistry::HasPolicyRegisteredHandler(
    std::string_view scheme) {
  return (policy_protocol_handlers_.find(scheme) !=
          policy_protocol_handlers_.end());
}

bool ProtocolHandlerRegistry::IsIgnored(const ProtocolHandler& handler) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  for (const auto& ignored_handler : ignored_protocol_handlers_) {
    if (ignored_handler == handler) {
      return true;
    }
  }
  return false;
}

bool ProtocolHandlerRegistry::HasRegisteredEquivalent(
    const ProtocolHandler& handler) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandlerList* handlers = GetHandlerList(handler.protocol());
  if (!handlers) {
    return false;
  }
  for (const auto& registered_handler : *handlers) {
    if (handler.IsEquivalent(registered_handler)) {
      return true;
    }
  }
  return false;
}

bool ProtocolHandlerRegistry::HasIgnoredEquivalent(
    const ProtocolHandler& handler) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  for (const auto& ignored_handler : ignored_protocol_handlers_) {
    if (handler.IsEquivalent(ignored_handler)) {
      return true;
    }
  }
  return false;
}

void ProtocolHandlerRegistry::RemoveIgnoredHandler(
    const ProtocolHandler& handler,
    bool save) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  bool should_notify = false;
  if (HandlerExists(handler, ignored_protocol_handlers_) &&
      HandlerExists(handler, user_ignored_protocol_handlers_)) {
    EraseHandler(handler, &user_ignored_protocol_handlers_);
    if (save) {
      Save();
    }
    if (!HandlerExists(handler, policy_ignored_protocol_handlers_)) {
      EraseHandler(handler, &ignored_protocol_handlers_);
      should_notify = true;
    }
  }
  if (should_notify)
    NotifyChanged();
}

bool ProtocolHandlerRegistry::HasDefaultHandler(std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return enabled_ && !GetHandlerForInternal(scheme).IsEmpty();
}

bool ProtocolHandlerRegistry::IsHandledProtocol(std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandler& handler = GetHandlerFor(scheme);
  return !handler.IsEmpty() && handler.is_confirmed();
}

void ProtocolHandlerRegistry::ConfirmProtocolHandler(std::string_view scheme,
                                                     bool save) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ProtocolHandler handler = GetHandlerForInternal(scheme);
  CHECK(handler.IsValid());
  if (handler.is_confirmed()) {
    return;
  }
  RemoveHandler(handler);
  handler.Confirm();
  RegisterProtocolHandler(handler, USER);
  SetDefault(handler);
  if (save) {
    Save();
  }
  NotifyChanged();
}

bool ProtocolHandlerRegistry::IsProtocolHandlerConfirmed(
    std::string_view scheme) const {
  DCHECK(HasDefaultHandler(scheme));
  return GetHandlerForInternal(scheme).is_confirmed();
}

bool ProtocolHandlerRegistry::ProtocolHandlerNeedsConfirmation(
    std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandler& handler = GetHandlerFor(scheme);
  return !handler.IsEmpty() && !handler.is_confirmed();
}

void ProtocolHandlerRegistry::RemoveHandler(const ProtocolHandler& handler,
                                            bool save) {
  if (IsIgnored(handler)) {
    RemoveIgnoredHandler(handler);
    return;
  }

  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ProtocolHandlerList& handlers = protocol_handlers_[handler.protocol()];
  bool erase_success = false;
  if (HandlerExists(handler, handlers) &&
      HandlerExists(handler, &user_protocol_handlers_)) {
    EraseHandler(handler, &user_protocol_handlers_);
    erase_success = true;
    if (!HandlerExists(handler, &policy_protocol_handlers_))
      EraseHandler(handler, &protocol_handlers_);
  }
  auto q = default_handlers_.find(handler.protocol());
  if (erase_success && q != default_handlers_.end() && q->second == handler) {
    default_handlers_.erase(q);
    // Make the new top handler in the list the default.
    if (!handlers.empty()) {
      // NOTE We pass a copy because SetDefault() modifies handlers.
      SetDefault(ProtocolHandler(handlers[0]));
    }
  }

  if (erase_success && !HasDefaultHandler(handler.protocol())) {
    delegate_->DeregisterExternalHandler(handler.protocol());
  }
  if (save) {
    Save();
  }
  if (erase_success)
    NotifyChanged();
}

void ProtocolHandlerRegistry::RemoveDefaultHandler(std::string_view scheme) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ProtocolHandler current_default = GetHandlerForInternal(scheme);
  if (!current_default.IsEmpty())
    RemoveHandler(current_default);
}

const ProtocolHandler& ProtocolHandlerRegistry::GetHandlerForInternal(
    std::string_view scheme) const {
  return LookupHandler(default_handlers_, scheme);
}

const ProtocolHandler& ProtocolHandlerRegistry::GetHandlerFor(
    std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const ProtocolHandler& handler =
      enabled_ ? GetHandlerForInternal(scheme)
               : ProtocolHandler::EmptyProtocolHandler();
  CHECK(handler.IsEmpty() || handler.is_confirmed() ||
        handler.IsExtensionHandler());
  CHECK(handler.IsEmpty() || IsHandlerAccessible(handler))
      << "OTR registry storage invariant violated: insertion guard let a "
         "disallowed handler through.";
  return handler;
}

GURL ProtocolHandlerRegistry::Translate(const GURL& url) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(IsHandledProtocol(url.scheme()));
  return TranslateUrl(default_handlers_, url);
}

void ProtocolHandlerRegistry::Enable() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (enabled_) {
    return;
  }
  enabled_ = true;
  for (const auto& [protocol, handler] : default_handlers_) {
    delegate_->RegisterExternalHandler(protocol);
  }
  Save();
  NotifyChanged();
}

void ProtocolHandlerRegistry::Disable() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!enabled_) {
    return;
  }
  enabled_ = false;

  for (const auto& [protocol, handler] : default_handlers_) {
    delegate_->DeregisterExternalHandler(protocol);
  }
  Save();
  NotifyChanged();
}

void ProtocolHandlerRegistry::Shutdown() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  delegate_.reset(NULL);

  weak_ptr_factory_.InvalidateWeakPtrs();
}

// static
void ProtocolHandlerRegistry::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterListPref(prefs::kRegisteredProtocolHandlers);
  registry->RegisterListPref(prefs::kIgnoredProtocolHandlers);
  registry->RegisterListPref(prefs::kPolicyRegisteredProtocolHandlers);
  registry->RegisterListPref(prefs::kPolicyIgnoredProtocolHandlers);
  registry->RegisterBooleanPref(prefs::kCustomHandlersEnabled, true);
}

ProtocolHandlerRegistry::~ProtocolHandlerRegistry() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void ProtocolHandlerRegistry::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void ProtocolHandlerRegistry::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void ProtocolHandlerRegistry::SetDelegateForTesting(  // IN-TEST
    std::unique_ptr<Delegate> delegate) {             // IN-TEST
  CHECK_IS_TEST();                                    // IN-TEST
  delegate_ = std::move(delegate);                    // IN-TEST
}

void ProtocolHandlerRegistry::PromoteHandler(const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(IsRegistered(handler));
  auto p = protocol_handlers_.find(handler.protocol());
  ProtocolHandlerList& list = p->second;
  list.erase(std::ranges::find(list, handler));
  list.insert(list.begin(), handler);
}

void ProtocolHandlerRegistry::Save() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (is_loading_) {
    return;
  }

  if (!prefs_)
    return;

  base::Value registered_protocol_handlers(EncodeRegisteredHandlers());
  base::Value ignored_protocol_handlers(EncodeIgnoredHandlers());

  prefs_->Set(prefs::kRegisteredProtocolHandlers, registered_protocol_handlers);
  prefs_->Set(prefs::kIgnoredProtocolHandlers, ignored_protocol_handlers);
  prefs_->SetBoolean(prefs::kCustomHandlersEnabled, enabled_);
}

const ProtocolHandlerRegistry::ProtocolHandlerList*
ProtocolHandlerRegistry::GetHandlerList(std::string_view scheme) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto p = protocol_handlers_.find(scheme);
  if (p == protocol_handlers_.end()) {
    return nullptr;
  }
  return &p->second;
}

bool ProtocolHandlerRegistry::ShouldPromoteToDefault(
    const ProtocolHandler& handler) const {
  ProtocolHandlerMap::const_iterator p =
      default_handlers_.find(handler.protocol());
  if (p == default_handlers_.end()) {
    return true;
  }

  // Avoid unnecessary promotions.
  const ProtocolHandler& default_handler = p->second;
  if (handler == default_handler) {
    return false;
  }

  // Extension handlers must not override WebAPI or PWAs registrations.
  if (handler.IsExtensionHandler()) {
    return default_handler.IsExtensionHandler();
  }

  // TODO(crbug.com/40482153): Implement a more sophisticated conflict
  // resolution mechanism to determine whether a handler can be replaced when
  // managing registrations from different sources.
  return true;
}

void ProtocolHandlerRegistry::SetDefault(const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  const std::string& protocol = handler.protocol();
  ProtocolHandlerMap::const_iterator p = default_handlers_.find(protocol);
  // If we're not loading, and we are setting a default for a new protocol,
  // register with the OS.
  if (!is_loading_ && p == default_handlers_.end())
    delegate_->RegisterWithOSAsDefaultClient(
        protocol, GetDefaultWebClientCallback(protocol));
  if (ShouldPromoteToDefault(handler)) {
    default_handlers_.erase(protocol);
    default_handlers_.insert(std::make_pair(protocol, handler));
    PromoteHandler(handler);
  }
}

void ProtocolHandlerRegistry::InsertHandler(const ProtocolHandler& handler) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto p = protocol_handlers_.find(handler.protocol());

  if (p != protocol_handlers_.end()) {
    p->second.push_back(handler);
    return;
  }

  ProtocolHandlerList new_list;
  new_list.push_back(handler);
  protocol_handlers_[handler.protocol()] = new_list;
}

base::ListValue ProtocolHandlerRegistry::EncodeRegisteredHandlers() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  base::ListValue encoded_handlers;
  for (const auto& [protocol, handlers_list] : user_protocol_handlers_) {
    for (const auto& handler : handlers_list) {
      base::DictValue encoded = handler.Encode();
      if (IsDefault(handler)) {
        encoded.Set("default", true);
      }
      encoded_handlers.Append(std::move(encoded));
    }
  }
  return encoded_handlers;
}

base::ListValue ProtocolHandlerRegistry::EncodeIgnoredHandlers() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  base::ListValue encoded_handlers;
  for (const auto& handler : user_ignored_protocol_handlers_) {
    encoded_handlers.Append(handler.Encode());
  }
  return encoded_handlers;
}

void ProtocolHandlerRegistry::NotifyChanged() {
  for (auto& observer : observers_)
    observer.OnProtocolHandlerRegistryChanged();
}

bool ProtocolHandlerRegistry::RegisterProtocolHandler(
    const ProtocolHandler& handler,
    const HandlerSource source) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(CanSchemeBeOverridden(handler.protocol()));
  DCHECK(!handler.IsEmpty());

  // Ignore invalid handlers.
  if (!handler.IsValid())
    return false;

  // Reject extension-privileged handlers that are not associated with an
  // extension. All addition paths funnel through here (including handlers
  // loaded from prefs), so enforcing it once drops orphaned handlers persisted
  // by builds that predate recording the extension id, whose elevated
  // privileges would otherwise survive the registering extension's removal.
  if (!handler.IsAllowedExtensionHandler()) {
    return false;
  }

  // OTR invariant: this registry only stores handlers usable in incognito.
  // All addition paths funnel through here (including those loaded from prefs
  // via the OverlayUserPrefStore), so enforcing the invariant once here lets
  // read methods iterate storage without per-call filtering.
  if (!IsHandlerAccessible(handler)) {
    return false;
  }

  ProtocolHandlerMultiMap& map =
      (source == POLICY) ? policy_protocol_handlers_ : user_protocol_handlers_;
  ProtocolHandlerList& list = map[handler.protocol()];
  if (!HandlerExists(handler, list))
    list.push_back(handler);
  if (IsRegistered(handler)) {
    return true;
  }
  if (enabled_ && !delegate_->IsExternalHandlerRegistered(handler.protocol()))
    delegate_->RegisterExternalHandler(handler.protocol());
  InsertHandler(handler);
  return true;
}

std::vector<const base::DictValue*>
ProtocolHandlerRegistry::GetHandlersFromPref(const char* pref_name) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  std::vector<const base::DictValue*> result;
  if (!prefs_ || !prefs_->HasPrefPath(pref_name)) {
    return result;
  }

  const base::ListValue& handlers = prefs_->GetList(pref_name);

  for (const auto& list_item : handlers) {
    if (const base::DictValue* encoded_handler = list_item.GetIfDict()) {
      if (ProtocolHandler::IsValidDict(*encoded_handler)) {
        result.push_back(encoded_handler);
      }
    }
  }

  return result;
}

void ProtocolHandlerRegistry::RegisterProtocolHandlersFromPref(
    const char* pref_name,
    const HandlerSource source) {
  std::vector<const base::DictValue*> registered_handlers =
      GetHandlersFromPref(pref_name);
  for (const auto* encoded_handler : registered_handlers) {
    ProtocolHandler handler =
        ProtocolHandler::CreateProtocolHandler(*encoded_handler);
    if (!RegisterProtocolHandler(handler, source))
      continue;
    if (encoded_handler->FindBool("default").value_or(false)) {
      SetDefault(handler);
    }
  }
}

void ProtocolHandlerRegistry::IgnoreProtocolHandler(
    const ProtocolHandler& handler,
    const HandlerSource source) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ProtocolHandlerList& list = (source == POLICY)
                                  ? policy_ignored_protocol_handlers_
                                  : user_ignored_protocol_handlers_;
  if (!HandlerExists(handler, list))
    list.push_back(handler);
  if (HandlerExists(handler, ignored_protocol_handlers_))
    return;
  ignored_protocol_handlers_.push_back(handler);
}

void ProtocolHandlerRegistry::IgnoreProtocolHandlersFromPref(
    const char* pref_name,
    const HandlerSource source) {
  std::vector<const base::DictValue*> ignored_handlers =
      GetHandlersFromPref(pref_name);
  for (const auto* encoded_handler : ignored_handlers)
    IgnoreProtocolHandler(
        ProtocolHandler::CreateProtocolHandler(*encoded_handler), source);
}

bool ProtocolHandlerRegistry::HandlerExists(const ProtocolHandler& handler,
                                            ProtocolHandlerMultiMap* map) {
  return HandlerExists(handler, (*map)[handler.protocol()]);
}

bool ProtocolHandlerRegistry::HandlerExists(const ProtocolHandler& handler,
                                            const ProtocolHandlerList& list) {
  return std::ranges::contains(list, handler);
}

void ProtocolHandlerRegistry::EraseHandler(const ProtocolHandler& handler,
                                           ProtocolHandlerMultiMap* map) {
  EraseHandler(handler, &(*map)[handler.protocol()]);
}

void ProtocolHandlerRegistry::EraseHandler(const ProtocolHandler& handler,
                                           ProtocolHandlerList* list) {
  list->erase(std::ranges::find(*list, handler));
}

void ProtocolHandlerRegistry::OnSetAsDefaultProtocolClientFinished(
    const std::string& protocol,
    bool is_default) {
  // Clear if the default protocol client isn't this installation.
  if (!is_default && delegate_->ShouldRemoveHandlersNotInOS())
    ClearDefault(protocol);
}

void ProtocolHandlerRegistry::SetIsLoading(bool is_loading) {
  is_loading_ = is_loading;
}

base::WeakPtr<ProtocolHandlerRegistry> ProtocolHandlerRegistry::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void ProtocolHandlerRegistry::AddPredefinedHandler(
    const ProtocolHandler& handler) {
  DCHECK(!is_loaded_);  // Must be called prior InitProtocolSettings.
  RegisterProtocolHandler(handler, USER);
  SetDefault(handler);
  predefined_protocol_handlers_.push_back(handler);
}

DefaultClientCallback ProtocolHandlerRegistry::GetDefaultWebClientCallback(
    const std::string& protocol) {
  return base::BindOnce(
      &ProtocolHandlerRegistry::OnSetAsDefaultProtocolClientFinished,
      weak_ptr_factory_.GetWeakPtr(), protocol);
}

}  // namespace custom_handlers
