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