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
16using namespace android;
17namespace net {
18namespace {
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.
23class 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.
88class 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.
103const String16 kQueryUrl("http://www.google.com");
104const String16 kQueryHost("www.google.com");
105String16 kResults;
106
107String16 currentPac;
108#define SCRIPT(x) (currentPac = String16(x))
109
110void 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
120std::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
144std::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
150TEST(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
166TEST(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
181TEST(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
222TEST(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.
252TEST(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.
263TEST(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.
283TEST(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.
313TEST(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
329TEST(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.
342TEST(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.
362TEST(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.
396TEST(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
440TEST(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
471TEST(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
489TEST(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.
508TEST(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
522TEST(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