Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / component_updater / pnacl_component_installer.cc
blob4f2c8228c08f5dd8d2d66030b9a86aa271708045
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"
7 #include <stdint.h>
8 #include <string>
9 #include <vector>
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 {
40 namespace {
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) {
49 std::string result;
50 base::ReplaceChars(input, "-", "_", &result);
51 return 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();
101 bool found = false;
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())
108 continue;
109 if (found) {
110 if (version.CompareTo(*latest_version) > 0) {
111 older_dirs->push_back(*latest_dir);
112 *latest_dir = path;
113 *latest_version = version;
114 } else {
115 older_dirs->push_back(path);
117 } else {
118 *latest_version = version;
119 *latest_dir = path;
120 found = true;
123 return found;
126 // Read a manifest file in.
127 base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) {
128 JSONFileValueDeserializer deserializer(manifest_path);
129 std::string error;
130 scoped_ptr<base::Value> root(deserializer.Deserialize(NULL, &error));
131 if (!root.get())
132 return NULL;
133 if (!root->IsType(base::Value::TYPE_DICTIONARY))
134 return NULL;
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))
143 return NULL;
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))
153 return NULL;
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.
163 std::string name;
164 if (!manifest.GetStringASCII("name", &name)) {
165 LOG(WARNING) << "'name' field is missing from manifest!";
166 return false;
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 << ")";
174 return false;
177 std::string proposed_version;
178 if (!manifest.GetStringASCII("version", &proposed_version)) {
179 LOG(WARNING) << "'version' field is missing from manifest!";
180 return false;
182 Version version(proposed_version.c_str());
183 if (!version.IsValid()) {
184 LOG(WARNING) << "'version' field in manifest is invalid "
185 << version.GetString();
186 return false;
189 // Now check the |pnacl_manifest|.
190 std::string arch;
191 if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
192 LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
193 return false;
195 if (arch.compare(UpdateQueryParams::GetNaclArch()) != 0) {
196 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch
197 << " vs " << UpdateQueryParams::GetNaclArch() << ")";
198 return false;
201 *version_out = version;
202 return true;
205 } // namespace
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));
224 return 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.";
233 return false;
236 Version version;
237 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
238 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
239 return false;
242 // Don't install if the current version is actually newer.
243 if (current_version().CompareTo(version) > 0) {
244 return false;
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))
252 return false;
254 if (!base::Move(unpack_path, path)) {
255 LOG(WARNING) << "Move failed, not installing.";
256 return false;
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);
265 return true;
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)))
275 return false;
277 *installed_file = GetPnaclBaseDirectory()
278 .AppendASCII(current_version().GetString())
279 .AppendASCII(file);
280 return true;
283 bool PnaclComponentInstaller::Uninstall() {
284 return false;
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;
298 namespace {
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.";
323 return;
327 Version current_version(kNullVersion);
328 std::string current_fingerprint;
329 std::vector<base::FilePath> older_dirs;
330 if (GetLatestPnaclDirectory(pci, &path, &current_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,
339 *pnacl_manifest,
340 &manifest_version) ||
341 !current_version.Equals(manifest_version)) {
342 current_version = Version(kNullVersion);
343 } else {
344 OverrideDirPnaclComponent(path);
345 base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
346 &current_fingerprint);
350 BrowserThread::PostTask(BrowserThread::UI,
351 FROM_HERE,
352 base::Bind(&FinishPnaclUpdateRegistration,
353 current_version,
354 current_fingerprint,
355 pci));
357 // Remove older versions of PNaCl.
358 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
359 iter != older_dirs.end();
360 ++iter) {
361 base::DeleteFile(*iter, true);
365 } // namespace
367 void PnaclComponentInstaller::RegisterPnaclComponent(
368 ComponentUpdateService* cus) {
369 cus_ = cus;
370 BrowserThread::PostTask(
371 BrowserThread::FILE, FROM_HERE,
372 base::Bind(&StartPnaclUpdateRegistration, make_scoped_refptr(this)));
375 } // namespace component_updater
377 namespace pnacl {
379 bool NeedsOnDemandUpdate() {
380 return base::subtle::NoBarrier_Load(
381 &component_updater::needs_on_demand_update) != 0;
384 } // namespace pnacl