• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 "chrome/browser/extensions/extension_updater.h"
6 
7 #include <algorithm>
8 #include <set>
9 
10 #include "base/compiler_specific.h"
11 #include "base/logging.h"
12 #include "base/file_util.h"
13 #include "base/metrics/histogram.h"
14 #include "base/rand_util.h"
15 #include "base/stl_util-inl.h"
16 #include "base/string_number_conversions.h"
17 #include "base/string_split.h"
18 #include "base/string_util.h"
19 #include "base/time.h"
20 #include "base/threading/thread.h"
21 #include "base/version.h"
22 #include "crypto/sha2.h"
23 #include "content/common/notification_service.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/extensions/extension_error_reporter.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/prefs/pref_service.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/utility_process_host.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/chrome_version_info.h"
32 #include "chrome/common/extensions/extension.h"
33 #include "chrome/common/extensions/extension_constants.h"
34 #include "chrome/common/extensions/extension_file_util.h"
35 #include "chrome/common/pref_names.h"
36 #include "googleurl/src/gurl.h"
37 #include "net/base/escape.h"
38 #include "net/base/load_flags.h"
39 #include "net/url_request/url_request_status.h"
40 
41 #if defined(OS_MACOSX)
42 #include "base/sys_string_conversions.h"
43 #endif
44 
45 #define SEND_ACTIVE_PINGS 1
46 
47 using base::RandDouble;
48 using base::RandInt;
49 using base::Time;
50 using base::TimeDelta;
51 using prefs::kExtensionBlacklistUpdateVersion;
52 using prefs::kLastExtensionsUpdateCheck;
53 using prefs::kNextExtensionsUpdateCheck;
54 
55 // Update AppID for extension blacklist.
56 const char* ExtensionUpdater::kBlacklistAppID = "com.google.crx.blacklist";
57 
58 // Wait at least 5 minutes after browser startup before we do any checks. If you
59 // change this value, make sure to update comments where it is used.
60 const int kStartupWaitSeconds = 60 * 5;
61 
62 // For sanity checking on update frequency - enforced in release mode only.
63 static const int kMinUpdateFrequencySeconds = 30;
64 static const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7;  // 7 days
65 
66 // Maximum length of an extension manifest update check url, since it is a GET
67 // request. We want to stay under 2K because of proxies, etc.
68 static const int kExtensionsManifestMaxURLSize = 2000;
69 
ManifestFetchData(const GURL & update_url)70 ManifestFetchData::ManifestFetchData(const GURL& update_url)
71     : base_url_(update_url),
72       full_url_(update_url) {
73 }
74 
~ManifestFetchData()75 ManifestFetchData::~ManifestFetchData() {}
76 
77 // The format for request parameters in update checks is:
78 //
79 //   ?x=EXT1_INFO&x=EXT2_INFO
80 //
81 // where EXT1_INFO and EXT2_INFO are url-encoded strings of the form:
82 //
83 //   id=EXTENSION_ID&v=VERSION&uc
84 //
85 // Additionally, we may include the parameter ping=PING_DATA where PING_DATA
86 // looks like r=DAYS or a=DAYS for extensions in the Chrome extensions gallery.
87 // ('r' refers to 'roll call' ie installation, and 'a' refers to 'active').
88 // These values will each be present at most once every 24 hours, and indicate
89 // the number of days since the last time it was present in an update check.
90 //
91 // So for two extensions like:
92 //   Extension 1- id:aaaa version:1.1
93 //   Extension 2- id:bbbb version:2.0
94 //
95 // the full update url would be:
96 //   http://somehost/path?x=id%3Daaaa%26v%3D1.1%26uc&x=id%3Dbbbb%26v%3D2.0%26uc
97 //
98 // (Note that '=' is %3D and '&' is %26 when urlencoded.)
AddExtension(std::string id,std::string version,const PingData & ping_data,const std::string & update_url_data)99 bool ManifestFetchData::AddExtension(std::string id, std::string version,
100                                      const PingData& ping_data,
101                                      const std::string& update_url_data) {
102   if (extension_ids_.find(id) != extension_ids_.end()) {
103     NOTREACHED() << "Duplicate extension id " << id;
104     return false;
105   }
106 
107   // Compute the string we'd append onto the full_url_, and see if it fits.
108   std::vector<std::string> parts;
109   parts.push_back("id=" + id);
110   parts.push_back("v=" + version);
111   parts.push_back("uc");
112 
113   if (!update_url_data.empty()) {
114     // Make sure the update_url_data string is escaped before using it so that
115     // there is no chance of overriding the id or v other parameter value
116     // we place into the x= value.
117     parts.push_back("ap=" + EscapeQueryParamValue(update_url_data, true));
118   }
119 
120   // Append rollcall and active ping parameters.
121   if (base_url_.DomainIs("google.com")) {
122     std::string ping_value;
123     pings_[id] = PingData(0, 0);
124 
125     if (ping_data.rollcall_days == kNeverPinged ||
126         ping_data.rollcall_days > 0) {
127       ping_value += "r=" + base::IntToString(ping_data.rollcall_days);
128       pings_[id].rollcall_days = ping_data.rollcall_days;
129     }
130 #if SEND_ACTIVE_PINGS
131     if (ping_data.active_days == kNeverPinged || ping_data.active_days > 0) {
132       if (!ping_value.empty())
133         ping_value += "&";
134       ping_value += "a=" + base::IntToString(ping_data.active_days);
135       pings_[id].active_days = ping_data.active_days;
136     }
137 #endif  // SEND_ACTIVE_PINGS
138     if (!ping_value.empty())
139       parts.push_back("ping=" + EscapeQueryParamValue(ping_value, true));
140   }
141 
142   std::string extra = full_url_.has_query() ? "&" : "?";
143   extra += "x=" + EscapeQueryParamValue(JoinString(parts, '&'), true);
144 
145   // Check against our max url size, exempting the first extension added.
146   int new_size = full_url_.possibly_invalid_spec().size() + extra.size();
147   if (!extension_ids_.empty() && new_size > kExtensionsManifestMaxURLSize) {
148     UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 1);
149     return false;
150   }
151   UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 0);
152 
153   // We have room so go ahead and add the extension.
154   extension_ids_.insert(id);
155   full_url_ = GURL(full_url_.possibly_invalid_spec() + extra);
156   return true;
157 }
158 
Includes(const std::string & extension_id) const159 bool ManifestFetchData::Includes(const std::string& extension_id) const {
160   return extension_ids_.find(extension_id) != extension_ids_.end();
161 }
162 
DidPing(std::string extension_id,PingType type) const163 bool ManifestFetchData::DidPing(std::string extension_id, PingType type) const {
164   std::map<std::string, PingData>::const_iterator i = pings_.find(extension_id);
165   if (i == pings_.end())
166     return false;
167   int value = 0;
168   if (type == ROLLCALL)
169     value = i->second.rollcall_days;
170   else if (type == ACTIVE)
171     value = i->second.active_days;
172   else
173     NOTREACHED();
174   return value == kNeverPinged || value > 0;
175 }
176 
177 namespace {
178 
179 // When we've computed a days value, we want to make sure we don't send a
180 // negative value (due to the system clock being set backwards, etc.), since -1
181 // is a special sentinel value that means "never pinged", and other negative
182 // values don't make sense.
SanitizeDays(int days)183 static int SanitizeDays(int days) {
184   if (days < 0)
185     return 0;
186   return days;
187 }
188 
189 // Calculates the value to use for the ping days parameter.
CalculatePingDays(const Time & last_ping_day)190 static int CalculatePingDays(const Time& last_ping_day) {
191   int days = ManifestFetchData::kNeverPinged;
192   if (!last_ping_day.is_null()) {
193     days = SanitizeDays((Time::Now() - last_ping_day).InDays());
194   }
195   return days;
196 }
197 
CalculateActivePingDays(const Time & last_active_ping_day,bool hasActiveBit)198 static int CalculateActivePingDays(const Time& last_active_ping_day,
199                                    bool hasActiveBit) {
200   if (!hasActiveBit)
201     return 0;
202   if (last_active_ping_day.is_null())
203     return ManifestFetchData::kNeverPinged;
204   return SanitizeDays((Time::Now() - last_active_ping_day).InDays());
205 }
206 
207 }  // namespace
208 
ManifestFetchesBuilder(ExtensionServiceInterface * service,ExtensionPrefs * prefs)209 ManifestFetchesBuilder::ManifestFetchesBuilder(
210     ExtensionServiceInterface* service,
211     ExtensionPrefs* prefs)
212     : service_(service), prefs_(prefs) {
213   DCHECK(service_);
214   DCHECK(prefs_);
215 }
216 
~ManifestFetchesBuilder()217 ManifestFetchesBuilder::~ManifestFetchesBuilder() {}
218 
AddExtension(const Extension & extension)219 void ManifestFetchesBuilder::AddExtension(const Extension& extension) {
220   // Skip extensions with empty update URLs converted from user
221   // scripts.
222   if (extension.converted_from_user_script() &&
223       extension.update_url().is_empty()) {
224     return;
225   }
226 
227   // If the extension updates itself from the gallery, ignore any update URL
228   // data.  At the moment there is no extra data that an extension can
229   // communicate to the the gallery update servers.
230   std::string update_url_data;
231   if (!extension.UpdatesFromGallery())
232     update_url_data = prefs_->GetUpdateUrlData(extension.id());
233 
234   AddExtensionData(extension.location(),
235                    extension.id(),
236                    *extension.version(),
237                    extension.GetType(),
238                    extension.update_url(), update_url_data);
239 }
240 
AddPendingExtension(const std::string & id,const PendingExtensionInfo & info)241 void ManifestFetchesBuilder::AddPendingExtension(
242     const std::string& id,
243     const PendingExtensionInfo& info) {
244   // Use a zero version to ensure that a pending extension will always
245   // be updated, and thus installed (assuming all extensions have
246   // non-zero versions).
247   scoped_ptr<Version> version(
248       Version::GetVersionFromString("0.0.0.0"));
249 
250   AddExtensionData(
251       info.install_source(), id, *version,
252       Extension::TYPE_UNKNOWN, info.update_url(), "");
253 }
254 
ReportStats() const255 void ManifestFetchesBuilder::ReportStats() const {
256   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension",
257                            url_stats_.extension_count);
258   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme",
259                            url_stats_.theme_count);
260   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp",
261                            url_stats_.app_count);
262   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending",
263                            url_stats_.pending_count);
264   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl",
265                            url_stats_.google_url_count);
266   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl",
267                            url_stats_.other_url_count);
268   UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl",
269                            url_stats_.no_url_count);
270 }
271 
GetFetches()272 std::vector<ManifestFetchData*> ManifestFetchesBuilder::GetFetches() {
273   std::vector<ManifestFetchData*> fetches;
274   fetches.reserve(fetches_.size());
275   for (std::multimap<GURL, ManifestFetchData*>::iterator it =
276            fetches_.begin(); it != fetches_.end(); ++it) {
277     fetches.push_back(it->second);
278   }
279   fetches_.clear();
280   url_stats_ = URLStats();
281   return fetches;
282 }
283 
AddExtensionData(Extension::Location location,const std::string & id,const Version & version,Extension::Type extension_type,GURL update_url,const std::string & update_url_data)284 void ManifestFetchesBuilder::AddExtensionData(
285     Extension::Location location,
286     const std::string& id,
287     const Version& version,
288     Extension::Type extension_type,
289     GURL update_url,
290     const std::string& update_url_data) {
291   if (!Extension::IsAutoUpdateableLocation(location)) {
292     return;
293   }
294 
295   // Skip extensions with non-empty invalid update URLs.
296   if (!update_url.is_empty() && !update_url.is_valid()) {
297     LOG(WARNING) << "Extension " << id << " has invalid update url "
298                  << update_url;
299     return;
300   }
301 
302   // Skip extensions with empty IDs.
303   if (id.empty()) {
304     LOG(WARNING) << "Found extension with empty ID";
305     return;
306   }
307 
308   if (update_url.DomainIs("google.com")) {
309     url_stats_.google_url_count++;
310   } else if (update_url.is_empty()) {
311     url_stats_.no_url_count++;
312     // Fill in default update URL.
313     //
314     // TODO(akalin): Figure out if we should use the HTTPS version.
315     update_url = Extension::GalleryUpdateUrl(false);
316   } else {
317     url_stats_.other_url_count++;
318   }
319 
320   switch (extension_type) {
321     case Extension::TYPE_THEME:
322       ++url_stats_.theme_count;
323       break;
324     case Extension::TYPE_EXTENSION:
325     case Extension::TYPE_USER_SCRIPT:
326       ++url_stats_.extension_count;
327       break;
328     case Extension::TYPE_HOSTED_APP:
329     case Extension::TYPE_PACKAGED_APP:
330       ++url_stats_.app_count;
331       break;
332     case Extension::TYPE_UNKNOWN:
333     default:
334       ++url_stats_.pending_count;
335       break;
336   }
337 
338   DCHECK(!update_url.is_empty());
339   DCHECK(update_url.is_valid());
340 
341   ManifestFetchData* fetch = NULL;
342   std::multimap<GURL, ManifestFetchData*>::iterator existing_iter =
343       fetches_.find(update_url);
344 
345   // Find or create a ManifestFetchData to add this extension to.
346   ManifestFetchData::PingData ping_data;
347   ping_data.rollcall_days = CalculatePingDays(prefs_->LastPingDay(id));
348   ping_data.active_days =
349       CalculateActivePingDays(prefs_->LastActivePingDay(id),
350                               prefs_->GetActiveBit(id));
351   while (existing_iter != fetches_.end()) {
352     if (existing_iter->second->AddExtension(id, version.GetString(),
353                                             ping_data, update_url_data)) {
354       fetch = existing_iter->second;
355       break;
356     }
357     existing_iter++;
358   }
359   if (!fetch) {
360     fetch = new ManifestFetchData(update_url);
361     fetches_.insert(std::pair<GURL, ManifestFetchData*>(update_url, fetch));
362     bool added = fetch->AddExtension(id, version.GetString(), ping_data,
363                                      update_url_data);
364     DCHECK(added);
365   }
366 }
367 
368 // A utility class to do file handling on the file I/O thread.
369 class ExtensionUpdaterFileHandler
370     : public base::RefCountedThreadSafe<ExtensionUpdaterFileHandler> {
371  public:
ExtensionUpdaterFileHandler(base::WeakPtr<ExtensionUpdater> updater)372   explicit ExtensionUpdaterFileHandler(
373       base::WeakPtr<ExtensionUpdater> updater)
374       : updater_(updater) {
375     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
376   }
377 
378   // Writes crx file data into a tempfile, and calls back the updater.
WriteTempFile(const std::string & extension_id,const std::string & data,const GURL & download_url)379   void WriteTempFile(const std::string& extension_id, const std::string& data,
380                      const GURL& download_url) {
381     // Make sure we're running in the right thread.
382     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
383 
384     bool failed = false;
385     FilePath path;
386     if (!file_util::CreateTemporaryFile(&path)) {
387       LOG(WARNING) << "Failed to create temporary file path";
388       failed = true;
389     } else if (file_util::WriteFile(path, data.c_str(), data.length()) !=
390         static_cast<int>(data.length())) {
391       // TODO(asargent) - It would be nice to back off updating altogether if
392       // the disk is full. (http://crbug.com/12763).
393       LOG(ERROR) << "Failed to write temporary file";
394       file_util::Delete(path, false);
395       failed = true;
396     }
397 
398     if (failed) {
399       if (!BrowserThread::PostTask(
400               BrowserThread::UI, FROM_HERE,
401               NewRunnableMethod(
402                   this, &ExtensionUpdaterFileHandler::OnCRXFileWriteError,
403                   extension_id))) {
404         NOTREACHED();
405       }
406     } else {
407       if (!BrowserThread::PostTask(
408               BrowserThread::UI, FROM_HERE,
409               NewRunnableMethod(
410                   this, &ExtensionUpdaterFileHandler::OnCRXFileWritten,
411                   extension_id, path, download_url))) {
412         NOTREACHED();
413         // Delete |path| since we couldn't post.
414         extension_file_util::DeleteFile(path, false);
415       }
416     }
417   }
418 
419  private:
420   friend class base::RefCountedThreadSafe<ExtensionUpdaterFileHandler>;
421 
~ExtensionUpdaterFileHandler()422   ~ExtensionUpdaterFileHandler() {
423     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
424            BrowserThread::CurrentlyOn(BrowserThread::FILE));
425   }
426 
OnCRXFileWritten(const std::string & id,const FilePath & path,const GURL & download_url)427   void OnCRXFileWritten(const std::string& id,
428                         const FilePath& path,
429                         const GURL& download_url) {
430     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
431     if (!updater_) {
432       // Delete |path| since we don't have an updater anymore.
433       if (!BrowserThread::PostTask(
434               BrowserThread::FILE, FROM_HERE,
435               NewRunnableFunction(
436                   extension_file_util::DeleteFile, path, false))) {
437         NOTREACHED();
438       }
439       return;
440     }
441     // The ExtensionUpdater now owns the temp file.
442     updater_->OnCRXFileWritten(id, path, download_url);
443   }
444 
OnCRXFileWriteError(const std::string & id)445   void OnCRXFileWriteError(const std::string& id) {
446     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
447     if (!updater_) {
448       return;
449     }
450     updater_->OnCRXFileWriteError(id);
451   }
452 
453   // Should be accessed only on UI thread.
454   base::WeakPtr<ExtensionUpdater> updater_;
455 };
456 
ExtensionFetch()457 ExtensionUpdater::ExtensionFetch::ExtensionFetch()
458     : id(""),
459       url(),
460       package_hash(""),
461       version("") {}
462 
ExtensionFetch(const std::string & i,const GURL & u,const std::string & h,const std::string & v)463 ExtensionUpdater::ExtensionFetch::ExtensionFetch(const std::string& i,
464                                                  const GURL& u,
465                                                  const std::string& h,
466                                                  const std::string& v)
467     : id(i), url(u), package_hash(h), version(v) {}
468 
~ExtensionFetch()469 ExtensionUpdater::ExtensionFetch::~ExtensionFetch() {}
470 
ExtensionUpdater(ExtensionServiceInterface * service,ExtensionPrefs * extension_prefs,PrefService * prefs,Profile * profile,int frequency_seconds)471 ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service,
472                                    ExtensionPrefs* extension_prefs,
473                                    PrefService* prefs,
474                                    Profile* profile,
475                                    int frequency_seconds)
476     : alive_(false),
477       weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
478       service_(service), frequency_seconds_(frequency_seconds),
479       method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
480       will_check_soon_(false), extension_prefs_(extension_prefs),
481       prefs_(prefs), profile_(profile), blacklist_checks_enabled_(true) {
482   Init();
483 }
484 
Init()485 void ExtensionUpdater::Init() {
486   DCHECK_GE(frequency_seconds_, 5);
487   DCHECK(frequency_seconds_ <= kMaxUpdateFrequencySeconds);
488 #ifdef NDEBUG
489   // In Release mode we enforce that update checks don't happen too often.
490   frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
491 #endif
492   frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
493 }
494 
~ExtensionUpdater()495 ExtensionUpdater::~ExtensionUpdater() {
496   Stop();
497 }
498 
EnsureInt64PrefRegistered(PrefService * prefs,const char name[])499 static void EnsureInt64PrefRegistered(PrefService* prefs,
500                                       const char name[]) {
501   if (!prefs->FindPreference(name))
502     prefs->RegisterInt64Pref(name, 0);
503 }
504 
EnsureBlacklistVersionPrefRegistered(PrefService * prefs)505 static void EnsureBlacklistVersionPrefRegistered(PrefService* prefs) {
506   if (!prefs->FindPreference(kExtensionBlacklistUpdateVersion))
507     prefs->RegisterStringPref(kExtensionBlacklistUpdateVersion, "0");
508 }
509 
510 // The overall goal here is to balance keeping clients up to date while
511 // avoiding a thundering herd against update servers.
DetermineFirstCheckDelay()512 TimeDelta ExtensionUpdater::DetermineFirstCheckDelay() {
513   DCHECK(alive_);
514   // If someone's testing with a quick frequency, just allow it.
515   if (frequency_seconds_ < kStartupWaitSeconds)
516     return TimeDelta::FromSeconds(frequency_seconds_);
517 
518   // If we've never scheduled a check before, start at frequency_seconds_.
519   if (!prefs_->HasPrefPath(kNextExtensionsUpdateCheck))
520     return TimeDelta::FromSeconds(frequency_seconds_);
521 
522   // If it's been a long time since our last actual check, we want to do one
523   // relatively soon.
524   Time now = Time::Now();
525   Time last = Time::FromInternalValue(prefs_->GetInt64(
526       kLastExtensionsUpdateCheck));
527   int days = (now - last).InDays();
528   if (days >= 30) {
529     // Wait 5-10 minutes.
530     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
531                                           kStartupWaitSeconds * 2));
532   } else if (days >= 14) {
533     // Wait 10-20 minutes.
534     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 2,
535                                           kStartupWaitSeconds * 4));
536   } else if (days >= 3) {
537     // Wait 20-40 minutes.
538     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 4,
539                                           kStartupWaitSeconds * 8));
540   }
541 
542   // Read the persisted next check time, and use that if it isn't too soon.
543   // Otherwise pick something random.
544   Time saved_next = Time::FromInternalValue(prefs_->GetInt64(
545       kNextExtensionsUpdateCheck));
546   Time earliest = now + TimeDelta::FromSeconds(kStartupWaitSeconds);
547   if (saved_next >= earliest) {
548     return saved_next - now;
549   } else {
550     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
551                                           frequency_seconds_));
552   }
553 }
554 
Start()555 void ExtensionUpdater::Start() {
556   DCHECK(!alive_);
557   // If these are NULL, then that means we've been called after Stop()
558   // has been called.
559   DCHECK(service_);
560   DCHECK(extension_prefs_);
561   DCHECK(prefs_);
562   DCHECK(profile_);
563   DCHECK(!weak_ptr_factory_.HasWeakPtrs());
564   file_handler_ =
565       new ExtensionUpdaterFileHandler(weak_ptr_factory_.GetWeakPtr());
566   alive_ = true;
567   // Make sure our prefs are registered, then schedule the first check.
568   EnsureInt64PrefRegistered(prefs_, kLastExtensionsUpdateCheck);
569   EnsureInt64PrefRegistered(prefs_, kNextExtensionsUpdateCheck);
570   EnsureBlacklistVersionPrefRegistered(prefs_);
571   ScheduleNextCheck(DetermineFirstCheckDelay());
572 }
573 
Stop()574 void ExtensionUpdater::Stop() {
575   weak_ptr_factory_.InvalidateWeakPtrs();
576   alive_ = false;
577   file_handler_ = NULL;
578   service_ = NULL;
579   extension_prefs_ = NULL;
580   prefs_ = NULL;
581   profile_ = NULL;
582   timer_.Stop();
583   will_check_soon_ = false;
584   method_factory_.RevokeAll();
585   manifest_fetcher_.reset();
586   extension_fetcher_.reset();
587   STLDeleteElements(&manifests_pending_);
588   manifests_pending_.clear();
589   extensions_pending_.clear();
590 }
591 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)592 void ExtensionUpdater::OnURLFetchComplete(
593     const URLFetcher* source,
594     const GURL& url,
595     const net::URLRequestStatus& status,
596     int response_code,
597     const ResponseCookies& cookies,
598     const std::string& data) {
599   // Stop() destroys all our URLFetchers, which means we shouldn't be
600   // called after Stop() is called.
601   DCHECK(alive_);
602 
603   if (source == manifest_fetcher_.get()) {
604     OnManifestFetchComplete(url, status, response_code, data);
605   } else if (source == extension_fetcher_.get()) {
606     OnCRXFetchComplete(url, status, response_code, data);
607   } else {
608     NOTREACHED();
609   }
610   NotifyIfFinished();
611 }
612 
613 // Utility class to handle doing xml parsing in a sandboxed utility process.
614 class SafeManifestParser : public UtilityProcessHost::Client {
615  public:
616   // Takes ownership of |fetch_data|.
SafeManifestParser(const std::string & xml,ManifestFetchData * fetch_data,base::WeakPtr<ExtensionUpdater> updater)617   SafeManifestParser(const std::string& xml, ManifestFetchData* fetch_data,
618                      base::WeakPtr<ExtensionUpdater> updater)
619       : xml_(xml), updater_(updater) {
620     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
621     fetch_data_.reset(fetch_data);
622   }
623 
624   // Posts a task over to the IO loop to start the parsing of xml_ in a
625   // utility process.
Start()626   void Start() {
627     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
628     if (!BrowserThread::PostTask(
629             BrowserThread::IO, FROM_HERE,
630             NewRunnableMethod(
631                 this, &SafeManifestParser::ParseInSandbox,
632                 g_browser_process->resource_dispatcher_host()))) {
633       NOTREACHED();
634     }
635   }
636 
637   // Creates the sandboxed utility process and tells it to start parsing.
ParseInSandbox(ResourceDispatcherHost * rdh)638   void ParseInSandbox(ResourceDispatcherHost* rdh) {
639     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
640 
641     // TODO(asargent) we shouldn't need to do this branch here - instead
642     // UtilityProcessHost should handle it for us. (http://crbug.com/19192)
643     bool use_utility_process = rdh &&
644         !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
645     if (use_utility_process) {
646       UtilityProcessHost* host = new UtilityProcessHost(
647           this, BrowserThread::UI);
648       host->StartUpdateManifestParse(xml_);
649     } else {
650       UpdateManifest manifest;
651       if (manifest.Parse(xml_)) {
652         if (!BrowserThread::PostTask(
653                 BrowserThread::UI, FROM_HERE,
654                 NewRunnableMethod(
655                     this, &SafeManifestParser::OnParseUpdateManifestSucceeded,
656                     manifest.results()))) {
657           NOTREACHED();
658         }
659       } else {
660         if (!BrowserThread::PostTask(
661                 BrowserThread::UI, FROM_HERE,
662                 NewRunnableMethod(
663                     this, &SafeManifestParser::OnParseUpdateManifestFailed,
664                     manifest.errors()))) {
665           NOTREACHED();
666         }
667       }
668     }
669   }
670 
671   // Callback from the utility process when parsing succeeded.
OnParseUpdateManifestSucceeded(const UpdateManifest::Results & results)672   virtual void OnParseUpdateManifestSucceeded(
673       const UpdateManifest::Results& results) {
674     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
675     if (!updater_) {
676       return;
677     }
678     updater_->HandleManifestResults(*fetch_data_, &results);
679   }
680 
681   // Callback from the utility process when parsing failed.
OnParseUpdateManifestFailed(const std::string & error_message)682   virtual void OnParseUpdateManifestFailed(const std::string& error_message) {
683     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
684     if (!updater_) {
685       return;
686     }
687     LOG(WARNING) << "Error parsing update manifest:\n" << error_message;
688     updater_->HandleManifestResults(*fetch_data_, NULL);
689   }
690 
691  private:
~SafeManifestParser()692   ~SafeManifestParser() {
693     // If we're using UtilityProcessHost, we may not be destroyed on
694     // the UI or IO thread.
695   }
696 
697   const std::string xml_;
698 
699   // Should be accessed only on UI thread.
700   scoped_ptr<ManifestFetchData> fetch_data_;
701   base::WeakPtr<ExtensionUpdater> updater_;
702 };
703 
704 
OnManifestFetchComplete(const GURL & url,const net::URLRequestStatus & status,int response_code,const std::string & data)705 void ExtensionUpdater::OnManifestFetchComplete(
706     const GURL& url,
707     const net::URLRequestStatus& status,
708     int response_code,
709     const std::string& data) {
710   // We want to try parsing the manifest, and if it indicates updates are
711   // available, we want to fire off requests to fetch those updates.
712   if (status.status() == net::URLRequestStatus::SUCCESS &&
713       (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
714     scoped_refptr<SafeManifestParser> safe_parser(
715         new SafeManifestParser(data, current_manifest_fetch_.release(),
716                                weak_ptr_factory_.GetWeakPtr()));
717     safe_parser->Start();
718   } else {
719     // TODO(asargent) Do exponential backoff here. (http://crbug.com/12546).
720     VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
721             << "' response code:" << response_code;
722     RemoveFromInProgress(current_manifest_fetch_->extension_ids());
723   }
724   manifest_fetcher_.reset();
725   current_manifest_fetch_.reset();
726 
727   // If we have any pending manifest requests, fire off the next one.
728   if (!manifests_pending_.empty()) {
729     ManifestFetchData* manifest_fetch = manifests_pending_.front();
730     manifests_pending_.pop_front();
731     StartUpdateCheck(manifest_fetch);
732   }
733 }
734 
HandleManifestResults(const ManifestFetchData & fetch_data,const UpdateManifest::Results * results)735 void ExtensionUpdater::HandleManifestResults(
736     const ManifestFetchData& fetch_data,
737     const UpdateManifest::Results* results) {
738   DCHECK(alive_);
739 
740   // Remove all the ids's from in_progress_ids_ (we will add them back in
741   // below if they actually have updates we need to fetch and install).
742   RemoveFromInProgress(fetch_data.extension_ids());
743 
744   if (!results) {
745     NotifyIfFinished();
746     return;
747   }
748 
749   // Examine the parsed manifest and kick off fetches of any new crx files.
750   std::vector<int> updates = DetermineUpdates(fetch_data, *results);
751   for (size_t i = 0; i < updates.size(); i++) {
752     const UpdateManifest::Result* update = &(results->list.at(updates[i]));
753     const std::string& id = update->extension_id;
754     in_progress_ids_.insert(id);
755     if (id != std::string(kBlacklistAppID))
756       NotifyUpdateFound(update->extension_id);
757     FetchUpdatedExtension(update->extension_id, update->crx_url,
758         update->package_hash, update->version);
759   }
760 
761   // If the manifest response included a <daystart> element, we want to save
762   // that value for any extensions which had sent a ping in the request.
763   if (fetch_data.base_url().DomainIs("google.com") &&
764       results->daystart_elapsed_seconds >= 0) {
765     Time daystart =
766       Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds);
767 
768     const std::set<std::string>& extension_ids = fetch_data.extension_ids();
769     std::set<std::string>::const_iterator i;
770     for (i = extension_ids.begin(); i != extension_ids.end(); i++) {
771       if (fetch_data.DidPing(*i, ManifestFetchData::ROLLCALL)) {
772         if (*i == kBlacklistAppID) {
773           extension_prefs_->SetBlacklistLastPingDay(daystart);
774         } else if (service_->GetExtensionById(*i, true) != NULL) {
775           extension_prefs_->SetLastPingDay(*i, daystart);
776         }
777       }
778       if (extension_prefs_->GetActiveBit(*i)) {
779         extension_prefs_->SetActiveBit(*i, false);
780         extension_prefs_->SetLastActivePingDay(*i, daystart);
781       }
782     }
783   }
784   NotifyIfFinished();
785 }
786 
ProcessBlacklist(const std::string & data)787 void ExtensionUpdater::ProcessBlacklist(const std::string& data) {
788   DCHECK(alive_);
789   // Verify sha256 hash value.
790   char sha256_hash_value[crypto::SHA256_LENGTH];
791   crypto::SHA256HashString(data, sha256_hash_value, crypto::SHA256_LENGTH);
792   std::string hash_in_hex = base::HexEncode(sha256_hash_value,
793                                             crypto::SHA256_LENGTH);
794 
795   if (current_extension_fetch_.package_hash != hash_in_hex) {
796     NOTREACHED() << "Fetched blacklist checksum is not as expected. "
797       << "Expected: " << current_extension_fetch_.package_hash
798       << " Actual: " << hash_in_hex;
799     return;
800   }
801   std::vector<std::string> blacklist;
802   base::SplitString(data, '\n', &blacklist);
803 
804   // Tell ExtensionService to update prefs.
805   service_->UpdateExtensionBlacklist(blacklist);
806 
807   // Update the pref value for blacklist version
808   prefs_->SetString(kExtensionBlacklistUpdateVersion,
809                     current_extension_fetch_.version);
810   prefs_->ScheduleSavePersistentPrefs();
811 }
812 
OnCRXFetchComplete(const GURL & url,const net::URLRequestStatus & status,int response_code,const std::string & data)813 void ExtensionUpdater::OnCRXFetchComplete(const GURL& url,
814                                           const net::URLRequestStatus& status,
815                                           int response_code,
816                                           const std::string& data) {
817   if (status.status() == net::URLRequestStatus::SUCCESS &&
818       (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
819     if (current_extension_fetch_.id == kBlacklistAppID) {
820       ProcessBlacklist(data);
821       in_progress_ids_.erase(current_extension_fetch_.id);
822     } else {
823       // Successfully fetched - now write crx to a file so we can have the
824       // ExtensionService install it.
825       if (!BrowserThread::PostTask(
826               BrowserThread::FILE, FROM_HERE,
827               NewRunnableMethod(
828                   file_handler_.get(),
829                   &ExtensionUpdaterFileHandler::WriteTempFile,
830                   current_extension_fetch_.id, data, url))) {
831         NOTREACHED();
832       }
833     }
834   } else {
835     // TODO(asargent) do things like exponential backoff, handling
836     // 503 Service Unavailable / Retry-After headers, etc. here.
837     // (http://crbug.com/12546).
838     VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
839             << "' response code:" << response_code;
840   }
841   extension_fetcher_.reset();
842   current_extension_fetch_ = ExtensionFetch();
843 
844   // If there are any pending downloads left, start one.
845   if (!extensions_pending_.empty()) {
846     ExtensionFetch next = extensions_pending_.front();
847     extensions_pending_.pop_front();
848     FetchUpdatedExtension(next.id, next.url, next.package_hash, next.version);
849   }
850 }
851 
OnCRXFileWritten(const std::string & id,const FilePath & path,const GURL & download_url)852 void ExtensionUpdater::OnCRXFileWritten(const std::string& id,
853                                         const FilePath& path,
854                                         const GURL& download_url) {
855   DCHECK(alive_);
856   // The ExtensionService is now responsible for cleaning up the temp file
857   // at |path|.
858   service_->UpdateExtension(id, path, download_url);
859   in_progress_ids_.erase(id);
860   NotifyIfFinished();
861 }
862 
OnCRXFileWriteError(const std::string & id)863 void ExtensionUpdater::OnCRXFileWriteError(const std::string& id) {
864   DCHECK(alive_);
865   in_progress_ids_.erase(id);
866   NotifyIfFinished();
867 }
868 
ScheduleNextCheck(const TimeDelta & target_delay)869 void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) {
870   DCHECK(alive_);
871   DCHECK(!timer_.IsRunning());
872   DCHECK(target_delay >= TimeDelta::FromSeconds(1));
873 
874   // Add +/- 10% random jitter.
875   double delay_ms = target_delay.InMillisecondsF();
876   double jitter_factor = (RandDouble() * .2) - 0.1;
877   delay_ms += delay_ms * jitter_factor;
878   TimeDelta actual_delay = TimeDelta::FromMilliseconds(
879       static_cast<int64>(delay_ms));
880 
881   // Save the time of next check.
882   Time next = Time::Now() + actual_delay;
883   prefs_->SetInt64(kNextExtensionsUpdateCheck, next.ToInternalValue());
884   prefs_->ScheduleSavePersistentPrefs();
885 
886   timer_.Start(actual_delay, this, &ExtensionUpdater::TimerFired);
887 }
888 
TimerFired()889 void ExtensionUpdater::TimerFired() {
890   DCHECK(alive_);
891   CheckNow();
892 
893   // If the user has overridden the update frequency, don't bother reporting
894   // this.
895   if (frequency_seconds_ == ExtensionService::kDefaultUpdateFrequencySeconds) {
896     Time last = Time::FromInternalValue(prefs_->GetInt64(
897         kLastExtensionsUpdateCheck));
898     if (last.ToInternalValue() != 0) {
899       // Use counts rather than time so we can use minutes rather than millis.
900       UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.UpdateCheckGap",
901           (Time::Now() - last).InMinutes(),
902           base::TimeDelta::FromSeconds(kStartupWaitSeconds).InMinutes(),
903           base::TimeDelta::FromDays(40).InMinutes(),
904           50);  // 50 buckets seems to be the default.
905     }
906   }
907 
908   // Save the last check time, and schedule the next check.
909   int64 now = Time::Now().ToInternalValue();
910   prefs_->SetInt64(kLastExtensionsUpdateCheck, now);
911   ScheduleNextCheck(TimeDelta::FromSeconds(frequency_seconds_));
912 }
913 
CheckSoon()914 void ExtensionUpdater::CheckSoon() {
915   DCHECK(alive_);
916   if (will_check_soon_) {
917     return;
918   }
919   if (BrowserThread::PostTask(
920           BrowserThread::UI, FROM_HERE,
921           method_factory_.NewRunnableMethod(
922               &ExtensionUpdater::DoCheckSoon))) {
923     will_check_soon_ = true;
924   } else {
925     NOTREACHED();
926   }
927 }
928 
WillCheckSoon() const929 bool ExtensionUpdater::WillCheckSoon() const {
930   return will_check_soon_;
931 }
932 
DoCheckSoon()933 void ExtensionUpdater::DoCheckSoon() {
934   DCHECK(will_check_soon_);
935   CheckNow();
936   will_check_soon_ = false;
937 }
938 
CheckNow()939 void ExtensionUpdater::CheckNow() {
940   DCHECK(alive_);
941   NotifyStarted();
942   ManifestFetchesBuilder fetches_builder(service_, extension_prefs_);
943 
944   const ExtensionList* extensions = service_->extensions();
945   for (ExtensionList::const_iterator iter = extensions->begin();
946        iter != extensions->end(); ++iter) {
947     fetches_builder.AddExtension(**iter);
948   }
949 
950   const PendingExtensionManager* pending_extension_manager =
951       service_->pending_extension_manager();
952 
953   PendingExtensionManager::const_iterator iter;
954   for (iter = pending_extension_manager->begin();
955        iter != pending_extension_manager->end(); iter++) {
956     // TODO(skerner): Move the determination of what gets fetched into
957     // class PendingExtensionManager.
958     Extension::Location location = iter->second.install_source();
959     if (location != Extension::EXTERNAL_PREF &&
960         location != Extension::EXTERNAL_REGISTRY)
961       fetches_builder.AddPendingExtension(iter->first, iter->second);
962   }
963 
964   fetches_builder.ReportStats();
965 
966   std::vector<ManifestFetchData*> fetches(fetches_builder.GetFetches());
967 
968   // Start a fetch of the blacklist if needed.
969   if (blacklist_checks_enabled_) {
970     // Note: it is very important that we use  the https version of the update
971     // url here to avoid DNS hijacking of the blacklist, which is not validated
972     // by a public key signature like .crx files are.
973     ManifestFetchData* blacklist_fetch =
974         new ManifestFetchData(Extension::GalleryUpdateUrl(true));
975     std::string version = prefs_->GetString(kExtensionBlacklistUpdateVersion);
976     ManifestFetchData::PingData ping_data;
977     ping_data.rollcall_days =
978         CalculatePingDays(extension_prefs_->BlacklistLastPingDay());
979     blacklist_fetch->AddExtension(kBlacklistAppID, version, ping_data, "");
980     StartUpdateCheck(blacklist_fetch);
981   }
982 
983   // Now start fetching regular extension updates
984   for (std::vector<ManifestFetchData*>::const_iterator it = fetches.begin();
985        it != fetches.end(); ++it) {
986     // StartUpdateCheck makes sure the url isn't already downloading or
987     // scheduled, so we don't need to check before calling it. Ownership of
988     // fetch is transferred here.
989     StartUpdateCheck(*it);
990   }
991   // We don't want to use fetches after this since StartUpdateCheck()
992   // takes ownership of its argument.
993   fetches.clear();
994 
995   NotifyIfFinished();
996 }
997 
GetExistingVersion(const std::string & id,std::string * version)998 bool ExtensionUpdater::GetExistingVersion(const std::string& id,
999                                           std::string* version) {
1000   DCHECK(alive_);
1001   if (id == kBlacklistAppID) {
1002     *version = prefs_->GetString(kExtensionBlacklistUpdateVersion);
1003     return true;
1004   }
1005   const Extension* extension = service_->GetExtensionById(id, false);
1006   if (!extension) {
1007     return false;
1008   }
1009   *version = extension->version()->GetString();
1010   return true;
1011 }
1012 
DetermineUpdates(const ManifestFetchData & fetch_data,const UpdateManifest::Results & possible_updates)1013 std::vector<int> ExtensionUpdater::DetermineUpdates(
1014     const ManifestFetchData& fetch_data,
1015     const UpdateManifest::Results& possible_updates) {
1016   DCHECK(alive_);
1017   std::vector<int> result;
1018 
1019   // This will only get set if one of possible_updates specifies
1020   // browser_min_version.
1021   scoped_ptr<Version> browser_version;
1022   PendingExtensionManager* pending_extension_manager =
1023       service_->pending_extension_manager();
1024 
1025   for (size_t i = 0; i < possible_updates.list.size(); i++) {
1026     const UpdateManifest::Result* update = &possible_updates.list[i];
1027 
1028     if (!fetch_data.Includes(update->extension_id))
1029       continue;
1030 
1031     if (!pending_extension_manager->IsIdPending(update->extension_id)) {
1032       // If we're not installing pending extension, and the update
1033       // version is the same or older than what's already installed,
1034       // we don't want it.
1035       std::string version;
1036       if (!GetExistingVersion(update->extension_id, &version))
1037         continue;
1038 
1039       scoped_ptr<Version> existing_version(
1040           Version::GetVersionFromString(version));
1041       scoped_ptr<Version> update_version(
1042           Version::GetVersionFromString(update->version));
1043 
1044       if (!update_version.get() ||
1045           update_version->CompareTo(*(existing_version.get())) <= 0) {
1046         continue;
1047       }
1048     }
1049 
1050     // If the update specifies a browser minimum version, do we qualify?
1051     if (update->browser_min_version.length() > 0) {
1052       // First determine the browser version if we haven't already.
1053       if (!browser_version.get()) {
1054         chrome::VersionInfo version_info;
1055         if (version_info.is_valid()) {
1056           browser_version.reset(Version::GetVersionFromString(
1057                                     version_info.Version()));
1058         }
1059       }
1060       scoped_ptr<Version> browser_min_version(
1061           Version::GetVersionFromString(update->browser_min_version));
1062       if (browser_version.get() && browser_min_version.get() &&
1063           browser_min_version->CompareTo(*browser_version.get()) > 0) {
1064         // TODO(asargent) - We may want this to show up in the extensions UI
1065         // eventually. (http://crbug.com/12547).
1066         LOG(WARNING) << "Updated version of extension " << update->extension_id
1067                      << " available, but requires chrome version "
1068                      << update->browser_min_version;
1069         continue;
1070       }
1071     }
1072     result.push_back(i);
1073   }
1074   return result;
1075 }
1076 
StartUpdateCheck(ManifestFetchData * fetch_data)1077 void ExtensionUpdater::StartUpdateCheck(ManifestFetchData* fetch_data) {
1078   AddToInProgress(fetch_data->extension_ids());
1079 
1080   scoped_ptr<ManifestFetchData> scoped_fetch_data(fetch_data);
1081   if (CommandLine::ForCurrentProcess()->HasSwitch(
1082       switches::kDisableBackgroundNetworking))
1083     return;
1084 
1085   std::deque<ManifestFetchData*>::const_iterator i;
1086   for (i = manifests_pending_.begin(); i != manifests_pending_.end(); i++) {
1087     if (fetch_data->full_url() == (*i)->full_url()) {
1088       // This url is already scheduled to be fetched.
1089       return;
1090     }
1091   }
1092 
1093   if (manifest_fetcher_.get() != NULL) {
1094     if (manifest_fetcher_->url() != fetch_data->full_url()) {
1095       manifests_pending_.push_back(scoped_fetch_data.release());
1096     }
1097   } else {
1098     UMA_HISTOGRAM_COUNTS("Extensions.UpdateCheckUrlLength",
1099         fetch_data->full_url().possibly_invalid_spec().length());
1100 
1101     current_manifest_fetch_.swap(scoped_fetch_data);
1102     manifest_fetcher_.reset(
1103         URLFetcher::Create(kManifestFetcherId, fetch_data->full_url(),
1104                            URLFetcher::GET, this));
1105     manifest_fetcher_->set_request_context(
1106         profile_->GetRequestContext());
1107     manifest_fetcher_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
1108                                       net::LOAD_DO_NOT_SAVE_COOKIES |
1109                                       net::LOAD_DISABLE_CACHE);
1110     manifest_fetcher_->Start();
1111   }
1112 }
1113 
FetchUpdatedExtension(const std::string & id,const GURL & url,const std::string & hash,const std::string & version)1114 void ExtensionUpdater::FetchUpdatedExtension(const std::string& id,
1115                                              const GURL& url,
1116                                              const std::string& hash,
1117                                              const std::string& version) {
1118   for (std::deque<ExtensionFetch>::const_iterator iter =
1119            extensions_pending_.begin();
1120        iter != extensions_pending_.end(); ++iter) {
1121     if (iter->id == id || iter->url == url) {
1122       return;  // already scheduled
1123     }
1124   }
1125 
1126   if (extension_fetcher_.get() != NULL) {
1127     if (extension_fetcher_->url() != url) {
1128       extensions_pending_.push_back(ExtensionFetch(id, url, hash, version));
1129     }
1130   } else {
1131     extension_fetcher_.reset(
1132         URLFetcher::Create(kExtensionFetcherId, url, URLFetcher::GET, this));
1133     extension_fetcher_->set_request_context(
1134         profile_->GetRequestContext());
1135     extension_fetcher_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
1136                                        net::LOAD_DO_NOT_SAVE_COOKIES |
1137                                        net::LOAD_DISABLE_CACHE);
1138     extension_fetcher_->Start();
1139     current_extension_fetch_ = ExtensionFetch(id, url, hash, version);
1140   }
1141 }
1142 
NotifyStarted()1143 void ExtensionUpdater::NotifyStarted() {
1144   NotificationService::current()->Notify(
1145       NotificationType::EXTENSION_UPDATING_STARTED,
1146       Source<Profile>(profile_),
1147       NotificationService::NoDetails());
1148 }
1149 
NotifyUpdateFound(const std::string & extension_id)1150 void ExtensionUpdater::NotifyUpdateFound(const std::string& extension_id) {
1151   NotificationService::current()->Notify(
1152       NotificationType::EXTENSION_UPDATE_FOUND,
1153       Source<Profile>(profile_),
1154       Details<const std::string>(&extension_id));
1155 }
1156 
NotifyIfFinished()1157 void ExtensionUpdater::NotifyIfFinished() {
1158   if (in_progress_ids_.empty()) {
1159     NotificationService::current()->Notify(
1160         NotificationType::EXTENSION_UPDATING_FINISHED,
1161         Source<Profile>(profile_),
1162         NotificationService::NoDetails());
1163     VLOG(1) << "Sending EXTENSION_UPDATING_FINISHED";
1164   }
1165 }
1166 
AddToInProgress(const std::set<std::string> & ids)1167 void ExtensionUpdater::AddToInProgress(const std::set<std::string>& ids) {
1168   std::set<std::string>::const_iterator i;
1169   for (i = ids.begin(); i != ids.end(); ++i)
1170     in_progress_ids_.insert(*i);
1171 }
1172 
RemoveFromInProgress(const std::set<std::string> & ids)1173 void ExtensionUpdater::RemoveFromInProgress(const std::set<std::string>& ids) {
1174   std::set<std::string>::const_iterator i;
1175   for (i = ids.begin(); i != ids.end(); ++i)
1176     in_progress_ids_.erase(*i);
1177 }
1178