[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / local_discovery / privet_url_fetcher.cc
blobcb519553b834610cc2b00f26313925b916038e7d
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.
5 #include "chrome/browser/local_discovery/privet_url_fetcher.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/json/json_reader.h"
11 #include "base/memory/singleton.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/rand_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/local_discovery/privet_constants.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "net/http/http_status_code.h"
19 #include "net/url_request/url_request_status.h"
21 namespace local_discovery {
23 namespace {
25 typedef std::map<std::string, std::string> TokenMap;
27 struct TokenMapHolder {
28 public:
29 static TokenMapHolder* GetInstance() {
30 return Singleton<TokenMapHolder>::get();
33 TokenMap map;
36 const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: ";
37 const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
38 const char kXPrivetEmptyToken[] = "\"\"";
39 const int kPrivetMaxRetries = 20;
40 const int kPrivetTimeoutOnError = 5;
41 const int kHTTPErrorCodeInvalidXPrivetToken = 418;
43 std::string MakeRangeHeader(int start, int end) {
44 DCHECK_GE(start, 0);
45 DCHECK_GT(end, 0);
46 DCHECK_GT(end, start);
47 return base::StringPrintf(kRangeHeaderFormat, start, end);
50 } // namespace
52 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
53 PrivetURLFetcher* fetcher,
54 const TokenCallback& callback) {
55 OnError(fetcher, TOKEN_ERROR);
58 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher,
59 bool response_is_file,
60 const std::string& data_string,
61 const base::FilePath& data_file) {
62 return false;
65 PrivetURLFetcher::PrivetURLFetcher(
66 const GURL& url,
67 net::URLFetcher::RequestType request_type,
68 net::URLRequestContextGetter* request_context,
69 PrivetURLFetcher::Delegate* delegate)
70 : url_(url),
71 request_type_(request_type),
72 request_context_(request_context),
73 delegate_(delegate),
74 do_not_retry_on_transient_error_(false),
75 send_empty_privet_token_(false),
76 has_byte_range_(false),
77 make_response_file_(false),
78 byte_range_start_(0),
79 byte_range_end_(0),
80 tries_(0),
81 weak_factory_(this) {}
83 PrivetURLFetcher::~PrivetURLFetcher() {
86 // static
87 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
88 const std::string& token) {
89 TokenMapHolder::GetInstance()->map[host] = token;
92 // static
93 void PrivetURLFetcher::ResetTokenMapForTests() {
94 TokenMapHolder::GetInstance()->map.clear();
97 void PrivetURLFetcher::DoNotRetryOnTransientError() {
98 DCHECK(tries_ == 0);
99 do_not_retry_on_transient_error_ = true;
102 void PrivetURLFetcher::SendEmptyPrivetToken() {
103 DCHECK(tries_ == 0);
104 send_empty_privet_token_ = true;
107 std::string PrivetURLFetcher::GetPrivetAccessToken() {
108 if (send_empty_privet_token_) {
109 return std::string();
112 TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance();
113 TokenMap::iterator found = token_map_holder->map.find(GetHostString());
114 return found != token_map_holder->map.end() ? found->second : std::string();
117 std::string PrivetURLFetcher::GetHostString() {
118 return url_.GetOrigin().spec();
121 void PrivetURLFetcher::SaveResponseToFile() {
122 DCHECK(tries_ == 0);
123 make_response_file_ = true;
126 void PrivetURLFetcher::SetByteRange(int start, int end) {
127 DCHECK(tries_ == 0);
128 byte_range_start_ = start;
129 byte_range_end_ = end;
130 has_byte_range_ = true;
133 void PrivetURLFetcher::Try() {
134 tries_++;
135 if (tries_ < kPrivetMaxRetries) {
136 std::string token = GetPrivetAccessToken();
138 if (token.empty())
139 token = kXPrivetEmptyToken;
141 url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
142 url_fetcher_->SetRequestContext(request_context_);
143 url_fetcher_->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix) +
144 token);
145 if (has_byte_range_) {
146 url_fetcher_->AddExtraRequestHeader(
147 MakeRangeHeader(byte_range_start_, byte_range_end_));
150 if (make_response_file_) {
151 url_fetcher_->SaveResponseToTemporaryFile(
152 content::BrowserThread::GetMessageLoopProxyForThread(
153 content::BrowserThread::FILE));
156 // URLFetcher requires us to set upload data for POST requests.
157 if (request_type_ == net::URLFetcher::POST) {
158 if (!upload_file_path_.empty()) {
159 url_fetcher_->SetUploadFilePath(
160 upload_content_type_,
161 upload_file_path_,
162 0 /*offset*/,
163 kuint64max /*length*/,
164 content::BrowserThread::GetMessageLoopProxyForThread(
165 content::BrowserThread::FILE));
166 } else {
167 url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
171 url_fetcher_->Start();
172 } else {
173 delegate_->OnError(this, RETRY_ERROR);
177 void PrivetURLFetcher::Start() {
178 DCHECK_EQ(tries_, 0); // We haven't called |Start()| yet.
180 if (!send_empty_privet_token_) {
181 std::string privet_access_token;
182 privet_access_token = GetPrivetAccessToken();
183 if (privet_access_token.empty()) {
184 RequestTokenRefresh();
185 return;
189 Try();
192 void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
193 const std::string& upload_data) {
194 DCHECK(upload_file_path_.empty());
195 upload_content_type_ = upload_content_type;
196 upload_data_ = upload_data;
199 void PrivetURLFetcher::SetUploadFilePath(
200 const std::string& upload_content_type,
201 const base::FilePath& upload_file_path) {
202 DCHECK(upload_data_.empty());
203 upload_content_type_ = upload_content_type;
204 upload_file_path_ = upload_file_path;
207 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
208 if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
209 ScheduleRetry(kPrivetTimeoutOnError);
210 return;
213 if (!OnURLFetchCompleteDoNotParseData(source)) {
214 // Byte ranges should only be used when we're not parsing the data
215 // as JSON.
216 DCHECK(!has_byte_range_);
218 // We should only be saving raw data to a file.
219 DCHECK(!make_response_file_);
221 OnURLFetchCompleteParseData(source);
225 // Note that this function returns "true" in error cases to indicate
226 // that it has fully handled the responses.
227 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
228 const net::URLFetcher* source) {
229 if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
230 RequestTokenRefresh();
231 return true;
234 if (source->GetResponseCode() != net::HTTP_OK &&
235 source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT) {
236 delegate_->OnError(this, RESPONSE_CODE_ERROR);
237 return true;
240 if (make_response_file_) {
241 base::FilePath response_file_path;
243 if (!source->GetResponseAsFilePath(true, &response_file_path)) {
244 delegate_->OnError(this, URL_FETCH_ERROR);
245 return true;
248 return delegate_->OnRawData(this, true, std::string(), response_file_path);
249 } else {
250 std::string response_str;
252 if (!source->GetResponseAsString(&response_str)) {
253 delegate_->OnError(this, URL_FETCH_ERROR);
254 return true;
257 return delegate_->OnRawData(this, false, response_str, base::FilePath());
261 void PrivetURLFetcher::OnURLFetchCompleteParseData(
262 const net::URLFetcher* source) {
263 if (source->GetResponseCode() != net::HTTP_OK) {
264 delegate_->OnError(this, RESPONSE_CODE_ERROR);
265 return;
268 std::string response_str;
270 if (!source->GetResponseAsString(&response_str)) {
271 delegate_->OnError(this, URL_FETCH_ERROR);
272 return;
275 base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
276 scoped_ptr<base::Value> value;
278 value.reset(json_reader.ReadToValue(response_str));
280 if (!value) {
281 delegate_->OnError(this, JSON_PARSE_ERROR);
282 return;
285 const base::DictionaryValue* dictionary_value;
287 if (!value->GetAsDictionary(&dictionary_value)) {
288 delegate_->OnError(this, JSON_PARSE_ERROR);
289 return;
292 std::string error;
293 if (dictionary_value->GetString(kPrivetKeyError, &error)) {
294 if (error == kPrivetErrorInvalidXPrivetToken) {
295 RequestTokenRefresh();
296 return;
297 } else if (PrivetErrorTransient(error)) {
298 if (!do_not_retry_on_transient_error_) {
299 int timeout_seconds;
300 if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
301 &timeout_seconds)) {
302 timeout_seconds = kPrivetDefaultTimeout;
305 ScheduleRetry(timeout_seconds);
306 return;
311 delegate_->OnParsedJson(this, dictionary_value,
312 dictionary_value->HasKey(kPrivetKeyError));
315 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
316 double random_scaling_factor =
317 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
319 int timeout_seconds_randomized =
320 static_cast<int>(timeout_seconds * random_scaling_factor);
322 timeout_seconds_randomized =
323 std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
325 base::MessageLoop::current()->PostDelayedTask(
326 FROM_HERE,
327 base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
328 base::TimeDelta::FromSeconds(timeout_seconds_randomized));
331 void PrivetURLFetcher::RequestTokenRefresh() {
332 delegate_->OnNeedPrivetToken(
333 this,
334 base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
337 void PrivetURLFetcher::RefreshToken(const std::string& token) {
338 if (token.empty()) {
339 delegate_->OnError(this, TOKEN_ERROR);
340 } else {
341 SetTokenForHost(GetHostString(), token);
342 Try();
346 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
347 return (error == kPrivetErrorDeviceBusy) ||
348 (error == kPrivetErrorPendingUserAction) ||
349 (error == kPrivetErrorPrinterBusy);
352 } // namespace local_discovery