• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
IsLogoOkToShow(const LogoMetadata & metadata,base::Time now)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.
GetLogoFromCacheOnFileThread(LogoCache * logo_cache,const GURL & logo_url,base::Time now)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 
DeleteLogoCacheOnFileThread(LogoCache * logo_cache)60 void DeleteLogoCacheOnFileThread(LogoCache* logo_cache) {
61   delete logo_cache;
62 }
63 
64 }  // namespace
65 
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)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 
~LogoTracker()82 LogoTracker::~LogoTracker() {
83   ReturnToIdle();
84   file_task_runner_->PostTask(
85       FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
86   logo_cache_ = NULL;
87 }
88 
SetServerAPI(const GURL & logo_url,const ParseLogoResponse & parse_logo_response_func,const AppendFingerprintToLogoURL & append_fingerprint_func)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 
GetLogo(LogoObserver * observer)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 
RemoveObserver(LogoObserver * observer)123 void LogoTracker::RemoveObserver(LogoObserver* observer) {
124   logo_observers_.RemoveObserver(observer);
125 }
126 
SetLogoCacheForTests(scoped_ptr<LogoCache> cache)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 
SetClockForTests(scoped_ptr<base::Clock> clock)134 void LogoTracker::SetClockForTests(scoped_ptr<base::Clock> clock) {
135   clock_ = clock.Pass();
136 }
137 
ReturnToIdle()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 
OnCachedLogoRead(scoped_ptr<EncodedLogo> cached_logo)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 
OnCachedLogoAvailable(const LogoMetadata & metadata,const SkBitmap & image)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 
SetCachedLogo(scoped_ptr<EncodedLogo> logo)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 
SetCachedMetadata(const LogoMetadata & metadata)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 
FetchLogo()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 
OnFreshLogoParsed(scoped_ptr<EncodedLogo> logo)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 
OnFreshLogoAvailable(scoped_ptr<EncodedLogo> encoded_logo,const SkBitmap & image)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 
OnURLFetchComplete(const net::URLFetcher * source)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 
OnURLFetchDownloadProgress(const net::URLFetcher * source,int64 current,int64 total)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