Extensions cleanup: Merge IsSyncableApp+Extension, ShouldSyncApp+Extension
[chromium-blink-merge.git] / chrome / browser / extensions / external_pref_loader.cc
blobd5509276e88abbc69f2c79aec33d98ddd3eedaba
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"
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/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;
27 namespace {
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,
45 false, // Recursive.
46 base::FileEnumerator::FILES);
47 #if defined(OS_WIN)
48 base::FilePath::StringType extension = base::UTF8ToWide(".json");
49 #elif defined(OS_POSIX)
50 base::FilePath::StringType extension(".json");
51 #endif
52 do {
53 base::FilePath file = json_files.Next();
54 if (file.BaseName().value() == kExternalExtensionJson)
55 continue; // Already taken care of elsewhere.
56 if (file.empty())
57 break;
58 if (file.MatchesExtension(extension)) {
59 external_extension_paths.insert(file.BaseName());
60 } else {
61 DVLOG(1) << "Not considering: " << file.LossyDisplayName()
62 << " (does not have a .json extension)";
64 } while (true);
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);
79 if (!extensions) {
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;
94 } // namespace
96 namespace extensions {
98 ExternalPrefLoader::ExternalPrefLoader(int base_path_id,
99 Options options,
100 Profile* profile)
101 : base_path_id_(base_path_id),
102 options_(options),
103 profile_(profile),
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().
115 return base_path_;
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()) {
123 DCHECK(profile_);
124 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
125 DCHECK(prefs);
126 syncable_pref_observer_.Add(prefs);
127 ProfileSyncService* service =
128 ProfileSyncServiceFactory::GetForProfile(profile_);
129 DCHECK(service);
130 if (service->CanSyncStart() &&
131 (service->HasSyncSetupCompleted() ||
132 browser_defaults::kSyncAutoStarts)) {
133 service->AddObserver(this);
134 } else {
135 PostLoadAndRemoveObservers();
138 } else {
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_);
152 DCHECK(service);
153 if (!service->CanSyncStart()) {
154 PostLoadAndRemoveObservers();
158 bool ExternalPrefLoader::PostLoadIfPrioritySyncReady() {
159 DCHECK(options_ & DELAY_LOAD_UNTIL_PRIORITY_SYNC);
160 DCHECK(profile_);
162 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
163 DCHECK(prefs);
164 if (prefs->IsPrioritySyncing()) {
165 PostLoadAndRemoveObservers();
166 return true;
169 return false;
172 void ExternalPrefLoader::PostLoadAndRemoveObservers() {
173 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
174 DCHECK(prefs);
175 syncable_pref_observer_.Remove(prefs);
177 ProfileSyncService* service =
178 ProfileSyncServiceFactory::GetForProfile(profile_);
179 DCHECK(service);
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());
201 if (!prefs->empty())
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());
210 prefs_.swap(prefs);
212 if (base_path_id_ == chrome::DIR_EXTERNAL_EXTENSIONS) {
213 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
214 prefs_->size());
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
219 // set |base_path_|.
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.
237 return;
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.";
249 return;
251 #else
252 // The only platform that uses this check is Mac OS. If you add one,
253 // you need to implement base::VerifyPathControlledByAdmin() for
254 // that platform.
255 NOTREACHED();
256 #endif // defined(OS_MACOSX)
259 JSONFileValueDeserializer deserializer(json_file);
260 scoped_ptr<base::DictionaryValue> ext_prefs(
261 ExtractExtensionPrefs(&deserializer, json_file));
262 if (ext_prefs)
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";
276 return;
279 // For each file read the json description & build the proper
280 // associated prefs.
281 for (std::set<base::FilePath>::const_iterator it = candidates.begin();
282 it != candidates.end();
283 ++it) {
284 base::FilePath extension_candidate_path = base_path_.Append(*it);
286 std::string id =
287 #if defined(OS_WIN)
288 base::UTF16ToASCII(
289 extension_candidate_path.RemoveExtension().BaseName().value());
290 #elif defined(OS_POSIX)
291 extension_candidate_path.RemoveExtension().BaseName().value();
292 #endif
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));
300 if (ext_prefs) {
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());
320 LoadFinished();
323 ExternalTestingLoader::~ExternalTestingLoader() {}
325 const base::FilePath ExternalTestingLoader::GetBaseCrxFilePath() {
326 return fake_base_path_;
329 } // namespace extensions