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/http/http_status_code.h"
19 #include "net/url_request/url_request_status.h"
21 namespace local_discovery
{
25 typedef std::map
<std::string
, std::string
> TokenMap
;
27 struct TokenMapHolder
{
29 static TokenMapHolder
* GetInstance() {
30 return Singleton
<TokenMapHolder
>::get();
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
) {
48 DCHECK_GT(end
, start
);
49 return base::StringPrintf(kRangeHeaderFormat
, start
, end
);
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
) {
71 PrivetURLFetcher::PrivetURLFetcher(
73 net::URLFetcher::RequestType request_type
,
74 net::URLRequestContextGetter
* request_context
,
75 PrivetURLFetcher::Delegate
* delegate
)
77 request_type_(request_type
),
78 request_context_(request_context
),
80 do_not_retry_on_transient_error_(false),
81 send_empty_privet_token_(false),
82 has_byte_range_(false),
83 make_response_file_(false),
91 PrivetURLFetcher::~PrivetURLFetcher() {
95 void PrivetURLFetcher::SetTokenForHost(const std::string
& host
,
96 const std::string
& token
) {
97 TokenMapHolder::GetInstance()->map
[host
] = token
;
101 void PrivetURLFetcher::ResetTokenMapForTests() {
102 TokenMapHolder::GetInstance()->map
.clear();
105 void PrivetURLFetcher::DoNotRetryOnTransientError() {
107 do_not_retry_on_transient_error_
= true;
110 void PrivetURLFetcher::SendEmptyPrivetToken() {
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() {
131 make_response_file_
= true;
134 void PrivetURLFetcher::V3Mode() {
138 void PrivetURLFetcher::SetByteRange(int start
, int end
) {
140 byte_range_start_
= start
;
141 byte_range_end_
= end
;
142 has_byte_range_
= true;
145 void PrivetURLFetcher::Try() {
147 if (tries_
< kPrivetMaxRetries
) {
150 url_fetcher_
.reset(net::URLFetcher::Create(url_
, request_type_
, this));
151 url_fetcher_
->SetRequestContext(request_context_
.get());
154 std::string auth_token
= delegate_
->GetAuthToken();
156 url_fetcher_
->AddExtraRequestHeader(
157 std::string(kXPrivetAuthTokenHeaderPrefix
) + auth_token
);
159 std::string token
= GetPrivetAccessToken();
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_
,
186 kuint64max
/*length*/,
187 content::BrowserThread::GetMessageLoopProxyForThread(
188 content::BrowserThread::FILE));
190 url_fetcher_
->SetUploadData(upload_content_type_
, upload_data_
);
194 url_fetcher_
->Start();
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();
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
);
236 if (!OnURLFetchCompleteDoNotParseData(source
)) {
237 // Byte ranges should only be used when we're not parsing the data
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();
257 if (source
->GetResponseCode() != net::HTTP_OK
&&
258 source
->GetResponseCode() != net::HTTP_PARTIAL_CONTENT
) {
259 delegate_
->OnError(this, RESPONSE_CODE_ERROR
);
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
);
271 return delegate_
->OnRawData(this, true, std::string(), response_file_path
);
273 std::string response_str
;
275 if (!source
->GetResponseAsString(&response_str
)) {
276 delegate_
->OnError(this, URL_FETCH_ERROR
);
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
);
291 std::string response_str
;
293 if (!source
->GetResponseAsString(&response_str
)) {
294 delegate_
->OnError(this, URL_FETCH_ERROR
);
298 base::JSONReader
json_reader(base::JSON_ALLOW_TRAILING_COMMAS
);
299 scoped_ptr
<base::Value
> value
;
301 value
.reset(json_reader
.ReadToValue(response_str
));
304 delegate_
->OnError(this, JSON_PARSE_ERROR
);
308 const base::DictionaryValue
* dictionary_value
= NULL
;
310 if (!value
->GetAsDictionary(&dictionary_value
)) {
311 delegate_
->OnError(this, JSON_PARSE_ERROR
);
316 if (dictionary_value
->GetString(kPrivetKeyError
, &error
)) {
317 if (error
== kPrivetErrorInvalidXPrivetToken
) {
318 RequestTokenRefresh();
320 } else if (PrivetErrorTransient(error
)) {
321 if (!do_not_retry_on_transient_error_
) {
323 if (!dictionary_value
->GetInteger(kPrivetKeyTimeout
,
325 timeout_seconds
= kPrivetDefaultTimeout
;
328 ScheduleRetry(timeout_seconds
);
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(
350 base::Bind(&PrivetURLFetcher::Try
, weak_factory_
.GetWeakPtr()),
351 base::TimeDelta::FromSeconds(timeout_seconds_randomized
));
354 void PrivetURLFetcher::RequestTokenRefresh() {
355 delegate_
->OnNeedPrivetToken(
357 base::Bind(&PrivetURLFetcher::RefreshToken
, weak_factory_
.GetWeakPtr()));
360 void PrivetURLFetcher::RefreshToken(const std::string
& token
) {
362 delegate_
->OnError(this, TOKEN_ERROR
);
364 SetTokenForHost(GetHostString(), token
);
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