Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / local_discovery / privet_url_fetcher.cc
blob390fb8350425fcddb07cfe50a48d5b6029d2151d
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/location.h"
12 #include "base/memory/singleton.h"
13 #include "base/rand_util.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/local_discovery/privet_constants.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "net/base/load_flags.h"
21 #include "net/http/http_status_code.h"
22 #include "net/url_request/url_request_status.h"
24 namespace local_discovery {
26 namespace {
28 typedef std::map<std::string, std::string> TokenMap;
30 struct TokenMapHolder {
31 public:
32 static TokenMapHolder* GetInstance() {
33 return Singleton<TokenMapHolder>::get();
36 TokenMap map;
39 const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: ";
40 const char kPrivetV3AuthTokenHeaderPrefix[] = "Authorization: ";
41 const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
42 const char kXPrivetEmptyToken[] = "\"\"";
43 const char kPrivetAuthTokenUnknown[] = "Unknown";
44 const int kPrivetMaxRetries = 20;
45 const int kPrivetTimeoutOnError = 5;
46 const int kHTTPErrorCodeInvalidXPrivetToken = 418;
48 std::string MakeRangeHeader(int start, int end) {
49 DCHECK_GE(start, 0);
50 DCHECK_GT(end, 0);
51 DCHECK_GT(end, start);
52 return base::StringPrintf(kRangeHeaderFormat, start, end);
55 } // namespace
57 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
58 PrivetURLFetcher* fetcher,
59 const TokenCallback& callback) {
60 OnError(fetcher, TOKEN_ERROR);
63 std::string PrivetURLFetcher::Delegate::GetAuthToken() {
64 return kPrivetAuthTokenUnknown;
67 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher,
68 bool response_is_file,
69 const std::string& data_string,
70 const base::FilePath& data_file) {
71 return false;
74 PrivetURLFetcher::PrivetURLFetcher(
75 const GURL& url,
76 net::URLFetcher::RequestType request_type,
77 net::URLRequestContextGetter* request_context,
78 PrivetURLFetcher::Delegate* delegate)
79 : url_(url),
80 request_type_(request_type),
81 request_context_(request_context),
82 delegate_(delegate),
83 do_not_retry_on_transient_error_(false),
84 send_empty_privet_token_(false),
85 has_byte_range_(false),
86 make_response_file_(false),
87 v3_mode_(false),
88 byte_range_start_(0),
89 byte_range_end_(0),
90 tries_(0),
91 weak_factory_(this) {
94 PrivetURLFetcher::~PrivetURLFetcher() {
97 // static
98 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
99 const std::string& token) {
100 TokenMapHolder::GetInstance()->map[host] = token;
103 // static
104 void PrivetURLFetcher::ResetTokenMapForTests() {
105 TokenMapHolder::GetInstance()->map.clear();
108 void PrivetURLFetcher::DoNotRetryOnTransientError() {
109 DCHECK(tries_ == 0);
110 do_not_retry_on_transient_error_ = true;
113 void PrivetURLFetcher::SendEmptyPrivetToken() {
114 DCHECK(tries_ == 0);
115 send_empty_privet_token_ = true;
118 std::string PrivetURLFetcher::GetPrivetAccessToken() {
119 if (send_empty_privet_token_) {
120 return std::string();
123 TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance();
124 TokenMap::iterator found = token_map_holder->map.find(GetHostString());
125 return found != token_map_holder->map.end() ? found->second : std::string();
128 std::string PrivetURLFetcher::GetHostString() {
129 return url_.GetOrigin().spec();
132 void PrivetURLFetcher::SaveResponseToFile() {
133 DCHECK(tries_ == 0);
134 make_response_file_ = true;
137 void PrivetURLFetcher::V3Mode() {
138 v3_mode_ = true;
141 void PrivetURLFetcher::SetByteRange(int start, int end) {
142 DCHECK(tries_ == 0);
143 byte_range_start_ = start;
144 byte_range_end_ = end;
145 has_byte_range_ = true;
148 void PrivetURLFetcher::Try() {
149 tries_++;
150 if (tries_ < kPrivetMaxRetries) {
151 url_fetcher_ = net::URLFetcher::Create(url_, request_type_, this);
152 // Privet requests are relevant to hosts on local network only.
153 url_fetcher_->SetLoadFlags(url_fetcher_->GetLoadFlags() |
154 net::LOAD_BYPASS_PROXY |
155 net::LOAD_DISABLE_CACHE);
156 url_fetcher_->SetRequestContext(request_context_.get());
158 if (v3_mode_) {
159 url_fetcher_->AddExtraRequestHeader(
160 std::string(kPrivetV3AuthTokenHeaderPrefix) +
161 delegate_->GetAuthToken());
162 } else {
163 std::string token = GetPrivetAccessToken();
165 if (token.empty())
166 token = kXPrivetEmptyToken;
168 url_fetcher_->AddExtraRequestHeader(
169 std::string(kXPrivetTokenHeaderPrefix) + token);
172 if (has_byte_range_) {
173 url_fetcher_->AddExtraRequestHeader(
174 MakeRangeHeader(byte_range_start_, byte_range_end_));
177 if (make_response_file_) {
178 url_fetcher_->SaveResponseToTemporaryFile(
179 content::BrowserThread::GetMessageLoopProxyForThread(
180 content::BrowserThread::FILE));
183 // URLFetcher requires us to set upload data for POST requests.
184 if (request_type_ == net::URLFetcher::POST) {
185 if (!upload_file_path_.empty()) {
186 url_fetcher_->SetUploadFilePath(
187 upload_content_type_,
188 upload_file_path_,
189 0 /*offset*/,
190 kuint64max /*length*/,
191 content::BrowserThread::GetMessageLoopProxyForThread(
192 content::BrowserThread::FILE));
193 } else {
194 url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
198 url_fetcher_->Start();
199 } else {
200 delegate_->OnError(this, RETRY_ERROR);
204 void PrivetURLFetcher::Start() {
205 DCHECK_EQ(tries_, 0); // We haven't called |Start()| yet.
207 if (!send_empty_privet_token_ && !v3_mode_) {
208 std::string privet_access_token;
209 privet_access_token = GetPrivetAccessToken();
210 if (privet_access_token.empty()) {
211 RequestTokenRefresh();
212 return;
216 Try();
219 void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
220 const std::string& upload_data) {
221 DCHECK(upload_file_path_.empty());
222 upload_content_type_ = upload_content_type;
223 upload_data_ = upload_data;
226 void PrivetURLFetcher::SetUploadFilePath(
227 const std::string& upload_content_type,
228 const base::FilePath& upload_file_path) {
229 DCHECK(upload_data_.empty());
230 upload_content_type_ = upload_content_type;
231 upload_file_path_ = upload_file_path;
234 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
235 if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE ||
236 source->GetResponseCode() == net::URLFetcher::RESPONSE_CODE_INVALID) {
237 ScheduleRetry(kPrivetTimeoutOnError);
238 return;
241 if (!OnURLFetchCompleteDoNotParseData(source)) {
242 // Byte ranges should only be used when we're not parsing the data
243 // as JSON.
244 DCHECK(!has_byte_range_);
246 // We should only be saving raw data to a file.
247 DCHECK(!make_response_file_);
249 OnURLFetchCompleteParseData(source);
253 // Note that this function returns "true" in error cases to indicate
254 // that it has fully handled the responses.
255 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
256 const net::URLFetcher* source) {
257 if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
258 RequestTokenRefresh();
259 return true;
262 if (source->GetResponseCode() != net::HTTP_OK &&
263 source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT &&
264 source->GetResponseCode() != net::HTTP_BAD_REQUEST) {
265 delegate_->OnError(this, RESPONSE_CODE_ERROR);
266 return true;
269 if (make_response_file_) {
270 base::FilePath response_file_path;
272 if (!source->GetResponseAsFilePath(true, &response_file_path)) {
273 delegate_->OnError(this, URL_FETCH_ERROR);
274 return true;
277 return delegate_->OnRawData(this, true, std::string(), response_file_path);
278 } else {
279 std::string response_str;
281 if (!source->GetResponseAsString(&response_str)) {
282 delegate_->OnError(this, URL_FETCH_ERROR);
283 return true;
286 return delegate_->OnRawData(this, false, response_str, base::FilePath());
290 void PrivetURLFetcher::OnURLFetchCompleteParseData(
291 const net::URLFetcher* source) {
292 // Response contains error description.
293 bool is_error_response = false;
294 if (v3_mode_ && source->GetResponseCode() == net::HTTP_BAD_REQUEST) {
295 is_error_response = true;
296 } else if (source->GetResponseCode() != net::HTTP_OK) {
297 delegate_->OnError(this, RESPONSE_CODE_ERROR);
298 return;
301 std::string response_str;
302 if (!source->GetResponseAsString(&response_str)) {
303 delegate_->OnError(this, URL_FETCH_ERROR);
304 return;
307 base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
308 scoped_ptr<base::Value> value = json_reader.ReadToValue(response_str);
309 if (!value) {
310 delegate_->OnError(this, JSON_PARSE_ERROR);
311 return;
314 const base::DictionaryValue* dictionary_value = NULL;
316 if (!value->GetAsDictionary(&dictionary_value)) {
317 delegate_->OnError(this, JSON_PARSE_ERROR);
318 return;
321 std::string error;
322 if (!v3_mode_ && dictionary_value->GetString(kPrivetKeyError, &error)) {
323 if (error == kPrivetErrorInvalidXPrivetToken) {
324 RequestTokenRefresh();
325 return;
326 } else if (PrivetErrorTransient(error)) {
327 if (!do_not_retry_on_transient_error_) {
328 int timeout_seconds;
329 if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
330 &timeout_seconds)) {
331 timeout_seconds = kPrivetDefaultTimeout;
334 ScheduleRetry(timeout_seconds);
335 return;
338 is_error_response = true;
341 delegate_->OnParsedJson(this, *dictionary_value, is_error_response);
344 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
345 double random_scaling_factor =
346 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
348 int timeout_seconds_randomized =
349 static_cast<int>(timeout_seconds * random_scaling_factor);
351 timeout_seconds_randomized =
352 std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
354 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
355 FROM_HERE, base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
356 base::TimeDelta::FromSeconds(timeout_seconds_randomized));
359 void PrivetURLFetcher::RequestTokenRefresh() {
360 delegate_->OnNeedPrivetToken(
361 this,
362 base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
365 void PrivetURLFetcher::RefreshToken(const std::string& token) {
366 if (token.empty()) {
367 delegate_->OnError(this, TOKEN_ERROR);
368 } else {
369 SetTokenForHost(GetHostString(), token);
370 Try();
374 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
375 return (error == kPrivetErrorDeviceBusy) ||
376 (error == kPrivetV3ErrorDeviceBusy) ||
377 (error == kPrivetErrorPendingUserAction) ||
378 (error == kPrivetErrorPrinterBusy);
381 } // namespace local_discovery