Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1800)

Unified Diff: components/reporting/core/browser/reporting_manager.cc

Issue 2249213002: [OBSOLETE] Reporting: Initial implementation. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix ProfileImplIOData Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/reporting/core/browser/reporting_manager.cc
diff --git a/components/reporting/core/browser/reporting_manager.cc b/components/reporting/core/browser/reporting_manager.cc
new file mode 100644
index 0000000000000000000000000000000000000000..abb0b33b023c7426dfd105ad43f21efcebe03919
--- /dev/null
+++ b/components/reporting/core/browser/reporting_manager.cc
@@ -0,0 +1,302 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/reporting/core/browser/reporting_manager.h"
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/default_tick_clock.h"
+#include "components/reporting/core/browser/reporting_util.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+const char kDefaultGroupName[] = "default";
+} // namespace
+
+namespace reporting {
+
+ReportingManager::ReportingManager(
+ std::unique_ptr<ReportingUploader> uploader,
+ const base::Callback<bool(const GURL&)>& is_origin_secure)
+ : is_origin_secure_(is_origin_secure),
+ clock_(new base::DefaultTickClock()),
+ uploader_(std::move(uploader)) {}
+ReportingManager::~ReportingManager() {}
+
+void ReportingManager::QueueReport(std::unique_ptr<ReportingReport> report) {
+ report->timestamp = clock_->NowTicks();
+ report->attempts = 0;
+ cache_.EnqueueReport(std::move(report));
+}
+
+void ReportingManager::ProcessHeader(const GURL& origin,
+ const std::string& header_value) {
+ DCHECK(is_origin_secure_.Run(origin));
+
+ std::vector<std::string> errors;
+ std::vector<EndpointTuple> tuples =
+ EndpointTuple::FromHeader(header_value, is_origin_secure_, &errors);
+
+ // TODO: Plumb these out in a more modular way and expose them somewhere more
+ // 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.
+ for (const std::string& error : errors) {
+ LOG(WARNING) << "Origin " << origin.spec() << " sent "
+ << "Report-To header with error: " << error << ": "
+ << header_value;
+ }
+
+ for (auto& tuple : tuples)
+ ProcessEndpoint(origin, tuple);
+}
+
+void ReportingManager::SendReports() {
+ const ReportingCache::ReportSet& reports = cache_.GetReports();
+ const ReportingCache::EndpointMap& endpoints = cache_.GetEndpoints();
+
+ std::map<const std::unique_ptr<ReportingEndpoint>*,
+ std::vector<const std::unique_ptr<ReportingReport>*>>
+ endpoint_map;
+ for (auto& report : reports) {
+ for (auto& pair : endpoints) {
+ const std::unique_ptr<ReportingEndpoint>& endpoint = pair.second;
+ if (endpoint->is_expired(clock_->NowTicks()) ||
+ endpoint->backoff.ShouldRejectRequest() ||
+ !DoesEndpointMatchReport(*endpoint, *report)) {
+ continue;
+ }
+ endpoint_map[&endpoint].push_back(&report);
+ 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*
+ }
+ next_report:;
+ }
+
+ for (auto& pair : endpoint_map) {
+ std::unique_ptr<Delivery> delivery(new Delivery(*pair.first, pair.second));
+ delivery->endpoint->last_used = clock_->NowTicks();
+ const GURL url = delivery->endpoint->url;
+ std::string json = SerializeReports(delivery->reports);
+ uploader_->AttemptDelivery(
+ url, json, base::Bind(&ReportingManager::OnDeliveryAttemptComplete,
+ base::Unretained(this), std::move(delivery)));
+ }
+}
+
+// static
+bool ReportingManager::EndpointTuple::FromDictionary(
+ const base::DictionaryValue& dictionary,
+ EndpointTuple* tuple_out,
+ std::string* error_out) {
+ std::string url_string;
+ if (!dictionary.GetString("url", &url_string)) {
+ *error_out = "url missing or not a string";
+ return false;
+ }
+ tuple_out->url = GURL(url_string);
+
+ tuple_out->subdomains = false;
+ dictionary.GetBoolean("includeSubdomains", &tuple_out->subdomains);
+
+ int ttl_sec;
+ if (!dictionary.GetInteger("max-age", &ttl_sec)) {
+ *error_out = "max-age missing or not an integer";
+ return false;
+ }
+ tuple_out->ttl = base::TimeDelta::FromSeconds(ttl_sec);
+
+ tuple_out->group = kDefaultGroupName;
+ dictionary.GetString("group", &tuple_out->group);
+
+ return true;
+}
+
+// static
+std::vector<ReportingManager::EndpointTuple>
+ReportingManager::EndpointTuple::FromHeader(
+ const std::string& header,
+ const base::Callback<bool(const GURL&)>& is_origin_secure,
+ std::vector<std::string>* errors_out) {
+ std::vector<ReportingManager::EndpointTuple> tuples;
+
+ errors_out->clear();
+
+ std::unique_ptr<base::Value> value(ParseJFV(header));
+ if (!value) {
+ errors_out->push_back("failed to parse JSON field value.");
+ return tuples;
+ }
+
+ base::ListValue* list;
+ bool was_list = value->GetAsList(&list);
+ DCHECK(was_list);
+
+ base::DictionaryValue* item;
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ std::string error_prefix = "endpoint " + base::SizeTToString(i + 1) +
+ " of " + base::SizeTToString(list->GetSize()) +
+ ":";
+ if (!list->GetDictionary(i, &item)) {
+ errors_out->push_back(error_prefix + "is not a dictionary.");
+ continue;
+ }
+ EndpointTuple tuple;
+ std::string error;
+ if (!EndpointTuple::FromDictionary(*item, &tuple, &error)) {
+ errors_out->push_back(error_prefix + error);
+ continue;
+ }
+ if (!is_origin_secure.Run(tuple.url)) {
+ errors_out->push_back(error_prefix + "url " + tuple.url.spec() +
+ " is insecure.");
+ continue;
+ }
+ if (tuple.ttl < base::TimeDelta()) {
+ errors_out->push_back(error_prefix + "ttl is negative.");
+ continue;
+ }
+ tuples.push_back(tuple);
+ }
+ return tuples;
+}
+
+std::string ReportingManager::EndpointTuple::ToString() const {
+ return "(url=" + url.spec() + ", subdomains=" +
+ (subdomains ? "true" : "false") + ", ttl=" +
+ base::Int64ToString(ttl.InSeconds()) + "s" + ", group=" + group + ")";
+}
+
+ReportingManager::Delivery::Delivery(
+ const std::unique_ptr<ReportingEndpoint>& endpoint,
+ const std::vector<const std::unique_ptr<ReportingReport>*>& reports)
+ : endpoint(endpoint), reports(reports) {}
+
+ReportingManager::Delivery::~Delivery() {}
+
+void ReportingManager::ProcessEndpoint(const GURL& origin,
+ const EndpointTuple& tuple) {
+ const std::unique_ptr<ReportingEndpoint>* endpoint_ptr =
+ cache_.GetEndpoint(tuple.url);
+ ReportingEndpoint* endpoint = endpoint_ptr ? endpoint_ptr->get() : nullptr;
+
+ if (endpoint)
+ endpoint->clients.erase(origin);
+
+ if (tuple.ttl <= base::TimeDelta())
+ return;
+
+ if (!endpoint) {
+ endpoint =
+ new ReportingEndpoint(tuple.url, backoff_policy_, clock_->NowTicks());
+ cache_.InsertEndpoint(base::WrapUnique(endpoint));
+ }
+
+ endpoint->clients.insert(std::make_pair(
+ GURL(origin), ReportingClient(origin, tuple.subdomains, tuple.group,
+ tuple.ttl, clock_->NowTicks())));
+}
+
+bool ReportingManager::DoesEndpointMatchReport(
+ const ReportingEndpoint& endpoint,
+ const ReportingReport& report) {
+ for (auto& pair : endpoint.clients) {
+ const ReportingClient& client = pair.second;
+ if (client.is_expired(clock_->NowTicks()))
+ continue;
+ if (!base::EqualsCaseInsensitiveASCII(client.group, report.group))
+ continue;
+ if (client.origin == report.origin)
+ return true;
+ if (client.subdomains && report.origin.DomainIs(client.origin.host_piece()))
+ return true;
+ }
+ return false;
+}
+
+void ReportingManager::OnDeliveryAttemptComplete(
+ const std::unique_ptr<Delivery>& delivery,
+ ReportingUploader::Outcome outcome) {
+ switch (outcome) {
+ case ReportingUploader::SUCCESS:
+ delivery->endpoint->backoff.InformOfRequest(true);
+ for (auto report_ptr : delivery->reports)
+ cache_.DequeueReport(*report_ptr);
+ break;
+ case ReportingUploader::FAILURE:
+ delivery->endpoint->backoff.InformOfRequest(false);
+ break;
+ case ReportingUploader::REMOVE_ENDPOINT:
+ // NOTE: This is not specified, but seems the obvious intention.
+ cache_.RemoveEndpoint(delivery->endpoint);
+ break;
+ }
+}
+
+std::string ReportingManager::SerializeReports(
+ const std::vector<const std::unique_ptr<ReportingReport>*>& reports) {
+ base::ListValue collection;
+ for (auto& report_ptr : reports) {
+ ReportingReport* report = report_ptr->get();
+ ++report->attempts;
+
+ std::unique_ptr<base::DictionaryValue> data(new base::DictionaryValue());
+ data->SetInteger("age",
+ (clock_->NowTicks() - report->timestamp).InMilliseconds());
+ data->SetString("type", report->type);
+ data->SetString("url", report->url.spec());
+ data->Set("report", report->body->DeepCopy());
+ collection.Append(std::move(data));
+ }
+
+ std::string json = "";
+ bool written = base::JSONWriter::Write(collection, &json);
+ DCHECK(written);
+ return json;
+}
+
+void ReportingManager::CollectGarbage() {
+ const base::TimeDelta kEndpointMaxUnusedTime = base::TimeDelta::FromDays(7);
+ const base::TimeDelta kReportMaxUndeliveredTime =
+ base::TimeDelta::FromDays(2);
+ const int kEndpointMaxFailures = 5;
+ const int kReportMaxFailures = 5;
+
+ // TODO: Histogram report and endpoint removal reasons and maybe ages.
+
+ const ReportingCache::EndpointMap& endpoints = cache_.GetEndpoints();
+ for (auto& pair : endpoints) {
+ const std::unique_ptr<ReportingEndpoint>& endpoint = pair.second;
+ if (endpoint->is_expired(clock_->NowTicks())) {
+ cache_.RemoveEndpoint(endpoint);
+ continue;
+ }
+ if (clock_->NowTicks() - endpoint->last_used > kEndpointMaxUnusedTime) {
+ cache_.RemoveEndpoint(endpoint);
+ continue;
+ }
+ if (endpoint->backoff.failure_count() > kEndpointMaxFailures) {
+ cache_.RemoveEndpoint(endpoint);
+ continue;
+ }
+ }
+
+ const ReportingCache::ReportSet& reports = cache_.GetReports();
+ for (auto& report : reports) {
+ if (report->attempts > kReportMaxFailures) {
+ cache_.DequeueReport(report);
+ continue;
+ }
+ if (clock_->NowTicks() - report->timestamp > kReportMaxUndeliveredTime) {
+ cache_.DequeueReport(report);
+ continue;
+ }
+ }
+}
+
+} // namespace reporting

Powered by Google App Engine
This is Rietveld 408576698