| // 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_, |
| 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_); |
| 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) { |
| 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 && 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_, |
| 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 |