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_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/sync/profile_sync_service_factory.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "components/syncable_prefs/pref_service_syncable.h"
24 #include "content/public/browser/browser_thread.h"
26 using content::BrowserThread
;
30 base::FilePath::CharType kExternalExtensionJson
[] =
31 FILE_PATH_LITERAL("external_extensions.json");
33 std::set
<base::FilePath
> GetPrefsCandidateFilesFromFolder(
34 const base::FilePath
& external_extension_search_path
) {
35 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
37 std::set
<base::FilePath
> external_extension_paths
;
39 if (!base::PathExists(external_extension_search_path
)) {
40 // Does not have to exist.
41 return external_extension_paths
;
44 base::FileEnumerator
json_files(
45 external_extension_search_path
,
47 base::FileEnumerator::FILES
);
49 base::FilePath::StringType extension
= base::UTF8ToWide(".json");
50 #elif defined(OS_POSIX)
51 base::FilePath::StringType
extension(".json");
54 base::FilePath file
= json_files
.Next();
55 if (file
.BaseName().value() == kExternalExtensionJson
)
56 continue; // Already taken care of elsewhere.
59 if (file
.MatchesExtension(extension
)) {
60 external_extension_paths
.insert(file
.BaseName());
62 DVLOG(1) << "Not considering: " << file
.LossyDisplayName()
63 << " (does not have a .json extension)";
67 return external_extension_paths
;
70 // Extracts extension information from a json file serialized by |serializer|.
71 // |path| is only used for informational purposes (outputted when an error
72 // occurs). An empty dictionary is returned in case of failure (e.g. invalid
73 // path or json content).
74 // Caller takes ownership of the returned dictionary.
75 base::DictionaryValue
* ExtractExtensionPrefs(
76 base::ValueDeserializer
* deserializer
,
77 const base::FilePath
& path
) {
78 std::string error_msg
;
79 base::Value
* extensions
= deserializer
->Deserialize(NULL
, &error_msg
);
81 LOG(WARNING
) << "Unable to deserialize json data: " << error_msg
82 << " in file " << path
.value() << ".";
83 return new base::DictionaryValue
;
86 base::DictionaryValue
* ext_dictionary
= NULL
;
87 if (extensions
->GetAsDictionary(&ext_dictionary
))
88 return ext_dictionary
;
90 LOG(WARNING
) << "Expected a JSON dictionary in file "
91 << path
.value() << ".";
92 return new base::DictionaryValue
;
97 namespace extensions
{
99 ExternalPrefLoader::ExternalPrefLoader(int base_path_id
,
102 : base_path_id_(base_path_id
),
105 syncable_pref_observer_(this) {
106 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
109 ExternalPrefLoader::~ExternalPrefLoader() {
112 const base::FilePath
ExternalPrefLoader::GetBaseCrxFilePath() {
113 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
115 // |base_path_| was set in LoadOnFileThread().
119 void ExternalPrefLoader::StartLoading() {
120 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
121 if ((options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
) &&
122 (profile_
&& profile_
->IsSyncAllowed())) {
123 if (!PostLoadIfPrioritySyncReady()) {
125 syncable_prefs::PrefServiceSyncable
* prefs
=
126 PrefServiceSyncableFromProfile(profile_
);
128 syncable_pref_observer_
.Add(prefs
);
129 ProfileSyncService
* service
=
130 ProfileSyncServiceFactory::GetForProfile(profile_
);
132 if (service
->CanSyncStart() &&
133 (service
->HasSyncSetupCompleted() ||
134 browser_defaults::kSyncAutoStarts
)) {
135 service
->AddObserver(this);
137 PostLoadAndRemoveObservers();
141 BrowserThread::PostTask(
142 BrowserThread::FILE, FROM_HERE
,
143 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
147 void ExternalPrefLoader::OnIsSyncingChanged() {
148 PostLoadIfPrioritySyncReady();
151 void ExternalPrefLoader::OnStateChanged() {
152 ProfileSyncService
* service
=
153 ProfileSyncServiceFactory::GetForProfile(profile_
);
155 if (!service
->CanSyncStart()) {
156 PostLoadAndRemoveObservers();
160 bool ExternalPrefLoader::PostLoadIfPrioritySyncReady() {
161 DCHECK(options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
);
164 syncable_prefs::PrefServiceSyncable
* prefs
=
165 PrefServiceSyncableFromProfile(profile_
);
167 if (prefs
->IsPrioritySyncing()) {
168 PostLoadAndRemoveObservers();
175 void ExternalPrefLoader::PostLoadAndRemoveObservers() {
176 syncable_prefs::PrefServiceSyncable
* prefs
=
177 PrefServiceSyncableFromProfile(profile_
);
179 syncable_pref_observer_
.Remove(prefs
);
181 ProfileSyncService
* service
=
182 ProfileSyncServiceFactory::GetForProfile(profile_
);
184 service
->RemoveObserver(this);
186 BrowserThread::PostTask(
187 BrowserThread::FILE, FROM_HERE
,
188 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
191 void ExternalPrefLoader::LoadOnFileThread() {
192 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
194 scoped_ptr
<base::DictionaryValue
> prefs(new base::DictionaryValue
);
196 // TODO(skerner): Some values of base_path_id_ will cause
197 // PathService::Get() to return false, because the path does
198 // not exist. Find and fix the build/install scripts so that
199 // this can become a CHECK(). Known examples include chrome
200 // OS developer builds and linux install packages.
201 // Tracked as crbug.com/70402 .
202 if (PathService::Get(base_path_id_
, &base_path_
)) {
203 ReadExternalExtensionPrefFile(prefs
.get());
206 LOG(WARNING
) << "You are using an old-style extension deployment method "
207 "(external_extensions.json), which will soon be "
208 "deprecated. (see http://developer.chrome.com/"
209 "extensions/external_extensions.html)";
211 ReadStandaloneExtensionPrefFiles(prefs
.get());
216 if (base_path_id_
== chrome::DIR_EXTERNAL_EXTENSIONS
) {
217 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
221 // If we have any records to process, then we must have
222 // read at least one .json file. If so, then we should have
224 if (!prefs_
->empty())
225 CHECK(!base_path_
.empty());
227 BrowserThread::PostTask(
228 BrowserThread::UI
, FROM_HERE
,
229 base::Bind(&ExternalPrefLoader::LoadFinished
, this));
232 void ExternalPrefLoader::ReadExternalExtensionPrefFile(
233 base::DictionaryValue
* prefs
) {
234 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
235 CHECK(NULL
!= prefs
);
237 base::FilePath json_file
= base_path_
.Append(kExternalExtensionJson
);
239 if (!base::PathExists(json_file
)) {
240 // This is not an error. The file does not exist by default.
244 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN
)) {
245 #if defined(OS_MACOSX)
246 if (!base::VerifyPathControlledByAdmin(json_file
)) {
247 LOG(ERROR
) << "Can not read external extensions source. The file "
248 << json_file
.value() << " and every directory in its path, "
249 << "must be owned by root, have group \"admin\", and not be "
250 << "writable by all users. These restrictions prevent "
251 << "unprivleged users from making chrome install extensions "
252 << "on other users' accounts.";
256 // The only platform that uses this check is Mac OS. If you add one,
257 // you need to implement base::VerifyPathControlledByAdmin() for
260 #endif // defined(OS_MACOSX)
263 JSONFileValueDeserializer
deserializer(json_file
);
264 scoped_ptr
<base::DictionaryValue
> ext_prefs(
265 ExtractExtensionPrefs(&deserializer
, json_file
));
267 prefs
->MergeDictionary(ext_prefs
.get());
270 void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
271 base::DictionaryValue
* prefs
) {
272 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
273 CHECK(NULL
!= prefs
);
275 // First list the potential .json candidates.
276 std::set
<base::FilePath
>
277 candidates
= GetPrefsCandidateFilesFromFolder(base_path_
);
278 if (candidates
.empty()) {
279 DVLOG(1) << "Extension candidates list empty";
283 // For each file read the json description & build the proper
285 for (std::set
<base::FilePath
>::const_iterator it
= candidates
.begin();
286 it
!= candidates
.end();
288 base::FilePath extension_candidate_path
= base_path_
.Append(*it
);
293 extension_candidate_path
.RemoveExtension().BaseName().value());
294 #elif defined(OS_POSIX)
295 extension_candidate_path
.RemoveExtension().BaseName().value();
298 DVLOG(1) << "Reading json file: "
299 << extension_candidate_path
.LossyDisplayName();
301 JSONFileValueDeserializer
deserializer(extension_candidate_path
);
302 scoped_ptr
<base::DictionaryValue
> ext_prefs(
303 ExtractExtensionPrefs(&deserializer
, extension_candidate_path
));
305 DVLOG(1) << "Adding extension with id: " << id
;
306 prefs
->Set(id
, ext_prefs
.release());
311 ExternalTestingLoader::ExternalTestingLoader(
312 const std::string
& json_data
,
313 const base::FilePath
& fake_base_path
)
314 : fake_base_path_(fake_base_path
) {
315 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
316 JSONStringValueDeserializer
deserializer(json_data
);
317 base::FilePath fake_json_path
= fake_base_path
.AppendASCII("fake.json");
318 testing_prefs_
.reset(ExtractExtensionPrefs(&deserializer
, fake_json_path
));
321 void ExternalTestingLoader::StartLoading() {
322 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
323 prefs_
.reset(testing_prefs_
->DeepCopy());
327 ExternalTestingLoader::~ExternalTestingLoader() {}
329 const base::FilePath
ExternalTestingLoader::GetBaseCrxFilePath() {
330 return fake_base_path_
;
333 } // namespace extensions