• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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/reporting/reporting_header_parser.h"
6 
7 #include <cstring>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/check.h"
13 #include "base/feature_list.h"
14 #include "base/functional/bind.h"
15 #include "base/json/json_reader.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "net/base/features.h"
20 #include "net/base/isolation_info.h"
21 #include "net/base/network_anonymization_key.h"
22 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
23 #include "net/reporting/reporting_cache.h"
24 #include "net/reporting/reporting_context.h"
25 #include "net/reporting/reporting_delegate.h"
26 #include "net/reporting/reporting_endpoint.h"
27 
28 namespace net {
29 
30 namespace {
31 
32 const char kUrlKey[] = "url";
33 const char kIncludeSubdomainsKey[] = "include_subdomains";
34 const char kEndpointsKey[] = "endpoints";
35 const char kGroupKey[] = "group";
36 const char kDefaultGroupName[] = "default";
37 const char kMaxAgeKey[] = "max_age";
38 const char kPriorityKey[] = "priority";
39 const char kWeightKey[] = "weight";
40 
41 // Processes a single endpoint url string parsed from header.
42 //
43 // |endpoint_url_string| is the string value of the endpoint URL.
44 // |header_origin_url| is the origin URL that sent the header.
45 //
46 // |endpoint_url_out| is the endpoint URL parsed out of the string.
47 // Returns true on success or false if url was invalid.
ProcessEndpointURLString(const std::string & endpoint_url_string,const url::Origin & header_origin,GURL & endpoint_url_out)48 bool ProcessEndpointURLString(const std::string& endpoint_url_string,
49                               const url::Origin& header_origin,
50                               GURL& endpoint_url_out) {
51   // Support path-absolute-URL string with exactly one leading "/"
52   if (std::strspn(endpoint_url_string.c_str(), "/") == 1) {
53     endpoint_url_out = header_origin.GetURL().Resolve(endpoint_url_string);
54   } else {
55     endpoint_url_out = GURL(endpoint_url_string);
56   }
57   if (!endpoint_url_out.is_valid())
58     return false;
59   if (!endpoint_url_out.SchemeIsCryptographic())
60     return false;
61   return true;
62 }
63 
64 // Processes a single endpoint tuple received in a Report-To header.
65 //
66 // |origin| is the origin that sent the Report-To header.
67 //
68 // |value| is the parsed JSON value of the endpoint tuple.
69 //
70 // |*endpoint_info_out| will contain the endpoint URL parsed out of the tuple.
71 // Returns true on success or false if endpoint was discarded.
ProcessEndpoint(ReportingDelegate * delegate,const ReportingEndpointGroupKey & group_key,const base::Value & value,ReportingEndpoint::EndpointInfo * endpoint_info_out)72 bool ProcessEndpoint(ReportingDelegate* delegate,
73                      const ReportingEndpointGroupKey& group_key,
74                      const base::Value& value,
75                      ReportingEndpoint::EndpointInfo* endpoint_info_out) {
76   const base::Value::Dict* dict = value.GetIfDict();
77   if (!dict)
78     return false;
79 
80   const std::string* endpoint_url_string = dict->FindString(kUrlKey);
81   if (!endpoint_url_string)
82     return false;
83 
84   GURL endpoint_url;
85   if (!ProcessEndpointURLString(*endpoint_url_string, group_key.origin,
86                                 endpoint_url)) {
87     return false;
88   }
89   endpoint_info_out->url = std::move(endpoint_url);
90 
91   int priority = ReportingEndpoint::EndpointInfo::kDefaultPriority;
92   if (const base::Value* priority_value = dict->Find(kPriorityKey)) {
93     if (!priority_value->is_int())
94       return false;
95     priority = priority_value->GetInt();
96   }
97   if (priority < 0)
98     return false;
99   endpoint_info_out->priority = priority;
100 
101   int weight = ReportingEndpoint::EndpointInfo::kDefaultWeight;
102   if (const base::Value* weight_value = dict->Find(kWeightKey)) {
103     if (!weight_value->is_int())
104       return false;
105     weight = weight_value->GetInt();
106   }
107   if (weight < 0)
108     return false;
109   endpoint_info_out->weight = weight;
110 
111   return delegate->CanSetClient(group_key.origin, endpoint_info_out->url);
112 }
113 
114 // Processes a single endpoint group tuple received in a Report-To header.
115 //
116 // |origin| is the origin that sent the Report-To header.
117 //
118 // |value| is the parsed JSON value of the endpoint group tuple.
119 // Returns true on successfully adding a non-empty group, or false if endpoint
120 // group was discarded or processed as a deletion.
ProcessEndpointGroup(ReportingDelegate * delegate,ReportingCache * cache,const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,const base::Value & value,ReportingEndpointGroup * parsed_endpoint_group_out)121 bool ProcessEndpointGroup(
122     ReportingDelegate* delegate,
123     ReportingCache* cache,
124     const NetworkAnonymizationKey& network_anonymization_key,
125     const url::Origin& origin,
126     const base::Value& value,
127     ReportingEndpointGroup* parsed_endpoint_group_out) {
128   const base::Value::Dict* dict = value.GetIfDict();
129   if (!dict)
130     return false;
131 
132   std::string group_name = kDefaultGroupName;
133   if (const base::Value* maybe_group_name = dict->Find(kGroupKey)) {
134     if (!maybe_group_name->is_string())
135       return false;
136     group_name = maybe_group_name->GetString();
137   }
138   ReportingEndpointGroupKey group_key(network_anonymization_key, origin,
139                                       group_name);
140   parsed_endpoint_group_out->group_key = group_key;
141 
142   int ttl_sec = dict->FindInt(kMaxAgeKey).value_or(-1);
143   if (ttl_sec < 0)
144     return false;
145   // max_age: 0 signifies removal of the endpoint group.
146   if (ttl_sec == 0) {
147     cache->RemoveEndpointGroup(group_key);
148     return false;
149   }
150   parsed_endpoint_group_out->ttl = base::Seconds(ttl_sec);
151 
152   absl::optional<bool> subdomains_bool = dict->FindBool(kIncludeSubdomainsKey);
153   if (subdomains_bool && subdomains_bool.value()) {
154     // Disallow eTLDs from setting include_subdomains endpoint groups.
155     if (registry_controlled_domains::GetRegistryLength(
156             origin.GetURL(),
157             registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
158             registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) == 0) {
159       return false;
160     }
161 
162     parsed_endpoint_group_out->include_subdomains = OriginSubdomains::INCLUDE;
163   }
164 
165   const base::Value::List* endpoint_list = dict->FindList(kEndpointsKey);
166   if (!endpoint_list)
167     return false;
168 
169   std::vector<ReportingEndpoint::EndpointInfo> endpoints;
170 
171   for (const base::Value& endpoint : *endpoint_list) {
172     ReportingEndpoint::EndpointInfo parsed_endpoint;
173     if (ProcessEndpoint(delegate, group_key, endpoint, &parsed_endpoint))
174       endpoints.push_back(std::move(parsed_endpoint));
175   }
176 
177   // Remove the group if it is empty.
178   if (endpoints.empty()) {
179     cache->RemoveEndpointGroup(group_key);
180     return false;
181   }
182 
183   parsed_endpoint_group_out->endpoints = std::move(endpoints);
184 
185   return true;
186 }
187 
188 // Processes a single endpoint tuple received in a Reporting-Endpoints header.
189 //
190 // |group_key| is the key for the endpoint group this endpoint belongs.
191 // |endpoint_url_string| is the endpoint url as received in the header.
192 //
193 // |endpoint_info_out| is the endpoint info parsed out of the value.
ProcessEndpoint(ReportingDelegate * delegate,const ReportingEndpointGroupKey & group_key,const std::string & endpoint_url_string,ReportingEndpoint::EndpointInfo & endpoint_info_out)194 bool ProcessEndpoint(ReportingDelegate* delegate,
195                      const ReportingEndpointGroupKey& group_key,
196                      const std::string& endpoint_url_string,
197                      ReportingEndpoint::EndpointInfo& endpoint_info_out) {
198   if (endpoint_url_string.empty())
199     return false;
200 
201   GURL endpoint_url;
202   if (!ProcessEndpointURLString(endpoint_url_string, group_key.origin,
203                                 endpoint_url)) {
204     return false;
205   }
206   endpoint_info_out.url = std::move(endpoint_url);
207   // Reporting-Endpoints endpoint doesn't have prioirty/weight so set to
208   // default.
209   endpoint_info_out.priority =
210       ReportingEndpoint::EndpointInfo::kDefaultPriority;
211   endpoint_info_out.weight = ReportingEndpoint::EndpointInfo::kDefaultWeight;
212 
213   return delegate->CanSetClient(group_key.origin, endpoint_info_out.url);
214 }
215 
216 // Process a single endpoint received in a Reporting-Endpoints header.
ProcessV1Endpoint(ReportingDelegate * delegate,ReportingCache * cache,const base::UnguessableToken & reporting_source,const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,const std::string & endpoint_name,const std::string & endpoint_url_string,ReportingEndpoint & parsed_endpoint_out)217 bool ProcessV1Endpoint(ReportingDelegate* delegate,
218                        ReportingCache* cache,
219                        const base::UnguessableToken& reporting_source,
220                        const NetworkAnonymizationKey& network_anonymization_key,
221                        const url::Origin& origin,
222                        const std::string& endpoint_name,
223                        const std::string& endpoint_url_string,
224                        ReportingEndpoint& parsed_endpoint_out) {
225   DCHECK(!reporting_source.is_empty());
226   ReportingEndpointGroupKey group_key(network_anonymization_key,
227                                       reporting_source, origin, endpoint_name);
228   parsed_endpoint_out.group_key = group_key;
229 
230   ReportingEndpoint::EndpointInfo parsed_endpoint;
231 
232   if (!ProcessEndpoint(delegate, group_key, endpoint_url_string,
233                        parsed_endpoint)) {
234     return false;
235   }
236   parsed_endpoint_out.info = std::move(parsed_endpoint);
237   return true;
238 }
239 
240 }  // namespace
241 
242 absl::optional<base::flat_map<std::string, std::string>>
ParseReportingEndpoints(const std::string & header)243 ParseReportingEndpoints(const std::string& header) {
244   // Ignore empty header values. Skip logging metric to maintain parity with
245   // ReportingHeaderType::kReportToInvalid.
246   if (header.empty())
247     return absl::nullopt;
248   absl::optional<structured_headers::Dictionary> header_dict =
249       structured_headers::ParseDictionary(header);
250   if (!header_dict) {
251     ReportingHeaderParser::RecordReportingHeaderType(
252         ReportingHeaderParser::ReportingHeaderType::kReportingEndpointsInvalid);
253     return absl::nullopt;
254   }
255   base::flat_map<std::string, std::string> parsed_header;
256   for (const structured_headers::DictionaryMember& entry : *header_dict) {
257     if (entry.second.member_is_inner_list ||
258         !entry.second.member.front().item.is_string()) {
259       ReportingHeaderParser::RecordReportingHeaderType(
260           ReportingHeaderParser::ReportingHeaderType::
261               kReportingEndpointsInvalid);
262       return absl::nullopt;
263     }
264     const std::string& endpoint_url_string =
265         entry.second.member.front().item.GetString();
266     parsed_header[entry.first] = endpoint_url_string;
267   }
268   return parsed_header;
269 }
270 
271 // static
RecordReportingHeaderType(ReportingHeaderType header_type)272 void ReportingHeaderParser::RecordReportingHeaderType(
273     ReportingHeaderType header_type) {
274   base::UmaHistogramEnumeration("Net.Reporting.HeaderType", header_type);
275 }
276 
277 // static
ParseReportToHeader(ReportingContext * context,const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,const base::Value::List & list)278 void ReportingHeaderParser::ParseReportToHeader(
279     ReportingContext* context,
280     const NetworkAnonymizationKey& network_anonymization_key,
281     const url::Origin& origin,
282     const base::Value::List& list) {
283   DCHECK(GURL::SchemeIsCryptographic(origin.scheme()));
284 
285   ReportingDelegate* delegate = context->delegate();
286   ReportingCache* cache = context->cache();
287 
288   std::vector<ReportingEndpointGroup> parsed_header;
289 
290   for (const auto& group_value : list) {
291     ReportingEndpointGroup parsed_endpoint_group;
292     if (ProcessEndpointGroup(delegate, cache, network_anonymization_key, origin,
293                              group_value, &parsed_endpoint_group)) {
294       parsed_header.push_back(std::move(parsed_endpoint_group));
295     }
296   }
297 
298   if (parsed_header.empty() && list.size() > 0) {
299     RecordReportingHeaderType(ReportingHeaderType::kReportToInvalid);
300   }
301 
302   // Remove the client if it has no valid endpoint groups.
303   if (parsed_header.empty()) {
304     cache->RemoveClient(network_anonymization_key, origin);
305     return;
306   }
307 
308   RecordReportingHeaderType(ReportingHeaderType::kReportTo);
309 
310   cache->OnParsedHeader(network_anonymization_key, origin,
311                         std::move(parsed_header));
312 }
313 
314 // static
ProcessParsedReportingEndpointsHeader(ReportingContext * context,const base::UnguessableToken & reporting_source,const IsolationInfo & isolation_info,const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,base::flat_map<std::string,std::string> header)315 void ReportingHeaderParser::ProcessParsedReportingEndpointsHeader(
316     ReportingContext* context,
317     const base::UnguessableToken& reporting_source,
318     const IsolationInfo& isolation_info,
319     const NetworkAnonymizationKey& network_anonymization_key,
320     const url::Origin& origin,
321     base::flat_map<std::string, std::string> header) {
322   DCHECK(base::FeatureList::IsEnabled(net::features::kDocumentReporting));
323   DCHECK(GURL::SchemeIsCryptographic(origin.scheme()));
324   DCHECK(!reporting_source.is_empty());
325   DCHECK(network_anonymization_key.IsEmpty() ||
326          network_anonymization_key ==
327              isolation_info.network_anonymization_key());
328 
329   ReportingDelegate* delegate = context->delegate();
330   ReportingCache* cache = context->cache();
331 
332   std::vector<ReportingEndpoint> parsed_header;
333 
334   for (const auto& member : header) {
335     ReportingEndpoint parsed_endpoint;
336     if (ProcessV1Endpoint(delegate, cache, reporting_source,
337                           network_anonymization_key, origin, member.first,
338                           member.second, parsed_endpoint)) {
339       parsed_header.push_back(std::move(parsed_endpoint));
340     }
341   }
342 
343   if (parsed_header.empty()) {
344     RecordReportingHeaderType(ReportingHeaderType::kReportingEndpointsInvalid);
345     return;
346   }
347 
348   RecordReportingHeaderType(ReportingHeaderType::kReportingEndpoints);
349   cache->OnParsedReportingEndpointsHeader(reporting_source, isolation_info,
350                                           std::move(parsed_header));
351 }
352 
353 }  // namespace net
354