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/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:
     29   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 
     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 
     52 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
     53     PrivetURLFetcher* fetcher,
     54     const TokenCallback& callback) {
     55   OnError(fetcher, TOKEN_ERROR);
     56 }
     57 
     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 
     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 
     83 PrivetURLFetcher::~PrivetURLFetcher() {
     84 }
     85 
     86 // static
     87 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
     88                                        const std::string& token) {
     89   TokenMapHolder::GetInstance()->map[host] = token;
     90 }
     91 
     92 // static
     93 void PrivetURLFetcher::ResetTokenMapForTests() {
     94   TokenMapHolder::GetInstance()->map.clear();
     95 }
     96 
     97 void PrivetURLFetcher::DoNotRetryOnTransientError() {
     98   DCHECK(tries_ == 0);
     99   do_not_retry_on_transient_error_ = true;
    100 }
    101 
    102 void PrivetURLFetcher::SendEmptyPrivetToken() {
    103   DCHECK(tries_ == 0);
    104   send_empty_privet_token_ = true;
    105 }
    106 
    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 
    117 std::string PrivetURLFetcher::GetHostString() {
    118   return url_.GetOrigin().spec();
    119 }
    120 
    121 void PrivetURLFetcher::SaveResponseToFile() {
    122   DCHECK(tries_ == 0);
    123   make_response_file_ = true;
    124 }
    125 
    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 
    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 
    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 
    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 
    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 
    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.
    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 
    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 
    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 
    331 void PrivetURLFetcher::RequestTokenRefresh() {
    332   delegate_->OnNeedPrivetToken(
    333       this,
    334       base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
    335 }
    336 
    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 
    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