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/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/browser/defaults.h"
19 #include "chrome/browser/prefs/pref_service_syncable.h"
20 #include "chrome/browser/sync/profile_sync_service_factory.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "content/public/browser/browser_thread.h"
24 using content::BrowserThread
;
28 base::FilePath::CharType kExternalExtensionJson
[] =
29 FILE_PATH_LITERAL("external_extensions.json");
31 std::set
<base::FilePath
> GetPrefsCandidateFilesFromFolder(
32 const base::FilePath
& external_extension_search_path
) {
33 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
35 std::set
<base::FilePath
> external_extension_paths
;
37 if (!base::PathExists(external_extension_search_path
)) {
38 // Does not have to exist.
39 return external_extension_paths
;
42 base::FileEnumerator
json_files(
43 external_extension_search_path
,
45 base::FileEnumerator::FILES
);
47 base::FilePath::StringType extension
= base::UTF8ToWide(".json");
48 #elif defined(OS_POSIX)
49 base::FilePath::StringType
extension(".json");
52 base::FilePath file
= json_files
.Next();
53 if (file
.BaseName().value() == kExternalExtensionJson
)
54 continue; // Already taken care of elsewhere.
57 if (file
.MatchesExtension(extension
)) {
58 external_extension_paths
.insert(file
.BaseName());
60 DVLOG(1) << "Not considering: " << file
.LossyDisplayName()
61 << " (does not have a .json extension)";
65 return external_extension_paths
;
68 // Extracts extension information from a json file serialized by |serializer|.
69 // |path| is only used for informational purposes (outputted when an error
70 // occurs). An empty dictionary is returned in case of failure (e.g. invalid
71 // path or json content).
72 // Caller takes ownership of the returned dictionary.
73 base::DictionaryValue
* ExtractExtensionPrefs(
74 base::ValueDeserializer
* deserializer
,
75 const base::FilePath
& path
) {
76 std::string error_msg
;
77 base::Value
* extensions
= deserializer
->Deserialize(NULL
, &error_msg
);
79 LOG(WARNING
) << "Unable to deserialize json data: " << error_msg
80 << " in file " << path
.value() << ".";
81 return new base::DictionaryValue
;
84 base::DictionaryValue
* ext_dictionary
= NULL
;
85 if (extensions
->GetAsDictionary(&ext_dictionary
))
86 return ext_dictionary
;
88 LOG(WARNING
) << "Expected a JSON dictionary in file "
89 << path
.value() << ".";
90 return new base::DictionaryValue
;
95 namespace extensions
{
97 ExternalPrefLoader::ExternalPrefLoader(int base_path_id
,
100 : base_path_id_(base_path_id
),
103 syncable_pref_observer_(this) {
104 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
107 ExternalPrefLoader::~ExternalPrefLoader() {
110 const base::FilePath
ExternalPrefLoader::GetBaseCrxFilePath() {
111 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
113 // |base_path_| was set in LoadOnFileThread().
117 void ExternalPrefLoader::StartLoading() {
118 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
119 if ((options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
) &&
120 (profile_
&& profile_
->IsSyncAccessible())) {
121 if (!PostLoadIfPrioritySyncReady()) {
123 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
125 syncable_pref_observer_
.Add(prefs
);
126 ProfileSyncService
* service
=
127 ProfileSyncServiceFactory::GetForProfile(profile_
);
129 if (service
->IsSyncEnabledAndLoggedIn() &&
130 (service
->HasSyncSetupCompleted() ||
131 browser_defaults::kSyncAutoStarts
)) {
132 service
->AddObserver(this);
134 PostLoadAndRemoveObservers();
138 BrowserThread::PostTask(
139 BrowserThread::FILE, FROM_HERE
,
140 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
144 void ExternalPrefLoader::OnIsSyncingChanged() {
145 PostLoadIfPrioritySyncReady();
148 void ExternalPrefLoader::OnStateChanged() {
149 ProfileSyncService
* service
=
150 ProfileSyncServiceFactory::GetForProfile(profile_
);
152 if (!service
->IsSyncEnabledAndLoggedIn()) {
153 PostLoadAndRemoveObservers();
157 bool ExternalPrefLoader::PostLoadIfPrioritySyncReady() {
158 DCHECK(options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
);
161 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
163 if (prefs
->IsPrioritySyncing()) {
164 PostLoadAndRemoveObservers();
171 void ExternalPrefLoader::PostLoadAndRemoveObservers() {
172 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
174 syncable_pref_observer_
.Remove(prefs
);
176 ProfileSyncService
* service
=
177 ProfileSyncServiceFactory::GetForProfile(profile_
);
179 service
->RemoveObserver(this);
181 BrowserThread::PostTask(
182 BrowserThread::FILE, FROM_HERE
,
183 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
186 void ExternalPrefLoader::LoadOnFileThread() {
187 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
189 scoped_ptr
<base::DictionaryValue
> prefs(new base::DictionaryValue
);
191 // TODO(skerner): Some values of base_path_id_ will cause
192 // PathService::Get() to return false, because the path does
193 // not exist. Find and fix the build/install scripts so that
194 // this can become a CHECK(). Known examples include chrome
195 // OS developer builds and linux install packages.
196 // Tracked as crbug.com/70402 .
197 if (PathService::Get(base_path_id_
, &base_path_
)) {
198 ReadExternalExtensionPrefFile(prefs
.get());
201 LOG(WARNING
) << "You are using an old-style extension deployment method "
202 "(external_extensions.json), which will soon be "
203 "deprecated. (see http://developer.chrome.com/"
204 "extensions/external_extensions.html)";
206 ReadStandaloneExtensionPrefFiles(prefs
.get());
211 if (base_path_id_
== chrome::DIR_EXTERNAL_EXTENSIONS
) {
212 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
216 // If we have any records to process, then we must have
217 // read at least one .json file. If so, then we should have
219 if (!prefs_
->empty())
220 CHECK(!base_path_
.empty());
222 BrowserThread::PostTask(
223 BrowserThread::UI
, FROM_HERE
,
224 base::Bind(&ExternalPrefLoader::LoadFinished
, this));
227 void ExternalPrefLoader::ReadExternalExtensionPrefFile(
228 base::DictionaryValue
* prefs
) {
229 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
230 CHECK(NULL
!= prefs
);
232 base::FilePath json_file
= base_path_
.Append(kExternalExtensionJson
);
234 if (!base::PathExists(json_file
)) {
235 // This is not an error. The file does not exist by default.
239 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN
)) {
240 #if defined(OS_MACOSX)
241 if (!base::VerifyPathControlledByAdmin(json_file
)) {
242 LOG(ERROR
) << "Can not read external extensions source. The file "
243 << json_file
.value() << " and every directory in its path, "
244 << "must be owned by root, have group \"admin\", and not be "
245 << "writable by all users. These restrictions prevent "
246 << "unprivleged users from making chrome install extensions "
247 << "on other users' accounts.";
251 // The only platform that uses this check is Mac OS. If you add one,
252 // you need to implement base::VerifyPathControlledByAdmin() for
255 #endif // defined(OS_MACOSX)
258 JSONFileValueDeserializer
deserializer(json_file
);
259 scoped_ptr
<base::DictionaryValue
> ext_prefs(
260 ExtractExtensionPrefs(&deserializer
, json_file
));
262 prefs
->MergeDictionary(ext_prefs
.get());
265 void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
266 base::DictionaryValue
* prefs
) {
267 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
268 CHECK(NULL
!= prefs
);
270 // First list the potential .json candidates.
271 std::set
<base::FilePath
>
272 candidates
= GetPrefsCandidateFilesFromFolder(base_path_
);
273 if (candidates
.empty()) {
274 DVLOG(1) << "Extension candidates list empty";
278 // For each file read the json description & build the proper
280 for (std::set
<base::FilePath
>::const_iterator it
= candidates
.begin();
281 it
!= candidates
.end();
283 base::FilePath extension_candidate_path
= base_path_
.Append(*it
);
288 extension_candidate_path
.RemoveExtension().BaseName().value());
289 #elif defined(OS_POSIX)
290 extension_candidate_path
.RemoveExtension().BaseName().value();
293 DVLOG(1) << "Reading json file: "
294 << extension_candidate_path
.LossyDisplayName();
296 JSONFileValueDeserializer
deserializer(extension_candidate_path
);
297 scoped_ptr
<base::DictionaryValue
> ext_prefs(
298 ExtractExtensionPrefs(&deserializer
, extension_candidate_path
));
300 DVLOG(1) << "Adding extension with id: " << id
;
301 prefs
->Set(id
, ext_prefs
.release());
306 ExternalTestingLoader::ExternalTestingLoader(
307 const std::string
& json_data
,
308 const base::FilePath
& fake_base_path
)
309 : fake_base_path_(fake_base_path
) {
310 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
311 JSONStringValueDeserializer
deserializer(json_data
);
312 base::FilePath fake_json_path
= fake_base_path
.AppendASCII("fake.json");
313 testing_prefs_
.reset(ExtractExtensionPrefs(&deserializer
, fake_json_path
));
316 void ExternalTestingLoader::StartLoading() {
317 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
318 prefs_
.reset(testing_prefs_
->DeepCopy());
322 ExternalTestingLoader::~ExternalTestingLoader() {}
324 const base::FilePath
ExternalTestingLoader::GetBaseCrxFilePath() {
325 return fake_base_path_
;
328 } // namespace extensions