1 // Copyright (c) 2012 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/component_updater/pnacl/pnacl_component_installer.h"
7 #include "base/base_paths.h"
9 #include "base/callback.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/file_util.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_path.h"
15 #include "base/json/json_file_value_serializer.h"
16 #include "base/logging.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/values.h"
20 #include "base/version.h"
21 #include "base/win/windows_version.h"
22 #include "build/build_config.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/component_updater/component_updater_service.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/common/chrome_paths.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/omaha_query_params/omaha_query_params.h"
30 #include "content/public/browser/browser_thread.h"
32 using chrome::OmahaQueryParams;
33 using content::BrowserThread;
37 // Name of the Pnacl component specified in the manifest.
38 const char kPnaclManifestName[] = "PNaCl Translator";
40 // Sanitize characters from Pnacl Arch value so that they can be used
41 // in path names. This should only be characters in the set: [a-z0-9_].
42 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
43 std::string SanitizeForPath(const std::string& input) {
45 ReplaceChars(input, "-", "_", &result);
49 // Set the component's hash to the multi-CRX PNaCl package.
50 void SetPnaclHash(CrxComponent* component) {
51 static const uint8 sha256_hash[32] =
52 { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
53 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44,
54 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51,
55 0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c};
57 component->pk_hash.assign(sha256_hash,
58 &sha256_hash[arraysize(sha256_hash)]);
61 // If we don't have Pnacl installed, this is the version we claim.
62 const char kNullVersion[] = "0.0.0.0";
64 // PNaCl is packaged as a multi-CRX. This returns the platform-specific
65 // subdirectory that is part of that multi-CRX.
66 base::FilePath GetPlatformDir(const base::FilePath& base_path) {
67 std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch());
68 return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
71 // Tell the rest of the world where to find the platform-specific PNaCl files.
72 void OverrideDirPnaclComponent(const base::FilePath& base_path) {
73 PathService::Override(chrome::DIR_PNACL_COMPONENT,
74 GetPlatformDir(base_path));
77 bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
78 base::FilePath* latest_dir,
79 Version* latest_version,
80 std::vector<base::FilePath>* older_dirs) {
81 // Enumerate all versions starting from the base directory.
82 base::FilePath base_dir = pci->GetPnaclBaseDirectory();
85 file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES);
86 for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
87 path = file_enumerator.Next()) {
88 Version version(path.BaseName().MaybeAsASCII());
89 if (!version.IsValid())
92 if (version.CompareTo(*latest_version) > 0) {
93 older_dirs->push_back(*latest_dir);
95 *latest_version = version;
97 older_dirs->push_back(path);
100 *latest_version = version;
108 // Read a manifest file in.
109 base::DictionaryValue* ReadJSONManifest(
110 const base::FilePath& manifest_path) {
111 JSONFileValueSerializer serializer(manifest_path);
113 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
116 if (!root->IsType(base::Value::TYPE_DICTIONARY))
118 return static_cast<base::DictionaryValue*>(root.release());
121 // Read the PNaCl specific manifest.
122 base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
123 base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII(
124 "pnacl_public_pnacl_json");
125 if (!base::PathExists(manifest_path))
127 return ReadJSONManifest(manifest_path);
130 // Read the component's manifest.json.
131 base::DictionaryValue* ReadComponentManifest(
132 const base::FilePath& unpack_path) {
133 base::FilePath manifest_path = unpack_path.Append(
134 FILE_PATH_LITERAL("manifest.json"));
135 if (!base::PathExists(manifest_path))
137 return ReadJSONManifest(manifest_path);
140 // Check that the component's manifest is for PNaCl, and check the
141 // PNaCl manifest indicates this is the correct arch-specific package.
142 bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
143 const base::DictionaryValue& pnacl_manifest,
144 Version* version_out) {
145 // Make sure we have the right |manifest| file.
147 if (!manifest.GetStringASCII("name", &name)) {
148 LOG(WARNING) << "'name' field is missing from manifest!";
151 // For the webstore, we've given different names to each of the
152 // architecture specific packages (and test/QA vs not test/QA)
153 // so only part of it is the same.
154 if (name.find(kPnaclManifestName) == std::string::npos) {
155 LOG(WARNING) << "'name' field in manifest is invalid ("
156 << name << ") -- missing ("
157 << kPnaclManifestName << ")";
161 std::string proposed_version;
162 if (!manifest.GetStringASCII("version", &proposed_version)) {
163 LOG(WARNING) << "'version' field is missing from manifest!";
166 Version version(proposed_version.c_str());
167 if (!version.IsValid()) {
168 LOG(WARNING) << "'version' field in manifest is invalid "
169 << version.GetString();
173 // Now check the |pnacl_manifest|.
175 if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
176 LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
179 if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) {
180 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
181 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")";
185 *version_out = version;
191 PnaclComponentInstaller::PnaclComponentInstaller()
193 updates_disabled_(false),
195 #if defined(OS_CHROMEOS)
200 PnaclComponentInstaller::~PnaclComponentInstaller() {
203 void PnaclComponentInstaller::OnUpdateError(int error) {
204 NOTREACHED() << "Pnacl update error: " << error;
207 // Pnacl components have the version encoded in the path itself:
208 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
209 // and the base directory will be:
210 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
211 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
212 // For ChromeOS, temporarily make this user-dependent (for integrity) until
213 // we find a better solution.
214 // This is not ideal because of the following:
215 // (a) We end up with per-user copies instead of a single copy
216 // (b) The profile can change as users log in to different accounts
217 // so we need to watch for user-login-events (see pnacl_profile_observer.h).
219 DCHECK(!current_profile_path_.empty());
220 base::FilePath path = current_profile_path_.Append(
221 FILE_PATH_LITERAL("pnacl"));
224 base::FilePath result;
225 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
230 void PnaclComponentInstaller::OnProfileChange() {
231 // On chromeos, we want to find the --login-profile=<foo> dir.
232 // Even though the path does vary between users, the content
233 // changes when logging out and logging in.
234 ProfileManager* pm = g_browser_process->profile_manager();
235 current_profile_path_ = pm->user_data_dir().Append(
236 pm->GetInitialProfileDir());
239 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
240 const base::FilePath& unpack_path) {
241 scoped_ptr<base::DictionaryValue> pnacl_manifest(
242 ReadPnaclManifest(unpack_path));
243 if (pnacl_manifest == NULL) {
244 LOG(WARNING) << "Failed to read pnacl manifest.";
249 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
250 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
254 // Don't install if the current version is actually newer.
255 if (current_version().CompareTo(version) > 0) {
259 // Passed the basic tests. Time to install it.
260 base::FilePath path = GetPnaclBaseDirectory().AppendASCII(
261 version.GetString());
262 if (base::PathExists(path)) {
263 LOG(WARNING) << "Target path already exists, not installing.";
266 if (!base::Move(unpack_path, path)) {
267 LOG(WARNING) << "Move failed, not installing.";
271 // Installation is done. Now tell the rest of chrome.
272 // - The path service.
273 // - Callbacks that requested an update.
274 set_current_version(version);
275 OverrideDirPnaclComponent(path);
279 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
280 // returns the assumed install path. The path separator in |file| is '/'
281 // for all platforms. Caller is responsible for checking that the
282 // |installed_file| actually exists.
283 bool PnaclComponentInstaller::GetInstalledFile(
284 const std::string& file, base::FilePath* installed_file) {
285 if (current_version().Equals(Version(kNullVersion)))
288 *installed_file = GetPnaclBaseDirectory().AppendASCII(
289 current_version().GetString()).AppendASCII(file);
293 CrxComponent PnaclComponentInstaller::GetCrxComponent() {
294 CrxComponent pnacl_component;
295 pnacl_component.version = current_version();
296 pnacl_component.name = "pnacl";
297 pnacl_component.installer = this;
298 pnacl_component.fingerprint = current_fingerprint();
299 SetPnaclHash(&pnacl_component);
301 return pnacl_component;
306 void FinishPnaclUpdateRegistration(const Version& current_version,
307 const std::string& current_fingerprint,
308 PnaclComponentInstaller* pci) {
309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310 pci->set_current_version(current_version);
311 pci->set_current_fingerprint(current_fingerprint);
312 CrxComponent pnacl_component = pci->GetCrxComponent();
314 ComponentUpdateService::Status status =
315 pci->cus()->RegisterComponent(pnacl_component);
316 if (status != ComponentUpdateService::kOk
317 && status != ComponentUpdateService::kReplaced) {
318 NOTREACHED() << "Pnacl component registration failed.";
322 // Check if there is an existing version on disk first to know when
323 // a hosted version is actually newer.
324 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
326 base::FilePath path = pci->GetPnaclBaseDirectory();
327 if (!base::PathExists(path)) {
328 if (!file_util::CreateDirectory(path)) {
329 NOTREACHED() << "Could not create base Pnacl directory.";
334 Version current_version(kNullVersion);
335 std::string current_fingerprint;
336 std::vector<base::FilePath> older_dirs;
337 if (GetLatestPnaclDirectory(pci, &path, ¤t_version, &older_dirs)) {
338 scoped_ptr<base::DictionaryValue> manifest(
339 ReadComponentManifest(path));
340 scoped_ptr<base::DictionaryValue> pnacl_manifest(
341 ReadPnaclManifest(path));
342 Version manifest_version;
343 // Check that the component manifest and PNaCl manifest files
344 // are legit, and that the indicated version matches the one
345 // encoded within the path name.
346 if (manifest == NULL || pnacl_manifest == NULL
347 || !CheckPnaclComponentManifest(*manifest,
350 || !current_version.Equals(manifest_version)) {
351 current_version = Version(kNullVersion);
353 OverrideDirPnaclComponent(path);
354 base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
355 ¤t_fingerprint);
359 // If updates are disabled, only discover the current version
360 // and OverrideDirPnaclComponent. That way, developers can use
361 // a pinned version. Do not actually finish registration with
362 // the component update service.
363 if (pci->updates_disabled())
366 BrowserThread::PostTask(
367 BrowserThread::UI, FROM_HERE,
368 base::Bind(&FinishPnaclUpdateRegistration,
373 // Remove older versions of PNaCl.
374 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
375 iter != older_dirs.end(); ++iter) {
376 base::DeleteFile(*iter, true);
380 // Remove old per-profile copies of PNaCl (was for ChromeOS).
381 // TODO(jvoung): Delete this code once most ChromeOS users have reaped
382 // their old per-profile copies of PNaCl.
383 void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller* pci) {
384 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
385 base::FilePath path = pci->GetPnaclBaseDirectory();
386 if (!base::PathExists(path))
389 // Do a basic sanity check first.
391 && path.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
392 base::DeleteFile(path, true);
396 void GetProfileInformation(PnaclComponentInstaller* pci) {
397 // Bail if not logged in yet.
398 if (!g_browser_process->profile_manager()->IsLoggedIn()) {
402 pci->OnProfileChange();
404 // Do not actually register PNaCl for component updates, for CHROMEOS.
405 // Just get the profile information and delete the per-profile files
407 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
408 base::Bind(&ReapOldChromeOSPnaclFiles, pci));
413 void PnaclComponentInstaller::RegisterPnaclComponent(
414 ComponentUpdateService* cus,
415 const CommandLine& command_line) {
416 // Register PNaCl by default (can be disabled).
417 updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
419 // If per_user, create a profile observer to watch for logins.
420 // Only do so after cus_ is set to something non-null.
421 if (per_user_ && !profile_observer_) {
422 profile_observer_.reset(new PnaclProfileObserver(this));
425 // Figure out profile information, before proceeding to look for files.
426 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
427 base::Bind(&GetProfileInformation, this));
429 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
430 base::Bind(&StartPnaclUpdateRegistration, this));
434 void PnaclComponentInstaller::ReRegisterPnacl() {
436 // Figure out profile information, before proceeding to look for files.
437 BrowserThread::PostTask(
438 BrowserThread::UI, FROM_HERE,
439 base::Bind(&GetProfileInformation, this));