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 kXPrivetAuthTokenHeaderPrefix[] = "X-Privet-Auth: ";
     38 const char kRangeHeaderFormat[] = "Range: bytes=%d-%d";
     39 const char kXPrivetEmptyToken[] = "\"\"";
     40 const char kPrivetAuthTokenUnknown[] = "Unknown";
     41 const int kPrivetMaxRetries = 20;
     42 const int kPrivetTimeoutOnError = 5;
     43 const int kHTTPErrorCodeInvalidXPrivetToken = 418;
     44 
     45 std::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 
     54 void PrivetURLFetcher::Delegate::OnNeedPrivetToken(
     55     PrivetURLFetcher* fetcher,
     56     const TokenCallback& callback) {
     57   OnError(fetcher, TOKEN_ERROR);
     58 }
     59 
     60 std::string PrivetURLFetcher::Delegate::GetAuthToken() {
     61   return kPrivetAuthTokenUnknown;
     62 }
     63 
     64 bool 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 
     71 PrivetURLFetcher::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 
     91 PrivetURLFetcher::~PrivetURLFetcher() {
     92 }
     93 
     94 // static
     95 void PrivetURLFetcher::SetTokenForHost(const std::string& host,
     96                                        const std::string& token) {
     97   TokenMapHolder::GetInstance()->map[host] = token;
     98 }
     99 
    100 // static
    101 void PrivetURLFetcher::ResetTokenMapForTests() {
    102   TokenMapHolder::GetInstance()->map.clear();
    103 }
    104 
    105 void PrivetURLFetcher::DoNotRetryOnTransientError() {
    106   DCHECK(tries_ == 0);
    107   do_not_retry_on_transient_error_ = true;
    108 }
    109 
    110 void PrivetURLFetcher::SendEmptyPrivetToken() {
    111   DCHECK(tries_ == 0);
    112   send_empty_privet_token_ = true;
    113 }
    114 
    115 std::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 
    125 std::string PrivetURLFetcher::GetHostString() {
    126   return url_.GetOrigin().spec();
    127 }
    128 
    129 void PrivetURLFetcher::SaveResponseToFile() {
    130   DCHECK(tries_ == 0);
    131   make_response_file_ = true;
    132 }
    133 
    134 void PrivetURLFetcher::V3Mode() {
    135   v3_mode_ = true;
    136 }
    137 
    138 void 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 
    145 void 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 
    200 void 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 
    215 void 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 
    222 void 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 
    230 void 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.
    250 bool 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 
    284 void 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 
    338 void 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 
    354 void PrivetURLFetcher::RequestTokenRefresh() {
    355   delegate_->OnNeedPrivetToken(
    356       this,
    357       base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr()));
    358 }
    359 
    360 void 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 
    369 bool 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