summaryrefslogtreecommitdiff
path: root/chromium/net/reporting
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-12 14:07:37 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-17 10:29:26 +0000
commitec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch)
tree25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/net/reporting
parentbb09965444b5bb20b096a291445170876225268d (diff)
downloadqtwebengine-chromium-ec02ee4181c49b61fce1c8fb99292dbb8139cc90.tar.gz
BASELINE: Update Chromium to 59.0.3071.134
Change-Id: Id02ef6fb2204c5fd21668a1c3e6911c83b17585a Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/net/reporting')
-rw-r--r--chromium/net/reporting/README.md77
-rw-r--r--chromium/net/reporting/reporting_browsing_data_remover.cc59
-rw-r--r--chromium/net/reporting/reporting_browsing_data_remover.h36
-rw-r--r--chromium/net/reporting/reporting_browsing_data_remover_unittest.cc193
-rw-r--r--chromium/net/reporting/reporting_cache.cc271
-rw-r--r--chromium/net/reporting/reporting_cache.h201
-rw-r--r--chromium/net/reporting/reporting_cache_unittest.cc404
-rw-r--r--chromium/net/reporting/reporting_client.cc28
-rw-r--r--chromium/net/reporting/reporting_client.h53
-rw-r--r--chromium/net/reporting/reporting_context.cc102
-rw-r--r--chromium/net/reporting/reporting_context.h116
-rw-r--r--chromium/net/reporting/reporting_delegate.cc13
-rw-r--r--chromium/net/reporting/reporting_delegate.h45
-rw-r--r--chromium/net/reporting/reporting_delivery_agent.cc142
-rw-r--r--chromium/net/reporting/reporting_delivery_agent.h99
-rw-r--r--chromium/net/reporting/reporting_delivery_agent_unittest.cc318
-rw-r--r--chromium/net/reporting/reporting_endpoint_manager.cc80
-rw-r--r--chromium/net/reporting/reporting_endpoint_manager.h82
-rw-r--r--chromium/net/reporting/reporting_endpoint_manager_unittest.cc178
-rw-r--r--chromium/net/reporting/reporting_garbage_collector.cc95
-rw-r--r--chromium/net/reporting/reporting_garbage_collector.h42
-rw-r--r--chromium/net/reporting/reporting_garbage_collector_unittest.cc91
-rw-r--r--chromium/net/reporting/reporting_header_parser.cc98
-rw-r--r--chromium/net/reporting/reporting_header_parser.h48
-rw-r--r--chromium/net/reporting/reporting_header_parser_unittest.cc109
-rw-r--r--chromium/net/reporting/reporting_observer.cc15
-rw-r--r--chromium/net/reporting/reporting_observer.h28
-rw-r--r--chromium/net/reporting/reporting_persister.cc358
-rw-r--r--chromium/net/reporting/reporting_persister.h40
-rw-r--r--chromium/net/reporting/reporting_persister_unittest.cc80
-rw-r--r--chromium/net/reporting/reporting_policy.cc38
-rw-r--r--chromium/net/reporting/reporting_policy.h48
-rw-r--r--chromium/net/reporting/reporting_report.cc31
-rw-r--r--chromium/net/reporting/reporting_report.h60
-rw-r--r--chromium/net/reporting/reporting_service.cc76
-rw-r--r--chromium/net/reporting/reporting_service.h74
-rw-r--r--chromium/net/reporting/reporting_service_unittest.cc78
-rw-r--r--chromium/net/reporting/reporting_test_util.cc185
-rw-r--r--chromium/net/reporting/reporting_test_util.h195
-rw-r--r--chromium/net/reporting/reporting_uploader.cc154
-rw-r--r--chromium/net/reporting/reporting_uploader.h46
-rw-r--r--chromium/net/reporting/reporting_uploader_unittest.cc325
42 files changed, 4811 insertions, 0 deletions
diff --git a/chromium/net/reporting/README.md b/chromium/net/reporting/README.md
new file mode 100644
index 00000000000..82d663a25cf
--- /dev/null
+++ b/chromium/net/reporting/README.md
@@ -0,0 +1,77 @@
+# Reporting
+
+Reporting is a central mechanism for sending out-of-band error reports
+to origins from various other components (e.g. HTTP Public Key Pinning,
+Interventions, or Content Security Policy could potentially use it).
+
+The parts of it that are exposed to the web platform are specified in
+the [draft spec](http://wicg.github.io/reporting/). This document
+assumes that you've read that one.
+
+## Reporting in Chromium
+
+Reporting is implemented as part of the network stack in Chromium, such
+that it can be used by other parts of the network stack (e.g. HPKP) or
+by non-browser embedders as well as by Chromium.
+
+Almost all of Reporting lives in `//net/reporting`; there is a small
+amount of code in `//chrome/browser/net` to set up Reporting in
+profiles and provide a persistent store for reports and endpoints
+across browser restarts.
+
+### Inside `//net`
+
+* The top-level class is the *`ReportingService`*. This lives in the
+ `URLRequestContext`, and provides the high-level operations used by
+ other parts of `//net` and other components: queueing reports,
+ handling configuration headers, clearing browsing data, and so on.
+
+ * Within `ReportingService` lives *`ReportingContext`*, which in turn
+ contains the inner workings of Reporting, spread across several
+ classes:
+
+ * The *`ReportingCache`* stores undelivered reports and unexpired
+ endpoint configurations.
+
+ * The *`ReportingHeaderParser`* parses `Report-To:` headers and
+ updates the `Cache` accordingly.
+
+ * The *`ReportingDeliveryAgent`* reads reports from the `Cache`,
+ decides which endpoints to deliver them to, and attempts to
+ do so. It uses a couple of helper classes:
+
+ * The *`ReportingUploader`* does the low-level work of delivering
+ reports: accepts a URL and JSON from the `DeliveryAgent`,
+ creates a `URLRequest`, and parses the result.
+
+ * The *`ReportingEndpointManager`* keeps track of which endpoints
+ are in use, and manages exponential backoff (using
+ `BackoffEntry`) for failing endpoints.
+
+ * The *`ReportingGarbageCollector`* periodically examines the
+ `Cache` and removes reports that have remained undelivered for too
+ long, or that have failed delivery too many times.
+
+ * The *`ReportingSerializer`* reads the `Cache` and serializes it
+ into a `base::Value` for persistent storage (in Chromium, as a
+ pref); it can also deserialize a serialized `Value` back into the
+ `Cache`.
+
+ * The *`ReportingBrowsingDataRemover`* examines the `Cache` upon
+ request and removes browsing data (reports and endpoints) of
+ selected types and origins.
+
+### Outside `//net`
+
+* In `*ProfileImplIOData*::InitializeInternal`, the `ReportingService`
+ is created and set in the `URLRequestContext`, where the net stack
+ can use it.
+
+ (There is currently no interface to Reporting besides "hop over to
+ the IO thread and poke the `ReportingService` in your favorite
+ `URLRequestContext`", but that should change as various components
+ need to queue reports.)
+
+* *`ChromeReportingDelegate`* implements `ReportingDelegate` and plumbs
+ the persistent data interface into prefs. It lives in
+ `//chrome/browser/net`.
diff --git a/chromium/net/reporting/reporting_browsing_data_remover.cc b/chromium/net/reporting/reporting_browsing_data_remover.cc
new file mode 100644
index 00000000000..5041a47487c
--- /dev/null
+++ b/chromium/net/reporting/reporting_browsing_data_remover.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 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 "net/reporting/reporting_browsing_data_remover.h"
+
+#include <vector>
+
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_report.h"
+
+namespace net {
+
+// static
+void ReportingBrowsingDataRemover::RemoveBrowsingData(
+ ReportingContext* context,
+ int data_type_mask,
+ base::Callback<bool(const GURL&)> origin_filter) {
+ ReportingCache* cache = context->cache();
+ bool remove_reports = (data_type_mask & DATA_TYPE_REPORTS) != 0;
+ bool remove_clients = (data_type_mask & DATA_TYPE_CLIENTS) != 0;
+
+ if (origin_filter.is_null()) {
+ if (remove_reports)
+ cache->RemoveAllReports();
+ if (remove_clients)
+ cache->RemoveAllClients();
+ return;
+ }
+
+ if (remove_reports) {
+ std::vector<const ReportingReport*> all_reports;
+ cache->GetReports(&all_reports);
+
+ std::vector<const ReportingReport*> reports_to_remove;
+ for (const ReportingReport* report : all_reports) {
+ if (origin_filter.Run(report->url))
+ reports_to_remove.push_back(report);
+ }
+
+ cache->RemoveReports(reports_to_remove);
+ }
+
+ if (remove_clients) {
+ std::vector<const ReportingClient*> all_clients;
+ cache->GetClients(&all_clients);
+
+ std::vector<const ReportingClient*> clients_to_remove;
+ for (const ReportingClient* client : all_clients) {
+ if (origin_filter.Run(client->origin.GetURL()))
+ clients_to_remove.push_back(client);
+ }
+
+ cache->RemoveClients(clients_to_remove);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_browsing_data_remover.h b/chromium/net/reporting/reporting_browsing_data_remover.h
new file mode 100644
index 00000000000..db2bfb6df19
--- /dev/null
+++ b/chromium/net/reporting/reporting_browsing_data_remover.h
@@ -0,0 +1,36 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_BROWSING_DATA_REMOVER_H_
+#define NET_REPORTING_REPORTING_BROWSING_DATA_REMOVER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class ReportingContext;
+
+// Clears browsing data (reports and clients) from the Reporting system.
+class NET_EXPORT ReportingBrowsingDataRemover {
+ public:
+ enum DataType {
+ DATA_TYPE_REPORTS = 0x1,
+ DATA_TYPE_CLIENTS = 0x2,
+ };
+
+ static void RemoveBrowsingData(
+ ReportingContext* context,
+ int data_type_mask,
+ base::Callback<bool(const GURL&)> origin_filter);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ReportingBrowsingDataRemover);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_BROWSING_DATA_REMOVER_H_
diff --git a/chromium/net/reporting/reporting_browsing_data_remover_unittest.cc b/chromium/net/reporting/reporting_browsing_data_remover_unittest.cc
new file mode 100644
index 00000000000..349c5d531ac
--- /dev/null
+++ b/chromium/net/reporting/reporting_browsing_data_remover_unittest.cc
@@ -0,0 +1,193 @@
+// Copyright 2017 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 "net/reporting/reporting_browsing_data_remover.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+class ReportingBrowsingDataRemoverTest : public ReportingTestBase {
+ protected:
+ void RemoveBrowsingData(bool remove_reports,
+ bool remove_clients,
+ std::string host) {
+ int data_type_mask = 0;
+ if (remove_reports)
+ data_type_mask |= ReportingBrowsingDataRemover::DATA_TYPE_REPORTS;
+ if (remove_clients)
+ data_type_mask |= ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS;
+
+ base::Callback<bool(const GURL&)> origin_filter;
+ if (!host.empty()) {
+ origin_filter =
+ base::Bind(&ReportingBrowsingDataRemoverTest::HostIs, host);
+ }
+
+ ReportingBrowsingDataRemover::RemoveBrowsingData(context(), data_type_mask,
+ origin_filter);
+ }
+
+ static bool HostIs(std::string host, const GURL& url) {
+ return url.host() == host;
+ }
+
+ size_t report_count() {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ return reports.size();
+ }
+
+ size_t client_count() {
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClients(&clients);
+ return clients.size();
+ }
+
+ const GURL kUrl1_ = GURL("https://origin1/path");
+ const GURL kUrl2_ = GURL("https://origin2/path");
+ const url::Origin kOrigin1_ = url::Origin(kUrl1_);
+ const url::Origin kOrigin2_ = url::Origin(kUrl2_);
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "default";
+};
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveNothing) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ false, /* remove_clients= */ false,
+ /* host= */ "");
+ EXPECT_EQ(2u, report_count());
+ EXPECT_EQ(2u, client_count());
+}
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveAllReports) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ true, /* remove_clients= */ false,
+ /* host= */ "");
+ EXPECT_EQ(0u, report_count());
+ EXPECT_EQ(2u, client_count());
+}
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveAllClients) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ false, /* remove_clients= */ true,
+ /* host= */ "");
+ EXPECT_EQ(2u, report_count());
+ EXPECT_EQ(0u, client_count());
+}
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveAllReportsAndClients) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ true, /* remove_clients= */ true,
+ /* host= */ "");
+ EXPECT_EQ(0u, report_count());
+ EXPECT_EQ(0u, client_count());
+}
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveSomeReports) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ true, /* remove_clients= */ false,
+ /* host= */ kUrl1_.host());
+ EXPECT_EQ(2u, client_count());
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_EQ(kUrl2_, reports[0]->url);
+}
+
+TEST_F(ReportingBrowsingDataRemoverTest, RemoveSomeClients) {
+ cache()->AddReport(kUrl1_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl2_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->SetClient(kOrigin1_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+ cache()->SetClient(kOrigin2_, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(7));
+
+ RemoveBrowsingData(/* remove_reports= */ false, /* remove_clients= */ true,
+ /* host= */ kUrl1_.host());
+ EXPECT_EQ(2u, report_count());
+ EXPECT_FALSE(FindClientInCache(cache(), kOrigin1_, kEndpoint_) != nullptr);
+ EXPECT_TRUE(FindClientInCache(cache(), kOrigin2_, kEndpoint_) != nullptr);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_cache.cc b/chromium/net/reporting/reporting_cache.cc
new file mode 100644
index 00000000000..b3b60101eb8
--- /dev/null
+++ b/chromium/net/reporting/reporting_cache.cc
@@ -0,0 +1,271 @@
+// Copyright 2017 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 "net/reporting/reporting_cache.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_report.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+// Returns the superdomain of a given domain, or the empty string if the given
+// domain is just a single label. Note that this does not take into account
+// anything like the Public Suffix List, so the superdomain may end up being a
+// bare TLD.
+//
+// Examples:
+//
+// GetSuperdomain("assets.example.com") -> "example.com"
+// GetSuperdomain("example.net") -> "net"
+// GetSuperdomain("littlebox") -> ""
+std::string GetSuperdomain(const std::string& domain) {
+ size_t dot_pos = domain.find('.');
+ if (dot_pos == std::string::npos)
+ return "";
+
+ return domain.substr(dot_pos + 1);
+}
+
+} // namespace
+
+ReportingCache::ReportingCache(ReportingContext* context) : context_(context) {
+ DCHECK(context_);
+}
+
+ReportingCache::~ReportingCache() {}
+
+void ReportingCache::AddReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body,
+ base::TimeTicks queued,
+ int attempts) {
+ auto report = base::MakeUnique<ReportingReport>(
+ url, group, type, std::move(body), queued, attempts);
+
+ auto inserted =
+ reports_.insert(std::make_pair(report.get(), std::move(report)));
+ DCHECK(inserted.second);
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::GetReports(
+ std::vector<const ReportingReport*>* reports_out) const {
+ reports_out->clear();
+ for (const auto& it : reports_) {
+ if (!base::ContainsKey(doomed_reports_, it.first))
+ reports_out->push_back(it.second.get());
+ }
+}
+
+void ReportingCache::SetReportsPending(
+ const std::vector<const ReportingReport*>& reports) {
+ for (const ReportingReport* report : reports) {
+ auto inserted = pending_reports_.insert(report);
+ DCHECK(inserted.second);
+ }
+}
+
+void ReportingCache::ClearReportsPending(
+ const std::vector<const ReportingReport*>& reports) {
+ std::vector<const ReportingReport*> reports_to_remove;
+
+ for (const ReportingReport* report : reports) {
+ size_t erased = pending_reports_.erase(report);
+ DCHECK_EQ(1u, erased);
+ if (base::ContainsKey(doomed_reports_, report)) {
+ reports_to_remove.push_back(report);
+ doomed_reports_.erase(report);
+ }
+ }
+
+ RemoveReports(reports_to_remove);
+}
+
+void ReportingCache::IncrementReportsAttempts(
+ const std::vector<const ReportingReport*>& reports) {
+ for (const ReportingReport* report : reports) {
+ DCHECK(base::ContainsKey(reports_, report));
+ reports_[report]->attempts++;
+ }
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveReports(
+ const std::vector<const ReportingReport*>& reports) {
+ for (const ReportingReport* report : reports) {
+ if (base::ContainsKey(pending_reports_, report)) {
+ doomed_reports_.insert(report);
+ } else {
+ DCHECK(!base::ContainsKey(doomed_reports_, report));
+ size_t erased = reports_.erase(report);
+ DCHECK_EQ(1u, erased);
+ }
+ }
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveAllReports() {
+ std::vector<std::unordered_map<const ReportingReport*,
+ std::unique_ptr<ReportingReport>>::iterator>
+ reports_to_remove;
+ for (auto it = reports_.begin(); it != reports_.end(); ++it) {
+ ReportingReport* report = it->second.get();
+ if (!base::ContainsKey(pending_reports_, report))
+ reports_to_remove.push_back(it);
+ else
+ doomed_reports_.insert(report);
+ }
+
+ for (auto& it : reports_to_remove)
+ reports_.erase(it);
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::GetClients(
+ std::vector<const ReportingClient*>* clients_out) const {
+ clients_out->clear();
+ for (const auto& it : clients_)
+ for (const auto& endpoint_and_client : it.second)
+ clients_out->push_back(endpoint_and_client.second.get());
+}
+
+void ReportingCache::GetClientsForOriginAndGroup(
+ const url::Origin& origin,
+ const std::string& group,
+ std::vector<const ReportingClient*>* clients_out) const {
+ clients_out->clear();
+
+ const auto it = clients_.find(origin);
+ if (it != clients_.end()) {
+ for (const auto& endpoint_and_client : it->second) {
+ if (endpoint_and_client.second->group == group)
+ clients_out->push_back(endpoint_and_client.second.get());
+ }
+ }
+
+ // If no clients were found, try successive superdomain suffixes until a
+ // client with includeSubdomains is found or there are no more domain
+ // components left.
+ std::string domain = origin.host();
+ while (clients_out->empty() && !domain.empty()) {
+ GetWildcardClientsForDomainAndGroup(domain, group, clients_out);
+ domain = GetSuperdomain(domain);
+ }
+}
+
+void ReportingCache::SetClient(const url::Origin& origin,
+ const GURL& endpoint,
+ ReportingClient::Subdomains subdomains,
+ const std::string& group,
+ base::TimeTicks expires) {
+ DCHECK(endpoint.SchemeIsCryptographic());
+
+ // Since |subdomains| may differ from a previous call to SetClient for this
+ // origin and endpoint, the cache needs to remove and re-add the client to the
+ // index of wildcard clients, if applicable.
+ if (base::ContainsKey(clients_, origin) &&
+ base::ContainsKey(clients_[origin], endpoint)) {
+ MaybeRemoveWildcardClient(clients_[origin][endpoint].get());
+ }
+
+ clients_[origin][endpoint] = base::MakeUnique<ReportingClient>(
+ origin, endpoint, subdomains, group, expires);
+
+ MaybeAddWildcardClient(clients_[origin][endpoint].get());
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveClients(
+ const std::vector<const ReportingClient*>& clients_to_remove) {
+ for (const ReportingClient* client : clients_to_remove) {
+ MaybeRemoveWildcardClient(client);
+ size_t erased = clients_[client->origin].erase(client->endpoint);
+ DCHECK_EQ(1u, erased);
+ }
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveClientForOriginAndEndpoint(const url::Origin& origin,
+ const GURL& endpoint) {
+ MaybeRemoveWildcardClient(clients_[origin][endpoint].get());
+ size_t erased = clients_[origin].erase(endpoint);
+ DCHECK_EQ(1u, erased);
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveClientsForEndpoint(const GURL& endpoint) {
+ for (auto& origin_and_endpoints : clients_) {
+ if (base::ContainsKey(origin_and_endpoints.second, endpoint)) {
+ MaybeRemoveWildcardClient(origin_and_endpoints.second[endpoint].get());
+ origin_and_endpoints.second.erase(endpoint);
+ }
+ }
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::RemoveAllClients() {
+ clients_.clear();
+ wildcard_clients_.clear();
+
+ context_->NotifyCacheUpdated();
+}
+
+void ReportingCache::MaybeAddWildcardClient(const ReportingClient* client) {
+ if (client->subdomains != ReportingClient::Subdomains::INCLUDE)
+ return;
+
+ const std::string& domain = client->origin.host();
+ auto inserted = wildcard_clients_[domain].insert(client);
+ DCHECK(inserted.second);
+}
+
+void ReportingCache::MaybeRemoveWildcardClient(const ReportingClient* client) {
+ if (client->subdomains != ReportingClient::Subdomains::INCLUDE)
+ return;
+
+ const std::string& domain = client->origin.host();
+ size_t erased = wildcard_clients_[domain].erase(client);
+ DCHECK_EQ(1u, erased);
+}
+
+void ReportingCache::GetWildcardClientsForDomainAndGroup(
+ const std::string& domain,
+ const std::string& group,
+ std::vector<const ReportingClient*>* clients_out) const {
+ clients_out->clear();
+
+ auto it = wildcard_clients_.find(domain);
+ if (it == wildcard_clients_.end())
+ return;
+
+ for (const ReportingClient* client : it->second) {
+ DCHECK_EQ(ReportingClient::Subdomains::INCLUDE, client->subdomains);
+ if (client->group == group)
+ clients_out->push_back(client);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_cache.h b/chromium/net/reporting/reporting_cache.h
new file mode 100644
index 00000000000..30ad0429e16
--- /dev/null
+++ b/chromium/net/reporting/reporting_cache.h
@@ -0,0 +1,201 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_CACHE_H_
+#define NET_REPORTING_REPORTING_CACHE_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/net_export.h"
+#include "net/reporting/reporting_client.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+class ReportingContext;
+struct ReportingReport;
+
+// The cache holds undelivered reports and clients (per-origin endpoint
+// configurations) in memory. (It is not responsible for persisting them.)
+//
+// This corresponds roughly to the "Reporting cache" in the spec, except that
+// endpoints and clients are stored in a more structurally-convenient way, and
+// endpoint failures/retry-after are tracked in ReportingEndpointManager.
+//
+// The cache implementation has the notion of "pending" reports. These are
+// reports that are part of an active delivery attempt, so they won't be
+// actually deallocated. Any attempt to remove a pending report wil mark it
+// "doomed", which will cause it to be deallocated once it is no longer pending.
+class NET_EXPORT ReportingCache {
+ public:
+ // |context| must outlive the ReportingCache.
+ ReportingCache(ReportingContext* context);
+
+ ~ReportingCache();
+
+ // Adds a report to the cache.
+ //
+ // All parameters correspond to the desired values for the relevant fields in
+ // ReportingReport.
+ void AddReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body,
+ base::TimeTicks queued,
+ int attempts);
+
+ // Gets all reports in the cache. The returned pointers are valid as long as
+ // either no calls to |RemoveReports| have happened or the reports' |pending|
+ // flag has been set to true using |SetReportsPending|. Does not return
+ // doomed reports (pending reports for which removal has been requested).
+ //
+ // (Clears any existing data in |*reports_out|.)
+ void GetReports(std::vector<const ReportingReport*>* reports_out) const;
+
+ // Marks a set of reports as pending. |reports| must not already be marked as
+ // pending.
+ void SetReportsPending(const std::vector<const ReportingReport*>& reports);
+
+ // Unmarks a set of reports as pending. |reports| must be previously marked as
+ // pending.
+ void ClearReportsPending(const std::vector<const ReportingReport*>& reports);
+
+ // Increments |attempts| on a set of reports.
+ void IncrementReportsAttempts(
+ const std::vector<const ReportingReport*>& reports);
+
+ // Removes a set of reports. Any reports that are pending will not be removed
+ // immediately, but rather marked doomed and removed once they are no longer
+ // pending.
+ void RemoveReports(const std::vector<const ReportingReport*>& reports);
+
+ // Removes all reports. Like |RemoveReports()|, pending reports are doomed
+ // until no longer pending.
+ void RemoveAllReports();
+
+ // Creates or updates a client for a particular origin and a particular
+ // endpoint.
+ //
+ // All parameters correspond to the desired values for the fields in
+ // |Client|.
+ //
+ // |endpoint| must use a cryptographic scheme.
+ void SetClient(const url::Origin& origin,
+ const GURL& endpoint,
+ ReportingClient::Subdomains subdomains,
+ const std::string& group,
+ base::TimeTicks expires);
+
+ // Gets all of the clients in the cache, regardless of origin or group.
+ //
+ // (Clears any existing data in |*clients_out|.)
+ void GetClients(std::vector<const ReportingClient*>* clients_out) const;
+
+ // Gets all of the clients configured for a particular origin in a particular
+ // group. The returned pointers are only guaranteed to be valid if no calls
+ // have been made to |SetClient| or |RemoveEndpoint| in between.
+ //
+ // If no origin match is found, the cache will return clients from the most
+ // specific superdomain which contains any clients with includeSubdomains set.
+ // For example, given the origin https://foo.bar.baz.com/, the cache would
+ // prioritize returning each potential match below over the ones below it:
+ //
+ // 1. https://foo.bar.baz.com/ (exact origin match)
+ // 2. https://foo.bar.baz.com:444/ (technically, a superdomain)
+ // 3. https://bar.baz.com/, https://bar.baz.com:444/, etc. (superdomain)
+ // 4. https://baz.com/, https://baz.com:444/, etc. (superdomain)
+ // etc.
+ //
+ // (Clears any existing data in |*clients_out|.)
+ void GetClientsForOriginAndGroup(
+ const url::Origin& origin,
+ const std::string& group,
+ std::vector<const ReportingClient*>* clients_out) const;
+
+ // Removes a set of clients.
+ //
+ // May invalidate ReportingClient pointers returned by |GetClients| or
+ // |GetClientsForOriginAndGroup|.
+ void RemoveClients(const std::vector<const ReportingClient*>& clients);
+
+ // Removes a client for a particular origin and a particular endpoint.
+ void RemoveClientForOriginAndEndpoint(const url::Origin& origin,
+ const GURL& endpoint);
+
+ // Removes all clients whose endpoint is |endpoint|.
+ //
+ // May invalidate ReportingClient pointers returned by |GetClients| or
+ // |GetClientsForOriginAndGroup|.
+ void RemoveClientsForEndpoint(const GURL& endpoint);
+
+ // Removes all clients.
+ void RemoveAllClients();
+
+ // Gets the count of reports in the cache, *including* doomed reports.
+ //
+ // Needed to ensure that doomed reports are eventually deleted, since no
+ // method provides a view of *every* report in the cache, just non-doomed
+ // ones.
+ size_t GetFullReportCountForTesting() const { return reports_.size(); }
+
+ bool IsReportPendingForTesting(const ReportingReport* report) const {
+ return base::ContainsKey(pending_reports_, report);
+ }
+
+ bool IsReportDoomedForTesting(const ReportingReport* report) const {
+ return base::ContainsKey(doomed_reports_, report);
+ }
+
+ private:
+ void MaybeAddWildcardClient(const ReportingClient* client);
+
+ void MaybeRemoveWildcardClient(const ReportingClient* client);
+
+ void GetWildcardClientsForDomainAndGroup(
+ const std::string& domain,
+ const std::string& group,
+ std::vector<const ReportingClient*>* clients_out) const;
+
+ ReportingContext* context_;
+
+ // Owns all clients, keyed by origin, then endpoint URL.
+ // (These would be unordered_map, but neither url::Origin nor GURL has a hash
+ // function implemented.)
+ std::map<url::Origin, std::map<GURL, std::unique_ptr<ReportingClient>>>
+ clients_;
+
+ // References but does not own all clients with includeSubdomains set, keyed
+ // by domain name.
+ std::unordered_map<std::string, std::unordered_set<const ReportingClient*>>
+ wildcard_clients_;
+
+ // Owns all reports, keyed by const raw pointer for easier lookup.
+ std::unordered_map<const ReportingReport*, std::unique_ptr<ReportingReport>>
+ reports_;
+
+ // Reports that have been marked pending (in use elsewhere and should not be
+ // deleted until no longer pending).
+ std::unordered_set<const ReportingReport*> pending_reports_;
+
+ // Reports that have been marked doomed (would have been deleted, but were
+ // pending when the deletion was requested).
+ std::unordered_set<const ReportingReport*> doomed_reports_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingCache);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_CACHE_H_
diff --git a/chromium/net/reporting/reporting_cache_unittest.cc b/chromium/net/reporting/reporting_cache_unittest.cc
new file mode 100644
index 00000000000..73023400392
--- /dev/null
+++ b/chromium/net/reporting/reporting_cache_unittest.cc
@@ -0,0 +1,404 @@
+// Copyright 2017 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 "net/reporting/reporting_cache.h"
+
+#include <string>
+
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_observer.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+namespace {
+
+class TestReportingObserver : public ReportingObserver {
+ public:
+ TestReportingObserver() : cache_update_count_(0) {}
+
+ void OnCacheUpdated() override { ++cache_update_count_; }
+
+ int cache_update_count() const { return cache_update_count_; }
+
+ private:
+ int cache_update_count_;
+};
+
+class ReportingCacheTest : public ReportingTestBase {
+ protected:
+ ReportingCacheTest() : ReportingTestBase() {
+ context()->AddObserver(&observer_);
+ }
+
+ ~ReportingCacheTest() override { context()->RemoveObserver(&observer_); }
+
+ TestReportingObserver* observer() { return &observer_; }
+
+ const GURL kUrl1_ = GURL("https://origin1/path");
+ const url::Origin kOrigin1_ = url::Origin(GURL("https://origin1/"));
+ const url::Origin kOrigin2_ = url::Origin(GURL("https://origin2/"));
+ const GURL kEndpoint1_ = GURL("https://endpoint1/");
+ const GURL kEndpoint2_ = GURL("https://endpoint2/");
+ const std::string kGroup1_ = "group1";
+ const std::string kGroup2 = "group2";
+ const std::string kType_ = "default";
+ const base::TimeTicks kNow_ = base::TimeTicks::Now();
+ const base::TimeTicks kExpires1_ = kNow_ + base::TimeDelta::FromDays(7);
+ const base::TimeTicks kExpires2_ = kExpires1_ + base::TimeDelta::FromDays(7);
+
+ private:
+ TestReportingObserver observer_;
+};
+
+TEST_F(ReportingCacheTest, Reports) {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+
+ cache()->AddReport(kUrl1_, kGroup1_, kType_,
+ base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+ EXPECT_EQ(1, observer()->cache_update_count());
+
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ const ReportingReport* report = reports[0];
+ ASSERT_TRUE(report);
+ EXPECT_EQ(kUrl1_, report->url);
+ EXPECT_EQ(kGroup1_, report->group);
+ EXPECT_EQ(kType_, report->type);
+ // TODO(juliatuttle): Check body?
+ EXPECT_EQ(kNow_, report->queued);
+ EXPECT_EQ(0, report->attempts);
+ EXPECT_FALSE(cache()->IsReportPendingForTesting(report));
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
+
+ cache()->IncrementReportsAttempts(reports);
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ report = reports[0];
+ ASSERT_TRUE(report);
+ EXPECT_EQ(1, report->attempts);
+
+ cache()->RemoveReports(reports);
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+}
+
+TEST_F(ReportingCacheTest, RemoveAllReports) {
+ cache()->AddReport(kUrl1_, kGroup1_, kType_,
+ base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+ cache()->AddReport(kUrl1_, kGroup1_, kType_,
+ base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ EXPECT_EQ(2u, reports.size());
+
+ cache()->RemoveAllReports();
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+}
+
+TEST_F(ReportingCacheTest, RemovePendingReports) {
+ cache()->AddReport(kUrl1_, kGroup1_, kType_,
+ base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+ EXPECT_EQ(1, observer()->cache_update_count());
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_FALSE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(reports[0]));
+
+ cache()->SetReportsPending(reports);
+ EXPECT_TRUE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(reports[0]));
+
+ cache()->RemoveReports(reports);
+ EXPECT_TRUE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_TRUE(cache()->IsReportDoomedForTesting(reports[0]));
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ // After removing report, future calls to GetReports should not return it.
+ std::vector<const ReportingReport*> visible_reports;
+ cache()->GetReports(&visible_reports);
+ EXPECT_TRUE(visible_reports.empty());
+ EXPECT_EQ(1u, cache()->GetFullReportCountForTesting());
+
+ // After clearing pending flag, report should be deleted.
+ cache()->ClearReportsPending(reports);
+ EXPECT_EQ(0u, cache()->GetFullReportCountForTesting());
+}
+
+TEST_F(ReportingCacheTest, RemoveAllPendingReports) {
+ cache()->AddReport(kUrl1_, kGroup1_, kType_,
+ base::MakeUnique<base::DictionaryValue>(), kNow_, 0);
+ EXPECT_EQ(1, observer()->cache_update_count());
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_FALSE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(reports[0]));
+
+ cache()->SetReportsPending(reports);
+ EXPECT_TRUE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(reports[0]));
+
+ cache()->RemoveAllReports();
+ EXPECT_TRUE(cache()->IsReportPendingForTesting(reports[0]));
+ EXPECT_TRUE(cache()->IsReportDoomedForTesting(reports[0]));
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ // After removing report, future calls to GetReports should not return it.
+ std::vector<const ReportingReport*> visible_reports;
+ cache()->GetReports(&visible_reports);
+ EXPECT_TRUE(visible_reports.empty());
+ EXPECT_EQ(1u, cache()->GetFullReportCountForTesting());
+
+ // After clearing pending flag, report should be deleted.
+ cache()->ClearReportsPending(reports);
+ EXPECT_EQ(0u, cache()->GetFullReportCountForTesting());
+}
+
+TEST_F(ReportingCacheTest, Endpoints) {
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ EXPECT_EQ(1, observer()->cache_update_count());
+
+ const ReportingClient* client =
+ FindClientInCache(cache(), kOrigin1_, kEndpoint1_);
+ ASSERT_TRUE(client);
+ EXPECT_EQ(kOrigin1_, client->origin);
+ EXPECT_EQ(kEndpoint1_, client->endpoint);
+ EXPECT_EQ(ReportingClient::Subdomains::EXCLUDE, client->subdomains);
+ EXPECT_EQ(kGroup1_, client->group);
+ EXPECT_EQ(kExpires1_, client->expires);
+
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup2, kExpires2_);
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ client = FindClientInCache(cache(), kOrigin1_, kEndpoint1_);
+ ASSERT_TRUE(client);
+ EXPECT_EQ(kOrigin1_, client->origin);
+ EXPECT_EQ(kEndpoint1_, client->endpoint);
+ EXPECT_EQ(ReportingClient::Subdomains::INCLUDE, client->subdomains);
+ EXPECT_EQ(kGroup2, client->group);
+ EXPECT_EQ(kExpires2_, client->expires);
+
+ cache()->RemoveClients(std::vector<const ReportingClient*>{client});
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ client = FindClientInCache(cache(), kOrigin1_, kEndpoint1_);
+ EXPECT_FALSE(client);
+}
+
+TEST_F(ReportingCacheTest, GetClientsForOriginAndGroup) {
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ cache()->SetClient(kOrigin1_, kEndpoint2_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup2, kExpires1_);
+ cache()->SetClient(kOrigin2_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin1_, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ const ReportingClient* client = clients[0];
+ ASSERT_TRUE(client);
+ EXPECT_EQ(kOrigin1_, client->origin);
+ EXPECT_EQ(kGroup1_, client->group);
+}
+
+TEST_F(ReportingCacheTest, RemoveClientForOriginAndEndpoint) {
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ cache()->SetClient(kOrigin1_, kEndpoint2_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup2, kExpires1_);
+ cache()->SetClient(kOrigin2_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ cache()->RemoveClientForOriginAndEndpoint(kOrigin1_, kEndpoint1_);
+ EXPECT_EQ(4, observer()->cache_update_count());
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin1_, kGroup1_, &clients);
+ EXPECT_TRUE(clients.empty());
+
+ cache()->GetClientsForOriginAndGroup(kOrigin1_, kGroup2, &clients);
+ EXPECT_EQ(1u, clients.size());
+
+ cache()->GetClientsForOriginAndGroup(kOrigin2_, kGroup1_, &clients);
+ EXPECT_EQ(1u, clients.size());
+}
+
+TEST_F(ReportingCacheTest, RemoveClientsForEndpoint) {
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ cache()->SetClient(kOrigin1_, kEndpoint2_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup2, kExpires1_);
+ cache()->SetClient(kOrigin2_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ cache()->RemoveClientsForEndpoint(kEndpoint1_);
+ EXPECT_EQ(4, observer()->cache_update_count());
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin1_, kGroup1_, &clients);
+ EXPECT_TRUE(clients.empty());
+
+ cache()->GetClientsForOriginAndGroup(kOrigin1_, kGroup2, &clients);
+ EXPECT_EQ(1u, clients.size());
+
+ cache()->GetClientsForOriginAndGroup(kOrigin2_, kGroup1_, &clients);
+ EXPECT_TRUE(clients.empty());
+}
+
+TEST_F(ReportingCacheTest, RemoveAllClients) {
+ cache()->SetClient(kOrigin1_, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ cache()->SetClient(kOrigin2_, kEndpoint2_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+ EXPECT_EQ(2, observer()->cache_update_count());
+
+ cache()->RemoveAllClients();
+ EXPECT_EQ(3, observer()->cache_update_count());
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClients(&clients);
+ EXPECT_TRUE(clients.empty());
+}
+
+TEST_F(ReportingCacheTest, ExcludeSubdomainsDifferentPort) {
+ const url::Origin kOrigin(GURL("https://example/"));
+ const url::Origin kDifferentPortOrigin(GURL("https://example:444/"));
+
+ cache()->SetClient(kDifferentPortOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ EXPECT_TRUE(clients.empty());
+}
+
+TEST_F(ReportingCacheTest, ExcludeSubdomainsSuperdomain) {
+ const url::Origin kOrigin(GURL("https://foo.example/"));
+ const url::Origin kSuperOrigin(GURL("https://example/"));
+
+ cache()->SetClient(kSuperOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ EXPECT_TRUE(clients.empty());
+}
+
+TEST_F(ReportingCacheTest, IncludeSubdomainsDifferentPort) {
+ const url::Origin kOrigin(GURL("https://example/"));
+ const url::Origin kDifferentPortOrigin(GURL("https://example:444/"));
+
+ cache()->SetClient(kDifferentPortOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ EXPECT_EQ(kDifferentPortOrigin, clients[0]->origin);
+}
+
+TEST_F(ReportingCacheTest, IncludeSubdomainsSuperdomain) {
+ const url::Origin kOrigin(GURL("https://foo.example/"));
+ const url::Origin kSuperOrigin(GURL("https://example/"));
+
+ cache()->SetClient(kSuperOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ EXPECT_EQ(kSuperOrigin, clients[0]->origin);
+}
+
+TEST_F(ReportingCacheTest, IncludeSubdomainsPreferOriginToDifferentPort) {
+ const url::Origin kOrigin(GURL("https://foo.example/"));
+ const url::Origin kDifferentPortOrigin(GURL("https://example:444/"));
+
+ cache()->SetClient(kOrigin, kEndpoint1_, ReportingClient::Subdomains::INCLUDE,
+ kGroup1_, kExpires1_);
+ cache()->SetClient(kDifferentPortOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ EXPECT_EQ(kOrigin, clients[0]->origin);
+}
+
+TEST_F(ReportingCacheTest, IncludeSubdomainsPreferOriginToSuperdomain) {
+ const url::Origin kOrigin(GURL("https://foo.example/"));
+ const url::Origin kSuperOrigin(GURL("https://example/"));
+
+ cache()->SetClient(kOrigin, kEndpoint1_, ReportingClient::Subdomains::INCLUDE,
+ kGroup1_, kExpires1_);
+ cache()->SetClient(kSuperOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ EXPECT_EQ(kOrigin, clients[0]->origin);
+}
+
+TEST_F(ReportingCacheTest, IncludeSubdomainsPreferMoreSpecificSuperdomain) {
+ const url::Origin kOrigin(GURL("https://foo.bar.example/"));
+ const url::Origin kSuperOrigin(GURL("https://bar.example/"));
+ const url::Origin kSuperSuperOrigin(GURL("https://example/"));
+
+ cache()->SetClient(kSuperOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+ cache()->SetClient(kSuperSuperOrigin, kEndpoint1_,
+ ReportingClient::Subdomains::INCLUDE, kGroup1_,
+ kExpires1_);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(kOrigin, kGroup1_, &clients);
+ ASSERT_EQ(1u, clients.size());
+ EXPECT_EQ(kSuperOrigin, clients[0]->origin);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_client.cc b/chromium/net/reporting/reporting_client.cc
new file mode 100644
index 00000000000..d8c8a0f35b7
--- /dev/null
+++ b/chromium/net/reporting/reporting_client.cc
@@ -0,0 +1,28 @@
+// Copyright 2017 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 "net/reporting/reporting_client.h"
+
+#include <string>
+
+#include "base/time/time.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+ReportingClient::ReportingClient(const url::Origin& origin,
+ const GURL& endpoint,
+ Subdomains subdomains,
+ const std::string& group,
+ base::TimeTicks expires)
+ : origin(origin),
+ endpoint(endpoint),
+ subdomains(subdomains),
+ group(group),
+ expires(expires) {}
+
+ReportingClient::~ReportingClient() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_client.h b/chromium/net/reporting/reporting_client.h
new file mode 100644
index 00000000000..0aa11bea57d
--- /dev/null
+++ b/chromium/net/reporting/reporting_client.h
@@ -0,0 +1,53 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_CLIENT_H_
+#define NET_REPORTING_REPORTING_CLIENT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+// The configuration by an origin to use an endpoint for report delivery.
+struct NET_EXPORT ReportingClient {
+ public:
+ enum class Subdomains { EXCLUDE = 0, INCLUDE = 1 };
+
+ ReportingClient(const url::Origin& origin,
+ const GURL& endpoint,
+ Subdomains subdomains,
+ const std::string& group,
+ base::TimeTicks expires);
+ ~ReportingClient();
+
+ // The origin from which reports will be delivered.
+ url::Origin origin;
+
+ // The endpoint to which reports may be delivered. (Origins may configure
+ // many.)
+ GURL endpoint;
+
+ // Whether subdomains of the host of |origin| should also be handled by this
+ // client.
+ Subdomains subdomains = Subdomains::EXCLUDE;
+
+ // The endpoint group to which this client belongs.
+ std::string group = "default";
+
+ // When this client's max-age has expired.
+ base::TimeTicks expires;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReportingClient);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_CLIENT_H_
diff --git a/chromium/net/reporting/reporting_context.cc b/chromium/net/reporting/reporting_context.cc
new file mode 100644
index 00000000000..1dd255bcb1b
--- /dev/null
+++ b/chromium/net/reporting/reporting_context.cc
@@ -0,0 +1,102 @@
+// Copyright 2017 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 "net/reporting/reporting_context.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/observer_list.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/backoff_entry.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_delivery_agent.h"
+#include "net/reporting/reporting_endpoint_manager.h"
+#include "net/reporting/reporting_garbage_collector.h"
+#include "net/reporting/reporting_observer.h"
+#include "net/reporting/reporting_persister.h"
+#include "net/reporting/reporting_policy.h"
+
+namespace net {
+
+class URLRequestContext;
+
+namespace {
+
+class ReportingContextImpl : public ReportingContext {
+ public:
+ ReportingContextImpl(const ReportingPolicy& policy,
+ std::unique_ptr<ReportingDelegate> delegate,
+ URLRequestContext* request_context)
+ : ReportingContext(policy,
+ std::move(delegate),
+ base::MakeUnique<base::DefaultClock>(),
+ base::MakeUnique<base::DefaultTickClock>(),
+ ReportingUploader::Create(request_context)) {}
+};
+
+} // namespace
+
+// static
+std::unique_ptr<ReportingContext> ReportingContext::Create(
+ const ReportingPolicy& policy,
+ std::unique_ptr<ReportingDelegate> delegate,
+ URLRequestContext* request_context) {
+ return base::MakeUnique<ReportingContextImpl>(policy, std::move(delegate),
+ request_context);
+}
+
+ReportingContext::~ReportingContext() {}
+
+void ReportingContext::Initialize() {
+ DCHECK(!initialized_);
+
+ persister_->Initialize();
+ garbage_collector_->Initialize();
+
+ initialized_ = true;
+}
+
+void ReportingContext::AddObserver(ReportingObserver* observer) {
+ DCHECK(!observers_.HasObserver(observer));
+ observers_.AddObserver(observer);
+}
+
+void ReportingContext::RemoveObserver(ReportingObserver* observer) {
+ DCHECK(observers_.HasObserver(observer));
+ observers_.RemoveObserver(observer);
+}
+
+void ReportingContext::NotifyCacheUpdated() {
+ if (!initialized_)
+ return;
+
+ for (auto& observer : observers_)
+ observer.OnCacheUpdated();
+}
+
+ReportingContext::ReportingContext(const ReportingPolicy& policy,
+ std::unique_ptr<ReportingDelegate> delegate,
+ std::unique_ptr<base::Clock> clock,
+ std::unique_ptr<base::TickClock> tick_clock,
+ std::unique_ptr<ReportingUploader> uploader)
+ : policy_(policy),
+ delegate_(std::move(delegate)),
+ clock_(std::move(clock)),
+ tick_clock_(std::move(tick_clock)),
+ uploader_(std::move(uploader)),
+ initialized_(false),
+ cache_(base::MakeUnique<ReportingCache>(this)),
+ endpoint_manager_(base::MakeUnique<ReportingEndpointManager>(this)),
+ delivery_agent_(base::MakeUnique<ReportingDeliveryAgent>(this)),
+ persister_(ReportingPersister::Create(this)),
+ garbage_collector_(ReportingGarbageCollector::Create(this)) {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_context.h b/chromium/net/reporting/reporting_context.h
new file mode 100644
index 00000000000..75aa11918ae
--- /dev/null
+++ b/chromium/net/reporting/reporting_context.h
@@ -0,0 +1,116 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_CONTEXT_H_
+#define NET_REPORTING_REPORTING_CONTEXT_H_
+
+#include <memory>
+
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_export.h"
+#include "net/reporting/reporting_policy.h"
+
+namespace base {
+class Clock;
+class TickClock;
+} // namespace base
+
+namespace net {
+
+class ReportingCache;
+class ReportingDelegate;
+class ReportingDeliveryAgent;
+class ReportingEndpointManager;
+class ReportingGarbageCollector;
+class ReportingObserver;
+class ReportingPersister;
+class ReportingUploader;
+class URLRequestContext;
+
+// Contains the various internal classes that make up the Reporting system.
+// Wrapped by ReportingService, which provides the external interface.
+class NET_EXPORT ReportingContext {
+ public:
+ static std::unique_ptr<ReportingContext> Create(
+ const ReportingPolicy& policy,
+ std::unique_ptr<ReportingDelegate> delegate,
+ URLRequestContext* request_context);
+
+ ~ReportingContext();
+
+ // Initializes the ReportingContext. This may take a while (e.g. it may
+ // involve reloading state persisted to disk). Should be called only once.
+ //
+ // Components of the ReportingContext won't reference their dependencies (e.g.
+ // the Clock/TickClock or Timers inside the individual components) until
+ // during/after the call to Init.
+ void Initialize();
+
+ bool initialized() const { return initialized_; }
+
+ const ReportingPolicy& policy() { return policy_; }
+ ReportingDelegate* delegate() { return delegate_.get(); }
+
+ base::Clock* clock() { return clock_.get(); }
+ base::TickClock* tick_clock() { return tick_clock_.get(); }
+ ReportingUploader* uploader() { return uploader_.get(); }
+
+ ReportingCache* cache() { return cache_.get(); }
+ ReportingEndpointManager* endpoint_manager() {
+ return endpoint_manager_.get();
+ }
+ ReportingDeliveryAgent* delivery_agent() { return delivery_agent_.get(); }
+ ReportingGarbageCollector* garbage_collector() {
+ return garbage_collector_.get();
+ }
+
+ ReportingPersister* persister() { return persister_.get(); }
+
+ void AddObserver(ReportingObserver* observer);
+ void RemoveObserver(ReportingObserver* observer);
+
+ void NotifyCacheUpdated();
+
+ protected:
+ ReportingContext(const ReportingPolicy& policy,
+ std::unique_ptr<ReportingDelegate> delegate,
+ std::unique_ptr<base::Clock> clock,
+ std::unique_ptr<base::TickClock> tick_clock,
+ std::unique_ptr<ReportingUploader> uploader);
+
+ private:
+ ReportingPolicy policy_;
+ std::unique_ptr<ReportingDelegate> delegate_;
+
+ std::unique_ptr<base::Clock> clock_;
+ std::unique_ptr<base::TickClock> tick_clock_;
+ std::unique_ptr<ReportingUploader> uploader_;
+
+ base::ObserverList<ReportingObserver, /* check_empty= */ true> observers_;
+ bool initialized_;
+
+ std::unique_ptr<ReportingCache> cache_;
+
+ // |endpoint_manager_| must come after |tick_clock_| and |cache_|.
+ std::unique_ptr<ReportingEndpointManager> endpoint_manager_;
+
+ // |delivery_agent_| must come after |tick_clock_|, |uploader_|, |cache_|,
+ // and |endpoint_manager_|.
+ std::unique_ptr<ReportingDeliveryAgent> delivery_agent_;
+
+ // |persister_| must come after |delegate_|, |clock_|, |tick_clock_|, and
+ // |cache_|.
+ std::unique_ptr<ReportingPersister> persister_;
+
+ // |garbage_collector_| must come after |tick_clock_| and |cache_|.
+ std::unique_ptr<ReportingGarbageCollector> garbage_collector_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingContext);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_CONTEXT_H_
diff --git a/chromium/net/reporting/reporting_delegate.cc b/chromium/net/reporting/reporting_delegate.cc
new file mode 100644
index 00000000000..cef5bafa28c
--- /dev/null
+++ b/chromium/net/reporting/reporting_delegate.cc
@@ -0,0 +1,13 @@
+// Copyright 2017 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 "net/reporting/reporting_delegate.h"
+
+namespace net {
+
+ReportingDelegate::~ReportingDelegate() {}
+
+ReportingDelegate::ReportingDelegate() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_delegate.h b/chromium/net/reporting/reporting_delegate.h
new file mode 100644
index 00000000000..57d01ab4a6a
--- /dev/null
+++ b/chromium/net/reporting/reporting_delegate.h
@@ -0,0 +1,45 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_DELEGATE_H_
+#define NET_REPORTING_REPORTING_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace net {
+
+// Delegate for things that the Reporting system can't do by itself, like
+// persisting data across embedder restarts.
+class NET_EXPORT ReportingDelegate {
+ public:
+ virtual ~ReportingDelegate();
+
+ // Gets previously persisted data, if any is available. Returns a null pointer
+ // if no data is available. Can be called any number of times.
+ virtual std::unique_ptr<const base::Value> GetPersistedData() = 0;
+
+ // Sets data to be persisted across embedder restarts. Ideally, this data will
+ // be returned by any future calls to GetPersistedData() in this or future
+ // sessions (until newer data is persisted), but no guarantee is made, since
+ // the underlying persistence mechanism may or may not be reliable.
+ virtual void PersistData(
+ std::unique_ptr<const base::Value> persisted_data) = 0;
+
+ protected:
+ ReportingDelegate();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReportingDelegate);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_DELEGATE_H_
diff --git a/chromium/net/reporting/reporting_delivery_agent.cc b/chromium/net/reporting/reporting_delivery_agent.cc
new file mode 100644
index 00000000000..f39dde936ea
--- /dev/null
+++ b/chromium/net/reporting/reporting_delivery_agent.cc
@@ -0,0 +1,142 @@
+// Copyright 2017 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 "net/reporting/reporting_delivery_agent.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/tick_clock.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_endpoint_manager.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_uploader.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+namespace {
+
+void SerializeReports(const std::vector<const ReportingReport*>& reports,
+ base::TimeTicks now,
+ std::string* json_out) {
+ base::ListValue reports_value;
+
+ for (const ReportingReport* report : reports) {
+ std::unique_ptr<base::DictionaryValue> report_value =
+ base::MakeUnique<base::DictionaryValue>();
+
+ report_value->SetInteger("age", (now - report->queued).InMilliseconds());
+ report_value->SetString("type", report->type);
+ report_value->SetString("url", report->url.spec());
+ report_value->Set("report", report->body->DeepCopy());
+
+ reports_value.Append(std::move(report_value));
+ }
+
+ bool json_written = base::JSONWriter::Write(reports_value, json_out);
+ DCHECK(json_written);
+}
+
+} // namespace
+
+ReportingDeliveryAgent::ReportingDeliveryAgent(ReportingContext* context)
+ : context_(context), weak_factory_(this) {}
+ReportingDeliveryAgent::~ReportingDeliveryAgent() {}
+
+class ReportingDeliveryAgent::Delivery {
+ public:
+ Delivery(const GURL& endpoint,
+ const std::vector<const ReportingReport*>& reports)
+ : endpoint(endpoint), reports(reports) {}
+
+ ~Delivery() {}
+
+ const GURL endpoint;
+ const std::vector<const ReportingReport*> reports;
+};
+
+void ReportingDeliveryAgent::SendReports() {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+
+ // Sort reports into (origin, group) buckets.
+ std::map<OriginGroup, std::vector<const ReportingReport*>>
+ origin_group_reports;
+ for (const ReportingReport* report : reports) {
+ OriginGroup origin_group(url::Origin(report->url), report->group);
+ origin_group_reports[origin_group].push_back(report);
+ }
+
+ // Find endpoint for each (origin, group) bucket and sort reports into
+ // endpoint buckets. Don't allow concurrent deliveries to the same (origin,
+ // group) bucket.
+ std::map<GURL, std::vector<const ReportingReport*>> endpoint_reports;
+ for (auto& it : origin_group_reports) {
+ const OriginGroup& origin_group = it.first;
+
+ if (base::ContainsKey(pending_origin_groups_, origin_group))
+ continue;
+
+ GURL endpoint_url;
+ if (!endpoint_manager()->FindEndpointForOriginAndGroup(
+ origin_group.first, origin_group.second, &endpoint_url)) {
+ continue;
+ }
+
+ endpoint_reports[endpoint_url].insert(endpoint_reports[endpoint_url].end(),
+ it.second.begin(), it.second.end());
+ pending_origin_groups_.insert(origin_group);
+ }
+
+ // Start a delivery to each endpoint.
+ for (auto& it : endpoint_reports) {
+ const GURL& endpoint = it.first;
+ const std::vector<const ReportingReport*>& reports = it.second;
+
+ endpoint_manager()->SetEndpointPending(endpoint);
+ cache()->SetReportsPending(reports);
+
+ std::string json;
+ SerializeReports(reports, tick_clock()->NowTicks(), &json);
+
+ uploader()->StartUpload(
+ endpoint, json,
+ base::Bind(&ReportingDeliveryAgent::OnUploadComplete,
+ weak_factory_.GetWeakPtr(),
+ base::MakeUnique<Delivery>(endpoint, reports)));
+ }
+}
+
+void ReportingDeliveryAgent::OnUploadComplete(
+ const std::unique_ptr<Delivery>& delivery,
+ ReportingUploader::Outcome outcome) {
+ if (outcome == ReportingUploader::Outcome::SUCCESS) {
+ cache()->RemoveReports(delivery->reports);
+ endpoint_manager()->InformOfEndpointRequest(delivery->endpoint, true);
+ } else {
+ cache()->IncrementReportsAttempts(delivery->reports);
+ endpoint_manager()->InformOfEndpointRequest(delivery->endpoint, false);
+ }
+
+ if (outcome == ReportingUploader::Outcome::REMOVE_ENDPOINT)
+ cache()->RemoveClientsForEndpoint(delivery->endpoint);
+
+ for (const ReportingReport* report : delivery->reports) {
+ pending_origin_groups_.erase(
+ OriginGroup(url::Origin(report->url), report->group));
+ }
+
+ endpoint_manager()->ClearEndpointPending(delivery->endpoint);
+ cache()->ClearReportsPending(delivery->reports);
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_delivery_agent.h b/chromium/net/reporting/reporting_delivery_agent.h
new file mode 100644
index 00000000000..eaae8a3ae62
--- /dev/null
+++ b/chromium/net/reporting/reporting_delivery_agent.h
@@ -0,0 +1,99 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_DELIVERY_AGENT_H_
+#define NET_REPORTING_REPORTING_DELIVERY_AGENT_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_export.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_uploader.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace base {
+class TickClock;
+} // namespace base
+
+namespace net {
+
+class ReportingCache;
+class ReportingEndpointManager;
+
+// Takes reports from the ReportingCache, assembles reports into deliveries to
+// endpoints, and sends those deliveries using ReportingUploader.
+//
+// Since the Reporting spec is completely silent on issues of concurrency, the
+// delivery agent handles it as so:
+//
+// 1. An individual report can only be included in one delivery at once -- if
+// SendReports is called again while a report is being delivered, it won't
+// be included in another delivery during that call to SendReports. (This is,
+// in fact, made redundant by rule 3, but it's included anyway in case rule 3
+// changes.)
+//
+// 2. An endpoint can only be the target of one delivery at once -- if
+// SendReports is called again with reports that could be delivered to that
+// endpoint, they won't be delivered to that endpoint.
+//
+// 3. Reports for an (origin, group) tuple can only be included in one delivery
+// at once -- if SendReports is called again with reports in that (origin,
+// group), they won't be included in any delivery during that call to
+// SendReports. (This prevents the agent from getting around rule 2 by using
+// other endpoints in the same group.)
+//
+// 4. Reports for the same origin *can* be included in multiple parallel
+// deliveries if they are in different groups within that origin.
+//
+// (Note that a single delivery can contain an infinite number of reports.)
+//
+// TODO(juliatuttle): Consider capping the maximum number of reports per
+// delivery attempt.
+class NET_EXPORT ReportingDeliveryAgent {
+ public:
+ // |context| must outlive the ReportingDeliveryAgent.
+ ReportingDeliveryAgent(ReportingContext* context);
+ ~ReportingDeliveryAgent();
+
+ // Tries to deliver all of the reports in the cache. Reports that are already
+ // being delivered will not be attempted a second time, and reports that do
+ // not have a viable endpoint will be neither attempted nor removed.
+ void SendReports();
+
+ private:
+ class Delivery;
+
+ using OriginGroup = std::pair<url::Origin, std::string>;
+
+ void OnUploadComplete(const std::unique_ptr<Delivery>& delivery,
+ ReportingUploader::Outcome outcome);
+
+ base::TickClock* tick_clock() { return context_->tick_clock(); }
+ ReportingCache* cache() { return context_->cache(); }
+ ReportingUploader* uploader() { return context_->uploader(); }
+ ReportingEndpointManager* endpoint_manager() {
+ return context_->endpoint_manager();
+ }
+
+ ReportingContext* context_;
+
+ // Tracks OriginGroup tuples for which there is a pending delivery running.
+ // (Would be an unordered_set, but there's no hash on pair.)
+ std::set<OriginGroup> pending_origin_groups_;
+
+ base::WeakPtrFactory<ReportingDeliveryAgent> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingDeliveryAgent);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_DELIVERY_AGENT_H_
diff --git a/chromium/net/reporting/reporting_delivery_agent_unittest.cc b/chromium/net/reporting/reporting_delivery_agent_unittest.cc
new file mode 100644
index 00000000000..d62f97c0133
--- /dev/null
+++ b/chromium/net/reporting/reporting_delivery_agent_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright 2017 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 "net/reporting/reporting_delivery_agent.h"
+
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/values_test_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/backoff_entry.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_test_util.h"
+#include "net/reporting/reporting_uploader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+namespace {
+
+class ReportingDeliveryAgentTest : public ReportingTestBase {
+ protected:
+ ReportingDeliveryAgentTest() {
+ ReportingPolicy policy;
+ policy.endpoint_backoff_policy.num_errors_to_ignore = 0;
+ policy.endpoint_backoff_policy.initial_delay_ms = 60000;
+ policy.endpoint_backoff_policy.multiply_factor = 2.0;
+ policy.endpoint_backoff_policy.jitter_factor = 0.0;
+ policy.endpoint_backoff_policy.maximum_backoff_ms = -1;
+ policy.endpoint_backoff_policy.entry_lifetime_ms = 0;
+ policy.endpoint_backoff_policy.always_use_initial_delay = false;
+ UsePolicy(policy);
+ }
+
+ base::TimeTicks tomorrow() {
+ return tick_clock()->NowTicks() + base::TimeDelta::FromDays(1);
+ }
+
+ const std::vector<std::unique_ptr<TestReportingUploader::PendingUpload>>&
+ pending_uploads() {
+ return uploader()->pending_uploads();
+ }
+
+ const GURL kUrl_ = GURL("https://origin/path");
+ const url::Origin kOrigin_ = url::Origin(GURL("https://origin/"));
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "type";
+};
+
+TEST_F(ReportingDeliveryAgentTest, SuccessfulUpload) {
+ static const int kAgeMillis = 12345;
+
+ base::DictionaryValue body;
+ body.SetString("key", "value");
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->AddReport(kUrl_, kGroup_, kType_, body.CreateDeepCopy(),
+ tick_clock()->NowTicks(), 0);
+
+ tick_clock()->Advance(base::TimeDelta::FromMilliseconds(kAgeMillis));
+
+ delivery_agent()->SendReports();
+
+ ASSERT_EQ(1u, pending_uploads().size());
+ EXPECT_EQ(kEndpoint_, pending_uploads()[0]->url());
+ {
+ auto value = pending_uploads()[0]->GetValue();
+
+ base::ListValue* list;
+ ASSERT_TRUE(value->GetAsList(&list));
+ EXPECT_EQ(1u, list->GetSize());
+
+ base::DictionaryValue* report;
+ ASSERT_TRUE(list->GetDictionary(0, &report));
+ EXPECT_EQ(4u, report->size());
+
+ ExpectDictIntegerValue(kAgeMillis, *report, "age");
+ ExpectDictStringValue(kType_, *report, "type");
+ ExpectDictStringValue(kUrl_.spec(), *report, "url");
+ ExpectDictDictionaryValue(body, *report, "report");
+ }
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+
+ // Successful upload should remove delivered reports.
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+
+ // TODO(juliatuttle): Check that BackoffEntry was informed of success.
+}
+
+TEST_F(ReportingDeliveryAgentTest, FailedUpload) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+
+ ASSERT_EQ(1u, pending_uploads().size());
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::FAILURE);
+
+ // Failed upload should increment reports' attempts.
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_EQ(1, reports[0]->attempts);
+
+ // Since endpoint is now failing, an upload won't be started despite a pending
+ // report.
+ ASSERT_TRUE(pending_uploads().empty());
+ delivery_agent()->SendReports();
+ EXPECT_TRUE(pending_uploads().empty());
+}
+
+TEST_F(ReportingDeliveryAgentTest, RemoveEndpointUpload) {
+ static const url::Origin kDifferentOrigin(GURL("https://origin2/"));
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->SetClient(kDifferentOrigin, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+ ASSERT_TRUE(FindClientInCache(cache(), kOrigin_, kEndpoint_));
+ ASSERT_TRUE(FindClientInCache(cache(), kDifferentOrigin, kEndpoint_));
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+
+ ASSERT_EQ(1u, pending_uploads().size());
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::REMOVE_ENDPOINT);
+
+ // "Remove endpoint" upload should remove endpoint from *all* origins and
+ // increment reports' attempts.
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_EQ(1, reports[0]->attempts);
+
+ EXPECT_FALSE(FindClientInCache(cache(), kOrigin_, kEndpoint_));
+ EXPECT_FALSE(FindClientInCache(cache(), kDifferentOrigin, kEndpoint_));
+
+ // Since endpoint is now failing, an upload won't be started despite a pending
+ // report.
+ delivery_agent()->SendReports();
+ EXPECT_TRUE(pending_uploads().empty());
+}
+
+TEST_F(ReportingDeliveryAgentTest, ConcurrentRemove) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ // Remove the report while the upload is running.
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ EXPECT_EQ(1u, reports.size());
+
+ const ReportingReport* report = reports[0];
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
+
+ // Report should appear removed, even though the cache has doomed it.
+ cache()->RemoveReports(reports);
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+ EXPECT_TRUE(cache()->IsReportDoomedForTesting(report));
+
+ // Completing upload shouldn't crash, and report should still be gone.
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ cache()->GetReports(&reports);
+ EXPECT_TRUE(reports.empty());
+ // This is slightly sketchy since |report| has been freed, but it nonetheless
+ // should not be in the set of doomed reports.
+ EXPECT_FALSE(cache()->IsReportDoomedForTesting(report));
+}
+
+// Test that the agent will combine reports destined for the same endpoint, even
+// if the reports are from different origins.
+TEST_F(ReportingDeliveryAgentTest,
+ BatchReportsFromDifferentOriginsToSameEndpoint) {
+ static const GURL kDifferentUrl("https://origin2/path");
+ static const url::Origin kDifferentOrigin(kDifferentUrl);
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->SetClient(kDifferentOrigin, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kDifferentUrl, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+}
+
+// Test that the agent won't start a second upload to the same endpoint (even
+// for a different origin) while one is pending, but will once it is no longer
+// pending.
+TEST_F(ReportingDeliveryAgentTest, SerializeUploadsToEndpoint) {
+ static const GURL kDifferentUrl("https://origin2/path");
+ static const url::Origin kDifferentOrigin(kDifferentUrl);
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->SetClient(kDifferentOrigin, kEndpoint_,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ EXPECT_EQ(1u, pending_uploads().size());
+
+ cache()->AddReport(kDifferentUrl, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+}
+
+// Test that the agent won't start a second upload for an (origin, group) while
+// one is pending, even if a different endpoint is available, but will once the
+// original delivery is complete and the (origin, group) is no longer pending.
+TEST_F(ReportingDeliveryAgentTest, SerializeUploadsToGroup) {
+ static const GURL kDifferentEndpoint("https://endpoint2/");
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->SetClient(kOrigin_, kDifferentEndpoint,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ EXPECT_EQ(1u, pending_uploads().size());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(1u, pending_uploads().size());
+
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+}
+
+// Tests that the agent will start parallel uploads to different groups within
+// the same origin.
+TEST_F(ReportingDeliveryAgentTest, ParallelizeUploadsAcrossGroups) {
+ static const GURL kDifferentEndpoint("https://endpoint2/");
+ static const std::string kDifferentGroup("group2");
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+ cache()->SetClient(kOrigin_, kDifferentEndpoint,
+ ReportingClient::Subdomains::EXCLUDE, kDifferentGroup,
+ tomorrow());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ cache()->AddReport(kUrl_, kDifferentGroup, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ delivery_agent()->SendReports();
+ ASSERT_EQ(2u, pending_uploads().size());
+
+ pending_uploads()[1]->Complete(ReportingUploader::Outcome::SUCCESS);
+ pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+ EXPECT_EQ(0u, pending_uploads().size());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_endpoint_manager.cc b/chromium/net/reporting/reporting_endpoint_manager.cc
new file mode 100644
index 00000000000..3d533dafbdf
--- /dev/null
+++ b/chromium/net/reporting/reporting_endpoint_manager.cc
@@ -0,0 +1,80 @@
+// Copyright 2017 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 "net/reporting/reporting_endpoint_manager.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/time/tick_clock.h"
+#include "net/base/backoff_entry.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_policy.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+ReportingEndpointManager::ReportingEndpointManager(ReportingContext* context)
+ : context_(context) {}
+
+ReportingEndpointManager::~ReportingEndpointManager() {}
+
+bool ReportingEndpointManager::FindEndpointForOriginAndGroup(
+ const url::Origin& origin,
+ const std::string& group,
+ GURL* endpoint_url_out) {
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClientsForOriginAndGroup(origin, group, &clients);
+
+ // Filter out expired, pending, and backed-off endpoints.
+ std::vector<const ReportingClient*> available_clients;
+ base::TimeTicks now = tick_clock()->NowTicks();
+ for (const ReportingClient* client : clients) {
+ if (client->expires < now)
+ continue;
+ if (base::ContainsKey(pending_endpoints_, client->endpoint))
+ continue;
+ if (base::ContainsKey(endpoint_backoff_, client->endpoint) &&
+ endpoint_backoff_[client->endpoint]->ShouldRejectRequest()) {
+ continue;
+ }
+ available_clients.push_back(client);
+ }
+
+ if (available_clients.empty()) {
+ *endpoint_url_out = GURL();
+ return false;
+ }
+
+ int random_index = base::RandInt(0, available_clients.size() - 1);
+ *endpoint_url_out = available_clients[random_index]->endpoint;
+ return true;
+}
+
+void ReportingEndpointManager::SetEndpointPending(const GURL& endpoint) {
+ DCHECK(!base::ContainsKey(pending_endpoints_, endpoint));
+ pending_endpoints_.insert(endpoint);
+}
+
+void ReportingEndpointManager::ClearEndpointPending(const GURL& endpoint) {
+ DCHECK(base::ContainsKey(pending_endpoints_, endpoint));
+ pending_endpoints_.erase(endpoint);
+}
+
+void ReportingEndpointManager::InformOfEndpointRequest(const GURL& endpoint,
+ bool succeeded) {
+ if (!base::ContainsKey(endpoint_backoff_, endpoint)) {
+ endpoint_backoff_[endpoint] = base::MakeUnique<BackoffEntry>(
+ &policy().endpoint_backoff_policy, tick_clock());
+ }
+ endpoint_backoff_[endpoint]->InformOfRequest(succeeded);
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_endpoint_manager.h b/chromium/net/reporting/reporting_endpoint_manager.h
new file mode 100644
index 00000000000..e3cc8277fa7
--- /dev/null
+++ b/chromium/net/reporting/reporting_endpoint_manager.h
@@ -0,0 +1,82 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_ENDPOINT_MANAGER_H_
+#define NET_REPORTING_REPORTING_ENDPOINT_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/tick_clock.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_export.h"
+#include "net/reporting/reporting_context.h"
+
+class GURL;
+
+namespace base {
+class TickClock;
+} // namespace base
+
+namespace url {
+class Origin;
+} // namespace url
+
+namespace net {
+
+class ReportingCache;
+struct ReportingPolicy;
+
+// Keeps track of which endpoints are pending (have active delivery attempts to
+// them) or in exponential backoff after one or more failures, and chooses an
+// endpoint from an endpoint group to receive reports for an origin.
+class NET_EXPORT ReportingEndpointManager {
+ public:
+ // |context| must outlive the ReportingEndpointManager.
+ ReportingEndpointManager(ReportingContext* context);
+ ~ReportingEndpointManager();
+
+ // Finds an endpoint configured by |origin| in group |group| that is not
+ // pending, in exponential backoff from failed requests, or expired.
+ //
+ // Deliberately chooses an endpoint randomly to ensure sites aren't relying on
+ // any sort of fallback ordering.
+ //
+ // Returns true and sets |*endpoint_url_out| to the endpoint URL if an
+ // endpoint was chosen; returns false (and leaves |*endpoint_url_out| invalid)
+ // if no endpoint was found.
+ bool FindEndpointForOriginAndGroup(const url::Origin& origin,
+ const std::string& group,
+ GURL* endpoint_url_out);
+
+ // Adds |endpoint| to the set of pending endpoints, preventing it from being
+ // chosen for a second parallel delivery attempt.
+ void SetEndpointPending(const GURL& endpoint);
+
+ // Removes |endpoint| from the set of pending endpoints.
+ void ClearEndpointPending(const GURL& endpoint);
+
+ // Informs the EndpointManager of a successful or unsuccessful request made to
+ // |endpoint| so it can manage exponential backoff of failing endpoints.
+ void InformOfEndpointRequest(const GURL& endpoint, bool succeeded);
+
+ private:
+ const ReportingPolicy& policy() { return context_->policy(); }
+ base::TickClock* tick_clock() { return context_->tick_clock(); }
+ ReportingCache* cache() { return context_->cache(); }
+
+ ReportingContext* context_;
+
+ std::set<GURL> pending_endpoints_;
+ std::map<GURL, std::unique_ptr<net::BackoffEntry>> endpoint_backoff_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingEndpointManager);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_ENDPOINT_MANAGER_H_
diff --git a/chromium/net/reporting/reporting_endpoint_manager_unittest.cc b/chromium/net/reporting/reporting_endpoint_manager_unittest.cc
new file mode 100644
index 00000000000..113128dbf50
--- /dev/null
+++ b/chromium/net/reporting/reporting_endpoint_manager_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright 2017 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 "net/reporting/reporting_endpoint_manager.h"
+
+#include <string>
+
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+namespace {
+
+class ReportingEndpointManagerTest : public ReportingTestBase {
+ protected:
+ const url::Origin kOrigin_ = url::Origin(GURL("https://origin/"));
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+};
+
+TEST_F(ReportingEndpointManagerTest, NoEndpoint) {
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+}
+
+TEST_F(ReportingEndpointManagerTest, Endpoint) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_TRUE(found_endpoint);
+ EXPECT_EQ(kEndpoint_, endpoint_url);
+}
+
+TEST_F(ReportingEndpointManagerTest, ExpiredEndpoint) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, yesterday());
+
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+}
+
+TEST_F(ReportingEndpointManagerTest, PendingEndpoint) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+
+ endpoint_manager()->SetEndpointPending(kEndpoint_);
+
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+
+ endpoint_manager()->ClearEndpointPending(kEndpoint_);
+
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_TRUE(found_endpoint);
+ EXPECT_EQ(kEndpoint_, endpoint_url);
+}
+
+TEST_F(ReportingEndpointManagerTest, BackedOffEndpoint) {
+ ASSERT_EQ(2.0, policy().endpoint_backoff_policy.multiply_factor);
+
+ base::TimeDelta initial_delay = base::TimeDelta::FromMilliseconds(
+ policy().endpoint_backoff_policy.initial_delay_ms);
+
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_, tomorrow());
+
+ endpoint_manager()->InformOfEndpointRequest(kEndpoint_, false);
+
+ // After one failure, endpoint is in exponential backoff.
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+
+ // After initial delay, endpoint is usable again.
+ tick_clock()->Advance(initial_delay);
+
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_TRUE(found_endpoint);
+ EXPECT_EQ(kEndpoint_, endpoint_url);
+
+ endpoint_manager()->InformOfEndpointRequest(kEndpoint_, false);
+
+ // After a second failure, endpoint is backed off again.
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+
+ tick_clock()->Advance(initial_delay);
+
+ // Next backoff is longer -- 2x the first -- so endpoint isn't usable yet.
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+
+ tick_clock()->Advance(initial_delay);
+
+ // After 2x the initial delay, the endpoint is usable again.
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_TRUE(found_endpoint);
+ EXPECT_EQ(kEndpoint_, endpoint_url);
+
+ endpoint_manager()->InformOfEndpointRequest(kEndpoint_, true);
+ endpoint_manager()->InformOfEndpointRequest(kEndpoint_, true);
+
+ // Two more successful requests should reset the backoff to the initial delay
+ // again.
+ endpoint_manager()->InformOfEndpointRequest(kEndpoint_, false);
+
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_FALSE(found_endpoint);
+
+ tick_clock()->Advance(initial_delay);
+
+ found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ EXPECT_TRUE(found_endpoint);
+}
+
+// Make sure that multiple endpoints will all be returned at some point, to
+// avoid accidentally or intentionally implementing any priority ordering.
+TEST_F(ReportingEndpointManagerTest, RandomEndpoint) {
+ static const GURL kEndpoint_1("https://endpoint1/");
+ static const GURL kEndpoint_2("https://endpoint2/");
+ static const int kMaxAttempts = 20;
+
+ cache()->SetClient(kOrigin_, kEndpoint_1,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+ cache()->SetClient(kOrigin_, kEndpoint_2,
+ ReportingClient::Subdomains::EXCLUDE, kGroup_, tomorrow());
+
+ bool endpoint1_seen = false;
+ bool endpoint2_seen = false;
+
+ for (int i = 0; i < kMaxAttempts; i++) {
+ GURL endpoint_url;
+ bool found_endpoint = endpoint_manager()->FindEndpointForOriginAndGroup(
+ kOrigin_, kGroup_, &endpoint_url);
+ ASSERT_TRUE(found_endpoint);
+ ASSERT_TRUE(endpoint_url == kEndpoint_1 || endpoint_url == kEndpoint_2);
+
+ if (endpoint_url == kEndpoint_1)
+ endpoint1_seen = true;
+ else if (endpoint_url == kEndpoint_2)
+ endpoint2_seen = true;
+
+ if (endpoint1_seen && endpoint2_seen)
+ break;
+ }
+
+ EXPECT_TRUE(endpoint1_seen);
+ EXPECT_TRUE(endpoint2_seen);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_garbage_collector.cc b/chromium/net/reporting/reporting_garbage_collector.cc
new file mode 100644
index 00000000000..76dbf3ad0e4
--- /dev/null
+++ b/chromium/net/reporting/reporting_garbage_collector.cc
@@ -0,0 +1,95 @@
+// Copyright 2017 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 "net/reporting/reporting_garbage_collector.h"
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_observer.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_report.h"
+
+namespace net {
+
+namespace {
+
+class ReportingGarbageCollectorImpl : public ReportingGarbageCollector,
+ public ReportingObserver {
+ public:
+ ReportingGarbageCollectorImpl(ReportingContext* context)
+ : context_(context), timer_(base::MakeUnique<base::OneShotTimer>()) {}
+
+ // ReportingGarbageCollector implementation:
+
+ ~ReportingGarbageCollectorImpl() override {
+ DCHECK(context_->initialized());
+ context_->RemoveObserver(this);
+ }
+
+ void Initialize() override {
+ context_->AddObserver(this);
+ CollectGarbage();
+ }
+
+ void SetTimerForTesting(std::unique_ptr<base::Timer> timer) override {
+ DCHECK(!context_->initialized());
+ timer_ = std::move(timer);
+ }
+
+ // ReportingObserver implementation:
+ void OnCacheUpdated() override {
+ DCHECK(context_->initialized());
+ if (!timer_->IsRunning())
+ StartTimer();
+ }
+
+ private:
+ void StartTimer() {
+ timer_->Start(FROM_HERE, context_->policy().garbage_collection_interval,
+ base::Bind(&ReportingGarbageCollectorImpl::CollectGarbage,
+ base::Unretained(this)));
+ }
+
+ void CollectGarbage() {
+ base::TimeTicks now = context_->tick_clock()->NowTicks();
+ const ReportingPolicy& policy = context_->policy();
+
+ std::vector<const ReportingReport*> all_reports;
+ context_->cache()->GetReports(&all_reports);
+
+ std::vector<const ReportingReport*> reports_to_remove;
+ for (const ReportingReport* report : all_reports) {
+ if (now - report->queued >= policy.max_report_age ||
+ report->attempts >= policy.max_report_attempts) {
+ reports_to_remove.push_back(report);
+ }
+ }
+
+ // Don't restart the timer on the garbage collector's own updates.
+ context_->RemoveObserver(this);
+ context_->cache()->RemoveReports(reports_to_remove);
+ context_->AddObserver(this);
+ }
+
+ ReportingContext* context_;
+ std::unique_ptr<base::Timer> timer_;
+};
+
+} // namespace
+
+// static
+std::unique_ptr<ReportingGarbageCollector> ReportingGarbageCollector::Create(
+ ReportingContext* context) {
+ return base::MakeUnique<ReportingGarbageCollectorImpl>(context);
+}
+
+ReportingGarbageCollector::~ReportingGarbageCollector() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_garbage_collector.h b/chromium/net/reporting/reporting_garbage_collector.h
new file mode 100644
index 00000000000..17bd9ab5626
--- /dev/null
+++ b/chromium/net/reporting/reporting_garbage_collector.h
@@ -0,0 +1,42 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_GARBAGE_COLLECTOR_H_
+#define NET_REPORTING_REPORTING_GARBAGE_COLLECTOR_H_
+
+#include <memory>
+
+#include "net/base/net_export.h"
+
+namespace base {
+class Timer;
+} // namespace base
+
+namespace net {
+
+class ReportingContext;
+
+// Removes reports that have remained undelivered for too long or that have been
+// included in too many failed delivery attempts.
+class NET_EXPORT ReportingGarbageCollector {
+ public:
+ // Creates a ReportingGarbageCollector. |context| must outlive the garbage
+ // collector.
+ static std::unique_ptr<ReportingGarbageCollector> Create(
+ ReportingContext* context);
+
+ virtual ~ReportingGarbageCollector();
+
+ // Initializes the GarbageCollector, which performs an initial garbage
+ // collection pass over any data already in the Cache.
+ virtual void Initialize() = 0;
+
+ // Replaces the internal Timer used for scheduling garbage collection passes
+ // with a caller-specified one so that unittests can provide a MockTimer.
+ virtual void SetTimerForTesting(std::unique_ptr<base::Timer> timer) = 0;
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_GARBAGE_COLLECTOR_H_
diff --git a/chromium/net/reporting/reporting_garbage_collector_unittest.cc b/chromium/net/reporting/reporting_garbage_collector_unittest.cc
new file mode 100644
index 00000000000..19ed2b30052
--- /dev/null
+++ b/chromium/net/reporting/reporting_garbage_collector_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2017 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 "net/reporting/reporting_garbage_collector.h"
+
+#include <string>
+
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/mock_timer.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+class ReportingGarbageCollectorTest : public ReportingTestBase {
+ protected:
+ size_t report_count() {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ return reports.size();
+ }
+
+ const GURL kUrl_ = GURL("https://origin/path");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "default";
+};
+
+// Make sure the garbage collector is actually present in the context.
+TEST_F(ReportingGarbageCollectorTest, Created) {
+ EXPECT_NE(nullptr, garbage_collector());
+}
+
+// Make sure that the garbage collection timer is started and stopped correctly.
+TEST_F(ReportingGarbageCollectorTest, Timer) {
+ EXPECT_FALSE(garbage_collection_timer()->IsRunning());
+
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ EXPECT_TRUE(garbage_collection_timer()->IsRunning());
+
+ garbage_collection_timer()->Fire();
+
+ EXPECT_FALSE(garbage_collection_timer()->IsRunning());
+}
+
+TEST_F(ReportingGarbageCollectorTest, Report) {
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ garbage_collection_timer()->Fire();
+
+ EXPECT_EQ(1u, report_count());
+}
+
+TEST_F(ReportingGarbageCollectorTest, ExpiredReport) {
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+ tick_clock()->Advance(2 * policy().max_report_age);
+ garbage_collection_timer()->Fire();
+
+ EXPECT_EQ(0u, report_count());
+}
+
+TEST_F(ReportingGarbageCollectorTest, FailedReport) {
+ cache()->AddReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>(),
+ tick_clock()->NowTicks(), 0);
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ for (int i = 0; i < policy().max_report_attempts; i++) {
+ cache()->IncrementReportsAttempts(reports);
+ }
+
+ garbage_collection_timer()->Fire();
+
+ EXPECT_EQ(0u, report_count());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_header_parser.cc b/chromium/net/reporting/reporting_header_parser.cc
new file mode 100644
index 00000000000..8dbcb8e20ab
--- /dev/null
+++ b/chromium/net/reporting/reporting_header_parser.cc
@@ -0,0 +1,98 @@
+// Copyright 2017 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 "net/reporting/reporting_header_parser.h"
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_context.h"
+
+namespace net {
+
+namespace {
+
+const char kUrlKey[] = "url";
+const char kIncludeSubdomainsKey[] = "includeSubdomains";
+const char kGroupKey[] = "group";
+const char kGroupDefaultValue[] = "default";
+const char kMaxAgeKey[] = "max-age";
+
+} // namespace
+
+// static
+void ReportingHeaderParser::ParseHeader(ReportingContext* context,
+ const GURL& url,
+ const std::string& json_value) {
+ DCHECK(url.SchemeIsCryptographic());
+
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::Read("[" + json_value + "]");
+ if (!value)
+ return;
+
+ const base::ListValue* list = nullptr;
+ bool is_list = value->GetAsList(&list);
+ DCHECK(is_list);
+
+ ReportingCache* cache = context->cache();
+ base::TimeTicks now = context->tick_clock()->NowTicks();
+ for (size_t i = 0; i < list->GetSize(); i++) {
+ const base::Value* endpoint = nullptr;
+ bool got_endpoint = list->Get(i, &endpoint);
+ DCHECK(got_endpoint);
+ ProcessEndpoint(cache, now, url, *endpoint);
+ }
+}
+
+// static
+void ReportingHeaderParser::ProcessEndpoint(ReportingCache* cache,
+ base::TimeTicks now,
+ const GURL& url,
+ const base::Value& value) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!value.GetAsDictionary(&dict))
+ return;
+ DCHECK(dict);
+
+ std::string endpoint_url_string;
+ if (!dict->GetString(kUrlKey, &endpoint_url_string))
+ return;
+
+ GURL endpoint_url(endpoint_url_string);
+ if (!endpoint_url.is_valid())
+ return;
+ if (!endpoint_url.SchemeIsCryptographic())
+ return;
+
+ int ttl_sec = -1;
+ if (!dict->GetInteger(kMaxAgeKey, &ttl_sec) || ttl_sec < 0)
+ return;
+
+ std::string group = kGroupDefaultValue;
+ if (dict->HasKey(kGroupKey) && !dict->GetString(kGroupKey, &group))
+ return;
+
+ ReportingClient::Subdomains subdomains = ReportingClient::Subdomains::EXCLUDE;
+ bool subdomains_bool = false;
+ if (dict->HasKey(kIncludeSubdomainsKey) &&
+ dict->GetBoolean(kIncludeSubdomainsKey, &subdomains_bool) &&
+ subdomains_bool == true) {
+ subdomains = ReportingClient::Subdomains::INCLUDE;
+ }
+
+ if (ttl_sec > 0) {
+ cache->SetClient(url::Origin(url), endpoint_url, subdomains, group,
+ now + base::TimeDelta::FromSeconds(ttl_sec));
+ } else {
+ cache->RemoveClientForOriginAndEndpoint(url::Origin(url), endpoint_url);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_header_parser.h b/chromium/net/reporting/reporting_header_parser.h
new file mode 100644
index 00000000000..2d7ac6ee242
--- /dev/null
+++ b/chromium/net/reporting/reporting_header_parser.h
@@ -0,0 +1,48 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_HEADER_PARSER_H_
+#define NET_REPORTING_REPORTING_HEADER_PARSER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace net {
+
+class ReportingCache;
+class ReportingContext;
+
+class NET_EXPORT ReportingHeaderParser {
+ public:
+ static void ParseHeader(ReportingContext* context,
+ const GURL& url,
+ const std::string& json_value);
+
+ private:
+ // Processes a single endpoint's parsed value from the Report-To header(s).
+ // Creates, updates, or removes a client in the cache as needed.
+ //
+ // |url| is the URL that the header came from.
+ //
+ // |value| is the parsed value.
+ static void ProcessEndpoint(ReportingCache* cache,
+ base::TimeTicks now,
+ const GURL& url,
+ const base::Value& value);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ReportingHeaderParser);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_HEADER_PARSER_H_
diff --git a/chromium/net/reporting/reporting_header_parser_unittest.cc b/chromium/net/reporting/reporting_header_parser_unittest.cc
new file mode 100644
index 00000000000..69210f2aa2f
--- /dev/null
+++ b/chromium/net/reporting/reporting_header_parser_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright 2017 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 "net/reporting/reporting_header_parser.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+namespace {
+
+class ReportingHeaderParserTest : public ReportingTestBase {
+ protected:
+ const GURL kUrl_ = GURL("https://origin/path");
+ const url::Origin kOrigin_ = url::Origin(GURL("https://origin/"));
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "type";
+};
+
+TEST_F(ReportingHeaderParserTest, Invalid) {
+ static const struct {
+ const char* header_value;
+ const char* description;
+ } kInvalidHeaderTestCases[] = {
+ {"{\"max-age\":1}", "missing url"},
+ {"{\"url\":0,\"max-age\":1}", "non-string url"},
+ {"{\"url\":\"http://insecure/\",\"max-age\":1}", "insecure url"},
+
+ {"{\"url\":\"https://endpoint/\"}", "missing max-age"},
+ {"{\"url\":\"https://endpoint/\",\"max-age\":\"\"}",
+ "non-integer max-age"},
+ {"{\"url\":\"https://endpoint/\",\"max-age\":-1}", "negative max-age"},
+
+ {"{\"url\":\"https://endpoint/\",\"max-age\":1,\"group\":0}",
+ "non-string group"},
+
+ // Note that a non-boolean includeSubdomains field is *not* invalid, per
+ // the spec.
+
+ {"[{\"url\":\"https://a/\",\"max-age\":1},"
+ "{\"url\":\"https://b/\",\"max-age\":1}]",
+ "wrapped in list"}};
+
+ for (size_t i = 0; i < arraysize(kInvalidHeaderTestCases); ++i) {
+ auto& test_case = kInvalidHeaderTestCases[i];
+ ReportingHeaderParser::ParseHeader(context(), kUrl_,
+ test_case.header_value);
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClients(&clients);
+ EXPECT_TRUE(clients.empty())
+ << "Invalid Report-To header (" << test_case.description << ": \""
+ << test_case.header_value << "\") parsed as valid.";
+ }
+}
+
+TEST_F(ReportingHeaderParserTest, Valid) {
+ ReportingHeaderParser::ParseHeader(
+ context(), kUrl_,
+ "{\"url\":\"" + kEndpoint_.spec() + "\",\"max-age\":86400}");
+
+ const ReportingClient* client =
+ FindClientInCache(cache(), kOrigin_, kEndpoint_);
+ ASSERT_TRUE(client);
+ EXPECT_EQ(kOrigin_, client->origin);
+ EXPECT_EQ(kEndpoint_, client->endpoint);
+ EXPECT_EQ(ReportingClient::Subdomains::EXCLUDE, client->subdomains);
+ EXPECT_EQ(86400, (client->expires - tick_clock()->NowTicks()).InSeconds());
+}
+
+TEST_F(ReportingHeaderParserTest, Subdomains) {
+ ReportingHeaderParser::ParseHeader(context(), kUrl_,
+ "{\"url\":\"" + kEndpoint_.spec() +
+ "\",\"max-age\":86400,"
+ "\"includeSubdomains\":true}");
+
+ const ReportingClient* client =
+ FindClientInCache(cache(), kOrigin_, kEndpoint_);
+ ASSERT_TRUE(client);
+ EXPECT_EQ(ReportingClient::Subdomains::INCLUDE, client->subdomains);
+}
+
+TEST_F(ReportingHeaderParserTest, ZeroMaxAge) {
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(1));
+
+ ReportingHeaderParser::ParseHeader(
+ context(), kUrl_,
+ "{\"url\":\"" + kEndpoint_.spec() + "\",\"max-age\":0}");
+
+ EXPECT_EQ(nullptr, FindClientInCache(cache(), kOrigin_, kEndpoint_));
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_observer.cc b/chromium/net/reporting/reporting_observer.cc
new file mode 100644
index 00000000000..5e8d778a61b
--- /dev/null
+++ b/chromium/net/reporting/reporting_observer.cc
@@ -0,0 +1,15 @@
+// Copyright 2017 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 "net/reporting/reporting_observer.h"
+
+namespace net {
+
+void ReportingObserver::OnCacheUpdated() {}
+
+ReportingObserver::ReportingObserver() {}
+
+ReportingObserver::~ReportingObserver() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_observer.h b/chromium/net/reporting/reporting_observer.h
new file mode 100644
index 00000000000..cb05389b6f3
--- /dev/null
+++ b/chromium/net/reporting/reporting_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_OBSERVER_H_
+#define NET_REPORTING_REPORTING_OBSERVER_H_
+
+#include "base/macros.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT ReportingObserver {
+ public:
+ // Called whenever any change is made to the ReportingCache.
+ virtual void OnCacheUpdated();
+
+ protected:
+ ReportingObserver();
+
+ ~ReportingObserver();
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingObserver);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_OBSERVER_H_
diff --git a/chromium/net/reporting/reporting_persister.cc b/chromium/net/reporting/reporting_persister.cc
new file mode 100644
index 00000000000..ec2a37b7e48
--- /dev/null
+++ b/chromium/net/reporting/reporting_persister.cc
@@ -0,0 +1,358 @@
+// Copyright 2017 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 "net/reporting/reporting_persister.h"
+
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_observer.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_report.h"
+
+namespace net {
+namespace {
+
+std::unique_ptr<base::Value> SerializeOrigin(const url::Origin& origin) {
+ auto serialized = base::MakeUnique<base::DictionaryValue>();
+
+ serialized->SetString("scheme", origin.scheme());
+ serialized->SetString("host", origin.host());
+ serialized->SetInteger("port", origin.port());
+ serialized->SetString("suborigin", origin.suborigin());
+
+ return std::move(serialized);
+}
+
+bool DeserializeOrigin(const base::DictionaryValue& serialized,
+ url::Origin* origin_out) {
+ std::string scheme;
+ if (!serialized.GetString("scheme", &scheme))
+ return false;
+
+ std::string host;
+ if (!serialized.GetString("host", &host))
+ return false;
+
+ int port_int;
+ if (!serialized.GetInteger("port", &port_int))
+ return false;
+ uint16_t port = static_cast<uint16_t>(port_int);
+ if (port_int != port)
+ return false;
+
+ std::string suborigin;
+ if (!serialized.GetString("suborigin", &suborigin))
+ return false;
+
+ *origin_out = url::Origin::CreateFromNormalizedTupleWithSuborigin(
+ scheme, host, port, suborigin);
+ return true;
+}
+
+class ReportingPersisterImpl : public ReportingPersister,
+ public ReportingObserver {
+ public:
+ ReportingPersisterImpl(ReportingContext* context)
+ : context_(context), timer_(base::MakeUnique<base::OneShotTimer>()) {}
+
+ // ReportingPersister implementation:
+
+ ~ReportingPersisterImpl() override {
+ DCHECK(context_->initialized());
+ context_->RemoveObserver(this);
+ }
+
+ void Initialize() override {
+ std::unique_ptr<const base::Value> persisted_data =
+ context_->delegate()->GetPersistedData();
+ if (persisted_data)
+ Deserialize(*persisted_data);
+ context_->AddObserver(this);
+ }
+
+ void SetTimerForTesting(std::unique_ptr<base::Timer> timer) override {
+ DCHECK(!context_->initialized());
+ timer_ = std::move(timer);
+ }
+
+ // ReportingObserver implementation:
+
+ void OnCacheUpdated() override {
+ DCHECK(context_->initialized());
+ if (!timer_->IsRunning())
+ StartTimer();
+ }
+
+ private:
+ void StartTimer() {
+ timer_->Start(
+ FROM_HERE, context_->policy().persistence_interval,
+ base::Bind(&ReportingPersisterImpl::Persist, base::Unretained(this)));
+ }
+
+ void Persist() { delegate()->PersistData(Serialize()); }
+
+ std::string SerializeTicks(base::TimeTicks time_ticks) {
+ base::Time time = time_ticks - tick_clock()->NowTicks() + clock()->Now();
+ return base::Int64ToString(time.ToInternalValue());
+ }
+
+ bool DeserializeTicks(const std::string& serialized,
+ base::TimeTicks* time_ticks_out) {
+ int64_t internal;
+ if (!base::StringToInt64(serialized, &internal))
+ return false;
+
+ base::Time time = base::Time::FromInternalValue(internal);
+ *time_ticks_out = time - clock()->Now() + tick_clock()->NowTicks();
+ return true;
+ }
+
+ std::unique_ptr<base::Value> SerializeReport(const ReportingReport& report) {
+ auto serialized = base::MakeUnique<base::DictionaryValue>();
+
+ serialized->SetString("url", report.url.spec());
+ serialized->SetString("group", report.group);
+ serialized->SetString("type", report.type);
+ serialized->Set("body", report.body->CreateDeepCopy());
+ serialized->SetString("queued", SerializeTicks(report.queued));
+ serialized->SetInteger("attempts", report.attempts);
+
+ return std::move(serialized);
+ }
+
+ bool DeserializeReport(const base::DictionaryValue& report) {
+ std::string url_string;
+ if (!report.GetString("url", &url_string))
+ return false;
+ GURL url(url_string);
+ if (!url.is_valid())
+ return false;
+
+ std::string group;
+ if (!report.GetString("group", &group))
+ return false;
+
+ std::string type;
+ if (!report.GetString("type", &type))
+ return false;
+
+ const base::Value* body_original;
+ if (!report.Get("body", &body_original))
+ return false;
+ std::unique_ptr<base::Value> body = body_original->CreateDeepCopy();
+
+ std::string queued_string;
+ if (!report.GetString("queued", &queued_string))
+ return false;
+ base::TimeTicks queued;
+ if (!DeserializeTicks(queued_string, &queued))
+ return false;
+
+ int attempts;
+ if (!report.GetInteger("attempts", &attempts))
+ return false;
+ if (attempts < 0)
+ return false;
+
+ cache()->AddReport(url, group, type, std::move(body), queued, attempts);
+ return true;
+ }
+
+ std::unique_ptr<base::Value> SerializeReports() {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+
+ auto serialized = base::MakeUnique<base::ListValue>();
+ for (const ReportingReport* report : reports)
+ serialized->Append(SerializeReport(*report));
+
+ return std::move(serialized);
+ }
+
+ bool DeserializeReports(const base::ListValue& reports) {
+ for (size_t i = 0; i < reports.GetSize(); ++i) {
+ const base::DictionaryValue* report;
+ if (!reports.GetDictionary(i, &report))
+ return false;
+ if (!DeserializeReport(*report))
+ return false;
+ }
+
+ return true;
+ }
+
+ std::unique_ptr<base::Value> SerializeClient(const ReportingClient& client) {
+ auto serialized = base::MakeUnique<base::DictionaryValue>();
+
+ serialized->Set("origin", SerializeOrigin(client.origin));
+ serialized->SetString("endpoint", client.endpoint.spec());
+ serialized->SetBoolean(
+ "subdomains",
+ client.subdomains == ReportingClient::Subdomains::INCLUDE);
+ serialized->SetString("group", client.group);
+ serialized->SetString("expires", SerializeTicks(client.expires));
+
+ return std::move(serialized);
+ }
+
+ bool DeserializeClient(const base::DictionaryValue& client) {
+ const base::DictionaryValue* origin_value;
+ if (!client.GetDictionary("origin", &origin_value))
+ return false;
+ url::Origin origin;
+ if (!DeserializeOrigin(*origin_value, &origin))
+ return false;
+
+ std::string endpoint_string;
+ if (!client.GetString("endpoint", &endpoint_string))
+ return false;
+ GURL endpoint(endpoint_string);
+ if (!endpoint.is_valid())
+ return false;
+
+ bool subdomains_bool;
+ if (!client.GetBoolean("subdomains", &subdomains_bool))
+ return false;
+ ReportingClient::Subdomains subdomains =
+ subdomains_bool ? ReportingClient::Subdomains::INCLUDE
+ : ReportingClient::Subdomains::EXCLUDE;
+
+ std::string group;
+ if (!client.GetString("group", &group))
+ return false;
+
+ std::string expires_string;
+ if (!client.GetString("expires", &expires_string))
+ return false;
+ base::TimeTicks expires;
+ if (!DeserializeTicks(expires_string, &expires))
+ return false;
+
+ cache()->SetClient(origin, endpoint, subdomains, group, expires);
+ return true;
+ }
+
+ std::unique_ptr<base::Value> SerializeClients() {
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClients(&clients);
+
+ auto serialized = base::MakeUnique<base::ListValue>();
+ for (const ReportingClient* client : clients)
+ serialized->Append(SerializeClient(*client));
+
+ return std::move(serialized);
+ }
+
+ bool DeserializeClients(const base::ListValue& clients) {
+ for (size_t i = 0; i < clients.GetSize(); ++i) {
+ const base::DictionaryValue* client;
+ if (!clients.GetDictionary(i, &client))
+ return false;
+ if (!DeserializeClient(*client))
+ return false;
+ }
+
+ return true;
+ }
+
+ static const int kSupportedVersion = 1;
+
+ std::unique_ptr<base::Value> Serialize() {
+ auto serialized = base::MakeUnique<base::DictionaryValue>();
+
+ serialized->SetInteger("reporting_serialized_cache_version",
+ kSupportedVersion);
+
+ bool persist_reports = policy().persist_reports_across_restarts;
+ serialized->SetBoolean("includes_reports", persist_reports);
+ if (persist_reports)
+ serialized->Set("reports", SerializeReports());
+
+ bool persist_clients = policy().persist_clients_across_restarts;
+ serialized->SetBoolean("includes_clients", persist_clients);
+ if (persist_clients)
+ serialized->Set("clients", SerializeClients());
+
+ return std::move(serialized);
+ }
+
+ bool Deserialize(const base::Value& serialized_value) {
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ DCHECK(reports.empty());
+
+ std::vector<const ReportingClient*> clients;
+ cache()->GetClients(&clients);
+ DCHECK(clients.empty());
+
+ int version;
+
+ const base::DictionaryValue* serialized;
+ if (!serialized_value.GetAsDictionary(&serialized))
+ return false;
+
+ if (!serialized->GetInteger("reporting_serialized_cache_version", &version))
+ return false;
+ if (version != kSupportedVersion)
+ return false;
+
+ bool includes_reports;
+ bool includes_clients;
+ if (!serialized->GetBoolean("includes_reports", &includes_reports) ||
+ !serialized->GetBoolean("includes_clients", &includes_clients)) {
+ return false;
+ }
+
+ if (includes_reports) {
+ const base::ListValue* reports;
+ if (!serialized->GetList("reports", &reports))
+ return false;
+ if (!DeserializeReports(*reports))
+ return false;
+ }
+
+ if (includes_clients) {
+ const base::ListValue* clients;
+ if (!serialized->GetList("clients", &clients))
+ return false;
+ if (!DeserializeClients(*clients))
+ return false;
+ }
+
+ return true;
+ }
+
+ const ReportingPolicy& policy() { return context_->policy(); }
+ ReportingDelegate* delegate() { return context_->delegate(); }
+ base::Clock* clock() { return context_->clock(); }
+ base::TickClock* tick_clock() { return context_->tick_clock(); }
+ ReportingCache* cache() { return context_->cache(); }
+
+ ReportingContext* context_;
+ std::unique_ptr<base::Timer> timer_;
+};
+
+} // namespace
+
+// static
+std::unique_ptr<ReportingPersister> ReportingPersister::Create(
+ ReportingContext* context) {
+ return base::MakeUnique<ReportingPersisterImpl>(context);
+}
+
+ReportingPersister::~ReportingPersister() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_persister.h b/chromium/net/reporting/reporting_persister.h
new file mode 100644
index 00000000000..27463a516bf
--- /dev/null
+++ b/chromium/net/reporting/reporting_persister.h
@@ -0,0 +1,40 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_PERSISTER_H_
+#define NET_REPORTING_REPORTING_PERSISTER_H_
+
+#include <memory>
+
+#include "net/base/net_export.h"
+
+namespace base {
+class Timer;
+} // namespace base
+
+namespace net {
+
+class ReportingContext;
+
+// Periodically persists the state of the Reporting system to (reasonably)
+// stable storage using the methods provided by the ReportingDelegate.
+class NET_EXPORT ReportingPersister {
+ public:
+ // Creates a ReportingPersister. |context| must outlive the persister.
+ static std::unique_ptr<ReportingPersister> Create(ReportingContext* context);
+
+ virtual ~ReportingPersister();
+
+ // Initializes the Persister, which deserializes any previously-persisted data
+ // that is available through the Context's Delegate.
+ virtual void Initialize() = 0;
+
+ // Replaces the internal Timer used for scheduling writes to stable storage
+ // with a caller-specified one so that unittests can provide a MockTimer.
+ virtual void SetTimerForTesting(std::unique_ptr<base::Timer> timer) = 0;
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_PERSISTER_H_
diff --git a/chromium/net/reporting/reporting_persister_unittest.cc b/chromium/net/reporting/reporting_persister_unittest.cc
new file mode 100644
index 00000000000..ed109edacae
--- /dev/null
+++ b/chromium/net/reporting/reporting_persister_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright 2017 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 "net/reporting/reporting_persister.h"
+
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/mock_timer.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+class ReportingPersisterTest : public ReportingTestBase {
+ protected:
+ const GURL kUrl_ = GURL("https://origin/path");
+ const url::Origin kOrigin_ = url::Origin(kUrl_);
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "default";
+};
+
+TEST_F(ReportingPersisterTest, Test) {
+ ReportingPolicy policy;
+ policy.persist_reports_across_restarts = true;
+ policy.persist_clients_across_restarts = true;
+ // Make sure reports don't expire on our simulated restart.
+ policy.max_report_age = base::TimeDelta::FromDays(30);
+ UsePolicy(policy);
+
+ static const int kAttempts = 3;
+
+ base::DictionaryValue body;
+ body.SetString("key", "value");
+
+ cache()->AddReport(kUrl_, kGroup_, kType_, body.CreateDeepCopy(),
+ tick_clock()->NowTicks(), kAttempts);
+ cache()->SetClient(kOrigin_, kEndpoint_, ReportingClient::Subdomains::EXCLUDE,
+ kGroup_,
+ tick_clock()->NowTicks() + base::TimeDelta::FromDays(1));
+
+ EXPECT_TRUE(persistence_timer()->IsRunning());
+ persistence_timer()->Fire();
+
+ SimulateRestart(/* delta= */ base::TimeDelta::FromHours(1),
+ /* delta_ticks= */ base::TimeDelta::FromHours(-3));
+
+ std::vector<const ReportingReport*> reports;
+ cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_EQ(kUrl_, reports[0]->url);
+ EXPECT_EQ(kGroup_, reports[0]->group);
+ EXPECT_EQ(kType_, reports[0]->type);
+ EXPECT_TRUE(base::Value::Equals(&body, reports[0]->body.get()));
+ EXPECT_EQ(tick_clock()->NowTicks() - base::TimeDelta::FromHours(1),
+ reports[0]->queued);
+ EXPECT_EQ(kAttempts, reports[0]->attempts);
+
+ const ReportingClient* client =
+ FindClientInCache(cache(), kOrigin_, kEndpoint_);
+ ASSERT_TRUE(client);
+ EXPECT_EQ(ReportingClient::Subdomains::EXCLUDE, client->subdomains);
+ EXPECT_EQ(kGroup_, client->group);
+ EXPECT_EQ(tick_clock()->NowTicks() + base::TimeDelta::FromDays(1) -
+ base::TimeDelta::FromHours(1),
+ client->expires);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_policy.cc b/chromium/net/reporting/reporting_policy.cc
new file mode 100644
index 00000000000..9461bfcb5cd
--- /dev/null
+++ b/chromium/net/reporting/reporting_policy.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 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 "net/reporting/reporting_policy.h"
+
+#include "base/time/time.h"
+
+namespace net {
+
+ReportingPolicy::ReportingPolicy()
+ : persistence_interval(base::TimeDelta::FromMinutes(1)),
+ persist_reports_across_restarts(false),
+ persist_clients_across_restarts(true),
+ garbage_collection_interval(base::TimeDelta::FromMinutes(5)),
+ max_report_age(base::TimeDelta::FromMinutes(15)),
+ max_report_attempts(5) {
+ endpoint_backoff_policy.num_errors_to_ignore = 0;
+ endpoint_backoff_policy.initial_delay_ms = 60 * 1000; // 1 minute
+ endpoint_backoff_policy.multiply_factor = 2.0;
+ endpoint_backoff_policy.jitter_factor = 0.1;
+ endpoint_backoff_policy.maximum_backoff_ms = -1; // 1 hour
+ endpoint_backoff_policy.entry_lifetime_ms = -1; // infinite
+ endpoint_backoff_policy.always_use_initial_delay = false;
+}
+
+ReportingPolicy::ReportingPolicy(const ReportingPolicy& other)
+ : endpoint_backoff_policy(other.endpoint_backoff_policy),
+ persistence_interval(other.persistence_interval),
+ persist_reports_across_restarts(other.persist_reports_across_restarts),
+ persist_clients_across_restarts(other.persist_clients_across_restarts),
+ garbage_collection_interval(other.garbage_collection_interval),
+ max_report_age(other.max_report_age),
+ max_report_attempts(other.max_report_attempts) {}
+
+ReportingPolicy::~ReportingPolicy() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_policy.h b/chromium/net/reporting/reporting_policy.h
new file mode 100644
index 00000000000..51bba69b403
--- /dev/null
+++ b/chromium/net/reporting/reporting_policy.h
@@ -0,0 +1,48 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_POLICY_H_
+#define NET_REPORTING_REPORTING_POLICY_H_
+
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Various policy knobs for the Reporting system.
+struct NET_EXPORT ReportingPolicy {
+ // Provides a reasonable default for use in a browser embedder.
+ ReportingPolicy();
+ ReportingPolicy(const ReportingPolicy& other);
+ ~ReportingPolicy();
+
+ // Backoff policy for failing endpoints.
+ BackoffEntry::Policy endpoint_backoff_policy;
+
+ // Minimum interval at which Reporting will persist state to (relatively)
+ // stable storage to be restored if the embedder restarts.
+ base::TimeDelta persistence_interval;
+
+ // Whether to persist undelivered reports across embedder restarts.
+ bool persist_reports_across_restarts;
+
+ // Whether to persist clients (per-origin endpoint configurations) across
+ // embedder restarts.
+ bool persist_clients_across_restarts;
+
+ // Minimum interval at which to garbage-collect the cache.
+ base::TimeDelta garbage_collection_interval;
+
+ // Maximum age a report can be queued for before being discarded as expired.
+ base::TimeDelta max_report_age;
+
+ // Maximum number of delivery attempts a report can have before being
+ // discarded as failed.
+ int max_report_attempts;
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_POLICY_H_
diff --git a/chromium/net/reporting/reporting_report.cc b/chromium/net/reporting/reporting_report.cc
new file mode 100644
index 00000000000..24e571a8eef
--- /dev/null
+++ b/chromium/net/reporting/reporting_report.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 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 "net/reporting/reporting_report.h"
+
+#include <memory>
+#include <string>
+
+#include "base/time/time.h"
+#include "base/values.h"
+#include "url/gurl.h"
+
+namespace net {
+
+ReportingReport::ReportingReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body,
+ base::TimeTicks queued,
+ int attempts)
+ : url(url),
+ group(group),
+ type(type),
+ body(std::move(body)),
+ queued(queued),
+ attempts(attempts) {}
+
+ReportingReport::~ReportingReport() {}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_report.h b/chromium/net/reporting/reporting_report.h
new file mode 100644
index 00000000000..b1b310171f9
--- /dev/null
+++ b/chromium/net/reporting/reporting_report.h
@@ -0,0 +1,60 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_REPORT_H_
+#define NET_REPORTING_REPORTING_REPORT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace net {
+
+// An undelivered report.
+struct NET_EXPORT ReportingReport {
+ public:
+ ReportingReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body,
+ base::TimeTicks queued,
+ int attempts);
+ ~ReportingReport();
+
+ // The URL of the document that triggered the report. (Included in the
+ // delivered report.)
+ GURL url;
+
+ // The endpoint group that should be used to deliver the report. (Not included
+ // in the delivered report.)
+ std::string group;
+
+ // The type of the report. (Included in the delivered report.)
+ std::string type;
+
+ // The body of the report. (Included in the delivered report.)
+ std::unique_ptr<const base::Value> body;
+
+ // When the report was queued. (Included in the delivered report as an age
+ // relative to the time of the delivery attempt.)
+ base::TimeTicks queued;
+
+ // The number of delivery attempts made so far, not including an active
+ // attempt. (Not included in the delivered report.)
+ int attempts = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReportingReport);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_REPORT_H_
diff --git a/chromium/net/reporting/reporting_service.cc b/chromium/net/reporting/reporting_service.cc
new file mode 100644
index 00000000000..6d2b215ce5f
--- /dev/null
+++ b/chromium/net/reporting/reporting_service.cc
@@ -0,0 +1,76 @@
+// Copyright 2017 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 "net/reporting/reporting_service.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_header_parser.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+class ReportingServiceImpl : public ReportingService {
+ public:
+ ReportingServiceImpl(std::unique_ptr<ReportingContext> context)
+ : context_(std::move(context)) {
+ // TODO(juliatuttle): This can be slow, so it might be better to expose it
+ // as a separate method and call it separately from constructing everything.
+ context_->Initialize();
+ }
+
+ ~ReportingServiceImpl() override {}
+
+ void QueueReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body) override {
+ DCHECK(context_->initialized());
+ context_->cache()->AddReport(url, group, type, std::move(body),
+ context_->tick_clock()->NowTicks(), 0);
+ }
+
+ void ProcessHeader(const GURL& url,
+ const std::string& header_value) override {
+ DCHECK(context_->initialized());
+ ReportingHeaderParser::ParseHeader(context_.get(), url, header_value);
+ }
+
+ private:
+ std::unique_ptr<ReportingContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingServiceImpl);
+};
+
+} // namespace
+
+ReportingService::~ReportingService() {}
+
+// static
+std::unique_ptr<ReportingService> ReportingService::Create(
+ const ReportingPolicy& policy,
+ URLRequestContext* request_context,
+ std::unique_ptr<ReportingDelegate> delegate) {
+ return base::MakeUnique<ReportingServiceImpl>(
+ ReportingContext::Create(policy, std::move(delegate), request_context));
+}
+
+// static
+std::unique_ptr<ReportingService> ReportingService::CreateForTesting(
+ std::unique_ptr<ReportingContext> reporting_context) {
+ return base::MakeUnique<ReportingServiceImpl>(std::move(reporting_context));
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_service.h b/chromium/net/reporting/reporting_service.h
new file mode 100644
index 00000000000..c305410a1a6
--- /dev/null
+++ b/chromium/net/reporting/reporting_service.h
@@ -0,0 +1,74 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_SERVICE_H_
+#define NET_REPORTING_REPORTING_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace base {
+class Value;
+} // namespace
+
+namespace net {
+
+class ReportingContext;
+class ReportingDelegate;
+struct ReportingPolicy;
+class URLRequestContext;
+
+// The external interface to the Reporting system, used by the embedder of //net
+// and also other parts of //net.
+class NET_EXPORT ReportingService {
+ public:
+ virtual ~ReportingService();
+
+ // Creates a ReportingService. |policy| will be copied. |request_context| must
+ // outlive the ReportingService. The ReportingService will take ownership of
+ // |delegate| and destroy it when the service is destroyed.
+ static std::unique_ptr<ReportingService> Create(
+ const ReportingPolicy& policy,
+ URLRequestContext* request_context,
+ std::unique_ptr<ReportingDelegate> delegate);
+
+ // Creates a ReportingService for testing purposes using an
+ // already-constructed ReportingContext. The ReportingService will take
+ // ownership of |reporting_context| and destroy it when the service is
+ // destroyed.
+ static std::unique_ptr<ReportingService> CreateForTesting(
+ std::unique_ptr<ReportingContext> reporting_context);
+
+ // Queues a report for delivery. |url| is the URL that originated the report.
+ // |group| is the endpoint group to which the report should be delivered.
+ // |type| is the type of the report. |body| is the body of the report.
+ //
+ // The Reporting system will take ownership of |body|; all other parameters
+ // will be copied.
+ virtual void QueueReport(const GURL& url,
+ const std::string& group,
+ const std::string& type,
+ std::unique_ptr<const base::Value> body) = 0;
+
+ // Processes a Report-To header. |url| is the URL that originated the header;
+ // |header_value| is the normalized value of the Report-To header.
+ virtual void ProcessHeader(const GURL& url,
+ const std::string& header_value) = 0;
+
+ protected:
+ ReportingService() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReportingService);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_SERVICE_H_
diff --git a/chromium/net/reporting/reporting_service_unittest.cc b/chromium/net/reporting/reporting_service_unittest.cc
new file mode 100644
index 00000000000..20c86560d22
--- /dev/null
+++ b/chromium/net/reporting/reporting_service_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2017 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 "net/reporting/reporting_service.h"
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ptr_util.h"
+#include "base/time/tick_clock.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_report.h"
+#include "net/reporting/reporting_service.h"
+#include "net/reporting/reporting_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+class ReportingServiceTest : public ::testing::Test {
+ protected:
+ const GURL kUrl_ = GURL("https://origin/path");
+ const url::Origin kOrigin_ = url::Origin(kUrl_);
+ const GURL kEndpoint_ = GURL("https://endpoint/");
+ const std::string kGroup_ = "group";
+ const std::string kType_ = "type";
+
+ ReportingServiceTest()
+ : context_(new TestReportingContext(ReportingPolicy())),
+ service_(
+ ReportingService::CreateForTesting(base::WrapUnique(context_))) {}
+
+ TestReportingContext* context() { return context_; }
+ ReportingService* service() { return service_.get(); }
+
+ private:
+ TestReportingContext* context_;
+ std::unique_ptr<ReportingService> service_;
+};
+
+TEST_F(ReportingServiceTest, QueueReport) {
+ service()->QueueReport(kUrl_, kGroup_, kType_,
+ base::MakeUnique<base::DictionaryValue>());
+
+ std::vector<const ReportingReport*> reports;
+ context()->cache()->GetReports(&reports);
+ ASSERT_EQ(1u, reports.size());
+ EXPECT_EQ(kUrl_, reports[0]->url);
+ EXPECT_EQ(kGroup_, reports[0]->group);
+ EXPECT_EQ(kType_, reports[0]->type);
+}
+
+TEST_F(ReportingServiceTest, ProcessHeader) {
+ service()->ProcessHeader(kUrl_, "{\"url\":\"" + kEndpoint_.spec() +
+ "\","
+ "\"group\":\"" +
+ kGroup_ +
+ "\","
+ "\"max-age\":86400}");
+
+ const ReportingClient* client =
+ FindClientInCache(context()->cache(), kOrigin_, kEndpoint_);
+ ASSERT_TRUE(client != nullptr);
+ EXPECT_EQ(kOrigin_, client->origin);
+ EXPECT_EQ(kEndpoint_, client->endpoint);
+ EXPECT_EQ(ReportingClient::Subdomains::EXCLUDE, client->subdomains);
+ EXPECT_EQ(kGroup_, client->group);
+ EXPECT_EQ(context()->tick_clock()->NowTicks() + base::TimeDelta::FromDays(1),
+ client->expires);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/reporting/reporting_test_util.cc b/chromium/net/reporting/reporting_test_util.cc
new file mode 100644
index 00000000000..8a7dbd06f4d
--- /dev/null
+++ b/chromium/net/reporting/reporting_test_util.cc
@@ -0,0 +1,185 @@
+// Copyright 2017 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 "net/reporting/reporting_test_util.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/timer/mock_timer.h"
+#include "base/timer/timer.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_garbage_collector.h"
+#include "net/reporting/reporting_persister.h"
+#include "net/reporting/reporting_policy.h"
+#include "net/reporting/reporting_uploader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+namespace {
+
+class PendingUploadImpl : public TestReportingUploader::PendingUpload {
+ public:
+ PendingUploadImpl(
+ const GURL& url,
+ const std::string& json,
+ const ReportingUploader::Callback& callback,
+ const base::Callback<void(PendingUpload*)>& complete_callback)
+ : url_(url),
+ json_(json),
+ callback_(callback),
+ complete_callback_(complete_callback) {}
+
+ ~PendingUploadImpl() override {}
+
+ // PendingUpload implementationP:
+ const GURL& url() const override { return url_; }
+ const std::string& json() const override { return json_; }
+ std::unique_ptr<base::Value> GetValue() const override {
+ return base::JSONReader::Read(json_);
+ }
+
+ void Complete(ReportingUploader::Outcome outcome) override {
+ callback_.Run(outcome);
+ // Deletes |this|.
+ complete_callback_.Run(this);
+ }
+
+ private:
+ GURL url_;
+ std::string json_;
+ ReportingUploader::Callback callback_;
+ base::Callback<void(PendingUpload*)> complete_callback_;
+};
+
+void ErasePendingUpload(
+ std::vector<std::unique_ptr<TestReportingUploader::PendingUpload>>* uploads,
+ TestReportingUploader::PendingUpload* upload) {
+ for (auto it = uploads->begin(); it != uploads->end(); ++it) {
+ if (it->get() == upload) {
+ uploads->erase(it);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+} // namespace
+
+const ReportingClient* FindClientInCache(const ReportingCache* cache,
+ const url::Origin& origin,
+ const GURL& endpoint) {
+ std::vector<const ReportingClient*> clients;
+ cache->GetClients(&clients);
+ for (const ReportingClient* client : clients) {
+ if (client->origin == origin && client->endpoint == endpoint)
+ return client;
+ }
+ return nullptr;
+}
+
+TestReportingDelegate::TestReportingDelegate() {}
+TestReportingDelegate::~TestReportingDelegate() {}
+
+void TestReportingDelegate::PersistData(
+ std::unique_ptr<const base::Value> persisted_data) {
+ persisted_data_ = std::move(persisted_data);
+}
+
+std::unique_ptr<const base::Value> TestReportingDelegate::GetPersistedData() {
+ if (!persisted_data_)
+ return std::unique_ptr<const base::Value>();
+ return persisted_data_->CreateDeepCopy();
+}
+
+TestReportingUploader::PendingUpload::~PendingUpload() {}
+TestReportingUploader::PendingUpload::PendingUpload() {}
+
+TestReportingUploader::TestReportingUploader() {}
+TestReportingUploader::~TestReportingUploader() {}
+
+void TestReportingUploader::StartUpload(const GURL& url,
+ const std::string& json,
+ const Callback& callback) {
+ pending_uploads_.push_back(base::MakeUnique<PendingUploadImpl>(
+ url, json, callback, base::Bind(&ErasePendingUpload, &pending_uploads_)));
+}
+
+TestReportingContext::TestReportingContext(const ReportingPolicy& policy)
+ : ReportingContext(policy,
+ base::MakeUnique<TestReportingDelegate>(),
+ base::MakeUnique<base::SimpleTestClock>(),
+ base::MakeUnique<base::SimpleTestTickClock>(),
+ base::MakeUnique<TestReportingUploader>()),
+ persistence_timer_(new base::MockTimer(/* retain_user_task= */ false,
+ /* is_repeating= */ false)),
+ garbage_collection_timer_(
+ new base::MockTimer(/* retain_user_task= */ false,
+ /* is_repeating= */ false)) {
+ persister()->SetTimerForTesting(base::WrapUnique(persistence_timer_));
+ garbage_collector()->SetTimerForTesting(
+ base::WrapUnique(garbage_collection_timer_));
+}
+
+TestReportingContext::~TestReportingContext() {
+ persistence_timer_ = nullptr;
+ garbage_collection_timer_ = nullptr;
+}
+
+ReportingTestBase::ReportingTestBase() {
+ // For tests, disable jitter.
+ ReportingPolicy policy;
+ policy.endpoint_backoff_policy.jitter_factor = 0.0;
+
+ CreateAndInitializeContext(policy, std::unique_ptr<const base::Value>(),
+ base::Time::Now(), base::TimeTicks::Now());
+}
+
+ReportingTestBase::~ReportingTestBase() {}
+
+void ReportingTestBase::UsePolicy(const ReportingPolicy& new_policy) {
+ CreateAndInitializeContext(new_policy, delegate()->GetPersistedData(),
+ clock()->Now(), tick_clock()->NowTicks());
+}
+
+void ReportingTestBase::SimulateRestart(base::TimeDelta delta,
+ base::TimeDelta delta_ticks) {
+ CreateAndInitializeContext(policy(), delegate()->GetPersistedData(),
+ clock()->Now() + delta,
+ tick_clock()->NowTicks() + delta_ticks);
+}
+
+void ReportingTestBase::CreateAndInitializeContext(
+ const ReportingPolicy& policy,
+ std::unique_ptr<const base::Value> persisted_data,
+ base::Time now,
+ base::TimeTicks now_ticks) {
+ context_ = base::MakeUnique<TestReportingContext>(policy);
+ delegate()->PersistData(std::move(persisted_data));
+ clock()->SetNow(now);
+ tick_clock()->SetNowTicks(now_ticks);
+ context_->Initialize();
+}
+
+base::TimeTicks ReportingTestBase::yesterday() {
+ return tick_clock()->NowTicks() - base::TimeDelta::FromDays(1);
+}
+
+base::TimeTicks ReportingTestBase::tomorrow() {
+ return tick_clock()->NowTicks() + base::TimeDelta::FromDays(1);
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_test_util.h b/chromium/net/reporting/reporting_test_util.h
new file mode 100644
index 00000000000..098fe0f4e21
--- /dev/null
+++ b/chromium/net/reporting/reporting_test_util.h
@@ -0,0 +1,195 @@
+// Copyright 2017 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.
+
+#ifndef NET_REPORTING_REPORTING_TEST_UTIL_H_
+#define NET_REPORTING_REPORTING_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_delegate.h"
+#include "net/reporting/reporting_uploader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GURL;
+
+namespace base {
+class MockTimer;
+class SimpleTestClock;
+class SimpleTestTickClock;
+class Value;
+} // namespace base
+
+namespace url {
+class Origin;
+} // namespace url
+
+namespace net {
+
+class ReportingCache;
+struct ReportingClient;
+class ReportingGarbageCollector;
+
+// Finds a particular client (by origin and endpoint) in the cache and returns
+// it (or nullptr if not found).
+const ReportingClient* FindClientInCache(const ReportingCache* cache,
+ const url::Origin& origin,
+ const GURL& endpoint);
+
+// A simple implementation of ReportingDelegate that only persists data in RAM.
+class TestReportingDelegate : public ReportingDelegate {
+ public:
+ TestReportingDelegate();
+
+ ~TestReportingDelegate() override;
+
+ // ReportingDelegate implementation:
+ std::unique_ptr<const base::Value> GetPersistedData() override;
+
+ void PersistData(std::unique_ptr<const base::Value> persisted_data) override;
+
+ private:
+ std::unique_ptr<const base::Value> persisted_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestReportingDelegate);
+};
+
+// A test implementation of ReportingUploader that holds uploads for tests to
+// examine and complete with a specified outcome.
+class TestReportingUploader : public ReportingUploader {
+ public:
+ class PendingUpload {
+ public:
+ virtual ~PendingUpload();
+
+ virtual const GURL& url() const = 0;
+ virtual const std::string& json() const = 0;
+ virtual std::unique_ptr<base::Value> GetValue() const = 0;
+
+ virtual void Complete(Outcome outcome) = 0;
+
+ protected:
+ PendingUpload();
+ };
+
+ TestReportingUploader();
+ ~TestReportingUploader() override;
+
+ const std::vector<std::unique_ptr<PendingUpload>>& pending_uploads() const {
+ return pending_uploads_;
+ }
+
+ // ReportingUploader implementation:
+ void StartUpload(const GURL& url,
+ const std::string& json,
+ const Callback& callback) override;
+
+ private:
+ std::vector<std::unique_ptr<PendingUpload>> pending_uploads_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestReportingUploader);
+};
+
+// A test implementation of ReportingContext that uses test versions of
+// ReportingDelegate, Clock, TickClock, and ReportingUploader.
+class TestReportingContext : public ReportingContext {
+ public:
+ TestReportingContext(const ReportingPolicy& policy);
+ ~TestReportingContext();
+
+ TestReportingDelegate* test_delegate() {
+ return reinterpret_cast<TestReportingDelegate*>(delegate());
+ }
+ base::SimpleTestClock* test_clock() {
+ return reinterpret_cast<base::SimpleTestClock*>(clock());
+ }
+ base::SimpleTestTickClock* test_tick_clock() {
+ return reinterpret_cast<base::SimpleTestTickClock*>(tick_clock());
+ }
+ base::MockTimer* test_persistence_timer() { return persistence_timer_; }
+ base::MockTimer* test_garbage_collection_timer() {
+ return garbage_collection_timer_;
+ }
+ TestReportingUploader* test_uploader() {
+ return reinterpret_cast<TestReportingUploader*>(uploader());
+ }
+
+ private:
+ // Owned by the Persister and GarbageCollector, respectively, but referenced
+ // here to preserve type:
+
+ base::MockTimer* persistence_timer_;
+ base::MockTimer* garbage_collection_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestReportingContext);
+};
+
+// A unit test base class that provides a TestReportingContext and shorthand
+// getters.
+class ReportingTestBase : public ::testing::Test {
+ protected:
+ ReportingTestBase();
+ ~ReportingTestBase() override;
+
+ void UsePolicy(const ReportingPolicy& policy);
+
+ // Simulates an embedder restart, preserving the ReportingPolicy and any data
+ // persisted via the TestReportingDelegate, but nothing else.
+ //
+ // Advances the Clock by |delta|, and the TickClock by |delta_ticks|. Both can
+ // be zero or negative.
+ void SimulateRestart(base::TimeDelta delta, base::TimeDelta delta_ticks);
+
+ TestReportingContext* context() { return context_.get(); }
+
+ const ReportingPolicy& policy() { return context_->policy(); }
+
+ TestReportingDelegate* delegate() { return context_->test_delegate(); }
+ base::SimpleTestClock* clock() { return context_->test_clock(); }
+ base::SimpleTestTickClock* tick_clock() {
+ return context_->test_tick_clock();
+ }
+ base::MockTimer* persistence_timer() {
+ return context_->test_persistence_timer();
+ }
+ base::MockTimer* garbage_collection_timer() {
+ return context_->test_garbage_collection_timer();
+ }
+ TestReportingUploader* uploader() { return context_->test_uploader(); }
+
+ ReportingCache* cache() { return context_->cache(); }
+ ReportingEndpointManager* endpoint_manager() {
+ return context_->endpoint_manager();
+ }
+ ReportingDeliveryAgent* delivery_agent() {
+ return context_->delivery_agent();
+ }
+ ReportingGarbageCollector* garbage_collector() {
+ return context_->garbage_collector();
+ }
+
+ ReportingPersister* persister() { return context_->persister(); }
+
+ base::TimeTicks yesterday();
+
+ base::TimeTicks tomorrow();
+
+ private:
+ void CreateAndInitializeContext(
+ const ReportingPolicy& policy,
+ std::unique_ptr<const base::Value> persisted_data,
+ base::Time now,
+ base::TimeTicks now_ticks);
+
+ std::unique_ptr<TestReportingContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReportingTestBase);
+};
+
+} // namespace net
+
+#endif // NET_REPORING_REPORTING_TEST_UTIL_H_
diff --git a/chromium/net/reporting/reporting_uploader.cc b/chromium/net/reporting/reporting_uploader.cc
new file mode 100644
index 00000000000..19dbecb740a
--- /dev/null
+++ b/chromium/net/reporting/reporting_uploader.cc
@@ -0,0 +1,154 @@
+// 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 "net/reporting/reporting_uploader.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "net/base/elements_upload_data_stream.h"
+#include "net/base/load_flags.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/redirect_info.h"
+#include "net/url_request/url_request_context.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+ReportingUploader::Outcome ResponseCodeToOutcome(int response_code) {
+ if (response_code >= 200 && response_code <= 299)
+ return ReportingUploader::Outcome::SUCCESS;
+ if (response_code == 410)
+ return ReportingUploader::Outcome::REMOVE_ENDPOINT;
+ return ReportingUploader::Outcome::FAILURE;
+}
+
+class ReportingUploaderImpl : public ReportingUploader, URLRequest::Delegate {
+ public:
+ ReportingUploaderImpl(const URLRequestContext* context) : context_(context) {
+ DCHECK(context_);
+ }
+
+ ~ReportingUploaderImpl() override {
+ for (auto& it : uploads_) {
+ base::ResetAndReturn(&it.second->second).Run(Outcome::FAILURE);
+ it.second->first->Cancel();
+ }
+ uploads_.clear();
+ }
+
+ void StartUpload(const GURL& url,
+ const std::string& json,
+ const Callback& callback) override {
+ std::unique_ptr<URLRequest> request =
+ context_->CreateRequest(url, IDLE, this);
+
+ request->set_method("POST");
+
+ request->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_DO_NOT_SAVE_COOKIES |
+ LOAD_DO_NOT_SEND_COOKIES);
+
+ request->SetExtraRequestHeaderByName(HttpRequestHeaders::kContentType,
+ kUploadContentType, true);
+
+ std::vector<char> json_data(json.begin(), json.end());
+ std::unique_ptr<UploadElementReader> reader(
+ new UploadOwnedBytesElementReader(&json_data));
+ request->set_upload(
+ ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
+
+ // This inherently sets mode = "no-cors", but that doesn't matter, because
+ // the origins that are included in the upload don't actually get to see
+ // the response.
+ //
+ // This inherently skips Service Worker, too.
+ request->Start();
+
+ // Have to grab the unique_ptr* first to ensure request.get() happens
+ // before std::move(request).
+ std::unique_ptr<Upload>* upload = &uploads_[request.get()];
+ *upload = base::MakeUnique<Upload>(std::move(request), callback);
+ }
+
+ // URLRequest::Delegate implementation:
+
+ void OnReceivedRedirect(URLRequest* request,
+ const RedirectInfo& redirect_info,
+ bool* defer_redirect) override {
+ if (!redirect_info.new_url.SchemeIsCryptographic()) {
+ request->Cancel();
+ return;
+ }
+ }
+
+ void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) override {
+ request->Cancel();
+ }
+
+ void OnCertificateRequested(URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) override {
+ request->Cancel();
+ }
+
+ void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) override {
+ request->Cancel();
+ }
+
+ void OnResponseStarted(URLRequest* request, int net_error) override {
+ // Grab Upload from map, and hold on to it in a local unique_ptr so it's
+ // removed at the end of the method.
+ auto it = uploads_.find(request);
+ DCHECK(it != uploads_.end());
+ std::unique_ptr<Upload> upload = std::move(it->second);
+ uploads_.erase(it);
+
+ // request->GetResponseCode() should work, but doesn't in the cases above
+ // where the request was canceled, so get the response code by hand.
+ // TODO(juliatuttle): Check if mmenke fixed this yet.
+ HttpResponseHeaders* headers = request->response_headers();
+ int response_code = headers ? headers->response_code() : 0;
+ Outcome outcome = ResponseCodeToOutcome(response_code);
+
+ upload->second.Run(outcome);
+
+ request->Cancel();
+ }
+
+ void OnReadCompleted(URLRequest* request, int bytes_read) override {
+ // Reporting doesn't need anything in the body of the response, so it
+ // doesn't read it, so it should never get OnReadCompleted calls.
+ NOTREACHED();
+ }
+
+ private:
+ using Upload = std::pair<std::unique_ptr<URLRequest>, Callback>;
+
+ const URLRequestContext* context_;
+ std::map<const URLRequest*, std::unique_ptr<Upload>> uploads_;
+};
+
+} // namespace
+
+// static
+const char ReportingUploader::kUploadContentType[] = "application/report";
+
+ReportingUploader::~ReportingUploader() {}
+
+// static
+std::unique_ptr<ReportingUploader> ReportingUploader::Create(
+ const URLRequestContext* context) {
+ return base::MakeUnique<ReportingUploaderImpl>(context);
+}
+
+} // namespace net
diff --git a/chromium/net/reporting/reporting_uploader.h b/chromium/net/reporting/reporting_uploader.h
new file mode 100644
index 00000000000..dfe50e73624
--- /dev/null
+++ b/chromium/net/reporting/reporting_uploader.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef NET_REPORTING_REPORTING_UPLOADER_H_
+#define NET_REPORTING_REPORTING_UPLOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class URLRequestContext;
+
+// Uploads already-serialized reports and converts responses to one of the
+// specified outcomes.
+class NET_EXPORT ReportingUploader {
+ public:
+ enum class Outcome { SUCCESS, REMOVE_ENDPOINT, FAILURE };
+
+ using Callback = base::Callback<void(Outcome outcome)>;
+
+ static const char kUploadContentType[];
+
+ virtual ~ReportingUploader();
+
+ // Starts to upload the reports in |json| (properly tagged as JSON data) to
+ // |url|, and calls |callback| when complete (whether successful or not).
+ virtual void StartUpload(const GURL& url,
+ const std::string& json,
+ const Callback& callback) = 0;
+
+ // Creates a real implementation of |ReportingUploader| that uploads reports
+ // using |context|.
+ static std::unique_ptr<ReportingUploader> Create(
+ const URLRequestContext* context);
+};
+
+} // namespace net
+
+#endif // NET_REPORTING_REPORTING_UPLOADER_H_
diff --git a/chromium/net/reporting/reporting_uploader_unittest.cc b/chromium/net/reporting/reporting_uploader_unittest.cc
new file mode 100644
index 00000000000..8dc7e6ec73c
--- /dev/null
+++ b/chromium/net/reporting/reporting_uploader_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright 2017 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 "net/reporting/reporting_uploader.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/run_loop.h"
+#include "net/cookies/cookie_store.h"
+#include "net/cookies/cookie_store_test_callbacks.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+class ReportingUploaderTest : public ::testing::Test {
+ protected:
+ ReportingUploaderTest()
+ : server_(test_server::EmbeddedTestServer::TYPE_HTTPS),
+ uploader_(ReportingUploader::Create(&context_)) {}
+
+ TestURLRequestContext context_;
+ test_server::EmbeddedTestServer server_;
+ std::unique_ptr<ReportingUploader> uploader_;
+};
+
+const char kUploadBody[] = "{}";
+
+void CheckUpload(const test_server::HttpRequest& request) {
+ EXPECT_EQ("POST", request.method_string);
+ auto it = request.headers.find("Content-Type");
+ EXPECT_TRUE(it != request.headers.end());
+ EXPECT_EQ(ReportingUploader::kUploadContentType, it->second);
+ EXPECT_TRUE(request.has_content);
+ EXPECT_EQ(kUploadBody, request.content);
+}
+
+std::unique_ptr<test_server::HttpResponse> ReturnResponse(
+ HttpStatusCode code,
+ const test_server::HttpRequest& request) {
+ auto response = base::MakeUnique<test_server::BasicHttpResponse>();
+ response->set_code(code);
+ response->set_content("");
+ response->set_content_type("text/plain");
+ return std::move(response);
+}
+
+std::unique_ptr<test_server::HttpResponse> ReturnInvalidResponse(
+ const test_server::HttpRequest& request) {
+ return base::MakeUnique<test_server::RawHttpResponse>(
+ "", "Not a valid HTTP response.");
+}
+
+class TestUploadCallback {
+ public:
+ TestUploadCallback() : called_(false), waiting_(false) {}
+
+ ReportingUploader::Callback callback() {
+ return base::Bind(&TestUploadCallback::OnUploadComplete,
+ base::Unretained(this));
+ }
+
+ void WaitForCall() {
+ if (called_)
+ return;
+
+ base::RunLoop run_loop;
+
+ waiting_ = true;
+ closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+
+ ReportingUploader::Outcome outcome() const { return outcome_; }
+
+ private:
+ void OnUploadComplete(ReportingUploader::Outcome outcome) {
+ EXPECT_FALSE(called_);
+
+ called_ = true;
+ outcome_ = outcome;
+
+ if (waiting_) {
+ waiting_ = false;
+ closure_.Run();
+ }
+ }
+
+ bool called_;
+ ReportingUploader::Outcome outcome_;
+
+ bool waiting_;
+ base::Closure closure_;
+};
+
+TEST_F(ReportingUploaderTest, Upload) {
+ server_.RegisterRequestMonitor(base::Bind(&CheckUpload));
+ server_.RegisterRequestHandler(base::Bind(&ReturnResponse, HTTP_OK));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+}
+
+TEST_F(ReportingUploaderTest, Success) {
+ server_.RegisterRequestHandler(base::Bind(&ReturnResponse, HTTP_OK));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_EQ(ReportingUploader::Outcome::SUCCESS, callback.outcome());
+}
+
+TEST_F(ReportingUploaderTest, NetworkError1) {
+ ASSERT_TRUE(server_.Start());
+ GURL url = server_.GetURL("/");
+ ASSERT_TRUE(server_.ShutdownAndWaitUntilComplete());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(url, kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
+}
+
+TEST_F(ReportingUploaderTest, NetworkError2) {
+ server_.RegisterRequestHandler(base::Bind(&ReturnInvalidResponse));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
+}
+
+TEST_F(ReportingUploaderTest, ServerError) {
+ server_.RegisterRequestHandler(
+ base::Bind(&ReturnResponse, HTTP_INTERNAL_SERVER_ERROR));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
+}
+
+TEST_F(ReportingUploaderTest, RemoveEndpoint) {
+ server_.RegisterRequestHandler(base::Bind(&ReturnResponse, HTTP_GONE));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_EQ(ReportingUploader::Outcome::REMOVE_ENDPOINT, callback.outcome());
+}
+
+const char kRedirectPath[] = "/redirect";
+
+std::unique_ptr<test_server::HttpResponse> ReturnRedirect(
+ const std::string& location,
+ const test_server::HttpRequest& request) {
+ if (request.relative_url != "/")
+ return std::unique_ptr<test_server::HttpResponse>();
+
+ auto response = base::MakeUnique<test_server::BasicHttpResponse>();
+ response->set_code(HTTP_FOUND);
+ response->AddCustomHeader("Location", location);
+ response->set_content(
+ "Thank you, Mario! But our Princess is in another castle.");
+ response->set_content_type("text/plain");
+ return std::move(response);
+}
+
+std::unique_ptr<test_server::HttpResponse> CheckRedirect(
+ bool* redirect_followed_out,
+ const test_server::HttpRequest& request) {
+ if (request.relative_url != kRedirectPath)
+ return std::unique_ptr<test_server::HttpResponse>();
+
+ *redirect_followed_out = true;
+ return ReturnResponse(HTTP_OK, request);
+}
+
+TEST_F(ReportingUploaderTest, FollowHttpsRedirect) {
+ bool followed = false;
+ server_.RegisterRequestHandler(base::Bind(&ReturnRedirect, kRedirectPath));
+ server_.RegisterRequestHandler(base::Bind(&CheckRedirect, &followed));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_TRUE(followed);
+ EXPECT_EQ(ReportingUploader::Outcome::SUCCESS, callback.outcome());
+}
+
+TEST_F(ReportingUploaderTest, DontFollowHttpRedirect) {
+ bool followed = false;
+
+ test_server::EmbeddedTestServer http_server_;
+ http_server_.RegisterRequestHandler(base::Bind(&CheckRedirect, &followed));
+ ASSERT_TRUE(http_server_.Start());
+
+ const GURL target = http_server_.GetURL(kRedirectPath);
+ server_.RegisterRequestHandler(base::Bind(&ReturnRedirect, target.spec()));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody, callback.callback());
+ callback.WaitForCall();
+
+ EXPECT_FALSE(followed);
+ EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
+}
+
+void CheckNoCookie(const test_server::HttpRequest& request) {
+ auto it = request.headers.find("Cookie");
+ EXPECT_TRUE(it == request.headers.end());
+}
+
+TEST_F(ReportingUploaderTest, DontSendCookies) {
+ server_.RegisterRequestMonitor(base::Bind(&CheckNoCookie));
+ server_.RegisterRequestHandler(base::Bind(&ReturnResponse, HTTP_OK));
+ ASSERT_TRUE(server_.Start());
+
+ ResultSavingCookieCallback<bool> cookie_callback;
+ context_.cookie_store()->SetCookieWithOptionsAsync(
+ server_.GetURL("/"), "foo=bar", CookieOptions(),
+ base::Bind(&ResultSavingCookieCallback<bool>::Run,
+ base::Unretained(&cookie_callback)));
+ cookie_callback.WaitUntilDone();
+ ASSERT_TRUE(cookie_callback.result());
+
+ TestUploadCallback upload_callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody,
+ upload_callback.callback());
+ upload_callback.WaitForCall();
+}
+
+std::unique_ptr<test_server::HttpResponse> SendCookie(
+ const test_server::HttpRequest& request) {
+ auto response = base::MakeUnique<test_server::BasicHttpResponse>();
+ response->set_code(HTTP_OK);
+ response->AddCustomHeader("Set-Cookie", "foo=bar");
+ response->set_content("");
+ response->set_content_type("text/plain");
+ return std::move(response);
+}
+
+TEST_F(ReportingUploaderTest, DontSaveCookies) {
+ server_.RegisterRequestHandler(base::Bind(&SendCookie));
+ ASSERT_TRUE(server_.Start());
+
+ TestUploadCallback upload_callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody,
+ upload_callback.callback());
+ upload_callback.WaitForCall();
+
+ GetCookieListCallback cookie_callback;
+ context_.cookie_store()->GetCookieListWithOptionsAsync(
+ server_.GetURL("/"), CookieOptions(),
+ base::Bind(&GetCookieListCallback::Run,
+ base::Unretained(&cookie_callback)));
+ cookie_callback.WaitUntilDone();
+
+ EXPECT_TRUE(cookie_callback.cookies().empty());
+}
+
+std::unique_ptr<test_server::HttpResponse> ReturnCacheableResponse(
+ int* request_count_out,
+ const test_server::HttpRequest& request) {
+ ++*request_count_out;
+ auto response = base::MakeUnique<test_server::BasicHttpResponse>();
+ response->set_code(HTTP_OK);
+ response->AddCustomHeader("Cache-Control", "max-age=86400");
+ response->set_content("");
+ response->set_content_type("text/plain");
+ return std::move(response);
+}
+
+// TODO(juliatuttle): This passes even if the uploader doesn't set
+// LOAD_DISABLE_CACHE. Maybe that's okay -- Chromium might not cache POST
+// responses ever -- but this test should either not exist or be sure that it is
+// testing actual functionality, not a default.
+TEST_F(ReportingUploaderTest, DontCacheResponse) {
+ int request_count = 0;
+ server_.RegisterRequestHandler(
+ base::Bind(&ReturnCacheableResponse, &request_count));
+ ASSERT_TRUE(server_.Start());
+
+ {
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody,
+ callback.callback());
+ callback.WaitForCall();
+ }
+ EXPECT_EQ(1, request_count);
+
+ {
+ TestUploadCallback callback;
+ uploader_->StartUpload(server_.GetURL("/"), kUploadBody,
+ callback.callback());
+ callback.WaitForCall();
+ }
+ EXPECT_EQ(2, request_count);
+}
+
+} // namespace
+} // namespace net