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/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/base/load_flags.h"
19 #include "net/http/http_status_code.h"
20 #include "net/url_request/url_request_status.h"
22 namespace local_discovery
{
26 typedef std::map
<std::string
, std::string
> TokenMap
;
28 struct TokenMapHolder
{
30 static TokenMapHolder
* GetInstance() {
31 return Singleton
<TokenMapHolder
>::get();
37 const char kXPrivetTokenHeaderPrefix
[] = "X-Privet-Token: ";
38 const char kPrivetV3AuthTokenHeaderPrefix
[] = "Authorization: ";
39 const char kRangeHeaderFormat
[] = "Range: bytes=%d-%d";
40 const char kXPrivetEmptyToken
[] = "\"\"";
41 const char kPrivetAuthTokenUnknown
[] = "Unknown";
42 const int kPrivetMaxRetries
= 20;
43 const int kPrivetTimeoutOnError
= 5;
44 const int kHTTPErrorCodeInvalidXPrivetToken
= 418;
46 std::string
MakeRangeHeader(int start
, int end
) {
49 DCHECK_GT(end
, start
);
50 return base::StringPrintf(kRangeHeaderFormat
, start
, end
);
55 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
56 PrivetURLFetcher
* fetcher
,
57 const TokenCallback
& callback
) {
58 OnError(fetcher
, TOKEN_ERROR
);
61 std::string
PrivetURLFetcher::Delegate::GetAuthToken() {
62 return kPrivetAuthTokenUnknown
;
65 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher
* fetcher
,
66 bool response_is_file
,
67 const std::string
& data_string
,
68 const base::FilePath
& data_file
) {
72 PrivetURLFetcher::PrivetURLFetcher(
74 net::URLFetcher::RequestType request_type
,
75 net::URLRequestContextGetter
* request_context
,
76 PrivetURLFetcher::Delegate
* delegate
)
78 request_type_(request_type
),
79 request_context_(request_context
),
81 do_not_retry_on_transient_error_(false),
82 send_empty_privet_token_(false),
83 has_byte_range_(false),
84 make_response_file_(false),
92 PrivetURLFetcher::~PrivetURLFetcher() {
96 void PrivetURLFetcher::SetTokenForHost(const std::string
& host
,
97 const std::string
& token
) {
98 TokenMapHolder::GetInstance()->map
[host
] = token
;
102 void PrivetURLFetcher::ResetTokenMapForTests() {
103 TokenMapHolder::GetInstance()->map
.clear();
106 void PrivetURLFetcher::DoNotRetryOnTransientError() {
108 do_not_retry_on_transient_error_
= true;
111 void PrivetURLFetcher::SendEmptyPrivetToken() {
113 send_empty_privet_token_
= true;
116 std::string
PrivetURLFetcher::GetPrivetAccessToken() {
117 if (send_empty_privet_token_
) {
118 return std::string();
121 TokenMapHolder
* token_map_holder
= TokenMapHolder::GetInstance();
122 TokenMap::iterator found
= token_map_holder
->map
.find(GetHostString());
123 return found
!= token_map_holder
->map
.end() ? found
->second
: std::string();
126 std::string
PrivetURLFetcher::GetHostString() {
127 return url_
.GetOrigin().spec();
130 void PrivetURLFetcher::SaveResponseToFile() {
132 make_response_file_
= true;
135 void PrivetURLFetcher::V3Mode() {
139 void PrivetURLFetcher::SetByteRange(int start
, int end
) {
141 byte_range_start_
= start
;
142 byte_range_end_
= end
;
143 has_byte_range_
= true;
146 void PrivetURLFetcher::Try() {
148 if (tries_
< kPrivetMaxRetries
) {
151 url_fetcher_
.reset(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
;
310 value
.reset(json_reader
.ReadToValue(response_str
));
313 delegate_
->OnError(this, JSON_PARSE_ERROR
);
317 const base::DictionaryValue
* dictionary_value
= NULL
;
319 if (!value
->GetAsDictionary(&dictionary_value
)) {
320 delegate_
->OnError(this, JSON_PARSE_ERROR
);
325 if (!v3_mode_
&& dictionary_value
->GetString(kPrivetKeyError
, &error
)) {
326 if (error
== kPrivetErrorInvalidXPrivetToken
) {
327 RequestTokenRefresh();
329 } else if (PrivetErrorTransient(error
)) {
330 if (!do_not_retry_on_transient_error_
) {
332 if (!dictionary_value
->GetInteger(kPrivetKeyTimeout
,
334 timeout_seconds
= kPrivetDefaultTimeout
;
337 ScheduleRetry(timeout_seconds
);
341 is_error_response
= true;
344 delegate_
->OnParsedJson(this, *dictionary_value
, is_error_response
);
347 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds
) {
348 double random_scaling_factor
=
349 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition
;
351 int timeout_seconds_randomized
=
352 static_cast<int>(timeout_seconds
* random_scaling_factor
);
354 timeout_seconds_randomized
=
355 std::max(timeout_seconds_randomized
, kPrivetMinimumTimeout
);
357 base::MessageLoop::current()->PostDelayedTask(
359 base::Bind(&PrivetURLFetcher::Try
, weak_factory_
.GetWeakPtr()),
360 base::TimeDelta::FromSeconds(timeout_seconds_randomized
));
363 void PrivetURLFetcher::RequestTokenRefresh() {
364 delegate_
->OnNeedPrivetToken(
366 base::Bind(&PrivetURLFetcher::RefreshToken
, weak_factory_
.GetWeakPtr()));
369 void PrivetURLFetcher::RefreshToken(const std::string
& token
) {
371 delegate_
->OnError(this, TOKEN_ERROR
);
373 SetTokenForHost(GetHostString(), token
);
378 bool PrivetURLFetcher::PrivetErrorTransient(const std::string
& error
) {
379 return (error
== kPrivetErrorDeviceBusy
) ||
380 (error
== kPrivetV3ErrorDeviceBusy
) ||
381 (error
== kPrivetErrorPendingUserAction
) ||
382 (error
== kPrivetErrorPrinterBusy
);
385 } // namespace local_discovery