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/file_util.h" 9 #include "base/files/file_enumerator.h" 10 #include "base/files/file_path.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 21 using content::BrowserThread; 22 23 namespace { 24 25 base::FilePath::CharType kExternalExtensionJson[] = 26 FILE_PATH_LITERAL("external_extensions.json"); 27 28 std::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 = 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. 70 DictionaryValue* ExtractExtensionPrefs(base::ValueSerializer* serializer, 71 const base::FilePath& path) { 72 std::string error_msg; 73 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 DictionaryValue; 78 } 79 80 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 DictionaryValue; 87 } 88 89 } // namespace 90 91 namespace extensions { 92 93 ExternalPrefLoader::ExternalPrefLoader(int base_path_id, Options options) 94 : base_path_id_(base_path_id), options_(options) { 95 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 96 } 97 98 const base::FilePath ExternalPrefLoader::GetBaseCrxFilePath() { 99 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 100 101 // |base_path_| was set in LoadOnFileThread(). 102 return base_path_; 103 } 104 105 void ExternalPrefLoader::StartLoading() { 106 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 107 BrowserThread::PostTask( 108 BrowserThread::FILE, FROM_HERE, 109 base::Bind(&ExternalPrefLoader::LoadOnFileThread, this)); 110 } 111 112 void ExternalPrefLoader::LoadOnFileThread() { 113 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 114 115 scoped_ptr<DictionaryValue> prefs(new 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://code.google.com/chrome/" 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 153 void ExternalPrefLoader::ReadExternalExtensionPrefFile(DictionaryValue* prefs) { 154 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 155 CHECK(NULL != prefs); 156 157 base::FilePath json_file = base_path_.Append(kExternalExtensionJson); 158 159 if (!base::PathExists(json_file)) { 160 // This is not an error. The file does not exist by default. 161 return; 162 } 163 164 if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN)) { 165 #if defined(OS_MACOSX) 166 if (!file_util::VerifyPathControlledByAdmin(json_file)) { 167 LOG(ERROR) << "Can not read external extensions source. The file " 168 << json_file.value() << " and every directory in its path, " 169 << "must be owned by root, have group \"admin\", and not be " 170 << "writable by all users. These restrictions prevent " 171 << "unprivleged users from making chrome install extensions " 172 << "on other users' accounts."; 173 return; 174 } 175 #else 176 // The only platform that uses this check is Mac OS. If you add one, 177 // you need to implement file_util::VerifyPathControlledByAdmin() for 178 // that platform. 179 NOTREACHED(); 180 #endif // defined(OS_MACOSX) 181 } 182 183 JSONFileValueSerializer serializer(json_file); 184 scoped_ptr<DictionaryValue> ext_prefs( 185 ExtractExtensionPrefs(&serializer, json_file)); 186 if (ext_prefs) 187 prefs->MergeDictionary(ext_prefs.get()); 188 } 189 190 void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles( 191 DictionaryValue* prefs) { 192 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 193 CHECK(NULL != prefs); 194 195 // First list the potential .json candidates. 196 std::set<base::FilePath> 197 candidates = GetPrefsCandidateFilesFromFolder(base_path_); 198 if (candidates.empty()) { 199 DVLOG(1) << "Extension candidates list empty"; 200 return; 201 } 202 203 // For each file read the json description & build the proper 204 // associated prefs. 205 for (std::set<base::FilePath>::const_iterator it = candidates.begin(); 206 it != candidates.end(); 207 ++it) { 208 base::FilePath extension_candidate_path = base_path_.Append(*it); 209 210 std::string id = 211 #if defined(OS_WIN) 212 WideToASCII( 213 extension_candidate_path.RemoveExtension().BaseName().value()); 214 #elif defined(OS_POSIX) 215 extension_candidate_path.RemoveExtension().BaseName().value().c_str(); 216 #endif 217 218 DVLOG(1) << "Reading json file: " 219 << extension_candidate_path.LossyDisplayName().c_str(); 220 221 JSONFileValueSerializer serializer(extension_candidate_path); 222 scoped_ptr<DictionaryValue> ext_prefs( 223 ExtractExtensionPrefs(&serializer, extension_candidate_path)); 224 if (ext_prefs) { 225 DVLOG(1) << "Adding extension with id: " << id; 226 prefs->Set(id, ext_prefs.release()); 227 } 228 } 229 } 230 231 ExternalTestingLoader::ExternalTestingLoader( 232 const std::string& json_data, 233 const base::FilePath& fake_base_path) 234 : fake_base_path_(fake_base_path) { 235 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 236 JSONStringValueSerializer serializer(json_data); 237 base::FilePath fake_json_path = fake_base_path.AppendASCII("fake.json"); 238 testing_prefs_.reset(ExtractExtensionPrefs(&serializer, fake_json_path)); 239 } 240 241 void ExternalTestingLoader::StartLoading() { 242 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 243 prefs_.reset(testing_prefs_->DeepCopy()); 244 LoadFinished(); 245 } 246 247 ExternalTestingLoader::~ExternalTestingLoader() {} 248 249 const base::FilePath ExternalTestingLoader::GetBaseCrxFilePath() { 250 return fake_base_path_; 251 } 252 253 } // extensions 254