Commit 21f20c89 authored by eroman@chromium.org's avatar eroman@chromium.org

Add three of the six extensions to PAC that Internet Explorer supports.

The following descriptions were taken from <http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx> 

---------------------------- 
* myIpAddressEx(): 
Returns a semi-colon delimited string containing all IP addresses for localhost (IPv6 and/or IPv4), or an empty string if unable to resolve localhost to an IP address. 

* dnsResolveEx(host): 
Returns semi-colon delimited string containing IPv6 and IPv4 addresses or an empty string if host is not resolvable. 

* isResolvableEx(): 
Returns TRUE if the host is resolvable to a IPv4 or IPv6 address, FALSE otherwise. 
---------------------------- 

These differ from the vanilla PAC functions in the following ways: 

* myIpAddressEx() returns all the addrsses for localhost (including IPv6 ones), whereas myIpAddress() only returns the first IPv4 one. 
* On failure, myIpAddress() returns "127.0.0.1" whereas on failure myIpAddressEx() returns empty string. 
* dnsResolveEx() returns a list of addresses (including IPV6 ones), whereas dnsResolve() only returns the first IPv4 address. 
* On failure, dnsResolve() returns |null|, whereas on failure dnsResolveEx() returns empty string. 

BUG=25407
TEST=ProxyResolverV8Test.DNSResolutionFailure, ProxyResolverJSBindingsTest.RestrictAddressFamily, ProxyResolverJSBindingsTest.ExFunctionsReturnList
Review URL: http://codereview.chromium.org/333006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30127 0039d316-1c4b-4281-b951-d872f2087c98
parent 13d54f79
......@@ -67,9 +67,15 @@ EVENT_TYPE(PROXY_SERVICE_WAITING_FOR_INIT_PAC)
// Measures the time taken to execute the "myIpAddress()" javascript binding.
EVENT_TYPE(PROXY_RESOLVER_V8_MY_IP_ADDRESS)
// Measures the time taken to execute the "myIpAddressEx()" javascript binding.
EVENT_TYPE(PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX)
// Measures the time taken to execute the "dnsResolve()" javascript binding.
EVENT_TYPE(PROXY_RESOLVER_V8_DNS_RESOLVE)
// Measures the time taken to execute the "dnsResolveEx()" javascript binding.
EVENT_TYPE(PROXY_RESOLVER_V8_DNS_RESOLVE_EX)
// ------------------------------------------------------------------------
// ClientSocketPoolBase::ConnectJob
// ------------------------------------------------------------------------
......
......@@ -35,10 +35,17 @@ function FindProxyForURL(url, host) {
alert(e);
}
// Call myIpAddress() with wonky arguments
// Call myIpAddress() with wonky arguments
myIpAddress(null);
myIpAddress(null, null);
// Call myIpAddressEx() correctly (no arguments).
myIpAddressEx();
// Call dnsResolveEx() (note that isResolvableEx() implicity calls it.)
isResolvableEx("is_resolvable");
dnsResolveEx("foobar");
return "DIRECT";
}
......
// This script should be run in an environment where all DNS resolution are
// failing. It tests that functions return the expected values.
//
// Returns "PROXY success:80" on success.
function FindProxyForURL(url, host) {
try {
expectEq("127.0.0.1", myIpAddress());
expectEq("", myIpAddressEx());
expectEq(null, dnsResolve("not-found"));
expectEq("", dnsResolveEx("not-found"));
expectEq(false, isResolvable("not-found"));
expectEq(false, isResolvableEx("not-found"));
return "PROXY success:80";
} catch(e) {
alert(e);
return "PROXY failed:80";
}
}
function expectEq(expected, actual) {
if (expected != actual)
throw "Expected " + expected + " but was " + actual;
}
......@@ -2,6 +2,14 @@
// source code is governed by a BSD-style license that can be found in the
// LICENSE file.
#include "build/build_config.h"
#if defined(OS_WIN)
#include <ws2tcpip.h>
#else
#include <netdb.h>
#endif
#include "net/proxy/proxy_resolver_js_bindings.h"
#include "base/compiler_specific.h"
......@@ -97,21 +105,24 @@ class DefaultJSBindings : public ProxyResolverJSBindings {
}
// Handler for "myIpAddress()". Returns empty string on failure.
// TODO(eroman): Perhaps enumerate the interfaces directly, using
// getifaddrs().
virtual std::string MyIpAddress() {
// DnsResolve("") returns "", so no need to check for failure.
return DnsResolve(GetHostName());
}
// Handler for "myIpAddressEx()". Returns empty string on failure.
virtual std::string MyIpAddressEx() {
return DnsResolveEx(GetHostName());
}
// Handler for "dnsResolve(host)". Returns empty string on failure.
virtual std::string DnsResolve(const std::string& host) {
// TODO(eroman): Should this return our IP address, or fail, or
// simply be unspecified (works differently on windows and mac os x).
if (host.empty())
return std::string();
// Do a sync resolve of the hostname.
// Disable IPv6 results. We do this because Internet Explorer does it --
// consequently a lot of existing PAC scripts assume they will only get
// Disable IPv6 results. We do this because the PAC specification isn't
// really IPv6 friendly, and Internet Explorer also restricts to IPv4.
// Consequently a lot of existing PAC scripts assume they will only get
// IPv4 results, and will misbehave if they get an IPv6 result.
// See http://crbug.com/24641 for more details.
net::AddressList address_list;
......@@ -130,6 +141,31 @@ class DefaultJSBindings : public ProxyResolverJSBindings {
return net::NetAddressToString(address_list.head());
}
// Handler for "dnsResolveEx(host)". Returns empty string on failure.
virtual std::string DnsResolveEx(const std::string& host) {
// Do a sync resolve of the hostname.
net::AddressList address_list;
int result = host_resolver_->Resolve(host,
ADDRESS_FAMILY_UNSPECIFIED,
&address_list);
if (result != OK)
return std::string(); // Failed.
// Stringify all of the addresses in the address list, separated
// by semicolons.
std::string address_list_str;
const struct addrinfo* current_address = address_list.head();
while (current_address) {
if (!address_list_str.empty())
address_list_str += ";";
address_list_str += net::NetAddressToString(current_address);
current_address = current_address->ai_next;
}
return address_list_str;
}
// Handler for when an error is encountered. |line_number| may be -1.
virtual void OnError(int line_number, const std::string& message) {
if (line_number == -1)
......
......@@ -24,9 +24,21 @@ class ProxyResolverJSBindings {
// Handler for "myIpAddress()". Returns empty string on failure.
virtual std::string MyIpAddress() = 0;
// Handler for "myIpAddressEx()". Returns empty string on failure.
//
// This is a Microsoft extension to PAC for IPv6, see:
// http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx
virtual std::string MyIpAddressEx() = 0;
// Handler for "dnsResolve(host)". Returns empty string on failure.
virtual std::string DnsResolve(const std::string& host) = 0;
// Handler for "dnsResolveEx(host)". Returns empty string on failure.
//
// This is a Microsoft extension to PAC for IPv6, see:
// http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx
virtual std::string DnsResolveEx(const std::string& host) = 0;
// Handler for when an error is encountered. |line_number| may be -1
// if a line number is not applicable to this error.
virtual void OnError(int line_number, const std::string& error) = 0;
......
......@@ -2,7 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#if defined(OS_WIN)
#include <ws2tcpip.h>
#else
#include <netdb.h>
#endif
#include "base/scoped_ptr.h"
#include "net/base/address_list.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
......@@ -12,6 +21,67 @@
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,
LoadLog* load_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 HostCache* GetHostCache() { return NULL; }
virtual void Shutdown() {}
private:
// Resolves an IP literal to an address list.
AddressList ResolveIPLiteral(const char* ip_literal) {
AddressList result;
int rv = SystemHostResolverProc(ip_literal,
ADDRESS_FAMILY_UNSPECIFIED,
&result);
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());
// Restore |result| (so it is freed properly).
result_head->ai_next = NULL;
return concatenated;
}
};
TEST(ProxyResolverJSBindingsTest, DnsResolve) {
scoped_refptr<MockHostResolver> host_resolver(new MockHostResolver);
......@@ -21,6 +91,7 @@ TEST(ProxyResolverJSBindingsTest, DnsResolve) {
// Empty string is not considered a valid host (even though on some systems
// requesting this will resolve to localhost).
host_resolver->rules()->AddSimulatedFailure("");
EXPECT_EQ("", bindings->DnsResolve(""));
// Should call through to the HostResolver.
......@@ -48,10 +119,19 @@ TEST(ProxyResolverJSBindingsTest, MyIpAddress) {
EXPECT_EQ("127.0.0.1", my_ip_address);
}
// Tests that myIpAddress() and dnsResolve() pass the flag
// ADDRESS_FAMILY_IPV4 to the host resolver, as we don't want them
// to return IPv6 results.
TEST(ProxyResolverJSBindingsTest, DontUseIPv6) {
// 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_refptr<MockHostResolver> host_resolver(new MockHostResolver);
// Get a hold of a DefaultJSBindings* (it is a hidden impl class).
......@@ -67,7 +147,9 @@ TEST(ProxyResolverJSBindingsTest, DontUseIPv6) {
host_resolver->rules()->AddRuleForAddressFamily(
"*", ADDRESS_FAMILY_IPV4, "192.168.1.2");
host_resolver->rules()->AddRuleForAddressFamily(
"*", ADDRESS_FAMILY_UNSPECIFIED, "192.168.2.1");
"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.
......@@ -81,9 +163,31 @@ TEST(ProxyResolverJSBindingsTest, DontUseIPv6) {
EXPECT_EQ("192.168.1.1", NetAddressToString(address_list.head()));
// Now the actual test.
EXPECT_EQ("192.168.1.2", bindings->MyIpAddress());
EXPECT_EQ("192.168.1.1", bindings->DnsResolve("foo"));
EXPECT_EQ("192.168.1.2", bindings->DnsResolve("foo2"));
EXPECT_EQ("192.168.1.2", bindings->MyIpAddress()); // IPv4 restricted.
EXPECT_EQ("192.168.1.1", bindings->DnsResolve("foo")); // IPv4 restricted.
EXPECT_EQ("192.168.1.2", bindings->DnsResolve("foo2")); // IPv4 restricted.
EXPECT_EQ("192.168.2.2", bindings->MyIpAddressEx()); // Unrestricted.
EXPECT_EQ("192.168.2.1", bindings->DnsResolveEx("foo")); // Unrestricted.
EXPECT_EQ("192.168.2.2", bindings->DnsResolveEx("foo2")); // 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_refptr<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, NULL));
EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2",
bindings->MyIpAddressEx());
EXPECT_EQ("192.168.1.1;172.22.34.1;200.100.1.2",
bindings->DnsResolveEx("FOO"));
}
} // namespace
......
......@@ -263,6 +263,14 @@
" date.setSeconds(date.getUTCSeconds());\n" \
" }\n" \
" return ((date1 <= date) && (date <= date2));\n" \
"}\n" \
"}\n"
// This is a Microsoft extension to PAC for IPv6, see:
// http://blogs.msdn.com/wndp/articles/IPV6_PAC_Extensions_v0_9.aspx
#define PROXY_RESOLVER_SCRIPT_EX \
"function isResolvableEx(host) {\n" \
" var ipList = dnsResolveEx(host);\n" \
" return (ipList != '');\n" \
"}\n"
#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
......@@ -14,6 +14,51 @@
#include "net/proxy/proxy_resolver_script.h"
#include "v8/include/v8.h"
// Notes on the javascript environment:
//
// For the majority of the PAC utility functions, we use the same code
// as Firefox. See the javascript library that proxy_resolver_scipt.h
// pulls in.
//
// In addition, we implement a subset of Microsoft's extensions to PAC.
// TODO(eroman): Implement the rest.
//
// - myIpAddressEx()
// - dnsResolveEx()
// - isResolvableEx()
//
// It is worth noting that the original PAC specification does not describe
// the return values on failure. Consequently, there are compatibility
// differences between browsers on what to return on failure, which are
// illustrated below:
//
// ----------------+-------------+-------------------+--------------
// | Firefox3 | InternetExplorer8 | --> Us <---
// ----------------+-------------+-------------------+--------------
// myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1"
// dnsResolve() | null | false | null
// myIpAddressEx() | N/A | "" | ""
// dnsResolveEx() | N/A | "" | ""
// ----------------+-------------+-------------------+--------------
//
// TODO(eroman): The cell above reading ??? means I didn't test it.
//
// Another difference is in how dnsResolve() and myIpAddress() are
// implemented -- whether they should restrict to IPv4 results, or
// include both IPv4 and IPv6. The following table illustrates the
// differences:
//
// -----------------+-------------+-------------------+--------------
// | Firefox3 | InternetExplorer8 | --> Us <---
// -----------------+-------------+-------------------+--------------
// myIpAddress() | IPv4/IPv6 | IPv4 | IPv4
// dnsResolve() | IPv4/IPv6 | IPv4 | IPv4
// isResolvable() | IPv4/IPv6 | IPv4 | IPv4
// myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6
// dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6
// isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6
// -----------------+-------------+-------------------+--------------
namespace net {
namespace {
......@@ -128,13 +173,28 @@ class ProxyResolverV8::Context {
global_template->Set(v8::String::New("dnsResolve"),
dns_resolve_template);
// Microsoft's PAC extensions (incomplete):
v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this_);
global_template->Set(v8::String::New("dnsResolveEx"),
dns_resolve_ex_template);
v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this_);
global_template->Set(v8::String::New("myIpAddressEx"),
my_ip_address_ex_template);
v8_context_ = v8::Context::New(NULL, global_template);
v8::Context::Scope ctx(v8_context_);
// Add the PAC utility functions to the environment.
// (This script should never fail, as it is a string literal!)
int rv = RunScript(PROXY_RESOLVER_SCRIPT, kPacUtilityResourceName);
// Note that the two string literals are concatenated.
int rv = RunScript(PROXY_RESOLVER_SCRIPT
PROXY_RESOLVER_SCRIPT_EX,
kPacUtilityResourceName);
if (rv != OK) {
NOTREACHED();
return rv;
......@@ -248,6 +308,25 @@ class ProxyResolverV8::Context {
return StdStringToV8String(result);
}
// V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
static v8::Handle<v8::Value> MyIpAddressExCallback(
const v8::Arguments& args) {
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
LoadLog::BeginEvent(context->current_request_load_log_,
LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
// We shouldn't be called with any arguments, but will not complain if
// we are.
std::string result = context->js_bindings_->MyIpAddressEx();
LoadLog::EndEvent(context->current_request_load_log_,
LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS_EX);
return StdStringToV8String(result);
}
// V8 callback for when "dnsResolve()" is invoked by the PAC script.
static v8::Handle<v8::Value> DnsResolveCallback(const v8::Arguments& args) {
Context* context =
......@@ -270,10 +349,35 @@ class ProxyResolverV8::Context {
LoadLog::EndEvent(context->current_request_load_log_,
LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE);
// DoDnsResolve() returns empty string on failure.
// DnsResolve() returns empty string on failure.
return result.empty() ? v8::Null() : StdStringToV8String(result);
}
// V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
static v8::Handle<v8::Value> DnsResolveExCallback(const v8::Arguments& args) {
Context* context =
static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
// We need at least one argument.
std::string host;
if (args.Length() == 0) {
host = "undefined";
} else {
if (!V8ObjectToString(args[0], &host))
return v8::Undefined();
}
LoadLog::BeginEvent(context->current_request_load_log_,
LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
std::string result = context->js_bindings_->DnsResolveEx(host);
LoadLog::EndEvent(context->current_request_load_log_,
LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE_EX);
return StdStringToV8String(result);
}
ProxyResolverJSBindings* js_bindings_;
LoadLog* current_request_load_log_;
v8::Persistent<v8::External> v8_this_;
......
......@@ -21,7 +21,7 @@ namespace {
// list, for later verification.
class MockJSBindings : public ProxyResolverJSBindings {
public:
MockJSBindings() : my_ip_address_count(0) {}
MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {}
virtual void Alert(const std::string& message) {
LOG(INFO) << "PAC-alert: " << message; // Helpful when debugging.
......@@ -33,11 +33,21 @@ class MockJSBindings : public ProxyResolverJSBindings {
return my_ip_address_result;
}
virtual std::string MyIpAddressEx() {
my_ip_address_ex_count++;
return my_ip_address_ex_result;
}
virtual std::string DnsResolve(const std::string& host) {
dns_resolves.push_back(host);
return dns_resolve_result;
}
virtual std::string DnsResolveEx(const std::string& host) {
dns_resolves_ex.push_back(host);
return dns_resolve_ex_result;
}
virtual void OnError(int line_number, const std::string& message) {
// Helpful when debugging.
LOG(INFO) << "PAC-error: [" << line_number << "] " << message;
......@@ -48,14 +58,18 @@ class MockJSBindings : public ProxyResolverJSBindings {
// Mock values to return.
std::string my_ip_address_result;
std::string my_ip_address_ex_result;
std::string dns_resolve_result;
std::string dns_resolve_ex_result;
// Inputs we got called with.
std::vector<std::string> alerts;
std::vector<std::string> errors;
std::vector<int> errors_line_number;
std::vector<std::string> dns_resolves;
std::vector<std::string> dns_resolves_ex;
int my_ip_address_count;
int my_ip_address_ex_count;
};
// This is the same as ProxyResolverV8, but it uses mock bindings in place of
......@@ -391,6 +405,14 @@ TEST(ProxyResolverV8Test, V8Bindings) {
// MyIpAddress was called two times.
EXPECT_EQ(2, bindings->my_ip_address_count);
// MyIpAddressEx was called once.
EXPECT_EQ(1, bindings->my_ip_address_ex_count);
// DnsResolveEx was called 2 times.
ASSERT_EQ(2U, bindings->dns_resolves_ex.size());
EXPECT_EQ("is_resolvable", bindings->dns_resolves_ex[0]);
EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]);
}
// Test that calls to the myIpAddress() and dnsResolve() bindings get
......@@ -464,5 +486,22 @@ TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) {
EXPECT_EQ("success:3", proxy_info.proxy_server().ToURI());
}
// Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(),
// dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding
// returns empty string (failure). This simulates the return values from
// those functions when the underlying DNS resolution fails.
TEST(ProxyResolverV8Test, DNSResolutionFailure) {
ProxyResolverV8WithMockBindings resolver;
int result = resolver.SetPacScriptFromDisk("dns_fail.js");
EXPECT_EQ(OK, result);
ProxyInfo proxy_info;
result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL);
EXPECT_EQ(OK, result);
EXPECT_FALSE(proxy_info.is_direct());
EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
}
} // namespace
} // namespace net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment