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