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 kRangeHeaderFormat
[] = "Range: bytes=%d-%d";
38 const char kXPrivetEmptyToken
[] = "\"\"";
39 const int kPrivetMaxRetries
= 20;
40 const int kPrivetTimeoutOnError
= 5;
41 const int kHTTPErrorCodeInvalidXPrivetToken
= 418;
43 std::string
MakeRangeHeader(int start
, int end
) {
46 DCHECK_GT(end
, start
);
47 return base::StringPrintf(kRangeHeaderFormat
, start
, end
);
52 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
53 PrivetURLFetcher
* fetcher
,
54 const TokenCallback
& callback
) {
55 OnError(fetcher
, TOKEN_ERROR
);
58 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher
* fetcher
,
59 bool response_is_file
,
60 const std::string
& data_string
,
61 const base::FilePath
& data_file
) {
65 PrivetURLFetcher::PrivetURLFetcher(
67 net::URLFetcher::RequestType request_type
,
68 net::URLRequestContextGetter
* request_context
,
69 PrivetURLFetcher::Delegate
* delegate
)
71 request_type_(request_type
),
72 request_context_(request_context
),
74 do_not_retry_on_transient_error_(false),
75 send_empty_privet_token_(false),
76 has_byte_range_(false),
77 make_response_file_(false),
81 weak_factory_(this) {}
83 PrivetURLFetcher::~PrivetURLFetcher() {
87 void PrivetURLFetcher::SetTokenForHost(const std::string
& host
,
88 const std::string
& token
) {
89 TokenMapHolder::GetInstance()->map
[host
] = token
;
93 void PrivetURLFetcher::ResetTokenMapForTests() {
94 TokenMapHolder::GetInstance()->map
.clear();
97 void PrivetURLFetcher::DoNotRetryOnTransientError() {
99 do_not_retry_on_transient_error_
= true;
102 void PrivetURLFetcher::SendEmptyPrivetToken() {
104 send_empty_privet_token_
= true;
107 std::string
PrivetURLFetcher::GetPrivetAccessToken() {
108 if (send_empty_privet_token_
) {
109 return std::string();
112 TokenMapHolder
* token_map_holder
= TokenMapHolder::GetInstance();
113 TokenMap::iterator found
= token_map_holder
->map
.find(GetHostString());
114 return found
!= token_map_holder
->map
.end() ? found
->second
: std::string();
117 std::string
PrivetURLFetcher::GetHostString() {
118 return url_
.GetOrigin().spec();
121 void PrivetURLFetcher::SaveResponseToFile() {
123 make_response_file_
= true;
126 void PrivetURLFetcher::SetByteRange(int start
, int end
) {
128 byte_range_start_
= start
;
129 byte_range_end_
= end
;
130 has_byte_range_
= true;
133 void PrivetURLFetcher::Try() {
135 if (tries_
< kPrivetMaxRetries
) {
136 std::string token
= GetPrivetAccessToken();
139 token
= kXPrivetEmptyToken
;
141 url_fetcher_
.reset(net::URLFetcher::Create(url_
, request_type_
, this));
142 url_fetcher_
->SetRequestContext(request_context_
);
143 url_fetcher_
->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix
) +
145 if (has_byte_range_
) {
146 url_fetcher_
->AddExtraRequestHeader(
147 MakeRangeHeader(byte_range_start_
, byte_range_end_
));
150 if (make_response_file_
) {
151 url_fetcher_
->SaveResponseToTemporaryFile(
152 content::BrowserThread::GetMessageLoopProxyForThread(
153 content::BrowserThread::FILE));
156 // URLFetcher requires us to set upload data for POST requests.
157 if (request_type_
== net::URLFetcher::POST
) {
158 if (!upload_file_path_
.empty()) {
159 url_fetcher_
->SetUploadFilePath(
160 upload_content_type_
,
163 kuint64max
/*length*/,
164 content::BrowserThread::GetMessageLoopProxyForThread(
165 content::BrowserThread::FILE));
167 url_fetcher_
->SetUploadData(upload_content_type_
, upload_data_
);
171 url_fetcher_
->Start();
173 delegate_
->OnError(this, RETRY_ERROR
);
177 void PrivetURLFetcher::Start() {
178 DCHECK_EQ(tries_
, 0); // We haven't called |Start()| yet.
180 if (!send_empty_privet_token_
) {
181 std::string privet_access_token
;
182 privet_access_token
= GetPrivetAccessToken();
183 if (privet_access_token
.empty()) {
184 RequestTokenRefresh();
192 void PrivetURLFetcher::SetUploadData(const std::string
& upload_content_type
,
193 const std::string
& upload_data
) {
194 DCHECK(upload_file_path_
.empty());
195 upload_content_type_
= upload_content_type
;
196 upload_data_
= upload_data
;
199 void PrivetURLFetcher::SetUploadFilePath(
200 const std::string
& upload_content_type
,
201 const base::FilePath
& upload_file_path
) {
202 DCHECK(upload_data_
.empty());
203 upload_content_type_
= upload_content_type
;
204 upload_file_path_
= upload_file_path
;
207 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher
* source
) {
208 if (source
->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE
) {
209 ScheduleRetry(kPrivetTimeoutOnError
);
213 if (!OnURLFetchCompleteDoNotParseData(source
)) {
214 // Byte ranges should only be used when we're not parsing the data
216 DCHECK(!has_byte_range_
);
218 // We should only be saving raw data to a file.
219 DCHECK(!make_response_file_
);
221 OnURLFetchCompleteParseData(source
);
225 // Note that this function returns "true" in error cases to indicate
226 // that it has fully handled the responses.
227 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
228 const net::URLFetcher
* source
) {
229 if (source
->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken
) {
230 RequestTokenRefresh();
234 if (source
->GetResponseCode() != net::HTTP_OK
&&
235 source
->GetResponseCode() != net::HTTP_PARTIAL_CONTENT
) {
236 delegate_
->OnError(this, RESPONSE_CODE_ERROR
);
240 if (make_response_file_
) {
241 base::FilePath response_file_path
;
243 if (!source
->GetResponseAsFilePath(true, &response_file_path
)) {
244 delegate_
->OnError(this, URL_FETCH_ERROR
);
248 return delegate_
->OnRawData(this, true, std::string(), response_file_path
);
250 std::string response_str
;
252 if (!source
->GetResponseAsString(&response_str
)) {
253 delegate_
->OnError(this, URL_FETCH_ERROR
);
257 return delegate_
->OnRawData(this, false, response_str
, base::FilePath());
261 void PrivetURLFetcher::OnURLFetchCompleteParseData(
262 const net::URLFetcher
* source
) {
263 if (source
->GetResponseCode() != net::HTTP_OK
) {
264 delegate_
->OnError(this, RESPONSE_CODE_ERROR
);
268 std::string response_str
;
270 if (!source
->GetResponseAsString(&response_str
)) {
271 delegate_
->OnError(this, URL_FETCH_ERROR
);
275 base::JSONReader
json_reader(base::JSON_ALLOW_TRAILING_COMMAS
);
276 scoped_ptr
<base::Value
> value
;
278 value
.reset(json_reader
.ReadToValue(response_str
));
281 delegate_
->OnError(this, JSON_PARSE_ERROR
);
285 const base::DictionaryValue
* dictionary_value
;
287 if (!value
->GetAsDictionary(&dictionary_value
)) {
288 delegate_
->OnError(this, JSON_PARSE_ERROR
);
293 if (dictionary_value
->GetString(kPrivetKeyError
, &error
)) {
294 if (error
== kPrivetErrorInvalidXPrivetToken
) {
295 RequestTokenRefresh();
297 } else if (PrivetErrorTransient(error
)) {
298 if (!do_not_retry_on_transient_error_
) {
300 if (!dictionary_value
->GetInteger(kPrivetKeyTimeout
,
302 timeout_seconds
= kPrivetDefaultTimeout
;
305 ScheduleRetry(timeout_seconds
);
311 delegate_
->OnParsedJson(this, dictionary_value
,
312 dictionary_value
->HasKey(kPrivetKeyError
));
315 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds
) {
316 double random_scaling_factor
=
317 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition
;
319 int timeout_seconds_randomized
=
320 static_cast<int>(timeout_seconds
* random_scaling_factor
);
322 timeout_seconds_randomized
=
323 std::max(timeout_seconds_randomized
, kPrivetMinimumTimeout
);
325 base::MessageLoop::current()->PostDelayedTask(
327 base::Bind(&PrivetURLFetcher::Try
, weak_factory_
.GetWeakPtr()),
328 base::TimeDelta::FromSeconds(timeout_seconds_randomized
));
331 void PrivetURLFetcher::RequestTokenRefresh() {
332 delegate_
->OnNeedPrivetToken(
334 base::Bind(&PrivetURLFetcher::RefreshToken
, weak_factory_
.GetWeakPtr()));
337 void PrivetURLFetcher::RefreshToken(const std::string
& token
) {
339 delegate_
->OnError(this, TOKEN_ERROR
);
341 SetTokenForHost(GetHostString(), token
);
346 bool PrivetURLFetcher::PrivetErrorTransient(const std::string
& error
) {
347 return (error
== kPrivetErrorDeviceBusy
) ||
348 (error
== kPrivetErrorPendingUserAction
) ||
349 (error
== kPrivetErrorPrinterBusy
);
352 } // namespace local_discovery