Index: chrome/browser/safe_browsing/notification_image_reporter.cc |
diff --git a/chrome/browser/safe_browsing/notification_image_reporter.cc b/chrome/browser/safe_browsing/notification_image_reporter.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d06bf14bbea9071ed4623f2fad9e44078681c874 |
--- /dev/null |
+++ b/chrome/browser/safe_browsing/notification_image_reporter.cc |
@@ -0,0 +1,207 @@ |
+// 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 "chrome/browser/safe_browsing/notification_image_reporter.h" |
+ |
+#include <cmath> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/logging.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/memory/ref_counted_memory.h" |
+#include "base/metrics/histogram_macros.h" |
+#include "base/rand_util.h" |
+#include "base/threading/sequenced_worker_pool.h" |
+#include "chrome/browser/browser_process.h" |
+#include "chrome/browser/profiles/profile.h" |
+#include "chrome/browser/safe_browsing/safe_browsing_service.h" |
+#include "chrome/common/safe_browsing/csd.pb.h" |
+#include "components/safe_browsing_db/database_manager.h" |
+#include "components/safe_browsing_db/safe_browsing_prefs.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "net/base/net_errors.h" |
+#include "net/url_request/report_sender.h" |
+#include "skia/ext/image_operations.h" |
+#include "third_party/skia/include/core/SkBitmap.h" |
+#include "ui/gfx/codec/png_codec.h" |
+#include "ui/gfx/geometry/size.h" |
+#include "url/gurl.h" |
+ |
+using content::BrowserThread; |
+ |
+namespace safe_browsing { |
+ |
+namespace { |
+ |
+const size_t kMaxReportsPerDay = 5; |
+ |
+void LogReportResult(const GURL& url, int net_error) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY("SafeBrowsing.NotificationImageReporter.NetError", |
+ net_error); |
+} |
+ |
+} // namespace |
+ |
+const char NotificationImageReporter::kReportingUploadUrl[] = |
+ "https://safebrowsing.googleusercontent.com/safebrowsing/clientreport/" |
+ "chrome-notification-image"; // TODO(johnme): Confirm URL. |
+ |
+NotificationImageReporter::NotificationImageReporter( |
+ net::URLRequestContext* request_context) |
+ : NotificationImageReporter(base::MakeUnique<net::ReportSender>( |
+ request_context, |
+ net::ReportSender::CookiesPreference::DO_NOT_SEND_COOKIES)) {} |
+ |
+NotificationImageReporter::NotificationImageReporter( |
+ std::unique_ptr<net::ReportSender> report_sender) |
+ : report_sender_(std::move(report_sender)), weak_factory_on_io_(this) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+} |
+ |
+NotificationImageReporter::~NotificationImageReporter() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+} |
+ |
+void NotificationImageReporter::ReportNotificationImageOnIO( |
+ Profile* profile, |
+ const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, |
+ const GURL& origin, |
+ const SkBitmap& image) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(profile); |
+ DCHECK_EQ(origin, origin.GetOrigin()); |
+ DCHECK(origin.is_valid()); |
+ |
+ // Skip whitelisted origins to cut down on report volume. |
+ if (!database_manager || database_manager->MatchCsdWhitelistUrl(origin)) { |
+ SkippedReporting(); |
+ return; |
+ } |
+ |
+ // Sample a Finch-controlled fraction only. |
+ double report_chance = GetReportChance(); |
+ if (base::RandDouble() >= report_chance) { |
+ SkippedReporting(); |
+ return; |
+ } |
+ |
+ // Avoid exceeding kMaxReportsPerDay. |
+ base::Time a_day_ago = base::Time::Now() - base::TimeDelta::FromDays(1); |
+ while (!report_times_.empty() && |
+ report_times_.front() < /* older than */ a_day_ago) { |
+ report_times_.pop(); |
+ } |
+ if (report_times_.size() >= kMaxReportsPerDay) { |
+ SkippedReporting(); |
+ return; |
+ } |
+ // n.b. we write to report_times_ here even if we'll later end up skipping |
+ // reporting because GetExtendedReportingLevel was not SBER_LEVEL_SCOUT. That |
+ // saves us two thread hops, with the downside that we may underreport |
+ // notifications on the first day that a user opts in to SBER_LEVEL_SCOUT. |
+ report_times_.push(base::Time::Now()); |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&NotificationImageReporter::ReportNotificationImageOnUI, |
+ weak_factory_on_io_.GetWeakPtr(), profile, origin, image)); |
+} |
+ |
+double NotificationImageReporter::GetReportChance() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ return 0.2; // TODO(johnme): Get this from Finch. |
+} |
+ |
+void NotificationImageReporter::SkippedReporting() {} |
+ |
+// static |
+void NotificationImageReporter::ReportNotificationImageOnUI( |
+ const base::WeakPtr<NotificationImageReporter>& weak_this_on_io, |
+ Profile* profile, |
+ const GURL& origin, |
+ const SkBitmap& image) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ |
+ // Skip reporting unless SBER2 Scout is enabled. |
+ switch (GetExtendedReportingLevel(*profile->GetPrefs())) { |
+ case SBER_LEVEL_OFF: |
+ case SBER_LEVEL_LEGACY: |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&NotificationImageReporter::SkippedReporting, |
+ weak_this_on_io)); |
+ return; |
+ case SBER_LEVEL_SCOUT: |
+ break; |
+ } |
+ |
+ BrowserThread::GetBlockingPool()->PostWorkerTask( |
+ FROM_HERE, |
+ base::Bind( |
+ &NotificationImageReporter::DownscaleNotificationImageOnBlockingPool, |
+ weak_this_on_io, origin, image)); |
+} |
+ |
+// static |
+void NotificationImageReporter::DownscaleNotificationImageOnBlockingPool( |
+ const base::WeakPtr<NotificationImageReporter>& weak_this_on_io, |
+ const GURL& origin, |
+ const SkBitmap& image) { |
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
+ |
+ // Downscale to fit within 512x512. TODO(johnme): Get this from Finch. |
+ const double MAX_SIZE = 512; |
+ double scale = std::min(MAX_SIZE / image.width(), MAX_SIZE / image.height()); |
+ SkBitmap downscaled_image = |
+ scale >= 1.0 ? image // already small enough |
+ : skia::ImageOperations::Resize( |
+ image, skia::ImageOperations::RESIZE_GOOD, |
+ std::lround(scale * image.width()), |
+ std::lround(scale * image.height())); |
+ |
+ // Encode as PNG. |
+ std::vector<unsigned char> png_bytes; |
+ if (!gfx::PNGCodec::EncodeBGRASkBitmap(downscaled_image, false, &png_bytes)) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&NotificationImageReporter::SendReportOnIO, weak_this_on_io, |
+ origin, base::RefCountedBytes::TakeVector(&png_bytes), |
+ gfx::Size(downscaled_image.width(), downscaled_image.height()), |
+ gfx::Size(image.width(), image.height()))); |
+} |
+ |
+void NotificationImageReporter::SendReportOnIO( |
+ const GURL& origin, |
+ scoped_refptr<base::RefCountedMemory> png_data, |
+ const gfx::Size& dimensions, |
+ const gfx::Size& original_dimensions) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ |
+ NotificationImageReportRequest report; |
+ report.set_notification_origin(origin.spec()); |
+ report.mutable_image()->set_png_data(png_data->front(), png_data->size()); |
+ report.mutable_image()->mutable_dimensions()->set_width(dimensions.width()); |
+ report.mutable_image()->mutable_dimensions()->set_height(dimensions.height()); |
+ if (dimensions != original_dimensions) { |
+ report.mutable_image()->mutable_original_dimensions()->set_width( |
+ original_dimensions.width()); |
+ report.mutable_image()->mutable_original_dimensions()->set_height( |
+ original_dimensions.height()); |
+ } |
+ |
+ std::string serialized_report; |
+ report.SerializeToString(&serialized_report); |
+ report_sender_->Send( |
+ GURL(kReportingUploadUrl), "application/octet-stream", serialized_report, |
+ base::Bind(&LogReportResult, GURL(kReportingUploadUrl), net::OK), |
+ base::Bind(&LogReportResult)); |
+ // TODO(johnme): Consider logging bandwidth and/or duration to UMA. |
+} |
+ |
+} // namespace safe_browsing |