// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/local_discovery/privet_url_fetcher.h" #include #include "base/bind.h" #include "base/json/json_reader.h" #include "base/location.h" #include "base/memory/singleton.h" #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/strings/stringprintf.h" #include "base/thread_task_runner_handle.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/local_discovery/privet_constants.h" #include "content/public/browser/browser_thread.h" #include "net/base/load_flags.h" #include "net/http/http_status_code.h" #include "net/url_request/url_request_status.h" namespace local_discovery { namespace { typedef std::map TokenMap; struct TokenMapHolder { public: static TokenMapHolder* GetInstance() { return Singleton::get(); } TokenMap map; }; const char kXPrivetTokenHeaderPrefix[] = "X-Privet-Token: "; const char kPrivetV3AuthTokenHeaderPrefix[] = "Authorization: "; const char kRangeHeaderFormat[] = "Range: bytes=%d-%d"; const char kXPrivetEmptyToken[] = "\"\""; const char kPrivetAuthTokenUnknown[] = "Unknown"; const int kPrivetMaxRetries = 20; const int kPrivetTimeoutOnError = 5; const int kHTTPErrorCodeInvalidXPrivetToken = 418; std::string MakeRangeHeader(int start, int end) { DCHECK_GE(start, 0); DCHECK_GT(end, 0); DCHECK_GT(end, start); return base::StringPrintf(kRangeHeaderFormat, start, end); } } // namespace void PrivetURLFetcher::Delegate::OnNeedPrivetToken( PrivetURLFetcher* fetcher, const TokenCallback& callback) { OnError(fetcher, TOKEN_ERROR); } std::string PrivetURLFetcher::Delegate::GetAuthToken() { return kPrivetAuthTokenUnknown; } bool PrivetURLFetcher::Delegate::OnRawData(PrivetURLFetcher* fetcher, bool response_is_file, const std::string& data_string, const base::FilePath& data_file) { return false; } PrivetURLFetcher::PrivetURLFetcher( const GURL& url, net::URLFetcher::RequestType request_type, net::URLRequestContextGetter* request_context, PrivetURLFetcher::Delegate* delegate) : url_(url), request_type_(request_type), request_context_(request_context), delegate_(delegate), do_not_retry_on_transient_error_(false), send_empty_privet_token_(false), has_byte_range_(false), make_response_file_(false), v3_mode_(false), byte_range_start_(0), byte_range_end_(0), tries_(0), weak_factory_(this) { } PrivetURLFetcher::~PrivetURLFetcher() { } // static void PrivetURLFetcher::SetTokenForHost(const std::string& host, const std::string& token) { TokenMapHolder::GetInstance()->map[host] = token; } // static void PrivetURLFetcher::ResetTokenMapForTests() { TokenMapHolder::GetInstance()->map.clear(); } void PrivetURLFetcher::DoNotRetryOnTransientError() { DCHECK(tries_ == 0); do_not_retry_on_transient_error_ = true; } void PrivetURLFetcher::SendEmptyPrivetToken() { DCHECK(tries_ == 0); send_empty_privet_token_ = true; } std::string PrivetURLFetcher::GetPrivetAccessToken() { if (send_empty_privet_token_) { return std::string(); } TokenMapHolder* token_map_holder = TokenMapHolder::GetInstance(); TokenMap::iterator found = token_map_holder->map.find(GetHostString()); return found != token_map_holder->map.end() ? found->second : std::string(); } std::string PrivetURLFetcher::GetHostString() { return url_.GetOrigin().spec(); } void PrivetURLFetcher::SaveResponseToFile() { DCHECK(tries_ == 0); make_response_file_ = true; } void PrivetURLFetcher::V3Mode() { v3_mode_ = true; } void PrivetURLFetcher::SetByteRange(int start, int end) { DCHECK(tries_ == 0); byte_range_start_ = start; byte_range_end_ = end; has_byte_range_ = true; } void PrivetURLFetcher::Try() { tries_++; if (tries_ < kPrivetMaxRetries) { url_fetcher_ = net::URLFetcher::Create(url_, request_type_, this); // Privet requests are relevant to hosts on local network only. url_fetcher_->SetLoadFlags(url_fetcher_->GetLoadFlags() | net::LOAD_BYPASS_PROXY | net::LOAD_DISABLE_CACHE); url_fetcher_->SetRequestContext(request_context_.get()); if (v3_mode_) { url_fetcher_->AddExtraRequestHeader( std::string(kPrivetV3AuthTokenHeaderPrefix) + delegate_->GetAuthToken()); } else { std::string token = GetPrivetAccessToken(); if (token.empty()) token = kXPrivetEmptyToken; url_fetcher_->AddExtraRequestHeader( std::string(kXPrivetTokenHeaderPrefix) + token); } if (has_byte_range_) { url_fetcher_->AddExtraRequestHeader( MakeRangeHeader(byte_range_start_, byte_range_end_)); } if (make_response_file_) { url_fetcher_->SaveResponseToTemporaryFile( content::BrowserThread::GetMessageLoopProxyForThread( content::BrowserThread::FILE)); } // URLFetcher requires us to set upload data for POST requests. if (request_type_ == net::URLFetcher::POST) { if (!upload_file_path_.empty()) { url_fetcher_->SetUploadFilePath( upload_content_type_, upload_file_path_, 0 /*offset*/, kuint64max /*length*/, content::BrowserThread::GetMessageLoopProxyForThread( content::BrowserThread::FILE)); } else { url_fetcher_->SetUploadData(upload_content_type_, upload_data_); } } url_fetcher_->Start(); } else { delegate_->OnError(this, RETRY_ERROR); } } void PrivetURLFetcher::Start() { DCHECK_EQ(tries_, 0); // We haven't called |Start()| yet. if (!send_empty_privet_token_ && !v3_mode_) { std::string privet_access_token; privet_access_token = GetPrivetAccessToken(); if (privet_access_token.empty()) { RequestTokenRefresh(); return; } } Try(); } void PrivetURLFetcher::SetUploadData(const std::string& upload_content_type, const std::string& upload_data) { DCHECK(upload_file_path_.empty()); upload_content_type_ = upload_content_type; upload_data_ = upload_data; } void PrivetURLFetcher::SetUploadFilePath( const std::string& upload_content_type, const base::FilePath& upload_file_path) { DCHECK(upload_data_.empty()); upload_content_type_ = upload_content_type; upload_file_path_ = upload_file_path; } void PrivetURLFetcher::OnURLFetchComplete(const net::URLFetcher* source) { if (source->GetResponseCode() == net::HTTP_SERVICE_UNAVAILABLE || source->GetResponseCode() == net::URLFetcher::RESPONSE_CODE_INVALID) { ScheduleRetry(kPrivetTimeoutOnError); return; } if (!OnURLFetchCompleteDoNotParseData(source)) { // Byte ranges should only be used when we're not parsing the data // as JSON. DCHECK(!has_byte_range_); // We should only be saving raw data to a file. DCHECK(!make_response_file_); OnURLFetchCompleteParseData(source); } } // Note that this function returns "true" in error cases to indicate // that it has fully handled the responses. bool PrivetURLFetcher::OnURLFetchCompleteDoNotParseData( const net::URLFetcher* source) { if (source->GetResponseCode() == kHTTPErrorCodeInvalidXPrivetToken) { RequestTokenRefresh(); return true; } if (source->GetResponseCode() != net::HTTP_OK && source->GetResponseCode() != net::HTTP_PARTIAL_CONTENT && source->GetResponseCode() != net::HTTP_BAD_REQUEST) { delegate_->OnError(this, RESPONSE_CODE_ERROR); return true; } if (make_response_file_) { base::FilePath response_file_path; if (!source->GetResponseAsFilePath(true, &response_file_path)) { delegate_->OnError(this, URL_FETCH_ERROR); return true; } return delegate_->OnRawData(this, true, std::string(), response_file_path); } else { std::string response_str; if (!source->GetResponseAsString(&response_str)) { delegate_->OnError(this, URL_FETCH_ERROR); return true; } return delegate_->OnRawData(this, false, response_str, base::FilePath()); } } void PrivetURLFetcher::OnURLFetchCompleteParseData( const net::URLFetcher* source) { // Response contains error description. bool is_error_response = false; if (v3_mode_ && source->GetResponseCode() == net::HTTP_BAD_REQUEST) { is_error_response = true; } else if (source->GetResponseCode() != net::HTTP_OK) { delegate_->OnError(this, RESPONSE_CODE_ERROR); return; } std::string response_str; if (!source->GetResponseAsString(&response_str)) { delegate_->OnError(this, URL_FETCH_ERROR); return; } base::JSONReader json_reader(base::JSON_ALLOW_TRAILING_COMMAS); scoped_ptr value = json_reader.ReadToValue(response_str); if (!value) { delegate_->OnError(this, JSON_PARSE_ERROR); return; } const base::DictionaryValue* dictionary_value = NULL; if (!value->GetAsDictionary(&dictionary_value)) { delegate_->OnError(this, JSON_PARSE_ERROR); return; } std::string error; if (!v3_mode_ && dictionary_value->GetString(kPrivetKeyError, &error)) { if (error == kPrivetErrorInvalidXPrivetToken) { RequestTokenRefresh(); return; } else if (PrivetErrorTransient(error)) { if (!do_not_retry_on_transient_error_) { int timeout_seconds; if (!dictionary_value->GetInteger(kPrivetKeyTimeout, &timeout_seconds)) { timeout_seconds = kPrivetDefaultTimeout; } ScheduleRetry(timeout_seconds); return; } } is_error_response = true; } delegate_->OnParsedJson(this, *dictionary_value, is_error_response); } void PrivetURLFetcher::ScheduleRetry(int timeout_seconds) { double random_scaling_factor = 1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition; int timeout_seconds_randomized = static_cast(timeout_seconds * random_scaling_factor); timeout_seconds_randomized = std::max(timeout_seconds_randomized, kPrivetMinimumTimeout); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&PrivetURLFetcher::Try, weak_factory_.GetWeakPtr()), base::TimeDelta::FromSeconds(timeout_seconds_randomized)); } void PrivetURLFetcher::RequestTokenRefresh() { delegate_->OnNeedPrivetToken( this, base::Bind(&PrivetURLFetcher::RefreshToken, weak_factory_.GetWeakPtr())); } void PrivetURLFetcher::RefreshToken(const std::string& token) { if (token.empty()) { delegate_->OnError(this, TOKEN_ERROR); } else { SetTokenForHost(GetHostString(), token); Try(); } } bool PrivetURLFetcher::PrivetErrorTransient(const std::string& error) { return (error == kPrivetErrorDeviceBusy) || (error == kPrivetV3ErrorDeviceBusy) || (error == kPrivetErrorPendingUserAction) || (error == kPrivetErrorPrinterBusy); } } // namespace local_discovery