summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/base/net_util.cc25
-rw-r--r--net/base/net_util.h5
-rw-r--r--net/base/net_util_unittest.cc89
-rw-r--r--net/data/proxy_resolver_v8_unittest/bindings.js46
-rw-r--r--net/proxy/proxy_resolver_v8.cc200
-rw-r--r--net/proxy/proxy_resolver_v8.h42
-rw-r--r--net/proxy/proxy_resolver_v8_unittest.cc282
7 files changed, 627 insertions, 62 deletions
diff --git a/net/base/net_util.cc b/net/base/net_util.cc
index 7cee8f1..7527b6c 100644
--- a/net/base/net_util.cc
+++ b/net/base/net_util.cc
@@ -15,7 +15,10 @@
#if defined(OS_WIN)
#include <windows.h>
#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <wspiapi.h> // Needed for Win2k compat.
#elif defined(OS_POSIX)
+#include <netdb.h>
#include <sys/socket.h>
#include <fcntl.h>
#endif
@@ -40,6 +43,9 @@
#include "googleurl/src/url_parse.h"
#include "net/base/escape.h"
#include "net/base/net_module.h"
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
#include "net/base/base64.h"
#include "unicode/datefmt.h"
@@ -1012,4 +1018,23 @@ bool GetHostAndPort(const std::string& host_and_port,
return GetHostAndPort(host_and_port.begin(), host_and_port.end(), host, port);
}
+std::string NetAddressToString(const struct addrinfo* net_address) {
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+
+ // This buffer is large enough to fit the biggest IPv6 string.
+ char buffer[INET6_ADDRSTRLEN];
+
+ int result = getnameinfo(net_address->ai_addr,
+ net_address->ai_addrlen, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST);
+
+ if (result != 0) {
+ DLOG(INFO) << "getnameinfo() failed with " << result;
+ return std::string();
+ }
+
+ return std::string(buffer);
+}
+
} // namespace net
diff --git a/net/base/net_util.h b/net/base/net_util.h
index 1fda9bd..6f3c1ff 100644
--- a/net/base/net_util.h
+++ b/net/base/net_util.h
@@ -17,6 +17,7 @@
#include "googleurl/src/url_canon.h"
#include "googleurl/src/url_parse.h"
+struct addrinfo;
class FilePath;
class GURL;
@@ -53,6 +54,10 @@ bool GetHostAndPort(const std::string& host_and_port,
std::string* host,
int* port);
+// Returns the string representation of an address, like "192.168.0.1".
+// Returns empty string on failure.
+std::string NetAddressToString(const struct addrinfo* net_address);
+
// Return the value of the HTTP response header with name 'name'. 'headers'
// should be in the format that URLRequest::GetResponseHeaders() returns.
// Returns the empty string if the header is not found.
diff --git a/net/base/net_util_unittest.cc b/net/base/net_util_unittest.cc
index c83d510..2293368 100644
--- a/net/base/net_util_unittest.cc
+++ b/net/base/net_util_unittest.cc
@@ -10,6 +10,12 @@
#include "net/base/net_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_WIN)
+#include <ws2tcpip.h>
+#else
+#include <netdb.h>
+#endif
+
namespace {
class NetUtilTest : public testing::Test {
@@ -57,6 +63,52 @@ struct SuggestedFilenameCase {
const wchar_t* expected_filename;
};
+// Returns an addrinfo for the given 32-bit address (IPv4.)
+// The result lives in static storage, so don't delete it.
+const struct addrinfo* GetIPv4Address(const uint8 bytes[4]) {
+ static struct addrinfo static_ai;
+ static struct sockaddr_in static_addr4;
+
+ struct addrinfo* ai = &static_ai;
+ ai->ai_socktype = SOCK_STREAM;
+ memset(ai, 0, sizeof(static_ai));
+
+ ai->ai_family = AF_INET;
+ ai->ai_addrlen = sizeof(static_addr4);
+
+ struct sockaddr_in* addr4 = &static_addr4;
+ memset(addr4, 0, sizeof(static_addr4));
+ addr4->sin_port = htons(80);
+ addr4->sin_family = ai->ai_family;
+ memcpy(&addr4->sin_addr, bytes, sizeof(bytes));
+
+ ai->ai_addr = (sockaddr*)addr4;
+ return ai;
+}
+
+// Returns a addrinfo for the given 128-bit address (IPv6.)
+// The result lives in static storage, so don't delete it.
+const struct addrinfo* GetIPv6Address(const uint8 bytes[16]) {
+ static struct addrinfo static_ai;
+ static struct sockaddr_in6 static_addr6;
+
+ struct addrinfo* ai = &static_ai;
+ ai->ai_socktype = SOCK_STREAM;
+ memset(ai, 0, sizeof(static_ai));
+
+ ai->ai_family = AF_INET6;
+ ai->ai_addrlen = sizeof(static_addr6);
+
+ struct sockaddr_in6* addr6 = &static_addr6;
+ memset(addr6, 0, sizeof(static_addr6));
+ addr6->sin6_port = htons(80);
+ addr6->sin6_family = ai->ai_family;
+ memcpy(&addr6->sin6_addr, bytes, sizeof(bytes));
+
+ ai->ai_addr = (sockaddr*)addr6;
+ return ai;
+}
+
} // anonymous namespace
TEST(NetUtilTest, FileURLConversion) {
@@ -736,6 +788,8 @@ TEST(NetUtilTest, GetDirectoryListingEntry) {
}
}
+#endif
+
TEST(NetUtilTest, GetHostAndPort) {
const struct {
const char* input;
@@ -781,4 +835,37 @@ TEST(NetUtilTest, GetHostAndPort) {
}
}
-#endif
+TEST(NetUtilTest, NetAddressToString_IPv4) {
+ const struct {
+ uint8 addr[4];
+ const char* result;
+ } tests[] = {
+ {{0, 0, 0, 0}, "0.0.0.0"},
+ {{127, 0, 0, 1}, "127.0.0.1"},
+ {{192, 168, 0, 1}, "192.168.0.1"},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ const addrinfo* ai = GetIPv4Address(tests[i].addr);
+ std::string result = net::NetAddressToString(ai);
+ EXPECT_EQ(std::string(tests[i].result), result);
+ }
+}
+
+// TODO(eroman): On windows this is failing with WSAEINVAL (10022).
+TEST(NetUtilTest, DISABLED_NetAddressToString_IPv6) {
+ const struct {
+ uint8 addr[16];
+ const char* result;
+ } tests[] = {
+ {{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA,
+ 0x98, 0x76, 0x54, 0x32, 0x10},
+ "fedc:ba98:7654:3210:fedc:ba98:7654:3210"},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ const addrinfo* ai = GetIPv6Address(tests[i].addr);
+ std::string result = net::NetAddressToString(ai);
+ EXPECT_EQ(std::string(tests[i].result), result);
+ }
+}
diff --git a/net/data/proxy_resolver_v8_unittest/bindings.js b/net/data/proxy_resolver_v8_unittest/bindings.js
new file mode 100644
index 0000000..0e748c1
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/bindings.js
@@ -0,0 +1,46 @@
+// Try calling the browser-side bound functions with varying (invalid)
+// inputs. There is no notion of "success" for this test, other than
+// verifying the correct C++ bindings were reached with expected values.
+
+function MyObject() {
+ this.x = "3";
+}
+
+MyObject.prototype.toString = function() {
+ throw "exception from calling toString()";
+}
+
+function FindProxyForURL(url, host) {
+ // Call dnsResolve with some wonky arguments.
+ dnsResolve();
+ dnsResolve(null);
+ dnsResolve(undefined);
+ dnsResolve("");
+ dnsResolve({foo: 'bar'});
+ dnsResolve(fn);
+ dnsResolve(['3']);
+ dnsResolve("arg1", "arg2", "arg3", "arg4");
+
+ // Call alert with some wonky arguments.
+ alert();
+ alert(null);
+ alert(undefined);
+ alert({foo:'bar'});
+
+ // This should throw an exception when we toString() the argument
+ // to alert in the bindings.
+ try {
+ alert(new MyObject());
+ } catch (e) {
+ alert(e);
+ }
+
+ // Call myIpAddress() with wonky arguments
+ myIpAddress(null);
+ myIpAddress(null, null);
+
+ return "DIRECT";
+}
+
+function fn() {}
+
diff --git a/net/proxy/proxy_resolver_v8.cc b/net/proxy/proxy_resolver_v8.cc
index f26a6f6..09472e9 100644
--- a/net/proxy/proxy_resolver_v8.cc
+++ b/net/proxy/proxy_resolver_v8.cc
@@ -4,21 +4,23 @@
#include "net/proxy/proxy_resolver_v8.h"
+#include "base/logging.h"
+#include "net/base/address_list.h"
+#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
#include "net/proxy/proxy_resolver_script.h"
#include "v8/include/v8.h"
-// TODO(eroman):
-// - javascript binding for "alert()"
-// - javascript binding for "myIpAddress()"
-// - javascript binding for "dnsResolve()"
-// - log errors for PAC authors (like if "FindProxyForURL" is missing, or
-// the javascript was malformed, etc..
namespace net {
namespace {
+// Pseudo-name for the PAC script.
+const char kPacResourceName[] = "proxy-pac-script.js";
+
+// Convert a V8 String to a std::string.
std::string V8StringToStdString(v8::Handle<v8::String> s) {
int len = s->Utf8Length();
std::string result;
@@ -26,24 +28,86 @@ std::string V8StringToStdString(v8::Handle<v8::String> s) {
return result;
}
+// Convert a std::string to a V8 string.
v8::Local<v8::String> StdStringToV8String(const std::string& s) {
return v8::String::New(s.data(), s.size());
}
+// String-ize a V8 object by calling its toString() method. Returns true
+// on success. This may fail if the toString() throws an exception.
+bool V8ObjectToString(v8::Handle<v8::Value> object, std::string* result) {
+ if (object.IsEmpty())
+ return false;
+
+ v8::HandleScope scope;
+ v8::Local<v8::String> str_object = object->ToString();
+ if (str_object.IsEmpty())
+ return false;
+ *result = V8StringToStdString(str_object);
+ return true;
+}
+
+// JSBIndings implementation.
+class DefaultJSBindings : public ProxyResolverV8::JSBindings {
+ public:
+ // Handler for "alert(message)".
+ virtual void Alert(const std::string& message) {
+ LOG(INFO) << "PAC-alert: " << message;
+ }
+
+ // Handler for "myIpAddress()". Returns empty string on failure.
+ virtual std::string MyIpAddress() {
+ // TODO(eroman):
+ NOTIMPLEMENTED();
+ return "127.0.0.1";
+ }
+
+ // Handler for "dnsResolve(host)". Returns empty string on failure.
+ virtual std::string DnsResolve(const std::string& host) {
+ // Try to resolve synchronously.
+ net::AddressList address_list;
+ const int kPort = 80; // Doesn't matter what this is.
+ int result = host_resolver_.Resolve(host, kPort, &address_list, NULL);
+
+ if (result != OK)
+ return std::string(); // Failed.
+
+ if (!address_list.head())
+ return std::string();
+
+ // There may be multiple results; we will just use the first one.
+ // This returns empty string on failure.
+ return net::NetAddressToString(address_list.head());
+ }
+
+ // 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)
+ LOG(INFO) << "PAC-error: " << message;
+ else
+ LOG(INFO) << "PAC-error: " << "line: " << line_number << ": " << message;
+ }
+
+ private:
+ HostResolver host_resolver_;
+};
+
} // namespace
// ProxyResolverV8::Context ---------------------------------------------------
class ProxyResolverV8::Context {
public:
- explicit Context(const std::string& pac_data) {
+ Context(JSBindings* js_bindings, const std::string& pac_data)
+ : js_bindings_(js_bindings) {
+ DCHECK(js_bindings != NULL);
InitV8(pac_data);
}
~Context() {
v8::Locker locked;
- v8::HandleScope scope;
+ v8_this_.Dispose();
v8_context_.Dispose();
}
@@ -55,23 +119,29 @@ class ProxyResolverV8::Context {
v8::Local<v8::Value> function =
v8_context_->Global()->Get(v8::String::New("FindProxyForURL"));
- if (!function->IsFunction())
+ if (!function->IsFunction()) {
+ js_bindings_->OnError(-1, "FindProxyForURL() is undefined.");
return ERR_PAC_SCRIPT_FAILED;
+ }
v8::Handle<v8::Value> argv[] = {
StdStringToV8String(query_url.spec()),
StdStringToV8String(query_url.host()),
};
+ v8::TryCatch try_catch;
v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
v8_context_->Global(), arraysize(argv), argv);
- // The handle is empty if an exception was thrown from "FindProxyForURL".
- if (ret.IsEmpty())
+ if (try_catch.HasCaught()) {
+ HandleError(try_catch.Message());
return ERR_PAC_SCRIPT_FAILED;
+ }
- if (!ret->IsString())
+ if (!ret->IsString()) {
+ js_bindings_->OnError(-1, "FindProxyForURL() did not return a string.");
return ERR_PAC_SCRIPT_FAILED;
+ }
std::string ret_str = V8StringToStdString(ret->ToString());
@@ -85,20 +155,110 @@ class ProxyResolverV8::Context {
v8::Locker locked;
v8::HandleScope scope;
+ v8_this_ = v8::Persistent<v8::External>::New(v8::External::New(this));
v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
+ // Attach the javascript bindings.
+ v8::Local<v8::FunctionTemplate> alert_template =
+ v8::FunctionTemplate::New(&AlertCallback, v8_this_);
+ global_template->Set(v8::String::New("alert"), alert_template);
+
+ v8::Local<v8::FunctionTemplate> my_ip_address_template =
+ v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_);
+ global_template->Set(v8::String::New("myIpAddress"),
+ my_ip_address_template);
+
+ v8::Local<v8::FunctionTemplate> dns_resolve_template =
+ v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_);
+ global_template->Set(v8::String::New("dnsResolve"),
+ dns_resolve_template);
+
v8_context_ = v8::Context::New(NULL, global_template);
+
v8::Context::Scope ctx(v8_context_);
- std::string text_raw = pac_data + PROXY_RESOLVER_SCRIPT;
+ v8::TryCatch try_catch;
+ // Compile the script, including the PAC library functions.
+ std::string text_raw = pac_data + PROXY_RESOLVER_SCRIPT;
v8::Local<v8::String> text = StdStringToV8String(text_raw);
- v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New(""));
+ v8::ScriptOrigin origin = v8::ScriptOrigin(
+ v8::String::New(kPacResourceName));
v8::Local<v8::Script> code = v8::Script::Compile(text, &origin);
+
+ // Execute.
if (!code.IsEmpty())
code->Run();
+
+ if (try_catch.HasCaught())
+ HandleError(try_catch.Message());
}
+ // Handle an exception thrown by V8.
+ void HandleError(v8::Handle<v8::Message> message) {
+ if (message.IsEmpty())
+ return;
+
+ // Otherwise dispatch to the bindings.
+ int line_number = message->GetLineNumber();
+ std::string error_message;
+ V8ObjectToString(message->Get(), &error_message);
+ js_bindings_->OnError(line_number, error_message);
+ }
+
+ // V8 callback for when "alert()" is invoked by the PAC script.
+ static v8::Handle<v8::Value> AlertCallback(const v8::Arguments& args) {
+ Context* context =
+ static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+ // Like firefox we assume "undefined" if no argument was specified, and
+ // disregard any arguments beyond the first.
+ std::string message;
+ if (args.Length() == 0) {
+ message = "undefined";
+ } else {
+ if (!V8ObjectToString(args[0], &message))
+ return v8::Undefined(); // toString() threw an exception.
+ }
+
+ context->js_bindings_->Alert(message);
+ return v8::Undefined();
+ }
+
+ // V8 callback for when "myIpAddress()" is invoked by the PAC script.
+ static v8::Handle<v8::Value> MyIpAddressCallback(const v8::Arguments& args) {
+ Context* context =
+ static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+ // We shouldn't be called with any arguments, but will not complain if
+ // we are.
+ std::string result = context->js_bindings_->MyIpAddress();
+ 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 =
+ 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();
+ }
+
+ std::string result = context->js_bindings_->DnsResolve(host);
+
+ // DoDnsResolve() returns empty string on failure.
+ return result.empty() ? v8::Null() : StdStringToV8String(result);
+ }
+
+ JSBindings* js_bindings_;
+ HostResolver host_resolver_;
+ v8::Persistent<v8::External> v8_this_;
v8::Persistent<v8::Context> v8_context_;
};
@@ -106,7 +266,15 @@ class ProxyResolverV8::Context {
// the |false| argument to ProxyResolver means the ProxyService will handle
// downloading of the PAC script, and notify changes through SetPacScript().
-ProxyResolverV8::ProxyResolverV8() : ProxyResolver(false /*does_fetch*/) {}
+ProxyResolverV8::ProxyResolverV8()
+ : ProxyResolver(false /*does_fetch*/),
+ js_bindings_(new DefaultJSBindings()) {
+}
+
+ProxyResolverV8::ProxyResolverV8(
+ ProxyResolverV8::JSBindings* custom_js_bindings)
+ : ProxyResolver(false), js_bindings_(custom_js_bindings) {
+}
ProxyResolverV8::~ProxyResolverV8() {}
@@ -125,7 +293,7 @@ int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
void ProxyResolverV8::SetPacScript(const std::string& data) {
context_.reset();
if (!data.empty())
- context_.reset(new Context(data));
+ context_.reset(new Context(js_bindings_.get(), data));
}
} // namespace net
diff --git a/net/proxy/proxy_resolver_v8.h b/net/proxy/proxy_resolver_v8.h
index 312b557..ec765fb 100644
--- a/net/proxy/proxy_resolver_v8.h
+++ b/net/proxy/proxy_resolver_v8.h
@@ -32,7 +32,26 @@ namespace net {
// and does not use locking since it expects to be alone.
class ProxyResolverV8 : public ProxyResolver {
public:
+ // Constructs a ProxyResolverV8 with default javascript bindings.
+ //
+ // The default javascript bindings will:
+ // - Send script error messages to LOG(INFO)
+ // - Send script alert()s to LOG(INFO)
+ // - Use the default host mapper to service dnsResolve(), synchronously
+ // on the V8 thread.
+ //
+ // For clients that need more control (for example, sending the script output
+ // to a UI widget), use the ProxyResolverV8(JSBindings*) and specify your
+ // own bindings.
ProxyResolverV8();
+
+ class JSBindings;
+
+ // Constructs a ProxyResolverV8 with custom bindings. ProxyResolverV8 takes
+ // ownership of |custom_js_bindings| and deletes it when ProxyResolverV8
+ // is destroyed.
+ explicit ProxyResolverV8(JSBindings* custom_js_bindings);
+
~ProxyResolverV8();
// ProxyResolver implementation:
@@ -41,6 +60,8 @@ class ProxyResolverV8 : public ProxyResolver {
ProxyInfo* results);
virtual void SetPacScript(const std::string& bytes);
+ JSBindings* js_bindings() const { return js_bindings_.get(); }
+
private:
// Context holds the Javascript state for the most recently loaded PAC
// script. It corresponds with the data from the last call to
@@ -48,9 +69,30 @@ class ProxyResolverV8 : public ProxyResolver {
class Context;
scoped_ptr<Context> context_;
+ scoped_ptr<JSBindings> js_bindings_;
+
DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8);
};
+// Interface for the javascript bindings.
+class ProxyResolverV8::JSBindings {
+ public:
+ virtual ~JSBindings() {}
+
+ // Handler for "alert(message)"
+ virtual void Alert(const std::string& message) = 0;
+
+ // Handler for "myIpAddress()". Returns empty string on failure.
+ virtual std::string MyIpAddress() = 0;
+
+ // Handler for "dnsResolve(host)". Returns empty string on failure.
+ virtual std::string DnsResolve(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;
+};
+
} // namespace net
#endif // NET_PROXY_PROXY_RESOLVER_V8_H_
diff --git a/net/proxy/proxy_resolver_v8_unittest.cc b/net/proxy/proxy_resolver_v8_unittest.cc
index bad4161..5f8bef2 100644
--- a/net/proxy/proxy_resolver_v8_unittest.cc
+++ b/net/proxy/proxy_resolver_v8_unittest.cc
@@ -12,27 +12,80 @@
namespace {
-// Initialize |resolver| with the PAC script data at |filename|.
-void InitWithScriptFromDisk(net::ProxyResolver* resolver,
- const char* filename) {
- FilePath path;
- PathService::Get(base::DIR_SOURCE_ROOT, &path);
- path = path.AppendASCII("net");
- path = path.AppendASCII("data");
- path = path.AppendASCII("proxy_resolver_v8_unittest");
- path = path.AppendASCII(filename);
-
- // Try to read the file from disk.
- std::string file_contents;
- bool ok = file_util::ReadFileToString(path, &file_contents);
-
- // If we can't load the file from disk, something is misconfigured.
- LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value();
- ASSERT_TRUE(ok);
-
- // Load the PAC script into the ProxyResolver.
- resolver->SetPacScript(file_contents);
-}
+// Javascript bindings for ProxyResolverV8, which returns mock values.
+// Each time one of the bindings is called into, we push the input into a
+// list, for later verification.
+class MockJSBindings : public net::ProxyResolverV8::JSBindings {
+ public:
+ MockJSBindings() : my_ip_address_count(0) {}
+
+ virtual void Alert(const std::string& message) {
+ LOG(INFO) << "PAC-alert: " << message; // Helpful when debugging.
+ alerts.push_back(message);
+ }
+
+ virtual std::string MyIpAddress() {
+ my_ip_address_count++;
+ return my_ip_address_result;
+ }
+
+ virtual std::string DnsResolve(const std::string& host) {
+ dns_resolves.push_back(host);
+ return dns_resolve_result;
+ }
+
+ virtual void OnError(int line_number, const std::string& message) {
+ // Helpful when debugging.
+ LOG(INFO) << "PAC-error: [" << line_number << "] " << message;
+
+ errors.push_back(message);
+ errors_line_number.push_back(line_number);
+ }
+
+ // Mock values to return.
+ std::string my_ip_address_result;
+ std::string dns_resolve_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;
+ int my_ip_address_count;
+};
+
+// This is the same as ProxyResolverV8, but it uses mock bindings in place of
+// the default bindings, and has a helper function to load PAC scripts from
+// disk.
+class ProxyResolverV8WithMockBindings : public net::ProxyResolverV8 {
+ public:
+ ProxyResolverV8WithMockBindings() : ProxyResolverV8(new MockJSBindings()) {}
+
+ MockJSBindings* mock_js_bindings() const {
+ return reinterpret_cast<MockJSBindings*>(js_bindings());
+ }
+
+ // Initialize with the PAC script data at |filename|.
+ void SetPacScriptFromDisk(const char* filename) {
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_resolver_v8_unittest");
+ path = path.AppendASCII(filename);
+
+ // Try to read the file from disk.
+ std::string file_contents;
+ bool ok = file_util::ReadFileToString(path, &file_contents);
+
+ // If we can't load the file from disk, something is misconfigured.
+ LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value();
+ ASSERT_TRUE(ok);
+
+ // Load the PAC script into the ProxyResolver.
+ SetPacScript(file_contents);
+ }
+};
// Doesn't really matter what these values are for many of the tests.
const GURL kQueryUrl("http://www.google.com");
@@ -41,30 +94,36 @@ const GURL kPacUrl;
}
TEST(ProxyResolverV8Test, Direct) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "direct.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("direct.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::OK, result);
EXPECT_TRUE(proxy_info.is_direct());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
}
TEST(ProxyResolverV8Test, ReturnEmptyString) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "return_empty_string.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("return_empty_string.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::OK, result);
EXPECT_TRUE(proxy_info.is_direct());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
}
TEST(ProxyResolverV8Test, Basic) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "passthrough.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("passthrough.js");
// The "FindProxyForURL" of this PAC script simply concatenates all of the
// arguments into a pseudo-host. The purpose of this test is to verify that
@@ -86,6 +145,9 @@ TEST(ProxyResolverV8Test, Basic) {
// the port number.
EXPECT_EQ("ftp.query.com.90.path.query.com:80",
proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
}
}
@@ -103,42 +165,69 @@ TEST(ProxyResolverV8Test, BadReturnType) {
};
for (size_t i = 0; i < arraysize(filenames); ++i) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, filenames[i]);
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk(filenames[i]);
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(1U, bindings->errors.size());
+ EXPECT_EQ("FindProxyForURL() did not return a string.",
+ bindings->errors[0]);
+ EXPECT_EQ(-1, bindings->errors_line_number[0]);
}
}
// Try using a PAC script which defines no "FindProxyForURL" function.
TEST(ProxyResolverV8Test, NoEntryPoint) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "no_entrypoint.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("no_entrypoint.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(1U, bindings->errors.size());
+ EXPECT_EQ("FindProxyForURL() is undefined.", bindings->errors[0]);
+ EXPECT_EQ(-1, bindings->errors_line_number[0]);
}
// Try loading a malformed PAC script.
TEST(ProxyResolverV8Test, ParseError) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "missing_close_brace.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("missing_close_brace.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+
+ // We get two errors -- one during compilation, and then later when
+ // trying to run FindProxyForURL().
+ ASSERT_EQ(2U, bindings->errors.size());
+
+ EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
+ bindings->errors[0]);
+ EXPECT_EQ(-1, bindings->errors_line_number[0]);
+
+ EXPECT_EQ("FindProxyForURL() is undefined.", bindings->errors[1]);
+ EXPECT_EQ(-1, bindings->errors_line_number[1]);
}
// Run a PAC script several times, which has side-effects.
TEST(ProxyResolverV8Test, SideEffects) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "side_effects.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("side_effects.js");
// The PAC script increments a counter each time we invoke it.
for (int i = 0; i < 3; ++i) {
@@ -151,7 +240,7 @@ TEST(ProxyResolverV8Test, SideEffects) {
// Reload the script -- the javascript environment should be reset, hence
// the counter starts over.
- InitWithScriptFromDisk(&resolver, "side_effects.js");
+ resolver.SetPacScriptFromDisk("side_effects.js");
for (int i = 0; i < 3; ++i) {
net::ProxyInfo proxy_info;
@@ -164,20 +253,27 @@ TEST(ProxyResolverV8Test, SideEffects) {
// Execute a PAC script which throws an exception in FindProxyForURL.
TEST(ProxyResolverV8Test, UnhandledException) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "unhandled_exception.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("unhandled_exception.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(1U, bindings->errors.size());
+ EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined",
+ bindings->errors[0]);
+ EXPECT_EQ(3, bindings->errors_line_number[0]);
}
// TODO(eroman): This test is disabed right now, since the parsing of
// host/port doesn't check for non-ascii characters.
TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "return_unicode.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("return_unicode.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
@@ -189,8 +285,8 @@ TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) {
// Test the PAC library functions that we expose in the JS environmnet.
TEST(ProxyResolverV8Test, JavascriptLibrary) {
- net::ProxyResolverV8 resolver;
- InitWithScriptFromDisk(&resolver, "pac_library_unittest.js");
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("pac_library_unittest.js");
net::ProxyInfo proxy_info;
int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
@@ -199,11 +295,14 @@ TEST(ProxyResolverV8Test, JavascriptLibrary) {
// exception. Otherwise it will return "PROXY success:80".
EXPECT_EQ(net::OK, result);
EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
}
// Try resolving when SetPacScript() has not been called.
TEST(ProxyResolverV8Test, NoSetPacScript) {
- net::ProxyResolverV8 resolver;
+ ProxyResolverV8WithMockBindings resolver;
net::ProxyInfo proxy_info;
@@ -212,7 +311,7 @@ TEST(ProxyResolverV8Test, NoSetPacScript) {
EXPECT_EQ(net::ERR_FAILED, result);
// Initialize it.
- InitWithScriptFromDisk(&resolver, "direct.js");
+ resolver.SetPacScriptFromDisk("direct.js");
// Resolve should now succeed.
result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
@@ -226,8 +325,101 @@ TEST(ProxyResolverV8Test, NoSetPacScript) {
EXPECT_EQ(net::ERR_FAILED, result);
// Load a good script once more.
- InitWithScriptFromDisk(&resolver, "direct.js");
+ resolver.SetPacScriptFromDisk("direct.js");
result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
EXPECT_EQ(net::OK, result);
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+// Test marshalling/un-marshalling of values between C++/V8.
+TEST(ProxyResolverV8Test, V8Bindings) {
+ ProxyResolverV8WithMockBindings resolver;
+ resolver.SetPacScriptFromDisk("bindings.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+
+ // Alert was called 5 times.
+ ASSERT_EQ(5U, bindings->alerts.size());
+ EXPECT_EQ("undefined", bindings->alerts[0]);
+ EXPECT_EQ("null", bindings->alerts[1]);
+ EXPECT_EQ("undefined", bindings->alerts[2]);
+ EXPECT_EQ("[object Object]", bindings->alerts[3]);
+ EXPECT_EQ("exception from calling toString()", bindings->alerts[4]);
+
+ // DnsResolve was called 8 times.
+ ASSERT_EQ(8U, bindings->dns_resolves.size());
+ EXPECT_EQ("undefined", bindings->dns_resolves[0]);
+ EXPECT_EQ("null", bindings->dns_resolves[1]);
+ EXPECT_EQ("undefined", bindings->dns_resolves[2]);
+ EXPECT_EQ("", bindings->dns_resolves[3]);
+ EXPECT_EQ("[object Object]", bindings->dns_resolves[4]);
+ EXPECT_EQ("function fn() {}", bindings->dns_resolves[5]);
+
+ // TODO(eroman): This isn't quite right... should probably stringize
+ // to something like "['3']".
+ EXPECT_EQ("3", bindings->dns_resolves[6]);
+
+ EXPECT_EQ("arg1", bindings->dns_resolves[7]);
+
+ // MyIpAddress was called two times.
+ EXPECT_EQ(2, bindings->my_ip_address_count);
+}
+
+TEST(ProxyResolverV8DefaultBindingsTest, DnsResolve) {
+ // Get a hold of a DefaultJSBindings* (it is a hidden impl class).
+ net::ProxyResolverV8 resolver;
+ net::ProxyResolverV8::JSBindings* bindings = resolver.js_bindings();
+
+ const struct {
+ const char* input;
+ const char* expected;
+ } tests[] = {
+ {"www.google.com", "127.0.0.1"},
+ {"", ""},
+ {".", ""},
+ {"foo@google.com", ""},
+ {"@google.com", ""},
+ {"foo:pass@google.com", ""},
+ {"%", ""},
+ {"www.google.com:80", ""},
+ {"www.google.com:", ""},
+ {"www.google.com.", ""},
+ {"#", ""},
+ {"127.0.0.1", ""},
+ {"this has spaces", ""},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string actual = bindings->DnsResolve(tests[i].input);
+
+ // ########################################################################
+ // TODO(eroman)
+ // ########################################################################
+ // THIS TEST IS CURRENTLY FLAWED.
+ //
+ // Since we are running in unit-test mode, the HostResolve is using a
+ // mock HostMapper, which will always return 127.0.0.1, without going
+ // through the real codepaths.
+ //
+ // It is important that these tests be run with the real thing, since we
+ // need to verify that HostResolver doesn't blow up when you send it
+ // weird inputs. This is necessary since the data reach it is UNSANITIZED.
+ // It comes directly from the PAC javascript.
+ //
+ // For now we just check that it maps to 127.0.0.1.
+ std::string expected = tests[i].expected;
+ if (expected == "")
+ expected = "127.0.0.1";
+ EXPECT_EQ(expected, actual);
+ }
}