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"
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
{
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
{
48 // Info about pending install request.
49 struct PendingInstallInfo
{
50 base::FilePath unpacked_extension_root
;
51 base::FilePath local_install_dir
;
53 ExtensionAssetsManager::InstallExtensionCallback callback
;
55 typedef std::vector
<PendingInstallInfo
> PendingInstallList
;
57 static ExtensionAssetsManagerHelper
* GetInstance() {
58 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
59 return 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
,
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
);
97 friend struct 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
);
115 const char ExtensionAssetsManagerChromeOS::kSharedExtensions
[] =
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
;
132 ExtensionAssetsManagerChromeOS
* ExtensionAssetsManagerChromeOS::GetInstance() {
133 return Singleton
<ExtensionAssetsManagerChromeOS
>::get();
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
,
147 InstallExtensionCallback callback
) {
148 if (!CanShareAssets(extension
, unpacked_extension_root
)) {
149 InstallLocalExtension(extension
->id(),
150 extension
->VersionString(),
151 unpacked_extension_root
,
157 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
158 base::Bind(&ExtensionAssetsManagerChromeOS::CheckSharedExtension
,
160 extension
->VersionString(),
161 unpacked_extension_root
,
167 void ExtensionAssetsManagerChromeOS::UninstallExtension(
168 const std::string
& id
,
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
);
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
,
188 base::FilePath
ExtensionAssetsManagerChromeOS::GetSharedInstallDir() {
189 if (g_shared_install_dir_override
)
190 return *g_shared_install_dir_override
;
192 return base::FilePath(kSharedExtensionsDir
);
196 bool ExtensionAssetsManagerChromeOS::IsSharedInstall(
197 const Extension
* extension
) {
198 return GetSharedInstallDir().IsParent(extension
->path());
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.
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
)) {
226 if (!CleanUpExtension(*it
, extension_info
, live_extension_paths
)) {
229 if (!extension_info
->size())
230 shared_extensions
->RemoveWithoutPathExpansion(*it
, NULL
);
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
);
244 base::SequencedTaskRunner
* ExtensionAssetsManagerChromeOS::GetFileTaskRunner(
246 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
247 ExtensionService
* extension_service
=
248 ExtensionSystem::Get(profile
)->extension_service();
249 return extension_service
->GetFileTaskRunner();
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
)) {
261 GURL update_url
= ManifestURL::GetUpdateURL(extension
);
262 if (!update_url
.is_empty() &&
263 !extension_urls::IsWebstoreUpdateUrl(update_url
)) {
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());
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
,
280 InstallExtensionCallback callback
) {
281 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
283 const std::string
& user_id
= profile
->GetProfileName();
284 user_manager::UserManager
* user_manager
= user_manager::UserManager::Get();
290 if (user_manager
->IsUserNonCryptohomeDataEphemeral(user_id
) ||
291 !user_manager
->IsLoggedInAsUserWithGaiaAccount()) {
292 // Don't cache anything in shared location for ephemeral user or special
294 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile
)->PostTask(
296 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension
,
299 unpacked_extension_root
,
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
++) {
321 if (users
->GetString(i
, &temp
) && temp
== user_id
) {
322 // Re-installation for the same user.
328 users
->AppendString(user_id
);
330 // unpacked_extension_root will be deleted by CrxInstaller.
331 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile
)->PostTask(
333 base::Bind(callback
, base::FilePath(shared_path
)));
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(
343 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension
,
346 unpacked_extension_root
));
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
));
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
=
382 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info
.profile
)->PostTask(
384 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension
,
387 info
.unpacked_extension_root
,
388 info
.local_install_dir
,
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
=
412 users
->AppendString(info
.profile
->GetProfileName());
414 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info
.profile
)->PostTask(
416 base::Bind(info
.callback
, shared_version_dir
));
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
));
432 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused(
433 const std::string
& id
,
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
)) {
445 std::vector
<std::string
> versions
;
446 versions
.reserve(extension_info
->size());
447 for (base::DictionaryValue::Iterator
it(*extension_info
);
450 versions
.push_back(it
.key());
453 base::StringValue
user_name(profile
->GetProfileName());
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
,
462 base::ListValue
* users
= NULL
;
463 if (!version_info
->GetList(kSharedExtensionUsers
, &users
)) {
467 if (users
->Remove(user_name
, NULL
) && !users
->GetSize()) {
468 std::string shared_path
;
469 if (!version_info
->GetString(kSharedExtensionPath
, &shared_path
)) {
473 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile
)->PostTask(
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.
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.
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();
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
,
520 !version_info
->GetList(kSharedExtensionUsers
, &users
) ||
521 !version_info
->GetString(kSharedExtensionPath
, &shared_path
)) {
526 size_t num_users
= users
->GetSize();
527 for (size_t i
= 0; i
< num_users
; i
++) {
529 if (!users
->GetString(i
, &user_id
)) {
533 const user_manager::User
* user
= user_manager
->FindUser(user_id
);
534 bool not_used
= false;
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.
541 chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user
);
542 ExtensionPrefs
* extension_prefs
= ExtensionPrefs::Get(profile
);
543 if (!extension_prefs
|| extension_prefs
->pref_service()->ReadOnly())
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
)) {
557 users
->Remove(i
, NULL
);
565 live_extension_paths
->insert(
566 std::make_pair(id
, base::FilePath(shared_path
)));
568 extension_info
->RemoveWithoutPathExpansion(*it
, NULL
);
575 } // namespace extensions