OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/local_discovery/privet_url_fetcher.h" | |
6 | |
7 #include <stdint.h> | |
8 | |
9 #include <algorithm> | |
10 #include <limits> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/debug/dump_without_crashing.h" | |
14 #include "base/json/json_reader.h" | |
15 #include "base/location.h" | |
16 #include "base/memory/singleton.h" | |
17 #include "base/rand_util.h" | |
18 #include "base/single_thread_task_runner.h" | |
19 #include "base/strings/stringprintf.h" | |
20 #include "base/thread_task_runner_handle.h" | |
21 #include "chrome/browser/browser_process.h" | |
22 #include "chrome/browser/local_discovery/privet_constants.h" | |
23 #include "content/public/browser/browser_thread.h" | |
24 #include "net/base/load_flags.h" | |
25 #include "net/http/http_status_code.h" | |
26 #include "net/url_request/url_request_context.h" | |
27 #include "net/url_request/url_request_status.h" | |
28 | |
29 namespace local_discovery { | |
30 | |
31 namespace { | |
32 | |
33 typedef std::map<std::string, std::string> TokenMap; | |
34 | |
35 struct TokenMapHolder { | |
36 public: | |
37 static TokenMapHolder* GetInstance() { | |
38 return base::Singleton<TokenMapHolder>::get(); | |
39 } | |
40 | |
41 TokenMap map; | |
42 }; | |
43 | |
44 const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: "; | |
45 const char kRangeHeaderFormat[] = "Range: bytes=%d-%d"; | |
46 const char kXPrivetEmptyToken[] = "\"\""; | |
47 const int kPrivetMaxRetries = 20; | |
48 const int kPrivetTimeoutOnError = 5; | |
49 const int kHTTPErrorCodeInvalidXPrivetToken = 418; | |
50 | |
51 std::string MakeRangeHeader(int start, int end) { | |
52 DCHECK_GE(start, 0); | |
53 DCHECK_GT(end, 0); | |
54 DCHECK_GT(end, start); | |
55 return base::StringPrintf(kRangeHeaderFormat, start, end); | |
56 } | |
57 | |
58 } // namespace | |
59 | |
60 void PrivetURLFetcher::Delegate::OnNeedPrivetToken( | |
61 PrivetURLFetcher* fetcher, | |
62 const TokenCallback& callback) { | |
63 OnError(fetcher, TOKEN_ERROR); | |
64 } | |
65 | |
66 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher, | |
67 bool response_is_file, | |
68 const std::string& data_string, | |
69 const base::FilePath& data_file) { | |
70 return false; | |
71 } | |
72 | |
73 PrivetURLFetcher::PrivetURLFetcher( | |
74 const GURL& url, | |
75 net::URLFetcher::RequestType request_type, | |
76 const scoped_refptr<net::URLRequestContextGetter>& context_getter, | |
77 PrivetURLFetcher::Delegate* delegate) | |
78 : url_(url), | |
79 request_type_(request_type), | |
80 context_getter_(context_getter), | |
81 delegate_(delegate), | |
82 max_retries_(kPrivetMaxRetries), | |
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 byte_range_start_(0), | |
88 byte_range_end_(0), | |
89 tries_(0), | |
90 weak_factory_(this) {} | |
91 | |
92 PrivetURLFetcher::~PrivetURLFetcher() { | |
93 } | |
94 | |
95 // static | |
96 void PrivetURLFetcher::SetTokenForHost(const std::string& host, | |
97 const std::string& token) { | |
98 TokenMapHolder::GetInstance()->map[host] = token; | |
99 } | |
100 | |
101 // static | |
102 void PrivetURLFetcher::ResetTokenMapForTests() { | |
103 TokenMapHolder::GetInstance()->map.clear(); | |
104 } | |
105 | |
106 void PrivetURLFetcher::SetMaxRetries(int max_retries) { | |
107 DCHECK_EQ(tries_, 0); | |
108 max_retries_ = max_retries; | |
109 } | |
110 | |
111 void PrivetURLFetcher::DoNotRetryOnTransientError() { | |
112 DCHECK_EQ(tries_, 0); | |
113 do_not_retry_on_transient_error_ = true; | |
114 } | |
115 | |
116 void PrivetURLFetcher::SendEmptyPrivetToken() { | |
117 DCHECK_EQ(tries_, 0); | |
118 send_empty_privet_token_ = true; | |
119 } | |
120 | |
121 std::string PrivetURLFetcher::GetPrivetAccessToken() { | |
122 if (send_empty_privet_token_) { | |
123 return std::string(); | |
124 } | |
125 | |
126 TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance(); | |
127 TokenMap::iterator found = token_map_holder->map.find(GetHostString()); | |
128 return found != token_map_holder->map.end() ? found->second : std::string(); | |
129 } | |
130 | |
131 std::string PrivetURLFetcher::GetHostString() { | |
132 return url_.GetOrigin().spec(); | |
133 } | |
134 | |
135 void PrivetURLFetcher::SaveResponseToFile() { | |
136 DCHECK_EQ(tries_, 0); | |
137 make_response_file_ = true; | |
138 } | |
139 | |
140 void PrivetURLFetcher::SetByteRange(int start, int end) { | |
141 DCHECK_EQ(tries_, 0); | |
142 byte_range_start_ = start; | |
143 byte_range_end_ = end; | |
144 has_byte_range_ = true; | |
145 } | |
146 | |
147 void PrivetURLFetcher::Try() { | |
148 tries_++; | |
149 if (tries_ <= max_retries_) { | |
150 DVLOG(1) << "Attempt: " << tries_; | |
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( | |
154 url_fetcher_->GetLoadFlags() | net::LOAD_BYPASS_PROXY | | |
155 net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SEND_COOKIES); | |
156 url_fetcher_->SetRequestContext(context_getter_.get()); | |
157 | |
158 std::string token = GetPrivetAccessToken(); | |
159 | |
160 if (token.empty()) | |
161 token = kXPrivetEmptyToken; | |
162 | |
163 url_fetcher_->AddExtraRequestHeader( | |
164 std::string(kXPrivetTokenHeaderPrefix) + token); | |
165 | |
166 if (has_byte_range_) { | |
167 url_fetcher_->AddExtraRequestHeader( | |
168 MakeRangeHeader(byte_range_start_, byte_range_end_)); | |
169 } | |
170 | |
171 if (make_response_file_) { | |
172 url_fetcher_->SaveResponseToTemporaryFile( | |
173 content::BrowserThread::GetMessageLoopProxyForThread( | |
174 content::BrowserThread::FILE)); | |
175 } | |
176 | |
177 // URLFetcher requires us to set upload data for POST requests. | |
178 if (request_type_ == net::URLFetcher::POST) { | |
179 if (!upload_file_path_.empty()) { | |
180 url_fetcher_->SetUploadFilePath( | |
181 upload_content_type_, upload_file_path_, 0 /*offset*/, | |
182 std::numeric_limits<uint64_t>::max() /*length*/, | |
183 content::BrowserThread::GetMessageLoopProxyForThread( | |
184 content::BrowserThread::FILE)); | |
185 } else { | |
186 url_fetcher_->SetUploadData(upload_content_type_, upload_data_); | |
187 } | |
188 } | |
189 | |
190 url_fetcher_->Start(); | |
191 } else { | |
192 delegate_->OnError(this, UNKNOWN_ERROR); | |
193 } | |
194 } | |
195 | |
196 void PrivetURLFetcher::Start() { | |
197 DCHECK_EQ(tries_, 0); // We haven't called |Start()| yet. | |
198 | |
199 if (!url_.is_valid()) { | |
200 // Not yet clear why it's possible. crbug.com/513505 | |
201 base::debug::DumpWithoutCrashing(); | |
202 return delegate_->OnError(this, UNKNOWN_ERROR); | |
203 } | |
204 | |
205 if (!send_empty_privet_token_) { | |
206 std::string privet_access_token; | |
207 privet_access_token = GetPrivetAccessToken(); | |
208 if (privet_access_token.empty()) { | |
209 RequestTokenRefresh(); | |
210 return; | |
211 } | |
212 } | |
213 | |
214 Try(); | |
215 } | |
216 | |
217 void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type, | |
218 const std::string& upload_data) { | |
219 DCHECK(upload_file_path_.empty()); | |
220 upload_content_type_ = upload_content_type; | |
221 upload_data_ = upload_data; | |
222 } | |
223 | |
224 void PrivetURLFetcher::SetUploadFilePath( | |
225 const std::string& upload_content_type, | |
226 const base::FilePath& upload_file_path) { | |
227 DCHECK(upload_data_.empty()); | |
228 upload_content_type_ = upload_content_type; | |
229 upload_file_path_ = upload_file_path; | |
230 } | |
231 | |
232 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) { | |
233 DVLOG(1) << "Status: " << source->GetStatus().status() | |
234 << ", ResponseCode: " << source->GetResponseCode(); | |
235 if (source->GetStatus().status() != net::URLRequestStatus::CANCELED && | |
236 (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE || | |
237 source->GetResponseCode() == net::URLFetcher::RESPONSE_CODE_INVALID)) { | |
238 ScheduleRetry(kPrivetTimeoutOnError); | |
239 return; | |
240 } | |
241 | |
242 if (!OnURLFetchCompleteDoNotParseData(source)) { | |
243 // Byte ranges should only be used when we're not parsing the data | |
244 // as JSON. | |
245 DCHECK(!has_byte_range_); | |
246 | |
247 // We should only be saving raw data to a file. | |
248 DCHECK(!make_response_file_); | |
249 | |
250 OnURLFetchCompleteParseData(source); | |
251 } | |
252 } | |
253 | |
254 // Note that this function returns "true" in error cases to indicate | |
255 // that it has fully handled the responses. | |
256 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData( | |
257 const net::URLFetcher* source) { | |
258 if (source->GetStatus().status() == net::URLRequestStatus::CANCELED) { | |
259 delegate_->OnError(this, REQUEST_CANCELED); | |
260 return true; | |
261 } | |
262 | |
263 if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) { | |
264 RequestTokenRefresh(); | |
265 return true; | |
266 } | |
267 | |
268 if (source->GetResponseCode() != net::HTTP_OK && | |
269 source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT && | |
270 source->GetResponseCode() != net::HTTP_BAD_REQUEST) { | |
271 delegate_->OnError(this, RESPONSE_CODE_ERROR); | |
272 return true; | |
273 } | |
274 | |
275 if (make_response_file_) { | |
276 base::FilePath response_file_path; | |
277 | |
278 if (!source->GetResponseAsFilePath(true, &response_file_path)) { | |
279 delegate_->OnError(this, UNKNOWN_ERROR); | |
280 return true; | |
281 } | |
282 | |
283 return delegate_->OnRawData(this, true, std::string(), response_file_path); | |
284 } else { | |
285 std::string response_str; | |
286 | |
287 if (!source->GetResponseAsString(&response_str)) { | |
288 delegate_->OnError(this, UNKNOWN_ERROR); | |
289 return true; | |
290 } | |
291 | |
292 return delegate_->OnRawData(this, false, response_str, base::FilePath()); | |
293 } | |
294 } | |
295 | |
296 void PrivetURLFetcher::OnURLFetchCompleteParseData( | |
297 const net::URLFetcher* source) { | |
298 // Response contains error description. | |
299 bool is_error_response = false; | |
300 if (source->GetResponseCode() != net::HTTP_OK) { | |
301 delegate_->OnError(this, RESPONSE_CODE_ERROR); | |
302 return; | |
303 } | |
304 | |
305 std::string response_str; | |
306 if (!source->GetResponseAsString(&response_str)) { | |
307 delegate_->OnError(this, UNKNOWN_ERROR); | |
308 return; | |
309 } | |
310 | |
311 base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS); | |
312 scoped_ptr<base::Value> value = json_reader.ReadToValue(response_str); | |
313 if (!value) { | |
314 delegate_->OnError(this, JSON_PARSE_ERROR); | |
315 return; | |
316 } | |
317 | |
318 const base::DictionaryValue* dictionary_value = NULL; | |
319 | |
320 if (!value->GetAsDictionary(&dictionary_value)) { | |
321 delegate_->OnError(this, JSON_PARSE_ERROR); | |
322 return; | |
323 } | |
324 | |
325 std::string error; | |
326 if (dictionary_value->GetString(kPrivetKeyError, &error)) { | |
327 if (error == kPrivetErrorInvalidXPrivetToken) { | |
328 RequestTokenRefresh(); | |
329 return; | |
330 } else if (PrivetErrorTransient(error)) { | |
331 if (!do_not_retry_on_transient_error_) { | |
332 int timeout_seconds; | |
333 if (!dictionary_value->GetInteger(kPrivetKeyTimeout, | |
334 &timeout_seconds)) { | |
335 timeout_seconds = kPrivetDefaultTimeout; | |
336 } | |
337 | |
338 ScheduleRetry(timeout_seconds); | |
339 return; | |
340 } | |
341 } | |
342 is_error_response = true; | |
343 } | |
344 | |
345 delegate_->OnParsedJson(this, *dictionary_value, is_error_response); | |
346 } | |
347 | |
348 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) { | |
349 double random_scaling_factor = | |
350 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition; | |
351 | |
352 int timeout_seconds_randomized = | |
353 static_cast<int>(timeout_seconds * random_scaling_factor); | |
354 | |
355 timeout_seconds_randomized = | |
356 std::max(timeout_seconds_randomized, kPrivetMinimumTimeout); | |
357 | |
358 // Don't wait because only error callback is going to be called. | |
359 if (tries_ >= max_retries_) | |
360 timeout_seconds_randomized = 0; | |
361 | |
362 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
363 FROM_HERE, base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()), | |
364 base::TimeDelta::FromSeconds(timeout_seconds_randomized)); | |
365 } | |
366 | |
367 void PrivetURLFetcher::RequestTokenRefresh() { | |
368 delegate_->OnNeedPrivetToken( | |
369 this, | |
370 base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr())); | |
371 } | |
372 | |
373 void PrivetURLFetcher::RefreshToken(const std::string& token) { | |
374 if (token.empty()) { | |
375 delegate_->OnError(this, TOKEN_ERROR); | |
376 } else { | |
377 SetTokenForHost(GetHostString(), token); | |
378 Try(); | |
379 } | |
380 } | |
381 | |
382 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) { | |
383 return (error == kPrivetErrorDeviceBusy) || | |
384 (error == kPrivetV3ErrorDeviceBusy) || | |
385 (error == kPrivetErrorPendingUserAction) || | |
386 (error == kPrivetErrorPrinterBusy); | |
387 } | |
388 | |
389 } // namespace local_discovery | |
OLD | NEW |