1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #define LOG_TAG "ProxyResolverTest" 6 #include <utils/Log.h> 7 8 #include <string.h> 9 10 #include <gtest/gtest.h> 11 12 #include "android_runtime/AndroidRuntime.h" 13 #include "proxy_test_script.h" 14 #include "proxy_resolver_v8.h" 15 16 using namespace android; 17 namespace net { 18 namespace { 19 20 // Javascript bindings for ProxyResolverV8, which returns mock values. 21 // Each time one of the bindings is called into, we push the input into a 22 // list, for later verification. 23 class MockJSBindings : public ProxyResolverJSBindings, public ProxyErrorListener { 24 public: 25 MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {} 26 27 virtual bool MyIpAddress(std::string* ip_address) { 28 my_ip_address_count++; 29 *ip_address = my_ip_address_result; 30 return !my_ip_address_result.empty(); 31 } 32 33 virtual bool MyIpAddressEx(std::string* ip_address_list) { 34 my_ip_address_ex_count++; 35 *ip_address_list = my_ip_address_ex_result; 36 return !my_ip_address_ex_result.empty(); 37 } 38 39 virtual bool DnsResolve(const std::string& host, std::string* ip_address) { 40 dns_resolves.push_back(host); 41 *ip_address = dns_resolve_result; 42 return !dns_resolve_result.empty(); 43 } 44 45 virtual bool DnsResolveEx(const std::string& host, 46 std::string* ip_address_list) { 47 dns_resolves_ex.push_back(host); 48 *ip_address_list = dns_resolve_ex_result; 49 return !dns_resolve_ex_result.empty(); 50 } 51 52 virtual void AlertMessage(String16 message) { 53 String8 m8(message); 54 std::string mstd(m8.string()); 55 56 ALOGD("PAC-alert: %s\n", mstd.c_str()); // Helpful when debugging. 57 alerts.push_back(mstd); 58 } 59 60 virtual void ErrorMessage(const String16 message) { 61 String8 m8(message); 62 std::string mstd(m8.string()); 63 64 ALOGD("PAC-error: %s\n", mstd.c_str()); // Helpful when debugging. 65 errors.push_back(mstd); 66 } 67 68 virtual void Shutdown() {} 69 70 // Mock values to return. 71 std::string my_ip_address_result; 72 std::string my_ip_address_ex_result; 73 std::string dns_resolve_result; 74 std::string dns_resolve_ex_result; 75 76 // Inputs we got called with. 77 std::vector<std::string> alerts; 78 std::vector<std::string> errors; 79 std::vector<std::string> dns_resolves; 80 std::vector<std::string> dns_resolves_ex; 81 int my_ip_address_count; 82 int my_ip_address_ex_count; 83 }; 84 85 // This is the same as ProxyResolverV8, but it uses mock bindings in place of 86 // the default bindings, and has a helper function to load PAC scripts from 87 // disk. 88 class ProxyResolverV8WithMockBindings : public ProxyResolverV8 { 89 public: 90 ProxyResolverV8WithMockBindings(MockJSBindings* mock_js_bindings) : 91 ProxyResolverV8(mock_js_bindings, mock_js_bindings), mock_js_bindings_(mock_js_bindings) { 92 } 93 94 MockJSBindings* mock_js_bindings() const { 95 return mock_js_bindings_; 96 } 97 98 private: 99 MockJSBindings* mock_js_bindings_; 100 }; 101 102 // Doesn't really matter what these values are for many of the tests. 103 const String16 kQueryUrl("http://www.google.com"); 104 const String16 kQueryHost("www.google.com"); 105 String16 kResults; 106 107 String16 currentPac; 108 #define SCRIPT(x) (currentPac = String16(x)) 109 110 void addString(std::vector<std::string>* list, std::string str) { 111 if (str.compare(0, 6, "DIRECT") == 0) { 112 list->push_back("DIRECT"); 113 } else if (str.compare(0, 6, "PROXY ") == 0) { 114 list->push_back(str.substr(6)); 115 } else { 116 ALOGE("Unrecognized proxy string"); 117 } 118 } 119 120 std::vector<std::string> string16ToProxyList(String16 response) { 121 std::vector<std::string> ret; 122 String8 response8(response); 123 std::string rstr(response8.string()); 124 if (rstr.find(';') == std::string::npos) { 125 addString(&ret, rstr); 126 return ret; 127 } 128 char str[128]; 129 rstr.copy(str, 0, rstr.length()); 130 const char* pch = strtok(str, ";"); 131 132 while (pch != NULL) { 133 // Skip leading whitespace 134 while ((*pch) == ' ') ++pch; 135 std::string pstring(pch); 136 addString(&ret, pstring); 137 138 pch = strtok(NULL, "; \t"); 139 } 140 141 return ret; 142 } 143 144 std::string StringPrintf(std::string str, int d) { 145 char buf[30]; 146 sprintf(buf, str.c_str(), d); 147 return std::string(buf); 148 } 149 150 TEST(ProxyResolverV8Test, Direct) { 151 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 152 int result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); 153 EXPECT_EQ(OK, result); 154 155 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 156 157 EXPECT_EQ(OK, result); 158 std::vector<std::string> proxies = string16ToProxyList(kResults); 159 EXPECT_EQ(proxies.size(), 1U); 160 EXPECT_EQ("DIRECT",proxies[0]); 161 162 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 163 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 164 } 165 166 TEST(ProxyResolverV8Test, ReturnEmptyString) { 167 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 168 int result = resolver.SetPacScript(SCRIPT(RETURN_EMPTY_STRING_JS)); 169 EXPECT_EQ(OK, result); 170 171 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 172 173 EXPECT_EQ(OK, result); 174 std::vector<std::string> proxies = string16ToProxyList(kResults); 175 EXPECT_EQ(proxies.size(), 0U); 176 177 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 178 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 179 } 180 181 TEST(ProxyResolverV8Test, Basic) { 182 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 183 int result = resolver.SetPacScript(SCRIPT(PASSTHROUGH_JS)); 184 EXPECT_EQ(OK, result); 185 186 // The "FindProxyForURL" of this PAC script simply concatenates all of the 187 // arguments into a pseudo-host. The purpose of this test is to verify that 188 // the correct arguments are being passed to FindProxyForURL(). 189 { 190 String16 queryUrl("http://query.com/path"); 191 String16 queryHost("query.com"); 192 result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults); 193 EXPECT_EQ(OK, result); 194 std::vector<std::string> proxies = string16ToProxyList(kResults); 195 EXPECT_EQ(1U, proxies.size()); 196 EXPECT_EQ("http.query.com.path.query.com", proxies[0]); 197 } 198 { 199 String16 queryUrl("ftp://query.com:90/path"); 200 String16 queryHost("query.com"); 201 int result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults); 202 203 EXPECT_EQ(OK, result); 204 // Note that FindProxyForURL(url, host) does not expect |host| to contain 205 // the port number. 206 std::vector<std::string> proxies = string16ToProxyList(kResults); 207 EXPECT_EQ(1U, proxies.size()); 208 EXPECT_EQ("ftp.query.com.90.path.query.com", proxies[0]); 209 210 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 211 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 212 } 213 214 // We call this so we'll have code coverage of the function and valgrind will 215 // make sure nothing bad happens. 216 // 217 // NOTE: This is here instead of in its own test so that we'll be calling it 218 // after having done something, in hopes it won't be a no-op. 219 resolver.PurgeMemory(); 220 } 221 222 TEST(ProxyResolverV8Test, BadReturnType) { 223 // These are the files of PAC scripts which each return a non-string 224 // types for FindProxyForURL(). They should all fail with 225 // ERR_PAC_SCRIPT_FAILED. 226 static const String16 files[] = { 227 String16(RETURN_UNDEFINED_JS), 228 String16(RETURN_INTEGER_JS), 229 String16(RETURN_FUNCTION_JS), 230 String16(RETURN_OBJECT_JS), 231 String16(RETURN_NULL_JS) 232 }; 233 234 for (size_t i = 0; i < 5; ++i) { 235 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 236 int result = resolver.SetPacScript(files[i]); 237 EXPECT_EQ(OK, result); 238 239 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 240 241 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 242 243 MockJSBindings* bindings = resolver.mock_js_bindings(); 244 EXPECT_EQ(0U, bindings->alerts.size()); 245 ASSERT_EQ(1U, bindings->errors.size()); 246 EXPECT_EQ("FindProxyForURL() did not return a string.", 247 bindings->errors[0]); 248 } 249 } 250 251 // Try using a PAC script which defines no "FindProxyForURL" function. 252 TEST(ProxyResolverV8Test, NoEntryPoint) { 253 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 254 int result = resolver.SetPacScript(SCRIPT(NO_ENTRYPOINT_JS)); 255 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 256 257 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 258 259 EXPECT_EQ(ERR_FAILED, result); 260 } 261 262 // Try loading a malformed PAC script. 263 TEST(ProxyResolverV8Test, ParseError) { 264 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 265 int result = resolver.SetPacScript(SCRIPT(MISSING_CLOSE_BRACE_JS)); 266 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 267 268 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 269 270 EXPECT_EQ(ERR_FAILED, result); 271 272 MockJSBindings* bindings = resolver.mock_js_bindings(); 273 EXPECT_EQ(0U, bindings->alerts.size()); 274 275 // We get one error during compilation. 276 ASSERT_EQ(1U, bindings->errors.size()); 277 278 EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", 279 bindings->errors[0]); 280 } 281 282 // Run a PAC script several times, which has side-effects. 283 TEST(ProxyResolverV8Test, SideEffects) { 284 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 285 int result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS)); 286 287 // The PAC script increments a counter each time we invoke it. 288 for (int i = 0; i < 3; ++i) { 289 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 290 EXPECT_EQ(OK, result); 291 std::vector<std::string> proxies = string16ToProxyList(kResults); 292 EXPECT_EQ(1U, proxies.size()); 293 EXPECT_EQ(StringPrintf("sideffect_%d", i), 294 proxies[0]); 295 } 296 297 // Reload the script -- the javascript environment should be reset, hence 298 // the counter starts over. 299 result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS)); 300 EXPECT_EQ(OK, result); 301 302 for (int i = 0; i < 3; ++i) { 303 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 304 EXPECT_EQ(OK, result); 305 std::vector<std::string> proxies = string16ToProxyList(kResults); 306 EXPECT_EQ(1U, proxies.size()); 307 EXPECT_EQ(StringPrintf("sideffect_%d", i), 308 proxies[0]); 309 } 310 } 311 312 // Execute a PAC script which throws an exception in FindProxyForURL. 313 TEST(ProxyResolverV8Test, UnhandledException) { 314 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 315 int result = resolver.SetPacScript(SCRIPT(UNHANDLED_EXCEPTION_JS)); 316 EXPECT_EQ(OK, result); 317 318 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 319 320 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 321 322 MockJSBindings* bindings = resolver.mock_js_bindings(); 323 EXPECT_EQ(0U, bindings->alerts.size()); 324 ASSERT_EQ(1U, bindings->errors.size()); 325 EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined", 326 bindings->errors[0]); 327 } 328 329 TEST(ProxyResolverV8Test, ReturnUnicode) { 330 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 331 int result = resolver.SetPacScript(SCRIPT(RETURN_UNICODE_JS)); 332 EXPECT_EQ(OK, result); 333 334 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 335 336 // The result from this resolve was unparseable, because it 337 // wasn't ASCII. 338 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 339 } 340 341 // Test the PAC library functions that we expose in the JS environmnet. 342 TEST(ProxyResolverV8Test, JavascriptLibrary) { 343 ALOGE("Javascript start"); 344 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 345 int result = resolver.SetPacScript(SCRIPT(PAC_LIBRARY_UNITTEST_JS)); 346 EXPECT_EQ(OK, result); 347 348 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 349 350 // If the javascript side of this unit-test fails, it will throw a javascript 351 // exception. Otherwise it will return "PROXY success:80". 352 EXPECT_EQ(OK, result); 353 std::vector<std::string> proxies = string16ToProxyList(kResults); 354 EXPECT_EQ(1U, proxies.size()); 355 EXPECT_EQ("success:80", proxies[0]); 356 357 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 358 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 359 } 360 361 // Try resolving when SetPacScriptByData() has not been called. 362 TEST(ProxyResolverV8Test, NoSetPacScript) { 363 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 364 365 366 // Resolve should fail, as we are not yet initialized with a script. 367 int result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 368 EXPECT_EQ(ERR_FAILED, result); 369 370 // Initialize it. 371 result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); 372 EXPECT_EQ(OK, result); 373 374 // Resolve should now succeed. 375 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 376 EXPECT_EQ(OK, result); 377 378 // Clear it, by initializing with an empty string. 379 resolver.SetPacScript(SCRIPT()); 380 381 // Resolve should fail again now. 382 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 383 EXPECT_EQ(ERR_FAILED, result); 384 385 // Load a good script once more. 386 result = resolver.SetPacScript(SCRIPT(DIRECT_JS)); 387 EXPECT_EQ(OK, result); 388 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 389 EXPECT_EQ(OK, result); 390 391 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 392 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 393 } 394 395 // Test marshalling/un-marshalling of values between C++/V8. 396 TEST(ProxyResolverV8Test, V8Bindings) { 397 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 398 MockJSBindings* bindings = resolver.mock_js_bindings(); 399 bindings->dns_resolve_result = "127.0.0.1"; 400 int result = resolver.SetPacScript(SCRIPT(BINDINGS_JS)); 401 EXPECT_EQ(OK, result); 402 403 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 404 405 EXPECT_EQ(OK, result); 406 std::vector<std::string> proxies = string16ToProxyList(kResults); 407 EXPECT_EQ(1U, proxies.size()); 408 EXPECT_EQ("DIRECT", proxies[0]); 409 410 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 411 412 // Alert was called 5 times. 413 ASSERT_EQ(5U, bindings->alerts.size()); 414 EXPECT_EQ("undefined", bindings->alerts[0]); 415 EXPECT_EQ("null", bindings->alerts[1]); 416 EXPECT_EQ("undefined", bindings->alerts[2]); 417 EXPECT_EQ("[object Object]", bindings->alerts[3]); 418 EXPECT_EQ("exception from calling toString()", bindings->alerts[4]); 419 420 // DnsResolve was called 8 times, however only 2 of those were string 421 // parameters. (so 6 of them failed immediately). 422 ASSERT_EQ(2U, bindings->dns_resolves.size()); 423 EXPECT_EQ("", bindings->dns_resolves[0]); 424 EXPECT_EQ("arg1", bindings->dns_resolves[1]); 425 426 // MyIpAddress was called two times. 427 EXPECT_EQ(2, bindings->my_ip_address_count); 428 429 // MyIpAddressEx was called once. 430 EXPECT_EQ(1, bindings->my_ip_address_ex_count); 431 432 // DnsResolveEx was called 2 times. 433 ASSERT_EQ(2U, bindings->dns_resolves_ex.size()); 434 EXPECT_EQ("is_resolvable", bindings->dns_resolves_ex[0]); 435 EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]); 436 } 437 438 // Test calling a binding (myIpAddress()) from the script's global scope. 439 // http://crbug.com/40026 440 TEST(ProxyResolverV8Test, BindingCalledDuringInitialization) { 441 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 442 443 int result = resolver.SetPacScript(SCRIPT(BINDING_FROM_GLOBAL_JS)); 444 EXPECT_EQ(OK, result); 445 446 MockJSBindings* bindings = resolver.mock_js_bindings(); 447 448 // myIpAddress() got called during initialization of the script. 449 EXPECT_EQ(1, bindings->my_ip_address_count); 450 451 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 452 453 EXPECT_EQ(OK, result); 454 std::vector<std::string> proxies = string16ToProxyList(kResults); 455 EXPECT_EQ(1U, proxies.size()); 456 EXPECT_NE("DIRECT", proxies[0]); 457 EXPECT_EQ("127.0.0.1:80", proxies[0]); 458 459 // Check that no other bindings were called. 460 EXPECT_EQ(0U, bindings->errors.size()); 461 ASSERT_EQ(0U, bindings->alerts.size()); 462 ASSERT_EQ(0U, bindings->dns_resolves.size()); 463 EXPECT_EQ(0, bindings->my_ip_address_ex_count); 464 ASSERT_EQ(0U, bindings->dns_resolves_ex.size()); 465 } 466 467 // Try loading a PAC script that ends with a comment and has no terminal 468 // newline. This should not cause problems with the PAC utility functions 469 // that we add to the script's environment. 470 // http://crbug.com/22864 471 TEST(ProxyResolverV8Test, EndsWithCommentNoNewline) { 472 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 473 int result = resolver.SetPacScript(SCRIPT(ENDS_WITH_COMMENT_JS)); 474 EXPECT_EQ(OK, result); 475 476 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 477 478 EXPECT_EQ(OK, result); 479 std::vector<std::string> proxies = string16ToProxyList(kResults); 480 EXPECT_EQ(1U, proxies.size()); 481 EXPECT_NE("DIRECT", proxies[0]); 482 EXPECT_EQ("success:80", proxies[0]); 483 } 484 485 // Try loading a PAC script that ends with a statement and has no terminal 486 // newline. This should not cause problems with the PAC utility functions 487 // that we add to the script's environment. 488 // http://crbug.com/22864 489 TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) { 490 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 491 int result = resolver.SetPacScript( 492 SCRIPT(ENDS_WITH_STATEMENT_NO_SEMICOLON_JS)); 493 EXPECT_EQ(OK, result); 494 495 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 496 497 EXPECT_EQ(OK, result); 498 std::vector<std::string> proxies = string16ToProxyList(kResults); 499 EXPECT_EQ(1U, proxies.size()); 500 EXPECT_NE("DIRECT", proxies[0]); 501 EXPECT_EQ("success:3", proxies[0]); 502 } 503 504 // Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(), 505 // dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding 506 // returns empty string (failure). This simulates the return values from 507 // those functions when the underlying DNS resolution fails. 508 TEST(ProxyResolverV8Test, DNSResolutionFailure) { 509 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 510 int result = resolver.SetPacScript(SCRIPT(DNS_FAIL_JS)); 511 EXPECT_EQ(OK, result); 512 513 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 514 515 EXPECT_EQ(OK, result); 516 std::vector<std::string> proxies = string16ToProxyList(kResults); 517 EXPECT_EQ(1U, proxies.size()); 518 EXPECT_NE("DIRECT", proxies[0]); 519 EXPECT_EQ("success:80", proxies[0]); 520 } 521 522 TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) { 523 return; 524 ProxyResolverV8WithMockBindings resolver(new MockJSBindings()); 525 int result = resolver.SetPacScript(String16(INTERNATIONAL_DOMAIN_NAMES_JS)); 526 EXPECT_EQ(OK, result); 527 528 // Execute FindProxyForURL(). 529 result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults); 530 531 EXPECT_EQ(OK, result); 532 std::vector<std::string> proxies = string16ToProxyList(kResults); 533 EXPECT_EQ(1U, proxies.size()); 534 EXPECT_EQ("DIRECT", proxies[0]); 535 536 // Check that the international domain name was converted to punycode 537 // before passing it onto the bindings layer. 538 MockJSBindings* bindings = resolver.mock_js_bindings(); 539 540 ASSERT_EQ(1u, bindings->dns_resolves.size()); 541 EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves[0]); 542 543 ASSERT_EQ(1u, bindings->dns_resolves_ex.size()); 544 EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves_ex[0]); 545 } 546 547 } // namespace 548 } // namespace net 549