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