| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/extensions/external_cache_impl.h" |
| |
| #include <stddef.h> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "chrome/browser/chromeos/extensions/external_cache_delegate.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/extensions/external_provider_impl.h" |
| #include "chrome/browser/extensions/updater/chrome_extension_downloader_factory.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/browser/updater/extension_downloader.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_urls.h" |
| #include "extensions/common/manifest.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| void FlushFile(const base::FilePath& path) { |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE); |
| file.Flush(); |
| file.Close(); |
| } |
| |
| // Wraps a base::OnceCallback with a base::Callback, which can be passed as a |
| // callback to extensions::LocalExtensionCache::PutExtension(). |
| // TODO(tbarzic): Remove this when LocalExtensionCache starts using |
| // OnceCallback. |
| void WrapPutExtensionCallback( |
| base::OnceCallback<void(const base::FilePath&, bool)> callback, |
| const base::FilePath& file_path, |
| bool file_ownership_passed) { |
| if (callback) |
| std::move(callback).Run(file_path, file_ownership_passed); |
| } |
| |
| // Wraps OnceClosure with a base::Closure, so it can be passed to |
| // extensions::LocalExtensionCache::Shutdown. |
| // TODO(tbarzic): Remove this when LocakExtensionCache starts using OnceClosure. |
| void WrapOnceClosure(base::OnceClosure closure) { |
| if (closure) |
| std::move(closure).Run(); |
| } |
| |
| } // namespace |
| |
| ExternalCacheImpl::ExternalCacheImpl( |
| const base::FilePath& cache_dir, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner, |
| ExternalCacheDelegate* delegate, |
| bool always_check_updates, |
| bool wait_for_cache_initialization) |
| : local_cache_(cache_dir, 0, base::TimeDelta(), backend_task_runner), |
| url_loader_factory_(std::move(url_loader_factory)), |
| backend_task_runner_(backend_task_runner), |
| delegate_(delegate), |
| always_check_updates_(always_check_updates), |
| wait_for_cache_initialization_(wait_for_cache_initialization), |
| cached_extensions_(new base::DictionaryValue()), |
| weak_ptr_factory_(this) { |
| notification_registrar_.Add( |
| this, extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| } |
| |
| ExternalCacheImpl::~ExternalCacheImpl() = default; |
| |
| const base::DictionaryValue* ExternalCacheImpl::GetCachedExtensions() { |
| return cached_extensions_.get(); |
| } |
| |
| void ExternalCacheImpl::Shutdown(base::OnceClosure callback) { |
| local_cache_.Shutdown( |
| base::Bind(&WrapOnceClosure, base::Passed(std::move(callback)))); |
| } |
| |
| void ExternalCacheImpl::UpdateExtensionsList( |
| std::unique_ptr<base::DictionaryValue> prefs) { |
| extensions_ = std::move(prefs); |
| |
| if (extensions_->empty()) { |
| // If list of know extensions is empty, don't init cache on disk. It is |
| // important shortcut for test to don't wait forever for cache dir |
| // initialization that should happen outside of Chrome on real device. |
| cached_extensions_->Clear(); |
| UpdateExtensionLoader(); |
| return; |
| } |
| |
| if (local_cache_.is_uninitialized()) { |
| local_cache_.Init(wait_for_cache_initialization_, |
| base::Bind(&ExternalCacheImpl::CheckCache, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| CheckCache(); |
| } |
| } |
| |
| void ExternalCacheImpl::OnDamagedFileDetected(const base::FilePath& path) { |
| for (base::DictionaryValue::Iterator it(*cached_extensions_.get()); |
| !it.IsAtEnd(); it.Advance()) { |
| const base::DictionaryValue* entry = NULL; |
| if (!it.value().GetAsDictionary(&entry)) { |
| NOTREACHED() << "ExternalCacheImpl found bad entry with type " |
| << it.value().type(); |
| continue; |
| } |
| |
| std::string external_crx; |
| if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx, |
| &external_crx) && |
| external_crx == path.value()) { |
| std::string id = it.key(); |
| LOG(ERROR) << "ExternalCacheImpl extension at " << path.value() |
| << " failed to install, deleting it."; |
| cached_extensions_->Remove(id, NULL); |
| extensions_->Remove(id, NULL); |
| |
| local_cache_.RemoveExtension(id, std::string()); |
| UpdateExtensionLoader(); |
| |
| // Don't try to DownloadMissingExtensions() from here, |
| // since it can cause a fail/retry loop. |
| return; |
| } |
| } |
| DLOG(ERROR) << "ExternalCacheImpl cannot find external_crx " << path.value(); |
| } |
| |
| void ExternalCacheImpl::RemoveExtensions(const std::vector<std::string>& ids) { |
| if (ids.empty()) |
| return; |
| |
| for (size_t i = 0; i < ids.size(); ++i) { |
| cached_extensions_->Remove(ids[i], NULL); |
| extensions_->Remove(ids[i], NULL); |
| local_cache_.RemoveExtension(ids[i], std::string()); |
| } |
| UpdateExtensionLoader(); |
| } |
| |
| bool ExternalCacheImpl::GetExtension(const std::string& id, |
| base::FilePath* file_path, |
| std::string* version) { |
| return local_cache_.GetExtension(id, std::string(), file_path, version); |
| } |
| |
| bool ExternalCacheImpl::ExtensionFetchPending(const std::string& id) { |
| return extensions_->HasKey(id) && !cached_extensions_->HasKey(id); |
| } |
| |
| void ExternalCacheImpl::PutExternalExtension( |
| const std::string& id, |
| const base::FilePath& crx_file_path, |
| const std::string& version, |
| PutExternalExtensionCallback callback) { |
| local_cache_.PutExtension( |
| id, std::string(), crx_file_path, version, |
| base::Bind( |
| &WrapPutExtensionCallback, |
| base::Passed(base::BindOnce( |
| &ExternalCacheImpl::OnPutExternalExtension, |
| weak_ptr_factory_.GetWeakPtr(), id, std::move(callback))))); |
| } |
| |
| void ExternalCacheImpl::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, type); |
| |
| extensions::CrxInstaller* installer = |
| content::Source<extensions::CrxInstaller>(source).ptr(); |
| OnDamagedFileDetected(installer->source_file()); |
| } |
| |
| void ExternalCacheImpl::OnExtensionDownloadFailed( |
| const std::string& id, |
| extensions::ExtensionDownloaderDelegate::Error error, |
| const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, |
| const std::set<int>& request_ids) { |
| if (error == NO_UPDATE_AVAILABLE) { |
| if (!cached_extensions_->HasKey(id)) { |
| LOG(ERROR) << "ExternalCacheImpl extension " << id |
| << " not found on update server"; |
| delegate_->OnExtensionDownloadFailed(id); |
| } else { |
| // No version update for an already cached extension. |
| delegate_->OnExtensionLoadedInCache(id); |
| } |
| } else { |
| LOG(ERROR) << "ExternalCacheImpl failed to download extension " << id |
| << ", error " << error; |
| delegate_->OnExtensionDownloadFailed(id); |
| } |
| } |
| |
| void ExternalCacheImpl::OnExtensionDownloadFinished( |
| const extensions::CRXFileInfo& file, |
| bool file_ownership_passed, |
| const GURL& download_url, |
| const std::string& version, |
| const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, |
| const std::set<int>& request_ids, |
| const InstallCallback& callback) { |
| DCHECK(file_ownership_passed); |
| local_cache_.PutExtension( |
| file.extension_id, file.expected_hash, file.path, version, |
| base::Bind(&ExternalCacheImpl::OnPutExtension, |
| weak_ptr_factory_.GetWeakPtr(), file.extension_id)); |
| if (!callback.is_null()) |
| callback.Run(true); |
| } |
| |
| bool ExternalCacheImpl::IsExtensionPending(const std::string& id) { |
| return ExtensionFetchPending(id); |
| } |
| |
| bool ExternalCacheImpl::GetExtensionExistingVersion(const std::string& id, |
| std::string* version) { |
| base::DictionaryValue* extension_dictionary = NULL; |
| if (cached_extensions_->GetDictionary(id, &extension_dictionary)) { |
| if (extension_dictionary->GetString( |
| extensions::ExternalProviderImpl::kExternalVersion, version)) { |
| return true; |
| } |
| *version = delegate_->GetInstalledExtensionVersion(id); |
| return !version->empty(); |
| } |
| return false; |
| } |
| |
| service_manager::Connector* ExternalCacheImpl::GetConnector() { |
| if (use_null_connector_) |
| return nullptr; |
| return content::ServiceManagerConnection::GetForProcess()->GetConnector(); |
| } |
| |
| void ExternalCacheImpl::UpdateExtensionLoader() { |
| VLOG(1) << "Notify ExternalCacheImpl delegate about cache update"; |
| if (delegate_) |
| delegate_->OnExtensionListsUpdated(cached_extensions_.get()); |
| } |
| |
| void ExternalCacheImpl::CheckCache() { |
| if (local_cache_.is_shutdown()) |
| return; |
| |
| // If url_loader_factory_ is missing we can't download anything. |
| if (url_loader_factory_) { |
| downloader_ = ChromeExtensionDownloaderFactory::CreateForURLLoaderFactory( |
| url_loader_factory_, this, GetConnector()); |
| } |
| |
| cached_extensions_->Clear(); |
| for (const auto& entry : extensions_->DictItems()) { |
| if (!entry.second.is_dict()) { |
| LOG(ERROR) << "ExternalCacheImpl found bad entry with type " |
| << entry.second.type(); |
| continue; |
| } |
| |
| if (downloader_) { |
| GURL update_url = |
| GetExtensionUpdateUrl(entry.second, always_check_updates_); |
| |
| if (update_url.is_valid()) { |
| downloader_->AddPendingExtension( |
| entry.first, update_url, extensions::Manifest::EXTERNAL_POLICY, |
| false, 0, extensions::ManifestFetchData::FetchPriority::BACKGROUND); |
| } |
| } |
| |
| base::FilePath file_path; |
| std::string version; |
| std::string hash; |
| if (local_cache_.GetExtension(entry.first, hash, &file_path, &version)) { |
| cached_extensions_->SetKey( |
| entry.first, |
| GetExtensionValueToCache(entry.second, file_path.value(), version)); |
| } else if (ShouldCacheImmediately( |
| entry.second, |
| delegate_->GetInstalledExtensionVersion(entry.first))) { |
| cached_extensions_->SetKey(entry.first, entry.second.Clone()); |
| } |
| } |
| |
| if (downloader_) |
| downloader_->StartAllPending(NULL); |
| |
| VLOG(1) << "Updated ExternalCacheImpl, there are " |
| << cached_extensions_->size() << " extensions cached"; |
| |
| UpdateExtensionLoader(); |
| } |
| |
| void ExternalCacheImpl::OnPutExtension(const std::string& id, |
| const base::FilePath& file_path, |
| bool file_ownership_passed) { |
| if (local_cache_.is_shutdown() || file_ownership_passed) { |
| backend_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&base::DeleteFile), file_path, true)); |
| return; |
| } |
| |
| VLOG(1) << "ExternalCacheImpl installed a new extension in the cache " << id; |
| |
| const base::Value* original_entry = |
| extensions_->FindKeyOfType(id, base::Value::Type::DICTIONARY); |
| if (!original_entry) { |
| LOG(ERROR) << "ExternalCacheImpl cannot find entry for extension " << id; |
| return; |
| } |
| |
| std::string version; |
| std::string hash; |
| if (!local_cache_.GetExtension(id, hash, NULL, &version)) { |
| // Copy entry to don't modify it inside extensions_. |
| LOG(ERROR) << "Can't find installed extension in cache " << id; |
| return; |
| } |
| |
| if (flush_on_put_) { |
| backend_task_runner_->PostTask(FROM_HERE, |
| base::BindOnce(&FlushFile, file_path)); |
| } |
| |
| cached_extensions_->SetKey( |
| id, |
| GetExtensionValueToCache(*original_entry, file_path.value(), version)); |
| |
| if (delegate_) |
| delegate_->OnExtensionLoadedInCache(id); |
| UpdateExtensionLoader(); |
| } |
| |
| void ExternalCacheImpl::OnPutExternalExtension( |
| const std::string& id, |
| PutExternalExtensionCallback callback, |
| const base::FilePath& file_path, |
| bool file_ownership_passed) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| OnPutExtension(id, file_path, file_ownership_passed); |
| std::move(callback).Run(id, !file_ownership_passed); |
| } |
| |
| } // namespace chromeos |