// 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 "net/proxy/proxy_resolver_js_bindings.h" #include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "net/base/address_list.h" #include "net/base/mock_host_resolver.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/base/net_log_unittest.h" #include "net/base/net_util.h" #include "net/base/sys_addrinfo.h" #include "net/proxy/proxy_resolver_request_context.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { // This is a HostResolver that synchronously resolves all hosts to the // following address list of length 3: // 192.168.1.1 // 172.22.34.1 // 200.100.1.2 class MockHostResolverWithMultipleResults : public HostResolver { public: // HostResolver methods: virtual int Resolve(const RequestInfo& info, AddressList* addresses, CompletionCallback* callback, RequestHandle* out_req, const BoundNetLog& net_log) { // Build up the result list (in reverse). AddressList temp_list = ResolveIPLiteral("200.100.1.2"); temp_list = PrependAddressToList("172.22.34.1", temp_list); temp_list = PrependAddressToList("192.168.1.1", temp_list); *addresses = temp_list; return OK; } virtual void CancelRequest(RequestHandle req) {} virtual void AddObserver(Observer* observer) {} virtual void RemoveObserver(Observer* observer) {} virtual void Shutdown() {} private: ~MockHostResolverWithMultipleResults() {} // Resolves an IP literal to an address list. AddressList ResolveIPLiteral(const char* ip_literal) { AddressList result; int rv = SystemHostResolverProc(ip_literal, ADDRESS_FAMILY_UNSPECIFIED, 0, &result, NULL); EXPECT_EQ(OK, rv); EXPECT_EQ(NULL, result.head()->ai_next); return result; } // Builds an AddressList that is |ip_literal| + |address_list|. // |orig_list| must not be empty. AddressList PrependAddressToList(const char* ip_literal, const AddressList& orig_list) { // Build an addrinfo for |ip_literal|. AddressList result = ResolveIPLiteral(ip_literal); struct addrinfo* result_head = const_cast<struct addrinfo*>(result.head()); // Temporarily append |orig_list| to |result|. result_head->ai_next = const_cast<struct addrinfo*>(orig_list.head()); // Make a copy of the concatenated list. AddressList concatenated; concatenated.Copy(result.head(), true); // Restore |result| (so it is freed properly). result_head->ai_next = NULL; return concatenated; } }; class MockFailingHostResolver : public HostResolver { public: MockFailingHostResolver() : count_(0) {} // HostResolver methods: virtual int Resolve(const RequestInfo& info, AddressList* addresses, CompletionCallback* callback, RequestHandle* out_req, const BoundNetLog& net_log) { count_++; return ERR_NAME_NOT_RESOLVED; } virtual void CancelRequest(RequestHandle req) {} virtual void AddObserver(Observer* observer) {} virtual void RemoveObserver(Observer* observer) {} virtual void Shutdown() {} // Returns the number of times Resolve() has been called. int count() const { return count_; } void ResetCount() { count_ = 0; } private: int count_; }; TEST(ProxyResolverJSBindingsTest, DnsResolve) { scoped_ptr<MockHostResolver> host_resolver(new MockHostResolver); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), NULL)); std::string ip_address; // Empty string is not considered a valid host (even though on some systems // requesting this will resolve to localhost). host_resolver->rules()->AddSimulatedFailure(""); EXPECT_FALSE(bindings->DnsResolve("", &ip_address)); // Should call through to the HostResolver. host_resolver->rules()->AddRule("google.com", "192.168.1.1"); EXPECT_TRUE(bindings->DnsResolve("google.com", &ip_address)); EXPECT_EQ("192.168.1.1", ip_address); // Resolve failures should give empty string. host_resolver->rules()->AddSimulatedFailure("fail"); EXPECT_FALSE(bindings->DnsResolve("fail", &ip_address)); // TODO(eroman): would be nice to have an IPV6 test here too, but that // won't work on all systems. } TEST(ProxyResolverJSBindingsTest, MyIpAddress) { scoped_ptr<MockHostResolver> host_resolver(new MockHostResolver); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), NULL)); // Our IP address is always going to be 127.0.0.1, since we are using a // mock host resolver. std::string my_ip_address; EXPECT_TRUE(bindings->MyIpAddress(&my_ip_address)); EXPECT_EQ("127.0.0.1", my_ip_address); } // Tests that the regular PAC functions restrict results to IPv4, // but that the Microsoft extensions to PAC do not. We test this // by seeing whether ADDRESS_FAMILY_IPV4 or ADDRESS_FAMILY_UNSPECIFIED // was passed into to the host resolver. // // Restricted to IPv4 address family: // myIpAddress() // dnsResolve() // // Unrestricted address family: // myIpAddressEx() // dnsResolveEx() TEST(ProxyResolverJSBindingsTest, RestrictAddressFamily) { scoped_ptr<MockHostResolver> host_resolver(new MockHostResolver); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), NULL)); // Make it so requests resolve to particular address patterns based on family: // IPV4_ONLY --> 192.168.1.* // UNSPECIFIED --> 192.168.2.1 host_resolver->rules()->AddRuleForAddressFamily( "foo", ADDRESS_FAMILY_IPV4, "192.168.1.1"); host_resolver->rules()->AddRuleForAddressFamily( "*", ADDRESS_FAMILY_IPV4, "192.168.1.2"); host_resolver->rules()->AddRuleForAddressFamily( "foo", ADDRESS_FAMILY_UNSPECIFIED, "192.168.2.1"); host_resolver->rules()->AddRuleForAddressFamily( "*", ADDRESS_FAMILY_UNSPECIFIED, "192.168.2.2"); // Verify that our mock setups works as expected, and we get different results // depending if the address family was IPV4_ONLY or not. HostResolver::RequestInfo info(HostPortPair("foo", 80)); AddressList address_list; EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL, BoundNetLog())); EXPECT_EQ("192.168.2.1", NetAddressToString(address_list.head())); info.set_address_family(ADDRESS_FAMILY_IPV4); EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, NULL, NULL, BoundNetLog())); EXPECT_EQ("192.168.1.1", NetAddressToString(address_list.head())); std::string ip_address; // Now the actual test. EXPECT_TRUE(bindings->MyIpAddress(&ip_address)); EXPECT_EQ("192.168.1.2", ip_address); // IPv4 restricted. EXPECT_TRUE(bindings->DnsResolve("foo", &ip_address)); EXPECT_EQ("192.168.1.1", ip_address); // IPv4 restricted. EXPECT_TRUE(bindings->DnsResolve("foo2", &ip_address)); EXPECT_EQ("192.168.1.2", ip_address); // IPv4 restricted. EXPECT_TRUE(bindings->MyIpAddressEx(&ip_address)); EXPECT_EQ("192.168.2.2", ip_address); // Unrestricted. EXPECT_TRUE(bindings->DnsResolveEx("foo", &ip_address)); EXPECT_EQ("192.168.2.1", ip_address); // Unrestricted. EXPECT_TRUE(bindings->DnsResolveEx("foo2", &ip_address)); EXPECT_EQ("192.168.2.2", ip_address); // Unrestricted. } // Test that myIpAddressEx() and dnsResolveEx() both return a semi-colon // separated list of addresses (as opposed to the non-Ex versions which // just return the first result). TEST(ProxyResolverJSBindingsTest, ExFunctionsReturnList) { scoped_ptr<HostResolver> host_resolver( new MockHostResolverWithMultipleResults); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), NULL)); std::string ip_addresses; EXPECT_TRUE(bindings->MyIpAddressEx(&ip_addresses)); EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", ip_addresses); EXPECT_TRUE(bindings->DnsResolveEx("FOO", &ip_addresses)); EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", ip_addresses); } TEST(ProxyResolverJSBindingsTest, PerRequestDNSCache) { scoped_ptr<MockFailingHostResolver> host_resolver( new MockFailingHostResolver); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), NULL)); std::string ip_address; // Call DnsResolve() 4 times for the same hostname -- this should issue // 4 separate calls to the underlying host resolver, since there is no // current request context. EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_EQ(4, host_resolver->count()); host_resolver->ResetCount(); // Now setup a per-request context, and try the same experiment -- we // expect the underlying host resolver to receive only 1 request this time, // since it will service the others from the per-request DNS cache. HostCache cache(50, base::TimeDelta::FromMinutes(10), base::TimeDelta::FromMinutes(10)); ProxyResolverRequestContext context(NULL, &cache); bindings->set_current_request_context(&context); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolve("foo", &ip_address)); EXPECT_EQ(1, host_resolver->count()); host_resolver->ResetCount(); // The "Ex" version shares this same cache, however since the flags // are different it won't reuse this particular entry. EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address)); EXPECT_EQ(1, host_resolver->count()); EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address)); EXPECT_FALSE(bindings->DnsResolveEx("foo", &ip_address)); EXPECT_EQ(1, host_resolver->count()); bindings->set_current_request_context(NULL); } // Test that when a binding is called, it logs to the per-request NetLog. TEST(ProxyResolverJSBindingsTest, NetLog) { scoped_ptr<MockFailingHostResolver> host_resolver( new MockFailingHostResolver); CapturingNetLog global_log(CapturingNetLog::kUnbounded); // Get a hold of a DefaultJSBindings* (it is a hidden impl class). scoped_ptr<ProxyResolverJSBindings> bindings( ProxyResolverJSBindings::CreateDefault(host_resolver.get(), &global_log)); // Attach a capturing NetLog as the current request's log stream. CapturingNetLog log(CapturingNetLog::kUnbounded); BoundNetLog bound_log(NetLog::Source(NetLog::SOURCE_NONE, 0), &log); ProxyResolverRequestContext context(&bound_log, NULL); bindings->set_current_request_context(&context); std::string ip_address; net::CapturingNetLog::EntryList entries; log.GetEntries(&entries); ASSERT_EQ(0u, entries.size()); // Call all the bindings. Each call should be logging something to // our NetLog. bindings->MyIpAddress(&ip_address); log.GetEntries(&entries); EXPECT_EQ(2u, entries.size()); EXPECT_TRUE(LogContainsBeginEvent( entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS)); EXPECT_TRUE(LogContainsEndEvent( entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS)); bindings->MyIpAddressEx(&ip_address); log.GetEntries(&entries); EXPECT_EQ(4u, entries.size()); EXPECT_TRUE(LogContainsBeginEvent( entries, 2, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX)); EXPECT_TRUE(LogContainsEndEvent( entries, 3, NetLog::TYPE_PAC_JAVASCRIPT_MY_IP_ADDRESS_EX)); bindings->DnsResolve("foo", &ip_address); log.GetEntries(&entries); EXPECT_EQ(6u, entries.size()); EXPECT_TRUE(LogContainsBeginEvent( entries, 4, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE)); EXPECT_TRUE(LogContainsEndEvent( entries, 5, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE)); bindings->DnsResolveEx("foo", &ip_address); log.GetEntries(&entries); EXPECT_EQ(8u, entries.size()); EXPECT_TRUE(LogContainsBeginEvent( entries, 6, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX)); EXPECT_TRUE(LogContainsEndEvent( entries, 7, NetLog::TYPE_PAC_JAVASCRIPT_DNS_RESOLVE_EX)); // Nothing has been emitted globally yet. net::CapturingNetLog::EntryList global_log_entries; global_log.GetEntries(&global_log_entries); EXPECT_EQ(0u, global_log_entries.size()); bindings->OnError(30, string16()); log.GetEntries(&entries); EXPECT_EQ(9u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 8, NetLog::TYPE_PAC_JAVASCRIPT_ERROR, NetLog::PHASE_NONE)); // We also emit errors to the top-level log stream. global_log.GetEntries(&global_log_entries); EXPECT_EQ(1u, global_log_entries.size()); EXPECT_TRUE(LogContainsEvent( global_log_entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ERROR, NetLog::PHASE_NONE)); bindings->Alert(string16()); log.GetEntries(&entries); EXPECT_EQ(10u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 9, NetLog::TYPE_PAC_JAVASCRIPT_ALERT, NetLog::PHASE_NONE)); // We also emit javascript alerts to the top-level log stream. global_log.GetEntries(&global_log_entries); EXPECT_EQ(2u, global_log_entries.size()); EXPECT_TRUE(LogContainsEvent( global_log_entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_ALERT, NetLog::PHASE_NONE)); } } // namespace } // namespace net