| // 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/proxy_resolver_js_bindings.h" |
| |
| #include "base/compiler_specific.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/string_util.h" |
| #include "net/base/address_list.h" |
| #include "net/base/host_cache.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/test_completion_callback.h" |
| #include "net/proxy/proxy_resolver_request_context.h" |
| #include "net/proxy/sync_host_resolver.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(OS_STARBOARD) |
| // Starboard doesn't implement GetHostName. |
| #define MAYBE_MyIpAddress DISABLED_MyIpAddress |
| #else |
| #define MAYBE_MyIpAddress MyIpAddress |
| #endif |
| |
| 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 SyncHostResolver { |
| public: |
| // HostResolver methods: |
| virtual int Resolve(const HostResolver::RequestInfo& info, |
| AddressList* addresses, |
| const BoundNetLog& bound_net_log) OVERRIDE { |
| return ParseAddressList("192.168.1.1,172.22.34.1,200.100.1.2", "", |
| addresses); |
| } |
| |
| virtual void Shutdown() OVERRIDE {} |
| |
| private: |
| virtual ~MockHostResolverWithMultipleResults() {} |
| }; |
| |
| class MockFailingHostResolver : public SyncHostResolver { |
| public: |
| MockFailingHostResolver() : count_(0) {} |
| |
| // HostResolver methods: |
| virtual int Resolve(const HostResolver::RequestInfo& info, |
| AddressList* addresses, |
| const BoundNetLog& bound_net_log) OVERRIDE { |
| count_++; |
| return ERR_NAME_NOT_RESOLVED; |
| } |
| |
| virtual void Shutdown() OVERRIDE {} |
| |
| // Returns the number of times Resolve() has been called. |
| int count() const { return count_; } |
| void ResetCount() { count_ = 0; } |
| |
| private: |
| int count_; |
| }; |
| |
| class MockSyncHostResolver : public SyncHostResolver { |
| public: |
| MockSyncHostResolver() { |
| resolver_.set_synchronous_mode(true); |
| } |
| |
| virtual int Resolve(const HostResolver::RequestInfo& info, |
| AddressList* addresses, |
| const BoundNetLog& bound_net_log) OVERRIDE { |
| return resolver_.Resolve(info, addresses, CompletionCallback(), NULL, |
| bound_net_log); |
| } |
| |
| virtual void Shutdown() OVERRIDE {} |
| |
| RuleBasedHostResolverProc* rules() { |
| return resolver_.rules(); |
| } |
| |
| private: |
| MockHostResolver resolver_; |
| }; |
| |
| TEST(ProxyResolverJSBindingsTest, DnsResolve) { |
| MockSyncHostResolver* host_resolver = new MockSyncHostResolver; |
| |
| // Get a hold of a DefaultJSBindings* (it is a hidden impl class). |
| scoped_ptr<ProxyResolverJSBindings> bindings( |
| ProxyResolverJSBindings::CreateDefault(host_resolver, NULL, 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, MAYBE_MyIpAddress) { |
| MockSyncHostResolver* host_resolver = new MockSyncHostResolver; |
| |
| // Get a hold of a DefaultJSBindings* (it is a hidden impl class). |
| scoped_ptr<ProxyResolverJSBindings> bindings( |
| ProxyResolverJSBindings::CreateDefault(host_resolver, NULL, 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) { |
| MockSyncHostResolver* host_resolver = new MockSyncHostResolver; |
| |
| // Get a hold of a DefaultJSBindings* (it is a hidden impl class). |
| scoped_ptr<ProxyResolverJSBindings> bindings( |
| ProxyResolverJSBindings::CreateDefault(host_resolver, NULL, 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, BoundNetLog())); |
| ASSERT_FALSE(address_list.empty()); |
| EXPECT_EQ("192.168.2.1", address_list.front().ToStringWithoutPort()); |
| |
| info.set_address_family(ADDRESS_FAMILY_IPV4); |
| EXPECT_EQ(OK, host_resolver->Resolve(info, &address_list, BoundNetLog())); |
| ASSERT_FALSE(address_list.empty()); |
| EXPECT_EQ("192.168.1.1", address_list.front().ToStringWithoutPort()); |
| |
| std::string ip_address; |
| // Now the actual test. |
| #if !defined(OS_STARBOARD) |
| EXPECT_TRUE(bindings->MyIpAddress(&ip_address)); |
| EXPECT_EQ("192.168.1.2", ip_address); // IPv4 restricted. |
| #endif |
| |
| 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. |
| |
| #if !defined(OS_STARBOARD) |
| EXPECT_TRUE(bindings->MyIpAddressEx(&ip_address)); |
| EXPECT_EQ("192.168.2.2", ip_address); // Unrestricted. |
| #endif |
| |
| 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) { |
| SyncHostResolver* host_resolver = |
| new MockHostResolverWithMultipleResults; |
| |
| // Get a hold of a DefaultJSBindings* (it is a hidden impl class). |
| scoped_ptr<ProxyResolverJSBindings> bindings( |
| ProxyResolverJSBindings::CreateDefault(host_resolver, NULL, NULL)); |
| |
| std::string ip_addresses; |
| |
| #if !defined(OS_STARBOARD) |
| EXPECT_TRUE(bindings->MyIpAddressEx(&ip_addresses)); |
| EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2", ip_addresses); |
| #endif |
| |
| 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) { |
| 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, NULL, 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. |
| const unsigned kMaxCacheEntries = 50; |
| HostCache cache(kMaxCacheEntries); |
| 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) { |
| MockFailingHostResolver* host_resolver = new MockFailingHostResolver; |
| |
| CapturingNetLog global_log; |
| |
| // Get a hold of a DefaultJSBindings* (it is a hidden impl class). |
| scoped_ptr<ProxyResolverJSBindings> bindings( |
| ProxyResolverJSBindings::CreateDefault( |
| host_resolver, &global_log, NULL)); |
| |
| // Attach a capturing NetLog as the current request's log stream. |
| CapturingNetLog log; |
| BoundNetLog bound_log(BoundNetLog::Make(&log, NetLog::SOURCE_NONE)); |
| ProxyResolverRequestContext context(&bound_log, NULL); |
| bindings->set_current_request_context(&context); |
| |
| std::string ip_address; |
| net::CapturingNetLog::CapturedEntryList 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::CapturedEntryList 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 |