• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <algorithm>
8 
9 #include "base/bind.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"
20 
21 namespace local_discovery {
22 
23 namespace {
24 
25 typedef std::map<std::string, std::string> TokenMap;
26 
27 struct TokenMapHolder {
28  public:
GetInstancelocal_discovery::__anon87dd07200111::TokenMapHolder29   static TokenMapHolder* GetInstance() {
30     return Singleton<TokenMapHolder>::get();
31   }
32 
33   TokenMap map;
34 };
35 
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;
42 
MakeRangeHeader(int start,int end)43 std::string MakeRangeHeader(int start, int end) {
44   DCHECK_GE(start, 0);
45   DCHECK_GT(end, 0);
46   DCHECK_GT(end, start);
47   return base::StringPrintf(kRangeHeaderFormat, start, end);
48 }
49 
50 }  // namespace
51 
OnNeedPrivetToken(PrivetURLFetcher * fetcher,const TokenCallback & callback)52 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
53     PrivetURLFetcher* fetcher,
54     const TokenCallback& callback) {
55   OnError(fetcher, TOKEN_ERROR);
56 }
57 
OnRawData(PrivetURLFetcher * fetcher,bool response_is_file,const std::string & data_string,const base::FilePath & data_file)58 bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher,
59                                            bool response_is_file,
60                                            const std::string& data_string,
61                                            const base::FilePath& data_file) {
62   return false;
63 }
64 
PrivetURLFetcher(const GURL & url,net::URLFetcher::RequestType request_type,net::URLRequestContextGetter * request_context,PrivetURLFetcher::Delegate * delegate)65 PrivetURLFetcher::PrivetURLFetcher(
66     const GURL& url,
67     net::URLFetcher::RequestType request_type,
68     net::URLRequestContextGetter* request_context,
69     PrivetURLFetcher::Delegate* delegate)
70     : url_(url),
71       request_type_(request_type),
72       request_context_(request_context),
73       delegate_(delegate),
74       do_not_retry_on_transient_error_(false),
75       send_empty_privet_token_(false),
76       has_byte_range_(false),
77       make_response_file_(false),
78       byte_range_start_(0),
79       byte_range_end_(0),
80       tries_(0),
81       weak_factory_(this) {}
82 
~PrivetURLFetcher()83 PrivetURLFetcher::~PrivetURLFetcher() {
84 }
85 
86 // static
SetTokenForHost(const std::string & host,const std::string & token)87 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
88                                        const std::string& token) {
89   TokenMapHolder::GetInstance()->map[host] = token;
90 }
91 
92 // static
ResetTokenMapForTests()93 void PrivetURLFetcher::ResetTokenMapForTests() {
94   TokenMapHolder::GetInstance()->map.clear();
95 }
96 
DoNotRetryOnTransientError()97 void PrivetURLFetcher::DoNotRetryOnTransientError() {
98   DCHECK(tries_ == 0);
99   do_not_retry_on_transient_error_ = true;
100 }
101 
SendEmptyPrivetToken()102 void PrivetURLFetcher::SendEmptyPrivetToken() {
103   DCHECK(tries_ == 0);
104   send_empty_privet_token_ = true;
105 }
106 
GetPrivetAccessToken()107 std::string PrivetURLFetcher::GetPrivetAccessToken() {
108   if (send_empty_privet_token_) {
109     return std::string();
110   }
111 
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();
115 }
116 
GetHostString()117 std::string PrivetURLFetcher::GetHostString() {
118   return url_.GetOrigin().spec();
119 }
120 
SaveResponseToFile()121 void PrivetURLFetcher::SaveResponseToFile() {
122   DCHECK(tries_ == 0);
123   make_response_file_ = true;
124 }
125 
SetByteRange(int start,int end)126 void PrivetURLFetcher::SetByteRange(int start, int end) {
127   DCHECK(tries_ == 0);
128   byte_range_start_ = start;
129   byte_range_end_ = end;
130   has_byte_range_ = true;
131 }
132 
Try()133 void PrivetURLFetcher::Try() {
134   tries_++;
135   if (tries_ < kPrivetMaxRetries) {
136     std::string token = GetPrivetAccessToken();
137 
138     if (token.empty())
139       token = kXPrivetEmptyToken;
140 
141     url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
142     url_fetcher_->SetRequestContext(request_context_);
143     url_fetcher_->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix) +
144                                         token);
145     if (has_byte_range_) {
146       url_fetcher_->AddExtraRequestHeader(
147           MakeRangeHeader(byte_range_start_, byte_range_end_));
148     }
149 
150     if (make_response_file_) {
151       url_fetcher_->SaveResponseToTemporaryFile(
152           content::BrowserThread::GetMessageLoopProxyForThread(
153               content::BrowserThread::FILE));
154     }
155 
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_,
161             upload_file_path_,
162             0 /*offset*/,
163             kuint64max /*length*/,
164             content::BrowserThread::GetMessageLoopProxyForThread(
165                 content::BrowserThread::FILE));
166       } else {
167         url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
168       }
169     }
170 
171     url_fetcher_->Start();
172   } else {
173     delegate_->OnError(this, RETRY_ERROR);
174   }
175 }
176 
Start()177 void PrivetURLFetcher::Start() {
178   DCHECK_EQ(tries_, 0);  // We haven't called |Start()| yet.
179 
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();
185       return;
186     }
187   }
188 
189   Try();
190 }
191 
SetUploadData(const std::string & upload_content_type,const std::string & upload_data)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;
197 }
198 
SetUploadFilePath(const std::string & upload_content_type,const base::FilePath & upload_file_path)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;
205 }
206 
OnURLFetchComplete(const net::URLFetcher * source)207 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
208   if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
209     ScheduleRetry(kPrivetTimeoutOnError);
210     return;
211   }
212 
213   if (!OnURLFetchCompleteDoNotParseData(source)) {
214     // Byte ranges should only be used when we're not parsing the data
215     // as JSON.
216     DCHECK(!has_byte_range_);
217 
218     // We should only be saving raw data to a file.
219     DCHECK(!make_response_file_);
220 
221     OnURLFetchCompleteParseData(source);
222   }
223 }
224 
225 // Note that this function returns "true" in error cases to indicate
226 // that it has fully handled the responses.
OnURLFetchCompleteDoNotParseData(const net::URLFetcher * source)227 bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
228     const net::URLFetcher* source) {
229   if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
230     RequestTokenRefresh();
231     return true;
232   }
233 
234   if (source->GetResponseCode() != net::HTTP_OK &&
235       source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT) {
236     delegate_->OnError(this, RESPONSE_CODE_ERROR);
237     return true;
238   }
239 
240   if (make_response_file_) {
241     base::FilePath response_file_path;
242 
243     if (!source->GetResponseAsFilePath(true, &response_file_path)) {
244       delegate_->OnError(this, URL_FETCH_ERROR);
245       return true;
246     }
247 
248     return delegate_->OnRawData(this, true, std::string(), response_file_path);
249   } else {
250     std::string response_str;
251 
252     if (!source->GetResponseAsString(&response_str)) {
253       delegate_->OnError(this, URL_FETCH_ERROR);
254       return true;
255     }
256 
257     return delegate_->OnRawData(this, false, response_str, base::FilePath());
258   }
259 }
260 
OnURLFetchCompleteParseData(const net::URLFetcher * source)261 void PrivetURLFetcher::OnURLFetchCompleteParseData(
262     const net::URLFetcher* source) {
263   if (source->GetResponseCode() != net::HTTP_OK) {
264     delegate_->OnError(this, RESPONSE_CODE_ERROR);
265     return;
266   }
267 
268   std::string response_str;
269 
270   if (!source->GetResponseAsString(&response_str)) {
271     delegate_->OnError(this, URL_FETCH_ERROR);
272     return;
273   }
274 
275   base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
276   scoped_ptr<base::Value> value;
277 
278   value.reset(json_reader.ReadToValue(response_str));
279 
280   if (!value) {
281     delegate_->OnError(this, JSON_PARSE_ERROR);
282     return;
283   }
284 
285   const base::DictionaryValue* dictionary_value;
286 
287   if (!value->GetAsDictionary(&dictionary_value)) {
288     delegate_->OnError(this, JSON_PARSE_ERROR);
289     return;
290   }
291 
292   std::string error;
293   if (dictionary_value->GetString(kPrivetKeyError, &error)) {
294     if (error == kPrivetErrorInvalidXPrivetToken) {
295       RequestTokenRefresh();
296       return;
297     } else if (PrivetErrorTransient(error)) {
298       if (!do_not_retry_on_transient_error_) {
299         int timeout_seconds;
300         if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
301                                           &timeout_seconds)) {
302           timeout_seconds = kPrivetDefaultTimeout;
303         }
304 
305         ScheduleRetry(timeout_seconds);
306         return;
307       }
308     }
309   }
310 
311   delegate_->OnParsedJson(this, dictionary_value,
312                           dictionary_value->HasKey(kPrivetKeyError));
313 }
314 
ScheduleRetry(int timeout_seconds)315 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
316   double random_scaling_factor =
317       1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
318 
319   int timeout_seconds_randomized =
320       static_cast<int>(timeout_seconds * random_scaling_factor);
321 
322   timeout_seconds_randomized =
323       std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
324 
325   base::MessageLoop::current()->PostDelayedTask(
326       FROM_HERE,
327       base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
328       base::TimeDelta::FromSeconds(timeout_seconds_randomized));
329 }
330 
RequestTokenRefresh()331 void PrivetURLFetcher::RequestTokenRefresh() {
332   delegate_->OnNeedPrivetToken(
333       this,
334       base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
335 }
336 
RefreshToken(const std::string & token)337 void PrivetURLFetcher::RefreshToken(const std::string& token) {
338   if (token.empty()) {
339     delegate_->OnError(this, TOKEN_ERROR);
340   } else {
341     SetTokenForHost(GetHostString(), token);
342     Try();
343   }
344 }
345 
PrivetErrorTransient(const std::string & error)346 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
347   return (error == kPrivetErrorDeviceBusy) ||
348          (error == kPrivetErrorPendingUserAction) ||
349          (error == kPrivetErrorPrinterBusy);
350 }
351 
352 }  // namespace local_discovery
353