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