diff options
-rw-r--r-- | net/base/net_util.cc | 25 | ||||
-rw-r--r-- | net/base/net_util.h | 5 | ||||
-rw-r--r-- | net/base/net_util_unittest.cc | 89 | ||||
-rw-r--r-- | net/data/proxy_resolver_v8_unittest/bindings.js | 46 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.cc | 200 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8.h | 42 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_v8_unittest.cc | 282 |
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); + } } |