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/extensions/external_registry_loader_win.h"
6
7#include "base/bind.h"
8#include "base/files/file_path.h"
9#include "base/files/file_util.h"
10#include "base/files/scoped_file.h"
11#include "base/metrics/histogram.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/time/time.h"
16#include "base/values.h"
17#include "base/version.h"
18#include "base/win/registry.h"
19#include "chrome/browser/extensions/external_provider_impl.h"
20#include "components/crx_file/id_util.h"
21#include "content/public/browser/browser_thread.h"
22
23using content::BrowserThread;
24
25namespace {
26
27// The Registry subkey that contains information about external extensions.
28const char kRegistryExtensions[] = "Software\\Google\\Chrome\\Extensions";
29
30// Registry value of the key that defines the installation parameter.
31const wchar_t kRegistryExtensionInstallParam[] = L"install_parameter";
32
33// Registry value of the key that defines the path to the .crx file.
34const wchar_t kRegistryExtensionPath[] = L"path";
35
36// Registry value of that key that defines the current version of the .crx file.
37const wchar_t kRegistryExtensionVersion[] = L"version";
38
39// Registry value of the key that defines an external update URL.
40const wchar_t kRegistryExtensionUpdateUrl[] = L"update_url";
41
42bool CanOpenFileForReading(const base::FilePath& path) {
43  base::ScopedFILE file_handle(base::OpenFile(path, "rb"));
44  return file_handle.get() != NULL;
45}
46
47std::string MakePrefName(const std::string& extension_id,
48                         const std::string& pref_name) {
49  return base::StringPrintf("%s.%s", extension_id.c_str(), pref_name.c_str());
50}
51
52}  // namespace
53
54namespace extensions {
55
56void ExternalRegistryLoader::StartLoading() {
57  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
58  BrowserThread::PostTask(
59      BrowserThread::FILE, FROM_HERE,
60      base::Bind(&ExternalRegistryLoader::LoadOnFileThread, this));
61}
62
63void ExternalRegistryLoader::LoadOnFileThread() {
64  CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65  base::TimeTicks start_time = base::TimeTicks::Now();
66  scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue);
67
68  // A map of IDs, to weed out duplicates between HKCU and HKLM.
69  std::set<base::string16> keys;
70  base::win::RegistryKeyIterator iterator_machine_key(
71      HKEY_LOCAL_MACHINE, base::ASCIIToWide(kRegistryExtensions).c_str());
72  for (; iterator_machine_key.Valid(); ++iterator_machine_key)
73    keys.insert(iterator_machine_key.Name());
74  base::win::RegistryKeyIterator iterator_user_key(
75      HKEY_CURRENT_USER, base::ASCIIToWide(kRegistryExtensions).c_str());
76  for (; iterator_user_key.Valid(); ++iterator_user_key)
77    keys.insert(iterator_user_key.Name());
78
79  // Iterate over the keys found, first trying HKLM, then HKCU, as per Windows
80  // policy conventions. We only fall back to HKCU if the HKLM key cannot be
81  // opened, not if the data within the key is invalid, for example.
82  for (std::set<base::string16>::const_iterator it = keys.begin();
83       it != keys.end(); ++it) {
84    base::win::RegKey key;
85    base::string16 key_path = base::ASCIIToWide(kRegistryExtensions);
86    key_path.append(L"\\");
87    key_path.append(*it);
88    if (key.Open(HKEY_LOCAL_MACHINE,
89                 key_path.c_str(), KEY_READ) != ERROR_SUCCESS) {
90      if (key.Open(HKEY_CURRENT_USER,
91                   key_path.c_str(), KEY_READ) != ERROR_SUCCESS) {
92        LOG(ERROR) << "Unable to read registry key at path (HKLM & HKCU): "
93                   << key_path << ".";
94        continue;
95      }
96    }
97
98    std::string id = base::UTF16ToASCII(*it);
99    base::StringToLowerASCII(&id);
100    if (!crx_file::id_util::IdIsValid(id)) {
101      LOG(ERROR) << "Invalid id value " << id
102                 << " for key " << key_path << ".";
103      continue;
104    }
105
106    base::string16 extension_dist_id;
107    if (key.ReadValue(kRegistryExtensionInstallParam, &extension_dist_id) ==
108        ERROR_SUCCESS) {
109      prefs->SetString(MakePrefName(id, ExternalProviderImpl::kInstallParam),
110                       base::UTF16ToASCII(extension_dist_id));
111    }
112
113    // If there is an update URL present, copy it to prefs and ignore
114    // path and version keys for this entry.
115    base::string16 extension_update_url;
116    if (key.ReadValue(kRegistryExtensionUpdateUrl, &extension_update_url)
117        == ERROR_SUCCESS) {
118      prefs->SetString(
119          MakePrefName(id, ExternalProviderImpl::kExternalUpdateUrl),
120          base::UTF16ToASCII(extension_update_url));
121      continue;
122    }
123
124    base::string16 extension_path_str;
125    if (key.ReadValue(kRegistryExtensionPath, &extension_path_str)
126        != ERROR_SUCCESS) {
127      // TODO(erikkay): find a way to get this into about:extensions
128      LOG(ERROR) << "Missing value " << kRegistryExtensionPath
129                 << " for key " << key_path << ".";
130      continue;
131    }
132
133    base::FilePath extension_path(extension_path_str);
134    if (!extension_path.IsAbsolute()) {
135      LOG(ERROR) << "File path " << extension_path_str
136                 << " needs to be absolute in key "
137                 << key_path;
138      continue;
139    }
140
141    if (!base::PathExists(extension_path)) {
142      LOG(ERROR) << "File " << extension_path_str
143                 << " for key " << key_path
144                 << " does not exist or is not readable.";
145      continue;
146    }
147
148    if (!CanOpenFileForReading(extension_path)) {
149      LOG(ERROR) << "File " << extension_path_str
150                 << " for key " << key_path << " can not be read. "
151                 << "Check that users who should have the extension "
152                 << "installed have permission to read it.";
153      continue;
154    }
155
156    base::string16 extension_version;
157    if (key.ReadValue(kRegistryExtensionVersion, &extension_version)
158        != ERROR_SUCCESS) {
159      // TODO(erikkay): find a way to get this into about:extensions
160      LOG(ERROR) << "Missing value " << kRegistryExtensionVersion
161                 << " for key " << key_path << ".";
162      continue;
163    }
164
165    Version version(base::UTF16ToASCII(extension_version));
166    if (!version.IsValid()) {
167      LOG(ERROR) << "Invalid version value " << extension_version
168                 << " for key " << key_path << ".";
169      continue;
170    }
171
172    prefs->SetString(
173        MakePrefName(id, ExternalProviderImpl::kExternalVersion),
174        base::UTF16ToASCII(extension_version));
175    prefs->SetString(
176        MakePrefName(id, ExternalProviderImpl::kExternalCrx),
177        extension_path_str);
178    prefs->SetBoolean(
179        MakePrefName(id, ExternalProviderImpl::kMayBeUntrusted),
180        true);
181  }
182
183  prefs_.reset(prefs.release());
184  LOCAL_HISTOGRAM_TIMES("Extensions.ExternalRegistryLoaderWin",
185                        base::TimeTicks::Now() - start_time);
186  BrowserThread::PostTask(
187      BrowserThread::UI, FROM_HERE,
188      base::Bind(&ExternalRegistryLoader::LoadFinished, this));
189}
190
191}  // namespace extensions
192