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/profiles/profile.h"
21 #include "chrome/browser/sync/profile_sync_service_factory.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "content/public/browser/browser_thread.h"
25 using content::BrowserThread
;
29 base::FilePath::CharType kExternalExtensionJson
[] =
30 FILE_PATH_LITERAL("external_extensions.json");
32 std::set
<base::FilePath
> GetPrefsCandidateFilesFromFolder(
33 const base::FilePath
& external_extension_search_path
) {
34 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
36 std::set
<base::FilePath
> external_extension_paths
;
38 if (!base::PathExists(external_extension_search_path
)) {
39 // Does not have to exist.
40 return external_extension_paths
;
43 base::FileEnumerator
json_files(
44 external_extension_search_path
,
46 base::FileEnumerator::FILES
);
48 base::FilePath::StringType extension
= base::UTF8ToWide(".json");
49 #elif defined(OS_POSIX)
50 base::FilePath::StringType
extension(".json");
53 base::FilePath file
= json_files
.Next();
54 if (file
.BaseName().value() == kExternalExtensionJson
)
55 continue; // Already taken care of elsewhere.
58 if (file
.MatchesExtension(extension
)) {
59 external_extension_paths
.insert(file
.BaseName());
61 DVLOG(1) << "Not considering: " << file
.LossyDisplayName()
62 << " (does not have a .json extension)";
66 return external_extension_paths
;
69 // Extracts extension information from a json file serialized by |serializer|.
70 // |path| is only used for informational purposes (outputted when an error
71 // occurs). An empty dictionary is returned in case of failure (e.g. invalid
72 // path or json content).
73 // Caller takes ownership of the returned dictionary.
74 base::DictionaryValue
* ExtractExtensionPrefs(
75 base::ValueDeserializer
* deserializer
,
76 const base::FilePath
& path
) {
77 std::string error_msg
;
78 base::Value
* extensions
= deserializer
->Deserialize(NULL
, &error_msg
);
80 LOG(WARNING
) << "Unable to deserialize json data: " << error_msg
81 << " in file " << path
.value() << ".";
82 return new base::DictionaryValue
;
85 base::DictionaryValue
* ext_dictionary
= NULL
;
86 if (extensions
->GetAsDictionary(&ext_dictionary
))
87 return ext_dictionary
;
89 LOG(WARNING
) << "Expected a JSON dictionary in file "
90 << path
.value() << ".";
91 return new base::DictionaryValue
;
96 namespace extensions
{
98 ExternalPrefLoader::ExternalPrefLoader(int base_path_id
,
101 : base_path_id_(base_path_id
),
104 syncable_pref_observer_(this) {
105 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
108 ExternalPrefLoader::~ExternalPrefLoader() {
111 const base::FilePath
ExternalPrefLoader::GetBaseCrxFilePath() {
112 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
114 // |base_path_| was set in LoadOnFileThread().
118 void ExternalPrefLoader::StartLoading() {
119 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
120 if ((options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
) &&
121 (profile_
&& profile_
->IsSyncAllowed())) {
122 if (!PostLoadIfPrioritySyncReady()) {
124 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
126 syncable_pref_observer_
.Add(prefs
);
127 ProfileSyncService
* service
=
128 ProfileSyncServiceFactory::GetForProfile(profile_
);
130 if (service
->CanSyncStart() &&
131 (service
->HasSyncSetupCompleted() ||
132 browser_defaults::kSyncAutoStarts
)) {
133 service
->AddObserver(this);
135 PostLoadAndRemoveObservers();
139 BrowserThread::PostTask(
140 BrowserThread::FILE, FROM_HERE
,
141 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
145 void ExternalPrefLoader::OnIsSyncingChanged() {
146 PostLoadIfPrioritySyncReady();
149 void ExternalPrefLoader::OnStateChanged() {
150 ProfileSyncService
* service
=
151 ProfileSyncServiceFactory::GetForProfile(profile_
);
153 if (!service
->CanSyncStart()) {
154 PostLoadAndRemoveObservers();
158 bool ExternalPrefLoader::PostLoadIfPrioritySyncReady() {
159 DCHECK(options_
& DELAY_LOAD_UNTIL_PRIORITY_SYNC
);
162 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
164 if (prefs
->IsPrioritySyncing()) {
165 PostLoadAndRemoveObservers();
172 void ExternalPrefLoader::PostLoadAndRemoveObservers() {
173 PrefServiceSyncable
* prefs
= PrefServiceSyncable::FromProfile(profile_
);
175 syncable_pref_observer_
.Remove(prefs
);
177 ProfileSyncService
* service
=
178 ProfileSyncServiceFactory::GetForProfile(profile_
);
180 service
->RemoveObserver(this);
182 BrowserThread::PostTask(
183 BrowserThread::FILE, FROM_HERE
,
184 base::Bind(&ExternalPrefLoader::LoadOnFileThread
, this));
187 void ExternalPrefLoader::LoadOnFileThread() {
188 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
190 scoped_ptr
<base::DictionaryValue
> prefs(new base::DictionaryValue
);
192 // TODO(skerner): Some values of base_path_id_ will cause
193 // PathService::Get() to return false, because the path does
194 // not exist. Find and fix the build/install scripts so that
195 // this can become a CHECK(). Known examples include chrome
196 // OS developer builds and linux install packages.
197 // Tracked as crbug.com/70402 .
198 if (PathService::Get(base_path_id_
, &base_path_
)) {
199 ReadExternalExtensionPrefFile(prefs
.get());
202 LOG(WARNING
) << "You are using an old-style extension deployment method "
203 "(external_extensions.json), which will soon be "
204 "deprecated. (see http://developer.chrome.com/"
205 "extensions/external_extensions.html)";
207 ReadStandaloneExtensionPrefFiles(prefs
.get());
212 if (base_path_id_
== chrome::DIR_EXTERNAL_EXTENSIONS
) {
213 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
217 // If we have any records to process, then we must have
218 // read at least one .json file. If so, then we should have
220 if (!prefs_
->empty())
221 CHECK(!base_path_
.empty());
223 BrowserThread::PostTask(
224 BrowserThread::UI
, FROM_HERE
,
225 base::Bind(&ExternalPrefLoader::LoadFinished
, this));
228 void ExternalPrefLoader::ReadExternalExtensionPrefFile(
229 base::DictionaryValue
* prefs
) {
230 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
231 CHECK(NULL
!= prefs
);
233 base::FilePath json_file
= base_path_
.Append(kExternalExtensionJson
);
235 if (!base::PathExists(json_file
)) {
236 // This is not an error. The file does not exist by default.
240 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN
)) {
241 #if defined(OS_MACOSX)
242 if (!base::VerifyPathControlledByAdmin(json_file
)) {
243 LOG(ERROR
) << "Can not read external extensions source. The file "
244 << json_file
.value() << " and every directory in its path, "
245 << "must be owned by root, have group \"admin\", and not be "
246 << "writable by all users. These restrictions prevent "
247 << "unprivleged users from making chrome install extensions "
248 << "on other users' accounts.";
252 // The only platform that uses this check is Mac OS. If you add one,
253 // you need to implement base::VerifyPathControlledByAdmin() for
256 #endif // defined(OS_MACOSX)
259 JSONFileValueDeserializer
deserializer(json_file
);
260 scoped_ptr
<base::DictionaryValue
> ext_prefs(
261 ExtractExtensionPrefs(&deserializer
, json_file
));
263 prefs
->MergeDictionary(ext_prefs
.get());
266 void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
267 base::DictionaryValue
* prefs
) {
268 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
269 CHECK(NULL
!= prefs
);
271 // First list the potential .json candidates.
272 std::set
<base::FilePath
>
273 candidates
= GetPrefsCandidateFilesFromFolder(base_path_
);
274 if (candidates
.empty()) {
275 DVLOG(1) << "Extension candidates list empty";
279 // For each file read the json description & build the proper
281 for (std::set
<base::FilePath
>::const_iterator it
= candidates
.begin();
282 it
!= candidates
.end();
284 base::FilePath extension_candidate_path
= base_path_
.Append(*it
);
289 extension_candidate_path
.RemoveExtension().BaseName().value());
290 #elif defined(OS_POSIX)
291 extension_candidate_path
.RemoveExtension().BaseName().value();
294 DVLOG(1) << "Reading json file: "
295 << extension_candidate_path
.LossyDisplayName();
297 JSONFileValueDeserializer
deserializer(extension_candidate_path
);
298 scoped_ptr
<base::DictionaryValue
> ext_prefs(
299 ExtractExtensionPrefs(&deserializer
, extension_candidate_path
));
301 DVLOG(1) << "Adding extension with id: " << id
;
302 prefs
->Set(id
, ext_prefs
.release());
307 ExternalTestingLoader::ExternalTestingLoader(
308 const std::string
& json_data
,
309 const base::FilePath
& fake_base_path
)
310 : fake_base_path_(fake_base_path
) {
311 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
312 JSONStringValueDeserializer
deserializer(json_data
);
313 base::FilePath fake_json_path
= fake_base_path
.AppendASCII("fake.json");
314 testing_prefs_
.reset(ExtractExtensionPrefs(&deserializer
, fake_json_path
));
317 void ExternalTestingLoader::StartLoading() {
318 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
319 prefs_
.reset(testing_prefs_
->DeepCopy());
323 ExternalTestingLoader::~ExternalTestingLoader() {}
325 const base::FilePath
ExternalTestingLoader::GetBaseCrxFilePath() {
326 return fake_base_path_
;
329 } // namespace extensions