Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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/logging.h"
     12 #include "base/memory/scoped_temp_dir.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/path_service.h"
     15 #include "base/threading/thread_restrictions.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "chrome/common/chrome_paths.h"
     18 #include "chrome/common/extensions/extension.h"
     19 #include "chrome/common/extensions/extension_action.h"
     20 #include "chrome/common/extensions/extension_l10n_util.h"
     21 #include "chrome/common/extensions/extension_constants.h"
     22 #include "chrome/common/extensions/extension_resource.h"
     23 #include "chrome/common/extensions/extension_sidebar_defaults.h"
     24 #include "content/common/json_value_serializer.h"
     25 #include "grit/generated_resources.h"
     26 #include "net/base/escape.h"
     27 #include "net/base/file_stream.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 
     30 namespace errors = extension_manifest_errors;
     31 
     32 namespace extension_file_util {
     33 
     34 // Validates locale info. Doesn't check if messages.json files are valid.
     35 static bool ValidateLocaleInfo(const Extension& extension, std::string* error);
     36 
     37 // Returns false and sets the error if script file can't be loaded,
     38 // or if it's not UTF-8 encoded.
     39 static bool IsScriptValid(const FilePath& path, const FilePath& relative_path,
     40                           int message_id, std::string* error);
     41 
     42 const char kInstallDirectoryName[] = "Extensions";
     43 
     44 FilePath InstallExtension(const FilePath& unpacked_source_dir,
     45                           const std::string& id,
     46                           const std::string& version,
     47                           const FilePath& all_extensions_dir) {
     48   FilePath extension_dir = all_extensions_dir.AppendASCII(id);
     49   FilePath version_dir;
     50 
     51   // Create the extension directory if it doesn't exist already.
     52   if (!file_util::PathExists(extension_dir)) {
     53     if (!file_util::CreateDirectory(extension_dir))
     54       return FilePath();
     55   }
     56 
     57   // Try to find a free directory. There can be legitimate conflicts in the case
     58   // of overinstallation of the same version.
     59   const int kMaxAttempts = 100;
     60   for (int i = 0; i < kMaxAttempts; ++i) {
     61     FilePath candidate = extension_dir.AppendASCII(
     62         base::StringPrintf("%s_%u", version.c_str(), i));
     63     if (!file_util::PathExists(candidate)) {
     64       version_dir = candidate;
     65       break;
     66     }
     67   }
     68 
     69   if (version_dir.empty()) {
     70     LOG(ERROR) << "Could not find a home for extension " << id << " with "
     71                << "version " << version << ".";
     72     return FilePath();
     73   }
     74 
     75   if (!file_util::Move(unpacked_source_dir, version_dir))
     76     return FilePath();
     77 
     78   return version_dir;
     79 }
     80 
     81 void UninstallExtension(const FilePath& extensions_dir,
     82                         const std::string& id) {
     83   // We don't care about the return value. If this fails (and it can, due to
     84   // plugins that aren't unloaded yet, it will get cleaned up by
     85   // ExtensionService::GarbageCollectExtensions).
     86   file_util::Delete(extensions_dir.AppendASCII(id), true);  // recursive.
     87 }
     88 
     89 scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
     90                                        Extension::Location location,
     91                                        int flags,
     92                                        std::string* error) {
     93   FilePath manifest_path =
     94       extension_path.Append(Extension::kManifestFilename);
     95   if (!file_util::PathExists(manifest_path)) {
     96     *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
     97     return NULL;
     98   }
     99 
    100   JSONFileValueSerializer serializer(manifest_path);
    101   scoped_ptr<Value> root(serializer.Deserialize(NULL, error));
    102   if (!root.get()) {
    103     if (error->empty()) {
    104       // If |error| is empty, than the file could not be read.
    105       // It would be cleaner to have the JSON reader give a specific error
    106       // in this case, but other code tests for a file error with
    107       // error->empty().  For now, be consistent.
    108       *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
    109     } else {
    110       *error = base::StringPrintf("%s  %s",
    111                                   errors::kManifestParseError,
    112                                   error->c_str());
    113     }
    114     return NULL;
    115   }
    116 
    117   if (!root->IsType(Value::TYPE_DICTIONARY)) {
    118     *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
    119     return NULL;
    120   }
    121 
    122   DictionaryValue* manifest = static_cast<DictionaryValue*>(root.get());
    123   if (!extension_l10n_util::LocalizeExtension(extension_path, manifest, error))
    124     return NULL;
    125 
    126   scoped_refptr<Extension> extension(Extension::Create(
    127       extension_path,
    128       location,
    129       *manifest,
    130       flags,
    131       error));
    132   if (!extension.get())
    133     return NULL;
    134 
    135   if (!ValidateExtension(extension.get(), error))
    136     return NULL;
    137 
    138   return extension;
    139 }
    140 
    141 bool ValidateExtension(Extension* extension, std::string* error) {
    142   // Validate icons exist.
    143   for (ExtensionIconSet::IconMap::const_iterator iter =
    144            extension->icons().map().begin();
    145        iter != extension->icons().map().end();
    146        ++iter) {
    147     const FilePath path = extension->GetResource(iter->second).GetFilePath();
    148     if (!file_util::PathExists(path)) {
    149       *error =
    150           l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ICON_FAILED,
    151                                     UTF8ToUTF16(iter->second));
    152       return false;
    153     }
    154   }
    155 
    156   // Theme resource validation.
    157   if (extension->is_theme()) {
    158     DictionaryValue* images_value = extension->GetThemeImages();
    159     if (images_value) {
    160       for (DictionaryValue::key_iterator iter = images_value->begin_keys();
    161            iter != images_value->end_keys(); ++iter) {
    162         std::string val;
    163         if (images_value->GetStringWithoutPathExpansion(*iter, &val)) {
    164           FilePath image_path = extension->path().AppendASCII(val);
    165           if (!file_util::PathExists(image_path)) {
    166             *error =
    167                 l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH,
    168                                           image_path.LossyDisplayName());
    169             return false;
    170           }
    171         }
    172       }
    173     }
    174 
    175     // Themes cannot contain other extension types.
    176     return true;
    177   }
    178 
    179   // Validate that claimed script resources actually exist,
    180   // and are UTF-8 encoded.
    181   for (size_t i = 0; i < extension->content_scripts().size(); ++i) {
    182     const UserScript& script = extension->content_scripts()[i];
    183 
    184     for (size_t j = 0; j < script.js_scripts().size(); j++) {
    185       const UserScript::File& js_script = script.js_scripts()[j];
    186       const FilePath& path = ExtensionResource::GetFilePath(
    187           js_script.extension_root(), js_script.relative_path());
    188       if (!IsScriptValid(path, js_script.relative_path(),
    189                          IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
    190         return false;
    191     }
    192 
    193     for (size_t j = 0; j < script.css_scripts().size(); j++) {
    194       const UserScript::File& css_script = script.css_scripts()[j];
    195       const FilePath& path = ExtensionResource::GetFilePath(
    196           css_script.extension_root(), css_script.relative_path());
    197       if (!IsScriptValid(path, css_script.relative_path(),
    198                          IDS_EXTENSION_LOAD_CSS_FAILED, error))
    199         return false;
    200     }
    201   }
    202 
    203   // Validate claimed plugin paths.
    204   for (size_t i = 0; i < extension->plugins().size(); ++i) {
    205     const Extension::PluginInfo& plugin = extension->plugins()[i];
    206     if (!file_util::PathExists(plugin.path)) {
    207       *error =
    208           l10n_util::GetStringFUTF8(
    209               IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
    210               plugin.path.LossyDisplayName());
    211       return false;
    212     }
    213   }
    214 
    215   // Validate icon location for page actions.
    216   ExtensionAction* page_action = extension->page_action();
    217   if (page_action) {
    218     std::vector<std::string> icon_paths(*page_action->icon_paths());
    219     if (!page_action->default_icon_path().empty())
    220       icon_paths.push_back(page_action->default_icon_path());
    221     for (std::vector<std::string>::iterator iter = icon_paths.begin();
    222          iter != icon_paths.end(); ++iter) {
    223       if (!file_util::PathExists(extension->GetResource(*iter).GetFilePath())) {
    224         *error =
    225             l10n_util::GetStringFUTF8(
    226                 IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED,
    227                 UTF8ToUTF16(*iter));
    228         return false;
    229       }
    230     }
    231   }
    232 
    233   // Validate icon location for browser actions.
    234   // Note: browser actions don't use the icon_paths().
    235   ExtensionAction* browser_action = extension->browser_action();
    236   if (browser_action) {
    237     std::string path = browser_action->default_icon_path();
    238     if (!path.empty() &&
    239         !file_util::PathExists(extension->GetResource(path).GetFilePath())) {
    240         *error =
    241             l10n_util::GetStringFUTF8(
    242                 IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED,
    243                 UTF8ToUTF16(path));
    244         return false;
    245     }
    246   }
    247 
    248   // Validate background page location, except for hosted apps, which should use
    249   // an external URL. Background page for hosted apps are verified when the
    250   // extension is created (in Extension::InitFromValue)
    251   if (!extension->background_url().is_empty() && !extension->is_hosted_app()) {
    252     FilePath page_path = ExtensionURLToRelativeFilePath(
    253         extension->background_url());
    254     const FilePath path = extension->GetResource(page_path).GetFilePath();
    255     if (path.empty() || !file_util::PathExists(path)) {
    256       *error =
    257           l10n_util::GetStringFUTF8(
    258               IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED,
    259               page_path.LossyDisplayName());
    260       return false;
    261     }
    262   }
    263 
    264   // Validate path to the options page.  Don't check the URL for hosted apps,
    265   // because they are expected to refer to an external URL.
    266   if (!extension->options_url().is_empty() && !extension->is_hosted_app()) {
    267     const FilePath options_path = ExtensionURLToRelativeFilePath(
    268         extension->options_url());
    269     const FilePath path = extension->GetResource(options_path).GetFilePath();
    270     if (path.empty() || !file_util::PathExists(path)) {
    271       *error =
    272           l10n_util::GetStringFUTF8(
    273               IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
    274               options_path.LossyDisplayName());
    275       return false;
    276     }
    277   }
    278 
    279   // Validate sidebar default page location.
    280   ExtensionSidebarDefaults* sidebar_defaults = extension->sidebar_defaults();
    281   if (sidebar_defaults && sidebar_defaults->default_page().is_valid()) {
    282     FilePath page_path = ExtensionURLToRelativeFilePath(
    283         sidebar_defaults->default_page());
    284     const FilePath path = extension->GetResource(page_path).GetFilePath();
    285     if (path.empty() || !file_util::PathExists(path)) {
    286       *error =
    287           l10n_util::GetStringFUTF8(
    288               IDS_EXTENSION_LOAD_SIDEBAR_PAGE_FAILED,
    289               page_path.LossyDisplayName());
    290       return false;
    291     }
    292   }
    293 
    294   // Validate locale info.
    295   if (!ValidateLocaleInfo(*extension, error))
    296     return false;
    297 
    298   // Check children of extension root to see if any of them start with _ and is
    299   // not on the reserved list.
    300   if (!CheckForIllegalFilenames(extension->path(), error)) {
    301     return false;
    302   }
    303 
    304   return true;
    305 }
    306 
    307 void GarbageCollectExtensions(
    308     const FilePath& install_directory,
    309     const std::map<std::string, FilePath>& extension_paths) {
    310   // Nothing to clean up if it doesn't exist.
    311   if (!file_util::DirectoryExists(install_directory))
    312     return;
    313 
    314   VLOG(1) << "Garbage collecting extensions...";
    315   file_util::FileEnumerator enumerator(install_directory,
    316                                        false,  // Not recursive.
    317                                        file_util::FileEnumerator::DIRECTORIES);
    318   FilePath extension_path;
    319   for (extension_path = enumerator.Next(); !extension_path.value().empty();
    320        extension_path = enumerator.Next()) {
    321     std::string extension_id;
    322 
    323     FilePath basename = extension_path.BaseName();
    324     if (IsStringASCII(basename.value())) {
    325       extension_id = UTF16ToASCII(basename.LossyDisplayName());
    326       if (!Extension::IdIsValid(extension_id))
    327         extension_id.clear();
    328     }
    329 
    330     // Delete directories that aren't valid IDs.
    331     if (extension_id.empty()) {
    332       LOG(WARNING) << "Invalid extension ID encountered in extensions "
    333                       "directory: " << basename.value();
    334       VLOG(1) << "Deleting invalid extension directory "
    335               << extension_path.value() << ".";
    336       file_util::Delete(extension_path, true);  // Recursive.
    337       continue;
    338     }
    339 
    340     std::map<std::string, FilePath>::const_iterator iter =
    341         extension_paths.find(extension_id);
    342 
    343     // If there is no entry in the prefs file, just delete the directory and
    344     // move on. This can legitimately happen when an uninstall does not
    345     // complete, for example, when a plugin is in use at uninstall time.
    346     if (iter == extension_paths.end()) {
    347       VLOG(1) << "Deleting unreferenced install for directory "
    348               << extension_path.LossyDisplayName() << ".";
    349       file_util::Delete(extension_path, true);  // Recursive.
    350       continue;
    351     }
    352 
    353     // Clean up old version directories.
    354     file_util::FileEnumerator versions_enumerator(
    355         extension_path,
    356         false,  // Not recursive.
    357         file_util::FileEnumerator::DIRECTORIES);
    358     for (FilePath version_dir = versions_enumerator.Next();
    359          !version_dir.value().empty();
    360          version_dir = versions_enumerator.Next()) {
    361       if (version_dir.BaseName() != iter->second.BaseName()) {
    362         VLOG(1) << "Deleting old version for directory "
    363                 << version_dir.LossyDisplayName() << ".";
    364         file_util::Delete(version_dir, true);  // Recursive.
    365       }
    366     }
    367   }
    368 }
    369 
    370 ExtensionMessageBundle* LoadExtensionMessageBundle(
    371     const FilePath& extension_path,
    372     const std::string& default_locale,
    373     std::string* error) {
    374   error->clear();
    375   // Load locale information if available.
    376   FilePath locale_path = extension_path.Append(Extension::kLocaleFolder);
    377   if (!file_util::PathExists(locale_path))
    378     return NULL;
    379 
    380   std::set<std::string> locales;
    381   if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error))
    382     return NULL;
    383 
    384   if (default_locale.empty() ||
    385       locales.find(default_locale) == locales.end()) {
    386     *error = l10n_util::GetStringUTF8(
    387         IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
    388     return NULL;
    389   }
    390 
    391   ExtensionMessageBundle* message_bundle =
    392       extension_l10n_util::LoadMessageCatalogs(
    393           locale_path,
    394           default_locale,
    395           extension_l10n_util::CurrentLocaleOrDefault(),
    396           locales,
    397           error);
    398 
    399   return message_bundle;
    400 }
    401 
    402 static bool ValidateLocaleInfo(const Extension& extension, std::string* error) {
    403   // default_locale and _locales have to be both present or both missing.
    404   const FilePath path = extension.path().Append(Extension::kLocaleFolder);
    405   bool path_exists = file_util::PathExists(path);
    406   std::string default_locale = extension.default_locale();
    407 
    408   // If both default locale and _locales folder are empty, skip verification.
    409   if (default_locale.empty() && !path_exists)
    410     return true;
    411 
    412   if (default_locale.empty() && path_exists) {
    413     *error = l10n_util::GetStringUTF8(
    414         IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
    415     return false;
    416   } else if (!default_locale.empty() && !path_exists) {
    417     *error = errors::kLocalesTreeMissing;
    418     return false;
    419   }
    420 
    421   // Treat all folders under _locales as valid locales.
    422   file_util::FileEnumerator locales(path,
    423                                     false,
    424                                     file_util::FileEnumerator::DIRECTORIES);
    425 
    426   std::set<std::string> all_locales;
    427   extension_l10n_util::GetAllLocales(&all_locales);
    428   const FilePath default_locale_path = path.AppendASCII(default_locale);
    429   bool has_default_locale_message_file = false;
    430 
    431   FilePath locale_path;
    432   while (!(locale_path = locales.Next()).empty()) {
    433     if (extension_l10n_util::ShouldSkipValidation(path, locale_path,
    434                                                   all_locales))
    435       continue;
    436 
    437     FilePath messages_path =
    438         locale_path.Append(Extension::kMessagesFilename);
    439 
    440     if (!file_util::PathExists(messages_path)) {
    441       *error = base::StringPrintf(
    442           "%s %s", errors::kLocalesMessagesFileMissing,
    443           UTF16ToUTF8(messages_path.LossyDisplayName()).c_str());
    444       return false;
    445     }
    446 
    447     if (locale_path == default_locale_path)
    448       has_default_locale_message_file = true;
    449   }
    450 
    451   // Only message file for default locale has to exist.
    452   if (!has_default_locale_message_file) {
    453     *error = errors::kLocalesNoDefaultMessages;
    454     return false;
    455   }
    456 
    457   return true;
    458 }
    459 
    460 static bool IsScriptValid(const FilePath& path,
    461                           const FilePath& relative_path,
    462                           int message_id,
    463                           std::string* error) {
    464   std::string content;
    465   if (!file_util::PathExists(path) ||
    466       !file_util::ReadFileToString(path, &content)) {
    467     *error = l10n_util::GetStringFUTF8(
    468         message_id,
    469         relative_path.LossyDisplayName());
    470     return false;
    471   }
    472 
    473   if (!IsStringUTF8(content)) {
    474     *error = l10n_util::GetStringFUTF8(
    475         IDS_EXTENSION_BAD_FILE_ENCODING,
    476         relative_path.LossyDisplayName());
    477     return false;
    478   }
    479 
    480   return true;
    481 }
    482 
    483 bool CheckForIllegalFilenames(const FilePath& extension_path,
    484                               std::string* error) {
    485   // Reserved underscore names.
    486   static const FilePath::CharType* reserved_names[] = {
    487     Extension::kLocaleFolder,
    488     FILE_PATH_LITERAL("__MACOSX"),
    489   };
    490   static std::set<FilePath::StringType> reserved_underscore_names(
    491       reserved_names, reserved_names + arraysize(reserved_names));
    492 
    493   // Enumerate all files and directories in the extension root.
    494   // There is a problem when using pattern "_*" with FileEnumerator, so we have
    495   // to cheat with find_first_of and match all.
    496   file_util::FileEnumerator all_files(
    497     extension_path,
    498     false,
    499     static_cast<file_util::FileEnumerator::FILE_TYPE>(
    500         file_util::FileEnumerator::DIRECTORIES |
    501           file_util::FileEnumerator::FILES));
    502 
    503   FilePath file;
    504   while (!(file = all_files.Next()).empty()) {
    505     FilePath::StringType filename = file.BaseName().value();
    506     // Skip all that don't start with "_".
    507     if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue;
    508     if (reserved_underscore_names.find(filename) ==
    509         reserved_underscore_names.end()) {
    510       *error = base::StringPrintf(
    511           "Cannot load extension with file or directory name %s. "
    512           "Filenames starting with \"_\" are reserved for use by the system.",
    513           filename.c_str());
    514       return false;
    515     }
    516   }
    517 
    518   return true;
    519 }
    520 
    521 FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
    522   std::string url_path = url.path();
    523   if (url_path.empty() || url_path[0] != '/')
    524     return FilePath();
    525 
    526   // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
    527   std::string file_path = UnescapeURLComponent(url_path,
    528       UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
    529   size_t skip = file_path.find_first_not_of("/\\");
    530   if (skip != file_path.npos)
    531     file_path = file_path.substr(skip);
    532 
    533   FilePath path =
    534 #if defined(OS_POSIX)
    535     FilePath(file_path);
    536 #elif defined(OS_WIN)
    537     FilePath(UTF8ToWide(file_path));
    538 #else
    539     FilePath();
    540     NOTIMPLEMENTED();
    541 #endif
    542 
    543   // It's still possible for someone to construct an annoying URL whose path
    544   // would still wind up not being considered relative at this point.
    545   // For example: chrome-extension://id/c:////foo.html
    546   if (path.IsAbsolute())
    547     return FilePath();
    548 
    549   return path;
    550 }
    551 
    552 FilePath GetUserDataTempDir() {
    553   // We do file IO in this function, but only when the current profile's
    554   // Temp directory has never been used before, or in a rare error case.
    555   // Developers are not likely to see these situations often, so do an
    556   // explicit thread check.
    557   base::ThreadRestrictions::AssertIOAllowed();
    558 
    559   // Getting chrome::DIR_USER_DATA_TEMP is failing.  Use histogram to see why.
    560   // TODO(skerner): Fix the problem, and remove this code.  crbug.com/70056
    561   enum DirectoryCreationResult {
    562     SUCCESS = 0,
    563 
    564     CANT_GET_PARENT_PATH,
    565     CANT_GET_UDT_PATH,
    566     NOT_A_DIRECTORY,
    567     CANT_CREATE_DIR,
    568     CANT_WRITE_TO_PATH,
    569 
    570     UNSET,
    571     NUM_DIRECTORY_CREATION_RESULTS
    572   };
    573 
    574   // All paths should set |result|.
    575   DirectoryCreationResult result = UNSET;
    576 
    577   FilePath temp_path;
    578   if (!PathService::Get(chrome::DIR_USER_DATA_TEMP, &temp_path)) {
    579     FilePath parent_path;
    580     if (!PathService::Get(chrome::DIR_USER_DATA, &parent_path))
    581       result = CANT_GET_PARENT_PATH;
    582     else
    583       result = CANT_GET_UDT_PATH;
    584 
    585   } else if (file_util::PathExists(temp_path)) {
    586 
    587     // Path exists.  Check that it is a directory we can write to.
    588     if (!file_util::DirectoryExists(temp_path)) {
    589       result = NOT_A_DIRECTORY;
    590 
    591     } else if (!file_util::PathIsWritable(temp_path)) {
    592       result = CANT_WRITE_TO_PATH;
    593 
    594     } else {
    595       // Temp is a writable directory.
    596       result = SUCCESS;
    597     }
    598 
    599   } else if (!file_util::CreateDirectory(temp_path)) {
    600     // Path doesn't exist, and we failed to create it.
    601     result = CANT_CREATE_DIR;
    602 
    603   } else {
    604     // Successfully created the Temp directory.
    605     result = SUCCESS;
    606   }
    607 
    608   UMA_HISTOGRAM_ENUMERATION("Extensions.GetUserDataTempDir",
    609                             result,
    610                             NUM_DIRECTORY_CREATION_RESULTS);
    611 
    612   if (result == SUCCESS)
    613     return temp_path;
    614 
    615   return FilePath();
    616 }
    617 
    618 void DeleteFile(const FilePath& path, bool recursive) {
    619   file_util::Delete(path, recursive);
    620 }
    621 
    622 }  // namespace extension_file_util
    623