| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/reporting/reporting_delivery_agent.h" |
| |
| #include <map> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/time/tick_clock.h" |
| #include "base/timer/timer.h" |
| #include "base/values.h" |
| #include "net/reporting/reporting_cache.h" |
| #include "net/reporting/reporting_cache_observer.h" |
| #include "net/reporting/reporting_delegate.h" |
| #include "net/reporting/reporting_endpoint_manager.h" |
| #include "net/reporting/reporting_report.h" |
| #include "net/reporting/reporting_uploader.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| void SerializeReports(const std::vector<const ReportingReport*>& reports, |
| base::TimeTicks now, |
| std::string* json_out) { |
| base::ListValue reports_value; |
| |
| for (const ReportingReport* report : reports) { |
| std::unique_ptr<base::DictionaryValue> report_value = |
| std::make_unique<base::DictionaryValue>(); |
| |
| report_value->SetInteger("age", (now - report->queued).InMilliseconds()); |
| report_value->SetString("type", report->type); |
| report_value->SetString("url", report->url.spec()); |
| report_value->SetString("user_agent", report->user_agent); |
| report_value->SetKey("body", report->body->Clone()); |
| |
| reports_value.Append(std::move(report_value)); |
| } |
| |
| bool json_written = base::JSONWriter::Write(reports_value, json_out); |
| DCHECK(json_written); |
| } |
| |
| class ReportingDeliveryAgentImpl : public ReportingDeliveryAgent, |
| public ReportingCacheObserver { |
| public: |
| ReportingDeliveryAgentImpl(ReportingContext* context) |
| : context_(context), |
| timer_(std::make_unique<base::OneShotTimer>()), |
| weak_factory_(this) { |
| context_->AddCacheObserver(this); |
| } |
| |
| // ReportingDeliveryAgent implementation: |
| |
| ~ReportingDeliveryAgentImpl() override { |
| context_->RemoveCacheObserver(this); |
| } |
| |
| void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer) override { |
| DCHECK(!timer_->IsRunning()); |
| timer_ = std::move(timer); |
| } |
| |
| // ReportingCacheObserver implementation: |
| void OnReportsUpdated() override { |
| if (CacheHasReports() && !timer_->IsRunning()) { |
| SendReports(); |
| StartTimer(); |
| } |
| } |
| |
| private: |
| using OriginGroup = std::pair<url::Origin, std::string>; |
| using OriginEndpoint = std::pair<url::Origin, GURL>; |
| |
| class Delivery { |
| public: |
| Delivery(const OriginEndpoint& report_origin_endpoint) |
| : report_origin(report_origin_endpoint.first), |
| endpoint(report_origin_endpoint.second) {} |
| |
| ~Delivery() = default; |
| |
| void AddReports(const ReportingClient* client, |
| const std::vector<const ReportingReport*>& to_add) { |
| reports_per_client[client->origin][client->endpoint] += to_add.size(); |
| reports.insert(reports.end(), to_add.begin(), to_add.end()); |
| } |
| |
| const url::Origin report_origin; |
| const GURL endpoint; |
| std::vector<const ReportingReport*> reports; |
| std::map<url::Origin, std::map<GURL, int>> reports_per_client; |
| }; |
| |
| bool CacheHasReports() { |
| std::vector<const ReportingReport*> reports; |
| context_->cache()->GetReports(&reports); |
| return !reports.empty(); |
| } |
| |
| void StartTimer() { |
| timer_->Start(FROM_HERE, policy().delivery_interval, |
| base::BindRepeating(&ReportingDeliveryAgentImpl::OnTimerFired, |
| base::Unretained(this))); |
| } |
| |
| void OnTimerFired() { |
| if (CacheHasReports()) { |
| SendReports(); |
| StartTimer(); |
| } |
| } |
| |
| void SendReports() { |
| std::vector<const ReportingReport*> reports; |
| cache()->GetNonpendingReports(&reports); |
| |
| // Mark all of these reports as pending, so that they're not deleted out |
| // from under us while we're checking permissions (possibly on another |
| // thread). |
| cache()->SetReportsPending(reports); |
| |
| // First determine which origins we're allowed to upload reports about. |
| std::set<url::Origin> report_origins; |
| for (const ReportingReport* report : reports) { |
| report_origins.insert(url::Origin::Create(report->url)); |
| } |
| delegate()->CanSendReports( |
| std::move(report_origins), |
| base::BindOnce(&ReportingDeliveryAgentImpl::OnSendPermissionsChecked, |
| weak_factory_.GetWeakPtr(), std::move(reports))); |
| } |
| |
| void OnSendPermissionsChecked(std::vector<const ReportingReport*> reports, |
| std::set<url::Origin> allowed_report_origins) { |
| // Sort reports into (origin, group) buckets. |
| std::map<OriginGroup, std::vector<const ReportingReport*>> |
| origin_group_reports; |
| for (const ReportingReport* report : reports) { |
| url::Origin report_origin = url::Origin::Create(report->url); |
| if (allowed_report_origins.find(report_origin) == |
| allowed_report_origins.end()) |
| continue; |
| OriginGroup origin_group(report_origin, report->group); |
| origin_group_reports[origin_group].push_back(report); |
| } |
| |
| // Find an endpoint for each (origin, group) bucket and sort reports into |
| // endpoint buckets. Don't allow concurrent deliveries to the same (origin, |
| // group) bucket. |
| std::map<OriginEndpoint, std::unique_ptr<Delivery>> deliveries; |
| for (auto& it : origin_group_reports) { |
| const OriginGroup& origin_group = it.first; |
| const url::Origin& report_origin = origin_group.first; |
| const std::string& group = origin_group.second; |
| |
| if (base::ContainsKey(pending_origin_groups_, origin_group)) |
| continue; |
| |
| const ReportingClient* client = |
| endpoint_manager()->FindClientForOriginAndGroup(report_origin, group); |
| if (client == nullptr) { |
| continue; |
| } |
| cache()->MarkClientUsed(client); |
| OriginEndpoint report_origin_endpoint(report_origin, client->endpoint); |
| |
| Delivery* delivery; |
| auto delivery_it = deliveries.find(report_origin_endpoint); |
| if (delivery_it == deliveries.end()) { |
| auto new_delivery = std::make_unique<Delivery>(report_origin_endpoint); |
| delivery = new_delivery.get(); |
| deliveries[report_origin_endpoint] = std::move(new_delivery); |
| } else { |
| delivery = delivery_it->second.get(); |
| } |
| |
| delivery->AddReports(client, it.second); |
| pending_origin_groups_.insert(origin_group); |
| } |
| |
| // Keep track of which of these reports we don't queue for delivery; we'll |
| // need to mark them as not-pending. |
| std::unordered_set<const ReportingReport*> undelivered_reports( |
| reports.begin(), reports.end()); |
| |
| // Start an upload for each delivery. |
| for (auto& it : deliveries) { |
| const OriginEndpoint& report_origin_endpoint = it.first; |
| const url::Origin& report_origin = report_origin_endpoint.first; |
| const GURL& endpoint = report_origin_endpoint.second; |
| std::unique_ptr<Delivery>& delivery = it.second; |
| |
| std::string json; |
| SerializeReports(delivery->reports, tick_clock()->NowTicks(), &json); |
| |
| int max_depth = 0; |
| for (const ReportingReport* report : delivery->reports) { |
| undelivered_reports.erase(report); |
| if (report->depth > max_depth) |
| max_depth = report->depth; |
| } |
| |
| // TODO: Calculate actual max depth. |
| uploader()->StartUpload( |
| report_origin, endpoint, json, max_depth, |
| base::BindOnce(&ReportingDeliveryAgentImpl::OnUploadComplete, |
| weak_factory_.GetWeakPtr(), std::move(delivery))); |
| } |
| |
| cache()->ClearReportsPending( |
| {undelivered_reports.begin(), undelivered_reports.end()}); |
| } |
| |
| void OnUploadComplete(std::unique_ptr<Delivery> delivery, |
| ReportingUploader::Outcome outcome) { |
| for (const auto& origin_and_pair : delivery->reports_per_client) { |
| const url::Origin& client_origin = origin_and_pair.first; |
| for (const auto& endpoint_and_count : origin_and_pair.second) { |
| const GURL& endpoint = endpoint_and_count.first; |
| int report_count = endpoint_and_count.second; |
| cache()->IncrementEndpointDeliveries( |
| client_origin, endpoint, report_count, |
| outcome == ReportingUploader::Outcome::SUCCESS); |
| } |
| } |
| |
| if (outcome == ReportingUploader::Outcome::SUCCESS) { |
| cache()->RemoveReports(delivery->reports, |
| ReportingReport::Outcome::DELIVERED); |
| endpoint_manager()->InformOfEndpointRequest(delivery->endpoint, true); |
| } else { |
| cache()->IncrementReportsAttempts(delivery->reports); |
| endpoint_manager()->InformOfEndpointRequest(delivery->endpoint, false); |
| } |
| |
| if (outcome == ReportingUploader::Outcome::REMOVE_ENDPOINT) |
| cache()->RemoveClientsForEndpoint(delivery->endpoint); |
| |
| for (const ReportingReport* report : delivery->reports) { |
| pending_origin_groups_.erase( |
| OriginGroup(delivery->report_origin, report->group)); |
| } |
| |
| cache()->ClearReportsPending(delivery->reports); |
| } |
| |
| const ReportingPolicy& policy() { return context_->policy(); } |
| const base::TickClock* tick_clock() { return context_->tick_clock(); } |
| ReportingDelegate* delegate() { return context_->delegate(); } |
| ReportingCache* cache() { return context_->cache(); } |
| ReportingUploader* uploader() { return context_->uploader(); } |
| ReportingEndpointManager* endpoint_manager() { |
| return context_->endpoint_manager(); |
| } |
| |
| ReportingContext* context_; |
| |
| std::unique_ptr<base::OneShotTimer> timer_; |
| |
| // Tracks OriginGroup tuples for which there is a pending delivery running. |
| // (Would be an unordered_set, but there's no hash on pair.) |
| std::set<OriginGroup> pending_origin_groups_; |
| |
| base::WeakPtrFactory<ReportingDeliveryAgentImpl> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ReportingDeliveryAgentImpl); |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<ReportingDeliveryAgent> ReportingDeliveryAgent::Create( |
| ReportingContext* context) { |
| return std::make_unique<ReportingDeliveryAgentImpl>(context); |
| } |
| |
| ReportingDeliveryAgent::~ReportingDeliveryAgent() = default; |
| |
| } // namespace net |