summaryrefslogtreecommitdiff
path: root/chromium/net/network_error_logging
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-05-16 09:59:13 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-05-20 10:28:53 +0000
commit6c11fb357ec39bf087b8b632e2b1e375aef1b38b (patch)
treec8315530db18a8ee566521c39ab8a6af4f72bc03 /chromium/net/network_error_logging
parent3ffaed019d0772e59d6cdb2d0d32fe4834c31f72 (diff)
downloadqtwebengine-chromium-6c11fb357ec39bf087b8b632e2b1e375aef1b38b.tar.gz
BASELINE: Update Chromium to 74.0.3729.159
Change-Id: I8d2497da544c275415aedd94dd25328d555de811 Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/net/network_error_logging')
-rw-r--r--chromium/net/network_error_logging/network_error_logging_service.cc299
-rw-r--r--chromium/net/network_error_logging/network_error_logging_service.h79
-rw-r--r--chromium/net/network_error_logging/network_error_logging_service_unittest.cc332
-rw-r--r--chromium/net/network_error_logging/network_error_logging_test_util.cc5
-rw-r--r--chromium/net/network_error_logging/network_error_logging_test_util.h4
5 files changed, 616 insertions, 103 deletions
diff --git a/chromium/net/network_error_logging/network_error_logging_service.cc b/chromium/net/network_error_logging/network_error_logging_service.cc
index 383426aeef4..4607d9fdb38 100644
--- a/chromium/net/network_error_logging/network_error_logging_service.cc
+++ b/chromium/net/network_error_logging/network_error_logging_service.cc
@@ -12,10 +12,11 @@
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
-#include "base/time/default_tick_clock.h"
-#include "base/time/tick_clock.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/base/ip_address.h"
@@ -168,8 +169,14 @@ void RecordHeaderOutcome(NetworkErrorLoggingService::HeaderOutcome outcome) {
void RecordRequestOutcome(NetworkErrorLoggingService::RequestOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION(
- NetworkErrorLoggingService::kRequestOutcomeHistogram, outcome,
- NetworkErrorLoggingService::RequestOutcome::MAX);
+ NetworkErrorLoggingService::kRequestOutcomeHistogram, outcome);
+}
+
+void RecordSignedExchangeRequestOutcome(
+ NetworkErrorLoggingService::RequestOutcome outcome) {
+ UMA_HISTOGRAM_ENUMERATION(
+ NetworkErrorLoggingService::kSignedExchangeRequestOutcomeHistogram,
+ outcome);
}
class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
@@ -187,6 +194,9 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
void OnHeader(const url::Origin& origin,
const IPAddress& received_ip_address,
const std::string& value) override {
+ if (shut_down_)
+ return;
+
// NEL is only available to secure origins, so don't permit insecure origins
// to set policies.
if (!origin.GetURL().SchemeIsCryptographic()) {
@@ -197,18 +207,20 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
OriginPolicy policy;
policy.origin = origin;
policy.received_ip_address = received_ip_address;
- HeaderOutcome outcome =
- ParseHeader(value, tick_clock_->NowTicks(), &policy);
+ policy.last_used = clock_->Now();
+ HeaderOutcome outcome = ParseHeader(value, clock_->Now(), &policy);
RecordHeaderOutcome(outcome);
if (outcome != HeaderOutcome::SET && outcome != HeaderOutcome::REMOVED)
return;
+ // If a policy for |origin| already existed, remove the old poliicy.
auto it = policies_.find(origin);
- if (it != policies_.end()) {
- MaybeRemoveWildcardPolicy(origin, &it->second);
- policies_.erase(it);
- }
+ if (it != policies_.end())
+ RemovePolicy(it);
+ // A policy's |expires| field is set to a null time if the max_age was 0.
+ // Having a max_age of 0 means that the policy should be removed, so return
+ // here instead of continuing on to inserting the policy.
if (policy.expires.is_null())
return;
@@ -216,11 +228,22 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
auto inserted = policies_.insert(std::make_pair(origin, policy));
DCHECK(inserted.second);
MaybeAddWildcardPolicy(origin, &inserted.first->second);
+
+ // Evict policies if the policy limit is exceeded.
+ if (policies_.size() > kMaxPolicies) {
+ RemoveAllExpiredPolicies();
+ while (policies_.size() > kMaxPolicies) {
+ EvictStalestPolicy();
+ }
+ }
}
void OnRequest(RequestDetails details) override {
+ if (shut_down_)
+ return;
+
if (!reporting_service_) {
- RecordRequestOutcome(RequestOutcome::DISCARDED_NO_REPORTING_SERVICE);
+ RecordRequestOutcome(RequestOutcome::kDiscardedNoReportingService);
return;
}
@@ -230,10 +253,13 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
auto report_origin = url::Origin::Create(details.uri);
const OriginPolicy* policy = FindPolicyForOrigin(report_origin);
if (!policy) {
- RecordRequestOutcome(RequestOutcome::DISCARDED_NO_ORIGIN_POLICY);
+ RecordRequestOutcome(RequestOutcome::kDiscardedNoOriginPolicy);
return;
}
+ // Mark the policy used.
+ policy->last_used = clock_->Now();
+
Error type = details.type;
// It is expected for Reporting uploads to terminate with ERR_ABORTED, since
// the ReportingUploader cancels them after receiving the response code and
@@ -258,7 +284,7 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
// meaningful if it only includes reports that otherwise could have been
// uploaded.
if (details.reporting_upload_depth > kMaxNestedReportDepth) {
- RecordRequestOutcome(RequestOutcome::DISCARDED_REPORTING_UPLOAD);
+ RecordRequestOutcome(RequestOutcome::kDiscardedReportingUpload);
return;
}
@@ -276,19 +302,19 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
// include_subdomains policies are only allowed to report on DNS resolution
// errors.
- if (phase_string != kDnsPhase && policy->include_subdomains &&
- !(policy->origin == report_origin)) {
- RecordRequestOutcome(RequestOutcome::DISCARDED_NON_DNS_SUBDOMAIN_REPORT);
+ if (phase_string != kDnsPhase &&
+ IsMismatchingSubdomainReport(*policy, report_origin)) {
+ RecordRequestOutcome(RequestOutcome::kDiscardedNonDNSSubdomainReport);
return;
}
bool success = (type == OK) && !IsHttpError(details);
- double sampling_fraction =
- success ? policy->success_fraction : policy->failure_fraction;
- if (base::RandDouble() >= sampling_fraction) {
+ const base::Optional<double> sampling_fraction =
+ SampleAndReturnFraction(*policy, success);
+ if (!sampling_fraction.has_value()) {
RecordRequestOutcome(success
- ? RequestOutcome::DISCARDED_UNSAMPLED_SUCCESS
- : RequestOutcome::DISCARDED_UNSAMPLED_FAILURE);
+ ? RequestOutcome::kDiscardedUnsampledSuccess
+ : RequestOutcome::kDiscardedUnsampledFailure);
return;
}
@@ -298,24 +324,79 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
<< details.uri;
reporting_service_->QueueReport(
details.uri, details.user_agent, policy->report_to, kReportType,
- CreateReportBody(phase_string, type_string, sampling_fraction, details),
+ CreateReportBody(phase_string, type_string, sampling_fraction.value(),
+ details),
details.reporting_upload_depth);
- RecordRequestOutcome(RequestOutcome::QUEUED);
+ RecordRequestOutcome(RequestOutcome::kQueued);
}
- void RemoveBrowsingData(const base::RepeatingCallback<bool(const GURL&)>&
- origin_filter) override {
- std::vector<url::Origin> origins_to_remove;
+ void QueueSignedExchangeReport(
+ const SignedExchangeReportDetails& details) override {
+ if (shut_down_)
+ return;
- for (auto it = policies_.begin(); it != policies_.end(); ++it) {
- if (origin_filter.Run(it->first.GetURL()))
- origins_to_remove.push_back(it->first);
+ if (!reporting_service_) {
+ RecordSignedExchangeRequestOutcome(
+ RequestOutcome::kDiscardedNoReportingService);
+ return;
+ }
+ if (!details.outer_url.SchemeIsCryptographic()) {
+ RecordSignedExchangeRequestOutcome(
+ RequestOutcome::kDiscardedInsecureOrigin);
+ return;
}
+ const auto report_origin = url::Origin::Create(details.outer_url);
+ const OriginPolicy* policy = FindPolicyForOrigin(report_origin);
+ if (!policy) {
+ RecordSignedExchangeRequestOutcome(
+ RequestOutcome::kDiscardedNoOriginPolicy);
+ return;
+ }
+
+ // Mark the policy used.
+ policy->last_used = clock_->Now();
+
+ if (IsMismatchingSubdomainReport(*policy, report_origin)) {
+ RecordSignedExchangeRequestOutcome(
+ RequestOutcome::kDiscardedNonDNSSubdomainReport);
+ return;
+ }
+ // Don't send the report when the IP addresses of the server and the policy
+ // don’t match. This case is coverd by OnRequest() while processing the HTTP
+ // response.
+ // This happens if the server has set the NEL policy previously, but doesn't
+ // set the NEL policy for the signed exchange response, and the IP address
+ // has changed due to DNS round robin.
+ if (details.server_ip_address != policy->received_ip_address) {
+ RecordSignedExchangeRequestOutcome(
+ RequestOutcome::kDiscardedIPAddressMismatch);
+ return;
+ }
+ const base::Optional<double> sampling_fraction =
+ SampleAndReturnFraction(*policy, details.success);
+ if (!sampling_fraction.has_value()) {
+ RecordSignedExchangeRequestOutcome(
+ details.success ? RequestOutcome::kDiscardedUnsampledSuccess
+ : RequestOutcome::kDiscardedUnsampledFailure);
+ return;
+ }
+ reporting_service_->QueueReport(
+ details.outer_url, details.user_agent, policy->report_to, kReportType,
+ CreateSignedExchangeReportBody(details, sampling_fraction.value()),
+ 0 /* depth */);
+ RecordSignedExchangeRequestOutcome(RequestOutcome::kQueued);
+ }
- for (auto it = origins_to_remove.begin(); it != origins_to_remove.end();
- ++it) {
- MaybeRemoveWildcardPolicy(*it, &policies_[*it]);
- policies_.erase(*it);
+ void RemoveBrowsingData(const base::RepeatingCallback<bool(const GURL&)>&
+ origin_filter) override {
+ for (auto it = policies_.begin(); it != policies_.end();) {
+ const url::Origin& origin = it->first;
+ // Remove policies matching the filter.
+ if (origin_filter.Run(origin.GetURL())) {
+ it = RemovePolicy(it);
+ } else {
+ ++it;
+ }
}
}
@@ -337,8 +418,8 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
policy_dict.SetKey("includeSubdomains",
base::Value(policy.include_subdomains));
policy_dict.SetKey("reportTo", base::Value(policy.report_to));
- policy_dict.SetKey(
- "expires", base::Value(NetLog::TickCountToString(policy.expires)));
+ policy_dict.SetKey("expires",
+ base::Value(NetLog::TimeToString(policy.expires)));
policy_dict.SetKey("successFraction",
base::Value(policy.success_fraction));
policy_dict.SetKey("failureFraction",
@@ -366,11 +447,15 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
// Reporting API endpoint group to which reports should be sent.
std::string report_to;
- base::TimeTicks expires;
+ base::Time expires;
double success_fraction;
double failure_fraction;
bool include_subdomains;
+
+ // Last time the policy was accessed to create a report, even if no report
+ // ends up being queued. Also updated when the policy is first set.
+ mutable base::Time last_used;
};
// Map from origin to origin's (owned) policy.
@@ -397,15 +482,15 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
WildcardPolicyMap wildcard_policies_;
HeaderOutcome ParseHeader(const std::string& json_value,
- base::TimeTicks now_ticks,
+ base::Time now,
OriginPolicy* policy_out) const {
DCHECK(policy_out);
if (json_value.size() > kMaxJsonSize)
return HeaderOutcome::DISCARDED_JSON_TOO_BIG;
- std::unique_ptr<base::Value> value =
- base::JSONReader::Read(json_value, base::JSON_PARSE_RFC, kMaxJsonDepth);
+ std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(
+ json_value, base::JSON_PARSE_RFC, kMaxJsonDepth);
if (!value)
return HeaderOutcome::DISCARDED_JSON_INVALID;
@@ -449,19 +534,17 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
policy_out->success_fraction = success_fraction;
policy_out->failure_fraction = failure_fraction;
if (max_age_sec > 0) {
- policy_out->expires =
- now_ticks + base::TimeDelta::FromSeconds(max_age_sec);
+ policy_out->expires = now + base::TimeDelta::FromSeconds(max_age_sec);
return HeaderOutcome::SET;
} else {
- policy_out->expires = base::TimeTicks();
+ policy_out->expires = base::Time();
return HeaderOutcome::REMOVED;
}
}
const OriginPolicy* FindPolicyForOrigin(const url::Origin& origin) const {
- // TODO(juliatuttle): Clean out expired policies sometime/somewhere.
auto it = policies_.find(origin);
- if (it != policies_.end() && tick_clock_->NowTicks() < it->second.expires)
+ if (it != policies_.end() && clock_->Now() < it->second.expires)
return &it->second;
std::string domain = origin.host();
@@ -492,7 +575,7 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
}
for (auto jt = it->second.begin(); jt != it->second.end(); ++jt) {
- if (tick_clock_->NowTicks() < (*jt)->expires)
+ if (clock_->Now() < (*jt)->expires)
return *jt;
}
@@ -511,14 +594,24 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
DCHECK(inserted.second);
}
- void MaybeRemoveWildcardPolicy(const url::Origin& origin,
- const OriginPolicy* policy) {
+ // Removes the policy pointed to by |policy_it|. Invalidates |policy_it|.
+ // Returns the iterator to the next element.
+ PolicyMap::iterator RemovePolicy(PolicyMap::iterator policy_it) {
+ DCHECK(policy_it != policies_.end());
+ OriginPolicy* policy = &policy_it->second;
+ MaybeRemoveWildcardPolicy(policy);
+ return policies_.erase(policy_it);
+ }
+
+ void MaybeRemoveWildcardPolicy(const OriginPolicy* policy) {
DCHECK(policy);
- DCHECK_EQ(policy, &policies_[origin]);
if (!policy->include_subdomains)
return;
+ const url::Origin& origin = policy->origin;
+ DCHECK_EQ(policy, &policies_[origin]);
+
auto wildcard_it = wildcard_policies_.find(origin.host());
DCHECK(wildcard_it != wildcard_policies_.end());
@@ -528,6 +621,30 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
wildcard_policies_.erase(wildcard_it);
}
+ void RemoveAllExpiredPolicies() {
+ for (auto it = policies_.begin(); it != policies_.end();) {
+ if (it->second.expires < clock_->Now()) {
+ it = RemovePolicy(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ void EvictStalestPolicy() {
+ PolicyMap::iterator stalest_it = policies_.begin();
+ for (auto it = policies_.begin(); it != policies_.end(); ++it) {
+ if (it->second.last_used < stalest_it->second.last_used)
+ stalest_it = it;
+ }
+
+ // This should only be called if we have hit the max policy limit, so there
+ // should be at least one policy.
+ DCHECK(stalest_it != policies_.end());
+
+ RemovePolicy(stalest_it);
+ }
+
std::unique_ptr<const base::Value> CreateReportBody(
const std::string& phase,
const std::string& type,
@@ -547,6 +664,49 @@ class NetworkErrorLoggingServiceImpl : public NetworkErrorLoggingService {
return std::move(body);
}
+
+ std::unique_ptr<const base::Value> CreateSignedExchangeReportBody(
+ const SignedExchangeReportDetails& details,
+ double sampling_fraction) const {
+ auto body = std::make_unique<base::DictionaryValue>();
+ body->SetString(kPhaseKey, kSignedExchangePhaseValue);
+ body->SetString(kTypeKey, details.type);
+ body->SetDouble(kSamplingFractionKey, sampling_fraction);
+ body->SetString(kReferrerKey, details.referrer);
+ body->SetString(kServerIpKey, details.server_ip_address.ToString());
+ body->SetString(kProtocolKey, details.protocol);
+ body->SetString(kMethodKey, details.method);
+ body->SetInteger(kStatusCodeKey, details.status_code);
+ body->SetInteger(kElapsedTimeKey, details.elapsed_time.InMilliseconds());
+
+ auto sxg_body = std::make_unique<base::DictionaryValue>();
+ sxg_body->SetKey(kOuterUrlKey, base::Value(details.outer_url.spec()));
+ if (details.inner_url.is_valid())
+ sxg_body->SetKey(kInnerUrlKey, base::Value(details.inner_url.spec()));
+
+ base::Value cert_url_list = base::Value(base::Value::Type::LIST);
+ if (details.cert_url.is_valid())
+ cert_url_list.GetList().push_back(base::Value(details.cert_url.spec()));
+ sxg_body->SetKey(kCertUrlKey, std::move(cert_url_list));
+ body->SetDictionary(kSignedExchangeBodyKey, std::move(sxg_body));
+
+ return std::move(body);
+ }
+
+ bool IsMismatchingSubdomainReport(const OriginPolicy& policy,
+ const url::Origin& report_origin) const {
+ return policy.include_subdomains && (policy.origin != report_origin);
+ }
+
+ // Returns a valid value of matching fraction iff the event should be sampled.
+ base::Optional<double> SampleAndReturnFraction(const OriginPolicy& policy,
+ bool success) const {
+ const double sampling_fraction =
+ success ? policy.success_fraction : policy.failure_fraction;
+ if (base::RandDouble() >= sampling_fraction)
+ return base::nullopt;
+ return sampling_fraction;
+ }
};
} // namespace
@@ -558,6 +718,16 @@ NetworkErrorLoggingService::RequestDetails::RequestDetails(
NetworkErrorLoggingService::RequestDetails::~RequestDetails() = default;
+NetworkErrorLoggingService::SignedExchangeReportDetails::
+ SignedExchangeReportDetails() = default;
+
+NetworkErrorLoggingService::SignedExchangeReportDetails::
+ SignedExchangeReportDetails(const SignedExchangeReportDetails& other) =
+ default;
+
+NetworkErrorLoggingService::SignedExchangeReportDetails::
+ ~SignedExchangeReportDetails() = default;
+
const char NetworkErrorLoggingService::kHeaderName[] = "NEL";
const char NetworkErrorLoggingService::kReportType[] = "network-error";
@@ -568,6 +738,10 @@ const char NetworkErrorLoggingService::kHeaderOutcomeHistogram[] =
const char NetworkErrorLoggingService::kRequestOutcomeHistogram[] =
"Net.NetworkErrorLogging.RequestOutcome";
+const char
+ NetworkErrorLoggingService::kSignedExchangeRequestOutcomeHistogram[] =
+ "Net.NetworkErrorLogging.SignedExchangeRequestOutcome";
+
// Allow NEL reports on regular requests, plus NEL reports on Reporting uploads
// containing only regular requests, but do not allow NEL reports on Reporting
// uploads containing Reporting uploads.
@@ -587,6 +761,15 @@ const char NetworkErrorLoggingService::kElapsedTimeKey[] = "elapsed_time";
const char NetworkErrorLoggingService::kPhaseKey[] = "phase";
const char NetworkErrorLoggingService::kTypeKey[] = "type";
+const char NetworkErrorLoggingService::kSignedExchangePhaseValue[] = "sxg";
+const char NetworkErrorLoggingService::kSignedExchangeBodyKey[] = "sxg";
+const char NetworkErrorLoggingService::kOuterUrlKey[] = "outer_url";
+const char NetworkErrorLoggingService::kInnerUrlKey[] = "inner_url";
+const char NetworkErrorLoggingService::kCertUrlKey[] = "cert_url";
+
+// See also: max number of Reporting endpoints specified in ReportingPolicy.
+const size_t NetworkErrorLoggingService::kMaxPolicies = 1000u;
+
// static
void NetworkErrorLoggingService::
RecordHeaderDiscardedForNoNetworkErrorLoggingService() {
@@ -613,13 +796,12 @@ void NetworkErrorLoggingService::
// static
void NetworkErrorLoggingService::
RecordRequestDiscardedForNoNetworkErrorLoggingService() {
- RecordRequestOutcome(
- RequestOutcome::DISCARDED_NO_NETWORK_ERROR_LOGGING_SERVICE);
+ RecordRequestOutcome(RequestOutcome::kDiscardedNoNetworkErrorLoggingService);
}
// static
void NetworkErrorLoggingService::RecordRequestDiscardedForInsecureOrigin() {
- RecordRequestOutcome(RequestOutcome::DISCARDED_INSECURE_ORIGIN);
+ RecordRequestOutcome(RequestOutcome::kDiscardedInsecureOrigin);
}
// static
@@ -635,9 +817,13 @@ void NetworkErrorLoggingService::SetReportingService(
reporting_service_ = reporting_service;
}
-void NetworkErrorLoggingService::SetTickClockForTesting(
- const base::TickClock* tick_clock) {
- tick_clock_ = tick_clock;
+void NetworkErrorLoggingService::OnShutdown() {
+ shut_down_ = true;
+ SetReportingService(nullptr);
+}
+
+void NetworkErrorLoggingService::SetClockForTesting(const base::Clock* clock) {
+ clock_ = clock;
}
base::Value NetworkErrorLoggingService::StatusAsValue() const {
@@ -651,7 +837,8 @@ std::set<url::Origin> NetworkErrorLoggingService::GetPolicyOriginsForTesting() {
}
NetworkErrorLoggingService::NetworkErrorLoggingService()
- : tick_clock_(base::DefaultTickClock::GetInstance()),
- reporting_service_(nullptr) {}
+ : clock_(base::DefaultClock::GetInstance()),
+ reporting_service_(nullptr),
+ shut_down_(false) {}
} // namespace net
diff --git a/chromium/net/network_error_logging/network_error_logging_service.h b/chromium/net/network_error_logging/network_error_logging_service.h
index 165ca37b6fa..7527ff1b1f2 100644
--- a/chromium/net/network_error_logging/network_error_logging_service.h
+++ b/chromium/net/network_error_logging/network_error_logging_service.h
@@ -12,7 +12,7 @@
#include "base/feature_list.h"
#include "base/macros.h"
-#include "base/time/tick_clock.h"
+#include "base/time/clock.h"
#include "base/time/time.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
@@ -71,6 +71,26 @@ class NET_EXPORT NetworkErrorLoggingService {
int reporting_upload_depth;
};
+ // The details of a signed exchange report.
+ struct NET_EXPORT SignedExchangeReportDetails {
+ SignedExchangeReportDetails();
+ SignedExchangeReportDetails(const SignedExchangeReportDetails& other);
+ ~SignedExchangeReportDetails();
+
+ bool success;
+ std::string type;
+ GURL outer_url;
+ GURL inner_url;
+ GURL cert_url;
+ std::string referrer;
+ IPAddress server_ip_address;
+ std::string protocol;
+ std::string method;
+ int32_t status_code;
+ base::TimeDelta elapsed_time;
+ std::string user_agent;
+ };
+
static const char kHeaderName[];
static const char kReportType[];
@@ -89,11 +109,21 @@ class NET_EXPORT NetworkErrorLoggingService {
static const char kPhaseKey[];
static const char kTypeKey[];
+ static const char kSignedExchangePhaseValue[];
+ static const char kSignedExchangeBodyKey[];
+ static const char kOuterUrlKey[];
+ static const char kInnerUrlKey[];
+ static const char kCertUrlKey[];
+
+ // Maximum number of NEL policies to store before evicting.
+ static const size_t kMaxPolicies;
+
// Histograms. These are mainly used in test cases to verify that interesting
// events occurred.
static const char kHeaderOutcomeHistogram[];
static const char kRequestOutcomeHistogram[];
+ static const char kSignedExchangeRequestOutcomeHistogram[];
enum class HeaderOutcome {
DISCARDED_NO_NETWORK_ERROR_LOGGING_SERVICE = 0,
@@ -120,19 +150,20 @@ class NET_EXPORT NetworkErrorLoggingService {
};
enum class RequestOutcome {
- DISCARDED_NO_NETWORK_ERROR_LOGGING_SERVICE = 0,
-
- DISCARDED_NO_REPORTING_SERVICE = 1,
- DISCARDED_INSECURE_ORIGIN = 2,
- DISCARDED_NO_ORIGIN_POLICY = 3,
- DISCARDED_UNMAPPED_ERROR = 4,
- DISCARDED_REPORTING_UPLOAD = 5,
- DISCARDED_UNSAMPLED_SUCCESS = 6,
- DISCARDED_UNSAMPLED_FAILURE = 7,
- QUEUED = 8,
- DISCARDED_NON_DNS_SUBDOMAIN_REPORT = 9,
-
- MAX
+ kDiscardedNoNetworkErrorLoggingService = 0,
+
+ kDiscardedNoReportingService = 1,
+ kDiscardedInsecureOrigin = 2,
+ kDiscardedNoOriginPolicy = 3,
+ kDiscardedUnmappedError = 4,
+ kDiscardedReportingUpload = 5,
+ kDiscardedUnsampledSuccess = 6,
+ kDiscardedUnsampledFailure = 7,
+ kQueued = 8,
+ kDiscardedNonDNSSubdomainReport = 9,
+ kDiscardedIPAddressMismatch = 10,
+
+ kMaxValue = kDiscardedIPAddressMismatch
};
static void RecordHeaderDiscardedForNoNetworkErrorLoggingService();
@@ -168,6 +199,10 @@ class NET_EXPORT NetworkErrorLoggingService {
// not called on any insecure requests.
virtual void OnRequest(RequestDetails details) = 0;
+ // Queues a Signed Exchange report.
+ virtual void QueueSignedExchangeReport(
+ const SignedExchangeReportDetails& details) = 0;
+
// Removes browsing data (origin policies) associated with any origin for
// which |origin_filter| returns true.
virtual void RemoveBrowsingData(
@@ -182,21 +217,29 @@ class NET_EXPORT NetworkErrorLoggingService {
// |reporting_service| must outlive the NetworkErrorLoggingService.
void SetReportingService(ReportingService* reporting_service);
- // Sets a base::TickClock (used to track policy expiration) for tests.
- // |tick_clock| must outlive the NetworkErrorLoggingService, and cannot be
+ // Shuts down the NEL service so that no more requests or headers are
+ // processed and no more reports are queued.
+ void OnShutdown();
+
+ // Sets a base::Clock (used to track policy expiration) for tests.
+ // |clock| must outlive the NetworkErrorLoggingService, and cannot be
// nullptr.
- void SetTickClockForTesting(const base::TickClock* tick_clock);
+ void SetClockForTesting(const base::Clock* clock);
+ // Dumps info about all the currently stored policies, including expired ones.
+ // Used to display information about NEL policies on the NetLog Reporting tab.
virtual base::Value StatusAsValue() const;
+ // Gets the origins of all currently stored policies, including expired ones.
virtual std::set<url::Origin> GetPolicyOriginsForTesting();
protected:
NetworkErrorLoggingService();
// Unowned:
- const base::TickClock* tick_clock_;
+ const base::Clock* clock_;
ReportingService* reporting_service_;
+ bool shut_down_;
private:
DISALLOW_COPY_AND_ASSIGN(NetworkErrorLoggingService);
diff --git a/chromium/net/network_error_logging/network_error_logging_service_unittest.cc b/chromium/net/network_error_logging/network_error_logging_service_unittest.cc
index 0d740a72c02..89eae7dae25 100644
--- a/chromium/net/network_error_logging/network_error_logging_service_unittest.cc
+++ b/chromium/net/network_error_logging/network_error_logging_service_unittest.cc
@@ -9,7 +9,8 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
-#include "base/test/simple_test_tick_clock.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/simple_test_clock.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
@@ -96,11 +97,18 @@ class TestReportingService : public ReportingService {
void RemoveAllBrowsingData(int data_type_mask) override { NOTREACHED(); }
+ void OnShutdown() override {}
+
const ReportingPolicy& GetPolicy() const override {
NOTREACHED();
return dummy_policy_;
}
+ ReportingContext* GetContextForTesting() const override {
+ NOTREACHED();
+ return nullptr;
+ }
+
private:
std::vector<Report> reports_;
ReportingPolicy dummy_policy_;
@@ -151,16 +159,56 @@ class NetworkErrorLoggingServiceTest : public ::testing::Test {
return details;
}
+ NetworkErrorLoggingService::SignedExchangeReportDetails
+ MakeSignedExchangeReportDetails(bool success,
+ const std::string& type,
+ const GURL& outer_url,
+ const GURL& inner_url,
+ const GURL& cert_url,
+ const IPAddress& server_ip_address) {
+ NetworkErrorLoggingService::SignedExchangeReportDetails details;
+ details.success = success;
+ details.type = type;
+ details.outer_url = outer_url;
+ details.inner_url = inner_url;
+ details.cert_url = cert_url;
+ details.referrer = kReferrer_.spec();
+ details.server_ip_address = server_ip_address;
+ details.protocol = "http/1.1";
+ details.method = "GET";
+ details.status_code = 200;
+ details.elapsed_time = base::TimeDelta::FromMilliseconds(1234);
+ details.user_agent = kUserAgent_;
+ return details;
+ }
NetworkErrorLoggingService* service() { return service_.get(); }
const std::vector<TestReportingService::Report>& reports() {
return reporting_service_->reports();
}
+ const url::Origin MakeOrigin(size_t index) {
+ GURL url(base::StringPrintf("https://example%zd.com/", index));
+ return url::Origin::Create(url);
+ }
+
+ // Returns whether the NetworkErrorLoggingService has a policy corresponding
+ // to |origin|. Returns true if so, even if the policy is expired.
+ bool HasPolicyForOrigin(const url::Origin& origin) {
+ std::set<url::Origin> all_policy_origins =
+ service_->GetPolicyOriginsForTesting();
+ return all_policy_origins.find(origin) != all_policy_origins.end();
+ }
+
+ size_t PolicyCount() { return service_->GetPolicyOriginsForTesting().size(); }
+
const GURL kUrl_ = GURL("https://example.com/path");
const GURL kUrlDifferentPort_ = GURL("https://example.com:4433/path");
const GURL kUrlSubdomain_ = GURL("https://subdomain.example.com/path");
const GURL kUrlDifferentHost_ = GURL("https://example2.com/path");
+ const GURL kInnerUrl_ = GURL("https://example.net/path");
+ const GURL kCertUrl_ = GURL("https://example.com/cert_path");
+
const IPAddress kServerIP_ = IPAddress(192, 168, 0, 1);
const IPAddress kOtherServerIP_ = IPAddress(192, 168, 0, 2);
const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
@@ -171,6 +219,10 @@ class NetworkErrorLoggingServiceTest : public ::testing::Test {
url::Origin::Create(kUrlDifferentHost_);
const std::string kHeader_ = "{\"report_to\":\"group\",\"max_age\":86400}";
+ const std::string kHeaderSuccessFraction0_ =
+ "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":0.0}";
+ const std::string kHeaderSuccessFraction1_ =
+ "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
const std::string kHeaderIncludeSubdomains_ =
"{\"report_to\":\"group\",\"max_age\":86400,\"include_subdomains\":true}";
const std::string kHeaderMaxAge0_ = "{\"max_age\":0}";
@@ -237,9 +289,7 @@ TEST_F(NetworkErrorLoggingServiceTest, JsonTooDeep) {
}
TEST_F(NetworkErrorLoggingServiceTest, SuccessReportQueued) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(MakeRequestDetails(kUrl_, OK));
@@ -385,9 +435,7 @@ TEST_F(NetworkErrorLoggingServiceTest, HttpErrorReportQueued) {
}
TEST_F(NetworkErrorLoggingServiceTest, SuccessReportDowngraded) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(
MakeRequestDetails(kUrl_, OK, "GET", 200, kOtherServerIP_));
@@ -421,9 +469,7 @@ TEST_F(NetworkErrorLoggingServiceTest, SuccessReportDowngraded) {
}
TEST_F(NetworkErrorLoggingServiceTest, FailureReportDowngraded) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED, "GET",
200, kOtherServerIP_));
@@ -457,9 +503,7 @@ TEST_F(NetworkErrorLoggingServiceTest, FailureReportDowngraded) {
}
TEST_F(NetworkErrorLoggingServiceTest, HttpErrorReportDowngraded) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(
MakeRequestDetails(kUrl_, OK, "GET", 504, kOtherServerIP_));
@@ -493,9 +537,7 @@ TEST_F(NetworkErrorLoggingServiceTest, HttpErrorReportDowngraded) {
}
TEST_F(NetworkErrorLoggingServiceTest, DNSFailureReportNotDowngraded) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(MakeRequestDetails(kUrl_, ERR_NAME_NOT_RESOLVED, "GET",
0, kOtherServerIP_));
@@ -529,9 +571,7 @@ TEST_F(NetworkErrorLoggingServiceTest, DNSFailureReportNotDowngraded) {
}
TEST_F(NetworkErrorLoggingServiceTest, SuccessPOSTReportQueued) {
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnRequest(MakeRequestDetails(kUrl_, OK, "POST"));
@@ -561,8 +601,11 @@ TEST_F(NetworkErrorLoggingServiceTest, SuccessPOSTReportQueued) {
TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ EXPECT_EQ(1u, PolicyCount());
+ // Max_age of 0 removes the policy.
service()->OnHeader(kOrigin_, kServerIP_, kHeaderMaxAge0_);
+ EXPECT_EQ(0u, PolicyCount());
service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
@@ -570,9 +613,7 @@ TEST_F(NetworkErrorLoggingServiceTest, MaxAge0) {
}
TEST_F(NetworkErrorLoggingServiceTest, SuccessFraction0) {
- static const std::string kHeaderSuccessFraction0 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":0.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction0);
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction0_);
// Each network error has a 0% chance of being reported. Fire off several and
// verify that no reports are produced.
@@ -753,22 +794,31 @@ TEST_F(NetworkErrorLoggingServiceTest,
TEST_F(NetworkErrorLoggingServiceTest, RemoveAllBrowsingData) {
service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ EXPECT_EQ(1u, PolicyCount());
+ EXPECT_TRUE(HasPolicyForOrigin(kOrigin_));
service()->RemoveAllBrowsingData();
service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
+ EXPECT_EQ(0u, PolicyCount());
+ EXPECT_FALSE(HasPolicyForOrigin(kOrigin_));
EXPECT_TRUE(reports().empty());
}
TEST_F(NetworkErrorLoggingServiceTest, RemoveSomeBrowsingData) {
service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
service()->OnHeader(kOriginDifferentHost_, kServerIP_, kHeader_);
+ EXPECT_EQ(2u, PolicyCount());
+ // Remove policy for kOrigin_ but not kOriginDifferentHost_
service()->RemoveBrowsingData(
base::BindRepeating([](const GURL& origin) -> bool {
return origin.host() == "example.com";
}));
+ EXPECT_EQ(1u, PolicyCount());
+ EXPECT_TRUE(HasPolicyForOrigin(kOriginDifferentHost_));
+ EXPECT_FALSE(HasPolicyForOrigin(kOrigin_));
service()->OnRequest(MakeRequestDetails(kUrl_, ERR_CONNECTION_REFUSED));
@@ -807,12 +857,20 @@ TEST_F(NetworkErrorLoggingServiceTest, NestedTooDeep) {
}
TEST_F(NetworkErrorLoggingServiceTest, StatusAsValue) {
- base::SimpleTestTickClock clock;
- service()->SetTickClockForTesting(&clock);
-
- static const std::string kHeaderSuccessFraction1 =
- "{\"report_to\":\"group\",\"max_age\":86400,\"success_fraction\":1.0}";
- service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1);
+ // The expiration times will be bogus, but we need a reproducible value for
+ // this test.
+ base::SimpleTestClock clock;
+ service()->SetClockForTesting(&clock);
+ // The clock is initialized to the "zero" or origin point of the Time class.
+ // This sets the clock's Time to the equivalent of the "zero" or origin point
+ // of the TimeTicks class, so that the serialized value produced by
+ // NetLog::TimeToString is consistent across restarts.
+ base::TimeDelta delta_from_origin =
+ base::Time::UnixEpoch().since_origin() -
+ base::TimeTicks::UnixEpoch().since_origin();
+ clock.Advance(delta_from_origin);
+
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
service()->OnHeader(kOriginDifferentHost_, kServerIP_, kHeader_);
service()->OnHeader(kOriginSubdomain_, kServerIP_, kHeaderIncludeSubdomains_);
const std::string kHeaderWrongTypes =
@@ -828,7 +886,8 @@ TEST_F(NetworkErrorLoggingServiceTest, StatusAsValue) {
kServerIP_, kHeaderWrongTypes);
base::Value actual = service()->StatusAsValue();
- std::unique_ptr<base::Value> expected = base::test::ParseJson(R"json(
+ std::unique_ptr<base::Value> expected =
+ base::test::ParseJsonDeprecated(R"json(
{
"originPolicies": [
{
@@ -869,5 +928,220 @@ TEST_F(NetworkErrorLoggingServiceTest, StatusAsValue) {
EXPECT_EQ(*expected, actual);
}
+TEST_F(NetworkErrorLoggingServiceTest, NoReportingService_SignedExchange) {
+ DestroyReportingService();
+
+ service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ false, "sxg.failed", kUrl_, kInnerUrl_, kCertUrl_, kServerIP_));
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, NoPolicyForOrigin_SignedExchange) {
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ false, "sxg.failed", kUrl_, kInnerUrl_, kCertUrl_, kServerIP_));
+ EXPECT_TRUE(reports().empty());
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, SuccessFraction0_SignedExchange) {
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction0_);
+
+ // Each network error has a 0% chance of being reported. Fire off several and
+ // verify that no reports are produced.
+ constexpr size_t kReportCount = 100;
+ for (size_t i = 0; i < kReportCount; ++i) {
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ true, "ok", kUrl_, kInnerUrl_, kCertUrl_, kServerIP_));
+ }
+
+ EXPECT_TRUE(reports().empty());
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, SuccessReportQueued_SignedExchange) {
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderSuccessFraction1_);
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ true, "ok", kUrl_, kInnerUrl_, kCertUrl_, kServerIP_));
+ ASSERT_EQ(1u, reports().size());
+ EXPECT_EQ(kUrl_, reports()[0].url);
+ EXPECT_EQ(kUserAgent_, reports()[0].user_agent);
+ EXPECT_EQ(kGroup_, reports()[0].group);
+ EXPECT_EQ(kType_, reports()[0].type);
+ EXPECT_EQ(0, reports()[0].depth);
+
+ const base::DictionaryValue* body;
+ ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
+ base::ExpectDictStringValue(kReferrer_.spec(), *body,
+ NetworkErrorLoggingService::kReferrerKey);
+ ExpectDictDoubleValue(1.0, *body,
+ NetworkErrorLoggingService::kSamplingFractionKey);
+ base::ExpectDictStringValue(kServerIP_.ToString(), *body,
+ NetworkErrorLoggingService::kServerIpKey);
+ base::ExpectDictStringValue("http/1.1", *body,
+ NetworkErrorLoggingService::kProtocolKey);
+ base::ExpectDictStringValue("GET", *body,
+ NetworkErrorLoggingService::kMethodKey);
+ base::ExpectDictIntegerValue(200, *body,
+ NetworkErrorLoggingService::kStatusCodeKey);
+ base::ExpectDictIntegerValue(1234, *body,
+ NetworkErrorLoggingService::kElapsedTimeKey);
+ base::ExpectDictStringValue(
+ NetworkErrorLoggingService::kSignedExchangePhaseValue, *body,
+ NetworkErrorLoggingService::kPhaseKey);
+ base::ExpectDictStringValue("ok", *body,
+ NetworkErrorLoggingService::kTypeKey);
+
+ const base::DictionaryValue* sxg_body;
+ ASSERT_TRUE(body->FindKey(NetworkErrorLoggingService::kSignedExchangeBodyKey)
+ ->GetAsDictionary(&sxg_body));
+
+ base::ExpectDictStringValue(kUrl_.spec(), *sxg_body,
+ NetworkErrorLoggingService::kOuterUrlKey);
+ base::ExpectDictStringValue(kInnerUrl_.spec(), *sxg_body,
+ NetworkErrorLoggingService::kInnerUrlKey);
+ base::ExpectStringValue(
+ kCertUrl_.spec(),
+ sxg_body->FindKey(NetworkErrorLoggingService::kCertUrlKey)->GetList()[0]);
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, FailureReportQueued_SignedExchange) {
+ service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ false, "sxg.failed", kUrl_, kInnerUrl_, kCertUrl_, kServerIP_));
+ ASSERT_EQ(1u, reports().size());
+ EXPECT_EQ(kUrl_, reports()[0].url);
+ EXPECT_EQ(kUserAgent_, reports()[0].user_agent);
+ EXPECT_EQ(kGroup_, reports()[0].group);
+ EXPECT_EQ(kType_, reports()[0].type);
+ EXPECT_EQ(0, reports()[0].depth);
+
+ const base::DictionaryValue* body;
+ ASSERT_TRUE(reports()[0].body->GetAsDictionary(&body));
+ base::ExpectDictStringValue(kReferrer_.spec(), *body,
+ NetworkErrorLoggingService::kReferrerKey);
+ ExpectDictDoubleValue(1.0, *body,
+ NetworkErrorLoggingService::kSamplingFractionKey);
+ base::ExpectDictStringValue(kServerIP_.ToString(), *body,
+ NetworkErrorLoggingService::kServerIpKey);
+ base::ExpectDictStringValue("http/1.1", *body,
+ NetworkErrorLoggingService::kProtocolKey);
+ base::ExpectDictStringValue("GET", *body,
+ NetworkErrorLoggingService::kMethodKey);
+ base::ExpectDictIntegerValue(200, *body,
+ NetworkErrorLoggingService::kStatusCodeKey);
+ base::ExpectDictIntegerValue(1234, *body,
+ NetworkErrorLoggingService::kElapsedTimeKey);
+ base::ExpectDictStringValue(
+ NetworkErrorLoggingService::kSignedExchangePhaseValue, *body,
+ NetworkErrorLoggingService::kPhaseKey);
+ base::ExpectDictStringValue("sxg.failed", *body,
+ NetworkErrorLoggingService::kTypeKey);
+
+ const base::DictionaryValue* sxg_body;
+ ASSERT_TRUE(body->FindKey(NetworkErrorLoggingService::kSignedExchangeBodyKey)
+ ->GetAsDictionary(&sxg_body));
+
+ base::ExpectDictStringValue(kUrl_.spec(), *sxg_body,
+ NetworkErrorLoggingService::kOuterUrlKey);
+ base::ExpectDictStringValue(kInnerUrl_.spec(), *sxg_body,
+ NetworkErrorLoggingService::kInnerUrlKey);
+ base::ExpectStringValue(
+ kCertUrl_.spec(),
+ sxg_body->FindKey(NetworkErrorLoggingService::kCertUrlKey)->GetList()[0]);
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, MismatchingSubdomain_SignedExchange) {
+ service()->OnHeader(kOrigin_, kServerIP_, kHeaderIncludeSubdomains_);
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ false, "sxg.failed", kUrlSubdomain_, kInnerUrl_, kCertUrl_, kServerIP_));
+ EXPECT_TRUE(reports().empty());
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, MismatchingIPAddress_SignedExchange) {
+ service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ service()->QueueSignedExchangeReport(MakeSignedExchangeReportDetails(
+ false, "sxg.failed", kUrl_, kInnerUrl_, kCertUrl_, kOtherServerIP_));
+ EXPECT_TRUE(reports().empty());
+}
+
+// When the max number of policies is exceeded, first try to remove expired
+// policies before evicting the least recently used unexpired policy.
+TEST_F(NetworkErrorLoggingServiceTest, EvictAllExpiredPoliciesFirst) {
+ base::SimpleTestClock clock;
+ service()->SetClockForTesting(&clock);
+
+ // Add 100 policies then make them expired.
+ for (size_t i = 0; i < 100; ++i) {
+ service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+ }
+ EXPECT_EQ(100u, PolicyCount());
+ clock.Advance(base::TimeDelta::FromSeconds(86401)); // max_age is 86400 sec
+ // Expired policies are allowed to linger before hitting the policy limit.
+ EXPECT_EQ(100u, PolicyCount());
+
+ // Reach the max policy limit.
+ for (size_t i = 100; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+ service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+ }
+ EXPECT_EQ(NetworkErrorLoggingService::kMaxPolicies, PolicyCount());
+
+ // Add one more policy to trigger eviction of only the expired policies.
+ service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ EXPECT_EQ(NetworkErrorLoggingService::kMaxPolicies - 100 + 1, PolicyCount());
+}
+
+TEST_F(NetworkErrorLoggingServiceTest, EvictLeastRecentlyUsedPolicy) {
+ base::SimpleTestClock clock;
+ service()->SetClockForTesting(&clock);
+
+ // A policy's |last_used| is updated when it is added
+ for (size_t i = 0; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+ service()->OnHeader(MakeOrigin(i), kServerIP_, kHeader_);
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ }
+
+ EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+ // Set another policy which triggers eviction. None of the policies have
+ // expired, so the least recently used (i.e. least recently added) policy
+ // should be evicted.
+ service()->OnHeader(kOrigin_, kServerIP_, kHeader_);
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+ EXPECT_FALSE(HasPolicyForOrigin(MakeOrigin(0))); // evicted
+ std::set<url::Origin> all_policy_origins =
+ service()->GetPolicyOriginsForTesting();
+ for (size_t i = 1; i < NetworkErrorLoggingService::kMaxPolicies; ++i) {
+ // Avoid n calls to HasPolicyForOrigin(), which would be O(n^2).
+ EXPECT_EQ(1u, all_policy_origins.count(MakeOrigin(i)));
+ }
+ EXPECT_TRUE(HasPolicyForOrigin(kOrigin_));
+
+ // Now use the policies in reverse order starting with kOrigin_, then add
+ // another policy to trigger eviction, to check that the stalest policy is
+ // identified correctly.
+ service()->OnRequest(
+ MakeRequestDetails(kOrigin_.GetURL(), ERR_CONNECTION_REFUSED));
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ for (size_t i = NetworkErrorLoggingService::kMaxPolicies - 1; i >= 1; --i) {
+ service()->OnRequest(
+ MakeRequestDetails(MakeOrigin(i).GetURL(), ERR_CONNECTION_REFUSED));
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ }
+ service()->OnHeader(kOriginSubdomain_, kServerIP_, kHeader_);
+ EXPECT_EQ(PolicyCount(), NetworkErrorLoggingService::kMaxPolicies);
+
+ EXPECT_FALSE(HasPolicyForOrigin(kOrigin_)); // evicted
+ all_policy_origins = service()->GetPolicyOriginsForTesting();
+ for (size_t i = NetworkErrorLoggingService::kMaxPolicies - 1; i >= 1; --i) {
+ // Avoid n calls to HasPolicyForOrigin(), which would be O(n^2).
+ EXPECT_EQ(1u, all_policy_origins.count(MakeOrigin(i)));
+ }
+ EXPECT_TRUE(HasPolicyForOrigin(kOriginSubdomain_)); // most recently added
+
+ // Note: This test advances the clock by ~2000 seconds, which is below the
+ // specified max_age of 86400 seconds, so none of the policies expire during
+ // this test.
+}
+
} // namespace
} // namespace net
diff --git a/chromium/net/network_error_logging/network_error_logging_test_util.cc b/chromium/net/network_error_logging/network_error_logging_test_util.cc
index d4d2a526419..54d637613d9 100644
--- a/chromium/net/network_error_logging/network_error_logging_test_util.cc
+++ b/chromium/net/network_error_logging/network_error_logging_test_util.cc
@@ -6,6 +6,8 @@
#include <algorithm>
+#include "net/base/ip_address.h"
+
namespace net {
TestNetworkErrorLoggingService::TestNetworkErrorLoggingService() = default;
@@ -30,6 +32,9 @@ void TestNetworkErrorLoggingService::OnRequest(RequestDetails details) {
errors_.push_back(std::move(details));
}
+void TestNetworkErrorLoggingService::QueueSignedExchangeReport(
+ const SignedExchangeReportDetails& details) {}
+
void TestNetworkErrorLoggingService::RemoveBrowsingData(
const base::RepeatingCallback<bool(const GURL&)>& origin_filter) {}
diff --git a/chromium/net/network_error_logging/network_error_logging_test_util.h b/chromium/net/network_error_logging/network_error_logging_test_util.h
index 9f46d13b5b0..3eb29c3c4e8 100644
--- a/chromium/net/network_error_logging/network_error_logging_test_util.h
+++ b/chromium/net/network_error_logging/network_error_logging_test_util.h
@@ -18,6 +18,8 @@
namespace net {
+class IPAddress;
+
// A NetworkErrorLoggingService implementation that stashes all NEL headers and
// reports so that they can be easily verified in unit tests.
class TestNetworkErrorLoggingService : public NetworkErrorLoggingService {
@@ -46,6 +48,8 @@ class TestNetworkErrorLoggingService : public NetworkErrorLoggingService {
const IPAddress& received_ip_address,
const std::string& value) override;
void OnRequest(RequestDetails details) override;
+ void QueueSignedExchangeReport(
+ const SignedExchangeReportDetails& details) override;
void RemoveBrowsingData(
const base::RepeatingCallback<bool(const GURL&)>& origin_filter) override;
void RemoveAllBrowsingData() override;