| // 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 |