pnacl_component_installer.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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.
4
5#include "chrome/browser/component_updater/pnacl/pnacl_component_installer.h"
6
7#include <string>
8#include <vector>
9
10#include "base/atomicops.h"
11#include "base/base_paths.h"
12#include "base/bind.h"
13#include "base/callback.h"
14#include "base/command_line.h"
15#include "base/compiler_specific.h"
16#include "base/file_util.h"
17#include "base/files/file_enumerator.h"
18#include "base/files/file_path.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/browser/component_updater/component_updater_service.h"
29#include "chrome/browser/omaha_query_params/omaha_query_params.h"
30#include "chrome/common/chrome_paths.h"
31#include "components/nacl/common/nacl_switches.h"
32#include "content/public/browser/browser_thread.h"
33
34using chrome::OmahaQueryParams;
35using content::BrowserThread;
36
37namespace component_updater {
38
39namespace {
40
41// Name of the Pnacl component specified in the manifest.
42const char kPnaclManifestName[] = "PNaCl Translator";
43
44// Sanitize characters from Pnacl Arch value so that they can be used
45// in path names.  This should only be characters in the set: [a-z0-9_].
46// Keep in sync with chrome/browser/nacl_host/nacl_file_host.
47std::string SanitizeForPath(const std::string& input) {
48  std::string result;
49  base::ReplaceChars(input, "-", "_", &result);
50  return result;
51}
52
53// Set the component's hash to the multi-CRX PNaCl package.
54void SetPnaclHash(CrxComponent* component) {
55  static const uint8 sha256_hash[32] = {
56      // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
57      0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36,
58      0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32,
59      0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba,
60      0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c
61  };
62
63  component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]);
64}
65
66// If we don't have Pnacl installed, this is the version we claim.
67const char kNullVersion[] = "0.0.0.0";
68const char kMinPnaclVersion[] = "0.1.0.13154";
69
70// Initially say that we do not need OnDemand updates. This should be
71// updated by CheckVersionCompatiblity(), before doing any URLRequests
72// that depend on PNaCl.
73volatile base::subtle::Atomic32 needs_on_demand_update = 0;
74
75void CheckVersionCompatiblity(const base::Version& current_version) {
76  // Using NoBarrier, since needs_on_demand_update is standalone and does
77  // not have other associated data.
78  base::subtle::NoBarrier_Store(&needs_on_demand_update,
79                                current_version.IsOlderThan(kMinPnaclVersion));
80}
81
82// PNaCl is packaged as a multi-CRX.  This returns the platform-specific
83// subdirectory that is part of that multi-CRX.
84base::FilePath GetPlatformDir(const base::FilePath& base_path) {
85  std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch());
86  return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
87}
88
89// Tell the rest of the world where to find the platform-specific PNaCl files.
90void OverrideDirPnaclComponent(const base::FilePath& base_path) {
91  PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path));
92}
93
94bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
95                             base::FilePath* latest_dir,
96                             Version* latest_version,
97                             std::vector<base::FilePath>* older_dirs) {
98  // Enumerate all versions starting from the base directory.
99  base::FilePath base_dir = pci->GetPnaclBaseDirectory();
100  bool found = false;
101  base::FileEnumerator file_enumerator(
102      base_dir, false, base::FileEnumerator::DIRECTORIES);
103  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
104       path = file_enumerator.Next()) {
105    Version version(path.BaseName().MaybeAsASCII());
106    if (!version.IsValid())
107      continue;
108    if (found) {
109      if (version.CompareTo(*latest_version) > 0) {
110        older_dirs->push_back(*latest_dir);
111        *latest_dir = path;
112        *latest_version = version;
113      } else {
114        older_dirs->push_back(path);
115      }
116    } else {
117      *latest_version = version;
118      *latest_dir = path;
119      found = true;
120    }
121  }
122  return found;
123}
124
125// Read a manifest file in.
126base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) {
127  JSONFileValueSerializer serializer(manifest_path);
128  std::string error;
129  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
130  if (!root.get())
131    return NULL;
132  if (!root->IsType(base::Value::TYPE_DICTIONARY))
133    return NULL;
134  return static_cast<base::DictionaryValue*>(root.release());
135}
136
137// Read the PNaCl specific manifest.
138base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
139  base::FilePath manifest_path =
140      GetPlatformDir(unpack_path).AppendASCII("pnacl_public_pnacl_json");
141  if (!base::PathExists(manifest_path))
142    return NULL;
143  return ReadJSONManifest(manifest_path);
144}
145
146// Read the component's manifest.json.
147base::DictionaryValue* ReadComponentManifest(
148    const base::FilePath& unpack_path) {
149  base::FilePath manifest_path =
150      unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
151  if (!base::PathExists(manifest_path))
152    return NULL;
153  return ReadJSONManifest(manifest_path);
154}
155
156// Check that the component's manifest is for PNaCl, and check the
157// PNaCl manifest indicates this is the correct arch-specific package.
158bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
159                                 const base::DictionaryValue& pnacl_manifest,
160                                 Version* version_out) {
161  // Make sure we have the right |manifest| file.
162  std::string name;
163  if (!manifest.GetStringASCII("name", &name)) {
164    LOG(WARNING) << "'name' field is missing from manifest!";
165    return false;
166  }
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 (" << name
172                 << ") -- missing (" << kPnaclManifestName << ")";
173    return false;
174  }
175
176  std::string proposed_version;
177  if (!manifest.GetStringASCII("version", &proposed_version)) {
178    LOG(WARNING) << "'version' field is missing from manifest!";
179    return false;
180  }
181  Version version(proposed_version.c_str());
182  if (!version.IsValid()) {
183    LOG(WARNING) << "'version' field in manifest is invalid "
184                 << version.GetString();
185    return false;
186  }
187
188  // Now check the |pnacl_manifest|.
189  std::string arch;
190  if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
191    LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
192    return false;
193  }
194  if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) {
195    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch
196                 << " vs " << OmahaQueryParams::GetNaclArch() << ")";
197    return false;
198  }
199
200  *version_out = version;
201  return true;
202}
203
204}  // namespace
205
206PnaclComponentInstaller::PnaclComponentInstaller()
207    : updates_disabled_(false), cus_(NULL) {
208}
209
210PnaclComponentInstaller::~PnaclComponentInstaller() {
211}
212
213void PnaclComponentInstaller::OnUpdateError(int error) {
214  NOTREACHED() << "Pnacl update error: " << error;
215}
216
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\.
221base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
222  base::FilePath result;
223  CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
224  return result;
225}
226
227bool 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;
234  }
235
236  Version version;
237  if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
238    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
239    return false;
240  }
241
242  // Don't install if the current version is actually newer.
243  if (current_version().CompareTo(version) > 0) {
244    return false;
245  }
246
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;
253  }
254  if (!base::Move(unpack_path, path)) {
255    LOG(WARNING) << "Move failed, not installing.";
256    return false;
257  }
258
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;
266}
267
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.
272bool PnaclComponentInstaller::GetInstalledFile(const std::string& file,
273                                               base::FilePath* installed_file) {
274  if (current_version().Equals(Version(kNullVersion)))
275    return false;
276
277  *installed_file = GetPnaclBaseDirectory()
278                        .AppendASCII(current_version().GetString())
279                        .AppendASCII(file);
280  return true;
281}
282
283CrxComponent PnaclComponentInstaller::GetCrxComponent() {
284  CrxComponent pnacl_component;
285  pnacl_component.version = current_version();
286  pnacl_component.name = "pnacl";
287  pnacl_component.installer = this;
288  pnacl_component.fingerprint = current_fingerprint();
289  SetPnaclHash(&pnacl_component);
290
291  return pnacl_component;
292}
293
294namespace {
295
296void FinishPnaclUpdateRegistration(const Version& current_version,
297                                   const std::string& current_fingerprint,
298                                   PnaclComponentInstaller* pci) {
299  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300  pci->set_current_version(current_version);
301  CheckVersionCompatiblity(current_version);
302  pci->set_current_fingerprint(current_fingerprint);
303  CrxComponent pnacl_component = pci->GetCrxComponent();
304
305  ComponentUpdateService::Status status =
306      pci->cus()->RegisterComponent(pnacl_component);
307  if (status != ComponentUpdateService::kOk &&
308      status != ComponentUpdateService::kReplaced) {
309    NOTREACHED() << "Pnacl component registration failed.";
310  }
311}
312
313// Check if there is an existing version on disk first to know when
314// a hosted version is actually newer.
315void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
316  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
317  base::FilePath path = pci->GetPnaclBaseDirectory();
318  if (!base::PathExists(path)) {
319    if (!base::CreateDirectory(path)) {
320      NOTREACHED() << "Could not create base Pnacl directory.";
321      return;
322    }
323  }
324
325  Version current_version(kNullVersion);
326  std::string current_fingerprint;
327  std::vector<base::FilePath> older_dirs;
328  if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
329    scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path));
330    scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path));
331    Version manifest_version;
332    // Check that the component manifest and PNaCl manifest files
333    // are legit, and that the indicated version matches the one
334    // encoded within the path name.
335    if (manifest == NULL || pnacl_manifest == NULL ||
336        !CheckPnaclComponentManifest(*manifest,
337                                     *pnacl_manifest,
338                                     &manifest_version) ||
339        !current_version.Equals(manifest_version)) {
340      current_version = Version(kNullVersion);
341    } else {
342      OverrideDirPnaclComponent(path);
343      base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
344                             &current_fingerprint);
345    }
346  }
347
348  // If updates are disabled, only discover the current version
349  // and OverrideDirPnaclComponent. That way, developers can use
350  // a pinned version. Do not actually finish registration with
351  // the component update service.
352  if (pci->updates_disabled())
353    return;
354
355  BrowserThread::PostTask(BrowserThread::UI,
356                          FROM_HERE,
357                          base::Bind(&FinishPnaclUpdateRegistration,
358                                     current_version,
359                                     current_fingerprint,
360                                     pci));
361
362  // Remove older versions of PNaCl.
363  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
364       iter != older_dirs.end();
365       ++iter) {
366    base::DeleteFile(*iter, true);
367  }
368}
369
370}  // namespace
371
372void PnaclComponentInstaller::RegisterPnaclComponent(
373    ComponentUpdateService* cus,
374    const CommandLine& command_line) {
375  // Register PNaCl by default (can be disabled).
376  updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
377  cus_ = cus;
378  BrowserThread::PostTask(BrowserThread::FILE,
379                          FROM_HERE,
380                          base::Bind(&StartPnaclUpdateRegistration, this));
381}
382
383}  // namespace component_updater
384
385namespace pnacl {
386
387bool NeedsOnDemandUpdate() {
388  return base::subtle::NoBarrier_Load(
389             &component_updater::needs_on_demand_update) != 0;
390}
391
392}  // namespace pnacl
393