pnacl_component_installer.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/compiler_specific.h"
15#include "base/file_util.h"
16#include "base/files/file_enumerator.h"
17#include "base/files/file_path.h"
18#include "base/json/json_file_value_serializer.h"
19#include "base/logging.h"
20#include "base/path_service.h"
21#include "base/strings/string_util.h"
22#include "base/values.h"
23#include "base/version.h"
24#include "base/win/windows_version.h"
25#include "build/build_config.h"
26#include "chrome/browser/browser_process.h"
27#include "chrome/browser/component_updater/component_updater_service.h"
28#include "chrome/common/chrome_paths.h"
29#include "components/nacl/common/nacl_switches.h"
30#include "components/omaha_query_params/omaha_query_params.h"
31#include "content/public/browser/browser_thread.h"
32
33using content::BrowserThread;
34using omaha_query_params::OmahaQueryParams;
35
36namespace component_updater {
37
38namespace {
39
40// Name of the Pnacl component specified in the manifest.
41const char kPnaclManifestName[] = "PNaCl Translator";
42
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.
46std::string SanitizeForPath(const std::string& input) {
47  std::string result;
48  base::ReplaceChars(input, "-", "_", &result);
49  return result;
50}
51
52// Set the component's hash to the multi-CRX PNaCl package.
53void 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,
57      0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32,
58      0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba,
59      0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c
60  };
61
62  component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]);
63}
64
65// If we don't have Pnacl installed, this is the version we claim.
66const char kNullVersion[] = "0.0.0.0";
67const char kMinPnaclVersion[] = "0.1.0.13367";
68
69// Initially say that we do not need OnDemand updates. This should be
70// updated by CheckVersionCompatiblity(), before doing any URLRequests
71// that depend on PNaCl.
72volatile base::subtle::Atomic32 needs_on_demand_update = 0;
73
74void CheckVersionCompatiblity(const base::Version& current_version) {
75  // Using NoBarrier, since needs_on_demand_update is standalone and does
76  // not have other associated data.
77  base::subtle::NoBarrier_Store(&needs_on_demand_update,
78                                current_version.IsOlderThan(kMinPnaclVersion));
79}
80
81// PNaCl is packaged as a multi-CRX.  This returns the platform-specific
82// subdirectory that is part of that multi-CRX.
83base::FilePath GetPlatformDir(const base::FilePath& base_path) {
84  std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch());
85  return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
86}
87
88// Tell the rest of the world where to find the platform-specific PNaCl files.
89void OverrideDirPnaclComponent(const base::FilePath& base_path) {
90  PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path));
91}
92
93bool 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();
99  bool found = false;
100  base::FileEnumerator file_enumerator(
101      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())
106      continue;
107    if (found) {
108      if (version.CompareTo(*latest_version) > 0) {
109        older_dirs->push_back(*latest_dir);
110        *latest_dir = path;
111        *latest_version = version;
112      } else {
113        older_dirs->push_back(path);
114      }
115    } else {
116      *latest_version = version;
117      *latest_dir = path;
118      found = true;
119    }
120  }
121  return found;
122}
123
124// Read a manifest file in.
125base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) {
126  JSONFileValueSerializer serializer(manifest_path);
127  std::string error;
128  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
129  if (!root.get())
130    return NULL;
131  if (!root->IsType(base::Value::TYPE_DICTIONARY))
132    return NULL;
133  return static_cast<base::DictionaryValue*>(root.release());
134}
135
136// Read the PNaCl specific manifest.
137base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
138  base::FilePath manifest_path =
139      GetPlatformDir(unpack_path).AppendASCII("pnacl_public_pnacl_json");
140  if (!base::PathExists(manifest_path))
141    return NULL;
142  return ReadJSONManifest(manifest_path);
143}
144
145// Read the component's manifest.json.
146base::DictionaryValue* ReadComponentManifest(
147    const base::FilePath& unpack_path) {
148  base::FilePath manifest_path =
149      unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
150  if (!base::PathExists(manifest_path))
151    return NULL;
152  return ReadJSONManifest(manifest_path);
153}
154
155// Check that the component's manifest is for PNaCl, and check the
156// PNaCl manifest indicates this is the correct arch-specific package.
157bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
158                                 const base::DictionaryValue& pnacl_manifest,
159                                 Version* version_out) {
160  // Make sure we have the right |manifest| file.
161  std::string name;
162  if (!manifest.GetStringASCII("name", &name)) {
163    LOG(WARNING) << "'name' field is missing from manifest!";
164    return false;
165  }
166  // For the webstore, we've given different names to each of the
167  // architecture specific packages (and test/QA vs not test/QA)
168  // so only part of it is the same.
169  if (name.find(kPnaclManifestName) == std::string::npos) {
170    LOG(WARNING) << "'name' field in manifest is invalid (" << name
171                 << ") -- missing (" << kPnaclManifestName << ")";
172    return false;
173  }
174
175  std::string proposed_version;
176  if (!manifest.GetStringASCII("version", &proposed_version)) {
177    LOG(WARNING) << "'version' field is missing from manifest!";
178    return false;
179  }
180  Version version(proposed_version.c_str());
181  if (!version.IsValid()) {
182    LOG(WARNING) << "'version' field in manifest is invalid "
183                 << version.GetString();
184    return false;
185  }
186
187  // Now check the |pnacl_manifest|.
188  std::string arch;
189  if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
190    LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
191    return false;
192  }
193  if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) {
194    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch
195                 << " vs " << OmahaQueryParams::GetNaclArch() << ")";
196    return false;
197  }
198
199  *version_out = version;
200  return true;
201}
202
203}  // namespace
204
205PnaclComponentInstaller::PnaclComponentInstaller() : cus_(NULL) {
206}
207
208PnaclComponentInstaller::~PnaclComponentInstaller() {
209}
210
211void PnaclComponentInstaller::OnUpdateError(int error) {
212  NOTREACHED() << "Pnacl update error: " << error;
213}
214
215// Pnacl components have the version encoded in the path itself:
216// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
217// and the base directory will be:
218// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
219base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
220  base::FilePath result;
221  CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
222  return result;
223}
224
225bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
226                                      const base::FilePath& unpack_path) {
227  scoped_ptr<base::DictionaryValue> pnacl_manifest(
228      ReadPnaclManifest(unpack_path));
229  if (pnacl_manifest == NULL) {
230    LOG(WARNING) << "Failed to read pnacl manifest.";
231    return false;
232  }
233
234  Version version;
235  if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
236    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
237    return false;
238  }
239
240  // Don't install if the current version is actually newer.
241  if (current_version().CompareTo(version) > 0) {
242    return false;
243  }
244
245  // Passed the basic tests. Time to install it.
246  base::FilePath path =
247      GetPnaclBaseDirectory().AppendASCII(version.GetString());
248  if (base::PathExists(path)) {
249    if (!base::DeleteFile(path, true))
250      return false;
251  }
252  if (!base::Move(unpack_path, path)) {
253    LOG(WARNING) << "Move failed, not installing.";
254    return false;
255  }
256
257  // Installation is done. Now tell the rest of chrome.
258  // - The path service.
259  // - Callbacks that requested an update.
260  set_current_version(version);
261  CheckVersionCompatiblity(version);
262  OverrideDirPnaclComponent(path);
263  return true;
264}
265
266// Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
267// returns the assumed install path. The path separator in |file| is '/'
268// for all platforms. Caller is responsible for checking that the
269// |installed_file| actually exists.
270bool PnaclComponentInstaller::GetInstalledFile(const std::string& file,
271                                               base::FilePath* installed_file) {
272  if (current_version().Equals(Version(kNullVersion)))
273    return false;
274
275  *installed_file = GetPnaclBaseDirectory()
276                        .AppendASCII(current_version().GetString())
277                        .AppendASCII(file);
278  return true;
279}
280
281CrxComponent PnaclComponentInstaller::GetCrxComponent() {
282  CrxComponent pnacl_component;
283  pnacl_component.version = current_version();
284  pnacl_component.name = "pnacl";
285  pnacl_component.installer = this;
286  pnacl_component.fingerprint = current_fingerprint();
287  SetPnaclHash(&pnacl_component);
288
289  return pnacl_component;
290}
291
292namespace {
293
294void FinishPnaclUpdateRegistration(const Version& current_version,
295                                   const std::string& current_fingerprint,
296                                   PnaclComponentInstaller* pci) {
297  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298  pci->set_current_version(current_version);
299  CheckVersionCompatiblity(current_version);
300  pci->set_current_fingerprint(current_fingerprint);
301  CrxComponent pnacl_component = pci->GetCrxComponent();
302
303  ComponentUpdateService::Status status =
304      pci->cus()->RegisterComponent(pnacl_component);
305  if (status != ComponentUpdateService::kOk &&
306      status != ComponentUpdateService::kReplaced) {
307    NOTREACHED() << "Pnacl component registration failed.";
308  }
309}
310
311// Check if there is an existing version on disk first to know when
312// a hosted version is actually newer.
313void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
314  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
315  base::FilePath path = pci->GetPnaclBaseDirectory();
316  if (!base::PathExists(path)) {
317    if (!base::CreateDirectory(path)) {
318      NOTREACHED() << "Could not create base Pnacl directory.";
319      return;
320    }
321  }
322
323  Version current_version(kNullVersion);
324  std::string current_fingerprint;
325  std::vector<base::FilePath> older_dirs;
326  if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
327    scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path));
328    scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path));
329    Version manifest_version;
330    // Check that the component manifest and PNaCl manifest files
331    // are legit, and that the indicated version matches the one
332    // encoded within the path name.
333    if (manifest == NULL || pnacl_manifest == NULL ||
334        !CheckPnaclComponentManifest(*manifest,
335                                     *pnacl_manifest,
336                                     &manifest_version) ||
337        !current_version.Equals(manifest_version)) {
338      current_version = Version(kNullVersion);
339    } else {
340      OverrideDirPnaclComponent(path);
341      base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
342                             &current_fingerprint);
343    }
344  }
345
346  BrowserThread::PostTask(BrowserThread::UI,
347                          FROM_HERE,
348                          base::Bind(&FinishPnaclUpdateRegistration,
349                                     current_version,
350                                     current_fingerprint,
351                                     pci));
352
353  // Remove older versions of PNaCl.
354  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
355       iter != older_dirs.end();
356       ++iter) {
357    base::DeleteFile(*iter, true);
358  }
359}
360
361}  // namespace
362
363void PnaclComponentInstaller::RegisterPnaclComponent(
364    ComponentUpdateService* cus) {
365  cus_ = cus;
366  BrowserThread::PostTask(BrowserThread::FILE,
367                          FROM_HERE,
368                          base::Bind(&StartPnaclUpdateRegistration, this));
369}
370
371}  // namespace component_updater
372
373namespace pnacl {
374
375bool NeedsOnDemandUpdate() {
376  return base::subtle::NoBarrier_Load(
377             &component_updater::needs_on_demand_update) != 0;
378}
379
380}  // namespace pnacl
381