// Copyright 2025 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/proxy_config/proxy_prefs_utils.h"

#include <string>

#include "components/policy/core/common/mock_policy_service.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/testing_pref_service.h"
#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "net/base/proxy_chain.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/url_constants.h"

namespace proxy_config {

TEST(ProxyPrefsUtilsTest, ProxyOverrideRuleHostFromString) {
  url::SchemeHostPort scheme_host_port =
      ProxyOverrideRuleHostFromString("https://google.com:123");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpsScheme);
  ASSERT_EQ(scheme_host_port.host(), "google.com");
  ASSERT_EQ(scheme_host_port.port(), 123);

  scheme_host_port = ProxyOverrideRuleHostFromString("google.com:123");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpScheme);
  ASSERT_EQ(scheme_host_port.host(), "google.com");
  ASSERT_EQ(scheme_host_port.port(), 123);

  scheme_host_port = ProxyOverrideRuleHostFromString("https://google.com");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpsScheme);
  ASSERT_EQ(scheme_host_port.host(), "google.com");
  ASSERT_EQ(scheme_host_port.port(), 443);

  scheme_host_port = ProxyOverrideRuleHostFromString("google.com");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpScheme);
  ASSERT_EQ(scheme_host_port.host(), "google.com");
  ASSERT_EQ(scheme_host_port.port(), 80);

  scheme_host_port = ProxyOverrideRuleHostFromString("192.168.1.1");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpScheme);
  ASSERT_EQ(scheme_host_port.host(), "192.168.1.1");
  ASSERT_EQ(scheme_host_port.port(), 80);

  scheme_host_port =
      ProxyOverrideRuleHostFromString("https://[3ffe:2a00:100:7031:0:0::1]");
  ASSERT_TRUE(scheme_host_port.IsValid());
  ASSERT_EQ(scheme_host_port.scheme(), url::kHttpsScheme);
  ASSERT_EQ(scheme_host_port.host(), "[3ffe:2a00:100:7031::1]");
  ASSERT_EQ(scheme_host_port.port(), 443);

  ASSERT_FALSE(ProxyOverrideRuleHostFromString("https://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleHostFromString("http://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleHostFromString("://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleHostFromString("123456789").IsValid());
}

TEST(ProxyPrefsUtilsTest, ProxyOverrideRuleProxyFromString) {
  net::ProxyChain proxy_chain =
      ProxyOverrideRuleProxyFromString("https://google.com:123");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_HTTPS);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 123);

  proxy_chain = ProxyOverrideRuleProxyFromString("https://google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_HTTPS);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 443);

  proxy_chain = ProxyOverrideRuleProxyFromString("HTTPS google.com:123");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_HTTPS);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 123);

  proxy_chain = ProxyOverrideRuleProxyFromString("HTTPS google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_HTTPS);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 443);

  proxy_chain = ProxyOverrideRuleProxyFromString("socks4://google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_SOCKS4);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 1080);

  proxy_chain = ProxyOverrideRuleProxyFromString("SOCKS4 google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_SOCKS4);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 1080);

  proxy_chain = ProxyOverrideRuleProxyFromString("socks5://google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_SOCKS5);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 1080);

  proxy_chain = ProxyOverrideRuleProxyFromString("SOCKS5 google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_SOCKS5);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 1080);

  proxy_chain = ProxyOverrideRuleProxyFromString("PROXY google.com");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_EQ(proxy_chain.First().scheme(), net::ProxyServer::SCHEME_HTTP);
  ASSERT_EQ(proxy_chain.First().GetHost(), "google.com");
  ASSERT_EQ(proxy_chain.First().GetPort(), 80);

  proxy_chain = ProxyOverrideRuleProxyFromString("DIRECT");
  ASSERT_TRUE(proxy_chain.IsValid());
  ASSERT_TRUE(proxy_chain.is_direct());

  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("google").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("google.com").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("https://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("http://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("://").IsValid());
  ASSERT_FALSE(ProxyOverrideRuleProxyFromString("123456789").IsValid());
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
namespace {

void SetAffiliation(TestingPrefServiceSimple& prefs,
                    policy::MockPolicyService& policy_service,
                    policy::PolicyMap& policy_map,
                    bool affiliated,
                    int enabled_for_all_users) {
  policy_map.Clear();
  policy_map.SetDeviceAffiliationIds({"device_id"});
  if (affiliated) {
    policy_map.SetUserAffiliationIds({"device_id"});
  } else {
    policy_map.SetUserAffiliationIds({"other_id"});
  }
  EXPECT_CALL(policy_service, GetPolicies(testing::_))
      .WillRepeatedly(testing::ReturnRef(policy_map));
  prefs.SetInteger(prefs::kEnableProxyOverrideRulesForAllUsers,
                   enabled_for_all_users);
}

void VerifyUnaffiliatedRestriction(TestingPrefServiceSimple& prefs,
                                   policy::MockPolicyService& policy_service,
                                   policy::PolicyMap& policy_map,
                                   bool expected_restricted) {
  // Unaffiliated: restricted if `expected_restricted` is true.
  SetAffiliation(prefs, policy_service, policy_map, /*affiliated=*/false,
                 /*enabled_for_all_users=*/0);
  EXPECT_EQ(ProxyOverrideRulesAllowed(&prefs, &policy_service),
            !expected_restricted);

  // Unaffiliated but enabled by policy: always allowed.
  SetAffiliation(prefs, policy_service, policy_map, /*affiliated=*/false,
                 /*enabled_for_all_users=*/1);
  EXPECT_TRUE(ProxyOverrideRulesAllowed(&prefs, &policy_service));

  // Affiliated: always allowed.
  SetAffiliation(prefs, policy_service, policy_map, /*affiliated=*/true,
                 /*enabled_for_all_users=*/0);
  EXPECT_TRUE(ProxyOverrideRulesAllowed(&prefs, &policy_service));
}

}  // namespace

TEST(ProxyPrefsUtilsTest, ProxyOverrideRulesAllowed) {
  TestingPrefServiceSimple prefs;
  PrefProxyConfigTrackerImpl::RegisterProfilePrefs(prefs.registry());
  testing::NiceMock<policy::MockPolicyService> policy_service;
  policy::PolicyMap policy_map;

  // If `kProxyOverrideRules` is not managed by policy or controlled by an
  // extension, it is always allowed.
  SetAffiliation(prefs, policy_service, policy_map, /*affiliated=*/false,
                 /*enabled_for_all_users=*/0);
  EXPECT_TRUE(ProxyOverrideRulesAllowed(&prefs, &policy_service));

  // Scenario 1: `kProxyOverrideRules` is controlled by an extension.
  // Affiliation is checked, but scope is ignored.
  prefs.SetExtensionPref(prefs::kProxyOverrideRules,
                         base::Value(base::Value::Type::LIST));

  prefs.SetInteger(prefs::kProxyOverrideRulesScope,
                   policy::POLICY_SCOPE_MACHINE);
  VerifyUnaffiliatedRestriction(prefs, policy_service, policy_map,
                                /*expected_restricted=*/true);

  prefs.SetInteger(prefs::kProxyOverrideRulesScope, policy::POLICY_SCOPE_USER);
  VerifyUnaffiliatedRestriction(prefs, policy_service, policy_map,
                                /*expected_restricted=*/true);

  // Scenario 2: `kProxyOverrideRules` is managed by policy.
  prefs.SetManagedPref(prefs::kProxyOverrideRules,
                       base::Value(base::Value::Type::LIST));

  // If rules are set at the machine scope, they are always allowed.
  prefs.SetInteger(prefs::kProxyOverrideRulesScope,
                   policy::POLICY_SCOPE_MACHINE);
  VerifyUnaffiliatedRestriction(prefs, policy_service, policy_map,
                                /*expected_restricted=*/false);

  // If rules are set at the user scope, they are restricted for unaffiliated
  // users.
  prefs.SetInteger(prefs::kProxyOverrideRulesScope, policy::POLICY_SCOPE_USER);
  VerifyUnaffiliatedRestriction(prefs, policy_service, policy_map,
                                /*expected_restricted=*/true);
}
#endif

}  // namespace proxy_config
