1// Copyright (c) 2012 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/service/cloud_print/cloud_print_url_fetcher.h"
6
7#include "base/metrics/histogram.h"
8#include "base/strings/stringprintf.h"
9#include "base/values.h"
10#include "chrome/common/cloud_print/cloud_print_constants.h"
11#include "chrome/common/cloud_print/cloud_print_helpers.h"
12#include "chrome/service/cloud_print/cloud_print_service_helpers.h"
13#include "chrome/service/cloud_print/cloud_print_token_store.h"
14#include "chrome/service/net/service_url_request_context_getter.h"
15#include "chrome/service/service_process.h"
16#include "net/base/load_flags.h"
17#include "net/http/http_status_code.h"
18#include "net/url_request/url_fetcher.h"
19#include "net/url_request/url_request_status.h"
20#include "url/gurl.h"
21
22namespace cloud_print {
23
24namespace {
25
26void ReportRequestTime(CloudPrintURLFetcher::RequestType type,
27                       base::TimeDelta time) {
28  if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
29    UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Register", time);
30  } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
31    UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.UpdatePrinter", time);
32  } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
33    UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.DownloadData", time);
34  } else {
35    UMA_HISTOGRAM_TIMES("CloudPrint.UrlFetcherRequestTime.Other", time);
36  }
37}
38
39void ReportRetriesCount(CloudPrintURLFetcher::RequestType type,
40                        int retries) {
41  if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
42    UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Register", retries);
43  } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
44    UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.UpdatePrinter",
45                             retries);
46  } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
47    UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.DownloadData",
48                             retries);
49  } else {
50    UMA_HISTOGRAM_COUNTS_100("CloudPrint.UrlFetcherRetries.Other", retries);
51  }
52}
53
54void ReportDownloadSize(CloudPrintURLFetcher::RequestType type, size_t size) {
55  if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
56    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Register", size);
57  } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
58    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.UpdatePrinter",
59                            size);
60  } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
61    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.DownloadData",
62                            size);
63  } else {
64    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherDownloadSize.Other", size);
65  }
66}
67
68void ReportUploadSize(CloudPrintURLFetcher::RequestType type, size_t size) {
69  if (type == CloudPrintURLFetcher::REQUEST_REGISTER) {
70    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Register", size);
71  } else if (type == CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER) {
72    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.UpdatePrinter",
73                            size);
74  } else if (type == CloudPrintURLFetcher::REQUEST_DATA) {
75    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.DownloadData",
76                            size);
77  } else {
78    UMA_HISTOGRAM_MEMORY_KB("CloudPrint.UrlFetcherUploadSize.Other", size);
79  }
80}
81
82CloudPrintURLFetcherFactory* g_factory = NULL;
83
84}  // namespace
85
86// virtual
87CloudPrintURLFetcherFactory::~CloudPrintURLFetcherFactory() {}
88
89// static
90CloudPrintURLFetcher* CloudPrintURLFetcher::Create() {
91  CloudPrintURLFetcherFactory* factory = CloudPrintURLFetcher::factory();
92  return factory ? factory->CreateCloudPrintURLFetcher() :
93      new CloudPrintURLFetcher;
94}
95
96// static
97CloudPrintURLFetcherFactory* CloudPrintURLFetcher::factory() {
98  return g_factory;
99}
100
101// static
102void CloudPrintURLFetcher::set_factory(CloudPrintURLFetcherFactory* factory) {
103  g_factory = factory;
104}
105
106CloudPrintURLFetcher::ResponseAction
107CloudPrintURLFetcher::Delegate::HandleRawResponse(
108    const net::URLFetcher* source,
109    const GURL& url,
110    const net::URLRequestStatus& status,
111    int response_code,
112    const net::ResponseCookies& cookies,
113    const std::string& data) {
114  return CONTINUE_PROCESSING;
115}
116
117CloudPrintURLFetcher::ResponseAction
118CloudPrintURLFetcher::Delegate::HandleRawData(
119    const net::URLFetcher* source,
120    const GURL& url,
121    const std::string& data) {
122  return CONTINUE_PROCESSING;
123}
124
125CloudPrintURLFetcher::ResponseAction
126CloudPrintURLFetcher::Delegate::HandleJSONData(
127    const net::URLFetcher* source,
128    const GURL& url,
129    base::DictionaryValue* json_data,
130    bool succeeded) {
131  return CONTINUE_PROCESSING;
132}
133
134CloudPrintURLFetcher::CloudPrintURLFetcher()
135    : delegate_(NULL),
136      num_retries_(0),
137      type_(REQUEST_MAX) {
138}
139
140bool CloudPrintURLFetcher::IsSameRequest(const net::URLFetcher* source) {
141  return (request_.get() == source);
142}
143
144void CloudPrintURLFetcher::StartGetRequest(
145    RequestType type,
146    const GURL& url,
147    Delegate* delegate,
148    int max_retries,
149    const std::string& additional_headers) {
150  StartRequestHelper(type, url, net::URLFetcher::GET, delegate, max_retries,
151                     std::string(), std::string(), additional_headers);
152}
153
154void CloudPrintURLFetcher::StartPostRequest(
155    RequestType type,
156    const GURL& url,
157    Delegate* delegate,
158    int max_retries,
159    const std::string& post_data_mime_type,
160    const std::string& post_data,
161    const std::string& additional_headers) {
162  StartRequestHelper(type, url, net::URLFetcher::POST, delegate, max_retries,
163                     post_data_mime_type, post_data, additional_headers);
164}
165
166void CloudPrintURLFetcher::OnURLFetchComplete(
167    const net::URLFetcher* source) {
168  VLOG(1) << "CP_PROXY: OnURLFetchComplete, url: " << source->GetURL()
169          << ", response code: " << source->GetResponseCode();
170  // Make sure we stay alive through the body of this function.
171  scoped_refptr<CloudPrintURLFetcher> keep_alive(this);
172  std::string data;
173  source->GetResponseAsString(&data);
174  ReportRequestTime(type_, base::Time::Now() - start_time_);
175  ReportDownloadSize(type_, data.size());
176  ResponseAction action = delegate_->HandleRawResponse(
177      source,
178      source->GetURL(),
179      source->GetStatus(),
180      source->GetResponseCode(),
181      source->GetCookies(),
182      data);
183
184  // If we get auth error, notify delegate and check if it wants to proceed.
185  if (action == CONTINUE_PROCESSING &&
186      source->GetResponseCode() == net::HTTP_FORBIDDEN) {
187    action = delegate_->OnRequestAuthError();
188  }
189
190  if (action == CONTINUE_PROCESSING) {
191    // We need to retry on all network errors.
192    if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200))
193      action = RETRY_REQUEST;
194    else
195      action = delegate_->HandleRawData(source, source->GetURL(), data);
196
197    if (action == CONTINUE_PROCESSING) {
198      // If the delegate is not interested in handling the raw response data,
199      // we assume that a JSON response is expected. If we do not get a JSON
200      // response, we will retry (to handle the case where we got redirected
201      // to a non-cloudprint-server URL eg. for authentication).
202      bool succeeded = false;
203      scoped_ptr<base::DictionaryValue> response_dict =
204          ParseResponseJSON(data, &succeeded);
205
206      if (response_dict) {
207        action = delegate_->HandleJSONData(source,
208                                           source->GetURL(),
209                                           response_dict.get(),
210                                           succeeded);
211      } else {
212        action = RETRY_REQUEST;
213      }
214    }
215  }
216  // Retry the request if needed.
217  if (action == RETRY_REQUEST) {
218    // Explicitly call ReceivedContentWasMalformed() to ensure the current
219    // request gets counted as a failure for calculation of the back-off
220    // period.  If it was already a failure by status code, this call will
221    // be ignored.
222    request_->ReceivedContentWasMalformed();
223
224    // If we receive error code from the server "Media Type Not Supported",
225    // there is no reason to retry, request will never succeed.
226    // In that case we should call OnRequestGiveUp() right away.
227    if (source->GetResponseCode() == net::HTTP_UNSUPPORTED_MEDIA_TYPE)
228      num_retries_ = source->GetMaxRetriesOn5xx();
229
230    ++num_retries_;
231    if ((-1 != source->GetMaxRetriesOn5xx()) &&
232        (num_retries_ > source->GetMaxRetriesOn5xx())) {
233      // Retry limit reached. Give up.
234      delegate_->OnRequestGiveUp();
235      action = STOP_PROCESSING;
236    } else {
237      // Either no retry limit specified or retry limit has not yet been
238      // reached. Try again. Set up the request headers again because the token
239      // may have changed.
240      SetupRequestHeaders();
241      request_->SetRequestContext(GetRequestContextGetter());
242      start_time_ = base::Time::Now();
243      request_->Start();
244    }
245  }
246  if (action != RETRY_REQUEST) {
247    ReportRetriesCount(type_, num_retries_);
248  }
249}
250
251void CloudPrintURLFetcher::StartRequestHelper(
252    RequestType type,
253    const GURL& url,
254    net::URLFetcher::RequestType request_type,
255    Delegate* delegate,
256    int max_retries,
257    const std::string& post_data_mime_type,
258    const std::string& post_data,
259    const std::string& additional_headers) {
260  DCHECK(delegate);
261  type_ = type;
262  UMA_HISTOGRAM_ENUMERATION("CloudPrint.UrlFetcherRequestType", type,
263                            REQUEST_MAX);
264  // Persist the additional headers in case we need to retry the request.
265  additional_headers_ = additional_headers;
266  request_.reset(net::URLFetcher::Create(0, url, request_type, this));
267  request_->SetRequestContext(GetRequestContextGetter());
268  // Since we implement our own retry logic, disable the retry in URLFetcher.
269  request_->SetAutomaticallyRetryOn5xx(false);
270  request_->SetMaxRetriesOn5xx(max_retries);
271  delegate_ = delegate;
272  SetupRequestHeaders();
273  request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
274                         net::LOAD_DO_NOT_SAVE_COOKIES);
275  if (request_type == net::URLFetcher::POST) {
276    request_->SetUploadData(post_data_mime_type, post_data);
277    ReportUploadSize(type_, post_data.size());
278  }
279  start_time_ = base::Time::Now();
280  request_->Start();
281}
282
283void CloudPrintURLFetcher::SetupRequestHeaders() {
284  std::string headers = delegate_->GetAuthHeader();
285  if (!headers.empty())
286    headers += "\r\n";
287  headers += kChromeCloudPrintProxyHeader;
288  if (!additional_headers_.empty()) {
289    headers += "\r\n";
290    headers += additional_headers_;
291  }
292  request_->SetExtraRequestHeaders(headers);
293}
294
295CloudPrintURLFetcher::~CloudPrintURLFetcher() {}
296
297net::URLRequestContextGetter* CloudPrintURLFetcher::GetRequestContextGetter() {
298  ServiceURLRequestContextGetter* getter =
299      g_service_process->GetServiceURLRequestContextGetter();
300  // Now set up the user agent for cloudprint.
301  std::string user_agent = getter->user_agent();
302  base::StringAppendF(&user_agent, " %s", kCloudPrintUserAgent);
303  getter->set_user_agent(user_agent);
304  return getter;
305}
306
307}  // namespace cloud_print
308