1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/safe_browsing/download_feedback_service.h"
6
7#include "base/bind.h"
8#include "base/files/file_path.h"
9#include "base/files/file_util_proxy.h"
10#include "base/metrics/histogram.h"
11#include "base/supports_user_data.h"
12#include "base/task_runner.h"
13#include "chrome/browser/safe_browsing/download_feedback.h"
14#include "content/public/browser/download_danger_type.h"
15#include "content/public/browser/download_item.h"
16
17namespace safe_browsing {
18
19namespace {
20
21const void* kPingKey = &kPingKey;
22
23class DownloadFeedbackPings : public base::SupportsUserData::Data {
24 public:
25  DownloadFeedbackPings(const std::string& ping_request,
26                        const std::string& ping_response);
27
28  // Stores the ping data in the given |download|.
29  static void CreateForDownload(content::DownloadItem* download,
30                                const std::string& ping_request,
31                                const std::string& ping_response);
32
33  // Returns the DownloadFeedbackPings object associated with |download|.  May
34  // return NULL.
35  static DownloadFeedbackPings* FromDownload(
36      const content::DownloadItem& download);
37
38
39  const std::string& ping_request() const {
40    return ping_request_;
41  }
42
43  const std::string& ping_response() const {
44    return ping_response_;
45  }
46
47 private:
48  std::string ping_request_;
49  std::string ping_response_;
50};
51
52DownloadFeedbackPings::DownloadFeedbackPings(const std::string& ping_request,
53                                             const std::string& ping_response)
54    : ping_request_(ping_request),
55      ping_response_(ping_response) {
56}
57
58// static
59void DownloadFeedbackPings::CreateForDownload(
60    content::DownloadItem* download,
61    const std::string& ping_request,
62    const std::string& ping_response) {
63  DownloadFeedbackPings* pings = new DownloadFeedbackPings(ping_request,
64                                                           ping_response);
65  download->SetUserData(kPingKey, pings);
66}
67
68// static
69DownloadFeedbackPings* DownloadFeedbackPings::FromDownload(
70    const content::DownloadItem& download) {
71  return static_cast<DownloadFeedbackPings*>(download.GetUserData(kPingKey));
72}
73
74}  // namespace
75
76DownloadFeedbackService::DownloadFeedbackService(
77    net::URLRequestContextGetter* request_context_getter,
78    base::TaskRunner* file_task_runner)
79    : request_context_getter_(request_context_getter),
80      file_task_runner_(file_task_runner),
81      weak_ptr_factory_(this) {
82}
83
84DownloadFeedbackService::~DownloadFeedbackService() {
85  DCHECK(CalledOnValidThread());
86}
87
88// static
89void DownloadFeedbackService::MaybeStorePingsForDownload(
90    DownloadProtectionService::DownloadCheckResult result,
91    content::DownloadItem* download,
92    const std::string& ping,
93    const std::string& response) {
94  if (result != DownloadProtectionService::UNCOMMON &&
95      result != DownloadProtectionService::DANGEROUS_HOST)
96    return;
97  UMA_HISTOGRAM_COUNTS("SBDownloadFeedback.SizeEligibleKB",
98                       download->GetReceivedBytes() / 1024);
99  if (download->GetReceivedBytes() > DownloadFeedback::kMaxUploadSize)
100    return;
101
102  DownloadFeedbackPings::CreateForDownload(download, ping, response);
103}
104
105// static
106bool DownloadFeedbackService::IsEnabledForDownload(
107    const content::DownloadItem& download) {
108  return !!DownloadFeedbackPings::FromDownload(download);
109}
110
111// static
112bool DownloadFeedbackService::GetPingsForDownloadForTesting(
113    const content::DownloadItem& download,
114    std::string* ping,
115    std::string* response) {
116  DownloadFeedbackPings* pings = DownloadFeedbackPings::FromDownload(download);
117  if (!pings)
118    return false;
119
120  *ping = pings->ping_request();
121  *response = pings->ping_response();
122  return true;
123}
124
125// static
126void DownloadFeedbackService::RecordEligibleDownloadShown(
127    content::DownloadDangerType danger_type) {
128  UMA_HISTOGRAM_ENUMERATION("SBDownloadFeedback.Eligible",
129                            danger_type,
130                            content::DOWNLOAD_DANGER_TYPE_MAX);
131}
132
133
134void DownloadFeedbackService::BeginFeedbackForDownload(
135    content::DownloadItem* download) {
136  DCHECK(CalledOnValidThread());
137
138  UMA_HISTOGRAM_ENUMERATION("SBDownloadFeedback.Activations",
139                            download->GetDangerType(),
140                            content::DOWNLOAD_DANGER_TYPE_MAX);
141
142  DownloadFeedbackPings* pings = DownloadFeedbackPings::FromDownload(*download);
143  DCHECK(pings);
144
145  download->StealDangerousDownload(
146      base::Bind(&DownloadFeedbackService::BeginFeedbackOrDeleteFile,
147                 file_task_runner_,
148                 weak_ptr_factory_.GetWeakPtr(),
149                 pings->ping_request(),
150                 pings->ping_response()));
151}
152
153// static
154void DownloadFeedbackService::BeginFeedbackOrDeleteFile(
155    const scoped_refptr<base::TaskRunner>& file_task_runner,
156    const base::WeakPtr<DownloadFeedbackService>& service,
157    const std::string& ping_request,
158    const std::string& ping_response,
159    const base::FilePath& path) {
160  if (service) {
161    service->BeginFeedback(ping_request, ping_response, path);
162  } else {
163    base::FileUtilProxy::DeleteFile(file_task_runner.get(),
164                                    path,
165                                    false,
166                                    base::FileUtilProxy::StatusCallback());
167  }
168}
169
170void DownloadFeedbackService::StartPendingFeedback() {
171  DCHECK(!active_feedback_.empty());
172  active_feedback_.front()->Start(base::Bind(
173      &DownloadFeedbackService::FeedbackComplete, base::Unretained(this)));
174}
175
176void DownloadFeedbackService::BeginFeedback(
177    const std::string& ping_request,
178    const std::string& ping_response,
179    const base::FilePath& path) {
180  DCHECK(CalledOnValidThread());
181  DownloadFeedback* feedback =
182      DownloadFeedback::Create(request_context_getter_.get(),
183                               file_task_runner_.get(),
184                               path,
185                               ping_request,
186                               ping_response);
187  active_feedback_.push_back(feedback);
188  UMA_HISTOGRAM_COUNTS_100("SBDownloadFeedback.ActiveFeedbacks",
189                           active_feedback_.size());
190
191  if (active_feedback_.size() == 1)
192    StartPendingFeedback();
193}
194
195void DownloadFeedbackService::FeedbackComplete() {
196  DVLOG(1) << __FUNCTION__;
197  DCHECK(CalledOnValidThread());
198  DCHECK(!active_feedback_.empty());
199  active_feedback_.erase(active_feedback_.begin());
200  if (!active_feedback_.empty())
201    StartPendingFeedback();
202}
203
204}  // namespace safe_browsing
205