Home | History | Annotate | Download | only in search_provider_logos
      1 // Copyright 2014 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 "components/search_provider_logos/logo_tracker.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/task_runner_util.h"
     11 #include "base/thread_task_runner_handle.h"
     12 #include "base/time/default_clock.h"
     13 #include "net/http/http_response_headers.h"
     14 #include "net/url_request/url_fetcher.h"
     15 #include "net/url_request/url_request_context_getter.h"
     16 #include "net/url_request/url_request_status.h"
     17 
     18 namespace search_provider_logos {
     19 
     20 namespace {
     21 
     22 const int64 kMaxDownloadBytes = 1024 * 1024;
     23 
     24 //const int kDecodeLogoTimeoutSeconds = 30;
     25 
     26 // Returns whether the metadata for the cached logo indicates that the logo is
     27 // OK to show, i.e. it's not expired or it's allowed to be shown temporarily
     28 // after expiration.
     29 bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) {
     30   base::TimeDelta offset =
     31       base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2);
     32   base::Time distant_past = now - offset;
     33   base::Time distant_future = now + offset;
     34   // Sanity check so logos aren't accidentally cached forever.
     35   if (metadata.expiration_time < distant_past ||
     36       metadata.expiration_time > distant_future) {
     37     return false;
     38   }
     39   return metadata.can_show_after_expiration || metadata.expiration_time >= now;
     40 }
     41 
     42 // Reads the logo from the cache and returns it. Returns NULL if the cache is
     43 // empty, corrupt, expired, or doesn't apply to the current logo URL.
     44 scoped_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache,
     45                                               const GURL& logo_url,
     46                                               base::Time now) {
     47   const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata();
     48   if (!metadata)
     49     return scoped_ptr<EncodedLogo>();
     50 
     51   if (metadata->source_url != logo_url.spec() ||
     52       !IsLogoOkToShow(*metadata, now)) {
     53     logo_cache->SetCachedLogo(NULL);
     54     return scoped_ptr<EncodedLogo>();
     55   }
     56 
     57   return logo_cache->GetCachedLogo().Pass();
     58 }
     59 
     60 void DeleteLogoCacheOnFileThread(LogoCache* logo_cache) {
     61   delete logo_cache;
     62 }
     63 
     64 }  // namespace
     65 
     66 LogoTracker::LogoTracker(
     67     base::FilePath cached_logo_directory,
     68     scoped_refptr<base::SequencedTaskRunner> file_task_runner,
     69     scoped_refptr<base::TaskRunner> background_task_runner,
     70     scoped_refptr<net::URLRequestContextGetter> request_context_getter,
     71     scoped_ptr<LogoDelegate> delegate)
     72     : is_idle_(true),
     73       is_cached_logo_valid_(false),
     74       logo_delegate_(delegate.Pass()),
     75       logo_cache_(new LogoCache(cached_logo_directory)),
     76       clock_(new base::DefaultClock()),
     77       file_task_runner_(file_task_runner),
     78       background_task_runner_(background_task_runner),
     79       request_context_getter_(request_context_getter),
     80       weak_ptr_factory_(this) {}
     81 
     82 LogoTracker::~LogoTracker() {
     83   ReturnToIdle();
     84   file_task_runner_->PostTask(
     85       FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
     86   logo_cache_ = NULL;
     87 }
     88 
     89 void LogoTracker::SetServerAPI(
     90     const GURL& logo_url,
     91     const ParseLogoResponse& parse_logo_response_func,
     92     const AppendFingerprintToLogoURL& append_fingerprint_func) {
     93   if (logo_url == logo_url_)
     94     return;
     95 
     96   ReturnToIdle();
     97 
     98   logo_url_ = logo_url;
     99   parse_logo_response_func_ = parse_logo_response_func;
    100   append_fingerprint_func_ = append_fingerprint_func;
    101 }
    102 
    103 void LogoTracker::GetLogo(LogoObserver* observer) {
    104   DCHECK(!logo_url_.is_empty());
    105   logo_observers_.AddObserver(observer);
    106 
    107   if (is_idle_) {
    108     is_idle_ = false;
    109     base::PostTaskAndReplyWithResult(
    110         file_task_runner_,
    111         FROM_HERE,
    112         base::Bind(&GetLogoFromCacheOnFileThread,
    113                    logo_cache_,
    114                    logo_url_,
    115                    clock_->Now()),
    116         base::Bind(&LogoTracker::OnCachedLogoRead,
    117                    weak_ptr_factory_.GetWeakPtr()));
    118   } else if (is_cached_logo_valid_) {
    119     observer->OnLogoAvailable(cached_logo_.get(), true);
    120   }
    121 }
    122 
    123 void LogoTracker::RemoveObserver(LogoObserver* observer) {
    124   logo_observers_.RemoveObserver(observer);
    125 }
    126 
    127 void LogoTracker::SetLogoCacheForTests(scoped_ptr<LogoCache> cache) {
    128   DCHECK(cache);
    129   file_task_runner_->PostTask(
    130         FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
    131   logo_cache_ = cache.release();
    132 }
    133 
    134 void LogoTracker::SetClockForTests(scoped_ptr<base::Clock> clock) {
    135   clock_ = clock.Pass();
    136 }
    137 
    138 void LogoTracker::ReturnToIdle() {
    139   // Cancel the current asynchronous operation, if any.
    140   fetcher_.reset();
    141   weak_ptr_factory_.InvalidateWeakPtrs();
    142 
    143   // Reset state.
    144   is_idle_ = true;
    145   cached_logo_.reset();
    146   is_cached_logo_valid_ = false;
    147 
    148   // Clear obsevers.
    149   FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnObserverRemoved());
    150   logo_observers_.Clear();
    151 }
    152 
    153 void LogoTracker::OnCachedLogoRead(scoped_ptr<EncodedLogo> cached_logo) {
    154   DCHECK(!is_idle_);
    155 
    156   if (cached_logo) {
    157     logo_delegate_->DecodeUntrustedImage(
    158         cached_logo->encoded_image,
    159         base::Bind(&LogoTracker::OnCachedLogoAvailable,
    160                    weak_ptr_factory_.GetWeakPtr(),
    161                    cached_logo->metadata));
    162   } else {
    163     OnCachedLogoAvailable(LogoMetadata(), SkBitmap());
    164   }
    165 }
    166 
    167 void LogoTracker::OnCachedLogoAvailable(const LogoMetadata& metadata,
    168                                         const SkBitmap& image) {
    169   DCHECK(!is_idle_);
    170 
    171   if (!image.isNull()) {
    172     cached_logo_.reset(new Logo());
    173     cached_logo_->metadata = metadata;
    174     cached_logo_->image = image;
    175   }
    176   is_cached_logo_valid_ = true;
    177   Logo* logo = cached_logo_.get();
    178   FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo, true));
    179   FetchLogo();
    180 }
    181 
    182 void LogoTracker::SetCachedLogo(scoped_ptr<EncodedLogo> logo) {
    183   file_task_runner_->PostTask(
    184       FROM_HERE,
    185       base::Bind(&LogoCache::SetCachedLogo,
    186                  base::Unretained(logo_cache_),
    187                  base::Owned(logo.release())));
    188 }
    189 
    190 void LogoTracker::SetCachedMetadata(const LogoMetadata& metadata) {
    191   file_task_runner_->PostTask(FROM_HERE,
    192                               base::Bind(&LogoCache::UpdateCachedLogoMetadata,
    193                                          base::Unretained(logo_cache_),
    194                                          metadata));
    195 }
    196 
    197 void LogoTracker::FetchLogo() {
    198   DCHECK(!fetcher_);
    199   DCHECK(!is_idle_);
    200 
    201   GURL url;
    202   if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() &&
    203       cached_logo_->metadata.expiration_time >= clock_->Now()) {
    204     url = append_fingerprint_func_.Run(logo_url_,
    205                                        cached_logo_->metadata.fingerprint);
    206   } else {
    207     url = logo_url_;
    208   }
    209 
    210   fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
    211   fetcher_->SetRequestContext(request_context_getter_);
    212   fetcher_->Start();
    213 }
    214 
    215 void LogoTracker::OnFreshLogoParsed(scoped_ptr<EncodedLogo> logo) {
    216   DCHECK(!is_idle_);
    217 
    218   if (logo)
    219     logo->metadata.source_url = logo_url_.spec();
    220 
    221   if (!logo || !logo->encoded_image) {
    222     OnFreshLogoAvailable(logo.Pass(), SkBitmap());
    223   } else {
    224     // Store the value of logo->encoded_image for use below. This ensures that
    225     // logo->encoded_image is evaulated before base::Passed(&logo), which sets
    226     // logo to NULL.
    227     scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image;
    228     logo_delegate_->DecodeUntrustedImage(
    229         encoded_image,
    230         base::Bind(&LogoTracker::OnFreshLogoAvailable,
    231                    weak_ptr_factory_.GetWeakPtr(),
    232                    base::Passed(&logo)));
    233   }
    234 }
    235 
    236 void LogoTracker::OnFreshLogoAvailable(scoped_ptr<EncodedLogo> encoded_logo,
    237                                        const SkBitmap& image) {
    238   DCHECK(!is_idle_);
    239 
    240   if (encoded_logo && !encoded_logo->encoded_image && cached_logo_ &&
    241       !encoded_logo->metadata.fingerprint.empty() &&
    242       encoded_logo->metadata.fingerprint ==
    243           cached_logo_->metadata.fingerprint) {
    244     // The cached logo was revalidated, i.e. its fingerprint was verified.
    245     SetCachedMetadata(encoded_logo->metadata);
    246   } else if (encoded_logo && image.isNull()) {
    247     // Image decoding failed. Do nothing.
    248   } else {
    249     scoped_ptr<Logo> logo;
    250     // Check if the server returned a valid, non-empty response.
    251     if (encoded_logo) {
    252       DCHECK(!image.isNull());
    253       logo.reset(new Logo());
    254       logo->metadata = encoded_logo->metadata;
    255       logo->image = image;
    256     }
    257 
    258     // Notify observers if a new logo was fetched, or if the new logo is NULL
    259     // but the cached logo was non-NULL.
    260     if (logo || cached_logo_) {
    261       FOR_EACH_OBSERVER(LogoObserver,
    262                         logo_observers_,
    263                         OnLogoAvailable(logo.get(), false));
    264       SetCachedLogo(encoded_logo.Pass());
    265     }
    266   }
    267 
    268   ReturnToIdle();
    269 }
    270 
    271 void LogoTracker::OnURLFetchComplete(const net::URLFetcher* source) {
    272   DCHECK(!is_idle_);
    273   scoped_ptr<net::URLFetcher> cleanup_fetcher(fetcher_.release());
    274 
    275   if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
    276     ReturnToIdle();
    277     return;
    278   }
    279 
    280   scoped_ptr<std::string> response(new std::string());
    281   source->GetResponseAsString(response.get());
    282   base::Time response_time = clock_->Now();
    283 
    284   base::PostTaskAndReplyWithResult(
    285       background_task_runner_,
    286       FROM_HERE,
    287       base::Bind(parse_logo_response_func_,
    288                  base::Passed(&response),
    289                  response_time),
    290       base::Bind(&LogoTracker::OnFreshLogoParsed,
    291                  weak_ptr_factory_.GetWeakPtr()));
    292 }
    293 
    294 void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher* source,
    295                                              int64 current,
    296                                              int64 total) {
    297   if (total > kMaxDownloadBytes || current > kMaxDownloadBytes) {
    298     LOG(WARNING) << "Search provider logo exceeded download size limit";
    299     ReturnToIdle();
    300   }
    301 }
    302 
    303 }  // namespace search_provider_logos
    304