Home | History | Annotate | Download | only in local_discovery
      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/message_loop/message_loop.h"
     12 #include "base/rand_util.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/local_discovery/privet_constants.h"
     15 #include "content/public/browser/browser_thread.h"
     16 #include "net/http/http_status_code.h"
     17 #include "net/url_request/url_request_status.h"
     18 
     19 namespace local_discovery {
     20 
     21 namespace {
     22 const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: ";
     23 const char kXPrivetEmptyToken[] = "\"\"";
     24 const int kPrivetMaxRetries = 20;
     25 const int kPrivetTimeoutOnError = 5;
     26 }
     27 
     28 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
     29     PrivetURLFetcher* fetcher,
     30     const TokenCallback& callback) {
     31   OnError(fetcher, TOKEN_ERROR);
     32 }
     33 
     34 PrivetURLFetcher::PrivetURLFetcher(
     35     const std::string& token,
     36     const GURL& url,
     37     net::URLFetcher::RequestType request_type,
     38     net::URLRequestContextGetter* request_context,
     39     PrivetURLFetcher::Delegate* delegate)
     40     : privet_access_token_(token), url_(url), request_type_(request_type),
     41       request_context_(request_context), delegate_(delegate),
     42       do_not_retry_on_transient_error_(false), allow_empty_privet_token_(false),
     43       tries_(0), weak_factory_(this) {
     44 }
     45 
     46 PrivetURLFetcher::~PrivetURLFetcher() {
     47 }
     48 
     49 void PrivetURLFetcher::DoNotRetryOnTransientError() {
     50   do_not_retry_on_transient_error_ = true;
     51 }
     52 
     53 void PrivetURLFetcher::AllowEmptyPrivetToken() {
     54   allow_empty_privet_token_ = true;
     55 }
     56 
     57 void PrivetURLFetcher::Try() {
     58   tries_++;
     59   if (tries_ < kPrivetMaxRetries) {
     60     std::string token = privet_access_token_;
     61 
     62     if (token.empty())
     63       token = kXPrivetEmptyToken;
     64 
     65     url_fetcher_.reset(net::URLFetcher::Create(url_, request_type_, this));
     66     url_fetcher_->SetRequestContext(request_context_);
     67     url_fetcher_->AddExtraRequestHeader(std::string(kXPrivetTokenHeaderPrefix) +
     68                                         token);
     69 
     70     // URLFetcher requires us to set upload data for POST requests.
     71     if (request_type_ == net::URLFetcher::POST) {
     72       if (!upload_file_path_.empty()) {
     73         url_fetcher_->SetUploadFilePath(
     74             upload_content_type_,
     75             upload_file_path_,
     76             0 /*offset*/,
     77             kuint64max /*length*/,
     78             content::BrowserThread::GetMessageLoopProxyForThread(
     79                 content::BrowserThread::FILE));
     80       } else {
     81         url_fetcher_->SetUploadData(upload_content_type_, upload_data_);
     82       }
     83     }
     84 
     85     url_fetcher_->Start();
     86   } else {
     87     delegate_->OnError(this, RETRY_ERROR);
     88   }
     89 }
     90 
     91 void PrivetURLFetcher::Start() {
     92   DCHECK_EQ(tries_, 0);  // We haven't called |Start()| yet.
     93 
     94   if (privet_access_token_.empty() && !allow_empty_privet_token_) {
     95     RequestTokenRefresh();
     96   } else {
     97     Try();
     98   }
     99 }
    100 
    101 void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type,
    102                                      const std::string& upload_data) {
    103   DCHECK(upload_file_path_.empty());
    104   upload_content_type_ = upload_content_type;
    105   upload_data_ = upload_data;
    106 }
    107 
    108 void PrivetURLFetcher::SetUploadFilePath(
    109     const std::string& upload_content_type,
    110     const base::FilePath& upload_file_path) {
    111   DCHECK(upload_data_.empty());
    112   upload_content_type_ = upload_content_type;
    113   upload_file_path_ = upload_file_path;
    114 }
    115 
    116 void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
    117   if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS ||
    118       source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE) {
    119     ScheduleRetry(kPrivetTimeoutOnError);
    120     return;
    121   }
    122 
    123   if (source->GetResponseCode() != net::HTTP_OK) {
    124     delegate_->OnError(this, RESPONSE_CODE_ERROR);
    125     return;
    126   }
    127 
    128   std::string response_str;
    129 
    130   if (!source->GetResponseAsString(&response_str)) {
    131     delegate_->OnError(this, URL_FETCH_ERROR);
    132     return;
    133   }
    134 
    135   base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS);
    136   scoped_ptr<base::Value> value;
    137 
    138   value.reset(json_reader.ReadToValue(response_str));
    139 
    140   if (!value) {
    141     delegate_->OnError(this, JSON_PARSE_ERROR);
    142     return;
    143   }
    144 
    145   const base::DictionaryValue* dictionary_value;
    146 
    147   if (!value->GetAsDictionary(&dictionary_value)) {
    148     delegate_->OnError(this, JSON_PARSE_ERROR);
    149     return;
    150   }
    151 
    152   std::string error;
    153   if (dictionary_value->GetString(kPrivetKeyError, &error)) {
    154     if (error == kPrivetErrorInvalidXPrivetToken) {
    155       RequestTokenRefresh();
    156       return;
    157     } else if (PrivetErrorTransient(error)) {
    158       if (!do_not_retry_on_transient_error_) {
    159         int timeout_seconds;
    160         if (!dictionary_value->GetInteger(kPrivetKeyTimeout,
    161                                           &timeout_seconds)) {
    162           timeout_seconds = kPrivetDefaultTimeout;
    163         }
    164 
    165         ScheduleRetry(timeout_seconds);
    166         return;
    167       }
    168     }
    169   }
    170 
    171   delegate_->OnParsedJson(this, dictionary_value,
    172                           dictionary_value->HasKey(kPrivetKeyError));
    173 }
    174 
    175 void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) {
    176   double random_scaling_factor =
    177       1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
    178 
    179   int timeout_seconds_randomized =
    180       static_cast<int>(timeout_seconds * random_scaling_factor);
    181 
    182   timeout_seconds_randomized =
    183       std::max(timeout_seconds_randomized, kPrivetMinimumTimeout);
    184 
    185   base::MessageLoop::current()->PostDelayedTask(
    186       FROM_HERE,
    187       base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()),
    188       base::TimeDelta::FromSeconds(timeout_seconds_randomized));
    189 }
    190 
    191 void PrivetURLFetcher::RequestTokenRefresh() {
    192   delegate_->OnNeedPrivetToken(
    193       this,
    194       base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
    195 }
    196 
    197 void PrivetURLFetcher::RefreshToken(const std::string& token) {
    198   if (token.empty()) {
    199     delegate_->OnError(this, TOKEN_ERROR);
    200   } else {
    201     privet_access_token_ = token;
    202     Try();
    203   }
    204 }
    205 
    206 bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) {
    207   return (error == kPrivetErrorDeviceBusy) ||
    208          (error == kPrivetErrorPendingUserAction) ||
    209          (error == kPrivetErrorPrinterBusy);
    210 }
    211 
    212 PrivetURLFetcherFactory::PrivetURLFetcherFactory(
    213     net::URLRequestContextGetter* request_context)
    214     : request_context_(request_context) {
    215 }
    216 
    217 PrivetURLFetcherFactory::~PrivetURLFetcherFactory() {
    218 }
    219 
    220 scoped_ptr<PrivetURLFetcher> PrivetURLFetcherFactory::CreateURLFetcher(
    221     const GURL& url, net::URLFetcher::RequestType request_type,
    222     PrivetURLFetcher::Delegate* delegate) const {
    223   return scoped_ptr<PrivetURLFetcher>(
    224       new PrivetURLFetcher(token_, url, request_type, request_context_.get(),
    225                            delegate));
    226 }
    227 
    228 }  // namespace local_discovery
    229