privet_url_fetcher.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
21namespace local_discovery {
22
23namespace {
24
25typedef std::map<std::string, std::string> TokenMap;
26
27struct TokenMapHolder {
28 public:
29  static TokenMapHolder* GetInstance() {
30    return Singleton<TokenMapHolder>::get();
31  }
32
33  TokenMap map;
34};
35
36const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: ";
37const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
38const char kXPrivetEmptyToken[] = "\"\"";
39const int kPrivetMaxRetries = 20;
40const int kPrivetTimeoutOnError = 5;
41const int kHTTPErrorCodeInvalidXPrivetToken = 418;
42
43std::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
52void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
53    PrivetURLFetcher* fetcher,
54    const TokenCallback& callback) {
55  OnError(fetcher, TOKEN_ERROR);
56}
57
58bool 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
65PrivetURLFetcher::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      allow_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
83PrivetURLFetcher::~PrivetURLFetcher() {
84}
85
86// static
87void PrivetURLFetcher::SetTokenForHost(const std::string& host,
88                                       const std::string& token) {
89  TokenMapHolder::GetInstance()->map[host] = token;
90}
91
92// static
93void PrivetURLFetcher::ResetTokenMapForTests() {
94  TokenMapHolder::GetInstance()->map.clear();
95}
96
97void PrivetURLFetcher::DoNotRetryOnTransientError() {
98  DCHECK(tries_ == 0);
99  do_not_retry_on_transient_error_ = true;
100}
101
102void PrivetURLFetcher::AllowEmptyPrivetToken() {
103  DCHECK(tries_ == 0);
104  allow_empty_privet_token_ = true;
105}
106
107std::string PrivetURLFetcher::GetPrivetAccessToken() {
108  TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance();
109  TokenMap::iterator found = token_map_holder->map.find(GetHostString());
110  return found != token_map_holder->map.end() ? found->second : std::string();
111}
112
113std::string PrivetURLFetcher::GetHostString() {
114  return url_.GetOrigin().spec();
115}
116
117void PrivetURLFetcher::SaveResponseToFile() {
118  DCHECK(tries_ == 0);
119  make_response_file_ = true;
120}
121
122void PrivetURLFetcher::SetByteRange(int start, int end) {
123  DCHECK(tries_ == 0);
124  byte_range_start_ = start;
125  byte_range_end_ = end;
126  has_byte_range_ = true;
127}
128
129void PrivetURLFetcher::Try() {
130  tries_++;
131  if (tries_ < kPrivetMaxRetries) {
132    std::string token = GetPrivetAccessToken();
133
134    if (token.empty())
135      token = kXPrivetEmptyToken;
136
137    url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
138    url_fetcher_->SetRequestContext(request_context_);
139    url_fetcher_->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix) +
140                                        token);
141    if (has_byte_range_) {
142      url_fetcher_->AddExtraRequestHeader(
143          MakeRangeHeader(byte_range_start_, byte_range_end_));
144    }
145
146    if (make_response_file_) {
147      url_fetcher_->SaveResponseToTemporaryFile(
148          content::BrowserThread::GetMessageLoopProxyForThread(
149              content::BrowserThread::FILE));
150    }
151
152    // URLFetcher requires us to set upload data for POST requests.
153    if (request_type_ == net::URLFetcher::POST) {
154      if (!upload_file_path_.empty()) {
155        url_fetcher_->SetUploadFilePath(
156            upload_content_type_,
157            upload_file_path_,
158            0 /*offset*/,
159            kuint64max /*length*/,
160            content::BrowserThread::GetMessageLoopProxyForThread(
161                content::BrowserThread::FILE));
162      } else {
163        url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
164      }
165    }
166
167    url_fetcher_->Start();
168  } else {
169    delegate_->OnError(this, RETRY_ERROR);
170  }
171}
172
173void PrivetURLFetcher::Start() {
174  DCHECK_EQ(tries_, 0);  // We haven't called |Start()| yet.
175
176  std::string privet_access_token = GetPrivetAccessToken();
177  if (privet_access_token.empty() && !allow_empty_privet_token_) {
178    RequestTokenRefresh();
179  } else {
180    Try();
181  }
182}
183
184void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
185                                     const std::string& upload_data) {
186  DCHECK(upload_file_path_.empty());
187  upload_content_type_ = upload_content_type;
188  upload_data_ = upload_data;
189}
190
191void PrivetURLFetcher::SetUploadFilePath(
192    const std::string& upload_content_type,
193    const base::FilePath& upload_file_path) {
194  DCHECK(upload_data_.empty());
195  upload_content_type_ = upload_content_type;
196  upload_file_path_ = upload_file_path;
197}
198
199void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
200  if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
201    ScheduleRetry(kPrivetTimeoutOnError);
202    return;
203  }
204
205  if (!OnURLFetchCompleteDoNotParseData(source)) {
206    // Byte ranges should only be used when we're not parsing the data
207    // as JSON.
208    DCHECK(!has_byte_range_);
209
210    // We should only be saving raw data to a file.
211    DCHECK(!make_response_file_);
212
213    OnURLFetchCompleteParseData(source);
214  }
215}
216
217// Note that this function returns "true" in error cases to indicate
218// that it has fully handled the responses.
219bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
220    const net::URLFetcher* source) {
221  if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
222    RequestTokenRefresh();
223    return true;
224  }
225
226  if (source->GetResponseCode() != net::HTTP_OK &&
227      source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT) {
228    delegate_->OnError(this, RESPONSE_CODE_ERROR);
229    return true;
230  }
231
232  if (make_response_file_) {
233    base::FilePath response_file_path;
234
235    if (!source->GetResponseAsFilePath(true, &response_file_path)) {
236      delegate_->OnError(this, URL_FETCH_ERROR);
237      return true;
238    }
239
240    return delegate_->OnRawData(this, true, std::string(), response_file_path);
241  } else {
242    std::string response_str;
243
244    if (!source->GetResponseAsString(&response_str)) {
245      delegate_->OnError(this, URL_FETCH_ERROR);
246      return true;
247    }
248
249    return delegate_->OnRawData(this, false, response_str, base::FilePath());
250  }
251}
252
253void PrivetURLFetcher::OnURLFetchCompleteParseData(
254    const net::URLFetcher* source) {
255  if (source->GetResponseCode() != net::HTTP_OK) {
256    delegate_->OnError(this, RESPONSE_CODE_ERROR);
257    return;
258  }
259
260  std::string response_str;
261
262  if (!source->GetResponseAsString(&response_str)) {
263    delegate_->OnError(this, URL_FETCH_ERROR);
264    return;
265  }
266
267  base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
268  scoped_ptr<base::Value> value;
269
270  value.reset(json_reader.ReadToValue(response_str));
271
272  if (!value) {
273    delegate_->OnError(this, JSON_PARSE_ERROR);
274    return;
275  }
276
277  const base::DictionaryValue* dictionary_value;
278
279  if (!value->GetAsDictionary(&dictionary_value)) {
280    delegate_->OnError(this, JSON_PARSE_ERROR);
281    return;
282  }
283
284  std::string error;
285  if (dictionary_value->GetString(kPrivetKeyError, &error)) {
286    if (error == kPrivetErrorInvalidXPrivetToken) {
287      RequestTokenRefresh();
288      return;
289    } else if (PrivetErrorTransient(error)) {
290      if (!do_not_retry_on_transient_error_) {
291        int timeout_seconds;
292        if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
293                                          &timeout_seconds)) {
294          timeout_seconds = kPrivetDefaultTimeout;
295        }
296
297        ScheduleRetry(timeout_seconds);
298        return;
299      }
300    }
301  }
302
303  delegate_->OnParsedJson(this, dictionary_value,
304                          dictionary_value->HasKey(kPrivetKeyError));
305}
306
307void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
308  double random_scaling_factor =
309      1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
310
311  int timeout_seconds_randomized =
312      static_cast<int>(timeout_seconds * random_scaling_factor);
313
314  timeout_seconds_randomized =
315      std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
316
317  base::MessageLoop::current()->PostDelayedTask(
318      FROM_HERE,
319      base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
320      base::TimeDelta::FromSeconds(timeout_seconds_randomized));
321}
322
323void PrivetURLFetcher::RequestTokenRefresh() {
324  delegate_->OnNeedPrivetToken(
325      this,
326      base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
327}
328
329void PrivetURLFetcher::RefreshToken(const std::string& token) {
330  if (token.empty()) {
331    delegate_->OnError(this, TOKEN_ERROR);
332  } else {
333    SetTokenForHost(GetHostString(), token);
334    Try();
335  }
336}
337
338bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
339  return (error == kPrivetErrorDeviceBusy) ||
340         (error == kPrivetErrorPendingUserAction) ||
341         (error == kPrivetErrorPrinterBusy);
342}
343
344}  // namespace local_discovery
345