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. 4 5#include "chrome/browser/extensions/external_pref_loader.h" 6 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/common/chrome_paths.h" 19#include "content/public/browser/browser_thread.h" 20 21using content::BrowserThread; 22 23namespace { 24 25base::FilePath::CharType kExternalExtensionJson[] = 26 FILE_PATH_LITERAL("external_extensions.json"); 27 28std::set<base::FilePath> GetPrefsCandidateFilesFromFolder( 29 const base::FilePath& external_extension_search_path) { 30 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 31 32 std::set<base::FilePath> external_extension_paths; 33 34 if (!base::PathExists(external_extension_search_path)) { 35 // Does not have to exist. 36 return external_extension_paths; 37 } 38 39 base::FileEnumerator json_files( 40 external_extension_search_path, 41 false, // Recursive. 42 base::FileEnumerator::FILES); 43#if defined(OS_WIN) 44 base::FilePath::StringType extension = base::UTF8ToWide(std::string(".json")); 45#elif defined(OS_POSIX) 46 base::FilePath::StringType extension(".json"); 47#endif 48 do { 49 base::FilePath file = json_files.Next(); 50 if (file.BaseName().value() == kExternalExtensionJson) 51 continue; // Already taken care of elsewhere. 52 if (file.empty()) 53 break; 54 if (file.MatchesExtension(extension)) { 55 external_extension_paths.insert(file.BaseName()); 56 } else { 57 DVLOG(1) << "Not considering: " << file.LossyDisplayName() 58 << " (does not have a .json extension)"; 59 } 60 } while (true); 61 62 return external_extension_paths; 63} 64 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. 70base::DictionaryValue* ExtractExtensionPrefs(base::ValueSerializer* serializer, 71 const base::FilePath& path) { 72 std::string error_msg; 73 base::Value* extensions = serializer->Deserialize(NULL, &error_msg); 74 if (!extensions) { 75 LOG(WARNING) << "Unable to deserialize json data: " << error_msg 76 << " in file " << path.value() << "."; 77 return new base::DictionaryValue; 78 } 79 80 base::DictionaryValue* ext_dictionary = NULL; 81 if (extensions->GetAsDictionary(&ext_dictionary)) 82 return ext_dictionary; 83 84 LOG(WARNING) << "Expected a JSON dictionary in file " 85 << path.value() << "."; 86 return new base::DictionaryValue; 87} 88 89} // namespace 90 91namespace extensions { 92 93ExternalPrefLoader::ExternalPrefLoader(int base_path_id, Options options) 94 : base_path_id_(base_path_id), options_(options) { 95 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 96} 97 98const base::FilePath ExternalPrefLoader::GetBaseCrxFilePath() { 99 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 100 101 // |base_path_| was set in LoadOnFileThread(). 102 return base_path_; 103} 104 105void ExternalPrefLoader::StartLoading() { 106 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 107 BrowserThread::PostTask( 108 BrowserThread::FILE, FROM_HERE, 109 base::Bind(&ExternalPrefLoader::LoadOnFileThread, this)); 110} 111 112void ExternalPrefLoader::LoadOnFileThread() { 113 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 114 115 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue); 116 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()); 125 126 if (!prefs->empty()) 127 LOG(WARNING) << "You are using an old-style extension deployment method " 128 "(external_extensions.json), which will soon be " 129 "deprecated. (see http://developer.chrome.com/" 130 "extensions/external_extensions.html)"; 131 132 ReadStandaloneExtensionPrefFiles(prefs.get()); 133 } 134 135 prefs_.swap(prefs); 136 137 if (base_path_id_ == chrome::DIR_EXTERNAL_EXTENSIONS) { 138 UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount", 139 prefs_->size()); 140 } 141 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 144 // set |base_path_|. 145 if (!prefs_->empty()) 146 CHECK(!base_path_.empty()); 147 148 BrowserThread::PostTask( 149 BrowserThread::UI, FROM_HERE, 150 base::Bind(&ExternalPrefLoader::LoadFinished, this)); 151} 152 153void ExternalPrefLoader::ReadExternalExtensionPrefFile( 154 base::DictionaryValue* prefs) { 155 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 156 CHECK(NULL != prefs); 157 158 base::FilePath json_file = base_path_.Append(kExternalExtensionJson); 159 160 if (!base::PathExists(json_file)) { 161 // This is not an error. The file does not exist by default. 162 return; 163 } 164 165 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN)) { 166#if defined(OS_MACOSX) 167 if (!base::VerifyPathControlledByAdmin(json_file)) { 168 LOG(ERROR) << "Can not read external extensions source. The file " 169 << json_file.value() << " and every directory in its path, " 170 << "must be owned by root, have group \"admin\", and not be " 171 << "writable by all users. These restrictions prevent " 172 << "unprivleged users from making chrome install extensions " 173 << "on other users' accounts."; 174 return; 175 } 176#else 177 // The only platform that uses this check is Mac OS. If you add one, 178 // you need to implement base::VerifyPathControlledByAdmin() for 179 // that platform. 180 NOTREACHED(); 181#endif // defined(OS_MACOSX) 182 } 183 184 JSONFileValueSerializer serializer(json_file); 185 scoped_ptr<base::DictionaryValue> ext_prefs( 186 ExtractExtensionPrefs(&serializer, json_file)); 187 if (ext_prefs) 188 prefs->MergeDictionary(ext_prefs.get()); 189} 190 191void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles( 192 base::DictionaryValue* prefs) { 193 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 194 CHECK(NULL != prefs); 195 196 // First list the potential .json candidates. 197 std::set<base::FilePath> 198 candidates = GetPrefsCandidateFilesFromFolder(base_path_); 199 if (candidates.empty()) { 200 DVLOG(1) << "Extension candidates list empty"; 201 return; 202 } 203 204 // For each file read the json description & build the proper 205 // associated prefs. 206 for (std::set<base::FilePath>::const_iterator it = candidates.begin(); 207 it != candidates.end(); 208 ++it) { 209 base::FilePath extension_candidate_path = base_path_.Append(*it); 210 211 std::string id = 212#if defined(OS_WIN) 213 base::UTF16ToASCII( 214 extension_candidate_path.RemoveExtension().BaseName().value()); 215#elif defined(OS_POSIX) 216 extension_candidate_path.RemoveExtension().BaseName().value().c_str(); 217#endif 218 219 DVLOG(1) << "Reading json file: " 220 << extension_candidate_path.LossyDisplayName().c_str(); 221 222 JSONFileValueSerializer serializer(extension_candidate_path); 223 scoped_ptr<base::DictionaryValue> ext_prefs( 224 ExtractExtensionPrefs(&serializer, extension_candidate_path)); 225 if (ext_prefs) { 226 DVLOG(1) << "Adding extension with id: " << id; 227 prefs->Set(id, ext_prefs.release()); 228 } 229 } 230} 231 232ExternalTestingLoader::ExternalTestingLoader( 233 const std::string& json_data, 234 const base::FilePath& fake_base_path) 235 : fake_base_path_(fake_base_path) { 236 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 237 JSONStringValueSerializer serializer(json_data); 238 base::FilePath fake_json_path = fake_base_path.AppendASCII("fake.json"); 239 testing_prefs_.reset(ExtractExtensionPrefs(&serializer, fake_json_path)); 240} 241 242void ExternalTestingLoader::StartLoading() { 243 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 244 prefs_.reset(testing_prefs_->DeepCopy()); 245 LoadFinished(); 246} 247 248ExternalTestingLoader::~ExternalTestingLoader() {} 249 250const base::FilePath ExternalTestingLoader::GetBaseCrxFilePath() { 251 return fake_base_path_; 252} 253 254} // namespace extensions 255