// Copyright (c) 2011 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 "chrome/browser/chromeos/proxy_config_service_impl.h" #include #include #include #include "base/format_macros.h" #include "base/logging.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "content/browser/browser_thread.h" #include "content/common/json_value_serializer.h" #include "net/proxy/proxy_config_service_common_unittest.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" namespace chromeos { namespace { struct Input { // Fields of chromeos::ProxyConfigServiceImpl::ProxyConfig. ProxyConfigServiceImpl::ProxyConfig::Mode mode; const char* pac_url; const char* single_uri; const char* http_uri; const char* https_uri; const char* ftp_uri; const char* socks_uri; const char* bypass_rules; }; // Builds an identifier for each test in an array. #define TEST_DESC(desc) base::StringPrintf("at line %d <%s>", __LINE__, desc) // Shortcuts to declare enums within chromeos's ProxyConfig. #define MK_MODE(mode) ProxyConfigServiceImpl::ProxyConfig::MODE_##mode #define MK_SRC(src) ProxyConfigServiceImpl::ProxyConfig::SOURCE_##src #define MK_SCHM(scheme) net::ProxyServer::SCHEME_##scheme // Inspired from net/proxy/proxy_config_service_linux_unittest.cc. const struct { // Short description to identify the test std::string description; bool is_valid; bool test_read_write_access; Input input; // Expected outputs from fields of net::ProxyConfig (via IO). bool auto_detect; GURL pac_url; net::ProxyRulesExpectation proxy_rules; } tests[] = { { TEST_DESC("No proxying"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(DIRECT), // mode }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Empty(), // proxy_rules }, { TEST_DESC("Auto detect"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(AUTO_DETECT), // mode }, // Expected result. true, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Empty(), // proxy_rules }, { TEST_DESC("Valid PAC URL"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(PAC_SCRIPT), // mode "http://wpad/wpad.dat", // pac_url }, // Expected result. false, // auto_detect GURL("http://wpad/wpad.dat"), // pac_url net::ProxyRulesExpectation::Empty(), // proxy_rules }, { TEST_DESC("Invalid PAC URL"), false, // is_valid false, // test_read_write_access { // Input. MK_MODE(PAC_SCRIPT), // mode "wpad.dat", // pac_url }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Empty(), // proxy_rules }, { TEST_DESC("Single-host in proxy list"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(SINGLE_PROXY), // mode NULL, // pac_url "www.google.com", // single_uri }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Single( // proxy_rules "www.google.com:80", // single proxy ""), // bypass rules }, { TEST_DESC("Single-host, different port"), true, // is_valid false, // test_read_write_access { // Input. MK_MODE(SINGLE_PROXY), // mode NULL, // pac_url "www.google.com:99", // single_uri }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Single( // proxy_rules "www.google.com:99", // single ""), // bypass rules }, { TEST_DESC("Tolerate a scheme"), true, // is_valid false, // test_read_write_access { // Input. MK_MODE(SINGLE_PROXY), // mode NULL, // pac_url "http://www.google.com:99", // single_uri }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Single( // proxy_rules "www.google.com:99", // single proxy ""), // bypass rules }, { TEST_DESC("Per-scheme proxy rules"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(PROXY_PER_SCHEME), // mode NULL, // pac_url NULL, // single_uri "www.google.com:80", // http_uri "www.foo.com:110", // https_uri "ftp.foo.com:121", // ftp_uri "socks.com:888", // socks_uri }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::PerSchemeWithSocks( // proxy_rules "www.google.com:80", // http "https://www.foo.com:110", // https "ftp.foo.com:121", // ftp "socks5://socks.com:888", // fallback proxy ""), // bypass rules }, { TEST_DESC("Bypass rules"), true, // is_valid true, // test_read_write_access { // Input. MK_MODE(SINGLE_PROXY), // mode NULL, // pac_url "www.google.com", // single_uri NULL, NULL, NULL, NULL, // per-proto ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8", // bypass_rules }, // Expected result. false, // auto_detect GURL(), // pac_url net::ProxyRulesExpectation::Single( // proxy_rules "www.google.com:80", // single proxy "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"), // bypass_rules }, }; // tests } // namespace class ProxyConfigServiceImplTest : public PlatformTest { protected: ProxyConfigServiceImplTest() : ui_thread_(BrowserThread::UI, &message_loop_), io_thread_(BrowserThread::IO, &message_loop_) { } virtual ~ProxyConfigServiceImplTest() { config_service_ = NULL; MessageLoop::current()->RunAllPending(); } void CreateConfigService( const ProxyConfigServiceImpl::ProxyConfig& init_config) { // Instantiate proxy config service with |init_config|. config_service_ = new ProxyConfigServiceImpl(init_config); } void SetAutomaticProxy( ProxyConfigServiceImpl::ProxyConfig::Mode mode, ProxyConfigServiceImpl::ProxyConfig::Source source, const char* pac_url, ProxyConfigServiceImpl::ProxyConfig* config, ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy* automatic_proxy) { config->mode = mode; automatic_proxy->source = source; if (pac_url) automatic_proxy->pac_url = GURL(pac_url); } void SetManualProxy( ProxyConfigServiceImpl::ProxyConfig::Mode mode, ProxyConfigServiceImpl::ProxyConfig::Source source, const char* server_uri, net::ProxyServer::Scheme scheme, ProxyConfigServiceImpl::ProxyConfig* config, ProxyConfigServiceImpl::ProxyConfig::ManualProxy* manual_proxy) { if (!server_uri) return; config->mode = mode; manual_proxy->source = source; manual_proxy->server = net::ProxyServer::FromURI(server_uri, scheme); } void InitConfigWithTestInput( const Input& input, ProxyConfigServiceImpl::ProxyConfig::Source source, ProxyConfigServiceImpl::ProxyConfig* init_config) { switch (input.mode) { case MK_MODE(DIRECT): case MK_MODE(AUTO_DETECT): case MK_MODE(PAC_SCRIPT): SetAutomaticProxy(input.mode, source, input.pac_url, init_config, &init_config->automatic_proxy); return; case MK_MODE(SINGLE_PROXY): SetManualProxy(input.mode, source, input.single_uri, MK_SCHM(HTTP), init_config, &init_config->single_proxy); break; case MK_MODE(PROXY_PER_SCHEME): SetManualProxy(input.mode, source, input.http_uri, MK_SCHM(HTTP), init_config, &init_config->http_proxy); SetManualProxy(input.mode, source, input.https_uri, MK_SCHM(HTTPS), init_config, &init_config->https_proxy); SetManualProxy(input.mode, source, input.ftp_uri, MK_SCHM(HTTP), init_config, &init_config->ftp_proxy); SetManualProxy(input.mode, source, input.socks_uri, MK_SCHM(SOCKS5), init_config, &init_config->socks_proxy); break; } if (input.bypass_rules) { init_config->bypass_rules.ParseFromStringUsingSuffixMatching( input.bypass_rules); } } void TestReadWriteAccessForMode(const Input& input, ProxyConfigServiceImpl::ProxyConfig::Source source) { // Init config from |source|. ProxyConfigServiceImpl::ProxyConfig init_config; InitConfigWithTestInput(input, source, &init_config); CreateConfigService(init_config); ProxyConfigServiceImpl::ProxyConfig config; config_service()->UIGetProxyConfig(&config); // For owner, write access to config should be equal CanBeWrittenByOwner(). // For non-owner, config is never writeable. bool expected_writeable_by_owner = CanBeWrittenByOwner(source); if (config.mode == MK_MODE(PROXY_PER_SCHEME)) { if (input.http_uri) { EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, "http")); EXPECT_FALSE(config.CanBeWrittenByUser(false, "http")); } if (input.https_uri) { EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, "http")); EXPECT_FALSE(config.CanBeWrittenByUser(false, "https")); } if (input.ftp_uri) { EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, "http")); EXPECT_FALSE(config.CanBeWrittenByUser(false, "ftp")); } if (input.socks_uri) { EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, "http")); EXPECT_FALSE(config.CanBeWrittenByUser(false, "socks")); } } else { EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, std::string())); EXPECT_FALSE(config.CanBeWrittenByUser(false, std::string())); } } void TestReadWriteAccessForScheme( ProxyConfigServiceImpl::ProxyConfig::Source source, const char* server_uri, const std::string& scheme) { // Init with manual |scheme| proxy. ProxyConfigServiceImpl::ProxyConfig init_config; ProxyConfigServiceImpl::ProxyConfig::ManualProxy* proxy = init_config.MapSchemeToProxy(scheme); net::ProxyServer::Scheme net_scheme = MK_SCHM(HTTP); if (scheme == "http" || scheme == "ftp") net_scheme = MK_SCHM(HTTP); else if (scheme == "https") net_scheme = MK_SCHM(HTTPS); else if (scheme == "socks") net_scheme = MK_SCHM(SOCKS4); SetManualProxy(MK_MODE(PROXY_PER_SCHEME), source, server_uri, net_scheme, &init_config, proxy); CreateConfigService(init_config); ProxyConfigServiceImpl::ProxyConfig config; config_service()->UIGetProxyConfig(&config); // For owner, write access to config should be equal CanBeWrittenByOwner(). // For non-owner, config is never writeable. bool expected_writeable_by_owner = CanBeWrittenByOwner(source); EXPECT_EQ(expected_writeable_by_owner, config.CanBeWrittenByUser(true, scheme)); EXPECT_FALSE(config.CanBeWrittenByUser(false, scheme)); const char* all_schemes[] = { "http", "https", "ftp", "socks", }; // Rest of protos should be writeable by owner, but not writeable by // non-owner. for (size_t i = 0; i < ARRAYSIZE_UNSAFE(all_schemes); ++i) { if (scheme == all_schemes[i]) continue; EXPECT_TRUE(config.CanBeWrittenByUser(true, all_schemes[i])); EXPECT_FALSE(config.CanBeWrittenByUser(false, all_schemes[i])); } } // Synchronously gets the latest proxy config. bool SyncGetLatestProxyConfig(net::ProxyConfig* config) { // Let message loop process all messages. MessageLoop::current()->RunAllPending(); // Calls IOGetProxyConfig (which is called from // ProxyConfigService::GetLatestProxyConfig), running on faked IO thread. return config_service_->IOGetProxyConfig(config); } ProxyConfigServiceImpl* config_service() const { return config_service_; } private: bool CanBeWrittenByOwner( ProxyConfigServiceImpl::ProxyConfig::Source source) const { return source == MK_SRC(POLICY) ? false : true; } ScopedStubCrosEnabler stub_cros_enabler_; MessageLoop message_loop_; BrowserThread ui_thread_; BrowserThread io_thread_; scoped_refptr config_service_; }; TEST_F(ProxyConfigServiceImplTest, ChromeosProxyConfigToNetProxyConfig) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); ProxyConfigServiceImpl::ProxyConfig init_config; InitConfigWithTestInput(tests[i].input, MK_SRC(OWNER), &init_config); CreateConfigService(init_config); net::ProxyConfig config; SyncGetLatestProxyConfig(&config); 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(ProxyConfigServiceImplTest, ModifyFromUI) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); // Init with direct. ProxyConfigServiceImpl::ProxyConfig init_config; SetAutomaticProxy(MK_MODE(DIRECT), MK_SRC(OWNER), NULL, &init_config, &init_config.automatic_proxy); CreateConfigService(init_config); // Set config to tests[i].input via UI. net::ProxyBypassRules bypass_rules; const Input& input = tests[i].input; switch (input.mode) { case MK_MODE(DIRECT) : config_service()->UISetProxyConfigToDirect(); break; case MK_MODE(AUTO_DETECT) : config_service()->UISetProxyConfigToAutoDetect(); break; case MK_MODE(PAC_SCRIPT) : config_service()->UISetProxyConfigToPACScript(GURL(input.pac_url)); break; case MK_MODE(SINGLE_PROXY) : config_service()->UISetProxyConfigToSingleProxy( net::ProxyServer::FromURI(input.single_uri, MK_SCHM(HTTP))); if (input.bypass_rules) { bypass_rules.ParseFromStringUsingSuffixMatching(input.bypass_rules); config_service()->UISetProxyConfigBypassRules(bypass_rules); } break; case MK_MODE(PROXY_PER_SCHEME) : if (input.http_uri) { config_service()->UISetProxyConfigToProxyPerScheme("http", net::ProxyServer::FromURI(input.http_uri, MK_SCHM(HTTP))); } if (input.https_uri) { config_service()->UISetProxyConfigToProxyPerScheme("https", net::ProxyServer::FromURI(input.https_uri, MK_SCHM(HTTPS))); } if (input.ftp_uri) { config_service()->UISetProxyConfigToProxyPerScheme("ftp", net::ProxyServer::FromURI(input.ftp_uri, MK_SCHM(HTTP))); } if (input.socks_uri) { config_service()->UISetProxyConfigToProxyPerScheme("socks", net::ProxyServer::FromURI(input.socks_uri, MK_SCHM(SOCKS5))); } if (input.bypass_rules) { bypass_rules.ParseFromStringUsingSuffixMatching(input.bypass_rules); config_service()->UISetProxyConfigBypassRules(bypass_rules); } break; } // Retrieve config from IO thread. net::ProxyConfig io_config; SyncGetLatestProxyConfig(&io_config); EXPECT_EQ(tests[i].auto_detect, io_config.auto_detect()); EXPECT_EQ(tests[i].pac_url, io_config.pac_url()); EXPECT_TRUE(tests[i].proxy_rules.Matches(io_config.proxy_rules())); // Retrieve config from UI thread. ProxyConfigServiceImpl::ProxyConfig ui_config; config_service()->UIGetProxyConfig(&ui_config); EXPECT_EQ(input.mode, ui_config.mode); if (tests[i].is_valid) { if (input.pac_url) EXPECT_EQ(GURL(input.pac_url), ui_config.automatic_proxy.pac_url); const net::ProxyRulesExpectation& proxy_rules = tests[i].proxy_rules; if (input.single_uri) EXPECT_EQ(proxy_rules.single_proxy, ui_config.single_proxy.server.ToURI()); if (input.http_uri) EXPECT_EQ(proxy_rules.proxy_for_http, ui_config.http_proxy.server.ToURI()); if (input.https_uri) EXPECT_EQ(proxy_rules.proxy_for_https, ui_config.https_proxy.server.ToURI()); if (input.ftp_uri) EXPECT_EQ(proxy_rules.proxy_for_ftp, ui_config.ftp_proxy.server.ToURI()); if (input.socks_uri) { EXPECT_EQ(proxy_rules.fallback_proxy, ui_config.socks_proxy.server.ToURI()); } if (input.bypass_rules) EXPECT_TRUE(bypass_rules.Equals(ui_config.bypass_rules)); } } } TEST_F(ProxyConfigServiceImplTest, ProxyChangedObserver) { // This is used to observe for OnProxyConfigChanged notification. class ProxyChangedObserver : public net::ProxyConfigService::Observer { public: explicit ProxyChangedObserver( const scoped_refptr& config_service) : config_service_(config_service) { config_service_->AddObserver(this); } virtual ~ProxyChangedObserver() { config_service_->RemoveObserver(this); } net::ProxyConfigService::ConfigAvailability availability() const { return availability_; } const net::ProxyConfig& config() const { return config_; } private: virtual void OnProxyConfigChanged( const net::ProxyConfig& config, net::ProxyConfigService::ConfigAvailability availability) { config_ = config; availability_ = availability; } scoped_refptr config_service_; net::ProxyConfigService::ConfigAvailability availability_; net::ProxyConfig config_; }; // Init with direct. ProxyConfigServiceImpl::ProxyConfig init_config; SetAutomaticProxy(MK_MODE(DIRECT), MK_SRC(OWNER), NULL, &init_config, &init_config.automatic_proxy); CreateConfigService(init_config); ProxyChangedObserver observer(config_service()); // Set to pac script from UI. EXPECT_TRUE(config_service()->UISetProxyConfigToPACScript( GURL("http://wpad.dat"))); // Retrieve config from IO thread. net::ProxyConfig io_config; SyncGetLatestProxyConfig(&io_config); // Observer should have gotten the same new proxy config. EXPECT_EQ(net::ProxyConfigService::CONFIG_VALID, observer.availability()); EXPECT_TRUE(io_config.Equals(observer.config())); } TEST_F(ProxyConfigServiceImplTest, SerializeAndDeserialize) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { if (!tests[i].is_valid) continue; SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); ProxyConfigServiceImpl::ProxyConfig source_config; InitConfigWithTestInput(tests[i].input, MK_SRC(OWNER), &source_config); // Serialize source_config into std::string. std::string serialized_value; EXPECT_TRUE(source_config.Serialize(&serialized_value)); // Deserialize std:string into target_config. ProxyConfigServiceImpl::ProxyConfig target_config; EXPECT_TRUE(target_config.Deserialize(serialized_value)); // Compare the configs after serialization and deserialization. net::ProxyConfig net_src_cfg; net::ProxyConfig net_tgt_cfg; source_config.ToNetProxyConfig(&net_src_cfg); target_config.ToNetProxyConfig(&net_tgt_cfg); #if !defined(NDEBUG) if (!net_src_cfg.Equals(net_tgt_cfg)) { std::string src_output, tgt_output; JSONStringValueSerializer src_serializer(&src_output); src_serializer.Serialize(*net_src_cfg.ToValue()); JSONStringValueSerializer tgt_serializer(&tgt_output); tgt_serializer.Serialize(*net_tgt_cfg.ToValue()); VLOG(1) << "source:\n" << src_output << "\ntarget:\n" << tgt_output; } #endif // !defined(NDEBUG) EXPECT_TRUE(net_src_cfg.Equals(net_tgt_cfg)); } } TEST_F(ProxyConfigServiceImplTest, ReadWriteAccessForPolicySource) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { if (!tests[i].test_read_write_access) continue; SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); TestReadWriteAccessForMode(tests[i].input, MK_SRC(POLICY)); } } TEST_F(ProxyConfigServiceImplTest, ReadWriteAccessForOwnerSource) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { if (!tests[i].test_read_write_access) continue; SCOPED_TRACE(StringPrintf("Test[%" PRIuS "] %s", i, tests[i].description.c_str())); TestReadWriteAccessForMode(tests[i].input, MK_SRC(OWNER)); } } TEST_F(ProxyConfigServiceImplTest, ReadWriteAccessForMixedSchemes) { const char* http_uri = "www.google.com:80"; const char* https_uri = "www.foo.com:110"; const char* ftp_uri = "ftp.foo.com:121"; const char* socks_uri = "socks.com:888"; // Init with policy source. TestReadWriteAccessForScheme(MK_SRC(POLICY), http_uri, "http"); TestReadWriteAccessForScheme(MK_SRC(POLICY), https_uri, "https"); TestReadWriteAccessForScheme(MK_SRC(POLICY), ftp_uri, "ftp"); TestReadWriteAccessForScheme(MK_SRC(POLICY), socks_uri, "socks"); // Init with owner source. TestReadWriteAccessForScheme(MK_SRC(OWNER), http_uri, "http"); TestReadWriteAccessForScheme(MK_SRC(OWNER), https_uri, "https"); TestReadWriteAccessForScheme(MK_SRC(OWNER), ftp_uri, "ftp"); TestReadWriteAccessForScheme(MK_SRC(OWNER), socks_uri, "socks"); } } // namespace chromeos