blob: 19a407ac41afafca735459f5a6d70eb713392182 [file] [log] [blame]
juliatuttle1690bc62017-03-29 17:16:021// Copyright 2017 The Chromium Authors. All rights reserved.
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 <string>
Lily Chenefb6fcf2019-04-19 04:17:548#include <utility>
9#include <vector>
juliatuttle1690bc62017-03-29 17:16:0210
Julia Tuttleec467a5f2018-02-22 20:22:4511#include "base/bind.h"
juliatuttle1690bc62017-03-29 17:16:0212#include "base/json/json_reader.h"
13#include "base/logging.h"
juliatuttle667c0bb2017-07-06 15:17:1314#include "base/metrics/histogram_macros.h"
juliatuttle1690bc62017-03-29 17:16:0215#include "base/time/time.h"
16#include "base/values.h"
Lily Chena5b78cff2019-07-19 22:10:5217#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
juliatuttle1690bc62017-03-29 17:16:0218#include "net/reporting/reporting_cache.h"
juliatuttleee4b55e2017-04-07 17:09:4519#include "net/reporting/reporting_context.h"
juliatuttle587548912017-05-23 14:17:2120#include "net/reporting/reporting_delegate.h"
Lily Chenfc92ff42019-05-06 22:59:1021#include "net/reporting/reporting_endpoint.h"
juliatuttle1690bc62017-03-29 17:16:0222
23namespace net {
24
25namespace {
26
Douglas Creager134b52e2018-11-09 18:00:1427using HeaderEndpointGroupOutcome =
28 ReportingHeaderParser::HeaderEndpointGroupOutcome;
29using HeaderEndpointOutcome = ReportingHeaderParser::HeaderEndpointOutcome;
30using HeaderOutcome = ReportingHeaderParser::HeaderOutcome;
juliatuttle667c0bb2017-07-06 15:17:1331
32void RecordHeaderOutcome(HeaderOutcome outcome) {
Douglas Creager134b52e2018-11-09 18:00:1433 UMA_HISTOGRAM_ENUMERATION(ReportingHeaderParser::kHeaderOutcomeHistogram,
34 outcome, HeaderOutcome::MAX);
juliatuttle667c0bb2017-07-06 15:17:1335}
36
Douglas Creagerf0db63a2018-02-28 17:50:2337void RecordHeaderEndpointGroupOutcome(HeaderEndpointGroupOutcome outcome) {
Douglas Creager134b52e2018-11-09 18:00:1438 UMA_HISTOGRAM_ENUMERATION(
39 ReportingHeaderParser::kHeaderEndpointGroupOutcomeHistogram, outcome,
40 HeaderEndpointGroupOutcome::MAX);
Douglas Creagerf0db63a2018-02-28 17:50:2341}
42
juliatuttle667c0bb2017-07-06 15:17:1343void RecordHeaderEndpointOutcome(HeaderEndpointOutcome outcome) {
Douglas Creager134b52e2018-11-09 18:00:1444 UMA_HISTOGRAM_ENUMERATION(
45 ReportingHeaderParser::kHeaderEndpointOutcomeHistogram, outcome,
46 HeaderEndpointOutcome::MAX);
juliatuttle667c0bb2017-07-06 15:17:1347}
48
juliatuttle1690bc62017-03-29 17:16:0249const char kUrlKey[] = "url";
Douglas Creagerbca64422018-06-18 13:54:4250const char kIncludeSubdomainsKey[] = "include_subdomains";
Douglas Creagerf0db63a2018-02-28 17:50:2351const char kEndpointsKey[] = "endpoints";
juliatuttle1690bc62017-03-29 17:16:0252const char kGroupKey[] = "group";
Lily Chenefb6fcf2019-04-19 04:17:5453const char kDefaultGroupName[] = "default";
Douglas Creagerbca64422018-06-18 13:54:4254const char kMaxAgeKey[] = "max_age";
Julia Tuttled56350d2017-12-07 19:11:1755const char kPriorityKey[] = "priority";
56const char kWeightKey[] = "weight";
juliatuttle1690bc62017-03-29 17:16:0257
Julia Tuttle443a0a682017-12-04 16:16:2658// Processes a single endpoint tuple received in a Report-To header.
59//
60// |origin| is the origin that sent the Report-To header.
61//
62// |value| is the parsed JSON value of the endpoint tuple.
63//
64// |*endpoint_out| will contain the endpoint URL parsed out of the tuple.
Lily Chenefb6fcf2019-04-19 04:17:5465HeaderEndpointOutcome ProcessEndpoint(
66 ReportingDelegate* delegate,
67 const url::Origin& origin,
68 const base::Value& value,
Lily Chenfc92ff42019-05-06 22:59:1069 ReportingEndpoint::EndpointInfo* endpoint_info_out) {
juliatuttle1690bc62017-03-29 17:16:0270 const base::DictionaryValue* dict = nullptr;
71 if (!value.GetAsDictionary(&dict))
juliatuttle667c0bb2017-07-06 15:17:1372 return HeaderEndpointOutcome::DISCARDED_NOT_DICTIONARY;
juliatuttle1690bc62017-03-29 17:16:0273 DCHECK(dict);
74
75 std::string endpoint_url_string;
juliatuttle667c0bb2017-07-06 15:17:1376 if (!dict->HasKey(kUrlKey))
Douglas Creagerf0db63a2018-02-28 17:50:2377 return HeaderEndpointOutcome::DISCARDED_URL_MISSING;
juliatuttle1690bc62017-03-29 17:16:0278 if (!dict->GetString(kUrlKey, &endpoint_url_string))
Douglas Creagerf0db63a2018-02-28 17:50:2379 return HeaderEndpointOutcome::DISCARDED_URL_NOT_STRING;
juliatuttle1690bc62017-03-29 17:16:0280
81 GURL endpoint_url(endpoint_url_string);
82 if (!endpoint_url.is_valid())
Douglas Creagerf0db63a2018-02-28 17:50:2383 return HeaderEndpointOutcome::DISCARDED_URL_INVALID;
juliatuttle1690bc62017-03-29 17:16:0284 if (!endpoint_url.SchemeIsCryptographic())
Douglas Creagerf0db63a2018-02-28 17:50:2385 return HeaderEndpointOutcome::DISCARDED_URL_INSECURE;
Lily Chenefb6fcf2019-04-19 04:17:5486 endpoint_info_out->url = std::move(endpoint_url);
juliatuttle1690bc62017-03-29 17:16:0287
Lily Chenfc92ff42019-05-06 22:59:1088 int priority = ReportingEndpoint::EndpointInfo::kDefaultPriority;
Julia Tuttled56350d2017-12-07 19:11:1789 if (dict->HasKey(kPriorityKey) && !dict->GetInteger(kPriorityKey, &priority))
90 return HeaderEndpointOutcome::DISCARDED_PRIORITY_NOT_INTEGER;
Lily Chenefb6fcf2019-04-19 04:17:5491 if (priority < 0)
92 return HeaderEndpointOutcome::DISCARDED_PRIORITY_NEGATIVE;
93 endpoint_info_out->priority = priority;
Julia Tuttled56350d2017-12-07 19:11:1794
Lily Chenfc92ff42019-05-06 22:59:1095 int weight = ReportingEndpoint::EndpointInfo::kDefaultWeight;
Julia Tuttled56350d2017-12-07 19:11:1796 if (dict->HasKey(kWeightKey) && !dict->GetInteger(kWeightKey, &weight))
97 return HeaderEndpointOutcome::DISCARDED_WEIGHT_NOT_INTEGER;
Lily Chenefb6fcf2019-04-19 04:17:5498 if (weight < 0)
99 return HeaderEndpointOutcome::DISCARDED_WEIGHT_NEGATIVE;
100 endpoint_info_out->weight = weight;
juliatuttle667c0bb2017-07-06 15:17:13101
juliatuttle667c0bb2017-07-06 15:17:13102 if (!delegate->CanSetClient(origin, endpoint_url))
103 return HeaderEndpointOutcome::SET_REJECTED_BY_DELEGATE;
104
juliatuttle667c0bb2017-07-06 15:17:13105 return HeaderEndpointOutcome::SET;
juliatuttle587548912017-05-23 14:17:21106}
107
Douglas Creagerf0db63a2018-02-28 17:50:23108// Processes a single endpoint group tuple received in a Report-To header.
109//
110// |origin| is the origin that sent the Report-To header.
111//
112// |value| is the parsed JSON value of the endpoint group tuple.
Lily Chenefb6fcf2019-04-19 04:17:54113HeaderEndpointGroupOutcome ProcessEndpointGroup(
114 ReportingDelegate* delegate,
115 ReportingCache* cache,
116 const url::Origin& origin,
117 const base::Value& value,
118 ReportingEndpointGroup* parsed_endpoint_group_out) {
Douglas Creagerf0db63a2018-02-28 17:50:23119 const base::DictionaryValue* dict = nullptr;
120 if (!value.GetAsDictionary(&dict))
121 return HeaderEndpointGroupOutcome::DISCARDED_NOT_DICTIONARY;
122 DCHECK(dict);
123
Lily Chenefb6fcf2019-04-19 04:17:54124 std::string group_name = kDefaultGroupName;
125 if (dict->HasKey(kGroupKey) && !dict->GetString(kGroupKey, &group_name))
Douglas Creagerf0db63a2018-02-28 17:50:23126 return HeaderEndpointGroupOutcome::DISCARDED_GROUP_NOT_STRING;
Lily Chenefb6fcf2019-04-19 04:17:54127 parsed_endpoint_group_out->name = std::move(group_name);
Douglas Creagerf0db63a2018-02-28 17:50:23128
129 int ttl_sec = -1;
130 if (!dict->HasKey(kMaxAgeKey))
131 return HeaderEndpointGroupOutcome::DISCARDED_TTL_MISSING;
132 if (!dict->GetInteger(kMaxAgeKey, &ttl_sec))
133 return HeaderEndpointGroupOutcome::DISCARDED_TTL_NOT_INTEGER;
134 if (ttl_sec < 0)
135 return HeaderEndpointGroupOutcome::DISCARDED_TTL_NEGATIVE;
Lily Chenefb6fcf2019-04-19 04:17:54136 // max_age: 0 signifies removal of the endpoint group.
137 if (ttl_sec == 0) {
138 cache->RemoveEndpointGroup(origin, group_name);
139 return HeaderEndpointGroupOutcome::REMOVED_TTL_ZERO;
140 }
141 parsed_endpoint_group_out->ttl = base::TimeDelta::FromSeconds(ttl_sec);
Douglas Creagerf0db63a2018-02-28 17:50:23142
Douglas Creagerf0db63a2018-02-28 17:50:23143 bool subdomains_bool = false;
144 if (dict->HasKey(kIncludeSubdomainsKey) &&
145 dict->GetBoolean(kIncludeSubdomainsKey, &subdomains_bool) &&
146 subdomains_bool == true) {
Lily Chena5b78cff2019-07-19 22:10:52147 // Disallow eTLDs from setting include_subdomains endpoint groups.
148 if (registry_controlled_domains::GetRegistryLength(
149 origin.GetURL(),
150 registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
151 registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES) == 0) {
152 return HeaderEndpointGroupOutcome::
153 DISCARDED_INCLUDE_SUBDOMAINS_NOT_ALLOWED;
154 }
155
Lily Chenefb6fcf2019-04-19 04:17:54156 parsed_endpoint_group_out->include_subdomains = OriginSubdomains::INCLUDE;
Douglas Creagerf0db63a2018-02-28 17:50:23157 }
158
159 const base::ListValue* endpoint_list = nullptr;
160 if (!dict->HasKey(kEndpointsKey))
161 return HeaderEndpointGroupOutcome::DISCARDED_ENDPOINTS_MISSING;
162 if (!dict->GetList(kEndpointsKey, &endpoint_list))
163 return HeaderEndpointGroupOutcome::DISCARDED_ENDPOINTS_NOT_LIST;
164
Lily Chenfc92ff42019-05-06 22:59:10165 std::vector<ReportingEndpoint::EndpointInfo> endpoints;
Lily Chenefb6fcf2019-04-19 04:17:54166
Douglas Creagerf0db63a2018-02-28 17:50:23167 for (size_t i = 0; i < endpoint_list->GetSize(); i++) {
168 const base::Value* endpoint = nullptr;
169 bool got_endpoint = endpoint_list->Get(i, &endpoint);
170 DCHECK(got_endpoint);
Lily Chenefb6fcf2019-04-19 04:17:54171
Lily Chenfc92ff42019-05-06 22:59:10172 ReportingEndpoint::EndpointInfo parsed_endpoint;
Douglas Creagerf0db63a2018-02-28 17:50:23173
174 HeaderEndpointOutcome outcome =
Lily Chenefb6fcf2019-04-19 04:17:54175 ProcessEndpoint(delegate, origin, *endpoint, &parsed_endpoint);
176
177 if (outcome == HeaderEndpointOutcome::SET)
178 endpoints.push_back(std::move(parsed_endpoint));
179
Douglas Creagerf0db63a2018-02-28 17:50:23180 RecordHeaderEndpointOutcome(outcome);
181 }
182
Lily Chenefb6fcf2019-04-19 04:17:54183 // Remove the group if it is empty.
184 if (endpoints.empty()) {
185 cache->RemoveEndpointGroup(origin, group_name);
186 return HeaderEndpointGroupOutcome::REMOVED_EMPTY;
187 }
188
189 parsed_endpoint_group_out->endpoints = std::move(endpoints);
190
Douglas Creagerf0db63a2018-02-28 17:50:23191 return HeaderEndpointGroupOutcome::PARSED;
192}
193
juliatuttle587548912017-05-23 14:17:21194} // namespace
195
196// static
Douglas Creager134b52e2018-11-09 18:00:14197const char ReportingHeaderParser::kHeaderOutcomeHistogram[] =
198 "Net.Reporting.HeaderOutcome";
199
200// static
201const char ReportingHeaderParser::kHeaderEndpointGroupOutcomeHistogram[] =
202 "Net.Reporting.HeaderEndpointGroupOutcome";
203
204// static
205const char ReportingHeaderParser::kHeaderEndpointOutcomeHistogram[] =
206 "Net.Reporting.HeaderEndpointOutcome";
207
208// static
juliatuttle667c0bb2017-07-06 15:17:13209void ReportingHeaderParser::RecordHeaderDiscardedForNoReportingService() {
210 RecordHeaderOutcome(HeaderOutcome::DISCARDED_NO_REPORTING_SERVICE);
211}
212
213// static
214void ReportingHeaderParser::RecordHeaderDiscardedForInvalidSSLInfo() {
215 RecordHeaderOutcome(HeaderOutcome::DISCARDED_INVALID_SSL_INFO);
216}
217
218// static
219void ReportingHeaderParser::RecordHeaderDiscardedForCertStatusError() {
220 RecordHeaderOutcome(HeaderOutcome::DISCARDED_CERT_STATUS_ERROR);
221}
222
223// static
Julia Tuttleef19cb52018-03-16 16:58:35224void ReportingHeaderParser::RecordHeaderDiscardedForJsonInvalid() {
225 RecordHeaderOutcome(HeaderOutcome::DISCARDED_JSON_INVALID);
226}
227
228// static
229void ReportingHeaderParser::RecordHeaderDiscardedForJsonTooBig() {
230 RecordHeaderOutcome(HeaderOutcome::DISCARDED_JSON_TOO_BIG);
Julia Tuttleec467a5f2018-02-22 20:22:45231}
232
233// static
juliatuttle587548912017-05-23 14:17:21234void ReportingHeaderParser::ParseHeader(ReportingContext* context,
235 const GURL& url,
Julia Tuttleec467a5f2018-02-22 20:22:45236 std::unique_ptr<base::Value> value) {
juliatuttle587548912017-05-23 14:17:21237 DCHECK(url.SchemeIsCryptographic());
238
Douglas Creagerf0db63a2018-02-28 17:50:23239 const base::ListValue* group_list = nullptr;
240 bool is_list = value->GetAsList(&group_list);
juliatuttle587548912017-05-23 14:17:21241 DCHECK(is_list);
242
243 ReportingDelegate* delegate = context->delegate();
244 ReportingCache* cache = context->cache();
Julia Tuttle443a0a682017-12-04 16:16:26245
246 url::Origin origin = url::Origin::Create(url);
247
Lily Chenefb6fcf2019-04-19 04:17:54248 std::vector<ReportingEndpointGroup> parsed_header;
Julia Tuttle443a0a682017-12-04 16:16:26249
Douglas Creagerf0db63a2018-02-28 17:50:23250 for (size_t i = 0; i < group_list->GetSize(); i++) {
Lily Chenefb6fcf2019-04-19 04:17:54251 const base::Value* group_value = nullptr;
252 bool got_group = group_list->Get(i, &group_value);
Douglas Creagerf0db63a2018-02-28 17:50:23253 DCHECK(got_group);
Lily Chenefb6fcf2019-04-19 04:17:54254 ReportingEndpointGroup parsed_endpoint_group;
Douglas Creagerf0db63a2018-02-28 17:50:23255 HeaderEndpointGroupOutcome outcome = ProcessEndpointGroup(
Lily Chenefb6fcf2019-04-19 04:17:54256 delegate, cache, origin, *group_value, &parsed_endpoint_group);
Douglas Creagerf0db63a2018-02-28 17:50:23257 RecordHeaderEndpointGroupOutcome(outcome);
Lily Chenefb6fcf2019-04-19 04:17:54258 if (outcome == HeaderEndpointGroupOutcome::PARSED)
259 parsed_header.push_back(std::move(parsed_endpoint_group));
Julia Tuttle443a0a682017-12-04 16:16:26260 }
261
Lily Chenefb6fcf2019-04-19 04:17:54262 // Remove the client if it has no valid endpoint groups.
263 if (parsed_header.empty()) {
264 cache->RemoveClient(origin);
265 RecordHeaderOutcome(HeaderOutcome::REMOVED_EMPTY);
266 return;
juliatuttle1690bc62017-03-29 17:16:02267 }
Julia Tuttleefe2fae42018-03-30 15:22:31268
Lily Chenefb6fcf2019-04-19 04:17:54269 cache->OnParsedHeader(origin, std::move(parsed_header));
Julia Tuttleefe2fae42018-03-30 15:22:31270 RecordHeaderOutcome(HeaderOutcome::PARSED);
juliatuttle1690bc62017-03-29 17:16:02271}
272
273} // namespace net