diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 15:05:36 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:33:47 +0000 |
commit | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (patch) | |
tree | d55b4003bde34d7d05f558f02cfd82b2a66a7aac /chromium/net/proxy_resolution | |
parent | 2b94bfe47ccb6c08047959d1c26e392919550e86 (diff) | |
download | qtwebengine-chromium-e684a3455bcc29a6e3e66a004e352dea4e1141e7.tar.gz |
BASELINE: Update Chromium to 72.0.3626.110 and Ninja to 1.9.0
Change-Id: Ic57220b00ecc929a893c91f5cc552f5d3e99e922
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/net/proxy_resolution')
31 files changed, 1654 insertions, 536 deletions
diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h index 512f532ee4c..38492debfee 100644 --- a/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h +++ b/chromium/net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ -#define NET_PROXY_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ +#ifndef NET_PROXY_RESOLUTION_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ +#define NET_PROXY_RESOLUTION_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ #include <stddef.h> @@ -194,4 +194,4 @@ class NET_EXPORT_PRIVATE DhcpPacFileAdapterFetcher } // namespace net -#endif // NET_PROXY_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ +#endif // NET_PROXY_RESOLUTION_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h index ee49706aa1b..433538458a1 100644 --- a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_DHCP_PAC_FILE_FETCHER_H_ -#define NET_PROXY_DHCP_PAC_FILE_FETCHER_H_ +#ifndef NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_H_ +#define NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_H_ #include "base/compiler_specific.h" #include "base/macros.h" @@ -111,4 +111,4 @@ class NET_EXPORT_PRIVATE DoNothingDhcpPacFileFetcher } // namespace net -#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_H_ +#endif // NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h index 832cea884be..79d8fae945f 100644 --- a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_factory.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_DHCP_PAC_FILE_FETCHER_FACTORY_H_ -#define NET_PROXY_DHCP_PAC_FILE_FETCHER_FACTORY_H_ +#ifndef NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_FACTORY_H_ +#define NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_FACTORY_H_ #include <memory> @@ -52,4 +52,4 @@ class NET_EXPORT DhcpPacFileFetcherFactory { } // namespace net -#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_FACTORY_H_ +#endif // NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_FACTORY_H_ diff --git a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h index f72fc6f3f81..1ad01e0cf7b 100644 --- a/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h +++ b/chromium/net/proxy_resolution/dhcp_pac_file_fetcher_win.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_DHCP_PAC_FILE_FETCHER_WIN_H_ -#define NET_PROXY_DHCP_PAC_FILE_FETCHER_WIN_H_ +#ifndef NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_WIN_H_ +#define NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_WIN_H_ #include <memory> #include <set> @@ -198,4 +198,4 @@ class NET_EXPORT_PRIVATE DhcpPacFileFetcherWin } // namespace net -#endif // NET_PROXY_DHCP_PAC_FILE_FETCHER_WIN_H_ +#endif // NET_PROXY_RESOLUTION_DHCP_PAC_FILE_FETCHER_WIN_H_ diff --git a/chromium/net/proxy_resolution/mock_pac_file_fetcher.h b/chromium/net/proxy_resolution/mock_pac_file_fetcher.h index fe44406d3fa..7213ad39a26 100644 --- a/chromium/net/proxy_resolution/mock_pac_file_fetcher.h +++ b/chromium/net/proxy_resolution/mock_pac_file_fetcher.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_MOCK_PAC_FILE_FETCHER_H_ -#define NET_PROXY_MOCK_PAC_FILE_FETCHER_H_ +#ifndef NET_PROXY_RESOLUTION_MOCK_PAC_FILE_FETCHER_H_ +#define NET_PROXY_RESOLUTION_MOCK_PAC_FILE_FETCHER_H_ #include "base/compiler_specific.h" #include "net/base/completion_once_callback.h" @@ -50,4 +50,4 @@ class MockPacFileFetcher : public PacFileFetcher { } // namespace net -#endif // NET_PROXY_MOCK_PAC_FILE_FETCHER_H_ +#endif // NET_PROXY_RESOLUTION_MOCK_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h deleted file mode 100644 index 651c4f01940..00000000000 --- a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2015 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. - -#ifndef NET_PROXY_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ -#define NET_PROXY_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ - -#include <utility> - -#include "base/strings/utf_string_conversions.h" -#include "base/threading/thread_checker.h" -#include "net/dns/host_resolver_mojo.h" -#include "net/log/net_log_with_source.h" -#include "net/proxy_resolution/proxy_resolver_v8_tracing.h" - -namespace net { - -// An implementation of ProxyResolverV8Tracing::Bindings that forwards requests -// onto a Client mojo interface. Alert() and OnError() may be called from any -// thread; when they are called from another thread, the calls are proxied to -// the origin task runner. GetHostResolver() and GetNetLogWithSource() may only -// be -// called from the origin task runner. -template <typename Client> -class MojoProxyResolverV8TracingBindings - : public ProxyResolverV8Tracing::Bindings, - public HostResolverMojo::Impl { - public: - explicit MojoProxyResolverV8TracingBindings(Client* client) - : client_(client), host_resolver_(this) { - DCHECK(client_); - } - - // ProxyResolverV8Tracing::Bindings overrides. - void Alert(const base::string16& message) override { - DCHECK(thread_checker_.CalledOnValidThread()); - client_->Alert(base::UTF16ToUTF8(message)); - } - - void OnError(int line_number, const base::string16& message) override { - DCHECK(thread_checker_.CalledOnValidThread()); - client_->OnError(line_number, base::UTF16ToUTF8(message)); - } - - HostResolver* GetHostResolver() override { - DCHECK(thread_checker_.CalledOnValidThread()); - return &host_resolver_; - } - - NetLogWithSource GetNetLogWithSource() override { - DCHECK(thread_checker_.CalledOnValidThread()); - return NetLogWithSource(); - } - - private: - // HostResolverMojo::Impl override. - void ResolveDns(std::unique_ptr<HostResolver::RequestInfo> request_info, - interfaces::HostResolverRequestClientPtr client) override { - DCHECK(thread_checker_.CalledOnValidThread()); - client_->ResolveDns(std::move(request_info), std::move(client)); - } - - base::ThreadChecker thread_checker_; - Client* client_; - HostResolverMojo host_resolver_; -}; - -} // namespace net - -#endif // NET_PROXY_RESOLUTION_MOJO_PROXY_RESOLVER_V8_TRACING_BINDINGS_H_ diff --git a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc b/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc deleted file mode 100644 index ffd37b145c4..00000000000 --- a/chromium/net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 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 "net/proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings.h" - -#include <string> -#include <utility> -#include <vector> - -#include "base/macros.h" -#include "base/strings/utf_string_conversions.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { - -class MojoProxyResolverV8TracingBindingsTest : public testing::Test { - public: - MojoProxyResolverV8TracingBindingsTest() = default; - - void SetUp() override { - bindings_.reset(new MojoProxyResolverV8TracingBindings< - MojoProxyResolverV8TracingBindingsTest>(this)); - } - - void Alert(const std::string& message) { alerts_.push_back(message); } - - void OnError(int32_t line_number, const std::string& message) { - errors_.push_back(std::make_pair(line_number, message)); - } - - void ResolveDns(std::unique_ptr<HostResolver::RequestInfo> request_info, - interfaces::HostResolverRequestClientPtr client) {} - - protected: - std::unique_ptr<MojoProxyResolverV8TracingBindings< - MojoProxyResolverV8TracingBindingsTest>> - bindings_; - - std::vector<std::string> alerts_; - std::vector<std::pair<int, std::string>> errors_; - - private: - DISALLOW_COPY_AND_ASSIGN(MojoProxyResolverV8TracingBindingsTest); -}; - -TEST_F(MojoProxyResolverV8TracingBindingsTest, Basic) { - bindings_->Alert(base::ASCIIToUTF16("alert")); - bindings_->OnError(-1, base::ASCIIToUTF16("error")); - - EXPECT_TRUE(bindings_->GetHostResolver()); - EXPECT_FALSE(bindings_->GetNetLogWithSource().net_log()); - - ASSERT_EQ(1u, alerts_.size()); - EXPECT_EQ("alert", alerts_[0]); - ASSERT_EQ(1u, errors_.size()); - EXPECT_EQ(-1, errors_[0].first); - EXPECT_EQ("error", errors_[0].second); -} - -} // namespace net diff --git a/chromium/net/proxy_resolution/pac_file_data.h b/chromium/net/proxy_resolution/pac_file_data.h index 5f75cf5b712..0a961fb3850 100644 --- a/chromium/net/proxy_resolution/pac_file_data.h +++ b/chromium/net/proxy_resolution/pac_file_data.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_PAC_FILE_DATA_H_ -#define NET_PROXY_PAC_FILE_DATA_H_ +#ifndef NET_PROXY_RESOLUTION_PAC_FILE_DATA_H_ +#define NET_PROXY_RESOLUTION_PAC_FILE_DATA_H_ #include "base/memory/ref_counted.h" #include "base/strings/string16.h" @@ -64,4 +64,4 @@ class NET_EXPORT_PRIVATE PacFileData } // namespace net -#endif // NET_PROXY_PAC_FILE_DATA_H_ +#endif // NET_PROXY_RESOLUTION_PAC_FILE_DATA_H_ diff --git a/chromium/net/proxy_resolution/pac_file_decider.h b/chromium/net/proxy_resolution/pac_file_decider.h index aaa0c25236b..af8438d414f 100644 --- a/chromium/net/proxy_resolution/pac_file_decider.h +++ b/chromium/net/proxy_resolution/pac_file_decider.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_PAC_FILE_DECIDER_H_ -#define NET_PROXY_PAC_FILE_DECIDER_H_ +#ifndef NET_PROXY_RESOLUTION_PAC_FILE_DECIDER_H_ +#define NET_PROXY_RESOLUTION_PAC_FILE_DECIDER_H_ #include <stddef.h> @@ -209,4 +209,4 @@ class NET_EXPORT_PRIVATE PacFileDecider { } // namespace net -#endif // NET_PROXY_PAC_FILE_DECIDER_H_ +#endif // NET_PROXY_RESOLUTION_PAC_FILE_DECIDER_H_ diff --git a/chromium/net/proxy_resolution/pac_file_fetcher.h b/chromium/net/proxy_resolution/pac_file_fetcher.h index bd0ca21104d..f446e36a2d5 100644 --- a/chromium/net/proxy_resolution/pac_file_fetcher.h +++ b/chromium/net/proxy_resolution/pac_file_fetcher.h @@ -6,8 +6,8 @@ // script. It is specific to fetching a PAC script; enforces timeout, max-size, // status code. -#ifndef NET_PROXY_PAC_FILE_FETCHER_H_ -#define NET_PROXY_PAC_FILE_FETCHER_H_ +#ifndef NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_H_ +#define NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_H_ #include "base/strings/string16.h" #include "net/base/completion_once_callback.h" @@ -66,4 +66,4 @@ class NET_EXPORT_PRIVATE PacFileFetcher { } // namespace net -#endif // NET_PROXY_PAC_FILE_FETCHER_H_ +#endif // NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_H_ diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc b/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc index 3b134a19e1b..41c67a80491 100644 --- a/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl.cc @@ -59,25 +59,44 @@ bool IsPacMimeType(const std::string& mime_type) { return false; } +struct BomMapping { + base::StringPiece prefix; + const char* charset; +}; + +const BomMapping kBomMappings[] = { + {"\xFE\xFF", "utf-16be"}, + {"\xFF\xFE", "utf-16le"}, + {"\xEF\xBB\xBF", "utf-8"}, +}; + // Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul // to |*utf16|. // If |charset| is empty, then we don't know what it was and guess. void ConvertResponseToUTF16(const std::string& charset, const std::string& bytes, base::string16* utf16) { - const char* codepage; - if (charset.empty()) { - // Assume ISO-8859-1 if no charset was specified. - codepage = kCharsetLatin1; - } else { - // Otherwise trust the charset that was provided. - codepage = charset.c_str(); + // Guess the charset by looking at the BOM. + base::StringPiece bytes_str(bytes); + for (const auto& bom : kBomMappings) { + if (bytes_str.starts_with(bom.prefix)) { + return ConvertResponseToUTF16( + bom.charset, + // Strip the BOM in the converted response. + bytes.substr(bom.prefix.size()), utf16); + } + } + + // Otherwise assume ISO-8859-1 if no charset was specified. + return ConvertResponseToUTF16(kCharsetLatin1, bytes, utf16); } + DCHECK(!charset.empty()); + // Be generous in the conversion -- if any characters lie outside of |charset| // (i.e. invalid), then substitute them with U+FFFD rather than failing. - ConvertToUTF16WithSubstitutions(bytes, codepage, utf16); + ConvertToUTF16WithSubstitutions(bytes, charset.c_str(), utf16); } } // namespace diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl.h b/chromium/net/proxy_resolution/pac_file_fetcher_impl.h index 448b0096eb2..a9e13db5196 100644 --- a/chromium/net/proxy_resolution/pac_file_fetcher_impl.h +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef NET_PROXY_PAC_FILE_FETCHER_IMPL_H_ -#define NET_PROXY_PAC_FILE_FETCHER_IMPL_H_ +#ifndef NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_IMPL_H_ +#define NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_IMPL_H_ #include <stddef.h> @@ -167,4 +167,4 @@ class NET_EXPORT PacFileFetcherImpl : public PacFileFetcher, } // namespace net -#endif // NET_PROXY_PAC_FILE_FETCHER_IMPL_H_ +#endif // NET_PROXY_RESOLUTION_PAC_FILE_FETCHER_IMPL_H_ diff --git a/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc b/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc index b658df80daa..9db5fce8c12 100644 --- a/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc +++ b/chromium/net/proxy_resolution/pac_file_fetcher_impl_unittest.cc @@ -552,6 +552,18 @@ TEST_F(PacFileFetcherImplTest, Encodings) { EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(ASCIIToUTF16("This was encoded as UTF-16BE.\n"), text); } + + // Test a response that lacks a charset, however starts with a UTF8 BOM. + { + GURL url(test_server_.GetURL("/utf8_bom")); + base::string16 text; + TestCompletionCallback callback; + int result = pac_fetcher->Fetch(url, &text, callback.callback(), + TRAFFIC_ANNOTATION_FOR_TESTS); + EXPECT_THAT(result, IsError(ERR_IO_PENDING)); + EXPECT_THAT(callback.WaitForResult(), IsOk()); + EXPECT_EQ(ASCIIToUTF16("/* UTF8 */\n"), text); + } } TEST_F(PacFileFetcherImplTest, DataURLs) { diff --git a/chromium/net/proxy_resolution/pac_library.cc b/chromium/net/proxy_resolution/pac_library.cc new file mode 100644 index 00000000000..69cb35fd06b --- /dev/null +++ b/chromium/net/proxy_resolution/pac_library.cc @@ -0,0 +1,290 @@ +// Copyright 2018 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 "net/proxy_resolution/pac_library.h" +#include "net/base/address_list.h" +#include "net/base/ip_address.h" +#include "net/base/network_interfaces.h" +#include "net/dns/host_resolver_proc.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/udp_client_socket.h" + +namespace net { + +namespace { + +enum class Mode { + kMyIpAddress, + kMyIpAddressEx, +}; + +// Helper used to accumulate and select the best candidate IP addresses. +// +// myIpAddress() is a broken API available to PAC scripts. +// It has the problematic definition of: +// "Returns the IP address of the host machine." +// +// This has ambiguity on what should happen for multi-homed hosts which may have +// multiple IP addresses to choose from. To be unambiguous we would need to +// know which hosts is going to be connected to, in order to use the outgoing +// IP for that request. +// +// However at this point that is not known, as the proxy still hasn't been +// decided. +// +// The strategy used here is to prioritize the IP address that would be used +// for connecting to the public internet by testing which interface is used for +// connecting to 8.8.8.8 and 2001:4860:4860::8888 (public IPs). +// +// If that fails, we will try resolving the machine's hostname, and also probing +// for routes in the private IP space. +// +// Link-local IP addresses are not generally returned, however may be if no +// other IP was found by the probes. +class MyIpAddressImpl { + public: + MyIpAddressImpl() = default; + + // Used for mocking the socket dependency. + void SetSocketFactoryForTest(ClientSocketFactory* socket_factory) { + override_socket_factory_ = socket_factory; + } + + // Used for mocking the DNS dependency. + void SetDNSResultForTest(const AddressList& addrs) { + override_dns_result_ = std::make_unique<AddressList>(addrs); + } + + IPAddressList Run(Mode mode) { + DCHECK(candidate_ips_.empty()); + DCHECK(link_local_ips_.empty()); + DCHECK(!done_); + + mode_ = mode; + + // Try several different methods to obtain IP addresses. + TestPublicInternetRoutes(); + TestResolvingHostname(); + TestPrivateIPRoutes(); + + return mode_ == Mode::kMyIpAddress ? GetResultForMyIpAddress() + : GetResultForMyIpAddressEx(); + } + + private: + // Adds |address| to the result. + void Add(const IPAddress& address) { + if (done_) + return; + + // Don't consider loopback addresses (ex: 127.0.0.1). These can notably be + // returned when probing addresses associated with the hostname. + if (address.IsLoopback()) + return; + + if (!seen_ips_.insert(address).second) + return; // Duplicate IP address. + + // Link-local addresses are only used as a last-resort if there are no + // better addresses. + if (address.IsLinkLocal()) { + link_local_ips_.push_back(address); + return; + } + + // For legacy reasons IPv4 addresses are favored over IPv6 for myIpAddress() + // - https://crbug.com/905126 - so this only stops the search when a IPv4 + // address is found. + if ((mode_ == Mode::kMyIpAddress) && address.IsIPv4()) + done_ = true; + + candidate_ips_.push_back(address); + } + + IPAddressList GetResultForMyIpAddress() const { + DCHECK_EQ(Mode::kMyIpAddress, mode_); + + if (!candidate_ips_.empty()) + return GetSingleResultFavoringIPv4(candidate_ips_); + + if (!link_local_ips_.empty()) + return GetSingleResultFavoringIPv4(link_local_ips_); + + return {}; + } + + IPAddressList GetResultForMyIpAddressEx() const { + DCHECK_EQ(Mode::kMyIpAddressEx, mode_); + + if (!candidate_ips_.empty()) + return candidate_ips_; + + if (!link_local_ips_.empty()) { + // Note that only a single link-local address is returned here, even + // though multiple could be returned for this API. See + // http://crbug.com/905366 before expanding this. + return GetSingleResultFavoringIPv4(link_local_ips_); + } + + return {}; + } + + // Tests what source IP address would be used for sending a UDP packet to the + // given destination IP. This does not hit the network and should be fast. + void TestRoute(const IPAddress& destination_ip) { + if (done_) + return; + + ClientSocketFactory* socket_factory = + override_socket_factory_ + ? override_socket_factory_ + : net::ClientSocketFactory::GetDefaultFactory(); + + auto socket = socket_factory->CreateDatagramClientSocket( + net::DatagramSocket::DEFAULT_BIND, nullptr, net::NetLogSource()); + + IPEndPoint destination(destination_ip, /*port=*/80); + + if (socket->Connect(destination) != OK) + return; + + IPEndPoint source; + if (socket->GetLocalAddress(&source) != OK) + return; + + Add(source.address()); + } + + void TestPublicInternetRoutes() { + if (done_) + return; + + // 8.8.8.8 and 2001:4860:4860::8888 are Google DNS. + TestRoute(IPAddress(8, 8, 8, 8)); + TestRoute(IPAddress(0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, + 0, 0x88, 0x88)); + + MarkAsDoneIfHaveCandidates(); + } + + // Marks the current search as done if candidate IPs have been found. + // + // This is used to stop exploring for IPs if any of the high-level tests find + // a match (i.e. either the public internet route test, or hostname test, or + // private route test found something). + // + // In the case of myIpAddressEx() this means it will be conservative in which + // IPs it returns and not enumerate the full set. See http://crbug.com/905366 + // before expanding that policy. + void MarkAsDoneIfHaveCandidates() { + if (!candidate_ips_.empty()) + done_ = true; + } + + void TestPrivateIPRoutes() { + if (done_) + return; + + // Representative IP from each range in RFC 1918. + TestRoute(IPAddress(10, 0, 0, 0)); + TestRoute(IPAddress(172, 16, 0, 0)); + TestRoute(IPAddress(192, 168, 0, 0)); + + // Representative IP for Unique Local Address (FC00::/7). + TestRoute(IPAddress(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + MarkAsDoneIfHaveCandidates(); + } + + void TestResolvingHostname() { + if (done_) + return; + + AddressList addrlist; + + int resolver_error; + + if (override_dns_result_) { + addrlist = *override_dns_result_; + resolver_error = addrlist.empty() ? ERR_NAME_NOT_RESOLVED : OK; + } else { + resolver_error = SystemHostResolverCall( + GetHostName(), AddressFamily::ADDRESS_FAMILY_UNSPECIFIED, 0, + &addrlist, + /*os_error=*/nullptr); + } + + if (resolver_error != OK) + return; + + for (const auto& e : addrlist.endpoints()) + Add(e.address()); + + MarkAsDoneIfHaveCandidates(); + } + + static IPAddressList GetSingleResultFavoringIPv4(const IPAddressList& ips) { + for (const auto& ip : ips) { + if (ip.IsIPv4()) + return {ip}; + } + + if (!ips.empty()) + return {ips.front()}; + + return {}; + } + + std::set<IPAddress> seen_ips_; + + // The preferred ordered candidate IPs so far. + IPAddressList candidate_ips_; + + // The link-local IP addresses seen so far (not part of |candidate_ips_|). + IPAddressList link_local_ips_; + + // The operation being carried out. + Mode mode_; + + // Whether the search for results has completed. + // + // Once "done", calling Add() will not change the final result. This is used + // to short-circuit early. + bool done_ = false; + + ClientSocketFactory* override_socket_factory_ = nullptr; + std::unique_ptr<AddressList> override_dns_result_; + + DISALLOW_COPY_AND_ASSIGN(MyIpAddressImpl); +}; + +} // namespace + +IPAddressList PacMyIpAddress() { + MyIpAddressImpl impl; + return impl.Run(Mode::kMyIpAddress); +} + +IPAddressList PacMyIpAddressEx() { + MyIpAddressImpl impl; + return impl.Run(Mode::kMyIpAddressEx); +} + +IPAddressList PacMyIpAddressForTest(ClientSocketFactory* socket_factory, + const AddressList& dns_result) { + MyIpAddressImpl impl; + impl.SetSocketFactoryForTest(socket_factory); + impl.SetDNSResultForTest(dns_result); + return impl.Run(Mode::kMyIpAddress); +} + +IPAddressList PacMyIpAddressExForTest(ClientSocketFactory* socket_factory, + const AddressList& dns_result) { + MyIpAddressImpl impl; + impl.SetSocketFactoryForTest(socket_factory); + impl.SetDNSResultForTest(dns_result); + return impl.Run(Mode::kMyIpAddressEx); +} + +} // namespace net diff --git a/chromium/net/proxy_resolution/pac_library.h b/chromium/net/proxy_resolution/pac_library.h new file mode 100644 index 00000000000..8f58980bf5e --- /dev/null +++ b/chromium/net/proxy_resolution/pac_library.h @@ -0,0 +1,37 @@ +// Copyright 2018 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. + +#ifndef NET_PROXY_RESOLUTION_PAC_LIBRARY_H_ +#define NET_PROXY_RESOLUTION_PAC_LIBRARY_H_ + +#include "net/base/ip_address.h" +#include "net/base/net_export.h" + +// TODO(eroman): Move other PAC library support functions into here. + +namespace net { + +class ClientSocketFactory; +class AddressList; + +// Implementations for myIpAddress() and myIpAddressEx() function calls +// available in the PAC environment. These are expected to be called on a worker +// thread as they may block. +// +// Do not use these outside of PAC as they are broken APIs. See comments in the +// implementation file for details. +NET_EXPORT_PRIVATE IPAddressList PacMyIpAddress(); +NET_EXPORT_PRIVATE IPAddressList PacMyIpAddressEx(); + +// Test exposed variants that allows mocking the UDP and DNS dependencies. +NET_EXPORT_PRIVATE IPAddressList +PacMyIpAddressForTest(ClientSocketFactory* socket_factory, + const AddressList& dns_result); +NET_EXPORT_PRIVATE IPAddressList +PacMyIpAddressExForTest(ClientSocketFactory* socket_factory, + const AddressList& dns_result); + +} // namespace net + +#endif // NET_PROXY_RESOLUTION_PAC_LIBRARY_H_ diff --git a/chromium/net/proxy_resolution/pac_library_unittest.cc b/chromium/net/proxy_resolution/pac_library_unittest.cc new file mode 100644 index 00000000000..d4057830e50 --- /dev/null +++ b/chromium/net/proxy_resolution/pac_library_unittest.cc @@ -0,0 +1,618 @@ +// Copyright 2018 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 "net/proxy_resolution/pac_library.h" + +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/network_interfaces.h" +#include "net/log/net_log_with_source.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/datagram_client_socket.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +// Helper for verifying whether the address list returned by myIpAddress() / +// myIpAddressEx() looks correct. +void VerifyActualMyIpAddresses(const IPAddressList& test_list) { + // Enumerate all of the IP addresses for the system (skipping loopback and + // link-local ones). This is used as a reference implementation to check + // whether |test_list| (which was obtained using a different strategy) looks + // correct. + std::set<IPAddress> candidates; + NetworkInterfaceList networks; + GetNetworkList(&networks, EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES); + for (const auto& network : networks) { + if (network.address.IsLinkLocal() || network.address.IsLoopback()) + continue; + candidates.insert(network.address); + } + + // Ordinarily the machine running this test will have an IP address. However + // for some bot configurations (notably Android) that may not be the case. + EXPECT_EQ(candidates.empty(), test_list.empty()); + + // |test_list| should be a subset of |candidates|. + for (const auto& ip : test_list) + EXPECT_EQ(1u, candidates.count(ip)); +} + +// Tests for PacMyIpAddress() and PacMyIpAddressEx(). +TEST(PacLibraryTest, ActualPacMyIpAddress) { + auto my_ip_addresses = PacMyIpAddress(); + + VerifyActualMyIpAddresses(my_ip_addresses); +} + +TEST(PacLibraryTest, ActualPacMyIpAddressEx) { + VerifyActualMyIpAddresses(PacMyIpAddressEx()); +} + +IPAddress CreateIPAddress(base::StringPiece literal) { + IPAddress result; + if (!result.AssignFromIPLiteral(literal)) { + ADD_FAILURE() << "Failed parsing IP: " << literal; + return IPAddress(); + } + return result; +} + +AddressList CreateAddressList( + const std::vector<base::StringPiece>& ip_literals) { + AddressList result; + for (const auto& ip : ip_literals) + result.push_back(IPEndPoint(CreateIPAddress(ip), 8080)); + return result; +} + +class MockUDPSocket : public DatagramClientSocket { + public: + MockUDPSocket(const IPAddress& peer_ip, + const IPAddress& local_ip, + Error connect_error) + : peer_ip_(peer_ip), local_ip_(local_ip), connect_error_(connect_error) {} + + ~MockUDPSocket() override = default; + + // Socket implementation. + int Read(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) override { + ADD_FAILURE() << "Called Read()"; + return ERR_UNEXPECTED; + } + int Write(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) override { + ADD_FAILURE() << "Called Read()"; + return ERR_UNEXPECTED; + } + int WriteAsync( + DatagramBuffers buffers, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) override { + ADD_FAILURE() << "Called WriteAsync()"; + return ERR_UNEXPECTED; + } + int WriteAsync( + const char* buffer, + size_t buf_len, + CompletionOnceCallback callback, + const NetworkTrafficAnnotationTag& traffic_annotation) override { + ADD_FAILURE() << "Called WriteAsync()"; + return ERR_UNEXPECTED; + } + DatagramBuffers GetUnwrittenBuffers() override { + ADD_FAILURE() << "Called GetUnwrittenBuffers()"; + return DatagramBuffers(); + } + int SetReceiveBufferSize(int32_t size) override { + ADD_FAILURE() << "Called SetReceiveBufferSize()"; + return ERR_UNEXPECTED; + } + int SetSendBufferSize(int32_t size) override { + ADD_FAILURE() << "Called SetSendBufferSize()"; + return ERR_UNEXPECTED; + } + int SetDoNotFragment() override { + ADD_FAILURE() << "Called SetDoNotFragment()"; + return ERR_UNEXPECTED; + } + // DatagramSocket implementation. + void Close() override { ADD_FAILURE() << "Called Close()"; } + int GetPeerAddress(IPEndPoint* address) const override { + ADD_FAILURE() << "Called GetPeerAddress()"; + return ERR_UNEXPECTED; + } + int GetLocalAddress(IPEndPoint* address) const override { + if (connect_error_ != OK) + return connect_error_; + + *address = IPEndPoint(local_ip_, 8080); + return OK; + } + void UseNonBlockingIO() override { + ADD_FAILURE() << "Called UseNonBlockingIO()"; + } + void SetWriteAsyncEnabled(bool enabled) override { + ADD_FAILURE() << "Called SetWriteAsyncEnabled()"; + } + void SetMaxPacketSize(size_t max_packet_size) override { + ADD_FAILURE() << "Called SetWriteAsyncEnabled()"; + } + bool WriteAsyncEnabled() override { + ADD_FAILURE() << "Called WriteAsyncEnabled()"; + return false; + } + void SetWriteMultiCoreEnabled(bool enabled) override { + ADD_FAILURE() << "Called SetWriteMultiCoreEnabled()"; + } + void SetSendmmsgEnabled(bool enabled) override { + ADD_FAILURE() << "Called SetSendmmsgEnabled()"; + } + void SetWriteBatchingActive(bool active) override { + ADD_FAILURE() << "Called SetWriteBatchingActive()"; + } + const NetLogWithSource& NetLog() const override { + ADD_FAILURE() << "Called NetLog()"; + return net_log_; + } + + // DatagramClientSocket implementation. + int Connect(const IPEndPoint& address) override { + EXPECT_EQ(peer_ip_.ToString(), address.address().ToString()); + return connect_error_; + } + int ConnectUsingNetwork(NetworkChangeNotifier::NetworkHandle network, + const IPEndPoint& address) override { + ADD_FAILURE() << "Called ConnectUsingNetwork()"; + return ERR_UNEXPECTED; + } + int ConnectUsingDefaultNetwork(const IPEndPoint& address) override { + ADD_FAILURE() << "Called ConnectUsingDefaultNetwork()"; + return ERR_UNEXPECTED; + } + NetworkChangeNotifier::NetworkHandle GetBoundNetwork() const override { + ADD_FAILURE() << "Called GetBoundNetwork()"; + return network_; + } + void ApplySocketTag(const SocketTag& tag) override { + ADD_FAILURE() << "Called ApplySocketTag()"; + } + void SetMsgConfirm(bool confirm) override { + ADD_FAILURE() << "Called SetMsgConfirm()"; + } + + private: + NetLogWithSource net_log_; + NetworkChangeNotifier::NetworkHandle network_; + + IPAddress peer_ip_; + IPAddress local_ip_; + Error connect_error_; + + DISALLOW_COPY_AND_ASSIGN(MockUDPSocket); +}; + +class MockSocketFactory : public ClientSocketFactory { + public: + MockSocketFactory() = default; + + void AddUDPConnectSuccess(base::StringPiece peer_ip_literal, + base::StringPiece local_ip_literal) { + auto peer_ip = CreateIPAddress(peer_ip_literal); + auto local_ip = CreateIPAddress(local_ip_literal); + + // The address family of local and peer IP must match. + ASSERT_EQ(peer_ip.size(), local_ip.size()); + + udp_sockets_.push_back(std::make_unique<MockUDPSocket>( + peer_ip, local_ip, OK)); + } + + void AddUDPConnectFailure(base::StringPiece peer_ip) { + udp_sockets_.push_back(std::make_unique<MockUDPSocket>( + CreateIPAddress(peer_ip), IPAddress(), ERR_ADDRESS_UNREACHABLE)); + } + + ~MockSocketFactory() override { + EXPECT_EQ(0u, udp_sockets_.size()) + << "Not all of the mock sockets were consumed."; + } + + // ClientSocketFactory + std::unique_ptr<DatagramClientSocket> CreateDatagramClientSocket( + DatagramSocket::BindType bind_type, + NetLog* net_log, + const NetLogSource& source) override { + if (udp_sockets_.empty()) { + ADD_FAILURE() << "Not enough mock UDP sockets"; + return nullptr; + } + + auto result = std::move(udp_sockets_.front()); + udp_sockets_.erase(udp_sockets_.begin()); + return result; + } + std::unique_ptr<TransportClientSocket> CreateTransportClientSocket( + const AddressList& addresses, + std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher, + NetLog* net_log, + const NetLogSource& source) override { + ADD_FAILURE() << "Called CreateTransportClientSocket()"; + return nullptr; + } + std::unique_ptr<SSLClientSocket> CreateSSLClientSocket( + std::unique_ptr<ClientSocketHandle> transport_socket, + const HostPortPair& host_and_port, + const SSLConfig& ssl_config, + const SSLClientSocketContext& context) override { + ADD_FAILURE() << "Called CreateSSLClientSocket()"; + return nullptr; + } + std::unique_ptr<ProxyClientSocket> CreateProxyClientSocket( + std::unique_ptr<ClientSocketHandle> transport_socket, + const std::string& user_agent, + const HostPortPair& endpoint, + HttpAuthController* http_auth_controller, + bool tunnel, + bool using_spdy, + NextProto negotiated_protocol, + bool is_https_proxy, + const NetworkTrafficAnnotationTag& traffic_annotation) override { + ADD_FAILURE() << "Called CreateProxyClientSocket()"; + return nullptr; + } + void ClearSSLSessionCache() override { + ADD_FAILURE() << "Called ClearSSLSessionCache()"; + } + + private: + std::vector<std::unique_ptr<MockUDPSocket>> udp_sockets_; + + DISALLOW_COPY_AND_ASSIGN(MockSocketFactory); +}; + +// Tests myIpAddress() when there is a route to 8.8.8.8. +TEST(PacLibraryTest, PacMyIpAddress8888) { + MockSocketFactory factory; + factory.AddUDPConnectSuccess("8.8.8.8", "192.168.1.1"); + + auto result = PacMyIpAddressForTest(&factory, {}); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("192.168.1.1", result.front().ToString()); +} + +// Tests myIpAddress() when there is no route to 8.8.8.8, but there is one to +// 2001:4860:4860::8888. +TEST(PacLibraryTest, PacMyIpAddress2001) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectSuccess("2001:4860:4860::8888", "2001::beef"); + + AddressList dns_result; + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("2001::beef", result.front().ToString()); +} + +// Tests myIpAddress() when there is no route to 8.8.8.8, no route to +// 2001:4860:4860::8888, however getaddrinfo(gethostname()) finds results. Most +// of those results are skipped over, and the IPv4 one is favored. +TEST(PacLibraryTest, PacMyIpAddressHostname) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = CreateAddressList({ + "169.254.13.16", "127.0.0.1", "::1", "fe89::beef", "2001::f001", + "178.1.99.3", "192.168.1.3", + }); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("178.1.99.3", result.front().ToString()); +} + +// Tests myIpAddress() when there is no route to 8.8.8.8, no route to +// 2001:4860:4860::8888, however getaddrinfo(gethostname()) finds multiple IPv6 +// results. +TEST(PacLibraryTest, PacMyIpAddressHostnameAllIPv6) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = + CreateAddressList({"::1", "2001::f001", "2001::f00d", "169.254.0.6"}); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("2001::f001", result.front().ToString()); +} + +// Tests myIpAddress() when there is no route to 8.8.8.8, no route to +// 2001:4860:4860::8888, no acceptable result in getaddrinfo(gethostname()), +// however there is a route for private address. +TEST(PacLibraryTest, PacMyIpAddressPrivateIPv4) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = CreateAddressList({ + "169.254.13.16", "127.0.0.1", "::1", "fe89::beef", + }); + + factory.AddUDPConnectSuccess("10.0.0.0", "127.0.0.1"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectSuccess("192.168.0.0", "63.31.9.8"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("63.31.9.8", result.front().ToString()); +} + +// Tests myIpAddress() when there is no route to 8.8.8.8, no route to +// 2001:4860:4860::8888, no acceptable result in getaddrinfo(gethostname()), +// however there is a route for private address. +TEST(PacLibraryTest, PacMyIpAddressPrivateIPv6) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result; + + factory.AddUDPConnectSuccess("10.0.0.0", "127.0.0.1"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectSuccess("FC00::", "2001::7777"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("2001::7777", result.front().ToString()); +} + +// Tests myIpAddress() when there are no routes, and getaddrinfo(gethostname()) +// fails. +TEST(PacLibraryTest, PacMyIpAddressAllFail) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result; + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + EXPECT_EQ(0u, result.size()); +} + +// Tests myIpAddress() when there are no routes, and +// getaddrinfo(gethostname()) only returns loopback. +TEST(PacLibraryTest, PacMyIpAddressAllFailOrLoopback) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = CreateAddressList({"127.0.0.1", "::1"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + EXPECT_EQ(0u, result.size()); +} + +// Tests myIpAddress() when there is only an IPv6 link-local address. +TEST(PacLibraryTest, PacMyIpAddressAllFailHasLinkLocal) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = + CreateAddressList({"127.0.0.1", "::1", "fe81::8881"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("fe81::8881", result.front().ToString()); +} + +// Tests myIpAddress() when there are only link-local addresses. The IPv4 +// link-local address is favored. +TEST(PacLibraryTest, PacMyIpAddressAllFailHasLinkLocalFavorIPv4) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = + CreateAddressList({"127.0.0.1", "::1", "fe81::8881", "169.254.89.133"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("169.254.89.133", result.front().ToString()); +} + +// Tests myIpAddressEx() when there is a route to 8.8.8.8 but not one to +// 2001:4860:4860::8888 +TEST(PacLibraryTest, PacMyIpAddressEx8888) { + MockSocketFactory factory; + factory.AddUDPConnectSuccess("8.8.8.8", "192.168.1.1"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + auto result = PacMyIpAddressExForTest(&factory, {}); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("192.168.1.1", result.front().ToString()); +} + +// Tests myIpAddressEx() when there is a route to 2001:4860:4860::8888 but +// not 8.8.8.8. +TEST(PacLibraryTest, PacMyIpAddressEx2001) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectSuccess("2001:4860:4860::8888", "2001::3333"); + + AddressList dns_result; + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("2001::3333", result.front().ToString()); +} + +// Tests myIpAddressEx() when there is a route to both 8.8.8.8 and +// 2001:4860:4860::8888. +TEST(PacLibraryTest, PacMyIpAddressEx8888And2001) { + MockSocketFactory factory; + factory.AddUDPConnectSuccess("8.8.8.8", "192.168.17.8"); + factory.AddUDPConnectSuccess("2001:4860:4860::8888", "2001::8333"); + + AddressList dns_result; + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + ASSERT_EQ(2u, result.size()); + EXPECT_EQ("192.168.17.8", result.front().ToString()); + EXPECT_EQ("2001::8333", result.back().ToString()); +} + +// Tests myIpAddressEx() when there is no route to 8.8.8.8, no route to +// 2001:4860:4860::8888, however getaddrinfo(gethostname()) finds results. Some +// of those results are skipped due to being link-local and loopback. +TEST(PacLibraryTest, PacMyIpAddressExHostname) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = CreateAddressList({ + "169.254.13.16", "::1", "fe89::beef", "2001::bebe", "178.1.99.3", + "127.0.0.1", "192.168.1.3", + }); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + ASSERT_EQ(3u, result.size()); + EXPECT_EQ("2001::bebe", result[0].ToString()); + EXPECT_EQ("178.1.99.3", result[1].ToString()); + EXPECT_EQ("192.168.1.3", result[2].ToString()); +} + +// Tests myIpAddressEx() when routes are found for private IP space. +TEST(PacLibraryTest, PacMyIpAddressExPrivateDuplicates) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result; + + factory.AddUDPConnectSuccess("10.0.0.0", "192.168.3.3"); + factory.AddUDPConnectSuccess("172.16.0.0", "192.168.3.4"); + factory.AddUDPConnectSuccess("192.168.0.0", "192.168.3.3"); + factory.AddUDPConnectSuccess("FC00::", "2001::beef"); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + + // Note that 192.168.3.3. was probed twice, but only added once to the final + // result. + ASSERT_EQ(3u, result.size()); + EXPECT_EQ("192.168.3.3", result[0].ToString()); + EXPECT_EQ("192.168.3.4", result[1].ToString()); + EXPECT_EQ("2001::beef", result[2].ToString()); +} + +// Tests myIpAddressEx() when there are no routes, and +// getaddrinfo(gethostname()) fails. +TEST(PacLibraryTest, PacMyIpAddressExAllFail) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result; + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + EXPECT_EQ(0u, result.size()); +} + +// Tests myIpAddressEx() when there are only IPv6 link-local address. +TEST(PacLibraryTest, PacMyIpAddressExAllFailHasLinkLocal) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = + CreateAddressList({"127.0.0.1", "::1", "fe81::8881", "fe80::8899"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectSuccess("FC00::", "fe80::1"); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + // There were four link-local addresses found, but only the first one is + // returned. + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("fe81::8881", result.front().ToString()); +} + +// Tests myIpAddressEx() when there are only link-local addresses. The IPv4 +// link-local address is favored. +TEST(PacLibraryTest, PacMyIpAddressExAllFailHasLinkLocalFavorIPv4) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = + CreateAddressList({"127.0.0.1", "::1", "fe81::8881", "169.254.89.133"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("169.254.89.133", result.front().ToString()); +} + +// Tests myIpAddressEx() when there are no routes, and +// getaddrinfo(gethostname()) only returns loopback. +TEST(PacLibraryTest, PacMyIpAddressExAllFailOrLoopback) { + MockSocketFactory factory; + factory.AddUDPConnectFailure("8.8.8.8"); + factory.AddUDPConnectFailure("2001:4860:4860::8888"); + + AddressList dns_result = CreateAddressList({"127.0.0.1", "::1"}); + + factory.AddUDPConnectFailure("10.0.0.0"); + factory.AddUDPConnectFailure("172.16.0.0"); + factory.AddUDPConnectFailure("192.168.0.0"); + factory.AddUDPConnectFailure("FC00::"); + + auto result = PacMyIpAddressExForTest(&factory, dns_result); + EXPECT_EQ(0u, result.size()); +} + +} // namespace +} // namespace net diff --git a/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc b/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc index 100e818b702..d6821f38eee 100644 --- a/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc +++ b/chromium/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc @@ -18,7 +18,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { net::ProxyBypassRules rules; std::string input(data, data + size); - rules.ParseFromString(input); - rules.ParseFromStringUsingSuffixMatching(input); + + const net::ProxyBypassRules::ParseFormat kFormats[] = { + net::ProxyBypassRules::ParseFormat::kDefault, + net::ProxyBypassRules::ParseFormat::kHostnameSuffixMatching, + }; + + for (auto format : kFormats) + rules.ParseFromString(input, format); + return 0; } diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules.cc b/chromium/net/proxy_resolution/proxy_bypass_rules.cc index 2462f9aa058..a26f352aa91 100644 --- a/chromium/net/proxy_resolution/proxy_bypass_rules.cc +++ b/chromium/net/proxy_resolution/proxy_bypass_rules.cc @@ -9,6 +9,7 @@ #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" +#include "build/build_config.h" #include "net/base/host_port_pair.h" #include "net/base/ip_address.h" #include "net/base/parse_number.h" @@ -18,16 +19,23 @@ namespace net { namespace { -bool IsIPv4LinkLocal(const IPAddress& addr) { - // 169.254.0.0/16 - return addr.IsIPv4() && (addr.bytes()[0] == 169) && (addr.bytes()[1] == 254); -} - -bool IsIPv6LinkLocal(const IPAddress& addr) { - // [fe80::]/10 - return addr.IsIPv6() && (addr.bytes()[0] == 0xFE) && - ((addr.bytes()[1] & 0xC0) == 0x80); -} +// The <-loopback> rule corresponds with "remove the implicitly added bypass +// rules". +// +// The name <-loopback> is not a very precise name (as the implicit rules cover +// more than strictly loopback addresses), however this is the name that is +// used on Windows so re-used here. +// +// For platform-differences between implicit rules see +// ProxyResolverRules::MatchesImplicitRules(). +const char kSubtractImplicitBypasses[] = "<-loopback>"; + +// The <local> rule bypasses any hostname that has no dots (and is not +// an IP literal). The name is misleading as it has nothing to do with +// localhost/loopback addresses, and would have better been called +// something like "simple hostnames". However this is the name used on +// Windows so is matched here. +const char kBypassSimpleHostnames[] = "<local>"; bool IsLinkLocalIP(const GURL& url) { // Quick fail if definitely not link-local, to avoid doing unnecessary work in @@ -38,12 +46,11 @@ bool IsLinkLocalIP(const GURL& url) { return false; } - base::StringPiece host(url.host()); IPAddress ip_address; if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece())) return false; - return IsIPv4LinkLocal(ip_address) || IsIPv6LinkLocal(ip_address); + return ip_address.IsLinkLocal(); } class HostnamePatternRule : public ProxyBypassRules::Rule { @@ -55,16 +62,17 @@ class HostnamePatternRule : public ProxyBypassRules::Rule { hostname_pattern_(base::ToLowerASCII(hostname_pattern)), optional_port_(optional_port) {} - bool Matches(const GURL& url) const override { + Result Evaluate(const GURL& url) const override { if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) - return false; // Didn't match port expectation. + return Result::kNoMatch; // Didn't match port expectation. if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) - return false; // Didn't match scheme expectation. + return Result::kNoMatch; // Didn't match scheme expectation. // Note it is necessary to lower-case the host, since GURL uses capital // letters for percent-escaped characters. - return base::MatchPattern(url.host(), hostname_pattern_); + return base::MatchPattern(url.host(), hostname_pattern_) ? Result::kBypass + : Result::kNoMatch; } std::string ToString() const override { @@ -77,77 +85,89 @@ class HostnamePatternRule : public ProxyBypassRules::Rule { return str; } - std::unique_ptr<Rule> Clone() const override { - return std::make_unique<HostnamePatternRule>( - optional_scheme_, hostname_pattern_, optional_port_); - } - private: const std::string optional_scheme_; const std::string hostname_pattern_; const int optional_port_; + + DISALLOW_COPY_AND_ASSIGN(HostnamePatternRule); }; -class BypassLocalRule : public ProxyBypassRules::Rule { +class BypassSimpleHostnamesRule : public ProxyBypassRules::Rule { public: - bool Matches(const GURL& url) const override { - const std::string& host = url.host(); - if (host == "127.0.0.1" || host == "[::1]") - return true; - return host.find('.') == std::string::npos; + BypassSimpleHostnamesRule() = default; + + Result Evaluate(const GURL& url) const override { + return ((url.host_piece().find('.') == std::string::npos) && + !url.HostIsIPAddress()) + ? Result::kBypass + : Result::kNoMatch; } - std::string ToString() const override { return "<local>"; } + std::string ToString() const override { return kBypassSimpleHostnames; } - std::unique_ptr<Rule> Clone() const override { - return std::make_unique<BypassLocalRule>(); + private: + DISALLOW_COPY_AND_ASSIGN(BypassSimpleHostnamesRule); +}; + +class SubtractImplicitBypassesRule : public ProxyBypassRules::Rule { + public: + SubtractImplicitBypassesRule() = default; + + Result Evaluate(const GURL& url) const override { + return ProxyBypassRules::MatchesImplicitRules(url) ? Result::kDontBypass + : Result::kNoMatch; } + + std::string ToString() const override { return kSubtractImplicitBypasses; } + + private: + DISALLOW_COPY_AND_ASSIGN(SubtractImplicitBypassesRule); }; // Rule for matching a URL that is an IP address, if that IP address falls // within a certain numeric range. For example, you could use this rule to // match all the IPs in the CIDR block 10.10.3.4/24. -class BypassIPBlockRule : public ProxyBypassRules::Rule { +class IPBlockRule : public ProxyBypassRules::Rule { public: // |ip_prefix| + |prefix_length| define the IP block to match. - BypassIPBlockRule(const std::string& description, - const std::string& optional_scheme, - const IPAddress& ip_prefix, - size_t prefix_length_in_bits) + IPBlockRule(const std::string& description, + const std::string& optional_scheme, + const IPAddress& ip_prefix, + size_t prefix_length_in_bits) : description_(description), optional_scheme_(optional_scheme), ip_prefix_(ip_prefix), prefix_length_in_bits_(prefix_length_in_bits) {} - bool Matches(const GURL& url) const override { + Result Evaluate(const GURL& url) const override { if (!url.HostIsIPAddress()) - return false; + return Result::kNoMatch; if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) - return false; // Didn't match scheme expectation. + return Result::kNoMatch; // Didn't match scheme expectation. // Parse the input IP literal to a number. IPAddress ip_address; if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece())) - return false; + return Result::kNoMatch; // Test if it has the expected prefix. return IPAddressMatchesPrefix(ip_address, ip_prefix_, - prefix_length_in_bits_); + prefix_length_in_bits_) + ? Result::kBypass + : Result::kNoMatch; } std::string ToString() const override { return description_; } - std::unique_ptr<Rule> Clone() const override { - return std::make_unique<BypassIPBlockRule>( - description_, optional_scheme_, ip_prefix_, prefix_length_in_bits_); - } - private: const std::string description_; const std::string optional_scheme_; const IPAddress ip_prefix_; const size_t prefix_length_in_bits_; + + DISALLOW_COPY_AND_ASSIGN(IPBlockRule); }; // Returns true if the given string represents an IP address. @@ -162,6 +182,92 @@ bool IsIPAddress(const std::string& domain) { return host_info.IsIPAddress(); } +std::unique_ptr<ProxyBypassRules::Rule> ParseRule( + const std::string& raw_untrimmed, + ProxyBypassRules::ParseFormat format) { + std::string raw; + base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw); + + // <local> and <-loopback> are special syntax used by WinInet's bypass list + // -- we allow it on all platforms and interpret it the same way. + if (base::LowerCaseEqualsASCII(raw, kBypassSimpleHostnames)) + return std::make_unique<BypassSimpleHostnamesRule>(); + if (base::LowerCaseEqualsASCII(raw, kSubtractImplicitBypasses)) + return std::make_unique<SubtractImplicitBypassesRule>(); + + // Extract any scheme-restriction. + std::string::size_type scheme_pos = raw.find("://"); + std::string scheme; + if (scheme_pos != std::string::npos) { + scheme = raw.substr(0, scheme_pos); + raw = raw.substr(scheme_pos + 3); + if (scheme.empty()) + return nullptr; + } + + if (raw.empty()) + return nullptr; + + // If there is a forward slash in the input, it is probably a CIDR style + // mask. + if (raw.find('/') != std::string::npos) { + IPAddress ip_prefix; + size_t prefix_length_in_bits; + + if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits)) + return nullptr; + + return std::make_unique<IPBlockRule>(raw, scheme, ip_prefix, + prefix_length_in_bits); + } + + // Check if we have an <ip-address>[:port] input. We need to treat this + // separately since the IP literal may not be in a canonical form. + std::string host; + int port; + if (ParseHostAndPort(raw, &host, &port)) { + // TODO(eroman): HostForURL() below DCHECKs() when |host| contains an + // embedded NULL. + if (host.find('\0') != std::string::npos) + return nullptr; + + // Note that HostPortPair is used to merely to convert any IPv6 literals to + // a URL-safe format that can be used by canonicalization below. + std::string bracketed_host = HostPortPair(host, 80).HostForURL(); + if (IsIPAddress(bracketed_host)) { + // Canonicalize the IP literal before adding it as a string pattern. + GURL tmp_url("http://" + bracketed_host); + return std::make_unique<HostnamePatternRule>(scheme, tmp_url.host(), + port); + } + } + + // Otherwise assume we have <hostname-pattern>[:port]. + std::string::size_type pos_colon = raw.rfind(':'); + port = -1; + if (pos_colon != std::string::npos) { + if (!ParseInt32(base::StringPiece(raw.begin() + pos_colon + 1, raw.end()), + ParseIntFormat::NON_NEGATIVE, &port) || + port > 0xFFFF) { + return nullptr; // Port was invalid. + } + raw = raw.substr(0, pos_colon); + } + + // Special-case hostnames that begin with a period. + // For example, we remap ".google.com" --> "*.google.com". + if (base::StartsWith(raw, ".", base::CompareCase::SENSITIVE)) + raw = "*" + raw; + + // If suffix matching was asked for, make sure the pattern starts with a + // wildcard. + if (format == ProxyBypassRules::ParseFormat::kHostnameSuffixMatching && + !base::StartsWith(raw, "*", base::CompareCase::SENSITIVE)) + raw = "*" + raw; + + return std::make_unique<HostnamePatternRule>(scheme, raw, port); +} + } // namespace ProxyBypassRules::Rule::Rule() = default; @@ -175,27 +281,67 @@ bool ProxyBypassRules::Rule::Equals(const Rule& rule) const { ProxyBypassRules::ProxyBypassRules() = default; ProxyBypassRules::ProxyBypassRules(const ProxyBypassRules& rhs) { - AssignFrom(rhs); + *this = rhs; } -ProxyBypassRules::~ProxyBypassRules() { - Clear(); +ProxyBypassRules::ProxyBypassRules(ProxyBypassRules&& rhs) { + *this = std::move(rhs); } +ProxyBypassRules::~ProxyBypassRules() = default; + ProxyBypassRules& ProxyBypassRules::operator=(const ProxyBypassRules& rhs) { - AssignFrom(rhs); + ParseFromString(rhs.ToString()); return *this; } -bool ProxyBypassRules::Matches(const GURL& url) const { - for (auto it = rules_.begin(); it != rules_.end(); ++it) { - if ((*it)->Matches(url)) - return true; +ProxyBypassRules& ProxyBypassRules::operator=(ProxyBypassRules&& rhs) { + rules_ = std::move(rhs.rules_); + return *this; +} + +bool ProxyBypassRules::Matches(const GURL& url, bool reverse) const { + // Later rules override earlier rules, so evaluating the rule list can be + // done by iterating over it in reverse and short-circuiting when a match is + // found. If no matches are found then the implicit rules are consulted. + // + // The order of evaluation generally doesn't matter, since the common + // case is to have a set of (positive) bypass rules. + // + // However when mixing positive and negative bypass rules evaluation + // order makes a difference. The chosen evaluation order here matches + // WinInet (which supports <-loopback> as a negative rule). + // + // Consider these two rule lists: + // (a) "localhost; <-loopback>" + // (b) "<-loopback>; localhost" + // + // The expectation is that Matches("http://localhost/") returns false + // for (a) since the final rule <-loopback> unbypasses it. Whereas it is + // expected to return true for (b), since the final rule "localhost" + // bypasses it again. + for (auto it = rules_.rbegin(); it != rules_.rend(); ++it) { + const std::unique_ptr<Rule>& rule = *it; + + switch (rule->Evaluate(url)) { + case Rule::Result::kBypass: + return !reverse; + case Rule::Result::kDontBypass: + return reverse; + case Rule::Result::kNoMatch: + continue; + } } - return false; + + // If none of the explicit rules matched, fall back to the implicit rules. + bool matches_implicit = MatchesImplicitRules(url); + if (matches_implicit) + return matches_implicit; + + return reverse; } -bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const { +bool ProxyBypassRules::operator==(const ProxyBypassRules& other) const { if (rules_.size() != other.rules_.size()) return false; @@ -206,13 +352,14 @@ bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const { return true; } -void ProxyBypassRules::ParseFromString(const std::string& raw) { - ParseFromStringInternal(raw, false); -} +void ProxyBypassRules::ParseFromString(const std::string& raw, + ParseFormat format) { + Clear(); -void ProxyBypassRules::ParseFromStringUsingSuffixMatching( - const std::string& raw) { - ParseFromStringInternal(raw, true); + base::StringTokenizer entries(raw, ",;"); + while (entries.GetNext()) { + AddRuleFromString(entries.token(), format); + } } bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme, @@ -226,17 +373,30 @@ bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme, return true; } -void ProxyBypassRules::AddRuleToBypassLocal() { - rules_.push_back(std::make_unique<BypassLocalRule>()); +void ProxyBypassRules::PrependRuleToBypassSimpleHostnames() { + rules_.insert(rules_.begin(), std::make_unique<BypassSimpleHostnamesRule>()); } -bool ProxyBypassRules::AddRuleFromString(const std::string& raw) { - return AddRuleFromStringInternalWithLogging(raw, false); +bool ProxyBypassRules::AddRuleFromString(const std::string& raw_untrimmed, + ParseFormat format) { + auto rule = ParseRule(raw_untrimmed, format); + + if (rule) { + rules_.push_back(std::move(rule)); + return true; + } + + return false; +} + +void ProxyBypassRules::AddRulesToSubtractImplicit() { + rules_.push_back(std::make_unique<SubtractImplicitBypassesRule>()); } -bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching( - const std::string& raw) { - return AddRuleFromStringInternalWithLogging(raw, true); +std::string ProxyBypassRules::GetRulesToSubtractImplicit() { + ProxyBypassRules rules; + rules.AddRulesToSubtractImplicit(); + return rules.ToString(); } std::string ProxyBypassRules::ToString() const { @@ -252,15 +412,6 @@ void ProxyBypassRules::Clear() { rules_.clear(); } -void ProxyBypassRules::AssignFrom(const ProxyBypassRules& other) { - Clear(); - - // Make a copy of the rules list. - for (auto it = other.rules_.begin(); it != other.rules_.end(); ++it) { - rules_.push_back((*it)->Clone()); - } -} - bool ProxyBypassRules::MatchesImplicitRules(const GURL& url) { // On Windows the implict rules are: // @@ -285,118 +436,20 @@ bool ProxyBypassRules::MatchesImplicitRules(const GURL& url) { // *.localhost // localhost6 // localhost6.localdomain6 + // loopback [Windows only] + // loopback. [Windows only] // [::1] // 127.0.0.1/8 // 169.254/16 // [FE80::]/10 - // - // TODO(eroman): Does "loopback" need special treatment on Windows? - return net::IsLocalhost(url) || IsLinkLocalIP(url); -} - -void ProxyBypassRules::ParseFromStringInternal( - const std::string& raw, - bool use_hostname_suffix_matching) { - Clear(); - - base::StringTokenizer entries(raw, ",;"); - while (entries.GetNext()) { - AddRuleFromStringInternalWithLogging(entries.token(), - use_hostname_suffix_matching); - } -} - -bool ProxyBypassRules::AddRuleFromStringInternal( - const std::string& raw_untrimmed, - bool use_hostname_suffix_matching) { - std::string raw; - base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw); - - // This is the special syntax used by WinInet's bypass list -- we allow it - // on all platforms and interpret it the same way. - if (base::LowerCaseEqualsASCII(raw, "<local>")) { - AddRuleToBypassLocal(); - return true; - } - - // Extract any scheme-restriction. - std::string::size_type scheme_pos = raw.find("://"); - std::string scheme; - if (scheme_pos != std::string::npos) { - scheme = raw.substr(0, scheme_pos); - raw = raw.substr(scheme_pos + 3); - if (scheme.empty()) - return false; - } - - if (raw.empty()) - return false; - - // If there is a forward slash in the input, it is probably a CIDR style - // mask. - if (raw.find('/') != std::string::npos) { - IPAddress ip_prefix; - size_t prefix_length_in_bits; - - if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits)) - return false; - - rules_.push_back(std::make_unique<BypassIPBlockRule>( - raw, scheme, ip_prefix, prefix_length_in_bits)); - - return true; - } - - // Check if we have an <ip-address>[:port] input. We need to treat this - // separately since the IP literal may not be in a canonical form. - std::string host; - int port; - if (ParseHostAndPort(raw, &host, &port)) { - // TODO(eroman): HostForURL() below DCHECKs() when |host| contains an - // embedded NULL. - if (host.find('\0') != std::string::npos) - return false; - - // Note that HostPortPair is used to merely to convert any IPv6 literals to - // a URL-safe format that can be used by canonicalization below. - std::string bracketed_host = HostPortPair(host, 80).HostForURL(); - if (IsIPAddress(bracketed_host)) { - // Canonicalize the IP literal before adding it as a string pattern. - GURL tmp_url("http://" + bracketed_host); - return AddRuleForHostname(scheme, tmp_url.host(), port); - } - } - - // Otherwise assume we have <hostname-pattern>[:port]. - std::string::size_type pos_colon = raw.rfind(':'); - port = -1; - if (pos_colon != std::string::npos) { - if (!ParseInt32(base::StringPiece(raw.begin() + pos_colon + 1, raw.end()), - ParseIntFormat::NON_NEGATIVE, &port) || - port > 0xFFFF) { - return false; // Port was invalid. - } - raw = raw.substr(0, pos_colon); - } - - // Special-case hostnames that begin with a period. - // For example, we remap ".google.com" --> "*.google.com". - if (base::StartsWith(raw, ".", base::CompareCase::SENSITIVE)) - raw = "*" + raw; - - // If suffix matching was asked for, make sure the pattern starts with a - // wildcard. - if (use_hostname_suffix_matching && - !base::StartsWith(raw, "*", base::CompareCase::SENSITIVE)) - raw = "*" + raw; - - return AddRuleForHostname(scheme, raw, port); -} - -bool ProxyBypassRules::AddRuleFromStringInternalWithLogging( - const std::string& raw, - bool use_hostname_suffix_matching) { - return AddRuleFromStringInternal(raw, use_hostname_suffix_matching); + return net::IsLocalhost(url) || + IsLinkLocalIP(url) +#if defined(OS_WIN) + // See http://crbug.com/904889 + || (url.host_piece() == "loopback") || + (url.host_piece() == "loopback.") +#endif + ; } } // namespace net diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules.h b/chromium/net/proxy_resolution/proxy_bypass_rules.h index 41ef6df3867..36ad6a2e19e 100644 --- a/chromium/net/proxy_resolution/proxy_bypass_rules.h +++ b/chromium/net/proxy_resolution/proxy_bypass_rules.h @@ -15,65 +15,101 @@ namespace net { -// ProxyBypassRules describes the set of URLs that should bypass the proxy -// settings, as a list of rules. A URL is said to match the bypass rules -// if it matches any one of these rules. +// ProxyBypassRules describes the set of URLs that should bypass the use of a +// proxy. +// +// The rules are expressed as an ordered list of rules, which can be thought of +// as being evaluated left-to-right. Order only matters when mixing "negative +// rules" with "positive rules". For more details see the comments in +// ProxyBypassRules::Matches(). +// +// This rule list is serializable to a string (either comma or semi-colon +// separated), which has similar semantics across platforms. +// +// When evalutating ProxyBypassRules there are some implicitly applied rules +// when the URL does not match any of the explicit rules. See +// MatchesImplicitRules() for details. class NET_EXPORT ProxyBypassRules { public: // Interface for an individual proxy bypass rule. class NET_EXPORT Rule { public: + // Describes the result of calling Rule::Evaluate() for a particular URL. + enum class Result { + // The URL does not match this rule. + kNoMatch, + + // The URL matches this rule, and should bypass the proxy. + kBypass, + + // The URL matches this rule, and should NOT bypass the proxy. + kDontBypass, + }; + Rule(); virtual ~Rule(); - // Returns true if |url| matches the rule. - virtual bool Matches(const GURL& url) const = 0; + // Evaluates the rule against |url|. + virtual Result Evaluate(const GURL& url) const = 0; - // Returns a string representation of this rule. This is used both for - // visualizing the rules, and also to test equality of a rules list. + // Returns a string representation of this rule (using + // ParseFormat::kDefault). virtual std::string ToString() const = 0; - // Creates a copy of this rule. - virtual std::unique_ptr<Rule> Clone() const = 0; - bool Equals(const Rule& rule) const; private: DISALLOW_COPY_AND_ASSIGN(Rule); }; + // The input format to use when parsing proxy bypass rules. This format + // only applies when parsing, since once parsed any serialization will be in + // terms of ParseFormat::kDefault. + enum class ParseFormat { + kDefault, + + // Variation of kDefault that interprets hostname patterns as being suffix + // tests rather than hostname tests. For example, "google.com" would be + // interpreted as "*google.com" when parsed with this format, and + // match "foogoogle.com". + // + // Only use this format if needed for compatibility when parsing Linux + // bypass strings. + kHostnameSuffixMatching, + }; + typedef std::vector<std::unique_ptr<Rule>> RuleList; // Note: This class supports copy constructor and assignment. ProxyBypassRules(); ProxyBypassRules(const ProxyBypassRules& rhs); + ProxyBypassRules(ProxyBypassRules&& rhs); ~ProxyBypassRules(); ProxyBypassRules& operator=(const ProxyBypassRules& rhs); + ProxyBypassRules& operator=(ProxyBypassRules&& rhs); // Returns the current list of rules. The rules list contains pointers // which are owned by this class, callers should NOT keep references // or delete them. const RuleList& rules() const { return rules_; } - // Returns true if |url| matches any of the proxy bypass rules. - bool Matches(const GURL& url) const; + // Returns true if the bypass rules indicate that |url| should bypass the + // proxy. Matching is done using both the explicit rules, as well as a + // set of global implicit rules. + // + // If |reverse| is set to true then the bypass + // rule list is inverted (this is almost equivalent to negating the result of + // Matches(), except for implicit matches). + bool Matches(const GURL& url, bool reverse = false) const; - // Returns true if |*this| is equal to |other|; in other words, whether they - // describe the same set of rules. - bool Equals(const ProxyBypassRules& other) const; + // Returns true if |*this| has the same serialized list of rules as |other|. + bool operator==(const ProxyBypassRules& other) const; // Initializes the list of rules by parsing the string |raw|. |raw| is a - // comma separated list of rules. See AddRuleFromString() to see the list - // of supported formats. - void ParseFromString(const std::string& raw); - - // This is a variant of ParseFromString, which interprets hostname patterns - // as suffix tests rather than hostname tests (so "google.com" would actually - // match "*google.com"). This is only currently used for the linux no_proxy - // environment variable. It is less flexible, since with the suffix matching - // format you can't match an individual host. - // NOTE: Use ParseFromString() unless you truly need this behavior. - void ParseFromStringUsingSuffixMatching(const std::string& raw); + // comma separated or semi-colon separated list of rules. See + // AddRuleFromString() to see the specific rule grammar. + void ParseFromString(const std::string& raw, + ParseFormat format = ParseFormat::kDefault); // Adds a rule that matches a URL when all of the following are true: // (a) The URL's scheme matches |optional_scheme|, if @@ -86,12 +122,14 @@ class NET_EXPORT ProxyBypassRules { const std::string& hostname_pattern, int optional_port); - // Adds a rule that bypasses all "local" hostnames. - // This matches IE's interpretation of the - // "Bypass proxy server for local addresses" settings checkbox. Fully - // qualified domain names or IP addresses are considered non-local, - // regardless of what they map to (except for the loopback addresses). - void AddRuleToBypassLocal(); + // Adds a rule to the front of thelist that bypasses hostnames without a dot + // in them (and is not an IP literal), which can be indicative of intranet + // websites. + // + // On Windows this corresponds to the "Bypass proxy server for local + // addresses" settings checkbox, and on macOS the "Exclude simple hostnames" + // checkbox. + void PrependRuleToBypassSimpleHostnames(); // Adds a rule given by the string |raw|. The format of |raw| can be any of // the following: @@ -123,60 +161,72 @@ class NET_EXPORT ProxyBypassRules { // Examples: // "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99" // - // (4) IP_LITERAL "/" PREFIX_LENGTH_IN_BITS + // (4) IPV4_LITERAL "/" PREFIX_LENGTH_IN_BITS // - // Match any URL that is to an IP literal that falls between the - // given range. IP range is specified using CIDR notation. + // Match any URL that is an IPv4 literal that falls between the + // given range. // // Examples: - // "192.168.1.1/16", "fefe:13::abc/33". + // "192.168.1.1/16" + // + // (5) IPV6_LITERAL "/" PREFIX_LENGTH_IN_BITS // - // (5) "<local>" + // Match any URL that is an IPv6 literal that falls between the given + // range. // - // Match local addresses. The meaning of "<local>" is whether the - // host matches one of: "127.0.0.1", "::1", "localhost". + // Note that IPV6_LITERAL must *not* be bracketed. "[fefe::/40]" for + // instance is not valid, but "fefe::/40" is. This notation comes from + // macOS's proxy bypass rules which supports IPv6 (Windows bypass rules do + // not). + // + // Examples: + // "fefe:13::abc/33". + // + // (6) "<local>" + // + // Matches hostnames without a period in them (and are not IP + // literals). + // + // This is equivalent to the same named bypass rule on Windows. + // + // (7) "<-loopback>" + // + // Subtracts the implicit proxy bypass rules (localhost and link local + // addresses), so they are no longer bypassed. + // + // This is equivalent to the same named bypass rule on Windows. // // See the unit-tests for more examples. // // Returns true if the rule was successfully added. - bool AddRuleFromString(const std::string& raw); + bool AddRuleFromString(const std::string& raw, + ParseFormat format = ParseFormat::kDefault); + + // Appends rules that "cancels out" the implicit bypass rules. See + // GetRulesToSubtractImplicit() for details. + void AddRulesToSubtractImplicit(); - // This is a variant of AddFromString, which interprets hostname patterns as - // suffix tests rather than hostname tests (so "google.com" would actually - // match "*google.com"). This is used for KDE which interprets every rule as - // a suffix test. It is less flexible, since with the suffix matching format - // you can't match an individual host. + // Returns a list of bypass rules that "cancels out" the implicit bypass + // rules. // - // Returns true if the rule was successfully added. + // The current set of implicit bypass rules are localhost and link-local + // addresses, and are subtracted using <-loopback> (an idiom from Windows), + // however this could change. // - // NOTE: Use AddRuleFromString() unless you truly need this behavior. - bool AddRuleFromStringUsingSuffixMatching(const std::string& raw); + // If using this for tests, see https://crbug.com/901896. + static std::string GetRulesToSubtractImplicit(); - // Converts the rules to string representation. Inverse operation to - // ParseFromString(). + // Converts the rules to a string representation (ParseFormat::kDefault). std::string ToString() const; // Removes all the rules. void Clear(); - // Sets |*this| to |other|. - void AssignFrom(const ProxyBypassRules& other); - // Returns true if |url| matches one of the implicit proxy bypass rules // (localhost or link local). static bool MatchesImplicitRules(const GURL& url); private: - // The following are variants of ParseFromString() and AddRuleFromString(), - // which additionally prefix hostname patterns with a wildcard if - // |use_hostname_suffix_matching| was true. - void ParseFromStringInternal(const std::string& raw, - bool use_hostname_suffix_matching); - bool AddRuleFromStringInternal(const std::string& raw, - bool use_hostname_suffix_matching); - bool AddRuleFromStringInternalWithLogging(const std::string& raw, - bool use_hostname_suffix_matching); - RuleList rules_; }; diff --git a/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc b/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc index 6cd111fd576..55e2877b5d6 100644 --- a/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc +++ b/chromium/net/proxy_resolution/proxy_bypass_rules_unittest.cc @@ -4,36 +4,110 @@ #include "net/proxy_resolution/proxy_bypass_rules.h" +#include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" +#include "build/build_config.h" #include "net/proxy_resolution/proxy_config_service_common_unittest.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_WIN) +// On Windows, "loopback" resolves to localhost and is implicitly bypassed to +// match WinInet. +#define BYPASS_LOOPBACK +#endif + namespace net { namespace { -// Calls |MatchesImplicitRules()| for each name in |hosts| (for various URL -// schemes), and checks that the result is |bypasses|. -template <size_t N> -void ExpectMatchesImplicitRules(const char* (&hosts)[N], bool bypasses) { +// Calls |rules.Matches()| for each name in |hosts| (for various URL schemes), +// and checks that the result is |bypasses|. If the host is in |inverted_hosts| +// then the expectation is reversed. +void ExpectRulesMatch(const ProxyBypassRules& rules, + const char* hosts[], + size_t num_hosts, + bool bypasses, + const std::set<std::string>& inverted_hosts) { // The scheme of the URL shouldn't matter. const char* kUrlSchemes[] = {"http://", "https://", "ftp://"}; for (auto* scheme : kUrlSchemes) { - for (size_t i = 0; i < N; ++i) { + for (size_t i = 0; i < num_hosts; ++i) { const char* host = hosts[i]; + + bool expectation = bypasses; + + if (inverted_hosts.count(std::string(host)) != 0) + expectation = !expectation; + std::string url = std::string(scheme) + std::string(host); - EXPECT_EQ(bypasses, ProxyBypassRules::MatchesImplicitRules(GURL(url))) - << url; + + EXPECT_EQ(expectation, rules.Matches(GURL(url))) << url; } } } +// Tests calling |rules.Matches()| for localhost URLs returns |bypasses|. +void ExpectBypassLocalhost( + const ProxyBypassRules& rules, + bool bypasses, + const std::set<std::string>& inverted_hosts = std::set<std::string>()) { + const char* kHosts[] = { + "localhost", + "localhost.", + "foo.localhost", + "localhost6", + "localhost6.localdomain6", + "127.0.0.1", + "127.100.0.2", + "[::1]", +#if defined(BYPASS_LOOPBACK) + "loopback", + "loopback.", +#endif + }; + + ExpectRulesMatch(rules, kHosts, base::size(kHosts), bypasses, inverted_hosts); +} + +// Tests calling |rules.Matches()| for link-local URLs returns |bypasses|. +void ExpectBypassLinkLocal(const ProxyBypassRules& rules, bool bypasses) { + const char* kHosts[] = { + "169.254.3.2", "169.254.100.1", "[FE80::8]", "[fe91::1]", + }; + + ExpectRulesMatch(rules, kHosts, base::size(kHosts), bypasses, {}); +} + +// Tests calling |rules.Matches()| with miscelaneous URLs that are neither +// localhost or link local IPs, returns |bypasses|. +void ExpectBypassMisc( + const ProxyBypassRules& rules, + bool bypasses, + const std::set<std::string>& inverted_hosts = std::set<std::string>()) { + const char* kHosts[] = { + "192.168.0.1", + "170.254.0.0", + "128.0.0.1", + "[::2]", + "[FD80::1]", + "foo", + "www.example3.com", +#if !defined(BYPASS_LOOPBACK) + "loopback", + "loopback.", +#endif + }; + + ExpectRulesMatch(rules, kHosts, base::size(kHosts), bypasses, inverted_hosts); +} + TEST(ProxyBypassRulesTest, ParseAndMatchBasicHost) { ProxyBypassRules rules; rules.ParseFromString("wWw.gOogle.com"); ASSERT_EQ(1u, rules.rules().size()); + // Hostname rules are normalized to lower-case. EXPECT_EQ("www.google.com", rules.rules()[0]->ToString()); // All of these match; port, scheme, and non-hostname components don't @@ -53,6 +127,7 @@ TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomain) { ProxyBypassRules rules; rules.ParseFromString(".gOOgle.com"); ASSERT_EQ(1u, rules.rules().size()); + // Hostname rules are normalized to lower-case. // Note that we inferred this was an "ends with" test. EXPECT_EQ("*.google.com", rules.rules()[0]->ToString()); @@ -73,6 +148,7 @@ TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomainWithPort) { ProxyBypassRules rules; rules.ParseFromString("*.GOOGLE.com:80"); ASSERT_EQ(1u, rules.rules().size()); + // Hostname rules are normalized to lower-case. EXPECT_EQ("*.google.com:80", rules.rules()[0]->ToString()); // All of these match; scheme, and non-hostname components don't matter. @@ -209,9 +285,10 @@ TEST(ProxyBypassRulesTest, HTTPOnlyWithWildcard) { TEST(ProxyBypassRulesTest, UseSuffixMatching) { ProxyBypassRules rules; - rules.ParseFromStringUsingSuffixMatching( + rules.ParseFromString( "foo1.com, .foo2.com, 192.168.1.1, " - "*foobar.com:80, *.foo, http://baz, <local>"); + "*foobar.com:80, *.foo, http://baz, <local>", + ProxyBypassRules::ParseFormat::kHostnameSuffixMatching); ASSERT_EQ(7u, rules.rules().size()); EXPECT_EQ("*foo1.com", rules.rules()[0]->ToString()); EXPECT_EQ("*.foo2.com", rules.rules()[1]->ToString()); @@ -254,58 +331,43 @@ TEST(ProxyBypassRulesTest, Equals) { rules1.ParseFromString("foo1.com, .foo2.com"); rules2.ParseFromString("foo1.com,.FOo2.com"); - EXPECT_TRUE(rules1.Equals(rules2)); - EXPECT_TRUE(rules2.Equals(rules1)); + EXPECT_EQ(rules1, rules2); + EXPECT_EQ(rules2, rules1); rules1.ParseFromString(".foo2.com"); rules2.ParseFromString("foo1.com,.FOo2.com"); - EXPECT_FALSE(rules1.Equals(rules2)); - EXPECT_FALSE(rules2.Equals(rules1)); + EXPECT_FALSE(rules1 == rules2); + EXPECT_FALSE(rules2 == rules1); } -TEST(ProxyBypassRulesTest, BypassLocalNames) { - const struct { - const char* url; - bool expected_is_local; - } tests[] = { - // Single-component hostnames are considered local. - {"http://localhost/x", true}, - {"http://www", true}, - - // IPv4 loopback interface. - {"http://127.0.0.1/x", true}, - {"http://127.0.0.1:80/x", true}, - - // IPv6 loopback interface. - {"http://[::1]:80/x", true}, - {"http://[0:0::1]:6233/x", true}, - {"http://[0:0:0:0:0:0:0:1]/x", true}, - - // Non-local URLs. - {"http://foo.com/", false}, - {"http://localhost.i/", false}, - {"http://www.google.com/", false}, - {"http://192.168.0.1/", false}, - - // Try with different protocols. - {"ftp://127.0.0.1/x", true}, - {"ftp://foobar.com/x", false}, - - // This is a bit of a gray-area, but GURL does not strip trailing dots - // in host-names, so the following are considered non-local. - {"http://www./x", false}, - {"http://localhost./x", false}, - }; - +TEST(ProxyBypassRulesTest, BypassSimpleHostnames) { + // Test the simple hostnames rule in isolation, by first removing the + // implicit rules. ProxyBypassRules rules; - rules.ParseFromString("<local>"); + rules.ParseFromString("<-loopback>; <local>"); - for (size_t i = 0; i < arraysize(tests); ++i) { - SCOPED_TRACE(base::StringPrintf( - "Test[%d]: %s", static_cast<int>(i), tests[i].url)); - EXPECT_EQ(tests[i].expected_is_local, rules.Matches(GURL(tests[i].url))); - } + ASSERT_EQ(2u, rules.rules().size()); + EXPECT_EQ("<-loopback>", rules.rules()[0]->ToString()); + EXPECT_EQ("<local>", rules.rules()[1]->ToString()); + + EXPECT_TRUE(rules.Matches(GURL("http://example/"))); + + EXPECT_FALSE(rules.Matches(GURL("http://example./"))); + EXPECT_FALSE(rules.Matches(GURL("http://example.com/"))); + EXPECT_FALSE(rules.Matches(GURL("http://[dead::beef]/"))); + EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1/"))); + + // Confusingly, <local> rule is NOT about localhost names. There is however + // overlap on "localhost6?" as it is both a simple hostname and a localhost + // name + ExpectBypassLocalhost(rules, false, {"localhost", "localhost6", "loopback"}); + + // Should NOT bypass link-local addresses. + ExpectBypassLinkLocal(rules, false); + + // Should not bypass other names either (except for the ones with no dot). + ExpectBypassMisc(rules, false, {"foo", "loopback"}); } TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv4) { @@ -336,32 +398,123 @@ TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv6) { EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1"))); } -// Check that ProxyBypassRules::MatchesImplicitRules() matches all localhost -// names, and link-local IPs, but nothing else. -TEST(ProxyBypassRulesTest, MatchesImplicitRules) { - const char* kLocalhosts[] = { - "localhost", - "localhost.", - "foo.localhost", - "localhost6", - "localhost6.localdomain6", - "127.0.0.1", - "127.100.0.2", - "[::1]", - }; +// Test that parsing an IPv6 range given a bracketed literal is not supported. +// Whether IPv6 literals need to be bracketed or not is pretty much a coin toss +// depending on the context, and here it is expected to be unbracketed to match +// macOS. It would be fine to support bracketed too, however none of the +// grammars we parse need that. +TEST(ProxyBypassRulesTest, ParseBracketedIPv6Range) { + ProxyBypassRules rules; + rules.ParseFromString("[a:b:c:d::]/48"); + ASSERT_EQ(0u, rules.rules().size()); +} - const char* kLinkLocalHosts[] = { - "169.254.3.2", "169.254.100.1", "[FE80::8]", "[fe91::1]", - }; +// Check which URLs an empty ProxyBypassRules matches. +TEST(ProxyBypassRulesTest, DefaultImplicitRules) { + ProxyBypassRules rules; - const char* kOtherHosts[] = { - "192.168.0.1", "170.254.0.0", "128.0.0.1", "[::2]", - "[FD80::1]", "foo", "www.example3.com", "loopback", - }; + EXPECT_EQ("", rules.ToString()); + + // Should bypass all localhost and loopback names. + ExpectBypassLocalhost(rules, true); + + // Should bypass all link-local addresses. + ExpectBypassLinkLocal(rules, true); + + // Should not bypass other names. + ExpectBypassMisc(rules, false); +} + +// Test use of the <-loopback> bypass rule. +TEST(ProxyBypassRulesTest, NegativeWinLoopback) { + ProxyBypassRules rules; + + rules.ParseFromString("www.example.com;<-loopback>"); + ASSERT_EQ(2u, rules.rules().size()); + EXPECT_EQ("www.example.com", rules.rules()[0]->ToString()); + EXPECT_EQ("<-loopback>", rules.rules()[1]->ToString()); + + // Should NOT bypass localhost and loopback names. + ExpectBypassLocalhost(rules, false); + + // Should NOT bypass link-local addresses. + ExpectBypassLinkLocal(rules, false); + + // Should not bypass other names either. + ExpectBypassMisc(rules, false); + + // Only www.example.com should be bypassed. + EXPECT_TRUE(rules.Matches(GURL("http://www.example.com/"))); +} + +// Verifies the evaluation order of mixing negative and positive rules. This +// expectation comes from WinInet (which is where <-loopback> comes from). +TEST(ProxyBypassRulesTest, RemoveImplicitAndAddLocalhost) { + ProxyBypassRules rules; + + rules.ParseFromString("<-loopback>; localhost"); + ASSERT_EQ(2u, rules.rules().size()); + EXPECT_EQ("<-loopback>", rules.rules()[0]->ToString()); + EXPECT_EQ("localhost", rules.rules()[1]->ToString()); + + // Should not bypass localhost names because of <-loopback>. Except for + // "localhost" which was added at the end. + ExpectBypassLocalhost(rules, false, {"localhost"}); + + // Should NOT bypass link-local addresses. + ExpectBypassLinkLocal(rules, false); + + // Should not bypass other names either. + ExpectBypassMisc(rules, false); +} + +// Verifies the evaluation order of mixing negative and positive rules. This +// expectation comes from WinInet (which is where <-loopback> comes from). +TEST(ProxyBypassRulesTest, AddLocalhostThenRemoveImplicit) { + ProxyBypassRules rules; + + rules.ParseFromString("localhost; <-loopback>"); + ASSERT_EQ(2u, rules.rules().size()); + EXPECT_EQ("localhost", rules.rules()[0]->ToString()); + EXPECT_EQ("<-loopback>", rules.rules()[1]->ToString()); + + // Because of the ordering, localhost is not bypassed, because <-loopback> + // "unbypasses" it. + ExpectBypassLocalhost(rules, false); + + // Should NOT bypass link-local addresses. + ExpectBypassLinkLocal(rules, false); + + // Should not bypass other names either. + ExpectBypassMisc(rules, false); +} + +TEST(ProxyBypassRulesTest, AddRulesToSubtractImplicit) { + ProxyBypassRules rules; + rules.ParseFromString("foo"); + + rules.AddRulesToSubtractImplicit(); + + ASSERT_EQ(2u, rules.rules().size()); + EXPECT_EQ("foo", rules.rules()[0]->ToString()); + EXPECT_EQ("<-loopback>", rules.rules()[1]->ToString()); +} + +TEST(ProxyBypassRulesTest, GetRulesToSubtractImplicit) { + EXPECT_EQ("<-loopback>;", ProxyBypassRules::GetRulesToSubtractImplicit()); +} + +// Verifies that the <local> and <-loopback> rules can be specified in any +// case. This matches how WinInet's parses them. +TEST(ProxyBypassRulesTest, LoopbackAndLocalCaseInsensitive) { + ProxyBypassRules rules; - ExpectMatchesImplicitRules(kLocalhosts, true); - ExpectMatchesImplicitRules(kLinkLocalHosts, true); - ExpectMatchesImplicitRules(kOtherHosts, false); + rules.ParseFromString("<Local>; <-LoopBacK>; <LoCaL>; <-LoOpBack>"); + ASSERT_EQ(4u, rules.rules().size()); + EXPECT_EQ("<local>", rules.rules()[0]->ToString()); + EXPECT_EQ("<-loopback>", rules.rules()[1]->ToString()); + EXPECT_EQ("<local>", rules.rules()[2]->ToString()); + EXPECT_EQ("<-loopback>", rules.rules()[3]->ToString()); } } // namespace diff --git a/chromium/net/proxy_resolution/proxy_config.cc b/chromium/net/proxy_resolution/proxy_config.cc index 0f46b0092d2..9cfe6d1c0c0 100644 --- a/chromium/net/proxy_resolution/proxy_config.cc +++ b/chromium/net/proxy_resolution/proxy_config.cc @@ -53,10 +53,7 @@ void ProxyConfig::ProxyRules::Apply(const GURL& url, ProxyInfo* result) const { return; } - bool bypass_proxy = bypass_rules.Matches(url); - if (reverse_bypass) - bypass_proxy = !bypass_proxy; - if (bypass_proxy) { + if (bypass_rules.Matches(url, reverse_bypass)) { result->UseDirectWithBypassedProxy(); return; } @@ -157,13 +154,12 @@ const ProxyList* ProxyConfig::ProxyRules::MapUrlSchemeToProxyList( } bool ProxyConfig::ProxyRules::Equals(const ProxyRules& other) const { - return type == other.type && - single_proxies.Equals(other.single_proxies) && + return type == other.type && single_proxies.Equals(other.single_proxies) && proxies_for_http.Equals(other.proxies_for_http) && proxies_for_https.Equals(other.proxies_for_https) && proxies_for_ftp.Equals(other.proxies_for_ftp) && fallback_proxies.Equals(other.fallback_proxies) && - bypass_rules.Equals(other.bypass_rules) && + bypass_rules == other.bypass_rules && reverse_bypass == other.reverse_bypass; } @@ -199,8 +195,6 @@ ProxyConfig::~ProxyConfig() = default; ProxyConfig& ProxyConfig::operator=(const ProxyConfig& config) = default; bool ProxyConfig::Equals(const ProxyConfig& other) const { - // The two configs can have different IDs and sources. We are just interested - // in if they have the same settings. return auto_detect_ == other.auto_detect_ && pac_url_ == other.pac_url_ && pac_mandatory_ == other.pac_mandatory_ && @@ -270,4 +264,4 @@ std::unique_ptr<base::DictionaryValue> ProxyConfig::ToValue() const { return dict; } -} // namespace net
\ No newline at end of file +} // namespace net diff --git a/chromium/net/proxy_resolution/proxy_config.h b/chromium/net/proxy_resolution/proxy_config.h index 02d12a3376c..5b7798e3d6c 100644 --- a/chromium/net/proxy_resolution/proxy_config.h +++ b/chromium/net/proxy_resolution/proxy_config.h @@ -152,8 +152,7 @@ class NET_EXPORT ProxyConfig { ~ProxyConfig(); ProxyConfig& operator=(const ProxyConfig& config); - // Returns true if the given config is equivalent to this config. The - // comparison ignores differences in |source()|. + // Returns true if the given config is equivalent to this config. bool Equals(const ProxyConfig& other) const; // Returns true if this config contains any "automatic" settings. See the diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux.cc b/chromium/net/proxy_resolution/proxy_config_service_linux.cc index 239b86c1b07..e8e16a7c5b6 100644 --- a/chromium/net/proxy_resolution/proxy_config_service_linux.cc +++ b/chromium/net/proxy_resolution/proxy_config_service_linux.cc @@ -199,8 +199,8 @@ ProxyConfigServiceLinux::Delegate::GetConfigFromEnv() { } // Note that this uses "suffix" matching. So a bypass of "google.com" // is understood to mean a bypass of "*google.com". - config.proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching( - no_proxy); + config.proxy_rules().bypass_rules.ParseFromString( + no_proxy, ProxyBypassRules::ParseFormat::kHostnameSuffixMatching); return ProxyConfigWithAnnotation( config, NetworkTrafficAnnotationTag(traffic_annotation_)); } @@ -381,7 +381,9 @@ class SettingGetterImplGSettings return false; } - bool MatchHostsUsingSuffixMatching() override { return false; } + ProxyBypassRules::ParseFormat GetBypassListFormat() override { + return ProxyBypassRules::ParseFormat::kDefault; + } private: bool GetStringByPath(GSettings* client, @@ -671,7 +673,9 @@ class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter { bool BypassListIsReversed() override { return reversed_bypass_list_; } - bool MatchHostsUsingSuffixMatching() override { return true; } + ProxyBypassRules::ParseFormat GetBypassListFormat() override { + return ProxyBypassRules::ParseFormat::kHostnameSuffixMatching; + } private: void ResetCachedSettings() { @@ -1137,18 +1141,14 @@ ProxyConfigServiceLinux::Delegate::GetConfigFromSettings() { } // Now the bypass list. + auto format = setting_getter_->GetBypassListFormat(); + std::vector<std::string> ignore_hosts_list; config.proxy_rules().bypass_rules.Clear(); if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS, &ignore_hosts_list)) { - std::vector<std::string>::const_iterator it(ignore_hosts_list.begin()); - for (; it != ignore_hosts_list.end(); ++it) { - if (setting_getter_->MatchHostsUsingSuffixMatching()) { - config.proxy_rules().bypass_rules.AddRuleFromStringUsingSuffixMatching( - *it); - } else { - config.proxy_rules().bypass_rules.AddRuleFromString(*it); - } + for (const auto& rule : ignore_hosts_list) { + config.proxy_rules().bypass_rules.AddRuleFromString(rule, format); } } // Note that there are no settings with semantics corresponding to diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux.h b/chromium/net/proxy_resolution/proxy_config_service_linux.h index 95762b85db7..963b1bcfd90 100644 --- a/chromium/net/proxy_resolution/proxy_config_service_linux.h +++ b/chromium/net/proxy_resolution/proxy_config_service_linux.h @@ -132,9 +132,8 @@ class NET_EXPORT_PRIVATE ProxyConfigServiceLinux : public ProxyConfigService { // whitelist rather than blacklist. (This is KDE-specific.) virtual bool BypassListIsReversed() = 0; - // Returns true if the bypass rules should be interpreted as - // suffix-matching rules. - virtual bool MatchHostsUsingSuffixMatching() = 0; + // Returns the format to use when parsing the bypass rules list. + virtual ProxyBypassRules::ParseFormat GetBypassListFormat() = 0; private: DISALLOW_COPY_AND_ASSIGN(SettingGetter); diff --git a/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc index 6dd7bd245b4..d22b825bcd3 100644 --- a/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc +++ b/chromium/net/proxy_resolution/proxy_config_service_linux_unittest.cc @@ -22,6 +22,7 @@ #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" +#include "base/task/task_scheduler/task_scheduler.h" #include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.h" #include "net/proxy_resolution/proxy_config.h" @@ -254,7 +255,9 @@ class MockSettingGetter : public ProxyConfigServiceLinux::SettingGetter { bool BypassListIsReversed() override { return false; } - bool MatchHostsUsingSuffixMatching() override { return false; } + ProxyBypassRules::ParseFormat GetBypassListFormat() override { + return ProxyBypassRules::ParseFormat::kDefault; + } // Intentionally public, for convenience when setting up a test. GSettingsValues values; @@ -1877,6 +1880,11 @@ TEST_F(ProxyConfigServiceLinuxTest, KDEFileChanged) { // observed. sync_config_getter.SetExpectedPacUrl("http://version2/wpad.dat"); + // Initialization posts a task to start watching kioslaverc file. Ensure that + // registration has happened before modifying it or the file change won't be + // observed. + base::TaskScheduler::GetInstance()->FlushForTesting(); + WriteFile(kioslaverc_, "[Proxy Settings]\nProxyType=2\n" "Proxy Config Script=http://version2/wpad.dat\n"); diff --git a/chromium/net/proxy_resolution/proxy_config_service_mac.cc b/chromium/net/proxy_resolution/proxy_config_service_mac.cc index 824978b9ae5..3409321beb0 100644 --- a/chromium/net/proxy_resolution/proxy_config_service_mac.cc +++ b/chromium/net/proxy_resolution/proxy_config_service_mac.cc @@ -153,7 +153,8 @@ void GetCurrentProxyConfig(const NetworkTrafficAnnotationTag traffic_annotation, if (GetBoolFromDictionary(config_dict.get(), kSCPropNetProxiesExcludeSimpleHostnames, false)) { - proxy_config.proxy_rules().bypass_rules.AddRuleToBypassLocal(); + proxy_config.proxy_rules() + .bypass_rules.PrependRuleToBypassSimpleHostnames(); } *config = ProxyConfigWithAnnotation(proxy_config, traffic_annotation); diff --git a/chromium/net/proxy_resolution/proxy_resolution_service.cc b/chromium/net/proxy_resolution/proxy_resolution_service.cc index 77787b65d17..cf2a9621221 100644 --- a/chromium/net/proxy_resolution/proxy_resolution_service.cc +++ b/chromium/net/proxy_resolution/proxy_resolution_service.cc @@ -21,6 +21,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "base/values.h" +#include "build/build_config.h" #include "net/base/net_errors.h" #include "net/base/proxy_delegate.h" #include "net/base/url_util.h" @@ -1561,6 +1562,9 @@ ProxyResolutionService::CreateSystemProxyConfigService( #elif defined(OS_ANDROID) return std::make_unique<ProxyConfigServiceAndroid>( main_task_runner, base::ThreadTaskRunnerHandle::Get()); +#elif defined(OS_FUCHSIA) + // TODO(crbug.com/889195): Implement a system proxy service for Fuchsia. + return std::make_unique<ProxyConfigServiceDirect>(); #else LOG(WARNING) << "Failed to choose a system proxy settings fetcher " "for this platform."; diff --git a/chromium/net/proxy_resolution/proxy_resolution_service.h b/chromium/net/proxy_resolution/proxy_resolution_service.h index 119c48a0184..6d7f7ed301a 100644 --- a/chromium/net/proxy_resolution/proxy_resolution_service.h +++ b/chromium/net/proxy_resolution/proxy_resolution_service.h @@ -390,7 +390,10 @@ class NET_EXPORT ProxyResolutionService const ProxyConfigWithAnnotation& config, ProxyConfigService::ConfigAvailability availability) override; - // When using a PAC script, don't let it decide the proxy for localhost URLs. + // When using a PAC script there isn't a user-configurable ProxyBypassRules to + // check, as the one from manual settings doesn't apply. However we + // still check for matches against the implicit bypass rules, to prevent PAC + // scripts from being able to proxy localhost. bool ApplyPacBypassRules(const GURL& url, ProxyInfo* results); std::unique_ptr<ProxyConfigService> config_service_; diff --git a/chromium/net/proxy_resolution/proxy_resolution_service_unittest.cc b/chromium/net/proxy_resolution/proxy_resolution_service_unittest.cc index 4759a8be888..8bf71e461a8 100644 --- a/chromium/net/proxy_resolution/proxy_resolution_service_unittest.cc +++ b/chromium/net/proxy_resolution/proxy_resolution_service_unittest.cc @@ -4025,9 +4025,7 @@ const char* kImplicityBypassedHosts[] = { const char* kUrlSchemes[] = {"http://", "https://", "ftp://"}; -// Tests that the implicit bypass rules do not affect when configured with -// manual proxy settings. -TEST_F(ProxyResolutionServiceTest, DontBypassWithManualSettings) { +TEST_F(ProxyResolutionServiceTest, ImplicitlyBypassWithManualSettings) { // Use manual proxy settings that specify a single proxy for all traffic. ProxyConfig config; config.proxy_rules().ParseFromString("foopy1:8080"); @@ -4046,8 +4044,8 @@ TEST_F(ProxyResolutionServiceTest, DontBypassWithManualSettings) { EXPECT_THAT(rv, IsOk()); EXPECT_EQ("foopy1:8080", info1.proxy_server().ToURI()); - // Test that localhost and link-local URLs do not bypass the proxy - // (independent of the URL scheme). + // Test that localhost and link-local URLs bypass the proxy (independent of + // the URL scheme). for (auto* host : kImplicityBypassedHosts) { for (auto* scheme : kUrlSchemes) { auto url = GURL(std::string(scheme) + std::string(host)); @@ -4059,13 +4057,12 @@ TEST_F(ProxyResolutionServiceTest, DontBypassWithManualSettings) { service->ResolveProxy(url, std::string(), &info, callback.callback(), &request, NetLogWithSource()); EXPECT_THAT(rv, IsOk()); - EXPECT_FALSE(info.is_direct()); - EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + EXPECT_TRUE(info.is_direct()); } } } -// Test that the when using a PAC script (sourced via auto-detect), +// Test that the when using a PAC script (sourced via auto-detect) certain // localhost names are implicitly bypassed. TEST_F(ProxyResolutionServiceTest, ImplicitlyBypassWithPac) { ProxyConfig config; diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8.cc b/chromium/net/proxy_resolution/proxy_resolver_v8.cc index c77fcc8dbdd..7a426d1b6c6 100644 --- a/chromium/net/proxy_resolution/proxy_resolver_v8.cc +++ b/chromium/net/proxy_resolution/proxy_resolver_v8.cc @@ -69,7 +69,7 @@ // --------------------+-------------+-------------------+-------------- // | Firefox3 | InternetExplorer8 | --> Us <--- // --------------------+-------------+-------------------+-------------- -// myIpAddress() | IPv4/IPv6 | IPv4 | IPv4 +// myIpAddress() | IPv4/IPv6 | IPv4 | IPv4/IPv6 // dnsResolve() | IPv4/IPv6 | IPv4 | IPv4 // isResolvable() | IPv4/IPv6 | IPv4 | IPv4 // myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6 @@ -385,6 +385,11 @@ class SharedIsolateFactory { static const char kNoOpt[] = "--noopt"; v8::V8::SetFlagsFromString(kNoOpt, strlen(kNoOpt)); + // WebAssembly isn't encountered during resolution, so reduce the + // potential attack surface. + static const char kNoExposeWasm[] = "--no-expose-wasm"; + v8::V8::SetFlagsFromString(kNoExposeWasm, strlen(kNoExposeWasm)); + gin::IsolateHolder::Initialize( gin::IsolateHolder::kNonStrictMode, gin::IsolateHolder::kStableV8Extras, diff --git a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc index 844f3c598fa..9a751c23d88 100644 --- a/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc +++ b/chromium/net/proxy_resolution/proxy_resolver_v8_tracing.cc @@ -560,7 +560,7 @@ void Job::ExecuteNonBlocking() { } int Job::ExecuteProxyResolver() { - TRACE_EVENT0(kNetTracingCategory, "Job::ExecuteProxyResolver"); + TRACE_EVENT0(NetTracingCategory(), "Job::ExecuteProxyResolver"); int result = ERR_UNEXPECTED; // Initialized to silence warnings. switch (operation_) { @@ -830,16 +830,16 @@ HostResolver::RequestInfo Job::MakeDnsRequestInfo(const std::string& host, ResolveDnsOperation op) { HostPortPair host_port = HostPortPair(host, 80); if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { + // TODO(eroman): Remove the need for hostname. This is currently relied on + // for the cache key (is_my_ip_address isn't part of it). host_port.set_host(GetHostName()); } HostResolver::RequestInfo info(host_port); // Flag myIpAddress requests. - if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) { - // TODO: Provide a RequestInfo construction mechanism that does not - // require a hostname and sets is_my_ip_address to true instead of this. + if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) info.set_is_my_ip_address(true); - } + // The non-ex flavors are limited to IPv4 results. if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) { info.set_address_family(ADDRESS_FAMILY_IPV4); |