privet_url_fetcher.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 kXPrivetAuthTokenHeaderPrefix[] = "X-Privet-Auth: ";
38const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
39const char kXPrivetEmptyToken[] = "\"\"";
40const char kPrivetAuthTokenUnknown[] = "Unknown";
41const int kPrivetMaxRetries = 20;
42const int kPrivetTimeoutOnError = 5;
43const int kHTTPErrorCodeInvalidXPrivetToken = 418;
44
45std::string MakeRangeHeader(int start, int end) {
46  DCHECK_GE(start, 0);
47  DCHECK_GT(end, 0);
48  DCHECK_GT(end, start);
49  return base::StringPrintf(kRangeHeaderFormat, start, end);
50}
51
52}  // namespace
53
54void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
55    PrivetURLFetcher* fetcher,
56    const TokenCallback& callback) {
57  OnError(fetcher, TOKEN_ERROR);
58}
59
60std::string PrivetURLFetcher::Delegate::GetAuthToken() {
61  return kPrivetAuthTokenUnknown;
62}
63
64bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher,
65                                           bool response_is_file,
66                                           const std::string& data_string,
67                                           const base::FilePath& data_file) {
68  return false;
69}
70
71PrivetURLFetcher::PrivetURLFetcher(
72    const GURL& url,
73    net::URLFetcher::RequestType request_type,
74    net::URLRequestContextGetter* request_context,
75    PrivetURLFetcher::Delegate* delegate)
76    : url_(url),
77      request_type_(request_type),
78      request_context_(request_context),
79      delegate_(delegate),
80      do_not_retry_on_transient_error_(false),
81      send_empty_privet_token_(false),
82      has_byte_range_(false),
83      make_response_file_(false),
84      v3_mode_(false),
85      byte_range_start_(0),
86      byte_range_end_(0),
87      tries_(0),
88      weak_factory_(this) {
89}
90
91PrivetURLFetcher::~PrivetURLFetcher() {
92}
93
94// static
95void PrivetURLFetcher::SetTokenForHost(const std::string& host,
96                                       const std::string& token) {
97  TokenMapHolder::GetInstance()->map[host] = token;
98}
99
100// static
101void PrivetURLFetcher::ResetTokenMapForTests() {
102  TokenMapHolder::GetInstance()->map.clear();
103}
104
105void PrivetURLFetcher::DoNotRetryOnTransientError() {
106  DCHECK(tries_ == 0);
107  do_not_retry_on_transient_error_ = true;
108}
109
110void PrivetURLFetcher::SendEmptyPrivetToken() {
111  DCHECK(tries_ == 0);
112  send_empty_privet_token_ = true;
113}
114
115std::string PrivetURLFetcher::GetPrivetAccessToken() {
116  if (send_empty_privet_token_) {
117    return std::string();
118  }
119
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();
123}
124
125std::string PrivetURLFetcher::GetHostString() {
126  return url_.GetOrigin().spec();
127}
128
129void PrivetURLFetcher::SaveResponseToFile() {
130  DCHECK(tries_ == 0);
131  make_response_file_ = true;
132}
133
134void PrivetURLFetcher::V3Mode() {
135  v3_mode_ = true;
136}
137
138void PrivetURLFetcher::SetByteRange(int start, int end) {
139  DCHECK(tries_ == 0);
140  byte_range_start_ = start;
141  byte_range_end_ = end;
142  has_byte_range_ = true;
143}
144
145void PrivetURLFetcher::Try() {
146  tries_++;
147  if (tries_ < kPrivetMaxRetries) {
148
149
150    url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
151    url_fetcher_->SetRequestContext(request_context_.get());
152
153    if (v3_mode_) {
154      std::string auth_token = delegate_->GetAuthToken();
155
156      url_fetcher_->AddExtraRequestHeader(
157          std::string(kXPrivetAuthTokenHeaderPrefix) + auth_token);
158    } else {
159      std::string token = GetPrivetAccessToken();
160
161      if (token.empty())
162        token = kXPrivetEmptyToken;
163
164      url_fetcher_->AddExtraRequestHeader(
165          std::string(kXPrivetTokenHeaderPrefix) + token);
166    }
167
168    if (has_byte_range_) {
169      url_fetcher_->AddExtraRequestHeader(
170          MakeRangeHeader(byte_range_start_, byte_range_end_));
171    }
172
173    if (make_response_file_) {
174      url_fetcher_->SaveResponseToTemporaryFile(
175          content::BrowserThread::GetMessageLoopProxyForThread(
176              content::BrowserThread::FILE));
177    }
178
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_,
184            upload_file_path_,
185            0 /*offset*/,
186            kuint64max /*length*/,
187            content::BrowserThread::GetMessageLoopProxyForThread(
188                content::BrowserThread::FILE));
189      } else {
190        url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
191      }
192    }
193
194    url_fetcher_->Start();
195  } else {
196    delegate_->OnError(this, RETRY_ERROR);
197  }
198}
199
200void PrivetURLFetcher::Start() {
201  DCHECK_EQ(tries_, 0);  // We haven't called |Start()| yet.
202
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();
208      return;
209    }
210  }
211
212  Try();
213}
214
215void 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;
220}
221
222void 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;
228}
229
230void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
231  if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
232    ScheduleRetry(kPrivetTimeoutOnError);
233    return;
234  }
235
236  if (!OnURLFetchCompleteDoNotParseData(source)) {
237    // Byte ranges should only be used when we're not parsing the data
238    // as JSON.
239    DCHECK(!has_byte_range_);
240
241    // We should only be saving raw data to a file.
242    DCHECK(!make_response_file_);
243
244    OnURLFetchCompleteParseData(source);
245  }
246}
247
248// Note that this function returns "true" in error cases to indicate
249// that it has fully handled the responses.
250bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData(
251    const net::URLFetcher* source) {
252  if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) {
253    RequestTokenRefresh();
254    return true;
255  }
256
257  if (source->GetResponseCode() != net::HTTP_OK &&
258      source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT) {
259    delegate_->OnError(this, RESPONSE_CODE_ERROR);
260    return true;
261  }
262
263  if (make_response_file_) {
264    base::FilePath response_file_path;
265
266    if (!source->GetResponseAsFilePath(true, &response_file_path)) {
267      delegate_->OnError(this, URL_FETCH_ERROR);
268      return true;
269    }
270
271    return delegate_->OnRawData(this, true, std::string(), response_file_path);
272  } else {
273    std::string response_str;
274
275    if (!source->GetResponseAsString(&response_str)) {
276      delegate_->OnError(this, URL_FETCH_ERROR);
277      return true;
278    }
279
280    return delegate_->OnRawData(this, false, response_str, base::FilePath());
281  }
282}
283
284void PrivetURLFetcher::OnURLFetchCompleteParseData(
285    const net::URLFetcher* source) {
286  if (source->GetResponseCode() != net::HTTP_OK) {
287    delegate_->OnError(this, RESPONSE_CODE_ERROR);
288    return;
289  }
290
291  std::string response_str;
292
293  if (!source->GetResponseAsString(&response_str)) {
294    delegate_->OnError(this, URL_FETCH_ERROR);
295    return;
296  }
297
298  base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
299  scoped_ptr<base::Value> value;
300
301  value.reset(json_reader.ReadToValue(response_str));
302
303  if (!value) {
304    delegate_->OnError(this, JSON_PARSE_ERROR);
305    return;
306  }
307
308  const base::DictionaryValue* dictionary_value = NULL;
309
310  if (!value->GetAsDictionary(&dictionary_value)) {
311    delegate_->OnError(this, JSON_PARSE_ERROR);
312    return;
313  }
314
315  std::string error;
316  if (dictionary_value->GetString(kPrivetKeyError, &error)) {
317    if (error == kPrivetErrorInvalidXPrivetToken) {
318      RequestTokenRefresh();
319      return;
320    } else if (PrivetErrorTransient(error)) {
321      if (!do_not_retry_on_transient_error_) {
322        int timeout_seconds;
323        if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
324                                          &timeout_seconds)) {
325          timeout_seconds = kPrivetDefaultTimeout;
326        }
327
328        ScheduleRetry(timeout_seconds);
329        return;
330      }
331    }
332  }
333
334  delegate_->OnParsedJson(
335      this, *dictionary_value, dictionary_value->HasKey(kPrivetKeyError));
336}
337
338void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
339  double random_scaling_factor =
340      1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
341
342  int timeout_seconds_randomized =
343      static_cast<int>(timeout_seconds * random_scaling_factor);
344
345  timeout_seconds_randomized =
346      std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
347
348  base::MessageLoop::current()->PostDelayedTask(
349      FROM_HERE,
350      base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
351      base::TimeDelta::FromSeconds(timeout_seconds_randomized));
352}
353
354void PrivetURLFetcher::RequestTokenRefresh() {
355  delegate_->OnNeedPrivetToken(
356      this,
357      base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
358}
359
360void PrivetURLFetcher::RefreshToken(const std::string& token) {
361  if (token.empty()) {
362    delegate_->OnError(this, TOKEN_ERROR);
363  } else {
364    SetTokenForHost(GetHostString(), token);
365    Try();
366  }
367}
368
369bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
370  return (error == kPrivetErrorDeviceBusy) ||
371         (error == kPrivetV3ErrorDeviceBusy) ||
372         (error == kPrivetErrorPendingUserAction) ||
373         (error == kPrivetErrorPrinterBusy);
374}
375
376}  // namespace local_discovery
377