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_pref_loader.h"
6
7#include "base/bind.h"
8#include "base/files/file_enumerator.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/json/json_file_value_serializer.h"
12#include "base/json/json_string_value_serializer.h"
13#include "base/logging.h"
14#include "base/metrics/histogram.h"
15#include "base/path_service.h"
16#include "base/strings/string_util.h"
17#include "base/strings/utf_string_conversions.h"
18#include "chrome/common/chrome_paths.h"
19#include "content/public/browser/browser_thread.h"
20
21using content::BrowserThread;
22
23namespace {
24
25base::FilePath::CharType kExternalExtensionJson[] =
26    FILE_PATH_LITERAL("external_extensions.json");
27
28std::set<base::FilePath> GetPrefsCandidateFilesFromFolder(
29      const base::FilePath& external_extension_search_path) {
30  CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
31
32  std::set<base::FilePath> external_extension_paths;
33
34  if (!base::PathExists(external_extension_search_path)) {
35    // Does not have to exist.
36    return external_extension_paths;
37  }
38
39  base::FileEnumerator json_files(
40      external_extension_search_path,
41      false,  // Recursive.
42      base::FileEnumerator::FILES);
43#if defined(OS_WIN)
44  base::FilePath::StringType extension = base::UTF8ToWide(std::string(".json"));
45#elif defined(OS_POSIX)
46  base::FilePath::StringType extension(".json");
47#endif
48  do {
49    base::FilePath file = json_files.Next();
50    if (file.BaseName().value() == kExternalExtensionJson)
51      continue;  // Already taken care of elsewhere.
52    if (file.empty())
53      break;
54    if (file.MatchesExtension(extension)) {
55      external_extension_paths.insert(file.BaseName());
56    } else {
57      DVLOG(1) << "Not considering: " << file.LossyDisplayName()
58               << " (does not have a .json extension)";
59    }
60  } while (true);
61
62  return external_extension_paths;
63}
64
65// Extracts extension information from a json file serialized by |serializer|.
66// |path| is only used for informational purposes (outputted when an error
67// occurs). An empty dictionary is returned in case of failure (e.g. invalid
68// path or json content).
69// Caller takes ownership of the returned dictionary.
70base::DictionaryValue* ExtractExtensionPrefs(base::ValueSerializer* serializer,
71                                             const base::FilePath& path) {
72  std::string error_msg;
73  base::Value* extensions = serializer->Deserialize(NULL, &error_msg);
74  if (!extensions) {
75    LOG(WARNING) << "Unable to deserialize json data: " << error_msg
76                 << " in file " << path.value() << ".";
77    return new base::DictionaryValue;
78  }
79
80  base::DictionaryValue* ext_dictionary = NULL;
81  if (extensions->GetAsDictionary(&ext_dictionary))
82    return ext_dictionary;
83
84  LOG(WARNING) << "Expected a JSON dictionary in file "
85               << path.value() << ".";
86  return new base::DictionaryValue;
87}
88
89}  // namespace
90
91namespace extensions {
92
93ExternalPrefLoader::ExternalPrefLoader(int base_path_id, Options options)
94    : base_path_id_(base_path_id), options_(options) {
95  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
96}
97
98const base::FilePath ExternalPrefLoader::GetBaseCrxFilePath() {
99  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
100
101  // |base_path_| was set in LoadOnFileThread().
102  return base_path_;
103}
104
105void ExternalPrefLoader::StartLoading() {
106  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
107  BrowserThread::PostTask(
108      BrowserThread::FILE, FROM_HERE,
109      base::Bind(&ExternalPrefLoader::LoadOnFileThread, this));
110}
111
112void ExternalPrefLoader::LoadOnFileThread() {
113  CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
114
115  scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue);
116
117  // TODO(skerner): Some values of base_path_id_ will cause
118  // PathService::Get() to return false, because the path does
119  // not exist.  Find and fix the build/install scripts so that
120  // this can become a CHECK().  Known examples include chrome
121  // OS developer builds and linux install packages.
122  // Tracked as crbug.com/70402 .
123  if (PathService::Get(base_path_id_, &base_path_)) {
124    ReadExternalExtensionPrefFile(prefs.get());
125
126    if (!prefs->empty())
127      LOG(WARNING) << "You are using an old-style extension deployment method "
128                      "(external_extensions.json), which will soon be "
129                      "deprecated. (see http://developer.chrome.com/"
130                      "extensions/external_extensions.html)";
131
132    ReadStandaloneExtensionPrefFiles(prefs.get());
133  }
134
135  prefs_.swap(prefs);
136
137  if (base_path_id_ == chrome::DIR_EXTERNAL_EXTENSIONS) {
138    UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
139                             prefs_->size());
140  }
141
142  // If we have any records to process, then we must have
143  // read at least one .json file.  If so, then we should have
144  // set |base_path_|.
145  if (!prefs_->empty())
146    CHECK(!base_path_.empty());
147
148  BrowserThread::PostTask(
149      BrowserThread::UI, FROM_HERE,
150      base::Bind(&ExternalPrefLoader::LoadFinished, this));
151}
152
153void ExternalPrefLoader::ReadExternalExtensionPrefFile(
154    base::DictionaryValue* prefs) {
155  CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
156  CHECK(NULL != prefs);
157
158  base::FilePath json_file = base_path_.Append(kExternalExtensionJson);
159
160  if (!base::PathExists(json_file)) {
161    // This is not an error.  The file does not exist by default.
162    return;
163  }
164
165  if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN)) {
166#if defined(OS_MACOSX)
167    if (!base::VerifyPathControlledByAdmin(json_file)) {
168      LOG(ERROR) << "Can not read external extensions source.  The file "
169                 << json_file.value() << " and every directory in its path, "
170                 << "must be owned by root, have group \"admin\", and not be "
171                 << "writable by all users. These restrictions prevent "
172                 << "unprivleged users from making chrome install extensions "
173                 << "on other users' accounts.";
174      return;
175    }
176#else
177    // The only platform that uses this check is Mac OS.  If you add one,
178    // you need to implement base::VerifyPathControlledByAdmin() for
179    // that platform.
180    NOTREACHED();
181#endif  // defined(OS_MACOSX)
182  }
183
184  JSONFileValueSerializer serializer(json_file);
185  scoped_ptr<base::DictionaryValue> ext_prefs(
186      ExtractExtensionPrefs(&serializer, json_file));
187  if (ext_prefs)
188    prefs->MergeDictionary(ext_prefs.get());
189}
190
191void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
192    base::DictionaryValue* prefs) {
193  CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
194  CHECK(NULL != prefs);
195
196  // First list the potential .json candidates.
197  std::set<base::FilePath>
198      candidates = GetPrefsCandidateFilesFromFolder(base_path_);
199  if (candidates.empty()) {
200    DVLOG(1) << "Extension candidates list empty";
201    return;
202  }
203
204  // For each file read the json description & build the proper
205  // associated prefs.
206  for (std::set<base::FilePath>::const_iterator it = candidates.begin();
207       it != candidates.end();
208       ++it) {
209    base::FilePath extension_candidate_path = base_path_.Append(*it);
210
211    std::string id =
212#if defined(OS_WIN)
213        base::UTF16ToASCII(
214            extension_candidate_path.RemoveExtension().BaseName().value());
215#elif defined(OS_POSIX)
216        extension_candidate_path.RemoveExtension().BaseName().value().c_str();
217#endif
218
219    DVLOG(1) << "Reading json file: "
220             << extension_candidate_path.LossyDisplayName().c_str();
221
222    JSONFileValueSerializer serializer(extension_candidate_path);
223    scoped_ptr<base::DictionaryValue> ext_prefs(
224        ExtractExtensionPrefs(&serializer, extension_candidate_path));
225    if (ext_prefs) {
226      DVLOG(1) << "Adding extension with id: " << id;
227      prefs->Set(id, ext_prefs.release());
228    }
229  }
230}
231
232ExternalTestingLoader::ExternalTestingLoader(
233    const std::string& json_data,
234    const base::FilePath& fake_base_path)
235    : fake_base_path_(fake_base_path) {
236  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
237  JSONStringValueSerializer serializer(json_data);
238  base::FilePath fake_json_path = fake_base_path.AppendASCII("fake.json");
239  testing_prefs_.reset(ExtractExtensionPrefs(&serializer, fake_json_path));
240}
241
242void ExternalTestingLoader::StartLoading() {
243  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244  prefs_.reset(testing_prefs_->DeepCopy());
245  LoadFinished();
246}
247
248ExternalTestingLoader::~ExternalTestingLoader() {}
249
250const base::FilePath ExternalTestingLoader::GetBaseCrxFilePath() {
251  return fake_base_path_;
252}
253
254}  // namespace extensions
255