Implicitly bypass localhost when proxying requests.
This aligns Chrome's behavior with the Windows and macOS proxy resolvers (but not Firefox).
Concretely:
* localhost names (as determined by net::IsLocalhost) now implicitly bypass the proxy
* link-local IP addresses implicitly bypass the proxy
The implicit rules are handled by ProxyBypassRules, and it is possible to override them when manually configuring proxy settings (but not when using PAC or auto-detect).
This change also adds support for the "<-loopback>" proxy bypass rule, with similar semantics as it has on Windows (removes the implicit bypass rules for localhost and link-local).
The compatibility risk of this change should be low as proxying through localhost was not universally supported. It is however an idiom used in testing (a number of our own tests had such a dependency). Impacted users can use the "<-loopback>" bypass rule as a workaround.
Bug: 413511, 899126, 901896
Change-Id: I263ca21ef9f12d4759a20cb4751dc3261bda6ac0
Reviewed-on: https://chromium-review.googlesource.com/c/1303626
Commit-Queue: Eric Roman <[email protected]>
Reviewed-by: Dominick Ng <[email protected]>
Reviewed-by: Tarun Bansal <[email protected]>
Reviewed-by: Matt Menke <[email protected]>
Reviewed-by: Sami Kyöstilä <[email protected]>
Cr-Commit-Position: refs/heads/master@{#606112}
diff --git a/net/http/http_auth_filter.cc b/net/http/http_auth_filter.cc
index 4d92591..d5efb6dd 100644
--- a/net/http/http_auth_filter.cc
+++ b/net/http/http_auth_filter.cc
@@ -35,10 +35,6 @@
return true;
}
-void HttpAuthFilterWhitelist::AddRuleToBypassLocal() {
- rules_.AddRuleToBypassLocal();
-}
-
bool HttpAuthFilterWhitelist::IsValid(const GURL& url,
HttpAuth::Target target) const {
if ((target != HttpAuth::AUTH_SERVER) && (target != HttpAuth::AUTH_PROXY))
@@ -51,7 +47,12 @@
void HttpAuthFilterWhitelist::SetWhitelist(
const std::string& server_whitelist) {
- rules_.ParseFromString(server_whitelist);
+ // TODO(eroman): Is this necessary? The issue is that
+ // HttpAuthFilterWhitelist is trying to use ProxyBypassRules as a generic
+ // URL filter. However internally it has some implicit rules for localhost
+ // and linklocal addresses.
+ rules_.ParseFromString(ProxyBypassRules::GetRulesToSubtractImplicit() + ";" +
+ server_whitelist);
}
} // namespace net
diff --git a/net/http/http_auth_filter.h b/net/http/http_auth_filter.h
index ae79019..4ebe897 100644
--- a/net/http/http_auth_filter.h
+++ b/net/http/http_auth_filter.h
@@ -41,9 +41,6 @@
// Adds an individual URL |filter| to the list, of the specified |target|.
bool AddFilter(const std::string& filter, HttpAuth::Target target);
- // Adds a rule that bypasses all "local" hostnames.
- void AddRuleToBypassLocal();
-
const ProxyBypassRules& rules() const { return rules_; }
// HttpAuthFilter methods:
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 1182d0f..38dce9c 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -13967,18 +13967,18 @@
HttpRequestInfo request;
request.method = "GET";
- request.url = GURL("https://[::1]:443/");
+ request.url = GURL("https://[::2]:443/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
- MockWrite("CONNECT [::1]:443 HTTP/1.1\r\n"
- "Host: [::1]:443\r\n"
+ MockWrite("CONNECT [::2]:443 HTTP/1.1\r\n"
+ "Host: [::2]:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
- "Host: [::1]\r\n"
+ "Host: [::2]\r\n"
"Connection: keep-alive\r\n\r\n"),
};
diff --git a/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc b/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc
index 100e818..d6821f38 100644
--- a/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc
+++ b/net/proxy_resolution/parse_proxy_bypass_rules_fuzzer.cc
@@ -18,7 +18,14 @@
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/net/proxy_resolution/proxy_bypass_rules.cc b/net/proxy_resolution/proxy_bypass_rules.cc
index 18a6683..7639414 100644
--- a/net/proxy_resolution/proxy_bypass_rules.cc
+++ b/net/proxy_resolution/proxy_bypass_rules.cc
@@ -18,6 +18,48 @@
namespace {
+// 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>";
+
+// TODO(eroman): Fix - this should be renamed to kBypassSimpleHostnames.
+const char kWinLocal[] = "<local>";
+
+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);
+}
+
+bool IsLinkLocalIP(const GURL& url) {
+ // Quick fail if definitely not link-local, to avoid doing unnecessary work in
+ // common case. The |url| should be canonicalized, which for IPv6 literals
+ // means lowercase.
+ if (!(url.host_piece().starts_with("169.254.") ||
+ url.host_piece().starts_with("[fe"))) {
+ 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);
+}
+
class HostnamePatternRule : public ProxyBypassRules::Rule {
public:
HostnamePatternRule(const std::string& optional_scheme,
@@ -27,16 +69,17 @@
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 {
@@ -49,77 +92,91 @@
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 {
+// TODO(https://crbug.com/902579): Fix.
+class WinLocalRule : public ProxyBypassRules::Rule {
public:
- bool Matches(const GURL& url) const override {
+ WinLocalRule() = default;
+
+ Result Evaluate(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;
+ return Result::kBypass;
+ return (host.find('.') == std::string::npos) ? Result::kBypass
+ : Result::kNoMatch;
}
- std::string ToString() const override { return "<local>"; }
+ std::string ToString() const override { return kWinLocal; }
- std::unique_ptr<Rule> Clone() const override {
- return std::make_unique<BypassLocalRule>();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WinLocalRule);
+};
+
+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.
@@ -134,6 +191,10 @@
return host_info.IsIPAddress();
}
+std::unique_ptr<ProxyBypassRules::Rule> ParseRule(
+ const std::string& raw_untrimmed,
+ ProxyBypassRules::ParseFormat format);
+
} // namespace
ProxyBypassRules::Rule::Rule() = default;
@@ -150,21 +211,52 @@
AssignFrom(rhs);
}
-ProxyBypassRules::~ProxyBypassRules() {
- Clear();
-}
+ProxyBypassRules::~ProxyBypassRules() = default;
ProxyBypassRules& ProxyBypassRules::operator=(const ProxyBypassRules& rhs) {
AssignFrom(rhs);
return *this;
}
-bool ProxyBypassRules::Matches(const GURL& url) const {
- for (auto it = rules_.begin(); it != rules_.end(); ++it) {
- if ((*it)->Matches(url))
- return true;
+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 {
@@ -178,15 +270,6 @@
return true;
}
-void ProxyBypassRules::ParseFromString(const std::string& raw) {
- ParseFromStringInternal(raw, false);
-}
-
-void ProxyBypassRules::ParseFromStringUsingSuffixMatching(
- const std::string& raw) {
- ParseFromStringInternal(raw, true);
-}
-
bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme,
const std::string& hostname_pattern,
int optional_port) {
@@ -199,16 +282,29 @@
}
void ProxyBypassRules::AddRuleToBypassLocal() {
- rules_.push_back(std::make_unique<BypassLocalRule>());
+ rules_.push_back(std::make_unique<WinLocalRule>());
}
-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;
}
-bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching(
- const std::string& raw) {
- return AddRuleFromStringInternalWithLogging(raw, true);
+void ProxyBypassRules::AddRulesToSubtractImplicit() {
+ rules_.push_back(std::make_unique<SubtractImplicitBypassesRule>());
+}
+
+std::string ProxyBypassRules::GetRulesToSubtractImplicit() {
+ ProxyBypassRules rules;
+ rules.AddRulesToSubtractImplicit();
+ return rules.ToString();
}
std::string ProxyBypassRules::ToString() const {
@@ -225,38 +321,33 @@
}
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());
- }
+ ParseFromString(other.ToString());
}
-void ProxyBypassRules::ParseFromStringInternal(
- const std::string& raw,
- bool use_hostname_suffix_matching) {
+void ProxyBypassRules::ParseFromString(const std::string& raw,
+ ParseFormat format) {
Clear();
base::StringTokenizer entries(raw, ",;");
while (entries.GetNext()) {
- AddRuleFromStringInternalWithLogging(entries.token(),
- use_hostname_suffix_matching);
+ AddRuleFromString(entries.token(), format);
}
}
-bool ProxyBypassRules::AddRuleFromStringInternal(
+// TODO(eroman): Move this up in file.
+namespace {
+std::unique_ptr<ProxyBypassRules::Rule> ParseRule(
const std::string& raw_untrimmed,
- bool use_hostname_suffix_matching) {
+ ProxyBypassRules::ParseFormat format) {
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;
- }
+ // <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, kWinLocal))
+ return std::make_unique<WinLocalRule>();
+ if (base::LowerCaseEqualsASCII(raw, kSubtractImplicitBypasses))
+ return std::make_unique<SubtractImplicitBypassesRule>();
// Extract any scheme-restriction.
std::string::size_type scheme_pos = raw.find("://");
@@ -265,11 +356,11 @@
scheme = raw.substr(0, scheme_pos);
raw = raw.substr(scheme_pos + 3);
if (scheme.empty())
- return false;
+ return nullptr;
}
if (raw.empty())
- return false;
+ return nullptr;
// If there is a forward slash in the input, it is probably a CIDR style
// mask.
@@ -278,12 +369,10 @@
size_t prefix_length_in_bits;
if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits))
- return false;
+ return nullptr;
- rules_.push_back(std::make_unique<BypassIPBlockRule>(
- raw, scheme, ip_prefix, prefix_length_in_bits));
-
- return true;
+ 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
@@ -294,7 +383,7 @@
// TODO(eroman): HostForURL() below DCHECKs() when |host| contains an
// embedded NULL.
if (host.find('\0') != std::string::npos)
- return false;
+ 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.
@@ -302,7 +391,8 @@
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);
+ return std::make_unique<HostnamePatternRule>(scheme, tmp_url.host(),
+ port);
}
}
@@ -313,7 +403,7 @@
if (!ParseInt32(base::StringPiece(raw.begin() + pos_colon + 1, raw.end()),
ParseIntFormat::NON_NEGATIVE, &port) ||
port > 0xFFFF) {
- return false; // Port was invalid.
+ return nullptr; // Port was invalid.
}
raw = raw.substr(0, pos_colon);
}
@@ -325,17 +415,45 @@
// If suffix matching was asked for, make sure the pattern starts with a
// wildcard.
- if (use_hostname_suffix_matching &&
+ if (format == ProxyBypassRules::ParseFormat::kHostnameSuffixMatching &&
!base::StartsWith(raw, "*", base::CompareCase::SENSITIVE))
raw = "*" + raw;
- return AddRuleForHostname(scheme, raw, port);
+ return std::make_unique<HostnamePatternRule>(scheme, raw, port);
}
+} // namespace
-bool ProxyBypassRules::AddRuleFromStringInternalWithLogging(
- const std::string& raw,
- bool use_hostname_suffix_matching) {
- return AddRuleFromStringInternal(raw, use_hostname_suffix_matching);
+bool ProxyBypassRules::MatchesImplicitRules(const GURL& url) {
+ // On Windows the implict rules are:
+ //
+ // localhost
+ // loopback
+ // 127.0.0.1
+ // [::1]
+ // 169.254/16
+ // [FE80::]/10
+ //
+ // And on macOS they are:
+ //
+ // localhost
+ // 127.0.0.1/8
+ // [::1]
+ // 169.254/16
+ //
+ // Our implicit rules are approximately:
+ //
+ // localhost
+ // localhost.
+ // *.localhost
+ // localhost6
+ // localhost6.localdomain6
+ // [::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);
}
} // namespace net
diff --git a/net/proxy_resolution/proxy_bypass_rules.h b/net/proxy_resolution/proxy_bypass_rules.h
index c072aef..62e2e85 100644
--- a/net/proxy_resolution/proxy_bypass_rules.h
+++ b/net/proxy_resolution/proxy_bypass_rules.h
@@ -15,33 +15,69 @@
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.
@@ -55,25 +91,24 @@
// 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;
// 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
@@ -91,6 +126,8 @@
// "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).
+ //
+ // TODO(https://crbug.com/902579): Fix.
void AddRuleToBypassLocal();
// Adds a rule given by the string |raw|. The format of |raw| can be any of
@@ -133,27 +170,40 @@
//
// (5) "<local>"
//
- // Match local addresses. The meaning of "<local>" is whether the
- // host matches one of: "127.0.0.1", "::1", "localhost".
+ // Matches the bypass rule in Windows of the same name, which essentially
+ // means hostnames without a period in them, as well as "127.0.0.1",
+ // "[::1]" and "localhost" (but not other localhost names).
+ //
+ // TODO(https://crbug.com/902579): Fix.
+ //
+ // (6) "<-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);
- // 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 true if the rule was successfully added.
- //
- // NOTE: Use AddRuleFromString() unless you truly need this behavior.
- bool AddRuleFromStringUsingSuffixMatching(const std::string& raw);
+ // Appends rules that "cancels out" the implicit bypass rules. See
+ // GetRulesToSubtractImplicit() for details.
+ void AddRulesToSubtractImplicit();
- // Converts the rules to string representation. Inverse operation to
- // ParseFromString().
+ // Returns a list of bypass rules that "cancels out" the implicit bypass
+ // rules.
+ //
+ // 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.
+ //
+ // If using this for tests, see https://crbug.com/901896.
+ static std::string GetRulesToSubtractImplicit();
+
+ // Converts the rules to a string representation (ParseFormat::kDefault).
std::string ToString() const;
// Removes all the rules.
@@ -162,17 +212,11 @@
// Sets |*this| to |other|.
void AssignFrom(const ProxyBypassRules& other);
- 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);
+ // Returns true if |url| matches one of the implicit proxy bypass rules
+ // (localhost or link local).
+ static bool MatchesImplicitRules(const GURL& url);
+ private:
RuleList rules_;
};
diff --git a/net/proxy_resolution/proxy_bypass_rules_unittest.cc b/net/proxy_resolution/proxy_bypass_rules_unittest.cc
index c3e2b13..107335c 100644
--- a/net/proxy_resolution/proxy_bypass_rules_unittest.cc
+++ b/net/proxy_resolution/proxy_bypass_rules_unittest.cc
@@ -4,6 +4,7 @@
#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 "net/proxy_resolution/proxy_config_service_common_unittest.h"
@@ -13,6 +14,77 @@
namespace {
+// 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 < 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(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]",
+ };
+
+ 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",
+ // On Windows, "loopback" is an implicitly matched hostname.
+ "loopback",
+ };
+
+ ExpectRulesMatch(rules, kHosts, base::size(kHosts), bypasses, inverted_hosts);
+}
+
TEST(ProxyBypassRulesTest, ParseAndMatchBasicHost) {
ProxyBypassRules rules;
rules.ParseFromString("wWw.gOogle.com");
@@ -192,9 +264,10 @@
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());
@@ -252,33 +325,35 @@
const char* url;
bool expected_is_local;
} tests[] = {
- // Single-component hostnames are considered local.
- {"http://localhost/x", true},
- {"http://www", true},
+ // 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},
+ // 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},
+ // 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},
+ // 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},
+ // 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},
+ // 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},
+
+ // localhost. is bypassed by the implict rules already.
+ {"http://localhost./x", true},
};
ProxyBypassRules rules;
@@ -319,6 +394,101 @@
EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1")));
}
+// Check which URLs an empty ProxyBypassRules matches.
+TEST(ProxyBypassRulesTest, DefaultImplicitRules) {
+ ProxyBypassRules rules;
+
+ 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());
+}
+
} // namespace
} // namespace net
diff --git a/net/proxy_resolution/proxy_config.cc b/net/proxy_resolution/proxy_config.cc
index 0f46b00..d48c2ce5 100644
--- a/net/proxy_resolution/proxy_config.cc
+++ b/net/proxy_resolution/proxy_config.cc
@@ -53,10 +53,7 @@
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;
}
@@ -199,8 +196,6 @@
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 +265,4 @@
return dict;
}
-} // namespace net
\ No newline at end of file
+} // namespace net
diff --git a/net/proxy_resolution/proxy_config.h b/net/proxy_resolution/proxy_config.h
index 02d12a3..5b7798e 100644
--- a/net/proxy_resolution/proxy_config.h
+++ b/net/proxy_resolution/proxy_config.h
@@ -152,8 +152,7 @@
~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/net/proxy_resolution/proxy_config_service_linux.cc b/net/proxy_resolution/proxy_config_service_linux.cc
index 239b86c..e8e16a7c 100644
--- a/net/proxy_resolution/proxy_config_service_linux.cc
+++ b/net/proxy_resolution/proxy_config_service_linux.cc
@@ -199,8 +199,8 @@
}
// 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 @@
return false;
}
- bool MatchHostsUsingSuffixMatching() override { return false; }
+ ProxyBypassRules::ParseFormat GetBypassListFormat() override {
+ return ProxyBypassRules::ParseFormat::kDefault;
+ }
private:
bool GetStringByPath(GSettings* client,
@@ -671,7 +673,9 @@
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 @@
}
// 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/net/proxy_resolution/proxy_config_service_linux.h b/net/proxy_resolution/proxy_config_service_linux.h
index 95762b8..963b1bcf 100644
--- a/net/proxy_resolution/proxy_config_service_linux.h
+++ b/net/proxy_resolution/proxy_config_service_linux.h
@@ -132,9 +132,8 @@
// 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/net/proxy_resolution/proxy_config_service_linux_unittest.cc b/net/proxy_resolution/proxy_config_service_linux_unittest.cc
index 6dd7bd24..dac47cf 100644
--- a/net/proxy_resolution/proxy_config_service_linux_unittest.cc
+++ b/net/proxy_resolution/proxy_config_service_linux_unittest.cc
@@ -254,7 +254,9 @@
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;
diff --git a/net/proxy_resolution/proxy_resolution_service.cc b/net/proxy_resolution/proxy_resolution_service.cc
index 4ada96d4..cf2a962 100644
--- a/net/proxy_resolution/proxy_resolution_service.cc
+++ b/net/proxy_resolution/proxy_resolution_service.cc
@@ -934,6 +934,9 @@
traffic_annotation_ = MutableNetworkTrafficAnnotationTag(
service_->config_->traffic_annotation());
+ if (service_->ApplyPacBypassRules(url_, results_))
+ return OK;
+
return resolver()->GetProxyForURL(
url_, results_,
base::Bind(&ProxyResolutionService::RequestImpl::QueryComplete,
@@ -1178,8 +1181,13 @@
DCHECK(config_);
// If it was impossible to fetch or parse the PAC script, we cannot complete
// the request here and bail out.
- if (permanent_error_ != OK)
+ if (permanent_error_ != OK) {
+ // Before returning the permanent error check if the URL would have been
+ // implicitly bypassed.
+ if (ApplyPacBypassRules(url, result))
+ return OK;
return permanent_error_;
+ }
if (config_->value().HasAutomaticSettings())
return ERR_IO_PENDING; // Must submit the request to the proxy resolver.
@@ -1615,6 +1623,18 @@
InitializeUsingLastFetchedConfig();
}
+bool ProxyResolutionService::ApplyPacBypassRules(const GURL& url,
+ ProxyInfo* results) {
+ DCHECK(config_);
+
+ if (ProxyBypassRules::MatchesImplicitRules(url)) {
+ results->UseDirectWithBypassedProxy();
+ return true;
+ }
+
+ return false;
+}
+
void ProxyResolutionService::InitializeUsingLastFetchedConfig() {
ResetProxyConfig(false);
diff --git a/net/proxy_resolution/proxy_resolution_service.h b/net/proxy_resolution/proxy_resolution_service.h
index 04a0d10..6d7f7ed 100644
--- a/net/proxy_resolution/proxy_resolution_service.h
+++ b/net/proxy_resolution/proxy_resolution_service.h
@@ -390,6 +390,12 @@
const ProxyConfigWithAnnotation& config,
ProxyConfigService::ConfigAvailability availability) override;
+ // 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_;
std::unique_ptr<ProxyResolverFactory> resolver_factory_;
std::unique_ptr<ProxyResolver> resolver_;
diff --git a/net/proxy_resolution/proxy_resolution_service_unittest.cc b/net/proxy_resolution/proxy_resolution_service_unittest.cc
index e36c559e..8bf71e4 100644
--- a/net/proxy_resolution/proxy_resolution_service_unittest.cc
+++ b/net/proxy_resolution/proxy_resolution_service_unittest.cc
@@ -4008,4 +4008,124 @@
pac_histogram.VerifyHistogram();
}
+const char* kImplicityBypassedHosts[] = {
+ "localhost",
+ "localhost.",
+ "foo.localhost",
+ "localhost6",
+ "localhost6.localdomain6",
+ "127.0.0.1",
+ "127.100.0.2",
+ "[::1]",
+ "169.254.3.2",
+ "169.254.100.1",
+ "[FE80::8]",
+ "[feb8::1]",
+};
+
+const char* kUrlSchemes[] = {"http://", "https://", "ftp://"};
+
+TEST_F(ProxyResolutionServiceTest, ImplicitlyBypassWithManualSettings) {
+ // Use manual proxy settings that specify a single proxy for all traffic.
+ ProxyConfig config;
+ config.proxy_rules().ParseFromString("foopy1:8080");
+ config.set_auto_detect(false);
+
+ auto service = ProxyResolutionService::CreateFixed(
+ ProxyConfigWithAnnotation(config, TRAFFIC_ANNOTATION_FOR_TESTS));
+
+ // A normal request should use the proxy.
+ std::unique_ptr<ProxyResolutionService::Request> request1;
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service->ResolveProxy(GURL("http://www.example.com"), std::string(),
+ &info1, callback1.callback(), &request1,
+ NetLogWithSource());
+ EXPECT_THAT(rv, IsOk());
+ EXPECT_EQ("foopy1:8080", info1.proxy_server().ToURI());
+
+ // 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));
+
+ std::unique_ptr<ProxyResolutionService::Request> request;
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv =
+ service->ResolveProxy(url, std::string(), &info, callback.callback(),
+ &request, NetLogWithSource());
+ EXPECT_THAT(rv, IsOk());
+ EXPECT_TRUE(info.is_direct());
+ }
+ }
+}
+
+// Test that the when using a PAC script (sourced via auto-detect) certain
+// localhost names are implicitly bypassed.
+TEST_F(ProxyResolutionServiceTest, ImplicitlyBypassWithPac) {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolver resolver;
+ MockAsyncProxyResolverFactory* factory =
+ new MockAsyncProxyResolverFactory(true);
+ ProxyResolutionService service(base::WrapUnique(config_service),
+ base::WrapUnique(factory), nullptr);
+
+ MockPacFileFetcher* fetcher = new MockPacFileFetcher;
+ service.SetPacFileFetchers(base::WrapUnique(fetcher),
+ std::make_unique<DoNothingDhcpPacFileFetcher>());
+
+ // Start 1 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ std::unique_ptr<ProxyResolutionService::Request> request1;
+ int rv =
+ service.ResolveProxy(GURL("http://www.google.com"), std::string(), &info1,
+ callback1.callback(), &request1, NetLogWithSource());
+ EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+
+ // This started auto-detect; complete it.
+ ASSERT_EQ(0u, factory->pending_requests().size());
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ factory->pending_requests()[0]->script_data()->utf16());
+ factory->pending_requests()[0]->CompleteNowWithForwarder(OK, &resolver);
+
+ ASSERT_EQ(1u, resolver.pending_jobs().size());
+ EXPECT_EQ(GURL("http://www.google.com"), resolver.pending_jobs()[0]->url());
+
+ // Complete the pending request.
+ resolver.pending_jobs()[0]->results()->UseNamedProxy("request1:80");
+ resolver.pending_jobs()[0]->CompleteNow(OK);
+
+ // Verify that request ran as expected.
+ EXPECT_THAT(callback1.WaitForResult(), IsOk());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // Test that localhost and link-local URLs bypass the use of PAC script
+ // (independent of the URL scheme).
+ for (auto* host : kImplicityBypassedHosts) {
+ for (auto* scheme : kUrlSchemes) {
+ auto url = GURL(std::string(scheme) + std::string(host));
+
+ std::unique_ptr<ProxyResolutionService::Request> request;
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv =
+ service.ResolveProxy(url, std::string(), &info, callback.callback(),
+ &request, NetLogWithSource());
+ EXPECT_THAT(rv, IsOk());
+ EXPECT_TRUE(info.is_direct());
+ }
+ }
+}
+
} // namespace net
diff --git a/net/url_request/url_fetcher_impl_unittest.cc b/net/url_request/url_fetcher_impl_unittest.cc
index e3fb7b85..eb3e20c 100644
--- a/net/url_request/url_fetcher_impl_unittest.cc
+++ b/net/url_request/url_fetcher_impl_unittest.cc
@@ -511,8 +511,9 @@
context_getter->set_proxy_resolution_service(
std::move(proxy_resolution_service));
- delegate.CreateFetcher(test_server_->GetURL(kDefaultResponsePath),
- URLFetcher::GET, context_getter);
+ delegate.CreateFetcher(
+ GURL(std::string("http://does.not.resolve.test") + kDefaultResponsePath),
+ URLFetcher::GET, context_getter);
delegate.StartFetcherAndWait();
EXPECT_TRUE(delegate.fetcher()->GetStatus().is_success());
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 84baee1..00c3ede8 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -4364,14 +4364,14 @@
BlockingNetworkDelegate network_delegate(
BlockingNetworkDelegate::AUTO_CALLBACK);
network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
- GURL redirect_url(http_test_server()->GetURL("/simple.html"));
+ GURL redirect_url("http://does.not.resolve.test/simple.html");
network_delegate.set_redirect_url(redirect_url);
TestURLRequestContextWithProxy context(
http_test_server()->host_port_pair().ToString(), &network_delegate);
{
- GURL original_url(http_test_server()->GetURL("/defaultresponse"));
+ GURL original_url("http://does.not.resolve.test/defaultresponse");
std::unique_ptr<URLRequest> r(context.CreateRequest(
original_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
@@ -4418,14 +4418,14 @@
TestDelegate d;
BlockingNetworkDelegate network_delegate(
BlockingNetworkDelegate::SYNCHRONOUS);
- GURL redirect_url(http_test_server()->GetURL("/simple.html"));
+ GURL redirect_url("http://does.not.resolve.test/simple.html");
network_delegate.set_redirect_url(redirect_url);
TestURLRequestContextWithProxy context(
http_test_server()->host_port_pair().ToString(), &network_delegate);
{
- GURL original_url(http_test_server()->GetURL("/defaultresponse"));
+ GURL original_url("http://does.not.resolve.test/defaultresponse");
std::unique_ptr<URLRequest> r(context.CreateRequest(
original_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
@@ -4529,14 +4529,14 @@
BlockingNetworkDelegate network_delegate(
BlockingNetworkDelegate::AUTO_CALLBACK);
network_delegate.set_block_on(BlockingNetworkDelegate::ON_HEADERS_RECEIVED);
- GURL redirect_url(http_test_server()->GetURL("/simple.html"));
+ GURL redirect_url("http://does.not.resolve.test/simple.html");
network_delegate.set_redirect_on_headers_received_url(redirect_url);
TestURLRequestContextWithProxy context(
http_test_server()->host_port_pair().ToString(), &network_delegate);
{
- GURL original_url(http_test_server()->GetURL("/defaultresponse"));
+ GURL original_url("http://does.not.resolve.test/defaultresponse");
std::unique_ptr<URLRequest> r(context.CreateRequest(
original_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index e15dbcb6..b720996f 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -349,11 +349,15 @@
ASSERT_TRUE(wss_server.StartInBackground());
ASSERT_TRUE(proxy_server.BlockUntilStarted());
ASSERT_TRUE(wss_server.BlockUntilStarted());
- std::string proxy_config =
- "https=" + proxy_server.host_port_pair().ToString();
+ ProxyConfig proxy_config;
+ proxy_config.proxy_rules().ParseFromString(
+ "https=" + proxy_server.host_port_pair().ToString());
+ // TODO(https://crbug.com/901896): Don't rely on proxying localhost.
+ proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
+
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
- ProxyResolutionService::CreateFixed(proxy_config,
- TRAFFIC_ANNOTATION_FOR_TESTS));
+ ProxyResolutionService::CreateFixed(ProxyConfigWithAnnotation(
+ proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)));
ASSERT_TRUE(proxy_resolution_service);
context_.set_proxy_resolution_service(proxy_resolution_service.get());
EXPECT_FALSE(ConnectAndWait(wss_server.GetURL(kEchoServer)));
@@ -371,12 +375,16 @@
ASSERT_TRUE(ws_server.StartInBackground());
ASSERT_TRUE(proxy_server.BlockUntilStarted());
ASSERT_TRUE(ws_server.BlockUntilStarted());
- std::string proxy_config = "https=" +
- proxy_server.host_port_pair().ToString() + ";" +
- "http=" + proxy_server.host_port_pair().ToString();
+ ProxyConfig proxy_config;
+ proxy_config.proxy_rules().ParseFromString(
+ "https=" + proxy_server.host_port_pair().ToString() + ";" +
+ "http=" + proxy_server.host_port_pair().ToString());
+ // TODO(https://crbug.com/901896): Don't rely on proxying localhost.
+ proxy_config.proxy_rules().bypass_rules.AddRulesToSubtractImplicit();
+
std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
- ProxyResolutionService::CreateFixed(proxy_config,
- TRAFFIC_ANNOTATION_FOR_TESTS));
+ ProxyResolutionService::CreateFixed(ProxyConfigWithAnnotation(
+ proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)));
context_.set_proxy_resolution_service(proxy_resolution_service.get());
InitialiseContext();
@@ -448,8 +456,8 @@
context_.set_proxy_resolution_service(proxy_resolution_service.get());
InitialiseContext();
- // We need to use something that doesn't look like localhost, or Windows'
- // resolver will send us direct regardless of what proxy.pac says.
+ // Use a name other than localhost, since localhost implicitly bypasses the
+ // use of proxy.pac.
HostPortPair fake_ws_host_port_pair("stealth-localhost",
ws_server.host_port_pair().port());