Chromium Code Reviews| 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 |