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 "chrome/browser/extensions/extension_garbage_collector.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 #include <unordered_set>
11 #include <utility>
12 
13 #include "base/bind.h"
14 #include "base/check_op.h"
15 #include "base/files/file_enumerator.h"
16 #include "base/files/file_util.h"
17 #include "base/location.h"
18 #include "base/notreached.h"
19 #include "base/one_shot_event.h"
20 #include "base/sequenced_task_runner.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "base/time/time.h"
26 #include "chrome/browser/extensions/extension_garbage_collector_factory.h"
27 #include "chrome/browser/extensions/extension_service.h"
28 #include "chrome/browser/extensions/install_tracker.h"
29 #include "chrome/browser/extensions/pending_extension_manager.h"
30 #include "components/crx_file/id_util.h"
31 #include "content/public/browser/browser_context.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/storage_partition.h"
34 #include "extensions/browser/extension_file_task_runner.h"
35 #include "extensions/browser/extension_prefs.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/extension_system.h"
38 #include "extensions/browser/extension_util.h"
39 #include "extensions/common/extension.h"
40 #include "extensions/common/file_util.h"
41 #include "extensions/common/manifest_handlers/app_isolation_info.h"
42 
43 namespace extensions {
44 
45 namespace {
46 
47 // Wait this long before trying to garbage collect extensions again.
48 constexpr base::TimeDelta kGarbageCollectRetryDelay =
49     base::TimeDelta::FromSeconds(30);
50 
51 // Wait this long after startup to see if there are any extensions which can be
52 // garbage collected.
53 constexpr base::TimeDelta kGarbageCollectStartupDelay =
54     base::TimeDelta::FromSeconds(30);
55 
56 typedef std::multimap<std::string, base::FilePath> ExtensionPathsMultimap;
57 
CheckExtensionDirectory(const base::FilePath & path,const ExtensionPathsMultimap & extension_paths)58 void CheckExtensionDirectory(const base::FilePath& path,
59                              const ExtensionPathsMultimap& extension_paths) {
60   base::FilePath basename = path.BaseName();
61   // Clean up temporary files left if Chrome crashed or quit in the middle
62   // of an extension install.
63   if (basename.value() == file_util::kTempDirectoryName) {
64     base::DeletePathRecursively(path);
65     return;
66   }
67 
68   // Parse directory name as a potential extension ID.
69   std::string extension_id;
70   if (base::IsStringASCII(basename.value())) {
71     extension_id = base::UTF16ToASCII(basename.LossyDisplayName());
72     if (!crx_file::id_util::IdIsValid(extension_id))
73       extension_id.clear();
74   }
75 
76   // Delete directories that aren't valid IDs.
77   if (extension_id.empty()) {
78     base::DeletePathRecursively(path);
79     return;
80   }
81 
82   typedef ExtensionPathsMultimap::const_iterator Iter;
83   std::pair<Iter, Iter> iter_pair = extension_paths.equal_range(extension_id);
84 
85   // If there is no entry in the prefs file, just delete the directory and
86   // move on. This can legitimately happen when an uninstall does not
87   // complete, for example, when a plugin is in use at uninstall time.
88   if (iter_pair.first == iter_pair.second) {
89     base::DeletePathRecursively(path);
90     return;
91   }
92 
93   // Clean up old version directories.
94   base::FileEnumerator versions_enumerator(
95       path, false /* Not recursive */, base::FileEnumerator::DIRECTORIES);
96   for (base::FilePath version_dir = versions_enumerator.Next();
97        !version_dir.empty();
98        version_dir = versions_enumerator.Next()) {
99     bool known_version = false;
100     for (auto iter = iter_pair.first; iter != iter_pair.second; ++iter) {
101       if (version_dir.BaseName() == iter->second.BaseName()) {
102         known_version = true;
103         break;
104       }
105     }
106     if (!known_version)
107       base::DeletePathRecursively(version_dir);
108   }
109 }
110 
111 }  // namespace
112 
ExtensionGarbageCollector(content::BrowserContext * context)113 ExtensionGarbageCollector::ExtensionGarbageCollector(
114     content::BrowserContext* context)
115     : context_(context), crx_installs_in_progress_(0) {
116   ExtensionSystem* extension_system = ExtensionSystem::Get(context_);
117   DCHECK(extension_system);
118 
119   extension_system->ready().PostDelayed(
120       FROM_HERE,
121       base::BindOnce(&ExtensionGarbageCollector::GarbageCollectExtensions,
122                      weak_factory_.GetWeakPtr()),
123       kGarbageCollectStartupDelay);
124 
125   extension_system->ready().Post(
126       FROM_HERE,
127       base::BindOnce(
128           &ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded,
129           weak_factory_.GetWeakPtr()));
130 
131   InstallTracker::Get(context_)->AddObserver(this);
132 }
133 
~ExtensionGarbageCollector()134 ExtensionGarbageCollector::~ExtensionGarbageCollector() {}
135 
136 // static
Get(content::BrowserContext * context)137 ExtensionGarbageCollector* ExtensionGarbageCollector::Get(
138     content::BrowserContext* context) {
139   return ExtensionGarbageCollectorFactory::GetForBrowserContext(context);
140 }
141 
Shutdown()142 void ExtensionGarbageCollector::Shutdown() {
143   InstallTracker::Get(context_)->RemoveObserver(this);
144 }
145 
GarbageCollectExtensionsForTest()146 void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() {
147   GarbageCollectExtensions();
148 }
149 
150 // static
GarbageCollectExtensionsOnFileThread(const base::FilePath & install_directory,const ExtensionPathsMultimap & extension_paths)151 void ExtensionGarbageCollector::GarbageCollectExtensionsOnFileThread(
152     const base::FilePath& install_directory,
153     const ExtensionPathsMultimap& extension_paths) {
154   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
155 
156   // Nothing to clean up if it doesn't exist.
157   if (!base::DirectoryExists(install_directory))
158     return;
159 
160   base::FileEnumerator enumerator(install_directory,
161                                   false,  // Not recursive.
162                                   base::FileEnumerator::DIRECTORIES);
163 
164   for (base::FilePath extension_path = enumerator.Next();
165        !extension_path.empty();
166        extension_path = enumerator.Next()) {
167     CheckExtensionDirectory(extension_path, extension_paths);
168   }
169 }
170 
GarbageCollectExtensions()171 void ExtensionGarbageCollector::GarbageCollectExtensions() {
172   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
173 
174   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
175   DCHECK(extension_prefs);
176 
177   if (extension_prefs->pref_service()->ReadOnly())
178     return;
179 
180   if (crx_installs_in_progress_ > 0) {
181     // Don't garbage collect while there are installations in progress,
182     // which may be using the temporary installation directory. Try to garbage
183     // collect again later.
184     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
185         FROM_HERE,
186         base::BindOnce(&ExtensionGarbageCollector::GarbageCollectExtensions,
187                        weak_factory_.GetWeakPtr()),
188         kGarbageCollectRetryDelay);
189     return;
190   }
191 
192   std::unique_ptr<ExtensionPrefs::ExtensionsInfo> info(
193       extension_prefs->GetInstalledExtensionsInfo());
194   std::multimap<std::string, base::FilePath> extension_paths;
195   for (size_t i = 0; i < info->size(); ++i) {
196     extension_paths.insert(
197         std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
198   }
199 
200   info = extension_prefs->GetAllDelayedInstallInfo();
201   for (size_t i = 0; i < info->size(); ++i) {
202     extension_paths.insert(
203         std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
204   }
205 
206   ExtensionService* service =
207       ExtensionSystem::Get(context_)->extension_service();
208   if (!GetExtensionFileTaskRunner()->PostTask(
209           FROM_HERE,
210           base::BindOnce(&GarbageCollectExtensionsOnFileThread,
211                          service->install_directory(), extension_paths))) {
212     NOTREACHED();
213   }
214 }
215 
GarbageCollectIsolatedStorageIfNeeded()216 void ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded() {
217   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
218 
219   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
220   DCHECK(extension_prefs);
221   if (!extension_prefs->NeedsStorageGarbageCollection())
222     return;
223   extension_prefs->SetNeedsStorageGarbageCollection(false);
224 
225   std::unique_ptr<std::unordered_set<base::FilePath>> active_paths(
226       new std::unordered_set<base::FilePath>());
227   std::unique_ptr<ExtensionSet> extensions =
228       ExtensionRegistry::Get(context_)->GenerateInstalledExtensionsSet();
229   for (ExtensionSet::const_iterator iter = extensions->begin();
230        iter != extensions->end();
231        ++iter) {
232     if (AppIsolationInfo::HasIsolatedStorage(iter->get())) {
233       active_paths->insert(
234           util::GetStoragePartitionForExtensionId((*iter)->id(), context_)
235               ->GetPath());
236     }
237   }
238 
239   DCHECK(!installs_delayed_for_gc_);
240   installs_delayed_for_gc_ = true;
241   content::BrowserContext::GarbageCollectStoragePartitions(
242       context_, std::move(active_paths),
243       base::BindOnce(
244           &ExtensionGarbageCollector::OnGarbageCollectIsolatedStorageFinished,
245           weak_factory_.GetWeakPtr()));
246 }
247 
OnGarbageCollectIsolatedStorageFinished()248 void ExtensionGarbageCollector::OnGarbageCollectIsolatedStorageFinished() {
249   DCHECK(installs_delayed_for_gc_);
250   installs_delayed_for_gc_ = false;
251 
252   ExtensionSystem::Get(context_)
253       ->extension_service()
254       ->MaybeFinishDelayedInstallations();
255 }
256 
OnBeginCrxInstall(const std::string & extension_id)257 void ExtensionGarbageCollector::OnBeginCrxInstall(
258     const std::string& extension_id) {
259   crx_installs_in_progress_++;
260 }
261 
OnFinishCrxInstall(const std::string & extension_id,bool success)262 void ExtensionGarbageCollector::OnFinishCrxInstall(
263     const std::string& extension_id,
264     bool success) {
265   crx_installs_in_progress_--;
266   if (crx_installs_in_progress_ < 0) {
267     // This can only happen if there is a mismatch in our begin/finish
268     // accounting.
269     NOTREACHED();
270 
271     // Don't let the count go negative to avoid garbage collecting when
272     // an install is actually in progress.
273     crx_installs_in_progress_ = 0;
274   }
275 }
276 
ShouldDelay(const Extension * extension,bool install_immediately)277 InstallGate::Action ExtensionGarbageCollector::ShouldDelay(
278     const Extension* extension,
279     bool install_immediately) {
280   return installs_delayed_for_gc_ ? DELAY : INSTALL;
281 }
282 
283 }  // namespace extensions
284