text: // 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.h"
  #include "base/strings/pattern.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_piece.h"
  #include "base/strings/string_tokenizer.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "net/base/host_port_pair.h"
  #include "net/base/ip_address_number.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_(base::ToLowerASCII(optional_scheme)),
          hostname_pattern_(base::ToLowerASCII(hostname_pattern)),
          optional_port_(optional_port) {}
  
    bool Matches(const GURL& url) const override {
      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 base::MatchPattern(url.host(), hostname_pattern_);
    }
  
    std::string ToString() const override {
      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;
    }
  
    Rule* Clone() const override {
      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:
    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;
    }
  
    std::string ToString() const override { return "<local>"; }
  
    Rule* Clone() const override { 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) {
    }
  
    bool Matches(const GURL& url) const override {
      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_);
    }
  
    std::string ToString() const override { return description_; }
  
    Rule* Clone() const override {
      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.
  // IPv6 addresses are expected to be bracketed.
  bool IsIPAddress(const std::string& domain) {
    // From GURL::HostIsIPAddress()
    url::RawCanonOutputT<char, 128> ignored_output;
    url::CanonHostInfo host_info;
    url::Component domain_comp(0, domain.size());
    url::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();
  
    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) {
      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)) {
      // 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(':');
    host = raw;
    port = -1;
    if (pos_colon != std::string::npos) {
      if (!base::StringToInt(base::StringPiece(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 (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);
  }
  
  }  // namespace net
  