1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/base/scheme_host_port_matcher_rule.h"
6
7 #include "base/strings/pattern.h"
8 #include "base/strings/strcat.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "net/base/host_port_pair.h"
12 #include "net/base/parse_number.h"
13 #include "net/base/url_util.h"
14 #include "url/url_util.h"
15
16 namespace net {
17
18 namespace {
19
AddBracketsIfIPv6(const IPAddress & ip_address)20 std::string AddBracketsIfIPv6(const IPAddress& ip_address) {
21 std::string ip_host = ip_address.ToString();
22 if (ip_address.IsIPv6())
23 return base::StringPrintf("[%s]", ip_host.c_str());
24 return ip_host;
25 }
26
27 } // namespace
28
29 // static
30 std::unique_ptr<SchemeHostPortMatcherRule>
FromUntrimmedRawString(base::StringPiece raw_untrimmed)31 SchemeHostPortMatcherRule::FromUntrimmedRawString(
32 base::StringPiece raw_untrimmed) {
33 base::StringPiece raw =
34 base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL);
35
36 // Extract any scheme-restriction.
37 std::string::size_type scheme_pos = raw.find("://");
38 std::string scheme;
39 if (scheme_pos != std::string::npos) {
40 scheme = std::string(raw.substr(0, scheme_pos));
41 raw = raw.substr(scheme_pos + 3);
42 if (scheme.empty())
43 return nullptr;
44 }
45
46 if (raw.empty())
47 return nullptr;
48
49 // If there is a forward slash in the input, it is probably a CIDR style
50 // mask.
51 if (raw.find('/') != std::string::npos) {
52 IPAddress ip_prefix;
53 size_t prefix_length_in_bits;
54
55 if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits))
56 return nullptr;
57
58 return std::make_unique<SchemeHostPortMatcherIPBlockRule>(
59 std::string(raw), scheme, ip_prefix, prefix_length_in_bits);
60 }
61
62 // Check if we have an <ip-address>[:port] input. We need to treat this
63 // separately since the IP literal may not be in a canonical form.
64 std::string host;
65 int port;
66 if (ParseHostAndPort(raw, &host, &port)) {
67 IPAddress ip_address;
68 if (ip_address.AssignFromIPLiteral(host)) {
69 // Instead of -1, 0 is invalid for IPEndPoint.
70 int adjusted_port = port == -1 ? 0 : port;
71 return std::make_unique<SchemeHostPortMatcherIPHostRule>(
72 scheme, IPEndPoint(ip_address, adjusted_port));
73 }
74 }
75
76 // Otherwise assume we have <hostname-pattern>[:port].
77 std::string::size_type pos_colon = raw.rfind(':');
78 port = -1;
79 if (pos_colon != std::string::npos) {
80 if (!ParseInt32(
81 base::MakeStringPiece(raw.begin() + pos_colon + 1, raw.end()),
82 ParseIntFormat::NON_NEGATIVE, &port) ||
83 port > 0xFFFF) {
84 return nullptr; // Port was invalid.
85 }
86 raw = raw.substr(0, pos_colon);
87 }
88
89 // Special-case hostnames that begin with a period.
90 // For example, we remap ".google.com" --> "*.google.com".
91 std::string hostname_pattern;
92 if (base::StartsWith(raw, ".", base::CompareCase::SENSITIVE)) {
93 hostname_pattern = base::StrCat({"*", raw});
94 } else {
95 hostname_pattern = std::string(raw);
96 }
97
98 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
99 scheme, hostname_pattern, port);
100 }
101
IsHostnamePatternRule() const102 bool SchemeHostPortMatcherRule::IsHostnamePatternRule() const {
103 return false;
104 }
105
106 SchemeHostPortMatcherHostnamePatternRule::
SchemeHostPortMatcherHostnamePatternRule(const std::string & optional_scheme,const std::string & hostname_pattern,int optional_port)107 SchemeHostPortMatcherHostnamePatternRule(
108 const std::string& optional_scheme,
109 const std::string& hostname_pattern,
110 int optional_port)
111 : optional_scheme_(base::ToLowerASCII(optional_scheme)),
112 hostname_pattern_(base::ToLowerASCII(hostname_pattern)),
113 optional_port_(optional_port) {
114 // |hostname_pattern| shouldn't be an IP address.
115 DCHECK(!url::HostIsIPAddress(hostname_pattern));
116 }
117
Evaluate(const GURL & url) const118 SchemeHostPortMatcherResult SchemeHostPortMatcherHostnamePatternRule::Evaluate(
119 const GURL& url) const {
120 if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) {
121 // Didn't match port expectation.
122 return SchemeHostPortMatcherResult::kNoMatch;
123 }
124
125 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
126 // Didn't match scheme expectation.
127 return SchemeHostPortMatcherResult::kNoMatch;
128 }
129
130 // Note it is necessary to lower-case the host, since GURL uses capital
131 // letters for percent-escaped characters.
132 return base::MatchPattern(url.host(), hostname_pattern_)
133 ? SchemeHostPortMatcherResult::kInclude
134 : SchemeHostPortMatcherResult::kNoMatch;
135 }
136
ToString() const137 std::string SchemeHostPortMatcherHostnamePatternRule::ToString() const {
138 std::string str;
139 if (!optional_scheme_.empty())
140 base::StringAppendF(&str, "%s://", optional_scheme_.c_str());
141 str += hostname_pattern_;
142 if (optional_port_ != -1)
143 base::StringAppendF(&str, ":%d", optional_port_);
144 return str;
145 }
146
IsHostnamePatternRule() const147 bool SchemeHostPortMatcherHostnamePatternRule::IsHostnamePatternRule() const {
148 return true;
149 }
150
151 std::unique_ptr<SchemeHostPortMatcherHostnamePatternRule>
GenerateSuffixMatchingRule() const152 SchemeHostPortMatcherHostnamePatternRule::GenerateSuffixMatchingRule() const {
153 if (!base::StartsWith(hostname_pattern_, "*", base::CompareCase::SENSITIVE)) {
154 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
155 optional_scheme_, "*" + hostname_pattern_, optional_port_);
156 }
157 // return a new SchemeHostPortMatcherHostNamePatternRule with the same data.
158 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
159 optional_scheme_, hostname_pattern_, optional_port_);
160 }
161
SchemeHostPortMatcherIPHostRule(const std::string & optional_scheme,const IPEndPoint & ip_end_point)162 SchemeHostPortMatcherIPHostRule::SchemeHostPortMatcherIPHostRule(
163 const std::string& optional_scheme,
164 const IPEndPoint& ip_end_point)
165 : optional_scheme_(base::ToLowerASCII(optional_scheme)),
166 ip_host_(AddBracketsIfIPv6(ip_end_point.address())),
167 optional_port_(ip_end_point.port()) {}
168
Evaluate(const GURL & url) const169 SchemeHostPortMatcherResult SchemeHostPortMatcherIPHostRule::Evaluate(
170 const GURL& url) const {
171 if (optional_port_ != 0 && url.EffectiveIntPort() != optional_port_) {
172 // Didn't match port expectation.
173 return SchemeHostPortMatcherResult::kNoMatch;
174 }
175
176 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
177 // Didn't match scheme expectation.
178 return SchemeHostPortMatcherResult::kNoMatch;
179 }
180
181 // Note it is necessary to lower-case the host, since GURL uses capital
182 // letters for percent-escaped characters.
183 return base::MatchPattern(url.host(), ip_host_)
184 ? SchemeHostPortMatcherResult::kInclude
185 : SchemeHostPortMatcherResult::kNoMatch;
186 }
187
ToString() const188 std::string SchemeHostPortMatcherIPHostRule::ToString() const {
189 std::string str;
190 if (!optional_scheme_.empty())
191 base::StringAppendF(&str, "%s://", optional_scheme_.c_str());
192 str += ip_host_;
193 if (optional_port_ != 0)
194 base::StringAppendF(&str, ":%d", optional_port_);
195 return str;
196 }
197
SchemeHostPortMatcherIPBlockRule(const std::string & description,const std::string & optional_scheme,const IPAddress & ip_prefix,size_t prefix_length_in_bits)198 SchemeHostPortMatcherIPBlockRule::SchemeHostPortMatcherIPBlockRule(
199 const std::string& description,
200 const std::string& optional_scheme,
201 const IPAddress& ip_prefix,
202 size_t prefix_length_in_bits)
203 : description_(description),
204 optional_scheme_(optional_scheme),
205 ip_prefix_(ip_prefix),
206 prefix_length_in_bits_(prefix_length_in_bits) {}
207
Evaluate(const GURL & url) const208 SchemeHostPortMatcherResult SchemeHostPortMatcherIPBlockRule::Evaluate(
209 const GURL& url) const {
210 if (!url.HostIsIPAddress())
211 return SchemeHostPortMatcherResult::kNoMatch;
212
213 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
214 // Didn't match scheme expectation.
215 return SchemeHostPortMatcherResult::kNoMatch;
216 }
217
218 // Parse the input IP literal to a number.
219 IPAddress ip_address;
220 if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece()))
221 return SchemeHostPortMatcherResult::kNoMatch;
222
223 // Test if it has the expected prefix.
224 return IPAddressMatchesPrefix(ip_address, ip_prefix_, prefix_length_in_bits_)
225 ? SchemeHostPortMatcherResult::kInclude
226 : SchemeHostPortMatcherResult::kNoMatch;
227 }
228
ToString() const229 std::string SchemeHostPortMatcherIPBlockRule::ToString() const {
230 return description_;
231 }
232
233 } // namespace net
234