Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_assets_manager_chromeos.cc
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.
4
5 #include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
6
7 #include <map>
8 #include <vector>
9
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 "chrome/common/extensions/manifest_url_handler.h"
23 #include "chromeos/chromeos_switches.h"
24 #include "components/user_manager/user_manager.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "extensions/browser/extension_prefs.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_urls.h"
30 #include "extensions/common/file_util.h"
31 #include "extensions/common/manifest.h"
32
33 using content::BrowserThread;
34
35 namespace extensions {
36 namespace {
37
38 // Path to shared extensions install dir.
39 const char kSharedExtensionsDir[] = "/var/cache/shared_extensions";
40
41 // Shared install dir overrider for tests only.
42 static const base::FilePath* g_shared_install_dir_override = NULL;
43
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;
54   };
55   typedef std::vector<PendingInstallInfo> PendingInstallList;
56
57   static ExtensionAssetsManagerHelper* GetInstance() {
58     DCHECK_CURRENTLY_ON(BrowserThread::UI);
59     return Singleton<ExtensionAssetsManagerHelper>::get();
60   }
61
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;
76
77     std::vector<PendingInstallInfo>& callbacks =
78         install_queue_[InstallQueue::key_type(id, version)];
79     callbacks.push_back(install_info);
80
81     return callbacks.size() == 1;
82   }
83
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);
94   }
95
96  private:
97   friend struct DefaultSingletonTraits<ExtensionAssetsManagerHelper>;
98
99   ExtensionAssetsManagerHelper() {}
100   ~ExtensionAssetsManagerHelper() {}
101
102   // Extension ID + version pair.
103   typedef std::pair<std::string, std::string> InstallItem;
104
105   // Queue of pending installs in progress.
106   typedef std::map<InstallItem, std::vector<PendingInstallInfo> > InstallQueue;
107
108   InstallQueue install_queue_;
109
110   DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerHelper);
111 };
112
113 }  // namespace
114
115 const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] =
116     "SharedExtensions";
117
118 const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path";
119
120 const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users";
121
122 ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
123
124 ExtensionAssetsManagerChromeOS::~ExtensionAssetsManagerChromeOS() {
125   if (g_shared_install_dir_override) {
126     delete g_shared_install_dir_override;
127     g_shared_install_dir_override = NULL;
128   }
129 }
130
131 // static
132 ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() {
133   return Singleton<ExtensionAssetsManagerChromeOS>::get();
134 }
135
136 // static
137 void ExtensionAssetsManagerChromeOS::RegisterPrefs(
138     PrefRegistrySimple* registry) {
139   registry->RegisterDictionaryPref(kSharedExtensions);
140 }
141
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;
155   }
156
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));
165 }
166
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;
175   }
176
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,
182                    id,
183                    profile));
184   }
185 }
186
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);
193 }
194
195 // static
196 bool ExtensionAssetsManagerChromeOS::IsSharedInstall(
197     const Extension* extension) {
198   return GetSharedInstallDir().IsParent(extension->path());
199 }
200
201 // static
202 bool ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions(
203     std::multimap<std::string, base::FilePath>* live_extension_paths) {
204   DCHECK_CURRENTLY_ON(BrowserThread::UI);
205
206   PrefService* local_state = g_browser_process->local_state();
207   // It happens in many unit tests.
208   if (!local_state)
209     return false;
210
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());
217   }
218
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;
225     }
226     if (!CleanUpExtension(*it, extension_info, live_extension_paths)) {
227       return false;
228     }
229     if (!extension_info->size())
230       shared_extensions->RemoveWithoutPathExpansion(*it, NULL);
231   }
232
233   return true;
234 }
235
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);
241 }
242
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();
250 }
251
252 // static
253 bool ExtensionAssetsManagerChromeOS::CanShareAssets(
254     const Extension* extension,
255     const base::FilePath& unpacked_extension_root) {
256   if (!CommandLine::ForCurrentProcess()->HasSwitch(
257           chromeos::switches::kEnableExtensionAssetsSharing)) {
258     return false;
259   }
260
261   GURL update_url = ManifestURL::GetUpdateURL(extension);
262   if (!update_url.is_empty() &&
263       !extension_urls::IsWebstoreUpdateUrl(update_url)) {
264     return false;
265   }
266
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());
271 }
272
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);
282
283   const std::string& user_id = profile->GetProfileName();
284   user_manager::UserManager* user_manager = user_manager::UserManager::Get();
285   if (!user_manager) {
286     NOTREACHED();
287     return;
288   }
289
290   if (user_manager->IsUserNonCryptohomeDataEphemeral(user_id) ||
291       !user_manager->IsLoggedInAsRegularUser()) {
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,
297                    id,
298                    version,
299                    unpacked_extension_root,
300                    local_install_dir,
301                    callback));
302     return;
303   }
304
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;
325       }
326     }
327     if (!user_found)
328       users->AppendString(user_id);
329
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,
344                      id,
345                      version,
346                      unpacked_extension_root));
347     }
348   }
349 }
350
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));
362 }
363
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);
370
371   ExtensionAssetsManagerHelper* helper =
372       ExtensionAssetsManagerHelper::GetInstance();
373   ExtensionAssetsManagerHelper::PendingInstallList pending_installs;
374   helper->SharedInstallDone(id, version, &pending_installs);
375
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,
385                      id,
386                      version,
387                      info.unpacked_extension_root,
388                      info.local_install_dir,
389                      info.callback));
390     }
391     return;
392   }
393
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);
400   }
401
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());
406
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->GetProfileName());
413
414     ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask(
415         FROM_HERE,
416         base::Bind(info.callback, shared_version_dir));
417   }
418 }
419
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));
429 }
430
431 // static
432 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused(
433     const std::string& id,
434     Profile* profile) {
435   DCHECK_CURRENTLY_ON(BrowserThread::UI);
436
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;
443   }
444
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());
451   }
452
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,
458                                                            &version_info)) {
459       NOTREACHED();
460       continue;
461     }
462     base::ListValue* users = NULL;
463     if (!version_info->GetList(kSharedExtensionUsers, &users)) {
464       NOTREACHED();
465       continue;
466     }
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;
472       }
473       ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
474           FROM_HERE,
475           base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion,
476                      base::FilePath(shared_path)));
477       extension_info->RemoveWithoutPathExpansion(*it, NULL);
478     }
479   }
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.
485   }
486 }
487
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.
493 }
494
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;
504   }
505
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());
511   }
512
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;
524     }
525
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;
532       }
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;
545
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;
552           }
553         }
554       }
555
556       if (not_used) {
557         users->Remove(i, NULL);
558
559         i--;
560         num_users--;
561       }
562     }
563
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);
569     }
570   }
571
572   return true;
573 }
574
575 }  // namespace extensions