Add GCMChannelStatusSyncer to schedule requests and enable/disable GCM
[chromium-blink-merge.git] / chrome / browser / local_discovery / privet_url_fetcher.cc
blobe26a062ffe5cb5f9944e5f946d6fa2be0a64a4ce
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 kXPrivetAuthTokenHeaderPrefix[] = "X-Privet-Auth: ";
38 const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
39 const char kXPrivetEmptyToken[] = "\"\"";
40 const char kPrivetAuthTokenUnknown[] = "Unknown";
41 const int kPrivetMaxRetries = 20;
42 const int kPrivetTimeoutOnError = 5;
43 const int kHTTPErrorCodeInvalidXPrivetToken = 418;
45 std::string MakeRangeHeader(int start, int end) {
46 DCHECK_GE(start, 0);
47 DCHECK_GT(end, 0);
48 DCHECK_GT(end, start);
49 return base::StringPrintf(kRangeHeaderFormat, start, end);
52 } // namespace
54 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
55 PrivetURLFetcher* fetcher,
56 const TokenCallback& callback) {
57 OnError(fetcher, TOKEN_ERROR);
60 std::string PrivetURLFetcher::Delegate::GetAuthToken() {
61 return kPrivetAuthTokenUnknown;
64 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher,
65 bool response_is_file,
66 const std::string& data_string,
67 const base::FilePath& data_file) {
68 return false;
71 PrivetURLFetcher::PrivetURLFetcher(
72 const GURL& url,
73 net::URLFetcher::RequestType request_type,
74 net::URLRequestContextGetter* request_context,
75 PrivetURLFetcher::Delegate* delegate)
76 : url_(url),
77 request_type_(request_type),
78 request_context_(request_context),
79 delegate_(delegate),
80 do_not_retry_on_transient_error_(false),
81 send_empty_privet_token_(false),
82 has_byte_range_(false),
83 make_response_file_(false),
84 v3_mode_(false),
85 byte_range_start_(0),
86 byte_range_end_(0),
87 tries_(0),
88 weak_factory_(this) {
91 PrivetURLFetcher::~PrivetURLFetcher() {
94 // static
95 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
96 const std::string& token) {
97 TokenMapHolder::GetInstance()->map[host] = token;
100 // static
101 void PrivetURLFetcher::ResetTokenMapForTests() {
102 TokenMapHolder::GetInstance()->map.clear();
105 void PrivetURLFetcher::DoNotRetryOnTransientError() {
106 DCHECK(tries_ == 0);
107 do_not_retry_on_transient_error_ = true;
110 void PrivetURLFetcher::SendEmptyPrivetToken() {
111 DCHECK(tries_ == 0);
112 send_empty_privet_token_ = true;
115 std::string PrivetURLFetcher::GetPrivetAccessToken() {
116 if (send_empty_privet_token_) {
117 return std::string();
120 TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance();
121 TokenMap::iterator found = token_map_holder->map.find(GetHostString());
122 return found != token_map_holder->map.end() ? found->second : std::string();
125 std::string PrivetURLFetcher::GetHostString() {
126 return url_.GetOrigin().spec();
129 void PrivetURLFetcher::SaveResponseToFile() {
130 DCHECK(tries_ == 0);
131 make_response_file_ = true;
134 void PrivetURLFetcher::V3Mode() {
135 v3_mode_ = true;
138 void PrivetURLFetcher::SetByteRange(int start, int end) {
139 DCHECK(tries_ == 0);
140 byte_range_start_ = start;
141 byte_range_end_ = end;
142 has_byte_range_ = true;
145 void PrivetURLFetcher::Try() {
146 tries_++;
147 if (tries_ < kPrivetMaxRetries) {
150 url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
151 url_fetcher_->SetRequestContext(request_context_.get());
153 if (v3_mode_) {
154 std::string auth_token = delegate_->GetAuthToken();
156 url_fetcher_->AddExtraRequestHeader(
157 std::string(kXPrivetAuthTokenHeaderPrefix) + auth_token);
158 } else {
159 std::string token = GetPrivetAccessToken();
161 if (token.empty())
162 token = kXPrivetEmptyToken;
164 url_fetcher_->AddExtraRequestHeader(
165 std::string(kXPrivetTokenHeaderPrefix) + token);
168 if (has_byte_range_) {
169 url_fetcher_->AddExtraRequestHeader(
170 MakeRangeHeader(byte_range_start_, byte_range_end_));
173 if (make_response_file_) {
174 url_fetcher_->SaveResponseToTemporaryFile(
175 content::BrowserThread::GetMessageLoopProxyForThread(
176 content::BrowserThread::FILE));
179 // URLFetcher requires us to set upload data for POST requests.
180 if (request_type_ == net::URLFetcher::POST) {
181 if (!upload_file_path_.empty()) {
182 url_fetcher_->SetUploadFilePath(
183 upload_content_type_,
184 upload_file_path_,
185 0 /*offset*/,
186 kuint64max /*length*/,
187 content::BrowserThread::GetMessageLoopProxyForThread(
188 content::BrowserThread::FILE));
189 } else {
190 url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
194 url_fetcher_->Start();
195 } else {
196 delegate_->OnError(this, RETRY_ERROR);
200 void PrivetURLFetcher::Start() {
201 DCHECK_EQ(tries_, 0); // We haven't called |Start()| yet.
203 if (!send_empty_privet_token_ && !v3_mode_) {
204 std::string privet_access_token;
205 privet_access_token = GetPrivetAccessToken();
206 if (privet_access_token.empty()) {
207 RequestTokenRefresh();
208 return;
212 Try();
215 void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
216 const std::string& upload_data) {
217 DCHECK(upload_file_path_.empty());
218 upload_content_type_ = upload_content_type;
219 upload_data_ = upload_data;
222 void PrivetURLFetcher::SetUploadFilePath(
223 const std::string& upload_content_type,
224 const base::FilePath& upload_file_path) {
225 DCHECK(upload_data_.empty());
226 upload_content_type_ = upload_content_type;
227 upload_file_path_ = upload_file_path;
230 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
231 if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
232 ScheduleRetry(kPrivetTimeoutOnError);
233 return;
236 if (!OnURLFetchCompleteDoNotParseData(source)) {
237 // Byte ranges should only be used when we're not parsing the data
238 // as JSON.
239 DCHECK(!has_byte_range_);
241 // We should only be saving raw data to a file.
242 DCHECK(!make_response_file_);
244 OnURLFetchCompleteParseData(source);
248 // Note that this function returns "true" in error cases to indicate
249 // that it has fully handled the responses.
250 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
251 const net::URLFetcher* source) {
252 if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
253 RequestTokenRefresh();
254 return true;
257 if (source->GetResponseCode() != net::HTTP_OK &&
258 source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT) {
259 delegate_->OnError(this, RESPONSE_CODE_ERROR);
260 return true;
263 if (make_response_file_) {
264 base::FilePath response_file_path;
266 if (!source->GetResponseAsFilePath(true, &response_file_path)) {
267 delegate_->OnError(this, URL_FETCH_ERROR);
268 return true;
271 return delegate_->OnRawData(this, true, std::string(), response_file_path);
272 } else {
273 std::string response_str;
275 if (!source->GetResponseAsString(&response_str)) {
276 delegate_->OnError(this, URL_FETCH_ERROR);
277 return true;
280 return delegate_->OnRawData(this, false, response_str, base::FilePath());
284 void PrivetURLFetcher::OnURLFetchCompleteParseData(
285 const net::URLFetcher* source) {
286 if (source->GetResponseCode() != net::HTTP_OK) {
287 delegate_->OnError(this, RESPONSE_CODE_ERROR);
288 return;
291 std::string response_str;
293 if (!source->GetResponseAsString(&response_str)) {
294 delegate_->OnError(this, URL_FETCH_ERROR);
295 return;
298 base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
299 scoped_ptr<base::Value> value;
301 value.reset(json_reader.ReadToValue(response_str));
303 if (!value) {
304 delegate_->OnError(this, JSON_PARSE_ERROR);
305 return;
308 const base::DictionaryValue* dictionary_value = NULL;
310 if (!value->GetAsDictionary(&dictionary_value)) {
311 delegate_->OnError(this, JSON_PARSE_ERROR);
312 return;
315 std::string error;
316 if (dictionary_value->GetString(kPrivetKeyError, &error)) {
317 if (error == kPrivetErrorInvalidXPrivetToken) {
318 RequestTokenRefresh();
319 return;
320 } else if (PrivetErrorTransient(error)) {
321 if (!do_not_retry_on_transient_error_) {
322 int timeout_seconds;
323 if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
324 &timeout_seconds)) {
325 timeout_seconds = kPrivetDefaultTimeout;
328 ScheduleRetry(timeout_seconds);
329 return;
334 delegate_->OnParsedJson(
335 this, *dictionary_value, dictionary_value->HasKey(kPrivetKeyError));
338 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
339 double random_scaling_factor =
340 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
342 int timeout_seconds_randomized =
343 static_cast<int>(timeout_seconds * random_scaling_factor);
345 timeout_seconds_randomized =
346 std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
348 base::MessageLoop::current()->PostDelayedTask(
349 FROM_HERE,
350 base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
351 base::TimeDelta::FromSeconds(timeout_seconds_randomized));
354 void PrivetURLFetcher::RequestTokenRefresh() {
355 delegate_->OnNeedPrivetToken(
356 this,
357 base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
360 void PrivetURLFetcher::RefreshToken(const std::string& token) {
361 if (token.empty()) {
362 delegate_->OnError(this, TOKEN_ERROR);
363 } else {
364 SetTokenForHost(GetHostString(), token);
365 Try();
369 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
370 return (error == kPrivetErrorDeviceBusy) ||
371 (error == kPrivetV3ErrorDeviceBusy) ||
372 (error == kPrivetErrorPendingUserAction) ||
373 (error == kPrivetErrorPrinterBusy);
376 } // namespace local_discovery