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
15using namespace android;
16namespace net {
17namespace {
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.
22class 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.
87class 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.
102const String16 kQueryUrl("http://www.google.com");
103const String16 kQueryHost("www.google.com");
104String16 kResults;
105
106String16 currentPac;
107#define SCRIPT(x) (currentPac = String16(x))
108
109void 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
119std::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
143std::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
149TEST(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
165TEST(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
180TEST(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
221TEST(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.
251TEST(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.
262TEST(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.
282TEST(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.
312TEST(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
328TEST(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.
341TEST(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.
361TEST(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.
395TEST(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
439TEST(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
470TEST(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
488TEST(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.
507TEST(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
521TEST(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