// Copyright (c) 2011 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/proxy_bypass_rules.h" #include "base/stl_util-inl.h" #include "base/string_number_conversions.h" #include "base/string_tokenizer.h" #include "base/string_util.h" #include "net/base/net_util.h" namespace net { namespace { class HostnamePatternRule : public ProxyBypassRules::Rule { public: HostnamePatternRule(const std::string& optional_scheme, const std::string& hostname_pattern, int optional_port) : optional_scheme_(StringToLowerASCII(optional_scheme)), hostname_pattern_(StringToLowerASCII(hostname_pattern)), optional_port_(optional_port) { } virtual bool Matches(const GURL& url) const { if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) return false; // Didn't match port expectation. if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) return false; // Didn't match scheme expectation. // Note it is necessary to lower-case the host, since GURL uses capital // letters for percent-escaped characters. return MatchPattern(StringToLowerASCII(url.host()), hostname_pattern_); } virtual std::string ToString() const { std::string str; if (!optional_scheme_.empty()) base::StringAppendF(&str, "%s://", optional_scheme_.c_str()); str += hostname_pattern_; if (optional_port_ != -1) base::StringAppendF(&str, ":%d", optional_port_); return str; } virtual Rule* Clone() const { return new HostnamePatternRule(optional_scheme_, hostname_pattern_, optional_port_); } private: const std::string optional_scheme_; const std::string hostname_pattern_; const int optional_port_; }; class BypassLocalRule : public ProxyBypassRules::Rule { public: virtual bool Matches(const GURL& url) const { const std::string& host = url.host(); if (host == "127.0.0.1" || host == "[::1]") return true; return host.find('.') == std::string::npos; } virtual std::string ToString() const { return "<local>"; } virtual Rule* Clone() const { return new BypassLocalRule(); } }; // 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 { public: // |ip_prefix| + |prefix_length| define the IP block to match. BypassIPBlockRule(const std::string& description, const std::string& optional_scheme, const IPAddressNumber& 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) { } virtual bool Matches(const GURL& url) const { if (!url.HostIsIPAddress()) return false; if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) return false; // Didn't match scheme expectation. // Parse the input IP literal to a number. IPAddressNumber ip_number; if (!ParseIPLiteralToNumber(url.HostNoBrackets(), &ip_number)) return false; // Test if it has the expected prefix. return IPNumberMatchesPrefix(ip_number, ip_prefix_, prefix_length_in_bits_); } virtual std::string ToString() const { return description_; } virtual Rule* Clone() const { return new BypassIPBlockRule(description_, optional_scheme_, ip_prefix_, prefix_length_in_bits_); } private: const std::string description_; const std::string optional_scheme_; const IPAddressNumber ip_prefix_; const size_t prefix_length_in_bits_; }; // Returns true if the given string represents an IP address. bool IsIPAddress(const std::string& domain) { // From GURL::HostIsIPAddress() url_canon::RawCanonOutputT<char, 128> ignored_output; url_canon::CanonHostInfo host_info; url_parse::Component domain_comp(0, domain.size()); url_canon::CanonicalizeIPAddress(domain.c_str(), domain_comp, &ignored_output, &host_info); return host_info.IsIPAddress(); } } // namespace ProxyBypassRules::Rule::Rule() { } ProxyBypassRules::Rule::~Rule() { } bool ProxyBypassRules::Rule::Equals(const Rule& rule) const { return ToString() == rule.ToString(); } ProxyBypassRules::ProxyBypassRules() { } ProxyBypassRules::ProxyBypassRules(const ProxyBypassRules& rhs) { AssignFrom(rhs); } ProxyBypassRules::~ProxyBypassRules() { Clear(); } ProxyBypassRules& ProxyBypassRules::operator=(const ProxyBypassRules& rhs) { AssignFrom(rhs); return *this; } bool ProxyBypassRules::Matches(const GURL& url) const { for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); ++it) { if ((*it)->Matches(url)) return true; } return false; } bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const { if (rules_.size() != other.rules_.size()) return false; for (size_t i = 0; i < rules_.size(); ++i) { if (!rules_[i]->Equals(*other.rules_[i])) return false; } 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) { if (hostname_pattern.empty()) return false; rules_.push_back(new HostnamePatternRule(optional_scheme, hostname_pattern, optional_port)); return true; } void ProxyBypassRules::AddRuleToBypassLocal() { rules_.push_back(new BypassLocalRule); } bool ProxyBypassRules::AddRuleFromString(const std::string& raw) { return AddRuleFromStringInternalWithLogging(raw, false); } bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching( const std::string& raw) { return AddRuleFromStringInternalWithLogging(raw, true); } std::string ProxyBypassRules::ToString() const { std::string result; for (RuleList::const_iterator rule(rules_.begin()); rule != rules_.end(); ++rule) { result += (*rule)->ToString(); result += ";"; } return result; } void ProxyBypassRules::Clear() { STLDeleteElements(&rules_); } void ProxyBypassRules::AssignFrom(const ProxyBypassRules& other) { Clear(); // Make a copy of the rules list. for (RuleList::const_iterator it = other.rules_.begin(); it != other.rules_.end(); ++it) { rules_.push_back((*it)->Clone()); } } void ProxyBypassRules::ParseFromStringInternal( const std::string& raw, bool use_hostname_suffix_matching) { Clear(); 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; TrimWhitespaceASCII(raw_untrimmed, 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 (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) { IPAddressNumber ip_prefix; size_t prefix_length_in_bits; if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits)) return false; rules_.push_back( new 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)) { if (IsIPAddress(host)) { // Canonicalize the IP literal before adding it as a string pattern. GURL tmp_url("http://" + host); return AddRuleForHostname(scheme, tmp_url.host(), port); } } // Otherwise assume we have <hostname-pattern>[:port]. std::string::size_type pos_colon = raw.rfind(':'); host = raw; port = -1; if (pos_colon != std::string::npos) { if (!base::StringToInt(raw.begin() + pos_colon + 1, raw.end(), &port) || (port < 0 || 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 (StartsWithASCII(raw, ".", false)) raw = "*" + raw; // If suffix matching was asked for, make sure the pattern starts with a // wildcard. if (use_hostname_suffix_matching && !StartsWithASCII(raw, "*", false)) 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); } } // namespace net