Roll src/third_party/WebKit f298044:aa8346d (svn 202628:202629)
[chromium-blink-merge.git] / chrome / browser / extensions / extension_assets_manager_chromeos.cc
blob8fc3425688318461dda6ab319a52a31cd002235e
1 // Copyright (c) 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.
5 #include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
7 #include <map>
8 #include <vector>
10 #include "base/command_line.h"
11 #include "base/files/file_util.h"
12 #include "base/memory/singleton.h"
13 #include "base/prefs/pref_registry_simple.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/prefs/scoped_user_pref_update.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/sys_info.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chromeos/profiles/profile_helper.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chromeos/chromeos_switches.h"
23 #include "components/user_manager/user_manager.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "extensions/browser/extension_prefs.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/extension_urls.h"
29 #include "extensions/common/file_util.h"
30 #include "extensions/common/manifest.h"
31 #include "extensions/common/manifest_url_handlers.h"
33 using content::BrowserThread;
35 namespace extensions {
36 namespace {
38 // Path to shared extensions install dir.
39 const char kSharedExtensionsDir[] = "/var/cache/shared_extensions";
41 // Shared install dir overrider for tests only.
42 static const base::FilePath* g_shared_install_dir_override = NULL;
44 // This helper class lives on UI thread only. Main purpose of this class is to
45 // track shared installation in progress between multiple profiles.
46 class ExtensionAssetsManagerHelper {
47 public:
48 // Info about pending install request.
49 struct PendingInstallInfo {
50 base::FilePath unpacked_extension_root;
51 base::FilePath local_install_dir;
52 Profile* profile;
53 ExtensionAssetsManager::InstallExtensionCallback callback;
55 typedef std::vector<PendingInstallInfo> PendingInstallList;
57 static ExtensionAssetsManagerHelper* GetInstance() {
58 DCHECK_CURRENTLY_ON(BrowserThread::UI);
59 return base::Singleton<ExtensionAssetsManagerHelper>::get();
62 // Remember that shared install is in progress. Return true if there is no
63 // other installs for given id and version.
64 bool RecordSharedInstall(
65 const std::string& id,
66 const std::string& version,
67 const base::FilePath& unpacked_extension_root,
68 const base::FilePath& local_install_dir,
69 Profile* profile,
70 ExtensionAssetsManager::InstallExtensionCallback callback) {
71 PendingInstallInfo install_info;
72 install_info.unpacked_extension_root = unpacked_extension_root;
73 install_info.local_install_dir = local_install_dir;
74 install_info.profile = profile;
75 install_info.callback = callback;
77 std::vector<PendingInstallInfo>& callbacks =
78 install_queue_[InstallQueue::key_type(id, version)];
79 callbacks.push_back(install_info);
81 return callbacks.size() == 1;
84 // Remove record about shared installation in progress and return
85 // |pending_installs|.
86 void SharedInstallDone(const std::string& id,
87 const std::string& version,
88 PendingInstallList* pending_installs) {
89 InstallQueue::iterator it = install_queue_.find(
90 InstallQueue::key_type(id, version));
91 DCHECK(it != install_queue_.end());
92 pending_installs->swap(it->second);
93 install_queue_.erase(it);
96 private:
97 friend struct base::DefaultSingletonTraits<ExtensionAssetsManagerHelper>;
99 ExtensionAssetsManagerHelper() {}
100 ~ExtensionAssetsManagerHelper() {}
102 // Extension ID + version pair.
103 typedef std::pair<std::string, std::string> InstallItem;
105 // Queue of pending installs in progress.
106 typedef std::map<InstallItem, std::vector<PendingInstallInfo> > InstallQueue;
108 InstallQueue install_queue_;
110 DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerHelper);
113 } // namespace
115 const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] =
116 "SharedExtensions";
118 const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path";
120 const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users";
122 ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
124 ExtensionAssetsManagerChromeOS::~ExtensionAssetsManagerChromeOS() {
125 if (g_shared_install_dir_override) {
126 delete g_shared_install_dir_override;
127 g_shared_install_dir_override = NULL;
131 // static
132 ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() {
133 return base::Singleton<ExtensionAssetsManagerChromeOS>::get();
136 // static
137 void ExtensionAssetsManagerChromeOS::RegisterPrefs(
138 PrefRegistrySimple* registry) {
139 registry->RegisterDictionaryPref(kSharedExtensions);
142 void ExtensionAssetsManagerChromeOS::InstallExtension(
143 const Extension* extension,
144 const base::FilePath& unpacked_extension_root,
145 const base::FilePath& local_install_dir,
146 Profile* profile,
147 InstallExtensionCallback callback) {
148 if (!CanShareAssets(extension, unpacked_extension_root)) {
149 InstallLocalExtension(extension->id(),
150 extension->VersionString(),
151 unpacked_extension_root,
152 local_install_dir,
153 callback);
154 return;
157 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
158 base::Bind(&ExtensionAssetsManagerChromeOS::CheckSharedExtension,
159 extension->id(),
160 extension->VersionString(),
161 unpacked_extension_root,
162 local_install_dir,
163 profile,
164 callback));
167 void ExtensionAssetsManagerChromeOS::UninstallExtension(
168 const std::string& id,
169 Profile* profile,
170 const base::FilePath& local_install_dir,
171 const base::FilePath& extension_root) {
172 if (local_install_dir.IsParent(extension_root)) {
173 file_util::UninstallExtension(local_install_dir, id);
174 return;
177 if (GetSharedInstallDir().IsParent(extension_root)) {
178 // In some test extensions installed outside local_install_dir emulate
179 // previous behavior that just do nothing in this case.
180 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
181 base::Bind(&ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused,
183 profile));
187 // static
188 base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() {
189 if (g_shared_install_dir_override)
190 return *g_shared_install_dir_override;
191 else
192 return base::FilePath(kSharedExtensionsDir);
195 // static
196 bool ExtensionAssetsManagerChromeOS::IsSharedInstall(
197 const Extension* extension) {
198 return GetSharedInstallDir().IsParent(extension->path());
201 // static
202 bool ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions(
203 std::multimap<std::string, base::FilePath>* live_extension_paths) {
204 DCHECK_CURRENTLY_ON(BrowserThread::UI);
206 PrefService* local_state = g_browser_process->local_state();
207 // It happens in many unit tests.
208 if (!local_state)
209 return false;
211 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
212 std::vector<std::string> extensions;
213 extensions.reserve(shared_extensions->size());
214 for (base::DictionaryValue::Iterator it(*shared_extensions);
215 !it.IsAtEnd(); it.Advance()) {
216 extensions.push_back(it.key());
219 for (std::vector<std::string>::iterator it = extensions.begin();
220 it != extensions.end(); it++) {
221 base::DictionaryValue* extension_info = NULL;
222 if (!shared_extensions->GetDictionary(*it, &extension_info)) {
223 NOTREACHED();
224 return false;
226 if (!CleanUpExtension(*it, extension_info, live_extension_paths)) {
227 return false;
229 if (!extension_info->size())
230 shared_extensions->RemoveWithoutPathExpansion(*it, NULL);
233 return true;
236 // static
237 void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting(
238 const base::FilePath& install_dir) {
239 DCHECK(!g_shared_install_dir_override);
240 g_shared_install_dir_override = new base::FilePath(install_dir);
243 // static
244 base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner(
245 Profile* profile) {
246 DCHECK_CURRENTLY_ON(BrowserThread::UI);
247 ExtensionService* extension_service =
248 ExtensionSystem::Get(profile)->extension_service();
249 return extension_service->GetFileTaskRunner();
252 // static
253 bool ExtensionAssetsManagerChromeOS::CanShareAssets(
254 const Extension* extension,
255 const base::FilePath& unpacked_extension_root) {
256 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
257 chromeos::switches::kEnableExtensionAssetsSharing)) {
258 return false;
261 GURL update_url = ManifestURL::GetUpdateURL(extension);
262 if (!update_url.is_empty() &&
263 !extension_urls::IsWebstoreUpdateUrl(update_url)) {
264 return false;
267 // Chrome caches crx files for installed by default apps so sharing assets is
268 // also possible. User specific apps should be excluded to not expose apps
269 // unique for the user outside of user's cryptohome.
270 return Manifest::IsExternalLocation(extension->location());
273 // static
274 void ExtensionAssetsManagerChromeOS::CheckSharedExtension(
275 const std::string& id,
276 const std::string& version,
277 const base::FilePath& unpacked_extension_root,
278 const base::FilePath& local_install_dir,
279 Profile* profile,
280 InstallExtensionCallback callback) {
281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
283 const std::string& user_id = profile->GetProfileUserName();
284 user_manager::UserManager* user_manager = user_manager::UserManager::Get();
285 if (!user_manager) {
286 NOTREACHED();
287 return;
290 if (user_manager->IsUserNonCryptohomeDataEphemeral(user_id) ||
291 !user_manager->IsLoggedInAsUserWithGaiaAccount()) {
292 // Don't cache anything in shared location for ephemeral user or special
293 // user types.
294 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
295 FROM_HERE,
296 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension,
298 version,
299 unpacked_extension_root,
300 local_install_dir,
301 callback));
302 return;
305 PrefService* local_state = g_browser_process->local_state();
306 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
307 base::DictionaryValue* extension_info = NULL;
308 base::DictionaryValue* version_info = NULL;
309 base::ListValue* users = NULL;
310 std::string shared_path;
311 if (shared_extensions->GetDictionary(id, &extension_info) &&
312 extension_info->GetDictionaryWithoutPathExpansion(
313 version, &version_info) &&
314 version_info->GetString(kSharedExtensionPath, &shared_path) &&
315 version_info->GetList(kSharedExtensionUsers, &users)) {
316 // This extension version already in shared location.
317 size_t users_size = users->GetSize();
318 bool user_found = false;
319 for (size_t i = 0; i < users_size; i++) {
320 std::string temp;
321 if (users->GetString(i, &temp) && temp == user_id) {
322 // Re-installation for the same user.
323 user_found = true;
324 break;
327 if (!user_found)
328 users->AppendString(user_id);
330 // unpacked_extension_root will be deleted by CrxInstaller.
331 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
332 FROM_HERE,
333 base::Bind(callback, base::FilePath(shared_path)));
334 } else {
335 // Desired version is not found in shared location.
336 ExtensionAssetsManagerHelper* helper =
337 ExtensionAssetsManagerHelper::GetInstance();
338 if (helper->RecordSharedInstall(id, version, unpacked_extension_root,
339 local_install_dir, profile, callback)) {
340 // There is no install in progress for given <id, version> so run install.
341 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
342 FROM_HERE,
343 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension,
345 version,
346 unpacked_extension_root));
351 // static
352 void ExtensionAssetsManagerChromeOS::InstallSharedExtension(
353 const std::string& id,
354 const std::string& version,
355 const base::FilePath& unpacked_extension_root) {
356 base::FilePath shared_install_dir = GetSharedInstallDir();
357 base::FilePath shared_version_dir = file_util::InstallExtension(
358 unpacked_extension_root, id, version, shared_install_dir);
359 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
360 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone,
361 id, version, shared_version_dir));
364 // static
365 void ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone(
366 const std::string& id,
367 const std::string& version,
368 const base::FilePath& shared_version_dir) {
369 DCHECK_CURRENTLY_ON(BrowserThread::UI);
371 ExtensionAssetsManagerHelper* helper =
372 ExtensionAssetsManagerHelper::GetInstance();
373 ExtensionAssetsManagerHelper::PendingInstallList pending_installs;
374 helper->SharedInstallDone(id, version, &pending_installs);
376 if (shared_version_dir.empty()) {
377 // Installation to shared location failed, try local dir.
378 // TODO(dpolukhin): add UMA stats reporting.
379 for (size_t i = 0; i < pending_installs.size(); i++) {
380 ExtensionAssetsManagerHelper::PendingInstallInfo& info =
381 pending_installs[i];
382 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask(
383 FROM_HERE,
384 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension,
386 version,
387 info.unpacked_extension_root,
388 info.local_install_dir,
389 info.callback));
391 return;
394 PrefService* local_state = g_browser_process->local_state();
395 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
396 base::DictionaryValue* extension_info = NULL;
397 if (!shared_extensions->GetDictionary(id, &extension_info)) {
398 extension_info = new base::DictionaryValue;
399 shared_extensions->Set(id, extension_info);
402 CHECK(!shared_extensions->HasKey(version));
403 base::DictionaryValue* version_info = new base::DictionaryValue;
404 extension_info->SetWithoutPathExpansion(version, version_info);
405 version_info->SetString(kSharedExtensionPath, shared_version_dir.value());
407 base::ListValue* users = new base::ListValue;
408 version_info->Set(kSharedExtensionUsers, users);
409 for (size_t i = 0; i < pending_installs.size(); i++) {
410 ExtensionAssetsManagerHelper::PendingInstallInfo& info =
411 pending_installs[i];
412 users->AppendString(info.profile->GetProfileUserName());
414 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask(
415 FROM_HERE,
416 base::Bind(info.callback, shared_version_dir));
420 // static
421 void ExtensionAssetsManagerChromeOS::InstallLocalExtension(
422 const std::string& id,
423 const std::string& version,
424 const base::FilePath& unpacked_extension_root,
425 const base::FilePath& local_install_dir,
426 InstallExtensionCallback callback) {
427 callback.Run(file_util::InstallExtension(
428 unpacked_extension_root, id, version, local_install_dir));
431 // static
432 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused(
433 const std::string& id,
434 Profile* profile) {
435 DCHECK_CURRENTLY_ON(BrowserThread::UI);
437 PrefService* local_state = g_browser_process->local_state();
438 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
439 base::DictionaryValue* extension_info = NULL;
440 if (!shared_extensions->GetDictionary(id, &extension_info)) {
441 NOTREACHED();
442 return;
445 std::vector<std::string> versions;
446 versions.reserve(extension_info->size());
447 for (base::DictionaryValue::Iterator it(*extension_info);
448 !it.IsAtEnd();
449 it.Advance()) {
450 versions.push_back(it.key());
453 base::StringValue user_name(profile->GetProfileUserName());
454 for (std::vector<std::string>::const_iterator it = versions.begin();
455 it != versions.end(); it++) {
456 base::DictionaryValue* version_info = NULL;
457 if (!extension_info->GetDictionaryWithoutPathExpansion(*it,
458 &version_info)) {
459 NOTREACHED();
460 continue;
462 base::ListValue* users = NULL;
463 if (!version_info->GetList(kSharedExtensionUsers, &users)) {
464 NOTREACHED();
465 continue;
467 if (users->Remove(user_name, NULL) && !users->GetSize()) {
468 std::string shared_path;
469 if (!version_info->GetString(kSharedExtensionPath, &shared_path)) {
470 NOTREACHED();
471 continue;
473 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
474 FROM_HERE,
475 base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion,
476 base::FilePath(shared_path)));
477 extension_info->RemoveWithoutPathExpansion(*it, NULL);
480 if (!extension_info->size()) {
481 shared_extensions->RemoveWithoutPathExpansion(id, NULL);
482 // Don't remove extension dir in shared location. It will be removed by GC
483 // when it is safe to do so, and this avoids a race condition between
484 // concurrent uninstall by one user and install by another.
488 // static
489 void ExtensionAssetsManagerChromeOS::DeleteSharedVersion(
490 const base::FilePath& shared_version_dir) {
491 CHECK(GetSharedInstallDir().IsParent(shared_version_dir));
492 base::DeleteFile(shared_version_dir, true); // recursive.
495 // static
496 bool ExtensionAssetsManagerChromeOS::CleanUpExtension(
497 const std::string& id,
498 base::DictionaryValue* extension_info,
499 std::multimap<std::string, base::FilePath>* live_extension_paths) {
500 user_manager::UserManager* user_manager = user_manager::UserManager::Get();
501 if (!user_manager) {
502 NOTREACHED();
503 return false;
506 std::vector<std::string> versions;
507 versions.reserve(extension_info->size());
508 for (base::DictionaryValue::Iterator it(*extension_info);
509 !it.IsAtEnd(); it.Advance()) {
510 versions.push_back(it.key());
513 for (std::vector<std::string>::const_iterator it = versions.begin();
514 it != versions.end(); it++) {
515 base::DictionaryValue* version_info = NULL;
516 base::ListValue* users = NULL;
517 std::string shared_path;
518 if (!extension_info->GetDictionaryWithoutPathExpansion(*it,
519 &version_info) ||
520 !version_info->GetList(kSharedExtensionUsers, &users) ||
521 !version_info->GetString(kSharedExtensionPath, &shared_path)) {
522 NOTREACHED();
523 return false;
526 size_t num_users = users->GetSize();
527 for (size_t i = 0; i < num_users; i++) {
528 std::string user_id;
529 if (!users->GetString(i, &user_id)) {
530 NOTREACHED();
531 return false;
533 const user_manager::User* user = user_manager->FindUser(user_id);
534 bool not_used = false;
535 if (!user) {
536 not_used = true;
537 } else if (user->is_logged_in()) {
538 // For logged in user also check that this path is actually used as
539 // installed extension or as delayed install.
540 Profile* profile =
541 chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user);
542 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile);
543 if (!extension_prefs || extension_prefs->pref_service()->ReadOnly())
544 return false;
546 scoped_ptr<ExtensionInfo> info =
547 extension_prefs->GetInstalledExtensionInfo(id);
548 if (!info || info->extension_path != base::FilePath(shared_path)) {
549 info = extension_prefs->GetDelayedInstallInfo(id);
550 if (!info || info->extension_path != base::FilePath(shared_path)) {
551 not_used = true;
556 if (not_used) {
557 users->Remove(i, NULL);
559 i--;
560 num_users--;
564 if (num_users) {
565 live_extension_paths->insert(
566 std::make_pair(id, base::FilePath(shared_path)));
567 } else {
568 extension_info->RemoveWithoutPathExpansion(*it, NULL);
572 return true;
575 } // namespace extensions