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 "base/trace_event/memory_usage_estimator.h"
12 #include "net/base/host_port_pair.h"
13 #include "net/base/parse_number.h"
14 #include "net/base/url_util.h"
15 #include "url/url_util.h"
16
17 namespace net {
18
19 namespace {
20
AddBracketsIfIPv6(const IPAddress & ip_address)21 std::string AddBracketsIfIPv6(const IPAddress& ip_address) {
22 std::string ip_host = ip_address.ToString();
23 if (ip_address.IsIPv6())
24 return base::StringPrintf("[%s]", ip_host.c_str());
25 return ip_host;
26 }
27
28 } // namespace
29
30 // static
31 std::unique_ptr<SchemeHostPortMatcherRule>
FromUntrimmedRawString(std::string_view raw_untrimmed)32 SchemeHostPortMatcherRule::FromUntrimmedRawString(
33 std::string_view raw_untrimmed) {
34 std::string_view raw =
35 base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL);
36
37 // Extract any scheme-restriction.
38 std::string::size_type scheme_pos = raw.find("://");
39 std::string scheme;
40 if (scheme_pos != std::string::npos) {
41 scheme = std::string(raw.substr(0, scheme_pos));
42 raw = raw.substr(scheme_pos + 3);
43 if (scheme.empty())
44 return nullptr;
45 }
46
47 if (raw.empty())
48 return nullptr;
49
50 // If there is a forward slash in the input, it is probably a CIDR style
51 // mask.
52 if (raw.find('/') != std::string::npos) {
53 IPAddress ip_prefix;
54 size_t prefix_length_in_bits;
55
56 if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits))
57 return nullptr;
58
59 return std::make_unique<SchemeHostPortMatcherIPBlockRule>(
60 std::string(raw), scheme, ip_prefix, prefix_length_in_bits);
61 }
62
63 // Check if we have an <ip-address>[:port] input. We need to treat this
64 // separately since the IP literal may not be in a canonical form.
65 std::string host;
66 int port;
67 if (ParseHostAndPort(raw, &host, &port)) {
68 IPAddress ip_address;
69 if (ip_address.AssignFromIPLiteral(host)) {
70 // Instead of -1, 0 is invalid for IPEndPoint.
71 int adjusted_port = port == -1 ? 0 : port;
72 return std::make_unique<SchemeHostPortMatcherIPHostRule>(
73 scheme, IPEndPoint(ip_address, adjusted_port));
74 }
75 }
76
77 // Otherwise assume we have <hostname-pattern>[:port].
78 std::string::size_type pos_colon = raw.rfind(':');
79 port = -1;
80 if (pos_colon != std::string::npos) {
81 if (!ParseInt32(
82 base::MakeStringPiece(raw.begin() + pos_colon + 1, raw.end()),
83 ParseIntFormat::NON_NEGATIVE, &port) ||
84 port > 0xFFFF) {
85 return nullptr; // Port was invalid.
86 }
87 raw = raw.substr(0, pos_colon);
88 }
89
90 // Special-case hostnames that begin with a period.
91 // For example, we remap ".google.com" --> "*.google.com".
92 std::string hostname_pattern;
93 if (raw.starts_with(".")) {
94 hostname_pattern = base::StrCat({"*", raw});
95 } else {
96 hostname_pattern = std::string(raw);
97 }
98
99 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
100 scheme, hostname_pattern, port);
101 }
102
IsHostnamePatternRule() const103 bool SchemeHostPortMatcherRule::IsHostnamePatternRule() const {
104 return false;
105 }
106
107 #if !BUILDFLAG(CRONET_BUILD)
EstimateMemoryUsage() const108 size_t SchemeHostPortMatcherRule::EstimateMemoryUsage() const {
109 return 0;
110 }
111 #endif // !BUILDFLAG(CRONET_BUILD)
112
113 SchemeHostPortMatcherHostnamePatternRule::
SchemeHostPortMatcherHostnamePatternRule(const std::string & optional_scheme,const std::string & hostname_pattern,int optional_port)114 SchemeHostPortMatcherHostnamePatternRule(
115 const std::string& optional_scheme,
116 const std::string& hostname_pattern,
117 int optional_port)
118 : optional_scheme_(base::ToLowerASCII(optional_scheme)),
119 hostname_pattern_(base::ToLowerASCII(hostname_pattern)),
120 optional_port_(optional_port) {
121 // |hostname_pattern| shouldn't be an IP address.
122 DCHECK(!url::HostIsIPAddress(hostname_pattern));
123 }
124
Evaluate(const GURL & url) const125 SchemeHostPortMatcherResult SchemeHostPortMatcherHostnamePatternRule::Evaluate(
126 const GURL& url) const {
127 if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) {
128 // Didn't match port expectation.
129 return SchemeHostPortMatcherResult::kNoMatch;
130 }
131
132 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
133 // Didn't match scheme expectation.
134 return SchemeHostPortMatcherResult::kNoMatch;
135 }
136
137 // Note it is necessary to lower-case the host, since GURL uses capital
138 // letters for percent-escaped characters.
139 return base::MatchPattern(url.host(), hostname_pattern_)
140 ? SchemeHostPortMatcherResult::kInclude
141 : SchemeHostPortMatcherResult::kNoMatch;
142 }
143
ToString() const144 std::string SchemeHostPortMatcherHostnamePatternRule::ToString() const {
145 std::string str;
146 if (!optional_scheme_.empty())
147 base::StringAppendF(&str, "%s://", optional_scheme_.c_str());
148 str += hostname_pattern_;
149 if (optional_port_ != -1)
150 base::StringAppendF(&str, ":%d", optional_port_);
151 return str;
152 }
153
IsHostnamePatternRule() const154 bool SchemeHostPortMatcherHostnamePatternRule::IsHostnamePatternRule() const {
155 return true;
156 }
157
158 std::unique_ptr<SchemeHostPortMatcherHostnamePatternRule>
GenerateSuffixMatchingRule() const159 SchemeHostPortMatcherHostnamePatternRule::GenerateSuffixMatchingRule() const {
160 if (!hostname_pattern_.starts_with("*")) {
161 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
162 optional_scheme_, "*" + hostname_pattern_, optional_port_);
163 }
164 // return a new SchemeHostPortMatcherHostNamePatternRule with the same data.
165 return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
166 optional_scheme_, hostname_pattern_, optional_port_);
167 }
168
169 #if !BUILDFLAG(CRONET_BUILD)
EstimateMemoryUsage() const170 size_t SchemeHostPortMatcherHostnamePatternRule::EstimateMemoryUsage() const {
171 return base::trace_event::EstimateMemoryUsage(optional_scheme_) +
172 base::trace_event::EstimateMemoryUsage(hostname_pattern_);
173 }
174 #endif // !BUILDFLAG(CRONET_BUILD)
175
SchemeHostPortMatcherIPHostRule(const std::string & optional_scheme,const IPEndPoint & ip_end_point)176 SchemeHostPortMatcherIPHostRule::SchemeHostPortMatcherIPHostRule(
177 const std::string& optional_scheme,
178 const IPEndPoint& ip_end_point)
179 : optional_scheme_(base::ToLowerASCII(optional_scheme)),
180 ip_host_(AddBracketsIfIPv6(ip_end_point.address())),
181 optional_port_(ip_end_point.port()) {}
182
Evaluate(const GURL & url) const183 SchemeHostPortMatcherResult SchemeHostPortMatcherIPHostRule::Evaluate(
184 const GURL& url) const {
185 if (optional_port_ != 0 && url.EffectiveIntPort() != optional_port_) {
186 // Didn't match port expectation.
187 return SchemeHostPortMatcherResult::kNoMatch;
188 }
189
190 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
191 // Didn't match scheme expectation.
192 return SchemeHostPortMatcherResult::kNoMatch;
193 }
194
195 // Note it is necessary to lower-case the host, since GURL uses capital
196 // letters for percent-escaped characters.
197 return base::MatchPattern(url.host(), ip_host_)
198 ? SchemeHostPortMatcherResult::kInclude
199 : SchemeHostPortMatcherResult::kNoMatch;
200 }
201
ToString() const202 std::string SchemeHostPortMatcherIPHostRule::ToString() const {
203 std::string str;
204 if (!optional_scheme_.empty())
205 base::StringAppendF(&str, "%s://", optional_scheme_.c_str());
206 str += ip_host_;
207 if (optional_port_ != 0)
208 base::StringAppendF(&str, ":%d", optional_port_);
209 return str;
210 }
211
212 #if !BUILDFLAG(CRONET_BUILD)
EstimateMemoryUsage() const213 size_t SchemeHostPortMatcherIPHostRule::EstimateMemoryUsage() const {
214 return base::trace_event::EstimateMemoryUsage(optional_scheme_) +
215 base::trace_event::EstimateMemoryUsage(ip_host_);
216 }
217 #endif // !BUILDFLAG(CRONET_BUILD)
218
SchemeHostPortMatcherIPBlockRule(const std::string & description,const std::string & optional_scheme,const IPAddress & ip_prefix,size_t prefix_length_in_bits)219 SchemeHostPortMatcherIPBlockRule::SchemeHostPortMatcherIPBlockRule(
220 const std::string& description,
221 const std::string& optional_scheme,
222 const IPAddress& ip_prefix,
223 size_t prefix_length_in_bits)
224 : description_(description),
225 optional_scheme_(optional_scheme),
226 ip_prefix_(ip_prefix),
227 prefix_length_in_bits_(prefix_length_in_bits) {}
228
Evaluate(const GURL & url) const229 SchemeHostPortMatcherResult SchemeHostPortMatcherIPBlockRule::Evaluate(
230 const GURL& url) const {
231 if (!url.HostIsIPAddress())
232 return SchemeHostPortMatcherResult::kNoMatch;
233
234 if (!optional_scheme_.empty() && url.scheme() != optional_scheme_) {
235 // Didn't match scheme expectation.
236 return SchemeHostPortMatcherResult::kNoMatch;
237 }
238
239 // Parse the input IP literal to a number.
240 IPAddress ip_address;
241 if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece()))
242 return SchemeHostPortMatcherResult::kNoMatch;
243
244 // Test if it has the expected prefix.
245 return IPAddressMatchesPrefix(ip_address, ip_prefix_, prefix_length_in_bits_)
246 ? SchemeHostPortMatcherResult::kInclude
247 : SchemeHostPortMatcherResult::kNoMatch;
248 }
249
ToString() const250 std::string SchemeHostPortMatcherIPBlockRule::ToString() const {
251 return description_;
252 }
253
254 #if !BUILDFLAG(CRONET_BUILD)
EstimateMemoryUsage() const255 size_t SchemeHostPortMatcherIPBlockRule::EstimateMemoryUsage() const {
256 return base::trace_event::EstimateMemoryUsage(description_) +
257 base::trace_event::EstimateMemoryUsage(optional_scheme_) +
258 base::trace_event::EstimateMemoryUsage(ip_prefix_);
259 }
260 #endif // !BUILDFLAG(CRONET_BUILD)
261
262 } // namespace net
263