1// Copyright 2014 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 "components/component_updater/component_updater_ping_manager.h"
6
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/compiler_specific.h"
13#include "base/location.h"
14#include "base/logging.h"
15#include "base/macros.h"
16#include "base/memory/scoped_ptr.h"
17#include "base/sequenced_task_runner.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "base/strings/stringprintf.h"
21#include "base/threading/thread_checker.h"
22#include "components/component_updater/component_updater_configurator.h"
23#include "components/component_updater/component_updater_utils.h"
24#include "components/component_updater/crx_update_item.h"
25#include "components/component_updater/request_sender.h"
26#include "url/gurl.h"
27
28namespace net {
29class URLFetcher;
30}  // namespace net
31
32namespace component_updater {
33
34namespace {
35
36// Returns a string literal corresponding to the value of the downloader |d|.
37const char* DownloaderToString(CrxDownloader::DownloadMetrics::Downloader d) {
38  switch (d) {
39    case CrxDownloader::DownloadMetrics::kUrlFetcher:
40      return "direct";
41    case CrxDownloader::DownloadMetrics::kBits:
42      return "bits";
43    default:
44      return "unknown";
45  }
46}
47
48// Returns a string representing a sequence of download complete events
49// corresponding to each download metrics in |item|.
50std::string BuildDownloadCompleteEventElements(const CrxUpdateItem* item) {
51  using base::StringAppendF;
52  std::string download_events;
53  for (size_t i = 0; i != item->download_metrics.size(); ++i) {
54    const CrxDownloader::DownloadMetrics& metrics = item->download_metrics[i];
55    std::string event("<event eventtype=\"14\"");
56    StringAppendF(&event, " eventresult=\"%d\"", metrics.error == 0);
57    StringAppendF(&event,
58                  " downloader=\"%s\"",
59                  DownloaderToString(metrics.downloader));
60    if (metrics.error) {
61      StringAppendF(&event, " errorcode=\"%d\"", metrics.error);
62    }
63    StringAppendF(&event, " url=\"%s\"", metrics.url.spec().c_str());
64
65    // -1 means that the  byte counts are not known.
66    if (metrics.downloaded_bytes != -1) {
67      StringAppendF(&event,
68                    " downloaded=\"%s\"",
69                    base::Int64ToString(metrics.downloaded_bytes).c_str());
70    }
71    if (metrics.total_bytes != -1) {
72      StringAppendF(&event,
73                    " total=\"%s\"",
74                    base::Int64ToString(metrics.total_bytes).c_str());
75    }
76
77    if (metrics.download_time_ms) {
78      StringAppendF(&event,
79                    " download_time_ms=\"%s\"",
80                    base::Uint64ToString(metrics.download_time_ms).c_str());
81    }
82    StringAppendF(&event, "/>");
83
84    download_events += event;
85  }
86  return download_events;
87}
88
89// Returns a string representing one ping event xml element for an update item.
90std::string BuildUpdateCompleteEventElement(const CrxUpdateItem* item) {
91  DCHECK(item->status == CrxUpdateItem::kNoUpdate ||
92         item->status == CrxUpdateItem::kUpdated);
93
94  using base::StringAppendF;
95
96  std::string ping_event("<event eventtype=\"3\"");
97  const int event_result = item->status == CrxUpdateItem::kUpdated;
98  StringAppendF(&ping_event, " eventresult=\"%d\"", event_result);
99  if (item->error_category)
100    StringAppendF(&ping_event, " errorcat=\"%d\"", item->error_category);
101  if (item->error_code)
102    StringAppendF(&ping_event, " errorcode=\"%d\"", item->error_code);
103  if (item->extra_code1)
104    StringAppendF(&ping_event, " extracode1=\"%d\"", item->extra_code1);
105  if (HasDiffUpdate(item))
106    StringAppendF(&ping_event, " diffresult=\"%d\"", !item->diff_update_failed);
107  if (item->diff_error_category) {
108    StringAppendF(
109        &ping_event, " differrorcat=\"%d\"", item->diff_error_category);
110  }
111  if (item->diff_error_code)
112    StringAppendF(&ping_event, " differrorcode=\"%d\"", item->diff_error_code);
113  if (item->diff_extra_code1) {
114    StringAppendF(
115        &ping_event, " diffextracode1=\"%d\"", item->diff_extra_code1);
116  }
117  if (!item->previous_fp.empty())
118    StringAppendF(&ping_event, " previousfp=\"%s\"", item->previous_fp.c_str());
119  if (!item->next_fp.empty())
120    StringAppendF(&ping_event, " nextfp=\"%s\"", item->next_fp.c_str());
121  StringAppendF(&ping_event, "/>");
122  return ping_event;
123}
124
125// Builds a ping message for the specified update item.
126std::string BuildPing(const Configurator& config, const CrxUpdateItem* item) {
127  const char app_element_format[] =
128      "<app appid=\"%s\" version=\"%s\" nextversion=\"%s\">"
129      "%s"
130      "%s"
131      "</app>";
132  const std::string app_element(base::StringPrintf(
133      app_element_format,
134      item->id.c_str(),                                    // "appid"
135      item->previous_version.GetString().c_str(),          // "version"
136      item->next_version.GetString().c_str(),              // "nextversion"
137      BuildUpdateCompleteEventElement(item).c_str(),       // update event
138      BuildDownloadCompleteEventElements(item).c_str()));  // download events
139
140  return BuildProtocolRequest(config.GetBrowserVersion().GetString(),
141                              config.GetChannel(),
142                              config.GetLang(),
143                              config.GetOSLongName(),
144                              app_element,
145                              "");
146}
147
148// Sends a fire and forget ping. The instances of this class have no
149// ownership and they self-delete upon completion. One instance of this class
150// can send only one ping.
151class PingSender {
152 public:
153  explicit PingSender(const Configurator& config);
154  ~PingSender();
155
156  bool SendPing(const CrxUpdateItem* item);
157
158 private:
159  void OnRequestSenderComplete(const net::URLFetcher* source);
160
161  const Configurator& config_;
162  scoped_ptr<RequestSender> request_sender_;
163  base::ThreadChecker thread_checker_;
164
165  DISALLOW_COPY_AND_ASSIGN(PingSender);
166};
167
168PingSender::PingSender(const Configurator& config) : config_(config) {
169}
170
171PingSender::~PingSender() {
172  DCHECK(thread_checker_.CalledOnValidThread());
173}
174
175void PingSender::OnRequestSenderComplete(const net::URLFetcher* source) {
176  DCHECK(thread_checker_.CalledOnValidThread());
177  delete this;
178}
179
180bool PingSender::SendPing(const CrxUpdateItem* item) {
181  DCHECK(item);
182  DCHECK(thread_checker_.CalledOnValidThread());
183
184  std::vector<GURL> urls(config_.PingUrl());
185
186  if (urls.empty())
187    return false;
188
189  request_sender_.reset(new RequestSender(config_));
190  request_sender_->Send(
191      BuildPing(config_, item),
192      urls,
193      base::Bind(&PingSender::OnRequestSenderComplete, base::Unretained(this)));
194  return true;
195}
196
197}  // namespace
198
199PingManager::PingManager(const Configurator& config) : config_(config) {
200}
201
202PingManager::~PingManager() {
203}
204
205// Sends a fire and forget ping when the updates are complete. The ping
206// sender object self-deletes after sending the ping has completed asynchrously.
207void PingManager::OnUpdateComplete(const CrxUpdateItem* item) {
208  PingSender* ping_sender(new PingSender(config_));
209  if (!ping_sender->SendPing(item))
210    delete ping_sender;
211}
212
213}  // namespace component_updater
214