// Copyright (c) 2010 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. #define LOG_TAG "ProxyResolverTest" #include <utils/Log.h> #include <string.h> #include <gtest/gtest.h> #include "android_runtime/AndroidRuntime.h" #include "proxy_test_script.h" #include "proxy_resolver_v8.h" using namespace android; namespace net { namespace { // 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 ProxyResolverJSBindings, public ProxyErrorListener { public: MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {} virtual bool MyIpAddress(std::string* ip_address) { my_ip_address_count++; *ip_address = my_ip_address_result; return !my_ip_address_result.empty(); } virtual bool MyIpAddressEx(std::string* ip_address_list) { my_ip_address_ex_count++; *ip_address_list = my_ip_address_ex_result; return !my_ip_address_ex_result.empty(); } virtual bool DnsResolve(const std::string& host, std::string* ip_address) { dns_resolves.push_back(host); *ip_address = dns_resolve_result; return !dns_resolve_result.empty(); } virtual bool DnsResolveEx(const std::string& host, std::string* ip_address_list) { dns_resolves_ex.push_back(host); *ip_address_list = dns_resolve_ex_result; return !dns_resolve_ex_result.empty(); } virtual void AlertMessage(String16 message) { String8 m8(message); std::string mstd(m8.string()); ALOGD("PAC-alert: %s\n", mstd.c_str()); // Helpful when debugging. alerts.push_back(mstd); } virtual void ErrorMessage(const String16 message) { String8 m8(message); std::string mstd(m8.string()); ALOGD("PAC-error: %s\n", mstd.c_str()); // Helpful when debugging. errors.push_back(mstd); } virtual void Shutdown() {} // 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<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 // the default bindings, and has a helper function to load PAC scripts from // disk. class ProxyResolverV8WithMockBindings : public ProxyResolverV8 { public: ProxyResolverV8WithMockBindings(MockJSBindings* mock_js_bindings) : ProxyResolverV8(mock_js_bindings, mock_js_bindings), mock_js_bindings_(mock_js_bindings) { } MockJSBindings* mock_js_bindings() const { return mock_js_bindings_; } private: MockJSBindings* mock_js_bindings_; }; // Doesn't really matter what these values are for many of the tests. const String16 kQueryUrl("http://www.google.com"); const String16 kQueryHost("www.google.com"); String16 kResults; String16 currentPac; #define SCRIPT(x) (currentPac = String16(x)) void addString(std::vector<std::string>* list, std::string str) { if (str.compare(0, 6, "DIRECT") == 0) { list->push_back("DIRECT"); } else if (str.compare(0, 6, "PROXY ") == 0) { list->push_back(str.substr(6)); } else { ALOGE("Unrecognized proxy string"); } } std::vector<std::string> string16ToProxyList(String16 response) { std::vector<std::string> ret; String8 response8(response); std::string rstr(response8.string()); if (rstr.find(';') == std::string::npos) { addString(&ret, rstr); return ret; } char str[128]; rstr.copy(str, 0, rstr.length()); const char* pch = strtok(str, ";"); while (pch != NULL) { // Skip leading whitespace while ((*pch) == ' ') ++pch; std::string pstring(pch); addString(&ret, pstring); pch = strtok(NULL, "; \t"); } return ret; } std::string StringPrintf(std::string str, int d) { char buf[30]; sprintf(buf, str.c_str(), d); return std::string(buf); } TEST(ProxyResolverV8Test, Direct) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(proxies.size(), 1U); EXPECT_EQ("DIRECT",proxies[0]); EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); } TEST(ProxyResolverV8Test, ReturnEmptyString) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(RETURN_EMPTY_STRING_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(proxies.size(), 0U); EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); } TEST(ProxyResolverV8Test, Basic) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(PASSTHROUGH_JS)); EXPECT_EQ(OK, result); // 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 // the correct arguments are being passed to FindProxyForURL(). { String16 queryUrl("http://query.com/path"); String16 queryHost("query.com"); result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ("http.query.com.path.query.com", proxies[0]); } { String16 queryUrl("ftp://query.com:90/path"); String16 queryHost("query.com"); int result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults); EXPECT_EQ(OK, result); // Note that FindProxyForURL(url, host) does not expect |host| to contain // the port number. std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ("ftp.query.com.90.path.query.com", proxies[0]); EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); } // We call this so we'll have code coverage of the function and valgrind will // make sure nothing bad happens. // // NOTE: This is here instead of in its own test so that we'll be calling it // after having done something, in hopes it won't be a no-op. resolver.PurgeMemory(); } TEST(ProxyResolverV8Test, BadReturnType) { // These are the files of PAC scripts which each return a non-string // types for FindProxyForURL(). They should all fail with // ERR_PAC_SCRIPT_FAILED. static const String16 files[] = { String16(RETURN_UNDEFINED_JS), String16(RETURN_INTEGER_JS), String16(RETURN_FUNCTION_JS), String16(RETURN_OBJECT_JS), String16(RETURN_NULL_JS) }; for (size_t i = 0; i < 5; ++i) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(files[i]); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(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]); } } // Try using a PAC script which defines no "FindProxyForURL" function. TEST(ProxyResolverV8Test, NoEntryPoint) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(NO_ENTRYPOINT_JS)); EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(ERR_FAILED, result); } // Try loading a malformed PAC script. TEST(ProxyResolverV8Test, ParseError) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(MISSING_CLOSE_BRACE_JS)); EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(ERR_FAILED, result); MockJSBindings* bindings = resolver.mock_js_bindings(); EXPECT_EQ(0U, bindings->alerts.size()); // We get one error during compilation. ASSERT_EQ(1U, bindings->errors.size()); EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", bindings->errors[0]); } // Run a PAC script several times, which has side-effects. TEST(ProxyResolverV8Test, SideEffects) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS)); // The PAC script increments a counter each time we invoke it. for (int i = 0; i < 3; ++i) { result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ(StringPrintf("sideffect_%d", i), proxies[0]); } // Reload the script -- the javascript environment should be reset, hence // the counter starts over. result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS)); EXPECT_EQ(OK, result); for (int i = 0; i < 3; ++i) { result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ(StringPrintf("sideffect_%d", i), proxies[0]); } } // Execute a PAC script which throws an exception in FindProxyForURL. TEST(ProxyResolverV8Test, UnhandledException) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(UNHANDLED_EXCEPTION_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(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]); } TEST(ProxyResolverV8Test, ReturnUnicode) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(RETURN_UNICODE_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); // The result from this resolve was unparseable, because it // wasn't ASCII. EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); } // Test the PAC library functions that we expose in the JS environmnet. TEST(ProxyResolverV8Test, JavascriptLibrary) { ALOGE("Javascript start"); ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(PAC_LIBRARY_UNITTEST_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); // If the javascript side of this unit-test fails, it will throw a javascript // exception. Otherwise it will return "PROXY success:80". EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ("success:80", proxies[0]); EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); } // Try resolving when SetPacScriptByData() has not been called. TEST(ProxyResolverV8Test, NoSetPacScript) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); // Resolve should fail, as we are not yet initialized with a script. int result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(ERR_FAILED, result); // Initialize it. result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); EXPECT_EQ(OK, result); // Resolve should now succeed. result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); // Clear it, by initializing with an empty string. resolver.SetPacScript(SCRIPT()); // Resolve should fail again now. result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(ERR_FAILED, result); // Load a good script once more. result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(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(new MockJSBindings()); MockJSBindings* bindings = resolver.mock_js_bindings(); bindings->dns_resolve_result = "127.0.0.1"; int result = resolver.SetPacScript(SCRIPT(BINDINGS_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ("DIRECT", proxies[0]); 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, however only 2 of those were string // parameters. (so 6 of them failed immediately). ASSERT_EQ(2U, bindings->dns_resolves.size()); EXPECT_EQ("", bindings->dns_resolves[0]); EXPECT_EQ("arg1", bindings->dns_resolves[1]); // 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 calling a binding (myIpAddress()) from the script's global scope. // http://crbug.com/40026 TEST(ProxyResolverV8Test, BindingCalledDuringInitialization) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(BINDING_FROM_GLOBAL_JS)); EXPECT_EQ(OK, result); MockJSBindings* bindings = resolver.mock_js_bindings(); // myIpAddress() got called during initialization of the script. EXPECT_EQ(1, bindings->my_ip_address_count); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_NE("DIRECT", proxies[0]); EXPECT_EQ("127.0.0.1:80", proxies[0]); // Check that no other bindings were called. EXPECT_EQ(0U, bindings->errors.size()); ASSERT_EQ(0U, bindings->alerts.size()); ASSERT_EQ(0U, bindings->dns_resolves.size()); EXPECT_EQ(0, bindings->my_ip_address_ex_count); ASSERT_EQ(0U, bindings->dns_resolves_ex.size()); } // Try loading a PAC script that ends with a comment and has no terminal // newline. This should not cause problems with the PAC utility functions // that we add to the script's environment. // http://crbug.com/22864 TEST(ProxyResolverV8Test, EndsWithCommentNoNewline) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(ENDS_WITH_COMMENT_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_NE("DIRECT", proxies[0]); EXPECT_EQ("success:80", proxies[0]); } // Try loading a PAC script that ends with a statement and has no terminal // newline. This should not cause problems with the PAC utility functions // that we add to the script's environment. // http://crbug.com/22864 TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) { ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript( SCRIPT(ENDS_WITH_STATEMENT_NO_SEMICOLON_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_NE("DIRECT", proxies[0]); EXPECT_EQ("success:3", proxies[0]); } // 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(new MockJSBindings()); int result = resolver.SetPacScript(SCRIPT(DNS_FAIL_JS)); EXPECT_EQ(OK, result); result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_NE("DIRECT", proxies[0]); EXPECT_EQ("success:80", proxies[0]); } TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) { return; ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); int result = resolver.SetPacScript(String16(INTERNATIONAL_DOMAIN_NAMES_JS)); EXPECT_EQ(OK, result); // Execute FindProxyForURL(). result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); EXPECT_EQ(OK, result); std::vector<std::string> proxies = string16ToProxyList(kResults); EXPECT_EQ(1U, proxies.size()); EXPECT_EQ("DIRECT", proxies[0]); // Check that the international domain name was converted to punycode // before passing it onto the bindings layer. MockJSBindings* bindings = resolver.mock_js_bindings(); ASSERT_EQ(1u, bindings->dns_resolves.size()); EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves[0]); ASSERT_EQ(1u, bindings->dns_resolves_ex.size()); EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves_ex[0]); } } // namespace } // namespace net