extension_assets_manager_chromeos.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright (c) 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include <map>
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include <vector>
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/command_line.h"
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/file_util.h"
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/memory/singleton.h"
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/pref_registry_simple.h"
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/pref_service.h"
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/scoped_user_pref_update.h"
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/sequenced_task_runner.h"
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/sys_info.h"
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/browser_process.h"
19116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "chrome/browser/chromeos/profiles/profile_helper.h"
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/extensions/extension_service.h"
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/profiles/profile.h"
22116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "chrome/common/extensions/extension_constants.h"
23116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch#include "chrome/common/extensions/manifest_url_handler.h"
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chromeos/chromeos_switches.h"
256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)#include "components/user_manager/user_manager.h"
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
2746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "extensions/browser/extension_prefs.h"
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "extensions/browser/extension_system.h"
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "extensions/common/extension.h"
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "extensions/common/file_util.h"
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "extensions/common/manifest.h"
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)using content::BrowserThread;
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)namespace extensions {
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)namespace {
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
3846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Path to shared extensions install dir.
3946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)const char kSharedExtensionsDir[] = "/var/cache/shared_extensions";
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Shared install dir overrider for tests only.
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)static const base::FilePath* g_shared_install_dir_override = NULL;
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// This helper class lives on UI thread only. Main purpose of this class is to
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// track shared installation in progress between multiple profiles.
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)class ExtensionAssetsManagerHelper {
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) public:
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Info about pending install request.
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  struct PendingInstallInfo {
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::FilePath unpacked_extension_root;
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::FilePath local_install_dir;
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile;
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ExtensionAssetsManager::InstallExtensionCallback callback;
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  };
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  typedef std::vector<PendingInstallInfo> PendingInstallList;
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  static ExtensionAssetsManagerHelper* GetInstance() {
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    DCHECK_CURRENTLY_ON(BrowserThread::UI);
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return Singleton<ExtensionAssetsManagerHelper>::get();
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Remember that shared install is in progress. Return true if there is no
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // other installs for given id and version.
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  bool RecordSharedInstall(
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const std::string& id,
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const std::string& version,
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const base::FilePath& unpacked_extension_root,
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const base::FilePath& local_install_dir,
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      Profile* profile,
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManager::InstallExtensionCallback callback) {
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    PendingInstallInfo install_info;
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    install_info.unpacked_extension_root = unpacked_extension_root;
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    install_info.local_install_dir = local_install_dir;
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    install_info.profile = profile;
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    install_info.callback = callback;
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    std::vector<PendingInstallInfo>& callbacks =
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        install_queue_[InstallQueue::key_type(id, version)];
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    callbacks.push_back(install_info);
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return callbacks.size() == 1;
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Remove record about shared installation in progress and return
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // |pending_installs|.
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  void SharedInstallDone(const std::string& id,
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                         const std::string& version,
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                         PendingInstallList* pending_installs) {
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    InstallQueue::iterator it = install_queue_.find(
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        InstallQueue::key_type(id, version));
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    DCHECK(it != install_queue_.end());
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    pending_installs->swap(it->second);
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    install_queue_.erase(it);
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) private:
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  friend struct DefaultSingletonTraits<ExtensionAssetsManagerHelper>;
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ExtensionAssetsManagerHelper() {}
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ~ExtensionAssetsManagerHelper() {}
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Extension ID + version pair.
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  typedef std::pair<std::string, std::string> InstallItem;
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Queue of pending installs in progress.
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  typedef std::map<InstallItem, std::vector<PendingInstallInfo> > InstallQueue;
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  InstallQueue install_queue_;
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerHelper);
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}  // namespace
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
11546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] =
11646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    "SharedExtensions";
11746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
11846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path";
11946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
12046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users";
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ExtensionAssetsManagerChromeOS::~ExtensionAssetsManagerChromeOS() {
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (g_shared_install_dir_override) {
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    delete g_shared_install_dir_override;
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    g_shared_install_dir_override = NULL;
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() {
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return Singleton<ExtensionAssetsManagerChromeOS>::get();
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::RegisterPrefs(
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    PrefRegistrySimple* registry) {
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  registry->RegisterDictionaryPref(kSharedExtensions);
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::InstallExtension(
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const Extension* extension,
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& unpacked_extension_root,
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& local_install_dir,
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile,
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    InstallExtensionCallback callback) {
148116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (!CanShareAssets(extension, unpacked_extension_root)) {
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    InstallLocalExtension(extension->id(),
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          extension->VersionString(),
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          unpacked_extension_root,
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          local_install_dir,
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          callback);
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      base::Bind(&ExtensionAssetsManagerChromeOS::CheckSharedExtension,
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 extension->id(),
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 extension->VersionString(),
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 unpacked_extension_root,
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 local_install_dir,
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 profile,
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 callback));
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::UninstallExtension(
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& id,
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile,
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& local_install_dir,
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& extension_root) {
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (local_install_dir.IsParent(extension_root)) {
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    file_util::UninstallExtension(local_install_dir, id);
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (GetSharedInstallDir().IsParent(extension_root)) {
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // In some test extensions installed outside local_install_dir emulate
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // previous behavior that just do nothing in this case.
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        base::Bind(&ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused,
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                   id,
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                   profile));
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
18846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() {
18946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (g_shared_install_dir_override)
19046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return *g_shared_install_dir_override;
19146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  else
19246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return base::FilePath(kSharedExtensionsDir);
19346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
19446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
19546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// static
196116680a4aac90f2aa7413d9095a592090648e557Ben Murdochbool ExtensionAssetsManagerChromeOS::IsSharedInstall(
197116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    const Extension* extension) {
198116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return GetSharedInstallDir().IsParent(extension->path());
199116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch}
200116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
201116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch// static
20246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)bool ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions(
20346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    std::multimap<std::string, base::FilePath>* live_extension_paths) {
20446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  DCHECK_CURRENTLY_ON(BrowserThread::UI);
20546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
20646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  PrefService* local_state = g_browser_process->local_state();
20746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // It happens in many unit tests.
20846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!local_state)
20946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return false;
21046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
21146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
21246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  std::vector<std::string> extensions;
21346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  extensions.reserve(shared_extensions->size());
21446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  for (base::DictionaryValue::Iterator it(*shared_extensions);
21546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)       !it.IsAtEnd(); it.Advance()) {
21646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    extensions.push_back(it.key());
21746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
21846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
21946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  for (std::vector<std::string>::iterator it = extensions.begin();
22046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)       it != extensions.end(); it++) {
22146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    base::DictionaryValue* extension_info = NULL;
22246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!shared_extensions->GetDictionary(*it, &extension_info)) {
22346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      NOTREACHED();
22446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return false;
22546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
22646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!CleanUpExtension(*it, extension_info, live_extension_paths)) {
22746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return false;
22846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
22946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!extension_info->size())
23046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      shared_extensions->RemoveWithoutPathExpansion(*it, NULL);
23146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
23246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
23346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return true;
23446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
23546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
23646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// static
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting(
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& install_dir) {
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK(!g_shared_install_dir_override);
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  g_shared_install_dir_override = new base::FilePath(install_dir);
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner(
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile) {
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK_CURRENTLY_ON(BrowserThread::UI);
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ExtensionService* extension_service =
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionSystem::Get(profile)->extension_service();
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return extension_service->GetFileTaskRunner();
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)bool ExtensionAssetsManagerChromeOS::CanShareAssets(
254116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    const Extension* extension,
255116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    const base::FilePath& unpacked_extension_root) {
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!CommandLine::ForCurrentProcess()->HasSwitch(
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          chromeos::switches::kEnableExtensionAssetsSharing)) {
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return false;
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
261116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  GURL update_url = ManifestURL::GetUpdateURL(extension);
262116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (!update_url.is_empty() &&
263116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      !extension_urls::IsWebstoreUpdateUrl(update_url)) {
264116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return false;
265116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  }
266116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Chrome caches crx files for installed by default apps so sharing assets is
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // also possible. User specific apps should be excluded to not expose apps
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // unique for the user outside of user's cryptohome.
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return Manifest::IsExternalLocation(extension->location());
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::CheckSharedExtension(
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& id,
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& version,
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& unpacked_extension_root,
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& local_install_dir,
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile,
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    InstallExtensionCallback callback) {
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK_CURRENTLY_ON(BrowserThread::UI);
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
28346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const std::string& user_id = profile->GetProfileName();
2846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
28546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!user_manager) {
28646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    NOTREACHED();
28746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return;
28846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
28946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
29046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (user_manager->IsUserNonCryptohomeDataEphemeral(user_id) ||
29146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      !user_manager->IsLoggedInAsRegularUser()) {
29246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // Don't cache anything in shared location for ephemeral user or special
29346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // user types.
29446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
29546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        FROM_HERE,
29646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension,
29746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   id,
29846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   version,
29946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   unpacked_extension_root,
30046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   local_install_dir,
30146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                   callback));
30246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return;
30346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
30446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  PrefService* local_state = g_browser_process->local_state();
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DictionaryValue* extension_info = NULL;
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DictionaryValue* version_info = NULL;
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::ListValue* users = NULL;
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  std::string shared_path;
311cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (shared_extensions->GetDictionary(id, &extension_info) &&
312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      extension_info->GetDictionaryWithoutPathExpansion(
313cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          version, &version_info) &&
314cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      version_info->GetString(kSharedExtensionPath, &shared_path) &&
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      version_info->GetList(kSharedExtensionUsers, &users)) {
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // This extension version already in shared location.
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    size_t users_size = users->GetSize();
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bool user_found = false;
319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (size_t i = 0; i < users_size; i++) {
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      std::string temp;
32146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (users->GetString(i, &temp) && temp == user_id) {
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        // Re-installation for the same user.
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        user_found = true;
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!user_found)
32846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      users->AppendString(user_id);
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // unpacked_extension_root will be deleted by CrxInstaller.
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        FROM_HERE,
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        base::Bind(callback, base::FilePath(shared_path)));
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Desired version is not found in shared location.
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ExtensionAssetsManagerHelper* helper =
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        ExtensionAssetsManagerHelper::GetInstance();
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (helper->RecordSharedInstall(id, version, unpacked_extension_root,
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                    local_install_dir, profile, callback)) {
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      // There is no install in progress for given <id, version> so run install.
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          FROM_HERE,
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension,
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     id,
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     version,
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     unpacked_extension_root));
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::InstallSharedExtension(
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const std::string& id,
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const std::string& version,
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      const base::FilePath& unpacked_extension_root) {
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::FilePath shared_install_dir = GetSharedInstallDir();
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::FilePath shared_version_dir = file_util::InstallExtension(
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      unpacked_extension_root, id, version, shared_install_dir);
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone,
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 id, version, shared_version_dir));
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
364cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
365cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone(
366cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& id,
367cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& version,
368cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& shared_version_dir) {
369cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK_CURRENTLY_ON(BrowserThread::UI);
370cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
371cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ExtensionAssetsManagerHelper* helper =
372cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManagerHelper::GetInstance();
373cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ExtensionAssetsManagerHelper::PendingInstallList pending_installs;
374cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  helper->SharedInstallDone(id, version, &pending_installs);
375cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
376cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (shared_version_dir.empty()) {
377cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Installation to shared location failed, try local dir.
378cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(dpolukhin): add UMA stats reporting.
379cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (size_t i = 0; i < pending_installs.size(); i++) {
380cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManagerHelper::PendingInstallInfo& info =
381cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          pending_installs[i];
382cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask(
383cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          FROM_HERE,
384cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension,
385cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     id,
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     version,
387cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     info.unpacked_extension_root,
388cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     info.local_install_dir,
389cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     info.callback));
390cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
391cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
392cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
393cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
394cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  PrefService* local_state = g_browser_process->local_state();
395cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
396cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DictionaryValue* extension_info = NULL;
397cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!shared_extensions->GetDictionary(id, &extension_info)) {
398cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    extension_info = new base::DictionaryValue;
399cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    shared_extensions->Set(id, extension_info);
400cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
401cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
402cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  CHECK(!shared_extensions->HasKey(version));
403cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DictionaryValue* version_info = new base::DictionaryValue;
404cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  extension_info->SetWithoutPathExpansion(version, version_info);
405cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  version_info->SetString(kSharedExtensionPath, shared_version_dir.value());
406cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
407cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::ListValue* users = new base::ListValue;
408cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  version_info->Set(kSharedExtensionUsers, users);
409cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (size_t i = 0; i < pending_installs.size(); i++) {
410cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ExtensionAssetsManagerHelper::PendingInstallInfo& info =
411cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        pending_installs[i];
412cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      users->AppendString(info.profile->GetProfileName());
413cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
414cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask(
415cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        FROM_HERE,
416cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        base::Bind(info.callback, shared_version_dir));
417cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
418cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
419cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
420cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
421cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::InstallLocalExtension(
422cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& id,
423cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& version,
424cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& unpacked_extension_root,
425cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& local_install_dir,
426cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    InstallExtensionCallback callback) {
427cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  callback.Run(file_util::InstallExtension(
428cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      unpacked_extension_root, id, version, local_install_dir));
429cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
430cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
431cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
432cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused(
433cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::string& id,
434cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Profile* profile) {
435cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK_CURRENTLY_ON(BrowserThread::UI);
436cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
437cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  PrefService* local_state = g_browser_process->local_state();
438cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
439cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DictionaryValue* extension_info = NULL;
440cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!shared_extensions->GetDictionary(id, &extension_info)) {
441cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    NOTREACHED();
442cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
443cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
444cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
445cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  std::vector<std::string> versions;
446cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  versions.reserve(extension_info->size());
447cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (base::DictionaryValue::Iterator it(*extension_info);
448cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       !it.IsAtEnd();
449cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       it.Advance()) {
450cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    versions.push_back(it.key());
451cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
452cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
453cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::StringValue user_name(profile->GetProfileName());
454cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (std::vector<std::string>::const_iterator it = versions.begin();
455cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       it != versions.end(); it++) {
456cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::DictionaryValue* version_info = NULL;
457cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!extension_info->GetDictionaryWithoutPathExpansion(*it,
458cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                                           &version_info)) {
459cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
460cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
461cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
462cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::ListValue* users = NULL;
463cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!version_info->GetList(kSharedExtensionUsers, &users)) {
464cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
465cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
466cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
467cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (users->Remove(user_name, NULL) && !users->GetSize()) {
468cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      std::string shared_path;
469cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (!version_info->GetString(kSharedExtensionPath, &shared_path)) {
470cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        NOTREACHED();
471cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        continue;
472cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
473cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
474cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          FROM_HERE,
475cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion,
476cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                     base::FilePath(shared_path)));
477cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      extension_info->RemoveWithoutPathExpansion(*it, NULL);
478cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
479cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
48046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!extension_info->size()) {
48146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    shared_extensions->RemoveWithoutPathExpansion(id, NULL);
48246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // Don't remove extension dir in shared location. It will be removed by GC
48346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // when it is safe to do so, and this avoids a race condition between
48446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // concurrent uninstall by one user and install by another.
48546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
486cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
487cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
488cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
489cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void ExtensionAssetsManagerChromeOS::DeleteSharedVersion(
490cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& shared_version_dir) {
491cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  CHECK(GetSharedInstallDir().IsParent(shared_version_dir));
492cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::DeleteFile(shared_version_dir, true);  // recursive.
493cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
494cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
49546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// static
49646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)bool ExtensionAssetsManagerChromeOS::CleanUpExtension(
49746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    const std::string& id,
49846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    base::DictionaryValue* extension_info,
49946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    std::multimap<std::string, base::FilePath>* live_extension_paths) {
5006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
50146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!user_manager) {
50246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    NOTREACHED();
50346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return false;
50446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
50546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
50646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  std::vector<std::string> versions;
50746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  versions.reserve(extension_info->size());
50846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  for (base::DictionaryValue::Iterator it(*extension_info);
50946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)       !it.IsAtEnd(); it.Advance()) {
51046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    versions.push_back(it.key());
51146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
51246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
51346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  for (std::vector<std::string>::const_iterator it = versions.begin();
51446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)       it != versions.end(); it++) {
51546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    base::DictionaryValue* version_info = NULL;
51646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    base::ListValue* users = NULL;
51746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    std::string shared_path;
51846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!extension_info->GetDictionaryWithoutPathExpansion(*it,
51946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                           &version_info) ||
52046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        !version_info->GetList(kSharedExtensionUsers, &users) ||
52146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        !version_info->GetString(kSharedExtensionPath, &shared_path)) {
52246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      NOTREACHED();
52346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return false;
52446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
52546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
52646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    size_t num_users = users->GetSize();
52746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    for (size_t i = 0; i < num_users; i++) {
52846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      std::string user_id;
52946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (!users->GetString(i, &user_id)) {
53046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        NOTREACHED();
53146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return false;
53246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      }
5335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      const user_manager::User* user = user_manager->FindUser(user_id);
53446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      bool not_used = false;
53546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (!user) {
53646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        not_used = true;
53746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      } else if (user->is_logged_in()) {
53846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        // For logged in user also check that this path is actually used as
53946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        // installed extension or as delayed install.
540116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        Profile* profile =
5416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user);
54246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile);
54346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        if (!extension_prefs || extension_prefs->pref_service()->ReadOnly())
54446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          return false;
54546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
54646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        scoped_ptr<ExtensionInfo> info =
54746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            extension_prefs->GetInstalledExtensionInfo(id);
54846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        if (!info || info->extension_path != base::FilePath(shared_path)) {
54946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          info = extension_prefs->GetDelayedInstallInfo(id);
55046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          if (!info || info->extension_path != base::FilePath(shared_path)) {
55146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            not_used = true;
55246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          }
55346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        }
55446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      }
55546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
55646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (not_used) {
55746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        users->Remove(i, NULL);
55846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
55946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        i--;
56046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        num_users--;
56146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      }
56246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
56346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
56446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (num_users) {
56546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      live_extension_paths->insert(
56646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          std::make_pair(id, base::FilePath(shared_path)));
56746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    } else {
56846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      extension_info->RemoveWithoutPathExpansion(*it, NULL);
56946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
57046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
57146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
57246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return true;
57346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
57446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
575cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}  // namespace extensions
576