diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-16 09:59:13 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-20 10:28:53 +0000 |
commit | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (patch) | |
tree | c8315530db18a8ee566521c39ab8a6af4f72bc03 /chromium/net/network_error_logging | |
parent | 3ffaed019d0772e59d6cdb2d0d32fe4834c31f72 (diff) | |
download | qtwebengine-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')
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; |