// Copyright 2014 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 "components/search_provider_logos/logo_tracker.h" #include <algorithm> #include "base/message_loop/message_loop.h" #include "base/task_runner_util.h" #include "base/thread_task_runner_handle.h" #include "base/time/default_clock.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_status.h" namespace search_provider_logos { namespace { const int64 kMaxDownloadBytes = 1024 * 1024; //const int kDecodeLogoTimeoutSeconds = 30; // Returns whether the metadata for the cached logo indicates that the logo is // OK to show, i.e. it's not expired or it's allowed to be shown temporarily // after expiration. bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) { base::TimeDelta offset = base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2); base::Time distant_past = now - offset; base::Time distant_future = now + offset; // Sanity check so logos aren't accidentally cached forever. if (metadata.expiration_time < distant_past || metadata.expiration_time > distant_future) { return false; } return metadata.can_show_after_expiration || metadata.expiration_time >= now; } // Reads the logo from the cache and returns it. Returns NULL if the cache is // empty, corrupt, expired, or doesn't apply to the current logo URL. scoped_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache, const GURL& logo_url, base::Time now) { const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata(); if (!metadata) return scoped_ptr<EncodedLogo>(); if (metadata->source_url != logo_url.spec() || !IsLogoOkToShow(*metadata, now)) { logo_cache->SetCachedLogo(NULL); return scoped_ptr<EncodedLogo>(); } return logo_cache->GetCachedLogo().Pass(); } void DeleteLogoCacheOnFileThread(LogoCache* logo_cache) { delete logo_cache; } } // namespace LogoTracker::LogoTracker( base::FilePath cached_logo_directory, scoped_refptr<base::SequencedTaskRunner> file_task_runner, scoped_refptr<base::TaskRunner> background_task_runner, scoped_refptr<net::URLRequestContextGetter> request_context_getter, scoped_ptr<LogoDelegate> delegate) : is_idle_(true), is_cached_logo_valid_(false), logo_delegate_(delegate.Pass()), logo_cache_(new LogoCache(cached_logo_directory)), clock_(new base::DefaultClock()), file_task_runner_(file_task_runner), background_task_runner_(background_task_runner), request_context_getter_(request_context_getter), weak_ptr_factory_(this) {} LogoTracker::~LogoTracker() { ReturnToIdle(); file_task_runner_->PostTask( FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_)); logo_cache_ = NULL; } void LogoTracker::SetServerAPI( const GURL& logo_url, const ParseLogoResponse& parse_logo_response_func, const AppendFingerprintToLogoURL& append_fingerprint_func) { if (logo_url == logo_url_) return; ReturnToIdle(); logo_url_ = logo_url; parse_logo_response_func_ = parse_logo_response_func; append_fingerprint_func_ = append_fingerprint_func; } void LogoTracker::GetLogo(LogoObserver* observer) { DCHECK(!logo_url_.is_empty()); logo_observers_.AddObserver(observer); if (is_idle_) { is_idle_ = false; base::PostTaskAndReplyWithResult( file_task_runner_.get(), FROM_HERE, base::Bind(&GetLogoFromCacheOnFileThread, logo_cache_, logo_url_, clock_->Now()), base::Bind(&LogoTracker::OnCachedLogoRead, weak_ptr_factory_.GetWeakPtr())); } else if (is_cached_logo_valid_) { observer->OnLogoAvailable(cached_logo_.get(), true); } } void LogoTracker::RemoveObserver(LogoObserver* observer) { logo_observers_.RemoveObserver(observer); } void LogoTracker::SetLogoCacheForTests(scoped_ptr<LogoCache> cache) { DCHECK(cache); file_task_runner_->PostTask( FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_)); logo_cache_ = cache.release(); } void LogoTracker::SetClockForTests(scoped_ptr<base::Clock> clock) { clock_ = clock.Pass(); } void LogoTracker::ReturnToIdle() { // Cancel the current asynchronous operation, if any. fetcher_.reset(); weak_ptr_factory_.InvalidateWeakPtrs(); // Reset state. is_idle_ = true; cached_logo_.reset(); is_cached_logo_valid_ = false; // Clear obsevers. FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnObserverRemoved()); logo_observers_.Clear(); } void LogoTracker::OnCachedLogoRead(scoped_ptr<EncodedLogo> cached_logo) { DCHECK(!is_idle_); if (cached_logo) { logo_delegate_->DecodeUntrustedImage( cached_logo->encoded_image, base::Bind(&LogoTracker::OnCachedLogoAvailable, weak_ptr_factory_.GetWeakPtr(), cached_logo->metadata)); } else { OnCachedLogoAvailable(LogoMetadata(), SkBitmap()); } } void LogoTracker::OnCachedLogoAvailable(const LogoMetadata& metadata, const SkBitmap& image) { DCHECK(!is_idle_); if (!image.isNull()) { cached_logo_.reset(new Logo()); cached_logo_->metadata = metadata; cached_logo_->image = image; } is_cached_logo_valid_ = true; Logo* logo = cached_logo_.get(); FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo, true)); FetchLogo(); } void LogoTracker::SetCachedLogo(scoped_ptr<EncodedLogo> logo) { file_task_runner_->PostTask( FROM_HERE, base::Bind(&LogoCache::SetCachedLogo, base::Unretained(logo_cache_), base::Owned(logo.release()))); } void LogoTracker::SetCachedMetadata(const LogoMetadata& metadata) { file_task_runner_->PostTask(FROM_HERE, base::Bind(&LogoCache::UpdateCachedLogoMetadata, base::Unretained(logo_cache_), metadata)); } void LogoTracker::FetchLogo() { DCHECK(!fetcher_); DCHECK(!is_idle_); GURL url; if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() && cached_logo_->metadata.expiration_time >= clock_->Now()) { url = append_fingerprint_func_.Run(logo_url_, cached_logo_->metadata.fingerprint); } else { url = logo_url_; } fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this)); fetcher_->SetRequestContext(request_context_getter_.get()); fetcher_->Start(); } void LogoTracker::OnFreshLogoParsed(scoped_ptr<EncodedLogo> logo) { DCHECK(!is_idle_); if (logo) logo->metadata.source_url = logo_url_.spec(); if (!logo || !logo->encoded_image.get()) { OnFreshLogoAvailable(logo.Pass(), SkBitmap()); } else { // Store the value of logo->encoded_image for use below. This ensures that // logo->encoded_image is evaulated before base::Passed(&logo), which sets // logo to NULL. scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image; logo_delegate_->DecodeUntrustedImage( encoded_image, base::Bind(&LogoTracker::OnFreshLogoAvailable, weak_ptr_factory_.GetWeakPtr(), base::Passed(&logo))); } } void LogoTracker::OnFreshLogoAvailable(scoped_ptr<EncodedLogo> encoded_logo, const SkBitmap& image) { DCHECK(!is_idle_); if (encoded_logo && !encoded_logo->encoded_image.get() && cached_logo_ && !encoded_logo->metadata.fingerprint.empty() && encoded_logo->metadata.fingerprint == cached_logo_->metadata.fingerprint) { // The cached logo was revalidated, i.e. its fingerprint was verified. SetCachedMetadata(encoded_logo->metadata); } else if (encoded_logo && image.isNull()) { // Image decoding failed. Do nothing. } else { scoped_ptr<Logo> logo; // Check if the server returned a valid, non-empty response. if (encoded_logo) { DCHECK(!image.isNull()); logo.reset(new Logo()); logo->metadata = encoded_logo->metadata; logo->image = image; } // Notify observers if a new logo was fetched, or if the new logo is NULL // but the cached logo was non-NULL. if (logo || cached_logo_) { FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo.get(), false)); SetCachedLogo(encoded_logo.Pass()); } } ReturnToIdle(); } void LogoTracker::OnURLFetchComplete(const net::URLFetcher* source) { DCHECK(!is_idle_); scoped_ptr<net::URLFetcher> cleanup_fetcher(fetcher_.release()); if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) { ReturnToIdle(); return; } scoped_ptr<std::string> response(new std::string()); source->GetResponseAsString(response.get()); base::Time response_time = clock_->Now(); base::PostTaskAndReplyWithResult( background_task_runner_.get(), FROM_HERE, base::Bind( parse_logo_response_func_, base::Passed(&response), response_time), base::Bind(&LogoTracker::OnFreshLogoParsed, weak_ptr_factory_.GetWeakPtr())); } void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher* source, int64 current, int64 total) { if (total > kMaxDownloadBytes || current > kMaxDownloadBytes) { LOG(WARNING) << "Search provider logo exceeded download size limit"; ReturnToIdle(); } } } // namespace search_provider_logos