OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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 "components/reporting/core/browser/reporting_manager.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/json/json_writer.h" | |
9 #include "base/memory/ptr_util.h" | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "base/strings/string_util.h" | |
12 #include "base/time/default_tick_clock.h" | |
13 #include "components/reporting/core/browser/reporting_util.h" | |
14 #include "net/http/http_request_info.h" | |
15 #include "net/http/http_response_headers.h" | |
16 #include "net/http/http_response_info.h" | |
17 #include "net/url_request/url_fetcher.h" | |
18 #include "net/url_request/url_request_context_getter.h" | |
19 | |
20 namespace { | |
21 const char kDefaultGroupName[] = "default"; | |
22 } // namespace | |
23 | |
24 namespace reporting { | |
25 | |
26 ReportingManager::ReportingManager( | |
27 std::unique_ptr<ReportingUploader> uploader, | |
28 const base::Callback<bool(const GURL&)>& is_origin_secure) | |
29 : is_origin_secure_(is_origin_secure), | |
30 clock_(new base::DefaultTickClock()), | |
31 uploader_(std::move(uploader)) {} | |
32 ReportingManager::~ReportingManager() {} | |
33 | |
34 void ReportingManager::QueueReport(std::unique_ptr<ReportingReport> report) { | |
35 report->timestamp = clock_->NowTicks(); | |
36 report->attempts = 0; | |
37 cache_.EnqueueReport(std::move(report)); | |
38 } | |
39 | |
40 void ReportingManager::ProcessHeader(const GURL& origin, | |
41 const std::string& header_value) { | |
42 DCHECK(is_origin_secure_.Run(origin)); | |
43 | |
44 std::vector<std::string> errors; | |
45 std::vector<EndpointTuple> tuples = | |
46 EndpointTuple::FromHeader(header_value, is_origin_secure_, &errors); | |
47 | |
48 // TODO: Plumb these out in a more modular way and expose them somewhere more | |
49 // visible? | |
Randy Smith (Not in Mondays)
2016/10/21 20:15:11
I'm trying to think what the right place would be.
Julia Tuttle
2016/11/02 20:44:39
The DevTools console, most likely.
| |
50 for (const std::string& error : errors) { | |
51 LOG(WARNING) << "Origin " << origin.spec() << " sent " | |
52 << "Report-To header with error: " << error << ": " | |
53 << header_value; | |
54 } | |
55 | |
56 for (auto& tuple : tuples) | |
57 ProcessEndpoint(origin, tuple); | |
58 } | |
59 | |
60 void ReportingManager::SendReports() { | |
61 const ReportingCache::ReportSet& reports = cache_.GetReports(); | |
62 const ReportingCache::EndpointMap& endpoints = cache_.GetEndpoints(); | |
63 | |
64 std::map<const std::unique_ptr<ReportingEndpoint>*, | |
65 std::vector<const std::unique_ptr<ReportingReport>*>> | |
66 endpoint_map; | |
67 for (auto& report : reports) { | |
68 for (auto& pair : endpoints) { | |
69 const std::unique_ptr<ReportingEndpoint>& endpoint = pair.second; | |
70 if (endpoint->is_expired(clock_->NowTicks()) || | |
71 endpoint->backoff.ShouldRejectRequest() || | |
72 !DoesEndpointMatchReport(*endpoint, *report)) { | |
73 continue; | |
74 } | |
75 endpoint_map[&endpoint].push_back(&report); | |
76 goto next_report; | |
Randy Smith (Not in Mondays)
2016/10/21 20:15:11
I think this matching-and-exit algorithm means tha
Randy Smith (Not in Mondays)
2016/10/21 20:15:11
Why not "break;"?
Julia Tuttle
2016/11/02 20:44:39
I think I was worried I'd end up breaking out of t
Julia Tuttle
2016/11/02 20:44:39
...I *don't* think that's what the spec wants. *I*
| |
77 } | |
78 next_report:; | |
79 } | |
80 | |
81 for (auto& pair : endpoint_map) { | |
82 std::unique_ptr<Delivery> delivery(new Delivery(*pair.first, pair.second)); | |
83 delivery->endpoint->last_used = clock_->NowTicks(); | |
84 const GURL url = delivery->endpoint->url; | |
85 std::string json = SerializeReports(delivery->reports); | |
86 uploader_->AttemptDelivery( | |
87 url, json, base::Bind(&ReportingManager::OnDeliveryAttemptComplete, | |
88 base::Unretained(this), std::move(delivery))); | |
89 } | |
90 } | |
91 | |
92 // static | |
93 bool ReportingManager::EndpointTuple::FromDictionary( | |
94 const base::DictionaryValue& dictionary, | |
95 EndpointTuple* tuple_out, | |
96 std::string* error_out) { | |
97 std::string url_string; | |
98 if (!dictionary.GetString("url", &url_string)) { | |
99 *error_out = "url missing or not a string"; | |
100 return false; | |
101 } | |
102 tuple_out->url = GURL(url_string); | |
103 | |
104 tuple_out->subdomains = false; | |
105 dictionary.GetBoolean("includeSubdomains", &tuple_out->subdomains); | |
106 | |
107 int ttl_sec; | |
108 if (!dictionary.GetInteger("max-age", &ttl_sec)) { | |
109 *error_out = "max-age missing or not an integer"; | |
110 return false; | |
111 } | |
112 tuple_out->ttl = base::TimeDelta::FromSeconds(ttl_sec); | |
113 | |
114 tuple_out->group = kDefaultGroupName; | |
115 dictionary.GetString("group", &tuple_out->group); | |
116 | |
117 return true; | |
118 } | |
119 | |
120 // static | |
121 std::vector<ReportingManager::EndpointTuple> | |
122 ReportingManager::EndpointTuple::FromHeader( | |
123 const std::string& header, | |
124 const base::Callback<bool(const GURL&)>& is_origin_secure, | |
125 std::vector<std::string>* errors_out) { | |
126 std::vector<ReportingManager::EndpointTuple> tuples; | |
127 | |
128 errors_out->clear(); | |
129 | |
130 std::unique_ptr<base::Value> value(ParseJFV(header)); | |
131 if (!value) { | |
132 errors_out->push_back("failed to parse JSON field value."); | |
133 return tuples; | |
134 } | |
135 | |
136 base::ListValue* list; | |
137 bool was_list = value->GetAsList(&list); | |
138 DCHECK(was_list); | |
139 | |
140 base::DictionaryValue* item; | |
141 for (size_t i = 0; i < list->GetSize(); i++) { | |
142 std::string error_prefix = "endpoint " + base::SizeTToString(i + 1) + | |
143 " of " + base::SizeTToString(list->GetSize()) + | |
144 ":"; | |
145 if (!list->GetDictionary(i, &item)) { | |
146 errors_out->push_back(error_prefix + "is not a dictionary."); | |
147 continue; | |
148 } | |
149 EndpointTuple tuple; | |
150 std::string error; | |
151 if (!EndpointTuple::FromDictionary(*item, &tuple, &error)) { | |
152 errors_out->push_back(error_prefix + error); | |
153 continue; | |
154 } | |
155 if (!is_origin_secure.Run(tuple.url)) { | |
156 errors_out->push_back(error_prefix + "url " + tuple.url.spec() + | |
157 " is insecure."); | |
158 continue; | |
159 } | |
160 if (tuple.ttl < base::TimeDelta()) { | |
161 errors_out->push_back(error_prefix + "ttl is negative."); | |
162 continue; | |
163 } | |
164 tuples.push_back(tuple); | |
165 } | |
166 return tuples; | |
167 } | |
168 | |
169 std::string ReportingManager::EndpointTuple::ToString() const { | |
170 return "(url=" + url.spec() + ", subdomains=" + | |
171 (subdomains ? "true" : "false") + ", ttl=" + | |
172 base::Int64ToString(ttl.InSeconds()) + "s" + ", group=" + group + ")"; | |
173 } | |
174 | |
175 ReportingManager::Delivery::Delivery( | |
176 const std::unique_ptr<ReportingEndpoint>& endpoint, | |
177 const std::vector<const std::unique_ptr<ReportingReport>*>& reports) | |
178 : endpoint(endpoint), reports(reports) {} | |
179 | |
180 ReportingManager::Delivery::~Delivery() {} | |
181 | |
182 void ReportingManager::ProcessEndpoint(const GURL& origin, | |
183 const EndpointTuple& tuple) { | |
184 const std::unique_ptr<ReportingEndpoint>* endpoint_ptr = | |
185 cache_.GetEndpoint(tuple.url); | |
186 ReportingEndpoint* endpoint = endpoint_ptr ? endpoint_ptr->get() : nullptr; | |
187 | |
188 if (endpoint) | |
189 endpoint->clients.erase(origin); | |
190 | |
191 if (tuple.ttl <= base::TimeDelta()) | |
192 return; | |
193 | |
194 if (!endpoint) { | |
195 endpoint = | |
196 new ReportingEndpoint(tuple.url, backoff_policy_, clock_->NowTicks()); | |
197 cache_.InsertEndpoint(base::WrapUnique(endpoint)); | |
198 } | |
199 | |
200 endpoint->clients.insert(std::make_pair( | |
201 GURL(origin), ReportingClient(origin, tuple.subdomains, tuple.group, | |
202 tuple.ttl, clock_->NowTicks()))); | |
203 } | |
204 | |
205 bool ReportingManager::DoesEndpointMatchReport( | |
206 const ReportingEndpoint& endpoint, | |
207 const ReportingReport& report) { | |
208 for (auto& pair : endpoint.clients) { | |
209 const ReportingClient& client = pair.second; | |
210 if (client.is_expired(clock_->NowTicks())) | |
211 continue; | |
212 if (!base::EqualsCaseInsensitiveASCII(client.group, report.group)) | |
213 continue; | |
214 if (client.origin == report.origin) | |
215 return true; | |
216 if (client.subdomains && report.origin.DomainIs(client.origin.host_piece())) | |
217 return true; | |
218 } | |
219 return false; | |
220 } | |
221 | |
222 void ReportingManager::OnDeliveryAttemptComplete( | |
223 const std::unique_ptr<Delivery>& delivery, | |
224 ReportingUploader::Outcome outcome) { | |
225 switch (outcome) { | |
226 case ReportingUploader::SUCCESS: | |
227 delivery->endpoint->backoff.InformOfRequest(true); | |
228 for (auto report_ptr : delivery->reports) | |
229 cache_.DequeueReport(*report_ptr); | |
230 break; | |
231 case ReportingUploader::FAILURE: | |
232 delivery->endpoint->backoff.InformOfRequest(false); | |
233 break; | |
234 case ReportingUploader::REMOVE_ENDPOINT: | |
235 // NOTE: This is not specified, but seems the obvious intention. | |
236 cache_.RemoveEndpoint(delivery->endpoint); | |
237 break; | |
238 } | |
239 } | |
240 | |
241 std::string ReportingManager::SerializeReports( | |
242 const std::vector<const std::unique_ptr<ReportingReport>*>& reports) { | |
243 base::ListValue collection; | |
244 for (auto& report_ptr : reports) { | |
245 ReportingReport* report = report_ptr->get(); | |
246 ++report->attempts; | |
247 | |
248 std::unique_ptr<base::DictionaryValue> data(new base::DictionaryValue()); | |
249 data->SetInteger("age", | |
250 (clock_->NowTicks() - report->timestamp).InMilliseconds()); | |
251 data->SetString("type", report->type); | |
252 data->SetString("url", report->url.spec()); | |
253 data->Set("report", report->body->DeepCopy()); | |
254 collection.Append(std::move(data)); | |
255 } | |
256 | |
257 std::string json = ""; | |
258 bool written = base::JSONWriter::Write(collection, &json); | |
259 DCHECK(written); | |
260 return json; | |
261 } | |
262 | |
263 void ReportingManager::CollectGarbage() { | |
264 const base::TimeDelta kEndpointMaxUnusedTime = base::TimeDelta::FromDays(7); | |
265 const base::TimeDelta kReportMaxUndeliveredTime = | |
266 base::TimeDelta::FromDays(2); | |
267 const int kEndpointMaxFailures = 5; | |
268 const int kReportMaxFailures = 5; | |
269 | |
270 // TODO: Histogram report and endpoint removal reasons and maybe ages. | |
271 | |
272 const ReportingCache::EndpointMap& endpoints = cache_.GetEndpoints(); | |
273 for (auto& pair : endpoints) { | |
274 const std::unique_ptr<ReportingEndpoint>& endpoint = pair.second; | |
275 if (endpoint->is_expired(clock_->NowTicks())) { | |
276 cache_.RemoveEndpoint(endpoint); | |
277 continue; | |
278 } | |
279 if (clock_->NowTicks() - endpoint->last_used > kEndpointMaxUnusedTime) { | |
280 cache_.RemoveEndpoint(endpoint); | |
281 continue; | |
282 } | |
283 if (endpoint->backoff.failure_count() > kEndpointMaxFailures) { | |
284 cache_.RemoveEndpoint(endpoint); | |
285 continue; | |
286 } | |
287 } | |
288 | |
289 const ReportingCache::ReportSet& reports = cache_.GetReports(); | |
290 for (auto& report : reports) { | |
291 if (report->attempts > kReportMaxFailures) { | |
292 cache_.DequeueReport(report); | |
293 continue; | |
294 } | |
295 if (clock_->NowTicks() - report->timestamp > kReportMaxUndeliveredTime) { | |
296 cache_.DequeueReport(report); | |
297 continue; | |
298 } | |
299 } | |
300 } | |
301 | |
302 } // namespace reporting | |
OLD | NEW |