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_component_installer.h"
11 #include "base/atomicops.h"
12 #include "base/base_paths.h"
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/compiler_specific.h"
16 #include "base/files/file_enumerator.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/json/json_file_value_serializer.h"
20 #include "base/logging.h"
21 #include "base/path_service.h"
22 #include "base/strings/string_util.h"
23 #include "base/values.h"
24 #include "base/version.h"
25 #include "base/win/windows_version.h"
26 #include "build/build_config.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "components/component_updater/component_updater_service.h"
30 #include "components/nacl/common/nacl_switches.h"
31 #include "components/update_client/update_query_params.h"
32 #include "content/public/browser/browser_thread.h"
34 using content::BrowserThread
;
35 using update_client::CrxComponent
;
36 using update_client::UpdateQueryParams
;
38 namespace component_updater
{
42 // Name of the Pnacl component specified in the manifest.
43 const char kPnaclManifestName
[] = "PNaCl Translator";
45 // Sanitize characters from Pnacl Arch value so that they can be used
46 // in path names. This should only be characters in the set: [a-z0-9_].
47 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
48 std::string
SanitizeForPath(const std::string
& input
) {
50 base::ReplaceChars(input
, "-", "_", &result
);
54 // Set the component's hash to the multi-CRX PNaCl package.
55 void SetPnaclHash(CrxComponent
* component
) {
56 static const uint8_t sha256_hash
[32] = {
57 // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
58 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36,
59 0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32,
60 0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba,
61 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c
64 component
->pk_hash
.assign(sha256_hash
, &sha256_hash
[arraysize(sha256_hash
)]);
67 // If we don't have Pnacl installed, this is the version we claim.
68 const char kNullVersion
[] = "0.0.0.0";
69 const char kMinPnaclVersion
[] = "0.2.0.86";
71 // Initially say that we do not need OnDemand updates. This should be
72 // updated by CheckVersionCompatiblity(), before doing any URLRequests
73 // that depend on PNaCl.
74 volatile base::subtle::Atomic32 needs_on_demand_update
= 0;
76 void CheckVersionCompatiblity(const base::Version
& current_version
) {
77 // Using NoBarrier, since needs_on_demand_update is standalone and does
78 // not have other associated data.
79 base::subtle::NoBarrier_Store(&needs_on_demand_update
,
80 current_version
.IsOlderThan(kMinPnaclVersion
));
83 // PNaCl is packaged as a multi-CRX. This returns the platform-specific
84 // subdirectory that is part of that multi-CRX.
85 base::FilePath
GetPlatformDir(const base::FilePath
& base_path
) {
86 std::string arch
= SanitizeForPath(UpdateQueryParams::GetNaclArch());
87 return base_path
.AppendASCII("_platform_specific").AppendASCII(arch
);
90 // Tell the rest of the world where to find the platform-specific PNaCl files.
91 void OverrideDirPnaclComponent(const base::FilePath
& base_path
) {
92 PathService::Override(chrome::DIR_PNACL_COMPONENT
, GetPlatformDir(base_path
));
95 bool GetLatestPnaclDirectory(const scoped_refptr
<PnaclComponentInstaller
>& pci
,
96 base::FilePath
* latest_dir
,
97 Version
* latest_version
,
98 std::vector
<base::FilePath
>* older_dirs
) {
99 // Enumerate all versions starting from the base directory.
100 base::FilePath base_dir
= pci
->GetPnaclBaseDirectory();
102 base::FileEnumerator
file_enumerator(
103 base_dir
, false, base::FileEnumerator::DIRECTORIES
);
104 for (base::FilePath path
= file_enumerator
.Next(); !path
.value().empty();
105 path
= file_enumerator
.Next()) {
106 Version
version(path
.BaseName().MaybeAsASCII());
107 if (!version
.IsValid())
110 if (version
.CompareTo(*latest_version
) > 0) {
111 older_dirs
->push_back(*latest_dir
);
113 *latest_version
= version
;
115 older_dirs
->push_back(path
);
118 *latest_version
= version
;
126 // Read a manifest file in.
127 base::DictionaryValue
* ReadJSONManifest(const base::FilePath
& manifest_path
) {
128 JSONFileValueDeserializer
deserializer(manifest_path
);
130 scoped_ptr
<base::Value
> root(deserializer
.Deserialize(NULL
, &error
));
133 if (!root
->IsType(base::Value::TYPE_DICTIONARY
))
135 return static_cast<base::DictionaryValue
*>(root
.release());
138 // Read the PNaCl specific manifest.
139 base::DictionaryValue
* ReadPnaclManifest(const base::FilePath
& unpack_path
) {
140 base::FilePath manifest_path
=
141 GetPlatformDir(unpack_path
).AppendASCII("pnacl_public_pnacl_json");
142 if (!base::PathExists(manifest_path
))
144 return ReadJSONManifest(manifest_path
);
147 // Read the component's manifest.json.
148 base::DictionaryValue
* ReadComponentManifest(
149 const base::FilePath
& unpack_path
) {
150 base::FilePath manifest_path
=
151 unpack_path
.Append(FILE_PATH_LITERAL("manifest.json"));
152 if (!base::PathExists(manifest_path
))
154 return ReadJSONManifest(manifest_path
);
157 // Check that the component's manifest is for PNaCl, and check the
158 // PNaCl manifest indicates this is the correct arch-specific package.
159 bool CheckPnaclComponentManifest(const base::DictionaryValue
& manifest
,
160 const base::DictionaryValue
& pnacl_manifest
,
161 Version
* version_out
) {
162 // Make sure we have the right |manifest| file.
164 if (!manifest
.GetStringASCII("name", &name
)) {
165 LOG(WARNING
) << "'name' field is missing from manifest!";
168 // For the webstore, we've given different names to each of the
169 // architecture specific packages (and test/QA vs not test/QA)
170 // so only part of it is the same.
171 if (name
.find(kPnaclManifestName
) == std::string::npos
) {
172 LOG(WARNING
) << "'name' field in manifest is invalid (" << name
173 << ") -- missing (" << 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(UpdateQueryParams::GetNaclArch()) != 0) {
196 LOG(WARNING
) << "'pnacl-arch' field in manifest is invalid (" << arch
197 << " vs " << UpdateQueryParams::GetNaclArch() << ")";
201 *version_out
= version
;
207 PnaclComponentInstaller::PnaclComponentInstaller() : cus_(NULL
) {
210 PnaclComponentInstaller::~PnaclComponentInstaller() {
213 void PnaclComponentInstaller::OnUpdateError(int error
) {
214 NOTREACHED() << "Pnacl update error: " << error
;
217 // Pnacl components have the version encoded in the path itself:
218 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
219 // and the base directory will be:
220 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
221 base::FilePath
PnaclComponentInstaller::GetPnaclBaseDirectory() {
222 base::FilePath result
;
223 CHECK(PathService::Get(chrome::DIR_PNACL_BASE
, &result
));
227 bool PnaclComponentInstaller::Install(const base::DictionaryValue
& manifest
,
228 const base::FilePath
& unpack_path
) {
229 scoped_ptr
<base::DictionaryValue
> pnacl_manifest(
230 ReadPnaclManifest(unpack_path
));
231 if (pnacl_manifest
== NULL
) {
232 LOG(WARNING
) << "Failed to read pnacl manifest.";
237 if (!CheckPnaclComponentManifest(manifest
, *pnacl_manifest
, &version
)) {
238 LOG(WARNING
) << "CheckPnaclComponentManifest failed, not installing.";
242 // Don't install if the current version is actually newer.
243 if (current_version().CompareTo(version
) > 0) {
247 // Passed the basic tests. Time to install it.
248 base::FilePath path
=
249 GetPnaclBaseDirectory().AppendASCII(version
.GetString());
250 if (base::PathExists(path
)) {
251 if (!base::DeleteFile(path
, true))
254 if (!base::Move(unpack_path
, path
)) {
255 LOG(WARNING
) << "Move failed, not installing.";
259 // Installation is done. Now tell the rest of chrome.
260 // - The path service.
261 // - Callbacks that requested an update.
262 set_current_version(version
);
263 CheckVersionCompatiblity(version
);
264 OverrideDirPnaclComponent(path
);
268 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
269 // returns the assumed install path. The path separator in |file| is '/'
270 // for all platforms. Caller is responsible for checking that the
271 // |installed_file| actually exists.
272 bool PnaclComponentInstaller::GetInstalledFile(const std::string
& file
,
273 base::FilePath
* installed_file
) {
274 if (current_version().Equals(Version(kNullVersion
)))
277 *installed_file
= GetPnaclBaseDirectory()
278 .AppendASCII(current_version().GetString())
283 bool PnaclComponentInstaller::Uninstall() {
287 CrxComponent
PnaclComponentInstaller::GetCrxComponent() {
288 CrxComponent pnacl_component
;
289 pnacl_component
.version
= current_version();
290 pnacl_component
.name
= "pnacl";
291 pnacl_component
.installer
= this;
292 pnacl_component
.fingerprint
= current_fingerprint();
293 SetPnaclHash(&pnacl_component
);
295 return pnacl_component
;
300 void FinishPnaclUpdateRegistration(
301 const Version
& current_version
,
302 const std::string
& current_fingerprint
,
303 const scoped_refptr
<PnaclComponentInstaller
>& pci
) {
304 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
305 pci
->set_current_version(current_version
);
306 CheckVersionCompatiblity(current_version
);
307 pci
->set_current_fingerprint(current_fingerprint
);
308 CrxComponent pnacl_component
= pci
->GetCrxComponent();
310 if (!pci
->cus()->RegisterComponent(pnacl_component
))
311 NOTREACHED() << "Pnacl component registration failed.";
314 // Check if there is an existing version on disk first to know when
315 // a hosted version is actually newer.
316 void StartPnaclUpdateRegistration(
317 const scoped_refptr
<PnaclComponentInstaller
>& pci
) {
318 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
319 base::FilePath path
= pci
->GetPnaclBaseDirectory();
320 if (!base::PathExists(path
)) {
321 if (!base::CreateDirectory(path
)) {
322 NOTREACHED() << "Could not create base Pnacl directory.";
327 Version
current_version(kNullVersion
);
328 std::string current_fingerprint
;
329 std::vector
<base::FilePath
> older_dirs
;
330 if (GetLatestPnaclDirectory(pci
, &path
, ¤t_version
, &older_dirs
)) {
331 scoped_ptr
<base::DictionaryValue
> manifest(ReadComponentManifest(path
));
332 scoped_ptr
<base::DictionaryValue
> pnacl_manifest(ReadPnaclManifest(path
));
333 Version manifest_version
;
334 // Check that the component manifest and PNaCl manifest files
335 // are legit, and that the indicated version matches the one
336 // encoded within the path name.
337 if (manifest
== NULL
|| pnacl_manifest
== NULL
||
338 !CheckPnaclComponentManifest(*manifest
,
340 &manifest_version
) ||
341 !current_version
.Equals(manifest_version
)) {
342 current_version
= Version(kNullVersion
);
344 OverrideDirPnaclComponent(path
);
345 base::ReadFileToString(path
.AppendASCII("manifest.fingerprint"),
346 ¤t_fingerprint
);
350 BrowserThread::PostTask(BrowserThread::UI
,
352 base::Bind(&FinishPnaclUpdateRegistration
,
357 // Remove older versions of PNaCl.
358 for (std::vector
<base::FilePath
>::iterator iter
= older_dirs
.begin();
359 iter
!= older_dirs
.end();
361 base::DeleteFile(*iter
, true);
367 void PnaclComponentInstaller::RegisterPnaclComponent(
368 ComponentUpdateService
* cus
) {
370 BrowserThread::PostTask(
371 BrowserThread::FILE, FROM_HERE
,
372 base::Bind(&StartPnaclUpdateRegistration
, make_scoped_refptr(this)));
375 } // namespace component_updater
379 bool NeedsOnDemandUpdate() {
380 return base::subtle::NoBarrier_Load(
381 &component_updater::needs_on_demand_update
) != 0;