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"
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
{
28 typedef std::map
<std::string
, std::string
> TokenMap
;
30 struct TokenMapHolder
{
32 static TokenMapHolder
* GetInstance() {
33 return base::Singleton
<TokenMapHolder
>::get();
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
) {
51 DCHECK_GT(end
, start
);
52 return base::StringPrintf(kRangeHeaderFormat
, start
, end
);
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
) {
74 PrivetURLFetcher::PrivetURLFetcher(
76 net::URLFetcher::RequestType request_type
,
77 net::URLRequestContextGetter
* request_context
,
78 PrivetURLFetcher::Delegate
* delegate
)
80 request_type_(request_type
),
81 request_context_(request_context
),
83 do_not_retry_on_transient_error_(false),
84 send_empty_privet_token_(false),
85 has_byte_range_(false),
86 make_response_file_(false),
94 PrivetURLFetcher::~PrivetURLFetcher() {
98 void PrivetURLFetcher::SetTokenForHost(const std::string
& host
,
99 const std::string
& token
) {
100 TokenMapHolder::GetInstance()->map
[host
] = token
;
104 void PrivetURLFetcher::ResetTokenMapForTests() {
105 TokenMapHolder::GetInstance()->map
.clear();
108 void PrivetURLFetcher::DoNotRetryOnTransientError() {
110 do_not_retry_on_transient_error_
= true;
113 void PrivetURLFetcher::SendEmptyPrivetToken() {
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() {
134 make_response_file_
= true;
137 void PrivetURLFetcher::V3Mode() {
141 void PrivetURLFetcher::SetByteRange(int start
, int end
) {
143 byte_range_start_
= start
;
144 byte_range_end_
= end
;
145 has_byte_range_
= true;
148 void PrivetURLFetcher::Try() {
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());
159 url_fetcher_
->AddExtraRequestHeader(
160 std::string(kPrivetV3AuthTokenHeaderPrefix
) +
161 delegate_
->GetAuthToken());
163 std::string token
= GetPrivetAccessToken();
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_
,
190 kuint64max
/*length*/,
191 content::BrowserThread::GetMessageLoopProxyForThread(
192 content::BrowserThread::FILE));
194 url_fetcher_
->SetUploadData(upload_content_type_
, upload_data_
);
198 url_fetcher_
->Start();
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();
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
);
241 if (!OnURLFetchCompleteDoNotParseData(source
)) {
242 // Byte ranges should only be used when we're not parsing the data
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();
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
);
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
);
277 return delegate_
->OnRawData(this, true, std::string(), response_file_path
);
279 std::string response_str
;
281 if (!source
->GetResponseAsString(&response_str
)) {
282 delegate_
->OnError(this, URL_FETCH_ERROR
);
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
);
301 std::string response_str
;
302 if (!source
->GetResponseAsString(&response_str
)) {
303 delegate_
->OnError(this, URL_FETCH_ERROR
);
307 base::JSONReader
json_reader(base::JSON_ALLOW_TRAILING_COMMAS
);
308 scoped_ptr
<base::Value
> value
= json_reader
.ReadToValue(response_str
);
310 delegate_
->OnError(this, JSON_PARSE_ERROR
);
314 const base::DictionaryValue
* dictionary_value
= NULL
;
316 if (!value
->GetAsDictionary(&dictionary_value
)) {
317 delegate_
->OnError(this, JSON_PARSE_ERROR
);
322 if (!v3_mode_
&& dictionary_value
->GetString(kPrivetKeyError
, &error
)) {
323 if (error
== kPrivetErrorInvalidXPrivetToken
) {
324 RequestTokenRefresh();
326 } else if (PrivetErrorTransient(error
)) {
327 if (!do_not_retry_on_transient_error_
) {
329 if (!dictionary_value
->GetInteger(kPrivetKeyTimeout
,
331 timeout_seconds
= kPrivetDefaultTimeout
;
334 ScheduleRetry(timeout_seconds
);
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(
362 base::Bind(&PrivetURLFetcher::RefreshToken
, weak_factory_
.GetWeakPtr()));
365 void PrivetURLFetcher::RefreshToken(const std::string
& token
) {
367 delegate_
->OnError(this, TOKEN_ERROR
);
369 SetTokenForHost(GetHostString(), token
);
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