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.
5 #include "chrome/browser/extensions/external_pref_loader.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/files/file_path.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"
21 using content::BrowserThread
;
25 base::FilePath::CharType kExternalExtensionJson
[] =
26 FILE_PATH_LITERAL("external_extensions.json");
28 std::set
<base::FilePath
> GetPrefsCandidateFilesFromFolder(
29 const base::FilePath
& external_extension_search_path
) {
30 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
32 std::set
<base::FilePath
> external_extension_paths
;
34 if (!file_util::PathExists(external_extension_search_path
)) {
35 // Does not have to exist.
36 return external_extension_paths
;
39 base::FileEnumerator
json_files(
40 external_extension_search_path
,
42 base::FileEnumerator::FILES
);
44 base::FilePath::StringType extension
= UTF8ToWide(std::string(".json"));
45 #elif defined(OS_POSIX)
46 base::FilePath::StringType
extension(".json");
49 base::FilePath file
= json_files
.Next();
50 if (file
.BaseName().value() == kExternalExtensionJson
)
51 continue; // Already taken care of elsewhere.
54 if (file
.MatchesExtension(extension
)) {
55 external_extension_paths
.insert(file
.BaseName());
57 DVLOG(1) << "Not considering: " << file
.LossyDisplayName()
58 << " (does not have a .json extension)";
62 return external_extension_paths
;
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.
70 DictionaryValue
* ExtractExtensionPrefs(base::ValueSerializer
* serializer
,
71 const base::FilePath
& path
) {
72 std::string error_msg
;
73 Value
* extensions
= serializer
->Deserialize(NULL
, &error_msg
);
75 LOG(WARNING
) << "Unable to deserialize json data: " << error_msg
76 << " in file " << path
.value() << ".";
77 return new DictionaryValue
;
80 DictionaryValue
* ext_dictionary
= NULL
;
81 if (extensions
->GetAsDictionary(&ext_dictionary
))
82 return ext_dictionary
;
84 LOG(WARNING
) << "Expected a JSON dictionary in file "
85 << path
.value() << ".";
86 return new DictionaryValue
;
91 namespace extensions
{
93 ExternalPrefLoader::ExternalPrefLoader(int base_path_id
, Options options
)
94 : base_path_id_(base_path_id
), options_(options
) {
95 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
98 const base::FilePath
ExternalPrefLoader::GetBaseCrxFilePath() {
99 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
101 // |base_path_| was set in LoadOnFileThread().
105 void ExternalPrefLoader::StartLoading() {
106 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
107 BrowserThread::PostTask(
108 BrowserThread::FILE, FROM_HERE
,
109 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
112 void ExternalPrefLoader::LoadOnFileThread() {
113 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
115 scoped_ptr
<DictionaryValue
> prefs(new DictionaryValue
);
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());
127 LOG(WARNING
) << "You are using an old-style extension deployment method "
128 "(external_extensions.json), which will soon be "
129 "deprecated. (see http://code.google.com/chrome/"
130 "extensions/external_extensions.html )";
132 ReadStandaloneExtensionPrefFiles(prefs
.get());
137 if (base_path_id_
== chrome::DIR_EXTERNAL_EXTENSIONS
) {
138 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
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
145 if (!prefs_
->empty())
146 CHECK(!base_path_
.empty());
148 BrowserThread::PostTask(
149 BrowserThread::UI
, FROM_HERE
,
150 base::Bind(&ExternalPrefLoader::LoadFinished
, this));
153 void ExternalPrefLoader::ReadExternalExtensionPrefFile(DictionaryValue
* prefs
) {
154 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
155 CHECK(NULL
!= prefs
);
157 base::FilePath json_file
= base_path_
.Append(kExternalExtensionJson
);
159 if (!file_util::PathExists(json_file
)) {
160 // This is not an error. The file does not exist by default.
164 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN
)) {
165 #if defined(OS_MACOSX)
166 if (!file_util::VerifyPathControlledByAdmin(json_file
)) {
167 LOG(ERROR
) << "Can not read external extensions source. The file "
168 << json_file
.value() << " and every directory in its path, "
169 << "must be owned by root, have group \"admin\", and not be "
170 << "writable by all users. These restrictions prevent "
171 << "unprivleged users from making chrome install extensions "
172 << "on other users' accounts.";
176 // The only platform that uses this check is Mac OS. If you add one,
177 // you need to implement file_util::VerifyPathControlledByAdmin() for
180 #endif // defined(OS_MACOSX)
183 JSONFileValueSerializer
serializer(json_file
);
184 scoped_ptr
<DictionaryValue
> ext_prefs(
185 ExtractExtensionPrefs(&serializer
, json_file
));
187 prefs
->MergeDictionary(ext_prefs
.get());
190 void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
191 DictionaryValue
* prefs
) {
192 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
193 CHECK(NULL
!= prefs
);
195 // First list the potential .json candidates.
196 std::set
<base::FilePath
>
197 candidates
= GetPrefsCandidateFilesFromFolder(base_path_
);
198 if (candidates
.empty()) {
199 DVLOG(1) << "Extension candidates list empty";
203 // For each file read the json description & build the proper
205 for (std::set
<base::FilePath
>::const_iterator it
= candidates
.begin();
206 it
!= candidates
.end();
208 base::FilePath extension_candidate_path
= base_path_
.Append(*it
);
213 extension_candidate_path
.RemoveExtension().BaseName().value());
214 #elif defined(OS_POSIX)
215 extension_candidate_path
.RemoveExtension().BaseName().value().c_str();
218 DVLOG(1) << "Reading json file: "
219 << extension_candidate_path
.LossyDisplayName().c_str();
221 JSONFileValueSerializer
serializer(extension_candidate_path
);
222 scoped_ptr
<DictionaryValue
> ext_prefs(
223 ExtractExtensionPrefs(&serializer
, extension_candidate_path
));
225 DVLOG(1) << "Adding extension with id: " << id
;
226 prefs
->Set(id
, ext_prefs
.release());
231 ExternalTestingLoader::ExternalTestingLoader(
232 const std::string
& json_data
,
233 const base::FilePath
& fake_base_path
)
234 : fake_base_path_(fake_base_path
) {
235 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
236 JSONStringValueSerializer
serializer(json_data
);
237 base::FilePath fake_json_path
= fake_base_path
.AppendASCII("fake.json");
238 testing_prefs_
.reset(ExtractExtensionPrefs(&serializer
, fake_json_path
));
241 void ExternalTestingLoader::StartLoading() {
242 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
243 prefs_
.reset(testing_prefs_
->DeepCopy());
247 ExternalTestingLoader::~ExternalTestingLoader() {}
249 const base::FilePath
ExternalTestingLoader::GetBaseCrxFilePath() {
250 return fake_base_path_
;