1 // Copyright (c) 2012 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.
5 #include "base/compiler_specific.h"
6 #include "base/files/file_util.h"
7 #include "base/path_service.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "net/base/completion_callback.h"
12 #include "net/base/net_errors.h"
13 #include "net/proxy/proxy_info.h"
14 #include "net/proxy/proxy_resolver_script_data.h"
15 #include "net/proxy/proxy_resolver_v8.h"
16 #include "testing/gtest/include/gtest/gtest.h"
22 // Javascript bindings for ProxyResolverV8, which returns mock values.
23 // Each time one of the bindings is called into, we push the input into a
24 // list, for later verification.
25 class MockJSBindings
: public ProxyResolverV8::JSBindings
{
28 : my_ip_address_count(0),
29 my_ip_address_ex_count(0),
30 should_terminate(false) {}
32 void Alert(const base::string16
& message
) override
{
33 VLOG(1) << "PAC-alert: " << message
; // Helpful when debugging.
34 alerts
.push_back(base::UTF16ToUTF8(message
));
37 bool ResolveDns(const std::string
& host
,
38 ResolveDnsOperation op
,
40 bool* terminate
) override
{
41 *terminate
= should_terminate
;
43 if (op
== MY_IP_ADDRESS
) {
44 my_ip_address_count
++;
45 *output
= my_ip_address_result
;
46 return !my_ip_address_result
.empty();
49 if (op
== MY_IP_ADDRESS_EX
) {
50 my_ip_address_ex_count
++;
51 *output
= my_ip_address_ex_result
;
52 return !my_ip_address_ex_result
.empty();
55 if (op
== DNS_RESOLVE
) {
56 dns_resolves
.push_back(host
);
57 *output
= dns_resolve_result
;
58 return !dns_resolve_result
.empty();
61 if (op
== DNS_RESOLVE_EX
) {
62 dns_resolves_ex
.push_back(host
);
63 *output
= dns_resolve_ex_result
;
64 return !dns_resolve_ex_result
.empty();
71 void OnError(int line_number
, const base::string16
& message
) override
{
72 // Helpful when debugging.
73 VLOG(1) << "PAC-error: [" << line_number
<< "] " << message
;
75 errors
.push_back(base::UTF16ToUTF8(message
));
76 errors_line_number
.push_back(line_number
);
79 // Mock values to return.
80 std::string my_ip_address_result
;
81 std::string my_ip_address_ex_result
;
82 std::string dns_resolve_result
;
83 std::string dns_resolve_ex_result
;
85 // Inputs we got called with.
86 std::vector
<std::string
> alerts
;
87 std::vector
<std::string
> errors
;
88 std::vector
<int> errors_line_number
;
89 std::vector
<std::string
> dns_resolves
;
90 std::vector
<std::string
> dns_resolves_ex
;
91 int my_ip_address_count
;
92 int my_ip_address_ex_count
;
94 // Whether ResolveDns() should terminate script execution.
95 bool should_terminate
;
98 class ProxyResolverV8Test
: public testing::Test
{
100 // Creates a ProxyResolverV8 using the PAC script contained in |filename|. If
101 // called more than once, the previous ProxyResolverV8 is deleted.
102 int CreateResolver(const char* filename
) {
104 PathService::Get(base::DIR_SOURCE_ROOT
, &path
);
105 path
= path
.AppendASCII("net");
106 path
= path
.AppendASCII("data");
107 path
= path
.AppendASCII("proxy_resolver_v8_unittest");
108 path
= path
.AppendASCII(filename
);
110 // Try to read the file from disk.
111 std::string file_contents
;
112 bool ok
= base::ReadFileToString(path
, &file_contents
);
114 // If we can't load the file from disk, something is misconfigured.
116 LOG(ERROR
) << "Failed to read file: " << path
.value();
120 // Create the ProxyResolver using the PAC script.
121 return ProxyResolverV8::Create(
122 ProxyResolverScriptData::FromUTF8(file_contents
), bindings(),
126 ProxyResolverV8
& resolver() {
131 MockJSBindings
* bindings() { return &js_bindings_
; }
134 MockJSBindings js_bindings_
;
135 scoped_ptr
<ProxyResolverV8
> resolver_
;
138 // Doesn't really matter what these values are for many of the tests.
139 const GURL
kQueryUrl("http://www.google.com");
142 TEST_F(ProxyResolverV8Test
, Direct
) {
143 ASSERT_EQ(OK
, CreateResolver("direct.js"));
145 ProxyInfo proxy_info
;
146 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
148 EXPECT_EQ(OK
, result
);
149 EXPECT_TRUE(proxy_info
.is_direct());
151 EXPECT_EQ(0U, bindings()->alerts
.size());
152 EXPECT_EQ(0U, bindings()->errors
.size());
155 TEST_F(ProxyResolverV8Test
, ReturnEmptyString
) {
156 ASSERT_EQ(OK
, CreateResolver("return_empty_string.js"));
158 ProxyInfo proxy_info
;
159 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
161 EXPECT_EQ(OK
, result
);
162 EXPECT_TRUE(proxy_info
.is_direct());
164 EXPECT_EQ(0U, bindings()->alerts
.size());
165 EXPECT_EQ(0U, bindings()->errors
.size());
168 TEST_F(ProxyResolverV8Test
, Basic
) {
169 ASSERT_EQ(OK
, CreateResolver("passthrough.js"));
171 // The "FindProxyForURL" of this PAC script simply concatenates all of the
172 // arguments into a pseudo-host. The purpose of this test is to verify that
173 // the correct arguments are being passed to FindProxyForURL().
175 ProxyInfo proxy_info
;
176 int result
= resolver().GetProxyForURL(GURL("http://query.com/path"),
177 &proxy_info
, bindings());
178 EXPECT_EQ(OK
, result
);
179 EXPECT_EQ("http.query.com.path.query.com:80",
180 proxy_info
.proxy_server().ToURI());
183 ProxyInfo proxy_info
;
184 int result
= resolver().GetProxyForURL(GURL("ftp://query.com:90/path"),
185 &proxy_info
, bindings());
186 EXPECT_EQ(OK
, result
);
187 // Note that FindProxyForURL(url, host) does not expect |host| to contain
189 EXPECT_EQ("ftp.query.com.90.path.query.com:80",
190 proxy_info
.proxy_server().ToURI());
192 EXPECT_EQ(0U, bindings()->alerts
.size());
193 EXPECT_EQ(0U, bindings()->errors
.size());
197 TEST_F(ProxyResolverV8Test
, BadReturnType
) {
198 // These are the filenames of PAC scripts which each return a non-string
199 // types for FindProxyForURL(). They should all fail with
200 // ERR_PAC_SCRIPT_FAILED.
201 static const char* const filenames
[] = {
202 "return_undefined.js",
204 "return_function.js",
206 // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ?
209 for (size_t i
= 0; i
< arraysize(filenames
); ++i
) {
210 ASSERT_EQ(OK
, CreateResolver(filenames
[i
]));
212 MockJSBindings bindings
;
213 ProxyInfo proxy_info
;
214 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, &bindings
);
216 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, result
);
218 EXPECT_EQ(0U, bindings
.alerts
.size());
219 ASSERT_EQ(1U, bindings
.errors
.size());
220 EXPECT_EQ("FindProxyForURL() did not return a string.", bindings
.errors
[0]);
221 EXPECT_EQ(-1, bindings
.errors_line_number
[0]);
225 // Try using a PAC script which defines no "FindProxyForURL" function.
226 TEST_F(ProxyResolverV8Test
, NoEntryPoint
) {
227 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, CreateResolver("no_entrypoint.js"));
229 ASSERT_EQ(1U, bindings()->errors
.size());
230 EXPECT_EQ("FindProxyForURL is undefined or not a function.",
231 bindings()->errors
[0]);
232 EXPECT_EQ(-1, bindings()->errors_line_number
[0]);
235 // Try loading a malformed PAC script.
236 TEST_F(ProxyResolverV8Test
, ParseError
) {
237 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, CreateResolver("missing_close_brace.js"));
239 EXPECT_EQ(0U, bindings()->alerts
.size());
241 // We get one error during compilation.
242 ASSERT_EQ(1U, bindings()->errors
.size());
244 EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
245 bindings()->errors
[0]);
246 EXPECT_EQ(5, bindings()->errors_line_number
[0]);
249 // Run a PAC script several times, which has side-effects.
250 TEST_F(ProxyResolverV8Test
, SideEffects
) {
251 ASSERT_EQ(OK
, CreateResolver("side_effects.js"));
253 // The PAC script increments a counter each time we invoke it.
254 for (int i
= 0; i
< 3; ++i
) {
255 ProxyInfo proxy_info
;
256 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
257 EXPECT_EQ(OK
, result
);
258 EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i
),
259 proxy_info
.proxy_server().ToURI());
262 // Reload the script -- the javascript environment should be reset, hence
263 // the counter starts over.
264 ASSERT_EQ(OK
, CreateResolver("side_effects.js"));
266 for (int i
= 0; i
< 3; ++i
) {
267 ProxyInfo proxy_info
;
268 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
269 EXPECT_EQ(OK
, result
);
270 EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i
),
271 proxy_info
.proxy_server().ToURI());
275 // Execute a PAC script which throws an exception in FindProxyForURL.
276 TEST_F(ProxyResolverV8Test
, UnhandledException
) {
277 ASSERT_EQ(OK
, CreateResolver("unhandled_exception.js"));
279 ProxyInfo proxy_info
;
280 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
282 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, result
);
284 EXPECT_EQ(0U, bindings()->alerts
.size());
285 ASSERT_EQ(1U, bindings()->errors
.size());
286 EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined",
287 bindings()->errors
[0]);
288 EXPECT_EQ(3, bindings()->errors_line_number
[0]);
291 // Execute a PAC script which throws an exception when first accessing
293 TEST_F(ProxyResolverV8Test
, ExceptionAccessingFindProxyForURLDuringInit
) {
294 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
,
295 CreateResolver("exception_findproxyforurl_during_init.js"));
297 ASSERT_EQ(2U, bindings()->errors
.size());
298 EXPECT_EQ("Uncaught crash!", bindings()->errors
[0]);
299 EXPECT_EQ(9, bindings()->errors_line_number
[0]);
300 EXPECT_EQ("Accessing FindProxyForURL threw an exception.",
301 bindings()->errors
[1]);
302 EXPECT_EQ(-1, bindings()->errors_line_number
[1]);
305 // Execute a PAC script which throws an exception during the second access to
307 TEST_F(ProxyResolverV8Test
, ExceptionAccessingFindProxyForURLDuringResolve
) {
308 ASSERT_EQ(OK
, CreateResolver("exception_findproxyforurl_during_resolve.js"));
310 ProxyInfo proxy_info
;
311 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
313 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, result
);
315 ASSERT_EQ(2U, bindings()->errors
.size());
316 EXPECT_EQ("Uncaught crash!", bindings()->errors
[0]);
317 EXPECT_EQ(17, bindings()->errors_line_number
[0]);
318 EXPECT_EQ("Accessing FindProxyForURL threw an exception.",
319 bindings()->errors
[1]);
320 EXPECT_EQ(-1, bindings()->errors_line_number
[1]);
323 TEST_F(ProxyResolverV8Test
, ReturnUnicode
) {
324 ASSERT_EQ(OK
, CreateResolver("return_unicode.js"));
326 ProxyInfo proxy_info
;
327 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
329 // The result from this resolve was unparseable, because it
331 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, result
);
334 // Test the PAC library functions that we expose in the JS environment.
335 TEST_F(ProxyResolverV8Test
, JavascriptLibrary
) {
336 ASSERT_EQ(OK
, CreateResolver("pac_library_unittest.js"));
338 ProxyInfo proxy_info
;
339 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
341 // If the javascript side of this unit-test fails, it will throw a javascript
342 // exception. Otherwise it will return "PROXY success:80".
343 EXPECT_EQ(OK
, result
);
344 EXPECT_EQ("success:80", proxy_info
.proxy_server().ToURI());
346 EXPECT_EQ(0U, bindings()->alerts
.size());
347 EXPECT_EQ(0U, bindings()->errors
.size());
350 // Test marshalling/un-marshalling of values between C++/V8.
351 TEST_F(ProxyResolverV8Test
, V8Bindings
) {
352 ASSERT_EQ(OK
, CreateResolver("bindings.js"));
353 bindings()->dns_resolve_result
= "127.0.0.1";
355 ProxyInfo proxy_info
;
356 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
358 EXPECT_EQ(OK
, result
);
359 EXPECT_TRUE(proxy_info
.is_direct());
361 EXPECT_EQ(0U, bindings()->errors
.size());
363 // Alert was called 5 times.
364 ASSERT_EQ(5U, bindings()->alerts
.size());
365 EXPECT_EQ("undefined", bindings()->alerts
[0]);
366 EXPECT_EQ("null", bindings()->alerts
[1]);
367 EXPECT_EQ("undefined", bindings()->alerts
[2]);
368 EXPECT_EQ("[object Object]", bindings()->alerts
[3]);
369 EXPECT_EQ("exception from calling toString()", bindings()->alerts
[4]);
371 // DnsResolve was called 8 times, however only 2 of those were string
372 // parameters. (so 6 of them failed immediately).
373 ASSERT_EQ(2U, bindings()->dns_resolves
.size());
374 EXPECT_EQ("", bindings()->dns_resolves
[0]);
375 EXPECT_EQ("arg1", bindings()->dns_resolves
[1]);
377 // MyIpAddress was called two times.
378 EXPECT_EQ(2, bindings()->my_ip_address_count
);
380 // MyIpAddressEx was called once.
381 EXPECT_EQ(1, bindings()->my_ip_address_ex_count
);
383 // DnsResolveEx was called 2 times.
384 ASSERT_EQ(2U, bindings()->dns_resolves_ex
.size());
385 EXPECT_EQ("is_resolvable", bindings()->dns_resolves_ex
[0]);
386 EXPECT_EQ("foobar", bindings()->dns_resolves_ex
[1]);
389 // Test calling a binding (myIpAddress()) from the script's global scope.
390 // http://crbug.com/40026
391 TEST_F(ProxyResolverV8Test
, BindingCalledDuringInitialization
) {
392 ASSERT_EQ(OK
, CreateResolver("binding_from_global.js"));
394 // myIpAddress() got called during initialization of the script.
395 EXPECT_EQ(1, bindings()->my_ip_address_count
);
397 ProxyInfo proxy_info
;
398 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
400 EXPECT_EQ(OK
, result
);
401 EXPECT_FALSE(proxy_info
.is_direct());
402 EXPECT_EQ("127.0.0.1:80", proxy_info
.proxy_server().ToURI());
404 // Check that no other bindings were called.
405 EXPECT_EQ(0U, bindings()->errors
.size());
406 ASSERT_EQ(0U, bindings()->alerts
.size());
407 ASSERT_EQ(0U, bindings()->dns_resolves
.size());
408 EXPECT_EQ(0, bindings()->my_ip_address_ex_count
);
409 ASSERT_EQ(0U, bindings()->dns_resolves_ex
.size());
412 // Try loading a PAC script that ends with a comment and has no terminal
413 // newline. This should not cause problems with the PAC utility functions
414 // that we add to the script's environment.
415 // http://crbug.com/22864
416 TEST_F(ProxyResolverV8Test
, EndsWithCommentNoNewline
) {
417 ASSERT_EQ(OK
, CreateResolver("ends_with_comment.js"));
419 ProxyInfo proxy_info
;
420 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
422 EXPECT_EQ(OK
, result
);
423 EXPECT_FALSE(proxy_info
.is_direct());
424 EXPECT_EQ("success:80", proxy_info
.proxy_server().ToURI());
427 // Try loading a PAC script that ends with a statement and has no terminal
428 // newline. This should not cause problems with the PAC utility functions
429 // that we add to the script's environment.
430 // http://crbug.com/22864
431 TEST_F(ProxyResolverV8Test
, EndsWithStatementNoNewline
) {
432 ASSERT_EQ(OK
, CreateResolver("ends_with_statement_no_semicolon.js"));
434 ProxyInfo proxy_info
;
435 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
437 EXPECT_EQ(OK
, result
);
438 EXPECT_FALSE(proxy_info
.is_direct());
439 EXPECT_EQ("success:3", proxy_info
.proxy_server().ToURI());
442 // Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(),
443 // dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding
444 // returns empty string (failure). This simulates the return values from
445 // those functions when the underlying DNS resolution fails.
446 TEST_F(ProxyResolverV8Test
, DNSResolutionFailure
) {
447 ASSERT_EQ(OK
, CreateResolver("dns_fail.js"));
449 ProxyInfo proxy_info
;
450 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
452 EXPECT_EQ(OK
, result
);
453 EXPECT_FALSE(proxy_info
.is_direct());
454 EXPECT_EQ("success:80", proxy_info
.proxy_server().ToURI());
457 TEST_F(ProxyResolverV8Test
, DNSResolutionOfInternationDomainName
) {
458 ASSERT_EQ(OK
, CreateResolver("international_domain_names.js"));
460 // Execute FindProxyForURL().
461 ProxyInfo proxy_info
;
462 int result
= resolver().GetProxyForURL(kQueryUrl
, &proxy_info
, bindings());
464 EXPECT_EQ(OK
, result
);
465 EXPECT_TRUE(proxy_info
.is_direct());
467 // Check that the international domain name was converted to punycode
468 // before passing it onto the bindings layer.
469 ASSERT_EQ(1u, bindings()->dns_resolves
.size());
470 EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves
[0]);
472 ASSERT_EQ(1u, bindings()->dns_resolves_ex
.size());
473 EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves_ex
[0]);
476 // Test that when resolving a URL which contains an IPv6 string literal, the
477 // brackets are removed from the host before passing it down to the PAC script.
478 // If we don't do this, then subsequent calls to dnsResolveEx(host) will be
479 // doomed to fail since it won't correspond with a valid name.
480 TEST_F(ProxyResolverV8Test
, IPv6HostnamesNotBracketed
) {
481 ASSERT_EQ(OK
, CreateResolver("resolve_host.js"));
483 ProxyInfo proxy_info
;
484 int result
= resolver().GetProxyForURL(
485 GURL("http://[abcd::efff]:99/watsupdawg"), &proxy_info
, bindings());
487 EXPECT_EQ(OK
, result
);
488 EXPECT_TRUE(proxy_info
.is_direct());
490 // We called dnsResolveEx() exactly once, by passing through the "host"
491 // argument to FindProxyForURL(). The brackets should have been stripped.
492 ASSERT_EQ(1U, bindings()->dns_resolves_ex
.size());
493 EXPECT_EQ("abcd::efff", bindings()->dns_resolves_ex
[0]);
496 // Test that terminating a script within DnsResolve() leads to eventual
497 // termination of the script. Also test that repeatedly calling terminate is
498 // safe, and running the script again after termination still works.
499 TEST_F(ProxyResolverV8Test
, Terminate
) {
500 ASSERT_EQ(OK
, CreateResolver("terminate.js"));
502 // Terminate script execution upon reaching dnsResolve(). Note that
503 // termination may not take effect right away (so the subsequent dnsResolve()
504 // and alert() may be run).
505 bindings()->should_terminate
= true;
507 ProxyInfo proxy_info
;
509 resolver().GetProxyForURL(GURL("http://hang/"), &proxy_info
, bindings());
511 // The script execution was terminated.
512 EXPECT_EQ(ERR_PAC_SCRIPT_FAILED
, result
);
514 EXPECT_EQ(1U, bindings()->dns_resolves
.size());
515 EXPECT_GE(2U, bindings()->dns_resolves_ex
.size());
516 EXPECT_GE(1U, bindings()->alerts
.size());
518 EXPECT_EQ(1U, bindings()->errors
.size());
520 // Termination shows up as an uncaught exception without any message.
521 EXPECT_EQ("", bindings()->errors
[0]);
523 bindings()->errors
.clear();
525 // Try running the script again, this time with a different input which won't
526 // cause a termination+hang.
527 result
= resolver().GetProxyForURL(GURL("http://kittens/"), &proxy_info
,
530 EXPECT_EQ(OK
, result
);
531 EXPECT_EQ(0u, bindings()->errors
.size());
532 EXPECT_EQ("kittens:88", proxy_info
.proxy_server().ToURI());