Home | History | Annotate | Download | only in proxy
      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