1 // Copyright (c) 2009 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/string_util.h" 7 #include "base/path_service.h" 8 #include "googleurl/src/gurl.h" 9 #include "net/base/load_log_unittest.h" 10 #include "net/base/net_errors.h" 11 #include "net/proxy/proxy_info.h" 12 #include "net/proxy/proxy_resolver_js_bindings.h" 13 #include "net/proxy/proxy_resolver_v8.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 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 { 23 public: 24 MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {} 25 26 virtual void Alert(const std::string& message) { 27 LOG(INFO) << "PAC-alert: " << message; // Helpful when debugging. 28 alerts.push_back(message); 29 } 30 31 virtual std::string MyIpAddress() { 32 my_ip_address_count++; 33 return my_ip_address_result; 34 } 35 36 virtual std::string MyIpAddressEx() { 37 my_ip_address_ex_count++; 38 return my_ip_address_ex_result; 39 } 40 41 virtual std::string DnsResolve(const std::string& host) { 42 dns_resolves.push_back(host); 43 return dns_resolve_result; 44 } 45 46 virtual std::string DnsResolveEx(const std::string& host) { 47 dns_resolves_ex.push_back(host); 48 return dns_resolve_ex_result; 49 } 50 51 virtual void OnError(int line_number, const std::string& message) { 52 // Helpful when debugging. 53 LOG(INFO) << "PAC-error: [" << line_number << "] " << message; 54 55 errors.push_back(message); 56 errors_line_number.push_back(line_number); 57 } 58 59 // Mock values to return. 60 std::string my_ip_address_result; 61 std::string my_ip_address_ex_result; 62 std::string dns_resolve_result; 63 std::string dns_resolve_ex_result; 64 65 // Inputs we got called with. 66 std::vector<std::string> alerts; 67 std::vector<std::string> errors; 68 std::vector<int> errors_line_number; 69 std::vector<std::string> dns_resolves; 70 std::vector<std::string> dns_resolves_ex; 71 int my_ip_address_count; 72 int my_ip_address_ex_count; 73 }; 74 75 // This is the same as ProxyResolverV8, but it uses mock bindings in place of 76 // the default bindings, and has a helper function to load PAC scripts from 77 // disk. 78 class ProxyResolverV8WithMockBindings : public ProxyResolverV8 { 79 public: 80 ProxyResolverV8WithMockBindings() : ProxyResolverV8(new MockJSBindings()) {} 81 82 MockJSBindings* mock_js_bindings() const { 83 return reinterpret_cast<MockJSBindings*>(js_bindings()); 84 } 85 86 // Initialize with the PAC script data at |filename|. 87 int SetPacScriptFromDisk(const char* filename) { 88 FilePath path; 89 PathService::Get(base::DIR_SOURCE_ROOT, &path); 90 path = path.AppendASCII("net"); 91 path = path.AppendASCII("data"); 92 path = path.AppendASCII("proxy_resolver_v8_unittest"); 93 path = path.AppendASCII(filename); 94 95 // Try to read the file from disk. 96 std::string file_contents; 97 bool ok = file_util::ReadFileToString(path, &file_contents); 98 99 // If we can't load the file from disk, something is misconfigured. 100 if (!ok) { 101 LOG(ERROR) << "Failed to read file: " << path.value(); 102 return ERR_UNEXPECTED; 103 } 104 105 // Load the PAC script into the ProxyResolver. 106 return SetPacScriptByData(file_contents, NULL); 107 } 108 }; 109 110 // Doesn't really matter what these values are for many of the tests. 111 const GURL kQueryUrl("http://www.google.com"); 112 const GURL kPacUrl; 113 114 115 TEST(ProxyResolverV8Test, Direct) { 116 ProxyResolverV8WithMockBindings resolver; 117 int result = resolver.SetPacScriptFromDisk("direct.js"); 118 EXPECT_EQ(OK, result); 119 120 ProxyInfo proxy_info; 121 scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded)); 122 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log); 123 124 EXPECT_EQ(OK, result); 125 EXPECT_TRUE(proxy_info.is_direct()); 126 127 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 128 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 129 130 // No bindings were called, so no log entries. 131 EXPECT_EQ(0u, log->entries().size()); 132 } 133 134 TEST(ProxyResolverV8Test, ReturnEmptyString) { 135 ProxyResolverV8WithMockBindings resolver; 136 int result = resolver.SetPacScriptFromDisk("return_empty_string.js"); 137 EXPECT_EQ(OK, result); 138 139 ProxyInfo proxy_info; 140 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 141 142 EXPECT_EQ(OK, result); 143 EXPECT_TRUE(proxy_info.is_direct()); 144 145 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 146 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 147 } 148 149 TEST(ProxyResolverV8Test, Basic) { 150 ProxyResolverV8WithMockBindings resolver; 151 int result = resolver.SetPacScriptFromDisk("passthrough.js"); 152 EXPECT_EQ(OK, result); 153 154 // The "FindProxyForURL" of this PAC script simply concatenates all of the 155 // arguments into a pseudo-host. The purpose of this test is to verify that 156 // the correct arguments are being passed to FindProxyForURL(). 157 { 158 ProxyInfo proxy_info; 159 result = resolver.GetProxyForURL(GURL("http://query.com/path"), 160 &proxy_info, NULL, NULL, NULL); 161 EXPECT_EQ(OK, result); 162 EXPECT_EQ("http.query.com.path.query.com:80", 163 proxy_info.proxy_server().ToURI()); 164 } 165 { 166 ProxyInfo proxy_info; 167 int result = resolver.GetProxyForURL(GURL("ftp://query.com:90/path"), 168 &proxy_info, NULL, NULL, NULL); 169 EXPECT_EQ(OK, result); 170 // Note that FindProxyForURL(url, host) does not expect |host| to contain 171 // the port number. 172 EXPECT_EQ("ftp.query.com.90.path.query.com:80", 173 proxy_info.proxy_server().ToURI()); 174 175 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 176 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 177 } 178 179 // We call this so we'll have code coverage of the function and valgrind will 180 // make sure nothing bad happens. 181 // 182 // NOTE: This is here instead of in its own test so that we'll be calling it 183 // after having done something, in hopes it won't be a no-op. 184 resolver.PurgeMemory(); 185 } 186 187 TEST(ProxyResolverV8Test, BadReturnType) { 188 // These are the filenames of PAC scripts which each return a non-string 189 // types for FindProxyForURL(). They should all fail with 190 // ERR_PAC_SCRIPT_FAILED. 191 static const char* const filenames[] = { 192 "return_undefined.js", 193 "return_integer.js", 194 "return_function.js", 195 "return_object.js", 196 // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ? 197 "return_null.js" 198 }; 199 200 for (size_t i = 0; i < arraysize(filenames); ++i) { 201 ProxyResolverV8WithMockBindings resolver; 202 int result = resolver.SetPacScriptFromDisk(filenames[i]); 203 EXPECT_EQ(OK, result); 204 205 ProxyInfo proxy_info; 206 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 207 208 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 209 210 MockJSBindings* bindings = resolver.mock_js_bindings(); 211 EXPECT_EQ(0U, bindings->alerts.size()); 212 ASSERT_EQ(1U, bindings->errors.size()); 213 EXPECT_EQ("FindProxyForURL() did not return a string.", 214 bindings->errors[0]); 215 EXPECT_EQ(-1, bindings->errors_line_number[0]); 216 } 217 } 218 219 // Try using a PAC script which defines no "FindProxyForURL" function. 220 TEST(ProxyResolverV8Test, NoEntryPoint) { 221 ProxyResolverV8WithMockBindings resolver; 222 int result = resolver.SetPacScriptFromDisk("no_entrypoint.js"); 223 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 224 225 ProxyInfo proxy_info; 226 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 227 228 EXPECT_EQ(ERR_FAILED, result); 229 } 230 231 // Try loading a malformed PAC script. 232 TEST(ProxyResolverV8Test, ParseError) { 233 ProxyResolverV8WithMockBindings resolver; 234 int result = resolver.SetPacScriptFromDisk("missing_close_brace.js"); 235 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 236 237 ProxyInfo proxy_info; 238 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 239 240 EXPECT_EQ(ERR_FAILED, result); 241 242 MockJSBindings* bindings = resolver.mock_js_bindings(); 243 EXPECT_EQ(0U, bindings->alerts.size()); 244 245 // We get one error during compilation. 246 ASSERT_EQ(1U, bindings->errors.size()); 247 248 EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input", 249 bindings->errors[0]); 250 EXPECT_EQ(-1, bindings->errors_line_number[0]); 251 } 252 253 // Run a PAC script several times, which has side-effects. 254 TEST(ProxyResolverV8Test, SideEffects) { 255 ProxyResolverV8WithMockBindings resolver; 256 int result = resolver.SetPacScriptFromDisk("side_effects.js"); 257 258 // The PAC script increments a counter each time we invoke it. 259 for (int i = 0; i < 3; ++i) { 260 ProxyInfo proxy_info; 261 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 262 EXPECT_EQ(OK, result); 263 EXPECT_EQ(StringPrintf("sideffect_%d:80", i), 264 proxy_info.proxy_server().ToURI()); 265 } 266 267 // Reload the script -- the javascript environment should be reset, hence 268 // the counter starts over. 269 result = resolver.SetPacScriptFromDisk("side_effects.js"); 270 EXPECT_EQ(OK, result); 271 272 for (int i = 0; i < 3; ++i) { 273 ProxyInfo proxy_info; 274 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 275 EXPECT_EQ(OK, result); 276 EXPECT_EQ(StringPrintf("sideffect_%d:80", i), 277 proxy_info.proxy_server().ToURI()); 278 } 279 } 280 281 // Execute a PAC script which throws an exception in FindProxyForURL. 282 TEST(ProxyResolverV8Test, UnhandledException) { 283 ProxyResolverV8WithMockBindings resolver; 284 int result = resolver.SetPacScriptFromDisk("unhandled_exception.js"); 285 EXPECT_EQ(OK, result); 286 287 ProxyInfo proxy_info; 288 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 289 290 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 291 292 MockJSBindings* bindings = resolver.mock_js_bindings(); 293 EXPECT_EQ(0U, bindings->alerts.size()); 294 ASSERT_EQ(1U, bindings->errors.size()); 295 EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined", 296 bindings->errors[0]); 297 EXPECT_EQ(3, bindings->errors_line_number[0]); 298 } 299 300 // TODO(eroman): This test is disabed right now, since the parsing of 301 // host/port doesn't check for non-ascii characters. 302 TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) { 303 ProxyResolverV8WithMockBindings resolver; 304 int result = resolver.SetPacScriptFromDisk("return_unicode.js"); 305 EXPECT_EQ(OK, result); 306 307 ProxyInfo proxy_info; 308 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 309 310 // The result from this resolve was unparseable, because it 311 // wasn't ascii. 312 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result); 313 } 314 315 // Test the PAC library functions that we expose in the JS environmnet. 316 TEST(ProxyResolverV8Test, JavascriptLibrary) { 317 ProxyResolverV8WithMockBindings resolver; 318 int result = resolver.SetPacScriptFromDisk("pac_library_unittest.js"); 319 EXPECT_EQ(OK, result); 320 321 ProxyInfo proxy_info; 322 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 323 324 // If the javascript side of this unit-test fails, it will throw a javascript 325 // exception. Otherwise it will return "PROXY success:80". 326 EXPECT_EQ(OK, result); 327 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 328 329 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 330 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 331 } 332 333 // Try resolving when SetPacScriptByData() has not been called. 334 TEST(ProxyResolverV8Test, NoSetPacScript) { 335 ProxyResolverV8WithMockBindings resolver; 336 337 ProxyInfo proxy_info; 338 339 // Resolve should fail, as we are not yet initialized with a script. 340 int result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 341 EXPECT_EQ(ERR_FAILED, result); 342 343 // Initialize it. 344 result = resolver.SetPacScriptFromDisk("direct.js"); 345 EXPECT_EQ(OK, result); 346 347 // Resolve should now succeed. 348 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 349 EXPECT_EQ(OK, result); 350 351 // Clear it, by initializing with an empty string. 352 resolver.SetPacScriptByData(std::string(), NULL); 353 354 // Resolve should fail again now. 355 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 356 EXPECT_EQ(ERR_FAILED, result); 357 358 // Load a good script once more. 359 result = resolver.SetPacScriptFromDisk("direct.js"); 360 EXPECT_EQ(OK, result); 361 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 362 EXPECT_EQ(OK, result); 363 364 EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size()); 365 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 366 } 367 368 // Test marshalling/un-marshalling of values between C++/V8. 369 TEST(ProxyResolverV8Test, V8Bindings) { 370 ProxyResolverV8WithMockBindings resolver; 371 int result = resolver.SetPacScriptFromDisk("bindings.js"); 372 EXPECT_EQ(OK, result); 373 374 ProxyInfo proxy_info; 375 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 376 377 EXPECT_EQ(OK, result); 378 EXPECT_TRUE(proxy_info.is_direct()); 379 380 MockJSBindings* bindings = resolver.mock_js_bindings(); 381 EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size()); 382 383 // Alert was called 5 times. 384 ASSERT_EQ(5U, bindings->alerts.size()); 385 EXPECT_EQ("undefined", bindings->alerts[0]); 386 EXPECT_EQ("null", bindings->alerts[1]); 387 EXPECT_EQ("undefined", bindings->alerts[2]); 388 EXPECT_EQ("[object Object]", bindings->alerts[3]); 389 EXPECT_EQ("exception from calling toString()", bindings->alerts[4]); 390 391 // DnsResolve was called 8 times. 392 ASSERT_EQ(8U, bindings->dns_resolves.size()); 393 EXPECT_EQ("undefined", bindings->dns_resolves[0]); 394 EXPECT_EQ("null", bindings->dns_resolves[1]); 395 EXPECT_EQ("undefined", bindings->dns_resolves[2]); 396 EXPECT_EQ("", bindings->dns_resolves[3]); 397 EXPECT_EQ("[object Object]", bindings->dns_resolves[4]); 398 EXPECT_EQ("function fn() {}", bindings->dns_resolves[5]); 399 400 // TODO(eroman): This isn't quite right... should probably stringize 401 // to something like "['3']". 402 EXPECT_EQ("3", bindings->dns_resolves[6]); 403 404 EXPECT_EQ("arg1", bindings->dns_resolves[7]); 405 406 // MyIpAddress was called two times. 407 EXPECT_EQ(2, bindings->my_ip_address_count); 408 409 // MyIpAddressEx was called once. 410 EXPECT_EQ(1, bindings->my_ip_address_ex_count); 411 412 // DnsResolveEx was called 2 times. 413 ASSERT_EQ(2U, bindings->dns_resolves_ex.size()); 414 EXPECT_EQ("is_resolvable", bindings->dns_resolves_ex[0]); 415 EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]); 416 } 417 418 // Test that calls to the myIpAddress() and dnsResolve() bindings get 419 // logged to the LoadLog parameter. 420 TEST(ProxyResolverV8Test, LoadLog) { 421 ProxyResolverV8WithMockBindings resolver; 422 int result = resolver.SetPacScriptFromDisk("simple.js"); 423 EXPECT_EQ(OK, result); 424 425 ProxyInfo proxy_info; 426 scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded)); 427 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log); 428 429 EXPECT_EQ(OK, result); 430 EXPECT_FALSE(proxy_info.is_direct()); 431 EXPECT_EQ("c:100", proxy_info.proxy_server().ToURI()); 432 433 // Note that dnsResolve() was never called directly, but it appears 434 // in the LoadLog. This is because it gets called indirectly by 435 // isInNet() and isResolvable(). 436 437 EXPECT_EQ(6u, log->entries().size()); 438 EXPECT_TRUE(LogContainsBeginEvent( 439 *log, 0, LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS)); 440 EXPECT_TRUE(LogContainsEndEvent( 441 *log, 1, LoadLog::TYPE_PROXY_RESOLVER_V8_MY_IP_ADDRESS)); 442 EXPECT_TRUE(LogContainsBeginEvent( 443 *log, 2, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE)); 444 EXPECT_TRUE(LogContainsEndEvent( 445 *log, 3, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE)); 446 EXPECT_TRUE(LogContainsBeginEvent( 447 *log, 4, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE)); 448 EXPECT_TRUE(LogContainsEndEvent( 449 *log, 5, LoadLog::TYPE_PROXY_RESOLVER_V8_DNS_RESOLVE)); 450 } 451 452 // Try loading a PAC script that ends with a comment and has no terminal 453 // newline. This should not cause problems with the PAC utility functions 454 // that we add to the script's environment. 455 // http://crbug.com/22864 456 TEST(ProxyResolverV8Test, EndsWithCommentNoNewline) { 457 ProxyResolverV8WithMockBindings resolver; 458 int result = resolver.SetPacScriptFromDisk("ends_with_comment.js"); 459 EXPECT_EQ(OK, result); 460 461 ProxyInfo proxy_info; 462 scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded)); 463 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log); 464 465 EXPECT_EQ(OK, result); 466 EXPECT_FALSE(proxy_info.is_direct()); 467 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 468 } 469 470 // Try loading a PAC script that ends with a statement and has no terminal 471 // newline. This should not cause problems with the PAC utility functions 472 // that we add to the script's environment. 473 // http://crbug.com/22864 474 TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) { 475 ProxyResolverV8WithMockBindings resolver; 476 int result = resolver.SetPacScriptFromDisk( 477 "ends_with_statement_no_semicolon.js"); 478 EXPECT_EQ(OK, result); 479 480 ProxyInfo proxy_info; 481 scoped_refptr<LoadLog> log(new LoadLog(LoadLog::kUnbounded)); 482 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, log); 483 484 EXPECT_EQ(OK, result); 485 EXPECT_FALSE(proxy_info.is_direct()); 486 EXPECT_EQ("success:3", proxy_info.proxy_server().ToURI()); 487 } 488 489 // Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(), 490 // dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding 491 // returns empty string (failure). This simulates the return values from 492 // those functions when the underlying DNS resolution fails. 493 TEST(ProxyResolverV8Test, DNSResolutionFailure) { 494 ProxyResolverV8WithMockBindings resolver; 495 int result = resolver.SetPacScriptFromDisk("dns_fail.js"); 496 EXPECT_EQ(OK, result); 497 498 ProxyInfo proxy_info; 499 result = resolver.GetProxyForURL(kQueryUrl, &proxy_info, NULL, NULL, NULL); 500 501 EXPECT_EQ(OK, result); 502 EXPECT_FALSE(proxy_info.is_direct()); 503 EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI()); 504 } 505 506 } // namespace 507 } // namespace net 508