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