// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/proxy_resolution/proxy_config_service_linux.h" #include #include #include #include "base/bind.h" #include "base/check.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/message_loop/message_pump_type.h" #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/task/single_thread_task_runner.h" #include "base/task/thread_pool/thread_pool_instance.h" #include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "net/proxy_resolution/proxy_config.h" #include "net/proxy_resolution/proxy_config_service_common_unittest.h" #include "net/test/test_with_task_environment.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" // TODO(eroman): Convert these to parameterized tests using TEST_P(). namespace net { namespace { // Set of values for all environment variables that we might // query. NULL represents an unset variable. struct EnvVarValues { // The strange capitalization is so that the field matches the // environment variable name exactly. const char* DESKTOP_SESSION; const char* HOME; const char* KDEHOME; const char* KDE_SESSION_VERSION; const char* XDG_CURRENT_DESKTOP; const char* auto_proxy; const char* all_proxy; const char* http_proxy; const char* https_proxy; const char* ftp_proxy; const char* SOCKS_SERVER; const char* SOCKS_VERSION; const char* no_proxy; const char* XDG_CONFIG_DIRS; }; // Undo macro pollution from GDK includes (from message_loop.h). #undef TRUE #undef FALSE // So as to distinguish between an unset boolean variable and // one that is false. enum BoolSettingValue { UNSET = 0, TRUE, FALSE }; // Set of values for all gsettings settings that we might query. struct GSettingsValues { // strings const char* mode; const char* autoconfig_url; const char* http_host; const char* secure_host; const char* ftp_host; const char* socks_host; // integers int http_port; int secure_port; int ftp_port; int socks_port; // booleans BoolSettingValue use_proxy; BoolSettingValue same_proxy; BoolSettingValue use_auth; // string list std::vector ignore_hosts; }; // Mapping from a setting name to the location of the corresponding // value (inside a EnvVarValues or GSettingsValues struct). template struct SettingsTable { typedef std::map map_type; // Gets the value from its location value_type Get(key_type key) { auto it = settings.find(key); // In case there's a typo or the unittest becomes out of sync. CHECK(it != settings.end()) << "key " << key << " not found"; value_type* value_ptr = it->second; return *value_ptr; } map_type settings; }; class MockEnvironment : public base::Environment { public: MockEnvironment() { #define ENTRY(x) table_[#x] = &values.x ENTRY(DESKTOP_SESSION); ENTRY(HOME); ENTRY(KDEHOME); ENTRY(KDE_SESSION_VERSION); ENTRY(XDG_CURRENT_DESKTOP); ENTRY(auto_proxy); ENTRY(all_proxy); ENTRY(http_proxy); ENTRY(https_proxy); ENTRY(ftp_proxy); ENTRY(no_proxy); ENTRY(SOCKS_SERVER); ENTRY(SOCKS_VERSION); ENTRY(XDG_CONFIG_DIRS); #undef ENTRY Reset(); } // Zeroes all environment values. void Reset() { EnvVarValues zero_values = {nullptr}; values = zero_values; } // Begin base::Environment implementation. bool GetVar(base::StringPiece variable_name, std::string* result) override { auto it = table_.find(variable_name); if (it == table_.end() || !*it->second) return false; // Note that the variable may be defined but empty. *result = *(it->second); return true; } bool SetVar(base::StringPiece variable_name, const std::string& new_value) override { ADD_FAILURE(); return false; } bool UnSetVar(base::StringPiece variable_name) override { ADD_FAILURE(); return false; } // End base::Environment implementation. // Intentionally public, for convenience when setting up a test. EnvVarValues values; private: std::map table_; }; class MockSettingGetter : public ProxyConfigServiceLinux::SettingGetter { public: typedef ProxyConfigServiceLinux::SettingGetter SettingGetter; MockSettingGetter() { #define ENTRY(key, field) \ strings_table.settings[SettingGetter::key] = &values.field ENTRY(PROXY_MODE, mode); ENTRY(PROXY_AUTOCONF_URL, autoconfig_url); ENTRY(PROXY_HTTP_HOST, http_host); ENTRY(PROXY_HTTPS_HOST, secure_host); ENTRY(PROXY_FTP_HOST, ftp_host); ENTRY(PROXY_SOCKS_HOST, socks_host); #undef ENTRY #define ENTRY(key, field) \ ints_table.settings[SettingGetter::key] = &values.field ENTRY(PROXY_HTTP_PORT, http_port); ENTRY(PROXY_HTTPS_PORT, secure_port); ENTRY(PROXY_FTP_PORT, ftp_port); ENTRY(PROXY_SOCKS_PORT, socks_port); #undef ENTRY #define ENTRY(key, field) \ bools_table.settings[SettingGetter::key] = &values.field ENTRY(PROXY_USE_HTTP_PROXY, use_proxy); ENTRY(PROXY_USE_SAME_PROXY, same_proxy); ENTRY(PROXY_USE_AUTHENTICATION, use_auth); #undef ENTRY string_lists_table.settings[SettingGetter::PROXY_IGNORE_HOSTS] = &values.ignore_hosts; Reset(); } // Zeros all environment values. void Reset() { GSettingsValues zero_values = {nullptr}; values = zero_values; } bool Init(const scoped_refptr& glib_task_runner) override { task_runner_ = glib_task_runner; return true; } void ShutDown() override {} bool SetUpNotifications( ProxyConfigServiceLinux::Delegate* delegate) override { return true; } const scoped_refptr& GetNotificationTaskRunner() override { return task_runner_; } bool GetString(StringSetting key, std::string* result) override { const char* value = strings_table.Get(key); if (value) { *result = value; return true; } return false; } bool GetBool(BoolSetting key, bool* result) override { BoolSettingValue value = bools_table.Get(key); switch (value) { case UNSET: return false; case TRUE: *result = true; break; case FALSE: *result = false; } return true; } bool GetInt(IntSetting key, int* result) override { // We don't bother to distinguish unset keys from 0 values. *result = ints_table.Get(key); return true; } bool GetStringList(StringListSetting key, std::vector* result) override { *result = string_lists_table.Get(key); // We don't bother to distinguish unset keys from empty lists. return !result->empty(); } bool BypassListIsReversed() override { return false; } bool UseSuffixMatching() override { return false; } // Intentionally public, for convenience when setting up a test. GSettingsValues values; private: scoped_refptr task_runner_; SettingsTable strings_table; SettingsTable bools_table; SettingsTable ints_table; SettingsTable> string_lists_table; }; // This helper class runs ProxyConfigServiceLinux::GetLatestProxyConfig() on // the main TaskRunner and synchronously waits for the result. // Some code duplicated from pac_file_fetcher_unittest.cc. class SyncConfigGetter : public ProxyConfigService::Observer { public: explicit SyncConfigGetter( std::unique_ptr config_service) : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED), main_thread_("Main_Thread"), config_service_(std::move(config_service)), matches_pac_url_event_( base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED) { // Start the main IO thread. base::Thread::Options options; options.message_pump_type = base::MessagePumpType::IO; main_thread_.StartWithOptions(std::move(options)); // Make sure the thread started. main_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&SyncConfigGetter::Init, base::Unretained(this))); Wait(); } ~SyncConfigGetter() override { // Clean up the main thread. main_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&SyncConfigGetter::CleanUp, base::Unretained(this))); Wait(); } // Does gsettings setup and initial fetch of the proxy config, // all on the calling thread (meant to be the thread with the // default glib main loop, which is the glib thread). void SetupAndInitialFetch() { config_service_->SetupAndFetchInitialConfig( base::ThreadTaskRunnerHandle::Get(), main_thread_.task_runner(), TRAFFIC_ANNOTATION_FOR_TESTS); } // Synchronously gets the proxy config. ProxyConfigService::ConfigAvailability SyncGetLatestProxyConfig( ProxyConfigWithAnnotation* config) { main_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&SyncConfigGetter::GetLatestConfigOnIOThread, base::Unretained(this))); Wait(); *config = proxy_config_; return get_latest_config_result_; } // Instructs |matches_pac_url_event_| to be signalled once the configuration // changes to |pac_url|. The way to use this function is: // // SetExpectedPacUrl(..); // EXPECT_TRUE(base::WriteFile(...)) // WaitUntilPacUrlMatchesExpectation(); // // The expectation must be set *before* any file-level mutation is done, // otherwise the change may be received before // WaitUntilPacUrlMatchesExpectation(), and subsequently be lost. void SetExpectedPacUrl(const std::string& pac_url) { base::AutoLock lock(lock_); expected_pac_url_ = GURL(pac_url); } // Blocks until the proxy config service has received a configuration // matching the value previously passed to SetExpectedPacUrl(). void WaitUntilPacUrlMatchesExpectation() { matches_pac_url_event_.Wait(); matches_pac_url_event_.Reset(); } private: void OnProxyConfigChanged( const ProxyConfigWithAnnotation& config, ProxyConfigService::ConfigAvailability availability) override { // If the configuration changed to |expected_pac_url_| signal the event. base::AutoLock lock(lock_); if (config.value().has_pac_url() && config.value().pac_url() == expected_pac_url_) { expected_pac_url_ = GURL(); matches_pac_url_event_.Signal(); } } // [Runs on |main_thread_|] void Init() { config_service_->AddObserver(this); event_.Signal(); } // Calls GetLatestProxyConfig, running on |main_thread_| Signals |event_| // on completion. void GetLatestConfigOnIOThread() { get_latest_config_result_ = config_service_->GetLatestProxyConfig(&proxy_config_); event_.Signal(); } // [Runs on |main_thread_|] Signals |event_| on cleanup completion. void CleanUp() { config_service_->RemoveObserver(this); config_service_.reset(); base::RunLoop().RunUntilIdle(); event_.Signal(); } void Wait() { event_.Wait(); event_.Reset(); } base::WaitableEvent event_; base::Thread main_thread_; std::unique_ptr config_service_; // The config obtained by |main_thread_| and read back by the main // thread. ProxyConfigWithAnnotation proxy_config_; // Return value from GetLatestProxyConfig(). ProxyConfigService::ConfigAvailability get_latest_config_result_; // If valid, |expected_pac_url_| is the URL that is being waited for in // the proxy configuration. The URL should only be accessed while |lock_| // is held. Once a configuration arrives for |expected_pac_url_| then the // event |matches_pac_url_event_| will be signalled. base::Lock lock_; GURL expected_pac_url_; base::WaitableEvent matches_pac_url_event_; }; // This test fixture is only really needed for the KDEConfigParser test case, // but all the test cases with the same prefix ("ProxyConfigServiceLinuxTest") // must use the same test fixture class (also "ProxyConfigServiceLinuxTest"). class ProxyConfigServiceLinuxTest : public PlatformTest, public WithTaskEnvironment { protected: void SetUp() override { PlatformTest::SetUp(); // Set up a temporary KDE home directory. std::string prefix("ProxyConfigServiceLinuxTest_user_home"); base::CreateNewTempDirectory(prefix, &user_home_); config_home_ = user_home_.Append(FILE_PATH_LITERAL(".config")); kde_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde")); base::FilePath path = kde_home_.Append(FILE_PATH_LITERAL("share")); path = path.Append(FILE_PATH_LITERAL("config")); base::CreateDirectory(path); kioslaverc_ = path.Append(FILE_PATH_LITERAL("kioslaverc")); // Set up paths but do not create the directory for .kde4. kde4_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde4")); path = kde4_home_.Append(FILE_PATH_LITERAL("share")); kde4_config_ = path.Append(FILE_PATH_LITERAL("config")); kioslaverc4_ = kde4_config_.Append(FILE_PATH_LITERAL("kioslaverc")); // Set up paths for KDE 5 kioslaverc5_ = config_home_.Append(FILE_PATH_LITERAL("kioslaverc")); config_xdg_home_ = user_home_.Append(FILE_PATH_LITERAL("xdg")); config_kdedefaults_home_ = config_home_.Append(FILE_PATH_LITERAL("kdedefaults")); kioslaverc5_xdg_ = config_xdg_home_.Append(FILE_PATH_LITERAL("kioslaverc")); kioslaverc5_kdedefaults_ = config_kdedefaults_home_.Append(FILE_PATH_LITERAL("kioslaverc")); } void TearDown() override { // Delete the temporary KDE home directory. base::DeletePathRecursively(user_home_); PlatformTest::TearDown(); } base::FilePath user_home_; base::FilePath config_home_; base::FilePath config_xdg_home_; base::FilePath config_kdedefaults_home_; // KDE3 paths. base::FilePath kde_home_; base::FilePath kioslaverc_; // KDE4 paths. base::FilePath kde4_home_; base::FilePath kde4_config_; base::FilePath kioslaverc4_; // KDE5 paths. base::FilePath kioslaverc5_; base::FilePath kioslaverc5_xdg_; base::FilePath kioslaverc5_kdedefaults_; }; // Builds an identifier for each test in an array. #define TEST_DESC(desc) base::StringPrintf("at line %d <%s>", __LINE__, desc) TEST_F(ProxyConfigServiceLinuxTest, BasicGSettingsTest) { std::vector empty_ignores; std::vector google_ignores; google_ignores.push_back("*.google.com"); // Inspired from proxy_config_service_win_unittest.cc. // Very neat, but harder to track down failures though. const struct { // Short description to identify the test std::string description; // Input. GSettingsValues values; // Expected outputs (availability and fields of ProxyConfig). ProxyConfigService::ConfigAvailability availability; bool auto_detect; GURL pac_url; ProxyRulesExpectation proxy_rules; } tests[] = { { TEST_DESC("No proxying"), { // Input. "none", // mode "", // autoconfig_url "", "", "", "", // hosts 0, 0, 0, 0, // ports FALSE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Auto detect"), { // Input. "auto", // mode "", // autoconfig_url "", "", "", "", // hosts 0, 0, 0, 0, // ports FALSE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, true, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Valid PAC URL"), { // Input. "auto", // mode "http://wpad/wpad.dat", // autoconfig_url "", "", "", "", // hosts 0, 0, 0, 0, // ports FALSE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL("http://wpad/wpad.dat"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Invalid PAC URL"), { // Input. "auto", // mode "wpad.dat", // autoconfig_url "", "", "", "", // hosts 0, 0, 0, 0, // ports FALSE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Single-host in proxy list"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", "", "", "", // hosts 80, 0, 0, 0, // ports TRUE, TRUE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:80", // single proxy ""), // bypass rules }, { TEST_DESC("use_http_proxy is honored"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", "", "", "", // hosts 80, 0, 0, 0, // ports FALSE, TRUE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("use_http_proxy and use_same_proxy are optional"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", "", "", "", // hosts 80, 0, 0, 0, // ports UNSET, UNSET, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Single-host, different port"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", "", "", "", // hosts 88, 0, 0, 0, // ports TRUE, TRUE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:88", // single proxy ""), // bypass rules }, { TEST_DESC("Per-scheme proxy rules"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", // http_host "www.foo.com", // secure_host "ftp.foo.com", // ftp "", // socks 88, 110, 121, 0, // ports TRUE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:88", // http "www.foo.com:110", // https "ftp.foo.com:121", // ftp ""), // bypass rules }, { TEST_DESC("socks"), { // Input. "manual", // mode "", // autoconfig_url "", "", "", "socks.com", // hosts 0, 0, 0, 99, // ports TRUE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks5://socks.com:99", // single proxy "") // bypass rules }, { TEST_DESC("Per-scheme proxy rules with fallback to SOCKS"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", // http_host "www.foo.com", // secure_host "ftp.foo.com", // ftp "foobar.net", // socks 88, 110, 121, 99, // ports TRUE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithSocks( "www.google.com:88", // http "www.foo.com:110", // https "ftp.foo.com:121", // ftp "socks5://foobar.net:99", // socks ""), // bypass rules }, { TEST_DESC( "Per-scheme proxy rules (just HTTP) with fallback to SOCKS"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", // http_host "", // secure_host "", // ftp "foobar.net", // socks 88, 0, 0, 99, // ports TRUE, FALSE, FALSE, // use, same, auth empty_ignores, // ignore_hosts }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithSocks( "www.google.com:88", // http "", // https "", // ftp "socks5://foobar.net:99", // socks ""), // bypass rules }, { TEST_DESC("Bypass *.google.com"), { // Input. "manual", // mode "", // autoconfig_url "www.google.com", "", "", "", // hosts 80, 0, 0, 0, // ports TRUE, TRUE, FALSE, // use, same, auth google_ignores, // ignore_hosts }, ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:80", // single proxy "*.google.com"), // bypass rules }, }; for (size_t i = 0; i < std::size(tests); ++i) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); auto env = std::make_unique(); auto setting_getter = std::make_unique(); auto* setting_getter_ptr = setting_getter.get(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), std::move(setting_getter), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; setting_getter_ptr->values = tests[i].values; sync_config_getter.SetupAndInitialFetch(); ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config); EXPECT_EQ(tests[i].availability, availability); if (availability == ProxyConfigService::CONFIG_VALID) { EXPECT_EQ(tests[i].auto_detect, config.value().auto_detect()); EXPECT_EQ(tests[i].pac_url, config.value().pac_url()); EXPECT_TRUE(tests[i].proxy_rules.Matches(config.value().proxy_rules())); } } } TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest) { // Inspired from proxy_config_service_win_unittest.cc. const struct { // Short description to identify the test std::string description; // Input. EnvVarValues values; // Expected outputs (availability and fields of ProxyConfig). ProxyConfigService::ConfigAvailability availability; bool auto_detect; GURL pac_url; ProxyRulesExpectation proxy_rules; } tests[] = { { TEST_DESC("No proxying"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy nullptr, // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS "*", // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Auto detect"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP "", // auto_proxy nullptr, // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, true, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Valid PAC URL"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP "http://wpad/wpad.dat", // auto_proxy nullptr, // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL("http://wpad/wpad.dat"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Invalid PAC URL"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP "wpad.dat", // auto_proxy nullptr, // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Single-host in proxy list"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "www.google.com", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:80", // single proxy ""), // bypass rules }, { TEST_DESC("Single-host, different port"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "www.google.com:99", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:99", // single ""), // bypass rules }, { TEST_DESC("Tolerate a scheme"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "http://www.google.com:99", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single("www.google.com:99", // single proxy ""), // bypass rules }, { TEST_DESC("Per-scheme proxy rules"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy nullptr, // all_proxy "www.google.com:80", "www.foo.com:110", "ftp.foo.com:121", // per-proto nullptr, nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "www.foo.com:110", // https "ftp.foo.com:121", // ftp ""), // bypass rules }, { TEST_DESC("socks"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies "socks.com:888", nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks5://socks.com:888", // single proxy ""), // bypass rules }, { TEST_DESC("socks4"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies "socks.com:888", "4", // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks4://socks.com:888", // single proxy ""), // bypass rules }, { TEST_DESC("socks default port"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "", // all_proxy nullptr, nullptr, nullptr, // per-proto proxies "socks.com", nullptr, // SOCKS nullptr, // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks5://socks.com:1080", // single proxy ""), // bypass rules }, { TEST_DESC("bypass"), { // Input. nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy "www.google.com", // all_proxy nullptr, nullptr, nullptr, // per-proto nullptr, nullptr, // SOCKS ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8", // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "www.google.com:80", "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"), }, }; for (size_t i = 0; i < std::size(tests); ++i) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); auto env = std::make_unique(); env->values = tests[i].values; auto setting_getter = std::make_unique(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), std::move(setting_getter), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config); EXPECT_EQ(tests[i].availability, availability); if (availability == ProxyConfigService::CONFIG_VALID) { EXPECT_EQ(tests[i].auto_detect, config.value().auto_detect()); EXPECT_EQ(tests[i].pac_url, config.value().pac_url()); EXPECT_TRUE(tests[i].proxy_rules.Matches(config.value().proxy_rules())); } } } TEST_F(ProxyConfigServiceLinuxTest, GSettingsNotification) { auto env = std::make_unique(); auto setting_getter = std::make_unique(); auto* setting_getter_ptr = setting_getter.get(); auto service = std::make_unique( std::move(env), std::move(setting_getter), TRAFFIC_ANNOTATION_FOR_TESTS); auto* service_ptr = service.get(); SyncConfigGetter sync_config_getter(std::move(service)); ProxyConfigWithAnnotation config; // Start with no proxy. setting_getter_ptr->values.mode = "none"; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_FALSE(config.value().auto_detect()); // Now set to auto-detect. setting_getter_ptr->values.mode = "auto"; // Simulate setting change notification callback. service_ptr->OnCheckProxyConfigSettings(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().auto_detect()); // Simulate two settings changes, where PROXY_MODE is missing. This will make // the settings be interpreted as DIRECT. // // Trigering the check a *second* time is a regression test for // https://crbug.com/848237, where a comparison is done between two nullopts. for (size_t i = 0; i < 2; ++i) { setting_getter_ptr->values.mode = nullptr; service_ptr->OnCheckProxyConfigSettings(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_FALSE(config.value().auto_detect()); EXPECT_TRUE(config.value().proxy_rules().empty()); } } TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser) { // One of the tests below needs a worst-case long line prefix. We build it // programmatically so that it will always be the right size. std::string long_line; size_t limit = ProxyConfigServiceLinux::SettingGetter::BUFFER_SIZE - 1; for (size_t i = 0; i < limit; ++i) long_line += "-"; // Inspired from proxy_config_service_win_unittest.cc. const struct { // Short description to identify the test std::string description; // Input. std::string kioslaverc; EnvVarValues env_values; // Expected outputs (availability and fields of ProxyConfig). ProxyConfigService::ConfigAvailability availability; bool auto_detect; GURL pac_url; ProxyRulesExpectation proxy_rules; } tests[] = { { TEST_DESC("No proxying"), // Input. "[Proxy Settings]\nProxyType=0\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Invalid proxy type (ProxyType=-3)"), // Input. "[Proxy Settings]\nProxyType=-3\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Invalid proxy type (ProxyType=AB-)"), // Input. "[Proxy Settings]\nProxyType=AB-\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Auto detect"), // Input. "[Proxy Settings]\nProxyType=3\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, true, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Valid PAC URL"), // Input. "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://wpad/wpad.dat\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL("http://wpad/wpad.dat"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Valid PAC file without file://"), // Input. "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=/wpad/wpad.dat\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL("file:///wpad/wpad.dat"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Per-scheme proxy rules"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "httpsProxy=www.foo.com\nftpProxy=ftp.foo.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "www.foo.com:80", // https "ftp.foo.com:80", // http ""), // bypass rules }, { TEST_DESC("Only HTTP proxy specified"), // Input. "[Proxy Settings]\nProxyType=1\n" "httpProxy=www.google.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Only HTTP proxy specified, different port"), // Input. "[Proxy Settings]\nProxyType=1\n" "httpProxy=www.google.com:88\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:88", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC( "Only HTTP proxy specified, different port, space-delimited"), // Input. "[Proxy Settings]\nProxyType=1\n" "httpProxy=www.google.com 88\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:88", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Bypass *.google.com"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Bypass *.google.com and *.kde.org"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com,.kde.org\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme( "www.google.com:80", // http "", // https "", // ftp "*.google.com,*.kde.org"), // bypass rules }, { TEST_DESC("Correctly parse bypass list with ReversedException=true"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=true\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithBypassReversed( "www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Correctly parse bypass list with ReversedException=false"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=false\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Correctly parse bypass list with ReversedException=1"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=1\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithBypassReversed( "www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Overflow: ReversedException=18446744073709551617"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=18446744073709551617\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Not a number: ReversedException=noitpecxE"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=noitpecxE\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("socks"), // Input. "[Proxy Settings]\nProxyType=1\nsocksProxy=socks.com 888\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks5://socks.com:888", // single proxy ""), // bypass rules }, { TEST_DESC("socks4"), // Input. "[Proxy Settings]\nProxyType=1\nsocksProxy=socks4://socks.com 888\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Single( "socks4://socks.com:888", // single proxy ""), // bypass rules }, { TEST_DESC("Treat all hostname patterns as wildcard patterns"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=google.com,kde.org,\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme( "www.google.com:80", // http "", // https "", // ftp "*google.com,*kde.org,"), // bypass rules }, { TEST_DESC("Allow trailing whitespace after boolean value"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "NoProxyFor=.google.com\nReversedException=true \n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithBypassReversed( "www.google.com:80", // http "", // https "", // ftp "*.google.com"), // bypass rules }, { TEST_DESC("Ignore settings outside [Proxy Settings]"), // Input. "httpsProxy=www.foo.com\n[Proxy Settings]\nProxyType=1\n" "httpProxy=www.google.com\n[Other Section]\nftpProxy=ftp.foo.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Handle CRLF line endings"), // Input. "[Proxy Settings]\r\nProxyType=1\r\nhttpProxy=www.google.com\r\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Handle blank lines and mixed line endings"), // Input. "[Proxy Settings]\r\n\nProxyType=1\n\r\nhttpProxy=www.google.com\n\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Handle localized settings"), // Input. "[Proxy Settings]\nProxyType[$e]=1\nhttpProxy[$e]=www.google.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""), // bypass rules }, { TEST_DESC("Ignore malformed localized settings"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n" "httpsProxy$e]=www.foo.com\nftpProxy=ftp.foo.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "ftp.foo.com:80", // ftp ""), // bypass rules }, { TEST_DESC("Handle strange whitespace"), // Input. "[Proxy Settings]\nProxyType [$e] =2\n" " Proxy Config Script = http:// foo\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL("http:// foo"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Ignore all of a line which is too long"), // Input. std::string("[Proxy Settings]\nProxyType=1\nftpProxy=ftp.foo.com\n") + long_line + "httpsProxy=www.foo.com\nhttpProxy=www.google.com\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "ftp.foo.com:80", // ftp ""), // bypass rules }, { TEST_DESC("Indirect Proxy - no env vars set"), // Input. "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n" "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n", {}, // env_values // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC("Indirect Proxy - with env vars set"), // Input. "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n" "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n" "socksProxy=SOCKS_SERVER\n", { // env_values nullptr, // DESKTOP_SESSION nullptr, // HOME nullptr, // KDEHOME nullptr, // KDE_SESSION_VERSION nullptr, // XDG_CURRENT_DESKTOP nullptr, // auto_proxy nullptr, // all_proxy "www.normal.com", // http_proxy "www.secure.com", // https_proxy "ftp.foo.com", // ftp_proxy "socks.comfy.com:1234", nullptr, // SOCKS ".google.com, .kde.org", // no_proxy }, // Expected result. ProxyConfigService::CONFIG_VALID, false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithSocks( "www.normal.com:80", // http "www.secure.com:80", // https "ftp.foo.com:80", // ftp "socks5://socks.comfy.com:1234", // socks "*.google.com,*.kde.org"), // bypass rules }, }; for (size_t i = 0; i < std::size(tests); ++i) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); auto env = std::make_unique(); env->values = tests[i].env_values; // Force the KDE getter to be used and tell it where the test is. env->values.DESKTOP_SESSION = "kde4"; env->values.KDEHOME = kde_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; // Overwrite the kioslaverc file. base::WriteFile(kioslaverc_, tests[i].kioslaverc.c_str(), tests[i].kioslaverc.length()); sync_config_getter.SetupAndInitialFetch(); ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config); EXPECT_EQ(tests[i].availability, availability); if (availability == ProxyConfigService::CONFIG_VALID) { EXPECT_EQ(tests[i].auto_detect, config.value().auto_detect()); EXPECT_EQ(tests[i].pac_url, config.value().pac_url()); EXPECT_TRUE(tests[i].proxy_rules.Matches(config.value().proxy_rules())); } } } TEST_F(ProxyConfigServiceLinuxTest, KDEHomePicker) { // Auto detect proxy settings. std::string slaverc3 = "[Proxy Settings]\nProxyType=3\n"; // Valid PAC URL. std::string slaverc4 = "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://wpad/wpad.dat\n"; GURL slaverc4_pac_url("http://wpad/wpad.dat"); // Basic HTTP proxy setting. std::string slaverc5 = "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com 80\n"; ProxyRulesExpectation slaverc5_rules = ProxyRulesExpectation::PerScheme("www.google.com:80", // http "", // https "", // ftp ""); // bypass rules // Overwrite the .kde kioslaverc file. base::WriteFile(kioslaverc_, slaverc3.c_str(), slaverc3.length()); // If .kde4 exists it will mess up the first test. It should not, as // we created the directory for $HOME in the test setup. CHECK(!base::DirectoryExists(kde4_home_)); { SCOPED_TRACE("KDE4, no .kde4 directory, verify fallback"); auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde4"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().auto_detect()); EXPECT_EQ(GURL(), config.value().pac_url()); } // Now create .kde4 and put a kioslaverc in the config directory. // Note that its timestamp will be at least as new as the .kde one. base::CreateDirectory(kde4_config_); base::WriteFile(kioslaverc4_, slaverc4.c_str(), slaverc4.length()); CHECK(base::PathExists(kioslaverc4_)); { SCOPED_TRACE("KDE4, .kde4 directory present, use it"); auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde4"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_FALSE(config.value().auto_detect()); EXPECT_EQ(slaverc4_pac_url, config.value().pac_url()); } { SCOPED_TRACE("KDE3, .kde4 directory present, ignore it"); auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().auto_detect()); EXPECT_EQ(GURL(), config.value().pac_url()); } { SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde"); auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde4"; env->values.HOME = user_home_.value().c_str(); env->values.KDEHOME = kde_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().auto_detect()); EXPECT_EQ(GURL(), config.value().pac_url()); } // Finally, make the .kde4 config directory older than the .kde directory // and make sure we then use .kde instead of .kde4 since it's newer. base::TouchFile(kde4_config_, base::Time(), base::Time()); { SCOPED_TRACE("KDE4, very old .kde4 directory present, use .kde"); auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde4"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().auto_detect()); EXPECT_EQ(GURL(), config.value().pac_url()); } // For KDE 5 create ${HOME}/.config and put a kioslaverc in the directory. base::CreateDirectory(config_home_); base::WriteFile(kioslaverc5_, slaverc5.c_str(), slaverc5.length()); CHECK(base::PathExists(kioslaverc5_)); { SCOPED_TRACE("KDE5, .kde and .kde4 present, use .config"); auto env = std::make_unique(); env->values.XDG_CURRENT_DESKTOP = "KDE"; env->values.KDE_SESSION_VERSION = "5"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_FALSE(config.value().auto_detect()); EXPECT_TRUE(slaverc5_rules.Matches(config.value().proxy_rules())); } } // Tests that the KDE proxy config service watches for file and directory // changes. TEST_F(ProxyConfigServiceLinuxTest, KDEFileChanged) { // Set up the initial .kde kioslaverc file. EXPECT_TRUE( base::WriteFile(kioslaverc_, "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://version1/wpad.dat\n")); // Initialize the config service using kioslaverc. auto env = std::make_unique(); env->values.DESKTOP_SESSION = "kde4"; env->values.HOME = user_home_.value().c_str(); SyncConfigGetter sync_config_getter(std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; sync_config_getter.SetupAndInitialFetch(); EXPECT_EQ(ProxyConfigService::CONFIG_VALID, sync_config_getter.SyncGetLatestProxyConfig(&config)); EXPECT_TRUE(config.value().has_pac_url()); EXPECT_EQ(GURL("http://version1/wpad.dat"), config.value().pac_url()); //----------------------------------------------------- // Change the kioslaverc file by overwriting it. Verify that the change was // observed. sync_config_getter.SetExpectedPacUrl("http://version2/wpad.dat"); // Initialization posts a task to start watching kioslaverc file. Ensure that // registration has happened before modifying it or the file change won't be // observed. base::ThreadPoolInstance::Get()->FlushForTesting(); EXPECT_TRUE( base::WriteFile(kioslaverc_, "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://version2/wpad.dat\n")); // Wait for change to be noticed. sync_config_getter.WaitUntilPacUrlMatchesExpectation(); //----------------------------------------------------- // Change the kioslaverc file by renaming it. If only the file's inode // were being watched (rather than directory) this will not result in // an observable change. Note that KDE when re-writing proxy settings does // so by renaming a new file, so the inode will change. sync_config_getter.SetExpectedPacUrl("http://version3/wpad.dat"); // Create a new file, and rename it into place. EXPECT_TRUE( base::WriteFile(kioslaverc_.AddExtension("new"), "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://version3/wpad.dat\n")); base::Move(kioslaverc_, kioslaverc_.AddExtension("old")); base::Move(kioslaverc_.AddExtension("new"), kioslaverc_); // Wait for change to be noticed. sync_config_getter.WaitUntilPacUrlMatchesExpectation(); //----------------------------------------------------- // Change the kioslaverc file once more by ovewriting it. This is really // just another test to make sure things still work after the directory // change was observed (this final test probably isn't very useful). sync_config_getter.SetExpectedPacUrl("http://version4/wpad.dat"); EXPECT_TRUE( base::WriteFile(kioslaverc_, "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://version4/wpad.dat\n")); // Wait for change to be noticed. sync_config_getter.WaitUntilPacUrlMatchesExpectation(); //----------------------------------------------------- // TODO(eroman): Add a test where kioslaverc is deleted next. Currently this // doesn't trigger any notifications, but it probably should. } TEST_F(ProxyConfigServiceLinuxTest, KDEMultipleKioslaverc) { std::string xdg_config_dirs = config_kdedefaults_home_.value(); xdg_config_dirs += ':'; xdg_config_dirs += config_xdg_home_.value(); const struct { // Short description to identify the test std::string description; // Input. std::string kioslaverc; base::FilePath kioslaverc_path; bool auto_detect; GURL pac_url; ProxyRulesExpectation proxy_rules; } tests[] = { { TEST_DESC("Use xdg/kioslaverc"), // Input. "[Proxy Settings]\nProxyType=3\n" "Proxy Config Script=http://wpad/wpad.dat\n" "httpsProxy=www.foo.com\n", kioslaverc5_xdg_, // kioslaverc path true, // auto_detect GURL(), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC(".config/kdedefaults/kioslaverc overrides xdg/kioslaverc"), // Input. "[Proxy Settings]\nProxyType=2\n" "NoProxyFor=.google.com,.kde.org\n", kioslaverc5_kdedefaults_, // kioslaverc path false, // auto_detect GURL("http://wpad/wpad.dat"), // pac_url ProxyRulesExpectation::Empty(), }, { TEST_DESC(".config/kioslaverc overrides others"), // Input. "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com 80\n" "ReversedException=true\n", kioslaverc5_, // kioslaverc path false, // auto_detect GURL(), // pac_url ProxyRulesExpectation::PerSchemeWithBypassReversed( "www.google.com:80", // http "www.foo.com:80", // https "", // ftp "*.google.com,*.kde.org"), // bypass rules, }, }; // Create directories for all configs base::CreateDirectory(config_home_); base::CreateDirectory(config_xdg_home_); base::CreateDirectory(config_kdedefaults_home_); for (size_t i = 0; i < std::size(tests); ++i) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); auto env = std::make_unique(); env->values.XDG_CURRENT_DESKTOP = "KDE"; env->values.KDE_SESSION_VERSION = "5"; env->values.HOME = user_home_.value().c_str(); env->values.XDG_CONFIG_DIRS = xdg_config_dirs.c_str(); SyncConfigGetter sync_config_getter( std::make_unique( std::move(env), TRAFFIC_ANNOTATION_FOR_TESTS)); ProxyConfigWithAnnotation config; // Write the kioslaverc file to specified location. base::WriteFile(tests[i].kioslaverc_path, tests[i].kioslaverc); CHECK(base::PathExists(tests[i].kioslaverc_path)); sync_config_getter.SetupAndInitialFetch(); ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config); EXPECT_EQ(availability, ProxyConfigService::CONFIG_VALID); if (availability == ProxyConfigService::CONFIG_VALID) { EXPECT_EQ(tests[i].auto_detect, config.value().auto_detect()); EXPECT_EQ(tests[i].pac_url, config.value().pac_url()); EXPECT_TRUE(tests[i].proxy_rules.Matches(config.value().proxy_rules())); } } } } // namespace } // namespace net