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 #include "base/file_util.h" 6 #include "base/path_service.h" 7 #include "base/string_util.h" 8 #include "base/stringprintf.h" 9 #include "base/utf_string_conversions.h" 10 #include "googleurl/src/gurl.h" 11 #include "net/base/net_errors.h" 12 #include "net/base/net_log_unittest.h" 13 #include "net/proxy/proxy_info.h" 14 #include "net/proxy/proxy_resolver_js_bindings.h" 15 #include "net/proxy/proxy_resolver_v8.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 18 namespace net { 19 namespace { 20 21 // Javascript bindings for ProxyResolverV8, which returns mock values. 22 // Each time one of the bindings is called into, we push the input into a 23 // list, for later verification. 24 class MockJSBindings : public ProxyResolverJSBindings { 25 public: 26 MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {} 27 28 virtual void Alert(const string16& message) { 29 VLOG(1) << "PAC-alert: " << message; // Helpful when debugging. 30 alerts.push_back(UTF16ToUTF8(message)); 31 } 32 33 virtual bool MyIpAddress(std::string* ip_address) { 34 my_ip_address_count++; 35 *ip_address = my_ip_address_result; 36 return !my_ip_address_result.empty(); 37 } 38 39 virtual bool MyIpAddressEx(std::string* ip_address_list) { 40 my_ip_address_ex_count++; 41 *ip_address_list = my_ip_address_ex_result; 42 return !my_ip_address_ex_result.empty(); 43 } 44 45 virtual bool DnsResolve(const std::string& host, std::string* ip_address) { 46 dns_resolves.push_back(host); 47 *ip_address = dns_resolve_result; 48 return !dns_resolve_result.empty(); 49 } 50 51 virtual bool DnsResolveEx(const std::string& host, 52 std::string* ip_address_list) { 53 dns_resolves_ex.push_back(host); 54 *ip_address_list = dns_resolve_ex_result; 55 return !dns_resolve_ex_result.empty(); 56 } 57 58 virtual void OnError(int line_number, const string16& message) { 59 // Helpful when debugging. 60 VLOG(1) << "PAC-error: [" << line_number << "] " << message; 61 62 errors.push_back(UTF16ToUTF8(message)); 63 errors_line_number.push_back(line_number); 64 } 65 66 virtual void Shutdown() {} 67 68 // Mock values to return. 69 std::string my_ip_address_result; 70 std::string my_ip_address_ex_result; 71 std::string dns_resolve_result; 72 std::string dns_resolve_ex_result; 73 74 // Inputs we got called with. 75 std::vector<std::string> alerts; 76 std::vector<std::string> errors; 77 std::vector<int> errors_line_number; 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() : ProxyResolverV8(new MockJSBindings()) {} 90 91 MockJSBindings* mock_js_bindings() const { 92 return reinterpret_cast<MockJSBindings*>(js_bindings()); 93 } 94 95 // Initialize with the PAC script data at |filename|. 96 int SetPacScriptFromDisk(const char* filename) { 97 FilePath path; 98 PathService::Get(base::DIR_SOURCE_ROOT, &path); 99 path = path.AppendASCII("net"); 100 path = path.AppendASCII("data"); 101 path = path.AppendASCII("proxy_resolver_v8_unittest"); 102 path = path.AppendASCII(filename); 103 104 // Try to read the file from disk. 105 std::string file_contents; 106 bool ok = file_util::ReadFileToString(path, &file_contents); 107 108 // If we can't load the file from disk, something is misconfigured. 109 if (!ok) { 110 LOG(ERROR) << "Failed to read file: " << path.value(); 111 return ERR_UNEXPECTED; 112 } 113 114 // Load the PAC script into the ProxyResolver. 115 return SetPacScript(ProxyResolverScriptData::FromUTF8(file_contents), 116 NULL); 117 } 118 }; 119 120 // Doesn't really matter what these values are for many of the tests. 121 const GURL kQueryUrl("http://www.google.com"); 122 const GURL kPacUrl; 123 124 125 TEST(ProxyResolverV8Test, Direct) { 126 ProxyResolverV8WithMockBindings resolver; 127 int result = resolver.SetPacScriptFromDisk("direct.js"); 128 EXPECT_EQ(OK, result); 129 130 ProxyInfo proxy_info; 131 CapturingBoundNetLog log(CapturingNetLog::kUnbounded); 132 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 133 log.bound()); 134 135 EXPECT_EQ(OK, result); 136 EXPECT_TRUE(proxy_info.is_direct()); 137 138 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 139 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 140 141 net::CapturingNetLog::EntryList entries; 142 log.GetEntries(&entries); 143 // No bindings were called, so no log entries. 144 EXPECT_EQ(0u, entries.size()); 145 } 146 147 TEST(ProxyResolverV8Test, ReturnEmptyString) { 148 ProxyResolverV8WithMockBindings resolver; 149 int result = resolver.SetPacScriptFromDisk("return_empty_string.js"); 150 EXPECT_EQ(OK, result); 151 152 ProxyInfo proxy_info; 153 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 154 BoundNetLog()); 155 156 EXPECT_EQ(OK, result); 157 EXPECT_TRUE(proxy_info.is_direct()); 158 159 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 160 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 161 } 162 163 TEST(ProxyResolverV8Test, Basic) { 164 ProxyResolverV8WithMockBindings resolver; 165 int result = resolver.SetPacScriptFromDisk("passthrough.js"); 166 EXPECT_EQ(OK, result); 167 168 // The "FindProxyForURL" of this PAC script simply concatenates all of the 169 // arguments into a pseudo-host. The purpose of this test is to verify that 170 // the correct arguments are being passed to FindProxyForURL(). 171 { 172 ProxyInfo proxy_info; 173 result = resolver.GetProxyForURL(GURL("http://query.com/path"), 174 &proxy_info, NULL, NULL, BoundNetLog()); 175 EXPECT_EQ(OK, result); 176 EXPECT_EQ("http.query.com.path.query.com:80", 177 proxy_info.proxy_server().ToURI()); 178 } 179 { 180 ProxyInfo proxy_info; 181 int result = resolver.GetProxyForURL(GURL("ftp://query.com:90/path"), 182 &proxy_info, NULL, NULL, 183 BoundNetLog()); 184 EXPECT_EQ(OK, result); 185 // Note that FindProxyForURL(url, host) does not expect |host| to contain 186 // the port number. 187 EXPECT_EQ("ftp.query.com.90.path.query.com:80", 188 proxy_info.proxy_server().ToURI()); 189 190 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 191 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 192 } 193 194 // We call this so we'll have code coverage of the function and valgrind will 195 // make sure nothing bad happens. 196 // 197 // NOTE: This is here instead of in its own test so that we'll be calling it 198 // after having done something, in hopes it won't be a no-op. 199 resolver.PurgeMemory(); 200 } 201 202 TEST(ProxyResolverV8Test, BadReturnType) { 203 // These are the filenames of PAC scripts which each return a non-string 204 // types for FindProxyForURL(). They should all fail with 205 // ERR_PAC_SCRIPT_FAILED. 206 static const char* const filenames[] = { 207 "return_undefined.js", 208 "return_integer.js", 209 "return_function.js", 210 "return_object.js", 211 // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ? 212 "return_null.js" 213 }; 214 215 for (size_t i = 0; i < arraysize(filenames); ++i) { 216 ProxyResolverV8WithMockBindings resolver; 217 int result = resolver.SetPacScriptFromDisk(filenames[i]); 218 EXPECT_EQ(OK, result); 219 220 ProxyInfo proxy_info; 221 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 222 BoundNetLog()); 223 224 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 225 226 MockJSBindings* bindings = resolver.mock_js_bindings(); 227 EXPECT_EQ(0U, bindings->alerts.size()); 228 ASSERT_EQ(1U, bindings->errors.size()); 229 EXPECT_EQ("FindProxyForURL() did not return a string.", 230 bindings->errors[0]); 231 EXPECT_EQ(-1, bindings->errors_line_number[0]); 232 } 233 } 234 235 // Try using a PAC script which defines no "FindProxyForURL" function. 236 TEST(ProxyResolverV8Test, NoEntryPoint) { 237 ProxyResolverV8WithMockBindings resolver; 238 int result = resolver.SetPacScriptFromDisk("no_entrypoint.js"); 239 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 240 241 ProxyInfo proxy_info; 242 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 243 BoundNetLog()); 244 245 EXPECT_EQ(ERR_FAILED, result); 246 } 247 248 // Try loading a malformed PAC script. 249 TEST(ProxyResolverV8Test, ParseError) { 250 ProxyResolverV8WithMockBindings resolver; 251 int result = resolver.SetPacScriptFromDisk("missing_close_brace.js"); 252 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 253 254 ProxyInfo proxy_info; 255 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 256 BoundNetLog()); 257 258 EXPECT_EQ(ERR_FAILED, result); 259 260 MockJSBindings* bindings = resolver.mock_js_bindings(); 261 EXPECT_EQ(0U, bindings->alerts.size()); 262 263 // We get one error during compilation. 264 ASSERT_EQ(1U, bindings->errors.size()); 265 266 EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", 267 bindings->errors[0]); 268 EXPECT_EQ(0, bindings->errors_line_number[0]); 269 } 270 271 // Run a PAC script several times, which has side-effects. 272 TEST(ProxyResolverV8Test, SideEffects) { 273 ProxyResolverV8WithMockBindings resolver; 274 int result = resolver.SetPacScriptFromDisk("side_effects.js"); 275 276 // The PAC script increments a counter each time we invoke it. 277 for (int i = 0; i < 3; ++i) { 278 ProxyInfo proxy_info; 279 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 280 BoundNetLog()); 281 EXPECT_EQ(OK, result); 282 EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i), 283 proxy_info.proxy_server().ToURI()); 284 } 285 286 // Reload the script -- the javascript environment should be reset, hence 287 // the counter starts over. 288 result = resolver.SetPacScriptFromDisk("side_effects.js"); 289 EXPECT_EQ(OK, result); 290 291 for (int i = 0; i < 3; ++i) { 292 ProxyInfo proxy_info; 293 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 294 BoundNetLog()); 295 EXPECT_EQ(OK, result); 296 EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i), 297 proxy_info.proxy_server().ToURI()); 298 } 299 } 300 301 // Execute a PAC script which throws an exception in FindProxyForURL. 302 TEST(ProxyResolverV8Test, UnhandledException) { 303 ProxyResolverV8WithMockBindings resolver; 304 int result = resolver.SetPacScriptFromDisk("unhandled_exception.js"); 305 EXPECT_EQ(OK, result); 306 307 ProxyInfo proxy_info; 308 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 309 BoundNetLog()); 310 311 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 312 313 MockJSBindings* bindings = resolver.mock_js_bindings(); 314 EXPECT_EQ(0U, bindings->alerts.size()); 315 ASSERT_EQ(1U, bindings->errors.size()); 316 EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined", 317 bindings->errors[0]); 318 EXPECT_EQ(3, bindings->errors_line_number[0]); 319 } 320 321 TEST(ProxyResolverV8Test, ReturnUnicode) { 322 ProxyResolverV8WithMockBindings resolver; 323 int result = resolver.SetPacScriptFromDisk("return_unicode.js"); 324 EXPECT_EQ(OK, result); 325 326 ProxyInfo proxy_info; 327 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 328 BoundNetLog()); 329 330 // The result from this resolve was unparseable, because it 331 // wasn't ASCII. 332 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 333 } 334 335 // Test the PAC library functions that we expose in the JS environmnet. 336 TEST(ProxyResolverV8Test, JavascriptLibrary) { 337 ProxyResolverV8WithMockBindings resolver; 338 int result = resolver.SetPacScriptFromDisk("pac_library_unittest.js"); 339 EXPECT_EQ(OK, result); 340 341 ProxyInfo proxy_info; 342 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 343 BoundNetLog()); 344 345 // If the javascript side of this unit-test fails, it will throw a javascript 346 // exception. Otherwise it will return "PROXY success:80". 347 EXPECT_EQ(OK, result); 348 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 349 350 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 351 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 352 } 353 354 // Try resolving when SetPacScriptByData() has not been called. 355 TEST(ProxyResolverV8Test, NoSetPacScript) { 356 ProxyResolverV8WithMockBindings resolver; 357 358 ProxyInfo proxy_info; 359 360 // Resolve should fail, as we are not yet initialized with a script. 361 int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 362 BoundNetLog()); 363 EXPECT_EQ(ERR_FAILED, result); 364 365 // Initialize it. 366 result = resolver.SetPacScriptFromDisk("direct.js"); 367 EXPECT_EQ(OK, result); 368 369 // Resolve should now succeed. 370 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 371 BoundNetLog()); 372 EXPECT_EQ(OK, result); 373 374 // Clear it, by initializing with an empty string. 375 resolver.SetPacScript( 376 ProxyResolverScriptData::FromUTF16(string16()), NULL); 377 378 // Resolve should fail again now. 379 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 380 BoundNetLog()); 381 EXPECT_EQ(ERR_FAILED, result); 382 383 // Load a good script once more. 384 result = resolver.SetPacScriptFromDisk("direct.js"); 385 EXPECT_EQ(OK, result); 386 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 387 BoundNetLog()); 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; 397 MockJSBindings* bindings = resolver.mock_js_bindings(); 398 bindings->dns_resolve_result = "127.0.0.1"; 399 int result = resolver.SetPacScriptFromDisk("bindings.js"); 400 EXPECT_EQ(OK, result); 401 402 ProxyInfo proxy_info; 403 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 404 BoundNetLog()); 405 406 EXPECT_EQ(OK, result); 407 EXPECT_TRUE(proxy_info.is_direct()); 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; 441 442 int result = resolver.SetPacScriptFromDisk("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 ProxyInfo proxy_info; 451 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 452 BoundNetLog()); 453 454 EXPECT_EQ(OK, result); 455 EXPECT_FALSE(proxy_info.is_direct()); 456 EXPECT_EQ("127.0.0.1:80", proxy_info.proxy_server().ToURI()); 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; 472 int result = resolver.SetPacScriptFromDisk("ends_with_comment.js"); 473 EXPECT_EQ(OK, result); 474 475 ProxyInfo proxy_info; 476 CapturingBoundNetLog log(CapturingNetLog::kUnbounded); 477 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 478 log.bound()); 479 480 EXPECT_EQ(OK, result); 481 EXPECT_FALSE(proxy_info.is_direct()); 482 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 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; 491 int result = resolver.SetPacScriptFromDisk( 492 "ends_with_statement_no_semicolon.js"); 493 EXPECT_EQ(OK, result); 494 495 ProxyInfo proxy_info; 496 CapturingBoundNetLog log(CapturingNetLog::kUnbounded); 497 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 498 log.bound()); 499 500 EXPECT_EQ(OK, result); 501 EXPECT_FALSE(proxy_info.is_direct()); 502 EXPECT_EQ("success:3", proxy_info.proxy_server().ToURI()); 503 } 504 505 // Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(), 506 // dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding 507 // returns empty string (failure). This simulates the return values from 508 // those functions when the underlying DNS resolution fails. 509 TEST(ProxyResolverV8Test, DNSResolutionFailure) { 510 ProxyResolverV8WithMockBindings resolver; 511 int result = resolver.SetPacScriptFromDisk("dns_fail.js"); 512 EXPECT_EQ(OK, result); 513 514 ProxyInfo proxy_info; 515 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 516 BoundNetLog()); 517 518 EXPECT_EQ(OK, result); 519 EXPECT_FALSE(proxy_info.is_direct()); 520 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 521 } 522 523 TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) { 524 ProxyResolverV8WithMockBindings resolver; 525 int result = resolver.SetPacScriptFromDisk("international_domain_names.js"); 526 EXPECT_EQ(OK, result); 527 528 // Execute FindProxyForURL(). 529 ProxyInfo proxy_info; 530 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, 531 BoundNetLog()); 532 533 EXPECT_EQ(OK, result); 534 EXPECT_TRUE(proxy_info.is_direct()); 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