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/atomicops.h"
8 #include "base/base_paths.h"
10 #include "base/callback.h"
11 #include "base/command_line.h"
12 #include "base/compiler_specific.h"
13 #include "base/file_util.h"
14 #include "base/files/file_enumerator.h"
15 #include "base/files/file_path.h"
16 #include "base/json/json_file_value_serializer.h"
17 #include "base/logging.h"
18 #include "base/path_service.h"
19 #include "base/strings/string_util.h"
20 #include "base/values.h"
21 #include "base/version.h"
22 #include "base/win/windows_version.h"
23 #include "build/build_config.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/component_updater/component_updater_service.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/omaha_query_params/omaha_query_params.h"
30 #include "components/nacl/common/nacl_switches.h"
31 #include "content/public/browser/browser_thread.h"
33 using chrome::OmahaQueryParams
;
34 using content::BrowserThread
;
36 namespace component_updater
{
40 // Name of the Pnacl component specified in the manifest.
41 const char kPnaclManifestName
[] = "PNaCl Translator";
43 // Sanitize characters from Pnacl Arch value so that they can be used
44 // in path names. This should only be characters in the set: [a-z0-9_].
45 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
46 std::string
SanitizeForPath(const std::string
& input
) {
48 base::ReplaceChars(input
, "-", "_", &result
);
52 // Set the component's hash to the multi-CRX PNaCl package.
53 void SetPnaclHash(CrxComponent
* component
) {
54 static const uint8 sha256_hash
[32] =
55 { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
56 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44,
57 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51,
58 0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c};
60 component
->pk_hash
.assign(sha256_hash
,
61 &sha256_hash
[arraysize(sha256_hash
)]);
64 // If we don't have Pnacl installed, this is the version we claim.
65 const char kNullVersion
[] = "0.0.0.0";
66 const char kMinPnaclVersion
[] = "0.1.0.12181";
68 // Initially say that we do not need OnDemand updates. This should be
69 // updated by CheckVersionCompatiblity(), before doing any URLRequests
70 // that depend on PNaCl.
71 volatile base::subtle::Atomic32 needs_on_demand_update
= 0;
73 void CheckVersionCompatiblity(const base::Version
& current_version
) {
74 // Using NoBarrier, since needs_on_demand_update is standalone and does
75 // not have other associated data.
76 base::subtle::NoBarrier_Store(&needs_on_demand_update
,
77 current_version
.IsOlderThan(kMinPnaclVersion
));
80 // PNaCl is packaged as a multi-CRX. This returns the platform-specific
81 // subdirectory that is part of that multi-CRX.
82 base::FilePath
GetPlatformDir(const base::FilePath
& base_path
) {
83 std::string arch
= SanitizeForPath(OmahaQueryParams::getNaclArch());
84 return base_path
.AppendASCII("_platform_specific").AppendASCII(arch
);
87 // Tell the rest of the world where to find the platform-specific PNaCl files.
88 void OverrideDirPnaclComponent(const base::FilePath
& base_path
) {
89 PathService::Override(chrome::DIR_PNACL_COMPONENT
,
90 GetPlatformDir(base_path
));
93 bool GetLatestPnaclDirectory(PnaclComponentInstaller
* pci
,
94 base::FilePath
* latest_dir
,
95 Version
* latest_version
,
96 std::vector
<base::FilePath
>* older_dirs
) {
97 // Enumerate all versions starting from the base directory.
98 base::FilePath base_dir
= pci
->GetPnaclBaseDirectory();
101 file_enumerator(base_dir
, false, base::FileEnumerator::DIRECTORIES
);
102 for (base::FilePath path
= file_enumerator
.Next(); !path
.value().empty();
103 path
= file_enumerator
.Next()) {
104 Version
version(path
.BaseName().MaybeAsASCII());
105 if (!version
.IsValid())
108 if (version
.CompareTo(*latest_version
) > 0) {
109 older_dirs
->push_back(*latest_dir
);
111 *latest_version
= version
;
113 older_dirs
->push_back(path
);
116 *latest_version
= version
;
124 // Read a manifest file in.
125 base::DictionaryValue
* ReadJSONManifest(
126 const base::FilePath
& manifest_path
) {
127 JSONFileValueSerializer
serializer(manifest_path
);
129 scoped_ptr
<base::Value
> root(serializer
.Deserialize(NULL
, &error
));
132 if (!root
->IsType(base::Value::TYPE_DICTIONARY
))
134 return static_cast<base::DictionaryValue
*>(root
.release());
137 // Read the PNaCl specific manifest.
138 base::DictionaryValue
* ReadPnaclManifest(const base::FilePath
& unpack_path
) {
139 base::FilePath manifest_path
= GetPlatformDir(unpack_path
).AppendASCII(
140 "pnacl_public_pnacl_json");
141 if (!base::PathExists(manifest_path
))
143 return ReadJSONManifest(manifest_path
);
146 // Read the component's manifest.json.
147 base::DictionaryValue
* ReadComponentManifest(
148 const base::FilePath
& unpack_path
) {
149 base::FilePath manifest_path
= unpack_path
.Append(
150 FILE_PATH_LITERAL("manifest.json"));
151 if (!base::PathExists(manifest_path
))
153 return ReadJSONManifest(manifest_path
);
156 // Check that the component's manifest is for PNaCl, and check the
157 // PNaCl manifest indicates this is the correct arch-specific package.
158 bool CheckPnaclComponentManifest(const base::DictionaryValue
& manifest
,
159 const base::DictionaryValue
& pnacl_manifest
,
160 Version
* version_out
) {
161 // Make sure we have the right |manifest| file.
163 if (!manifest
.GetStringASCII("name", &name
)) {
164 LOG(WARNING
) << "'name' field is missing from manifest!";
167 // For the webstore, we've given different names to each of the
168 // architecture specific packages (and test/QA vs not test/QA)
169 // so only part of it is the same.
170 if (name
.find(kPnaclManifestName
) == std::string::npos
) {
171 LOG(WARNING
) << "'name' field in manifest is invalid ("
172 << name
<< ") -- missing ("
173 << kPnaclManifestName
<< ")";
177 std::string proposed_version
;
178 if (!manifest
.GetStringASCII("version", &proposed_version
)) {
179 LOG(WARNING
) << "'version' field is missing from manifest!";
182 Version
version(proposed_version
.c_str());
183 if (!version
.IsValid()) {
184 LOG(WARNING
) << "'version' field in manifest is invalid "
185 << version
.GetString();
189 // Now check the |pnacl_manifest|.
191 if (!pnacl_manifest
.GetStringASCII("pnacl-arch", &arch
)) {
192 LOG(WARNING
) << "'pnacl-arch' field is missing from pnacl-manifest!";
195 if (arch
.compare(OmahaQueryParams::getNaclArch()) != 0) {
196 LOG(WARNING
) << "'pnacl-arch' field in manifest is invalid ("
197 << arch
<< " vs " << OmahaQueryParams::getNaclArch() << ")";
201 *version_out
= version
;
207 PnaclComponentInstaller::PnaclComponentInstaller()
209 updates_disabled_(false),
211 #if defined(OS_CHROMEOS)
216 PnaclComponentInstaller::~PnaclComponentInstaller() {
219 void PnaclComponentInstaller::OnUpdateError(int error
) {
220 NOTREACHED() << "Pnacl update error: " << error
;
223 // Pnacl components have the version encoded in the path itself:
224 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
225 // and the base directory will be:
226 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
227 base::FilePath
PnaclComponentInstaller::GetPnaclBaseDirectory() {
228 // For ChromeOS, temporarily make this user-dependent (for integrity) until
229 // we find a better solution.
230 // This is not ideal because of the following:
231 // (a) We end up with per-user copies instead of a single copy
232 // (b) The profile can change as users log in to different accounts
233 // so we need to watch for user-login-events (see pnacl_profile_observer.h).
235 DCHECK(!current_profile_path_
.empty());
236 base::FilePath path
= current_profile_path_
.Append(
237 FILE_PATH_LITERAL("pnacl"));
240 base::FilePath result
;
241 CHECK(PathService::Get(chrome::DIR_PNACL_BASE
, &result
));
246 void PnaclComponentInstaller::OnProfileChange() {
247 // On chromeos, we want to find the --login-profile=<foo> dir.
248 // Even though the path does vary between users, the content
249 // changes when logging out and logging in.
250 ProfileManager
* pm
= g_browser_process
->profile_manager();
251 current_profile_path_
= pm
->user_data_dir().Append(
252 pm
->GetInitialProfileDir());
255 bool PnaclComponentInstaller::Install(const base::DictionaryValue
& manifest
,
256 const base::FilePath
& unpack_path
) {
257 scoped_ptr
<base::DictionaryValue
> pnacl_manifest(
258 ReadPnaclManifest(unpack_path
));
259 if (pnacl_manifest
== NULL
) {
260 LOG(WARNING
) << "Failed to read pnacl manifest.";
265 if (!CheckPnaclComponentManifest(manifest
, *pnacl_manifest
, &version
)) {
266 LOG(WARNING
) << "CheckPnaclComponentManifest failed, not installing.";
270 // Don't install if the current version is actually newer.
271 if (current_version().CompareTo(version
) > 0) {
275 // Passed the basic tests. Time to install it.
276 base::FilePath path
= GetPnaclBaseDirectory().AppendASCII(
277 version
.GetString());
278 if (base::PathExists(path
)) {
279 if (!base::DeleteFile(path
, true))
282 if (!base::Move(unpack_path
, path
)) {
283 LOG(WARNING
) << "Move failed, not installing.";
287 // Installation is done. Now tell the rest of chrome.
288 // - The path service.
289 // - Callbacks that requested an update.
290 set_current_version(version
);
291 CheckVersionCompatiblity(version
);
292 OverrideDirPnaclComponent(path
);
296 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
297 // returns the assumed install path. The path separator in |file| is '/'
298 // for all platforms. Caller is responsible for checking that the
299 // |installed_file| actually exists.
300 bool PnaclComponentInstaller::GetInstalledFile(
301 const std::string
& file
, base::FilePath
* installed_file
) {
302 if (current_version().Equals(Version(kNullVersion
)))
305 *installed_file
= GetPnaclBaseDirectory().AppendASCII(
306 current_version().GetString()).AppendASCII(file
);
310 CrxComponent
PnaclComponentInstaller::GetCrxComponent() {
311 CrxComponent pnacl_component
;
312 pnacl_component
.version
= current_version();
313 pnacl_component
.name
= "pnacl";
314 pnacl_component
.installer
= this;
315 pnacl_component
.fingerprint
= current_fingerprint();
316 SetPnaclHash(&pnacl_component
);
318 return pnacl_component
;
323 void FinishPnaclUpdateRegistration(const Version
& current_version
,
324 const std::string
& current_fingerprint
,
325 PnaclComponentInstaller
* pci
) {
326 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
327 pci
->set_current_version(current_version
);
328 CheckVersionCompatiblity(current_version
);
329 pci
->set_current_fingerprint(current_fingerprint
);
330 CrxComponent pnacl_component
= pci
->GetCrxComponent();
332 ComponentUpdateService::Status status
=
333 pci
->cus()->RegisterComponent(pnacl_component
);
334 if (status
!= ComponentUpdateService::kOk
335 && status
!= ComponentUpdateService::kReplaced
) {
336 NOTREACHED() << "Pnacl component registration failed.";
340 // Check if there is an existing version on disk first to know when
341 // a hosted version is actually newer.
342 void StartPnaclUpdateRegistration(PnaclComponentInstaller
* pci
) {
343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
344 base::FilePath path
= pci
->GetPnaclBaseDirectory();
345 if (!base::PathExists(path
)) {
346 if (!base::CreateDirectory(path
)) {
347 NOTREACHED() << "Could not create base Pnacl directory.";
352 Version
current_version(kNullVersion
);
353 std::string current_fingerprint
;
354 std::vector
<base::FilePath
> older_dirs
;
355 if (GetLatestPnaclDirectory(pci
, &path
, ¤t_version
, &older_dirs
)) {
356 scoped_ptr
<base::DictionaryValue
> manifest(
357 ReadComponentManifest(path
));
358 scoped_ptr
<base::DictionaryValue
> pnacl_manifest(
359 ReadPnaclManifest(path
));
360 Version manifest_version
;
361 // Check that the component manifest and PNaCl manifest files
362 // are legit, and that the indicated version matches the one
363 // encoded within the path name.
364 if (manifest
== NULL
|| pnacl_manifest
== NULL
365 || !CheckPnaclComponentManifest(*manifest
,
368 || !current_version
.Equals(manifest_version
)) {
369 current_version
= Version(kNullVersion
);
371 OverrideDirPnaclComponent(path
);
372 base::ReadFileToString(path
.AppendASCII("manifest.fingerprint"),
373 ¤t_fingerprint
);
377 // If updates are disabled, only discover the current version
378 // and OverrideDirPnaclComponent. That way, developers can use
379 // a pinned version. Do not actually finish registration with
380 // the component update service.
381 if (pci
->updates_disabled())
384 BrowserThread::PostTask(
385 BrowserThread::UI
, FROM_HERE
,
386 base::Bind(&FinishPnaclUpdateRegistration
,
391 // Remove older versions of PNaCl.
392 for (std::vector
<base::FilePath
>::iterator iter
= older_dirs
.begin();
393 iter
!= older_dirs
.end(); ++iter
) {
394 base::DeleteFile(*iter
, true);
398 // Remove old per-profile copies of PNaCl (was for ChromeOS).
399 // TODO(jvoung): Delete this code once most ChromeOS users have reaped
400 // their old per-profile copies of PNaCl.
401 void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller
* pci
) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
403 base::FilePath path
= pci
->GetPnaclBaseDirectory();
404 if (!base::PathExists(path
))
407 // Do a basic sanity check first.
409 && path
.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
410 base::DeleteFile(path
, true);
414 void GetProfileInformation(PnaclComponentInstaller
* pci
) {
415 // Bail if not logged in yet.
416 if (!g_browser_process
->profile_manager()->IsLoggedIn()) {
420 pci
->OnProfileChange();
422 // Do not actually register PNaCl for component updates, for CHROMEOS.
423 // Just get the profile information and delete the per-profile files
425 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
426 base::Bind(&ReapOldChromeOSPnaclFiles
, pci
));
431 void PnaclComponentInstaller::RegisterPnaclComponent(
432 ComponentUpdateService
* cus
,
433 const CommandLine
& command_line
) {
434 // Register PNaCl by default (can be disabled).
435 updates_disabled_
= command_line
.HasSwitch(switches::kDisablePnaclInstall
);
437 // If per_user, create a profile observer to watch for logins.
438 // Only do so after cus_ is set to something non-null.
439 if (per_user_
&& !profile_observer_
) {
440 profile_observer_
.reset(new PnaclProfileObserver(this));
443 // Figure out profile information, before proceeding to look for files.
444 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
445 base::Bind(&GetProfileInformation
, this));
447 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
448 base::Bind(&StartPnaclUpdateRegistration
, this));
452 void PnaclComponentInstaller::ReRegisterPnacl() {
454 // Figure out profile information, before proceeding to look for files.
455 BrowserThread::PostTask(
456 BrowserThread::UI
, FROM_HERE
,
457 base::Bind(&GetProfileInformation
, this));
460 } // namespace component_updater
464 bool NeedsOnDemandUpdate() {
465 return base::subtle::NoBarrier_Load(
466 &component_updater::needs_on_demand_update
) != 0;