123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "services/proxy_resolver/proxy_resolver_v8.h"
- #include "base/compiler_specific.h"
- #include "base/files/file_util.h"
- #include "base/path_service.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/test/task_environment.h"
- #include "net/base/net_errors.h"
- #include "net/base/proxy_string_util.h"
- #include "net/proxy_resolution/pac_file_data.h"
- #include "net/proxy_resolution/proxy_info.h"
- #include "net/test/gtest_util.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "url/gurl.h"
- using net::test::IsError;
- using net::test::IsOk;
- using ::testing::IsEmpty;
- namespace proxy_resolver {
- namespace {
- // Javascript bindings for ProxyResolverV8, which returns mock values.
- // Each time one of the bindings is called into, we push the input into a
- // list, for later verification.
- class MockJSBindings : public ProxyResolverV8::JSBindings {
- public:
- MockJSBindings()
- : my_ip_address_count(0),
- my_ip_address_ex_count(0),
- should_terminate(false) {}
- void Alert(const std::u16string& message) override {
- VLOG(1) << "PAC-alert: " << message; // Helpful when debugging.
- alerts.push_back(base::UTF16ToUTF8(message));
- }
- bool ResolveDns(const std::string& host,
- net::ProxyResolveDnsOperation op,
- std::string* output,
- bool* terminate) override {
- *terminate = should_terminate;
- if (op == net::ProxyResolveDnsOperation::MY_IP_ADDRESS) {
- my_ip_address_count++;
- *output = my_ip_address_result;
- return !my_ip_address_result.empty();
- }
- if (op == net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX) {
- my_ip_address_ex_count++;
- *output = my_ip_address_ex_result;
- return !my_ip_address_ex_result.empty();
- }
- if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE) {
- dns_resolves.push_back(host);
- *output = dns_resolve_result;
- return !dns_resolve_result.empty();
- }
- if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE_EX) {
- dns_resolves_ex.push_back(host);
- *output = dns_resolve_ex_result;
- return !dns_resolve_ex_result.empty();
- }
- CHECK(false);
- return false;
- }
- void OnError(int line_number, const std::u16string& message) override {
- // Helpful when debugging.
- VLOG(1) << "PAC-error: [" << line_number << "] " << message;
- errors.push_back(base::UTF16ToUTF8(message));
- errors_line_number.push_back(line_number);
- }
- // Mock values to return.
- std::string my_ip_address_result;
- std::string my_ip_address_ex_result;
- std::string dns_resolve_result;
- std::string dns_resolve_ex_result;
- // Inputs we got called with.
- std::vector<std::string> alerts;
- std::vector<std::string> errors;
- std::vector<int> errors_line_number;
- std::vector<std::string> dns_resolves;
- std::vector<std::string> dns_resolves_ex;
- int my_ip_address_count;
- int my_ip_address_ex_count;
- // Whether ResolveDns() should terminate script execution.
- bool should_terminate;
- };
- class ProxyResolverV8Test : public testing::Test {
- public:
- // Creates a ProxyResolverV8 using the PAC script contained in |filename|. If
- // called more than once, the previous ProxyResolverV8 is deleted.
- int CreateResolver(const char* filename) {
- base::FilePath path;
- base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
- path = path.AppendASCII("services");
- path = path.AppendASCII("proxy_resolver");
- path = path.AppendASCII("test");
- path = path.AppendASCII("data");
- path = path.AppendASCII("proxy_resolver_v8_unittest");
- path = path.AppendASCII(filename);
- // Try to read the file from disk.
- std::string file_contents;
- bool ok = base::ReadFileToString(path, &file_contents);
- // If we can't load the file from disk, something is misconfigured.
- if (!ok) {
- LOG(ERROR) << "Failed to read file: " << path.value();
- return net::ERR_FAILED;
- }
- // Create the ProxyResolver using the PAC script.
- return ProxyResolverV8::Create(net::PacFileData::FromUTF8(file_contents),
- bindings(), &resolver_);
- }
- ProxyResolverV8& resolver() {
- DCHECK(resolver_);
- return *resolver_;
- }
- MockJSBindings* bindings() { return &js_bindings_; }
- private:
- base::test::TaskEnvironment task_environment_;
- MockJSBindings js_bindings_;
- std::unique_ptr<ProxyResolverV8> resolver_;
- };
- TEST_F(ProxyResolverV8Test, Direct) {
- ASSERT_THAT(CreateResolver("direct.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_TRUE(proxy_info.is_direct());
- EXPECT_EQ(0U, bindings()->alerts.size());
- EXPECT_EQ(0U, bindings()->errors.size());
- }
- TEST_F(ProxyResolverV8Test, ReturnEmptyString) {
- ASSERT_THAT(CreateResolver("return_empty_string.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_TRUE(proxy_info.is_direct());
- EXPECT_EQ(0U, bindings()->alerts.size());
- EXPECT_EQ(0U, bindings()->errors.size());
- }
- TEST_F(ProxyResolverV8Test, Basic) {
- ASSERT_THAT(CreateResolver("passthrough.js"), IsOk());
- // The "FindProxyForURL" of this PAC script simply concatenates all of the
- // arguments into a pseudo-host. The purpose of this test is to verify that
- // the correct arguments are being passed to FindProxyForURL().
- {
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://query.com/path"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_EQ("http.query.com.path.query.com:80",
- ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- {
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("ftp://query.com:90/path"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- // Note that FindProxyForURL(url, host) does not expect |host| to contain
- // the port number.
- EXPECT_EQ("ftp.query.com.90.path.query.com:80",
- ProxyServerToProxyUri(proxy_info.proxy_server()));
- EXPECT_EQ(0U, bindings()->alerts.size());
- EXPECT_EQ(0U, bindings()->errors.size());
- }
- }
- TEST_F(ProxyResolverV8Test, BadReturnType) {
- // These are the filenames of PAC scripts which each return a non-string
- // types for FindProxyForURL(). They should all fail with
- // net::ERR_PAC_SCRIPT_FAILED.
- static const char* const filenames[] = {
- "return_undefined.js", "return_integer.js", "return_function.js",
- "return_object.js",
- // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ?
- "return_null.js"};
- for (size_t i = 0; i < std::size(filenames); ++i) {
- ASSERT_THAT(CreateResolver(filenames[i]), IsOk());
- MockJSBindings bindings;
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, &bindings);
- EXPECT_THAT(result, IsError(net::ERR_PAC_SCRIPT_FAILED));
- EXPECT_EQ(0U, bindings.alerts.size());
- ASSERT_EQ(1U, bindings.errors.size());
- EXPECT_EQ("FindProxyForURL() did not return a string.", bindings.errors[0]);
- EXPECT_EQ(-1, bindings.errors_line_number[0]);
- }
- }
- // Try using a PAC script which defines no "FindProxyForURL" function.
- TEST_F(ProxyResolverV8Test, NoEntryPoint) {
- EXPECT_THAT(CreateResolver("no_entrypoint.js"),
- IsError(net::ERR_PAC_SCRIPT_FAILED));
- ASSERT_EQ(1U, bindings()->errors.size());
- EXPECT_EQ("FindProxyForURL is undefined or not a function.",
- bindings()->errors[0]);
- EXPECT_EQ(-1, bindings()->errors_line_number[0]);
- }
- // Try loading a malformed PAC script.
- TEST_F(ProxyResolverV8Test, ParseError) {
- EXPECT_THAT(CreateResolver("missing_close_brace.js"),
- IsError(net::ERR_PAC_SCRIPT_FAILED));
- EXPECT_EQ(0U, bindings()->alerts.size());
- // We get one error during compilation.
- ASSERT_EQ(1U, bindings()->errors.size());
- EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
- bindings()->errors[0]);
- EXPECT_EQ(7, bindings()->errors_line_number[0]);
- }
- // Run a PAC script several times, which has side-effects.
- TEST_F(ProxyResolverV8Test, SideEffects) {
- ASSERT_THAT(CreateResolver("side_effects.js"), IsOk());
- // The PAC script increments a counter each time we invoke it.
- for (int i = 0; i < 3; ++i) {
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i),
- ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- // Reload the script -- the javascript environment should be reset, hence
- // the counter starts over.
- ASSERT_THAT(CreateResolver("side_effects.js"), IsOk());
- for (int i = 0; i < 3; ++i) {
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i),
- ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- }
- // Execute a PAC script which throws an exception in FindProxyForURL.
- TEST_F(ProxyResolverV8Test, UnhandledException) {
- ASSERT_THAT(CreateResolver("unhandled_exception.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsError(net::ERR_PAC_SCRIPT_FAILED));
- EXPECT_EQ(0U, bindings()->alerts.size());
- ASSERT_EQ(1U, bindings()->errors.size());
- EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined",
- bindings()->errors[0]);
- EXPECT_EQ(3, bindings()->errors_line_number[0]);
- }
- // Execute a PAC script which throws an exception when first accessing
- // FindProxyForURL
- TEST_F(ProxyResolverV8Test, ExceptionAccessingFindProxyForURLDuringInit) {
- EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED,
- CreateResolver("exception_findproxyforurl_during_init.js"));
- ASSERT_EQ(2U, bindings()->errors.size());
- EXPECT_EQ("Uncaught crash!", bindings()->errors[0]);
- EXPECT_EQ(9, bindings()->errors_line_number[0]);
- EXPECT_EQ("Accessing FindProxyForURL threw an exception.",
- bindings()->errors[1]);
- EXPECT_EQ(-1, bindings()->errors_line_number[1]);
- }
- // Execute a PAC script which throws an exception during the second access to
- // FindProxyForURL
- TEST_F(ProxyResolverV8Test, ExceptionAccessingFindProxyForURLDuringResolve) {
- ASSERT_THAT(CreateResolver("exception_findproxyforurl_during_resolve.js"),
- IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsError(net::ERR_PAC_SCRIPT_FAILED));
- ASSERT_EQ(2U, bindings()->errors.size());
- EXPECT_EQ("Uncaught crash!", bindings()->errors[0]);
- EXPECT_EQ(17, bindings()->errors_line_number[0]);
- EXPECT_EQ("Accessing FindProxyForURL threw an exception.",
- bindings()->errors[1]);
- EXPECT_EQ(-1, bindings()->errors_line_number[1]);
- }
- TEST_F(ProxyResolverV8Test, ReturnUnicode) {
- ASSERT_THAT(CreateResolver("return_unicode.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- // The result from this resolve was unparseable, because it
- // wasn't ASCII.
- EXPECT_THAT(result, IsError(net::ERR_PAC_SCRIPT_FAILED));
- }
- // Test the PAC library functions that we expose in the JS environment.
- TEST_F(ProxyResolverV8Test, JavascriptLibrary) {
- ASSERT_THAT(CreateResolver("pac_library_unittest.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- // If the javascript side of this unit-test fails, it will throw a javascript
- // exception. Otherwise it will return "PROXY success:80".
- EXPECT_THAT(bindings()->alerts, IsEmpty());
- EXPECT_THAT(bindings()->errors, IsEmpty());
- ASSERT_THAT(result, IsOk());
- EXPECT_EQ("success:80", ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- // Test marshalling/un-marshalling of values between C++/V8.
- TEST_F(ProxyResolverV8Test, V8Bindings) {
- ASSERT_THAT(CreateResolver("bindings.js"), IsOk());
- bindings()->dns_resolve_result = "127.0.0.1";
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_TRUE(proxy_info.is_direct());
- EXPECT_EQ(0U, bindings()->errors.size());
- // Alert was called 5 times.
- ASSERT_EQ(5U, bindings()->alerts.size());
- EXPECT_EQ("undefined", bindings()->alerts[0]);
- EXPECT_EQ("null", bindings()->alerts[1]);
- EXPECT_EQ("undefined", bindings()->alerts[2]);
- EXPECT_EQ("[object Object]", bindings()->alerts[3]);
- EXPECT_EQ("exception from calling toString()", bindings()->alerts[4]);
- // DnsResolve was called 8 times, however only 2 of those were string
- // parameters. (so 6 of them failed immediately).
- ASSERT_EQ(2U, bindings()->dns_resolves.size());
- EXPECT_EQ("", bindings()->dns_resolves[0]);
- EXPECT_EQ("arg1", bindings()->dns_resolves[1]);
- // MyIpAddress was called two times.
- EXPECT_EQ(2, bindings()->my_ip_address_count);
- // MyIpAddressEx was called once.
- EXPECT_EQ(1, bindings()->my_ip_address_ex_count);
- // DnsResolveEx was called 2 times.
- ASSERT_EQ(2U, bindings()->dns_resolves_ex.size());
- EXPECT_EQ("is_resolvable", bindings()->dns_resolves_ex[0]);
- EXPECT_EQ("foobar", bindings()->dns_resolves_ex[1]);
- }
- // Test calling a binding (myIpAddress()) from the script's global scope.
- // http://crbug.com/40026
- TEST_F(ProxyResolverV8Test, BindingCalledDuringInitialization) {
- ASSERT_THAT(CreateResolver("binding_from_global.js"), IsOk());
- // myIpAddress() got called during initialization of the script.
- EXPECT_EQ(1, bindings()->my_ip_address_count);
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_FALSE(proxy_info.is_direct());
- EXPECT_EQ("127.0.0.1:80", ProxyServerToProxyUri(proxy_info.proxy_server()));
- // Check that no other bindings were called.
- EXPECT_EQ(0U, bindings()->errors.size());
- ASSERT_EQ(0U, bindings()->alerts.size());
- ASSERT_EQ(0U, bindings()->dns_resolves.size());
- EXPECT_EQ(0, bindings()->my_ip_address_ex_count);
- ASSERT_EQ(0U, bindings()->dns_resolves_ex.size());
- }
- // Try loading a PAC script that ends with a comment and has no terminal
- // newline. This should not cause problems with the PAC utility functions
- // that we add to the script's environment.
- // http://crbug.com/22864
- TEST_F(ProxyResolverV8Test, EndsWithCommentNoNewline) {
- ASSERT_THAT(CreateResolver("ends_with_comment.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_FALSE(proxy_info.is_direct());
- EXPECT_EQ("success:80", ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- // Try loading a PAC script that ends with a statement and has no terminal
- // newline. This should not cause problems with the PAC utility functions
- // that we add to the script's environment.
- // http://crbug.com/22864
- TEST_F(ProxyResolverV8Test, EndsWithStatementNoNewline) {
- ASSERT_THAT(CreateResolver("ends_with_statement_no_semicolon.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_FALSE(proxy_info.is_direct());
- EXPECT_EQ("success:3", ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- // Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(),
- // dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding
- // returns empty string (failure). This simulates the return values from
- // those functions when the underlying DNS resolution fails.
- TEST_F(ProxyResolverV8Test, DNSResolutionFailure) {
- ASSERT_THAT(CreateResolver("dns_fail.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_FALSE(proxy_info.is_direct());
- EXPECT_EQ("success:80", ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- TEST_F(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) {
- ASSERT_THAT(CreateResolver("international_domain_names.js"), IsOk());
- // Execute FindProxyForURL().
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(GURL("http://www.google.com"),
- &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_TRUE(proxy_info.is_direct());
- // Check that the international domain name was converted to punycode
- // before passing it onto the bindings layer.
- ASSERT_EQ(1u, bindings()->dns_resolves.size());
- EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves[0]);
- ASSERT_EQ(1u, bindings()->dns_resolves_ex.size());
- EXPECT_EQ("xn--bcher-kva.ch", bindings()->dns_resolves_ex[0]);
- }
- // Test that when resolving a URL which contains an IPv6 string literal, the
- // brackets are removed from the host before passing it down to the PAC script.
- // If we don't do this, then subsequent calls to dnsResolveEx(host) will be
- // doomed to fail since it won't correspond with a valid name.
- TEST_F(ProxyResolverV8Test, IPv6HostnamesNotBracketed) {
- ASSERT_THAT(CreateResolver("resolve_host.js"), IsOk());
- net::ProxyInfo proxy_info;
- int result = resolver().GetProxyForURL(
- GURL("http://[abcd::efff]:99/watsupdawg"), &proxy_info, bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_TRUE(proxy_info.is_direct());
- // We called dnsResolveEx() exactly once, by passing through the "host"
- // argument to FindProxyForURL(). The brackets should have been stripped.
- ASSERT_EQ(1U, bindings()->dns_resolves_ex.size());
- EXPECT_EQ("abcd::efff", bindings()->dns_resolves_ex[0]);
- }
- // Test that terminating a script within DnsResolve() leads to eventual
- // termination of the script. Also test that repeatedly calling terminate is
- // safe, and running the script again after termination still works.
- TEST_F(ProxyResolverV8Test, Terminate) {
- ASSERT_THAT(CreateResolver("terminate.js"), IsOk());
- // Terminate script execution upon reaching dnsResolve(). Note that
- // termination may not take effect right away (so the subsequent dnsResolve()
- // and alert() may be run).
- bindings()->should_terminate = true;
- net::ProxyInfo proxy_info;
- int result =
- resolver().GetProxyForURL(GURL("http://hang/"), &proxy_info, bindings());
- // The script execution was terminated.
- EXPECT_THAT(result, IsError(net::ERR_PAC_SCRIPT_FAILED));
- EXPECT_EQ(1U, bindings()->dns_resolves.size());
- EXPECT_GE(2U, bindings()->dns_resolves_ex.size());
- EXPECT_GE(1U, bindings()->alerts.size());
- EXPECT_EQ(1U, bindings()->errors.size());
- // Termination shows up as an uncaught exception without any message.
- EXPECT_EQ("", bindings()->errors[0]);
- bindings()->errors.clear();
- // Try running the script again, this time with a different input which won't
- // cause a termination+hang.
- result = resolver().GetProxyForURL(GURL("http://kittens/"), &proxy_info,
- bindings());
- EXPECT_THAT(result, IsOk());
- EXPECT_EQ(0u, bindings()->errors.size());
- EXPECT_EQ("kittens:88", ProxyServerToProxyUri(proxy_info.proxy_server()));
- }
- } // namespace
- } // namespace proxy_resolver
|