1// Copyright (c) 2013 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/chromeos/extensions/external_cache.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/file_util.h"
11#include "base/files/file_enumerator.h"
12#include "base/location.h"
13#include "base/logging.h"
14#include "base/strings/string_util.h"
15#include "base/values.h"
16#include "base/version.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/extensions/crx_installer.h"
19#include "chrome/browser/extensions/external_provider_impl.h"
20#include "chrome/browser/extensions/updater/extension_downloader.h"
21#include "chrome/common/extensions/extension_constants.h"
22#include "content/public/browser/notification_details.h"
23#include "content/public/browser/notification_service.h"
24#include "content/public/browser/notification_source.h"
25#include "extensions/common/extension.h"
26#include "net/url_request/url_request_context_getter.h"
27
28namespace chromeos {
29
30ExternalCache::ExternalCache(const base::FilePath& cache_dir,
31                             net::URLRequestContextGetter* request_context,
32                             const scoped_refptr<base::SequencedTaskRunner>&
33                                 backend_task_runner,
34                             Delegate* delegate,
35                             bool always_check_updates,
36                             bool wait_for_cache_initialization)
37    : local_cache_(cache_dir, 0, base::TimeDelta(), backend_task_runner),
38      request_context_(request_context),
39      backend_task_runner_(backend_task_runner),
40      delegate_(delegate),
41      always_check_updates_(always_check_updates),
42      wait_for_cache_initialization_(wait_for_cache_initialization),
43      cached_extensions_(new base::DictionaryValue()),
44      weak_ptr_factory_(this) {
45  notification_registrar_.Add(
46      this,
47      chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
48      content::NotificationService::AllBrowserContextsAndSources());
49}
50
51ExternalCache::~ExternalCache() {
52}
53
54void ExternalCache::Shutdown(const base::Closure& callback) {
55  local_cache_.Shutdown(callback);
56}
57
58void ExternalCache::UpdateExtensionsList(
59    scoped_ptr<base::DictionaryValue> prefs) {
60  extensions_ = prefs.Pass();
61
62  if (extensions_->empty()) {
63    // If list of know extensions is empty, don't init cache on disk. It is
64    // important shortcut for test to don't wait forever for cache dir
65    // initialization that should happen outside of Chrome on real device.
66    cached_extensions_->Clear();
67    UpdateExtensionLoader();
68    return;
69  }
70
71  if (local_cache_.is_uninitialized()) {
72    local_cache_.Init(wait_for_cache_initialization_,
73                      base::Bind(&ExternalCache::CheckCache,
74                                 weak_ptr_factory_.GetWeakPtr()));
75  } else {
76    CheckCache();
77  }
78}
79
80void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
81  for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
82       !it.IsAtEnd(); it.Advance()) {
83    const base::DictionaryValue* entry = NULL;
84    if (!it.value().GetAsDictionary(&entry)) {
85      NOTREACHED() << "ExternalCache found bad entry with type "
86                   << it.value().GetType();
87      continue;
88    }
89
90    std::string external_crx;
91    if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
92                         &external_crx) &&
93        external_crx == path.value()) {
94      std::string id = it.key();
95      LOG(ERROR) << "ExternalCache extension at " << path.value()
96                 << " failed to install, deleting it.";
97      cached_extensions_->Remove(id, NULL);
98      extensions_->Remove(id, NULL);
99
100      local_cache_.RemoveExtension(id);
101      UpdateExtensionLoader();
102
103      // Don't try to DownloadMissingExtensions() from here,
104      // since it can cause a fail/retry loop.
105      return;
106    }
107  }
108  LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
109}
110
111void ExternalCache::RemoveExtensions(const std::vector<std::string>& ids) {
112  if (ids.empty())
113    return;
114
115  for (size_t i = 0; i < ids.size(); ++i) {
116    cached_extensions_->Remove(ids[i], NULL);
117    extensions_->Remove(ids[i], NULL);
118    local_cache_.RemoveExtension(ids[i]);
119  }
120  UpdateExtensionLoader();
121}
122
123bool ExternalCache::GetExtension(const std::string& id,
124                                 base::FilePath* file_path,
125                                 std::string* version) {
126  return local_cache_.GetExtension(id, file_path, version);
127}
128
129void ExternalCache::Observe(int type,
130                            const content::NotificationSource& source,
131                            const content::NotificationDetails& details) {
132  switch (type) {
133    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
134      extensions::CrxInstaller* installer =
135          content::Source<extensions::CrxInstaller>(source).ptr();
136      OnDamagedFileDetected(installer->source_file());
137      break;
138    }
139
140    default:
141      NOTREACHED();
142  }
143}
144
145void ExternalCache::OnExtensionDownloadFailed(
146    const std::string& id,
147    extensions::ExtensionDownloaderDelegate::Error error,
148    const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
149    const std::set<int>& request_ids) {
150  if (error == NO_UPDATE_AVAILABLE) {
151    if (!cached_extensions_->HasKey(id)) {
152      LOG(ERROR) << "ExternalCache extension " << id
153                 << " not found on update server";
154      delegate_->OnExtensionDownloadFailed(id, error);
155    } else {
156      delegate_->OnExtensionLoadedInCache(id);
157    }
158  } else {
159    LOG(ERROR) << "ExternalCache failed to download extension " << id
160               << ", error " << error;
161    delegate_->OnExtensionDownloadFailed(id, error);
162  }
163}
164
165void ExternalCache::OnExtensionDownloadFinished(
166    const std::string& id,
167    const base::FilePath& path,
168    bool file_ownership_passed,
169    const GURL& download_url,
170    const std::string& version,
171    const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
172    const std::set<int>& request_ids) {
173  DCHECK(file_ownership_passed);
174  local_cache_.PutExtension(id, path, version,
175                            base::Bind(&ExternalCache::OnPutExtension,
176                                       weak_ptr_factory_.GetWeakPtr(),
177                                       id));
178}
179
180bool ExternalCache::IsExtensionPending(const std::string& id) {
181  // Pending means that there is no installed version yet.
182  return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
183}
184
185bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
186                                                std::string* version) {
187  base::DictionaryValue* extension_dictionary = NULL;
188  if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
189    if (extension_dictionary->GetString(
190            extensions::ExternalProviderImpl::kExternalVersion, version)) {
191      return true;
192    }
193    *version = delegate_->GetInstalledExtensionVersion(id);
194    return !version->empty();
195  }
196  return false;
197}
198
199void ExternalCache::UpdateExtensionLoader() {
200  VLOG(1) << "Notify ExternalCache delegate about cache update";
201  if (delegate_)
202    delegate_->OnExtensionListsUpdated(cached_extensions_.get());
203}
204
205void ExternalCache::CheckCache() {
206  if (local_cache_.is_shutdown())
207    return;
208
209  // If request_context_ is missing we can't download anything.
210  if (!downloader_ && request_context_) {
211    downloader_.reset(
212        new extensions::ExtensionDownloader(this, request_context_));
213  }
214
215  cached_extensions_->Clear();
216  for (base::DictionaryValue::Iterator it(*extensions_.get());
217       !it.IsAtEnd(); it.Advance()) {
218    const base::DictionaryValue* entry = NULL;
219    if (!it.value().GetAsDictionary(&entry)) {
220      LOG(ERROR) << "ExternalCache found bad entry with type "
221                 << it.value().GetType();
222      continue;
223    }
224
225    bool keep_if_present =
226        entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
227    std::string external_update_url;
228    entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
229                     &external_update_url);
230    if (downloader_ && !keep_if_present) {
231      GURL update_url;
232      if (!external_update_url.empty())
233        update_url = GURL(external_update_url);
234      else if (always_check_updates_)
235        update_url = extension_urls::GetWebstoreUpdateUrl();
236
237      if (update_url.is_valid())
238        downloader_->AddPendingExtension(it.key(), update_url, 0);
239    }
240
241    base::FilePath file_path;
242    std::string version;
243    if (local_cache_.GetExtension(it.key(), &file_path, &version)) {
244      // Copy entry to don't modify it inside extensions_.
245      base::DictionaryValue* entry_copy = entry->DeepCopy();
246
247      if (extension_urls::IsWebstoreUpdateUrl(GURL(external_update_url))) {
248        entry_copy->SetBoolean(
249            extensions::ExternalProviderImpl::kIsFromWebstore, true);
250      }
251      entry_copy->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl,
252                         NULL);
253      entry_copy->SetString(extensions::ExternalProviderImpl::kExternalVersion,
254                            version);
255      entry_copy->SetString(extensions::ExternalProviderImpl::kExternalCrx,
256                            file_path.value());
257      cached_extensions_->Set(it.key(), entry_copy);
258    } else {
259      bool has_external_crx = entry->HasKey(
260          extensions::ExternalProviderImpl::kExternalCrx);
261      bool is_already_installed =
262          !delegate_->GetInstalledExtensionVersion(it.key()).empty();
263      if (keep_if_present || has_external_crx || is_already_installed) {
264        // Copy entry to don't modify it inside extensions_.
265        cached_extensions_->Set(it.key(), entry->DeepCopy());
266      }
267    }
268  }
269
270  if (downloader_)
271    downloader_->StartAllPending(NULL);
272
273  VLOG(1) << "Updated ExternalCache, there are "
274          << cached_extensions_->size() << " extensions cached";
275
276  UpdateExtensionLoader();
277}
278
279void ExternalCache::OnPutExtension(const std::string& id,
280                                   const base::FilePath& file_path,
281                                   bool file_ownership_passed) {
282  if (local_cache_.is_shutdown() || file_ownership_passed) {
283    backend_task_runner_->PostTask(FROM_HERE,
284        base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, true));
285    return;
286  }
287
288  VLOG(1) << "ExternalCache installed a new extension in the cache " << id;
289
290  base::DictionaryValue* entry = NULL;
291  if (!extensions_->GetDictionary(id, &entry)) {
292    LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
293    return;
294  }
295
296  // Copy entry to don't modify it inside extensions_.
297  entry = entry->DeepCopy();
298
299  std::string version;
300  if (!local_cache_.GetExtension(id, NULL, &version)) {
301    // Copy entry to don't modify it inside extensions_.
302    LOG(ERROR) << "Can't find installed extension in cache " << id;
303    return;
304  }
305
306  std::string update_url;
307  if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
308                       &update_url) &&
309      extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
310    entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
311  }
312  entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
313  entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
314  entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
315                   file_path.value());
316
317  cached_extensions_->Set(id, entry);
318  if (delegate_)
319    delegate_->OnExtensionLoadedInCache(id);
320  UpdateExtensionLoader();
321}
322
323std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
324    const std::string& id) {
325  return std::string();
326}
327
328}  // namespace chromeos
329