- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / component_updater / pnacl / pnacl_component_installer.cc
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 "base/base_paths.h"
8 #include "base/bind.h"
9 #include "base/callback.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/file_util.h"
13 #include "base/files/file_enumerator.h"
14 #include "base/files/file_path.h"
15 #include "base/json/json_file_value_serializer.h"
16 #include "base/logging.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/values.h"
20 #include "base/version.h"
21 #include "base/win/windows_version.h"
22 #include "build/build_config.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/component_updater/component_updater_service.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/common/chrome_paths.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/omaha_query_params/omaha_query_params.h"
30 #include "content/public/browser/browser_thread.h"
31
32 using chrome::OmahaQueryParams;
33 using content::BrowserThread;
34
35 namespace {
36
37 // Name of the Pnacl component specified in the manifest.
38 const char kPnaclManifestName[] = "PNaCl Translator";
39
40 // Sanitize characters from Pnacl Arch value so that they can be used
41 // in path names.  This should only be characters in the set: [a-z0-9_].
42 // Keep in sync with chrome/browser/nacl_host/nacl_file_host.
43 std::string SanitizeForPath(const std::string& input) {
44   std::string result;
45   ReplaceChars(input, "-", "_", &result);
46   return result;
47 }
48
49 // Set the component's hash to the multi-CRX PNaCl package.
50 void SetPnaclHash(CrxComponent* component) {
51  static const uint8 sha256_hash[32] =
52      { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc
53        0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44,
54        0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51,
55        0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c};
56
57   component->pk_hash.assign(sha256_hash,
58                             &sha256_hash[arraysize(sha256_hash)]);
59 }
60
61 // If we don't have Pnacl installed, this is the version we claim.
62 const char kNullVersion[] = "0.0.0.0";
63
64 // PNaCl is packaged as a multi-CRX.  This returns the platform-specific
65 // subdirectory that is part of that multi-CRX.
66 base::FilePath GetPlatformDir(const base::FilePath& base_path) {
67   std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch());
68   return base_path.AppendASCII("_platform_specific").AppendASCII(arch);
69 }
70
71 // Tell the rest of the world where to find the platform-specific PNaCl files.
72 void OverrideDirPnaclComponent(const base::FilePath& base_path) {
73   PathService::Override(chrome::DIR_PNACL_COMPONENT,
74                         GetPlatformDir(base_path));
75 }
76
77 bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci,
78                              base::FilePath* latest_dir,
79                              Version* latest_version,
80                              std::vector<base::FilePath>* older_dirs) {
81   // Enumerate all versions starting from the base directory.
82   base::FilePath base_dir = pci->GetPnaclBaseDirectory();
83   bool found = false;
84   base::FileEnumerator
85       file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES);
86   for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
87        path = file_enumerator.Next()) {
88     Version version(path.BaseName().MaybeAsASCII());
89     if (!version.IsValid())
90       continue;
91     if (found) {
92       if (version.CompareTo(*latest_version) > 0) {
93         older_dirs->push_back(*latest_dir);
94         *latest_dir = path;
95         *latest_version = version;
96       } else {
97         older_dirs->push_back(path);
98       }
99     } else {
100       *latest_version = version;
101       *latest_dir = path;
102       found = true;
103     }
104   }
105   return found;
106 }
107
108 // Read a manifest file in.
109 base::DictionaryValue* ReadJSONManifest(
110     const base::FilePath& manifest_path) {
111   JSONFileValueSerializer serializer(manifest_path);
112   std::string error;
113   scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
114   if (!root.get())
115     return NULL;
116   if (!root->IsType(base::Value::TYPE_DICTIONARY))
117     return NULL;
118   return static_cast<base::DictionaryValue*>(root.release());
119 }
120
121 // Read the PNaCl specific manifest.
122 base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
123   base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII(
124       "pnacl_public_pnacl_json");
125   if (!base::PathExists(manifest_path))
126     return NULL;
127   return ReadJSONManifest(manifest_path);
128 }
129
130 // Read the component's manifest.json.
131 base::DictionaryValue* ReadComponentManifest(
132     const base::FilePath& unpack_path) {
133   base::FilePath manifest_path = unpack_path.Append(
134       FILE_PATH_LITERAL("manifest.json"));
135   if (!base::PathExists(manifest_path))
136     return NULL;
137   return ReadJSONManifest(manifest_path);
138 }
139
140 // Check that the component's manifest is for PNaCl, and check the
141 // PNaCl manifest indicates this is the correct arch-specific package.
142 bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest,
143                                  const base::DictionaryValue& pnacl_manifest,
144                                  Version* version_out) {
145   // Make sure we have the right |manifest| file.
146   std::string name;
147   if (!manifest.GetStringASCII("name", &name)) {
148     LOG(WARNING) << "'name' field is missing from manifest!";
149     return false;
150   }
151   // For the webstore, we've given different names to each of the
152   // architecture specific packages (and test/QA vs not test/QA)
153   // so only part of it is the same.
154   if (name.find(kPnaclManifestName) == std::string::npos) {
155     LOG(WARNING) << "'name' field in manifest is invalid ("
156                  << name << ") -- missing ("
157                  << kPnaclManifestName << ")";
158     return false;
159   }
160
161   std::string proposed_version;
162   if (!manifest.GetStringASCII("version", &proposed_version)) {
163     LOG(WARNING) << "'version' field is missing from manifest!";
164     return false;
165   }
166   Version version(proposed_version.c_str());
167   if (!version.IsValid()) {
168     LOG(WARNING) << "'version' field in manifest is invalid "
169                  << version.GetString();
170     return false;
171   }
172
173   // Now check the |pnacl_manifest|.
174   std::string arch;
175   if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) {
176     LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!";
177     return false;
178   }
179   if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) {
180     LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
181                  << arch << " vs " << OmahaQueryParams::getNaclArch() << ")";
182     return false;
183   }
184
185   *version_out = version;
186   return true;
187 }
188
189 }  // namespace
190
191 PnaclComponentInstaller::PnaclComponentInstaller()
192     : per_user_(false),
193       updates_disabled_(false),
194       cus_(NULL) {
195 #if defined(OS_CHROMEOS)
196   per_user_ = true;
197 #endif
198 }
199
200 PnaclComponentInstaller::~PnaclComponentInstaller() {
201 }
202
203 void PnaclComponentInstaller::OnUpdateError(int error) {
204   NOTREACHED() << "Pnacl update error: " << error;
205 }
206
207 // Pnacl components have the version encoded in the path itself:
208 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\.
209 // and the base directory will be:
210 // <profile>\AppData\Local\Google\Chrome\User Data\pnacl\.
211 base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() {
212   // For ChromeOS, temporarily make this user-dependent (for integrity) until
213   // we find a better solution.
214   // This is not ideal because of the following:
215   //   (a) We end up with per-user copies instead of a single copy
216   //   (b) The profile can change as users log in to different accounts
217   //   so we need to watch for user-login-events (see pnacl_profile_observer.h).
218   if (per_user_) {
219     DCHECK(!current_profile_path_.empty());
220     base::FilePath path = current_profile_path_.Append(
221         FILE_PATH_LITERAL("pnacl"));
222     return path;
223   } else {
224     base::FilePath result;
225     CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
226     return result;
227   }
228 }
229
230 void PnaclComponentInstaller::OnProfileChange() {
231   // On chromeos, we want to find the --login-profile=<foo> dir.
232   // Even though the path does vary between users, the content
233   // changes when logging out and logging in.
234   ProfileManager* pm = g_browser_process->profile_manager();
235   current_profile_path_ = pm->user_data_dir().Append(
236       pm->GetInitialProfileDir());
237 }
238
239 bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest,
240                                       const base::FilePath& unpack_path) {
241   scoped_ptr<base::DictionaryValue> pnacl_manifest(
242       ReadPnaclManifest(unpack_path));
243   if (pnacl_manifest == NULL) {
244     LOG(WARNING) << "Failed to read pnacl manifest.";
245     return false;
246   }
247
248   Version version;
249   if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) {
250     LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
251     return false;
252   }
253
254   // Don't install if the current version is actually newer.
255   if (current_version().CompareTo(version) > 0) {
256     return false;
257   }
258
259   // Passed the basic tests. Time to install it.
260   base::FilePath path = GetPnaclBaseDirectory().AppendASCII(
261       version.GetString());
262   if (base::PathExists(path)) {
263     LOG(WARNING) << "Target path already exists, not installing.";
264     return false;
265   }
266   if (!base::Move(unpack_path, path)) {
267     LOG(WARNING) << "Move failed, not installing.";
268     return false;
269   }
270
271   // Installation is done. Now tell the rest of chrome.
272   // - The path service.
273   // - Callbacks that requested an update.
274   set_current_version(version);
275   OverrideDirPnaclComponent(path);
276   return true;
277 }
278
279 // Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo",
280 // returns the assumed install path. The path separator in |file| is '/'
281 // for all platforms. Caller is responsible for checking that the
282 // |installed_file| actually exists.
283 bool PnaclComponentInstaller::GetInstalledFile(
284     const std::string& file, base::FilePath* installed_file) {
285   if (current_version().Equals(Version(kNullVersion)))
286     return false;
287
288   *installed_file = GetPnaclBaseDirectory().AppendASCII(
289       current_version().GetString()).AppendASCII(file);
290   return true;
291 }
292
293 CrxComponent PnaclComponentInstaller::GetCrxComponent() {
294   CrxComponent pnacl_component;
295   pnacl_component.version = current_version();
296   pnacl_component.name = "pnacl";
297   pnacl_component.installer = this;
298   pnacl_component.fingerprint = current_fingerprint();
299   SetPnaclHash(&pnacl_component);
300
301   return pnacl_component;
302 }
303
304 namespace {
305
306 void FinishPnaclUpdateRegistration(const Version& current_version,
307                                    const std::string& current_fingerprint,
308                                    PnaclComponentInstaller* pci) {
309   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310   pci->set_current_version(current_version);
311   pci->set_current_fingerprint(current_fingerprint);
312   CrxComponent pnacl_component = pci->GetCrxComponent();
313
314   ComponentUpdateService::Status status =
315       pci->cus()->RegisterComponent(pnacl_component);
316   if (status != ComponentUpdateService::kOk
317       && status != ComponentUpdateService::kReplaced) {
318     NOTREACHED() << "Pnacl component registration failed.";
319   }
320 }
321
322 // Check if there is an existing version on disk first to know when
323 // a hosted version is actually newer.
324 void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) {
325   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
326   base::FilePath path = pci->GetPnaclBaseDirectory();
327   if (!base::PathExists(path)) {
328     if (!file_util::CreateDirectory(path)) {
329       NOTREACHED() << "Could not create base Pnacl directory.";
330       return;
331     }
332   }
333
334   Version current_version(kNullVersion);
335   std::string current_fingerprint;
336   std::vector<base::FilePath> older_dirs;
337   if (GetLatestPnaclDirectory(pci, &path, &current_version, &older_dirs)) {
338     scoped_ptr<base::DictionaryValue> manifest(
339         ReadComponentManifest(path));
340     scoped_ptr<base::DictionaryValue> pnacl_manifest(
341         ReadPnaclManifest(path));
342     Version manifest_version;
343     // Check that the component manifest and PNaCl manifest files
344     // are legit, and that the indicated version matches the one
345     // encoded within the path name.
346     if (manifest == NULL || pnacl_manifest == NULL
347         || !CheckPnaclComponentManifest(*manifest,
348                                         *pnacl_manifest,
349                                         &manifest_version)
350         || !current_version.Equals(manifest_version)) {
351       current_version = Version(kNullVersion);
352     } else {
353       OverrideDirPnaclComponent(path);
354       base::ReadFileToString(path.AppendASCII("manifest.fingerprint"),
355                              &current_fingerprint);
356     }
357   }
358
359   // If updates are disabled, only discover the current version
360   // and OverrideDirPnaclComponent. That way, developers can use
361   // a pinned version. Do not actually finish registration with
362   // the component update service.
363   if (pci->updates_disabled())
364     return;
365
366   BrowserThread::PostTask(
367       BrowserThread::UI, FROM_HERE,
368       base::Bind(&FinishPnaclUpdateRegistration,
369                  current_version,
370                  current_fingerprint,
371                  pci));
372
373   // Remove older versions of PNaCl.
374   for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
375        iter != older_dirs.end(); ++iter) {
376     base::DeleteFile(*iter, true);
377   }
378 }
379
380 // Remove old per-profile copies of PNaCl (was for ChromeOS).
381 // TODO(jvoung): Delete this code once most ChromeOS users have reaped
382 // their old per-profile copies of PNaCl.
383 void ReapOldChromeOSPnaclFiles(PnaclComponentInstaller* pci) {
384   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
385   base::FilePath path = pci->GetPnaclBaseDirectory();
386   if (!base::PathExists(path))
387     return;
388
389   // Do a basic sanity check first.
390   if (pci->per_user()
391       && path.BaseName().value().compare(FILE_PATH_LITERAL("pnacl")) == 0)
392     base::DeleteFile(path, true);
393 }
394
395
396 void GetProfileInformation(PnaclComponentInstaller* pci) {
397   // Bail if not logged in yet.
398   if (!g_browser_process->profile_manager()->IsLoggedIn()) {
399     return;
400   }
401
402   pci->OnProfileChange();
403
404   // Do not actually register PNaCl for component updates, for CHROMEOS.
405   // Just get the profile information and delete the per-profile files
406   // if they exist.
407  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
408                          base::Bind(&ReapOldChromeOSPnaclFiles, pci));
409 }
410
411 }  // namespace
412
413 void PnaclComponentInstaller::RegisterPnaclComponent(
414                             ComponentUpdateService* cus,
415                             const CommandLine& command_line) {
416   // Register PNaCl by default (can be disabled).
417   updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall);
418   cus_ = cus;
419   // If per_user, create a profile observer to watch for logins.
420   // Only do so after cus_ is set to something non-null.
421   if (per_user_ && !profile_observer_) {
422     profile_observer_.reset(new PnaclProfileObserver(this));
423   }
424   if (per_user_) {
425     // Figure out profile information, before proceeding to look for files.
426     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
427                             base::Bind(&GetProfileInformation, this));
428   } else {
429     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
430                             base::Bind(&StartPnaclUpdateRegistration, this));
431   }
432 }
433
434 void PnaclComponentInstaller::ReRegisterPnacl() {
435   DCHECK(per_user_);
436   // Figure out profile information, before proceeding to look for files.
437   BrowserThread::PostTask(
438       BrowserThread::UI, FROM_HERE,
439       base::Bind(&GetProfileInformation, this));
440 }