Home | History | Annotate | Download | only in extensions
      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