diff options
Diffstat (limited to 'chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc')
-rw-r--r-- | chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc | 1912 |
1 files changed, 1912 insertions, 0 deletions
diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc new file mode 100644 index 00000000000..7aec908af44 --- /dev/null +++ b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc @@ -0,0 +1,1912 @@ +// 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 <map> +#include <string> +#include <vector> + +#include "base/bind.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/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.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/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/proxy_resolution/proxy_config.h" +#include "net/proxy_resolution/proxy_config_service_common_unittest.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; +}; + +// 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<std::string> ignore_hosts; +}; + +// Mapping from a setting name to the location of the corresponding +// value (inside a EnvVarValues or GSettingsValues struct). +template <typename key_type, typename value_type> +struct SettingsTable { + typedef std::map<key_type, value_type*> 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); +#undef ENTRY + Reset(); + } + + // Zeroes all environment values. + void Reset() { + EnvVarValues zero_values = {0}; + 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<base::StringPiece, const char**> 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 = {0}; + values = zero_values; + } + + bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& 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<base::SequencedTaskRunner>& GetNotificationTaskRunner() + override { + return task_runner_; + } + + ProxyConfigSource GetConfigSource() override { + return PROXY_CONFIG_SOURCE_TEST; + } + + 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<std::string>* 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 MatchHostsUsingSuffixMatching() override { return false; } + + // Intentionally public, for convenience when setting up a test. + GSettingsValues values; + + private: + scoped_refptr<base::SequencedTaskRunner> task_runner_; + SettingsTable<StringSetting, const char*> strings_table; + SettingsTable<BoolSetting, BoolSettingValue> bools_table; + SettingsTable<IntSetting, int> ints_table; + SettingsTable<StringListSetting, std::vector<std::string>> string_lists_table; +}; + +// This helper class runs ProxyConfigServiceLinux::GetLatestProxyConfig() on +// the main TaskRunner and synchronously waits for the result. +// Some code duplicated from proxy_script_fetcher_unittest.cc. +class SyncConfigGetter : public ProxyConfigService::Observer { + public: + // Takes ownership of |config_service|. + explicit SyncConfigGetter(ProxyConfigServiceLinux* config_service) + : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + main_thread_("Main_Thread"), + config_service_(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_loop_type = base::MessageLoop::TYPE_IO; + main_thread_.StartWithOptions(options); + + // Make sure the thread started. + main_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&SyncConfigGetter::Init, base::Unretained(this))); + Wait(); + } + + ~SyncConfigGetter() override { + // Clean up the main thread. + main_thread_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&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()); + } + // Synchronously gets the proxy config. + ProxyConfigService::ConfigAvailability SyncGetLatestProxyConfig( + ProxyConfig* config) { + main_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&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(..); + // 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 ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) override { + // If the configuration changed to |expected_pac_url_| signal the event. + base::AutoLock lock(lock_); + if (config.has_pac_url() && config.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); + delete config_service_; + base::RunLoop().RunUntilIdle(); + event_.Signal(); + } + + void Wait() { + event_.Wait(); + event_.Reset(); + } + + base::WaitableEvent event_; + base::Thread main_thread_; + + ProxyConfigServiceLinux* config_service_; + + // The config obtained by |main_thread_| and read back by the main + // thread. + ProxyConfig 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 { + 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")); + } + + void TearDown() override { + // Delete the temporary KDE home directory. + base::DeleteFile(user_home_, true); + PlatformTest::TearDown(); + } + + base::FilePath user_home_; + base::FilePath config_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_; +}; + +// 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<std::string> empty_ignores; + + std::vector<std::string> 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 < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + MockSettingGetter* setting_getter = new MockSettingGetter; + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env), setting_getter)); + ProxyConfig config; + setting_getter->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.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.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 < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values = tests[i].values; + MockSettingGetter* setting_getter = new MockSettingGetter; + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env), setting_getter)); + ProxyConfig 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.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules())); + } + } +} + +TEST_F(ProxyConfigServiceLinuxTest, GSettingsNotification) { + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + MockSettingGetter* setting_getter = new MockSettingGetter; + ProxyConfigServiceLinux* service = + new ProxyConfigServiceLinux(std::move(env), setting_getter); + SyncConfigGetter sync_config_getter(service); + ProxyConfig config; + + // Start with no proxy. + setting_getter->values.mode = "none"; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + + // Now set to auto-detect. + setting_getter->values.mode = "auto"; + // Simulate setting change notification callback. + service->OnCheckProxyConfigSettings(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); +} + +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,<local>\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,<local>"), // 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", + { + // 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 + nullptr, nullptr, // SOCKS + ".google.com, .kde.org", // no_proxy + }, + + // Expected result. + ProxyConfigService::CONFIG_VALID, + false, // auto_detect + GURL(), // pac_url + ProxyRulesExpectation::PerScheme( + "www.normal.com:80", // http + "www.secure.com:80", // https + "ftp.foo.com:80", // ftp + "*.google.com,*.kde.org"), // bypass rules + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i, + tests[i].description.c_str())); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + 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( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig 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.auto_detect()); + EXPECT_EQ(tests[i].pac_url, config.pac_url()); + EXPECT_TRUE(tests[i].proxy_rules.Matches(config.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"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.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"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + EXPECT_EQ(slaverc4_pac_url, config.pac_url()); + } + + { + SCOPED_TRACE("KDE3, .kde4 directory present, ignore it"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.pac_url()); + } + + { + SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + 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( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.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"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.auto_detect()); + EXPECT_EQ(GURL(), config.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"); + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.XDG_CURRENT_DESKTOP = "KDE"; + env->values.KDE_SESSION_VERSION = "5"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_FALSE(config.auto_detect()); + EXPECT_TRUE(slaverc5_rules.Matches(config.proxy_rules())); + } +} + +void WriteFile(const base::FilePath& path, base::StringPiece data) { + EXPECT_TRUE(base::WriteFile(path, data.data(), data.size())); +} + +// Tests that the KDE proxy config service watches for file and directory +// changes. +TEST_F(ProxyConfigServiceLinuxTest, KDEFileChanged) { + // Set up the initial .kde kioslaverc file. + WriteFile(kioslaverc_, + "[Proxy Settings]\nProxyType=2\n" + "Proxy Config Script=http://version1/wpad.dat\n"); + + // Initialize the config service using kioslaverc. + std::unique_ptr<MockEnvironment> env(new MockEnvironment); + env->values.DESKTOP_SESSION = "kde4"; + env->values.HOME = user_home_.value().c_str(); + SyncConfigGetter sync_config_getter( + new ProxyConfigServiceLinux(std::move(env))); + ProxyConfig config; + sync_config_getter.SetupAndInitialFetch(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, + sync_config_getter.SyncGetLatestProxyConfig(&config)); + EXPECT_TRUE(config.has_pac_url()); + EXPECT_EQ(GURL("http://version1/wpad.dat"), config.pac_url()); + + //----------------------------------------------------- + + // Change the kioslaverc file by overwriting it. Verify that the change was + // observed. + sync_config_getter.SetExpectedPacUrl("http://version2/wpad.dat"); + + 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. + 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"); + + 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. +} + +} // namespace + +} // namespace net |