Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2013 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/common/extensions/extension_file_util.h"
      6 
      7 #include <map>
      8 #include <vector>
      9 
     10 #include "base/file_util.h"
     11 #include "base/files/file_enumerator.h"
     12 #include "base/files/file_path.h"
     13 #include "base/files/scoped_temp_dir.h"
     14 #include "base/json/json_file_value_serializer.h"
     15 #include "base/logging.h"
     16 #include "base/metrics/histogram.h"
     17 #include "base/path_service.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "base/threading/thread_restrictions.h"
     21 #include "chrome/common/chrome_paths.h"
     22 #include "chrome/common/extensions/api/extension_action/action_info.h"
     23 #include "chrome/common/extensions/extension.h"
     24 #include "chrome/common/extensions/extension_icon_set.h"
     25 #include "chrome/common/extensions/extension_l10n_util.h"
     26 #include "chrome/common/extensions/extension_manifest_constants.h"
     27 #include "chrome/common/extensions/extension_messages.h"
     28 #include "chrome/common/extensions/manifest.h"
     29 #include "chrome/common/extensions/manifest_handler.h"
     30 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     31 #include "chrome/common/extensions/manifest_handlers/theme_handler.h"
     32 #include "chrome/common/extensions/message_bundle.h"
     33 #include "extensions/common/constants.h"
     34 #include "extensions/common/extension_resource.h"
     35 #include "extensions/common/install_warning.h"
     36 #include "grit/generated_resources.h"
     37 #include "net/base/escape.h"
     38 #include "net/base/file_stream.h"
     39 #include "ui/base/l10n/l10n_util.h"
     40 
     41 using extensions::Extension;
     42 using extensions::ExtensionResource;
     43 using extensions::Manifest;
     44 
     45 namespace errors = extension_manifest_errors;
     46 
     47 namespace {
     48 
     49 const base::FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp");
     50 
     51 // Add the image paths contained in the |icon_set| to |image_paths|.
     52 void AddPathsFromIconSet(const ExtensionIconSet& icon_set,
     53                          std::set<base::FilePath>* image_paths) {
     54   // TODO(viettrungluu): These |FilePath::FromUTF8Unsafe()| indicate that we're
     55   // doing something wrong.
     56   for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin();
     57        iter != icon_set.map().end(); ++iter) {
     58     image_paths->insert(base::FilePath::FromUTF8Unsafe(iter->second));
     59   }
     60 }
     61 
     62 }  // namespace
     63 
     64 namespace extension_file_util {
     65 
     66 base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir,
     67                                 const std::string& id,
     68                                 const std::string& version,
     69                                 const base::FilePath& extensions_dir) {
     70   base::FilePath extension_dir = extensions_dir.AppendASCII(id);
     71   base::FilePath version_dir;
     72 
     73   // Create the extension directory if it doesn't exist already.
     74   if (!base::PathExists(extension_dir)) {
     75     if (!file_util::CreateDirectory(extension_dir))
     76       return base::FilePath();
     77   }
     78 
     79   // Get a temp directory on the same file system as the profile.
     80   base::FilePath install_temp_dir = GetInstallTempDir(extensions_dir);
     81   base::ScopedTempDir extension_temp_dir;
     82   if (install_temp_dir.empty() ||
     83       !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) {
     84     LOG(ERROR) << "Creating of temp dir under in the profile failed.";
     85     return base::FilePath();
     86   }
     87   base::FilePath crx_temp_source =
     88       extension_temp_dir.path().Append(unpacked_source_dir.BaseName());
     89   if (!base::Move(unpacked_source_dir, crx_temp_source)) {
     90     LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value()
     91                << " to : " << crx_temp_source.value() << " failed.";
     92     return base::FilePath();
     93   }
     94 
     95   // Try to find a free directory. There can be legitimate conflicts in the case
     96   // of overinstallation of the same version.
     97   const int kMaxAttempts = 100;
     98   for (int i = 0; i < kMaxAttempts; ++i) {
     99     base::FilePath candidate = extension_dir.AppendASCII(
    100         base::StringPrintf("%s_%u", version.c_str(), i));
    101     if (!base::PathExists(candidate)) {
    102       version_dir = candidate;
    103       break;
    104     }
    105   }
    106 
    107   if (version_dir.empty()) {
    108     LOG(ERROR) << "Could not find a home for extension " << id << " with "
    109                << "version " << version << ".";
    110     return base::FilePath();
    111   }
    112 
    113   if (!base::Move(crx_temp_source, version_dir)) {
    114     LOG(ERROR) << "Installing extension from : " << crx_temp_source.value()
    115                << " into : " << version_dir.value() << " failed.";
    116     return base::FilePath();
    117   }
    118 
    119   return version_dir;
    120 }
    121 
    122 void UninstallExtension(const base::FilePath& extensions_dir,
    123                         const std::string& id) {
    124   // We don't care about the return value. If this fails (and it can, due to
    125   // plugins that aren't unloaded yet), it will get cleaned up by
    126   // ExtensionService::GarbageCollectExtensions.
    127   base::DeleteFile(extensions_dir.AppendASCII(id), true);  // recursive.
    128 }
    129 
    130 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path,
    131                                        Manifest::Location location,
    132                                        int flags,
    133                                        std::string* error) {
    134   return LoadExtension(extension_path, std::string(), location, flags, error);
    135 }
    136 
    137 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path,
    138                                        const std::string& extension_id,
    139                                        Manifest::Location location,
    140                                        int flags,
    141                                        std::string* error) {
    142   scoped_ptr<base::DictionaryValue> manifest(
    143       LoadManifest(extension_path, error));
    144   if (!manifest.get())
    145     return NULL;
    146   if (!extension_l10n_util::LocalizeExtension(extension_path, manifest.get(),
    147                                               error)) {
    148     return NULL;
    149   }
    150 
    151   scoped_refptr<Extension> extension(Extension::Create(extension_path,
    152                                                        location,
    153                                                        *manifest,
    154                                                        flags,
    155                                                        extension_id,
    156                                                        error));
    157   if (!extension.get())
    158     return NULL;
    159 
    160   std::vector<extensions::InstallWarning> warnings;
    161   if (!ValidateExtension(extension.get(), error, &warnings))
    162     return NULL;
    163   extension->AddInstallWarnings(warnings);
    164 
    165   return extension;
    166 }
    167 
    168 base::DictionaryValue* LoadManifest(const base::FilePath& extension_path,
    169                                     std::string* error) {
    170   base::FilePath manifest_path =
    171       extension_path.Append(extensions::kManifestFilename);
    172   if (!base::PathExists(manifest_path)) {
    173     *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
    174     return NULL;
    175   }
    176 
    177   JSONFileValueSerializer serializer(manifest_path);
    178   scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error));
    179   if (!root.get()) {
    180     if (error->empty()) {
    181       // If |error| is empty, than the file could not be read.
    182       // It would be cleaner to have the JSON reader give a specific error
    183       // in this case, but other code tests for a file error with
    184       // error->empty().  For now, be consistent.
    185       *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
    186     } else {
    187       *error = base::StringPrintf("%s  %s",
    188                                   errors::kManifestParseError,
    189                                   error->c_str());
    190     }
    191     return NULL;
    192   }
    193 
    194   if (!root->IsType(base::Value::TYPE_DICTIONARY)) {
    195     *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
    196     return NULL;
    197   }
    198 
    199   return static_cast<base::DictionaryValue*>(root.release());
    200 }
    201 
    202 std::vector<base::FilePath> FindPrivateKeyFiles(
    203     const base::FilePath& extension_dir) {
    204   std::vector<base::FilePath> result;
    205   // Pattern matching only works at the root level, so filter manually.
    206   base::FileEnumerator traversal(extension_dir, /*recursive=*/true,
    207                                  base::FileEnumerator::FILES);
    208   for (base::FilePath current = traversal.Next(); !current.empty();
    209        current = traversal.Next()) {
    210     if (!current.MatchesExtension(extensions::kExtensionKeyFileExtension))
    211       continue;
    212 
    213     std::string key_contents;
    214     if (!file_util::ReadFileToString(current, &key_contents)) {
    215       // If we can't read the file, assume it's not a private key.
    216       continue;
    217     }
    218     std::string key_bytes;
    219     if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) {
    220       // If we can't parse the key, assume it's ok too.
    221       continue;
    222     }
    223 
    224     result.push_back(current);
    225   }
    226   return result;
    227 }
    228 
    229 bool ValidateFilePath(const base::FilePath& path) {
    230   int64 size = 0;
    231   if (!base::PathExists(path) ||
    232       !file_util::GetFileSize(path, &size) ||
    233       size == 0) {
    234     return false;
    235   }
    236 
    237   return true;
    238 }
    239 
    240 bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
    241                               const Extension* extension,
    242                               int error_message_id,
    243                               std::string* error) {
    244   for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin();
    245        iter != icon_set.map().end();
    246        ++iter) {
    247     const base::FilePath path =
    248         extension->GetResource(iter->second).GetFilePath();
    249     if (!ValidateFilePath(path)) {
    250       *error = l10n_util::GetStringFUTF8(error_message_id,
    251                                          UTF8ToUTF16(iter->second));
    252       return false;
    253     }
    254   }
    255   return true;
    256 }
    257 
    258 bool ValidateExtension(const Extension* extension,
    259                        std::string* error,
    260                        std::vector<extensions::InstallWarning>* warnings) {
    261   // Ask registered manifest handlers to validate their paths.
    262   if (!extensions::ManifestHandler::ValidateExtension(
    263           extension, error, warnings))
    264     return false;
    265 
    266   // Check children of extension root to see if any of them start with _ and is
    267   // not on the reserved list.
    268   if (!CheckForIllegalFilenames(extension->path(), error)) {
    269     return false;
    270   }
    271 
    272   // Check that extensions don't include private key files.
    273   std::vector<base::FilePath> private_keys =
    274       FindPrivateKeyFiles(extension->path());
    275   if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) {
    276     if (!private_keys.empty()) {
    277       // Only print one of the private keys because l10n_util doesn't have a way
    278       // to translate a list of strings.
    279       *error = l10n_util::GetStringFUTF8(
    280           IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
    281           private_keys.front().LossyDisplayName());
    282       return false;
    283     }
    284   } else {
    285     for (size_t i = 0; i < private_keys.size(); ++i) {
    286       warnings->push_back(extensions::InstallWarning(
    287           extensions::InstallWarning::FORMAT_TEXT,
    288           l10n_util::GetStringFUTF8(
    289               IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
    290               private_keys[i].LossyDisplayName())));
    291     }
    292     // Only warn; don't block loading the extension.
    293   }
    294   return true;
    295 }
    296 
    297 std::set<base::FilePath> GetBrowserImagePaths(const Extension* extension) {
    298   std::set<base::FilePath> image_paths;
    299 
    300   AddPathsFromIconSet(extensions::IconsInfo::GetIcons(extension), &image_paths);
    301 
    302   // Theme images
    303   const base::DictionaryValue* theme_images =
    304       extensions::ThemeInfo::GetImages(extension);
    305   if (theme_images) {
    306     for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd();
    307          it.Advance()) {
    308       base::FilePath::StringType path;
    309       if (it.value().GetAsString(&path))
    310         image_paths.insert(base::FilePath(path));
    311     }
    312   }
    313 
    314   const extensions::ActionInfo* page_action =
    315       extensions::ActionInfo::GetPageActionInfo(extension);
    316   if (page_action && !page_action->default_icon.empty())
    317     AddPathsFromIconSet(page_action->default_icon, &image_paths);
    318 
    319   const extensions::ActionInfo* browser_action =
    320       extensions::ActionInfo::GetBrowserActionInfo(extension);
    321   if (browser_action && !browser_action->default_icon.empty())
    322     AddPathsFromIconSet(browser_action->default_icon, &image_paths);
    323 
    324   return image_paths;
    325 }
    326 
    327 void GarbageCollectExtensions(
    328     const base::FilePath& install_directory,
    329     const std::multimap<std::string, base::FilePath>& extension_paths) {
    330   // Nothing to clean up if it doesn't exist.
    331   if (!base::DirectoryExists(install_directory))
    332     return;
    333 
    334   DVLOG(1) << "Garbage collecting extensions...";
    335   base::FileEnumerator enumerator(install_directory,
    336                                   false,  // Not recursive.
    337                                   base::FileEnumerator::DIRECTORIES);
    338   base::FilePath extension_path;
    339   for (extension_path = enumerator.Next(); !extension_path.value().empty();
    340        extension_path = enumerator.Next()) {
    341     std::string extension_id;
    342 
    343     base::FilePath basename = extension_path.BaseName();
    344     // Clean up temporary files left if Chrome crashed or quit in the middle
    345     // of an extension install.
    346     if (basename.value() == kTempDirectoryName) {
    347       base::DeleteFile(extension_path, true);  // Recursive
    348       continue;
    349     }
    350 
    351     // Parse directory name as a potential extension ID.
    352     if (IsStringASCII(basename.value())) {
    353       extension_id = UTF16ToASCII(basename.LossyDisplayName());
    354       if (!Extension::IdIsValid(extension_id))
    355         extension_id.clear();
    356     }
    357 
    358     // Delete directories that aren't valid IDs.
    359     if (extension_id.empty()) {
    360       DLOG(WARNING) << "Invalid extension ID encountered in extensions "
    361                        "directory: " << basename.value();
    362       DVLOG(1) << "Deleting invalid extension directory "
    363                << extension_path.value() << ".";
    364       base::DeleteFile(extension_path, true);  // Recursive.
    365       continue;
    366     }
    367 
    368     typedef std::multimap<std::string, base::FilePath>::const_iterator Iter;
    369     std::pair<Iter, Iter> iter_pair = extension_paths.equal_range(extension_id);
    370 
    371     // If there is no entry in the prefs file, just delete the directory and
    372     // move on. This can legitimately happen when an uninstall does not
    373     // complete, for example, when a plugin is in use at uninstall time.
    374     if (iter_pair.first == iter_pair.second) {
    375       DVLOG(1) << "Deleting unreferenced install for directory "
    376                << extension_path.LossyDisplayName() << ".";
    377       base::DeleteFile(extension_path, true);  // Recursive.
    378       continue;
    379     }
    380 
    381     // Clean up old version directories.
    382     base::FileEnumerator versions_enumerator(
    383         extension_path,
    384         false,  // Not recursive.
    385         base::FileEnumerator::DIRECTORIES);
    386     for (base::FilePath version_dir = versions_enumerator.Next();
    387          !version_dir.value().empty();
    388          version_dir = versions_enumerator.Next()) {
    389       bool knownVersion = false;
    390       for (Iter it = iter_pair.first; it != iter_pair.second; ++it)
    391         if (version_dir.BaseName() == it->second.BaseName()) {
    392           knownVersion = true;
    393           break;
    394         }
    395       if (!knownVersion) {
    396         DVLOG(1) << "Deleting old version for directory "
    397                  << version_dir.LossyDisplayName() << ".";
    398         base::DeleteFile(version_dir, true);  // Recursive.
    399       }
    400     }
    401   }
    402 }
    403 
    404 extensions::MessageBundle* LoadMessageBundle(
    405     const base::FilePath& extension_path,
    406     const std::string& default_locale,
    407     std::string* error) {
    408   error->clear();
    409   // Load locale information if available.
    410   base::FilePath locale_path = extension_path.Append(extensions::kLocaleFolder);
    411   if (!base::PathExists(locale_path))
    412     return NULL;
    413 
    414   std::set<std::string> locales;
    415   if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error))
    416     return NULL;
    417 
    418   if (default_locale.empty() ||
    419       locales.find(default_locale) == locales.end()) {
    420     *error = l10n_util::GetStringUTF8(
    421         IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
    422     return NULL;
    423   }
    424 
    425   extensions::MessageBundle* message_bundle =
    426       extension_l10n_util::LoadMessageCatalogs(
    427           locale_path,
    428           default_locale,
    429           extension_l10n_util::CurrentLocaleOrDefault(),
    430           locales,
    431           error);
    432 
    433   return message_bundle;
    434 }
    435 
    436 SubstitutionMap* LoadMessageBundleSubstitutionMap(
    437     const base::FilePath& extension_path,
    438     const std::string& extension_id,
    439     const std::string& default_locale) {
    440   SubstitutionMap* returnValue = new SubstitutionMap();
    441   if (!default_locale.empty()) {
    442     // Touch disk only if extension is localized.
    443     std::string error;
    444     scoped_ptr<extensions::MessageBundle> bundle(
    445         LoadMessageBundle(extension_path, default_locale, &error));
    446 
    447     if (bundle.get())
    448       *returnValue = *bundle->dictionary();
    449   }
    450 
    451   // Add @@extension_id reserved message here, so it's available to
    452   // non-localized extensions too.
    453   returnValue->insert(
    454       std::make_pair(extensions::MessageBundle::kExtensionIdKey, extension_id));
    455 
    456   return returnValue;
    457 }
    458 
    459 bool CheckForIllegalFilenames(const base::FilePath& extension_path,
    460                               std::string* error) {
    461   // Reserved underscore names.
    462   static const base::FilePath::CharType* reserved_names[] = {
    463     extensions::kLocaleFolder,
    464     extensions::kPlatformSpecificFolder,
    465     FILE_PATH_LITERAL("__MACOSX"),
    466   };
    467   CR_DEFINE_STATIC_LOCAL(
    468       std::set<base::FilePath::StringType>, reserved_underscore_names,
    469       (reserved_names, reserved_names + arraysize(reserved_names)));
    470 
    471   // Enumerate all files and directories in the extension root.
    472   // There is a problem when using pattern "_*" with FileEnumerator, so we have
    473   // to cheat with find_first_of and match all.
    474   const int kFilesAndDirectories =
    475       base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES;
    476   base::FileEnumerator all_files(extension_path, false, kFilesAndDirectories);
    477 
    478   base::FilePath file;
    479   while (!(file = all_files.Next()).empty()) {
    480     base::FilePath::StringType filename = file.BaseName().value();
    481     // Skip all that don't start with "_".
    482     if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue;
    483     if (reserved_underscore_names.find(filename) ==
    484         reserved_underscore_names.end()) {
    485       *error = base::StringPrintf(
    486           "Cannot load extension with file or directory name %s. "
    487           "Filenames starting with \"_\" are reserved for use by the system.",
    488           filename.c_str());
    489       return false;
    490     }
    491   }
    492 
    493   return true;
    494 }
    495 
    496 base::FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
    497   std::string url_path = url.path();
    498   if (url_path.empty() || url_path[0] != '/')
    499     return base::FilePath();
    500 
    501   // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
    502   std::string file_path = net::UnescapeURLComponent(url_path,
    503       net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
    504   size_t skip = file_path.find_first_not_of("/\\");
    505   if (skip != file_path.npos)
    506     file_path = file_path.substr(skip);
    507 
    508   base::FilePath path = base::FilePath::FromUTF8Unsafe(file_path);
    509 
    510   // It's still possible for someone to construct an annoying URL whose path
    511   // would still wind up not being considered relative at this point.
    512   // For example: chrome-extension://id/c:////foo.html
    513   if (path.IsAbsolute())
    514     return base::FilePath();
    515 
    516   return path;
    517 }
    518 
    519 base::FilePath ExtensionResourceURLToFilePath(const GURL& url,
    520                                               const base::FilePath& root) {
    521   std::string host = net::UnescapeURLComponent(url.host(),
    522       net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
    523   if (host.empty())
    524     return base::FilePath();
    525 
    526   base::FilePath relative_path = ExtensionURLToRelativeFilePath(url);
    527   if (relative_path.empty())
    528     return base::FilePath();
    529 
    530   base::FilePath path = root.AppendASCII(host).Append(relative_path);
    531   if (!base::PathExists(path))
    532     return base::FilePath();
    533   path = base::MakeAbsoluteFilePath(path);
    534   if (path.empty() || !root.IsParent(path))
    535     return base::FilePath();
    536   return path;
    537 }
    538 
    539 base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir) {
    540   // We do file IO in this function, but only when the current profile's
    541   // Temp directory has never been used before, or in a rare error case.
    542   // Developers are not likely to see these situations often, so do an
    543   // explicit thread check.
    544   base::ThreadRestrictions::AssertIOAllowed();
    545 
    546   // Create the temp directory as a sub-directory of the Extensions directory.
    547   // This guarantees it is on the same file system as the extension's eventual
    548   // install target.
    549   base::FilePath temp_path = extensions_dir.Append(kTempDirectoryName);
    550   if (base::PathExists(temp_path)) {
    551     if (!base::DirectoryExists(temp_path)) {
    552       DLOG(WARNING) << "Not a directory: " << temp_path.value();
    553       return base::FilePath();
    554     }
    555     if (!base::PathIsWritable(temp_path)) {
    556       DLOG(WARNING) << "Can't write to path: " << temp_path.value();
    557       return base::FilePath();
    558     }
    559     // This is a directory we can write to.
    560     return temp_path;
    561   }
    562 
    563   // Directory doesn't exist, so create it.
    564   if (!file_util::CreateDirectory(temp_path)) {
    565     DLOG(WARNING) << "Couldn't create directory: " << temp_path.value();
    566     return base::FilePath();
    567   }
    568   return temp_path;
    569 }
    570 
    571 void DeleteFile(const base::FilePath& path, bool recursive) {
    572   base::DeleteFile(path, recursive);
    573 }
    574 
    575 }  // namespace extension_file_util
    576