Home | History | Annotate | Download | only in manifest_handlers
      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/manifest_handlers/content_scripts_handler.h"
      6 
      7 #include "base/files/file_util.h"
      8 #include "base/lazy_instance.h"
      9 #include "base/memory/scoped_ptr.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "base/values.h"
     14 #include "chrome/grit/generated_resources.h"
     15 #include "content/public/common/url_constants.h"
     16 #include "extensions/common/error_utils.h"
     17 #include "extensions/common/extension.h"
     18 #include "extensions/common/extension_resource.h"
     19 #include "extensions/common/manifest_constants.h"
     20 #include "extensions/common/manifest_handlers/permissions_parser.h"
     21 #include "extensions/common/permissions/permissions_data.h"
     22 #include "extensions/common/url_pattern.h"
     23 #include "extensions/common/url_pattern_set.h"
     24 #include "ui/base/l10n/l10n_util.h"
     25 #include "url/gurl.h"
     26 
     27 namespace extensions {
     28 
     29 namespace keys = extensions::manifest_keys;
     30 namespace values = manifest_values;
     31 namespace errors = manifest_errors;
     32 
     33 namespace {
     34 
     35 // Helper method that loads either the include_globs or exclude_globs list
     36 // from an entry in the content_script lists of the manifest.
     37 bool LoadGlobsHelper(const base::DictionaryValue* content_script,
     38                      int content_script_index,
     39                      const char* globs_property_name,
     40                      base::string16* error,
     41                      void(UserScript::*add_method)(const std::string& glob),
     42                      UserScript* instance) {
     43   if (!content_script->HasKey(globs_property_name))
     44     return true;  // they are optional
     45 
     46   const base::ListValue* list = NULL;
     47   if (!content_script->GetList(globs_property_name, &list)) {
     48     *error = ErrorUtils::FormatErrorMessageUTF16(
     49         errors::kInvalidGlobList,
     50         base::IntToString(content_script_index),
     51         globs_property_name);
     52     return false;
     53   }
     54 
     55   for (size_t i = 0; i < list->GetSize(); ++i) {
     56     std::string glob;
     57     if (!list->GetString(i, &glob)) {
     58       *error = ErrorUtils::FormatErrorMessageUTF16(
     59           errors::kInvalidGlob,
     60           base::IntToString(content_script_index),
     61           globs_property_name,
     62           base::IntToString(i));
     63       return false;
     64     }
     65 
     66     (instance->*add_method)(glob);
     67   }
     68 
     69   return true;
     70 }
     71 
     72 // Helper method that loads a UserScript object from a dictionary in the
     73 // content_script list of the manifest.
     74 bool LoadUserScriptFromDictionary(const base::DictionaryValue* content_script,
     75                                   int definition_index,
     76                                   Extension* extension,
     77                                   base::string16* error,
     78                                   UserScript* result) {
     79   // run_at
     80   if (content_script->HasKey(keys::kRunAt)) {
     81     std::string run_location;
     82     if (!content_script->GetString(keys::kRunAt, &run_location)) {
     83       *error = ErrorUtils::FormatErrorMessageUTF16(
     84           errors::kInvalidRunAt,
     85           base::IntToString(definition_index));
     86       return false;
     87     }
     88 
     89     if (run_location == values::kRunAtDocumentStart) {
     90       result->set_run_location(UserScript::DOCUMENT_START);
     91     } else if (run_location == values::kRunAtDocumentEnd) {
     92       result->set_run_location(UserScript::DOCUMENT_END);
     93     } else if (run_location == values::kRunAtDocumentIdle) {
     94       result->set_run_location(UserScript::DOCUMENT_IDLE);
     95     } else {
     96       *error = ErrorUtils::FormatErrorMessageUTF16(
     97           errors::kInvalidRunAt,
     98           base::IntToString(definition_index));
     99       return false;
    100     }
    101   }
    102 
    103   // all frames
    104   if (content_script->HasKey(keys::kAllFrames)) {
    105     bool all_frames = false;
    106     if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
    107       *error = ErrorUtils::FormatErrorMessageUTF16(
    108             errors::kInvalidAllFrames, base::IntToString(definition_index));
    109       return false;
    110     }
    111     result->set_match_all_frames(all_frames);
    112   }
    113 
    114   // match about blank
    115   if (content_script->HasKey(keys::kMatchAboutBlank)) {
    116     bool match_about_blank = false;
    117     if (!content_script->GetBoolean(keys::kMatchAboutBlank,
    118                                     &match_about_blank)) {
    119       *error = ErrorUtils::FormatErrorMessageUTF16(
    120           errors::kInvalidMatchAboutBlank, base::IntToString(definition_index));
    121       return false;
    122     }
    123     result->set_match_about_blank(match_about_blank);
    124   }
    125 
    126   // matches (required)
    127   const base::ListValue* matches = NULL;
    128   if (!content_script->GetList(keys::kMatches, &matches)) {
    129     *error = ErrorUtils::FormatErrorMessageUTF16(
    130         errors::kInvalidMatches,
    131         base::IntToString(definition_index));
    132     return false;
    133   }
    134 
    135   if (matches->GetSize() == 0) {
    136     *error = ErrorUtils::FormatErrorMessageUTF16(
    137         errors::kInvalidMatchCount,
    138         base::IntToString(definition_index));
    139     return false;
    140   }
    141   for (size_t j = 0; j < matches->GetSize(); ++j) {
    142     std::string match_str;
    143     if (!matches->GetString(j, &match_str)) {
    144       *error = ErrorUtils::FormatErrorMessageUTF16(
    145           errors::kInvalidMatch,
    146           base::IntToString(definition_index),
    147           base::IntToString(j),
    148           errors::kExpectString);
    149       return false;
    150     }
    151 
    152     URLPattern pattern(UserScript::ValidUserScriptSchemes(
    153         PermissionsData::CanExecuteScriptEverywhere(extension)));
    154 
    155     URLPattern::ParseResult parse_result = pattern.Parse(match_str);
    156     if (parse_result != URLPattern::PARSE_SUCCESS) {
    157       *error = ErrorUtils::FormatErrorMessageUTF16(
    158           errors::kInvalidMatch,
    159           base::IntToString(definition_index),
    160           base::IntToString(j),
    161           URLPattern::GetParseResultString(parse_result));
    162       return false;
    163     }
    164 
    165     // TODO(aboxhall): check for webstore
    166     if (!PermissionsData::CanExecuteScriptEverywhere(extension) &&
    167         pattern.scheme() != content::kChromeUIScheme) {
    168       // Exclude SCHEME_CHROMEUI unless it's been explicitly requested.
    169       // If the --extensions-on-chrome-urls flag has not been passed, requesting
    170       // a chrome:// url will cause a parse failure above, so there's no need to
    171       // check the flag here.
    172       pattern.SetValidSchemes(
    173           pattern.valid_schemes() & ~URLPattern::SCHEME_CHROMEUI);
    174     }
    175 
    176     if (pattern.MatchesScheme(url::kFileScheme) &&
    177         !PermissionsData::CanExecuteScriptEverywhere(extension)) {
    178       extension->set_wants_file_access(true);
    179       if (!(extension->creation_flags() & Extension::ALLOW_FILE_ACCESS)) {
    180         pattern.SetValidSchemes(
    181             pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
    182       }
    183     }
    184 
    185     result->add_url_pattern(pattern);
    186   }
    187 
    188   // exclude_matches
    189   if (content_script->HasKey(keys::kExcludeMatches)) {  // optional
    190     const base::ListValue* exclude_matches = NULL;
    191     if (!content_script->GetList(keys::kExcludeMatches, &exclude_matches)) {
    192       *error = ErrorUtils::FormatErrorMessageUTF16(
    193           errors::kInvalidExcludeMatches,
    194           base::IntToString(definition_index));
    195       return false;
    196     }
    197 
    198     for (size_t j = 0; j < exclude_matches->GetSize(); ++j) {
    199       std::string match_str;
    200       if (!exclude_matches->GetString(j, &match_str)) {
    201         *error = ErrorUtils::FormatErrorMessageUTF16(
    202             errors::kInvalidExcludeMatch,
    203             base::IntToString(definition_index),
    204             base::IntToString(j),
    205             errors::kExpectString);
    206         return false;
    207       }
    208 
    209       int valid_schemes = UserScript::ValidUserScriptSchemes(
    210           PermissionsData::CanExecuteScriptEverywhere(extension));
    211       URLPattern pattern(valid_schemes);
    212 
    213       URLPattern::ParseResult parse_result = pattern.Parse(match_str);
    214       if (parse_result != URLPattern::PARSE_SUCCESS) {
    215         *error = ErrorUtils::FormatErrorMessageUTF16(
    216             errors::kInvalidExcludeMatch,
    217             base::IntToString(definition_index), base::IntToString(j),
    218             URLPattern::GetParseResultString(parse_result));
    219         return false;
    220       }
    221 
    222       result->add_exclude_url_pattern(pattern);
    223     }
    224   }
    225 
    226   // include/exclude globs (mostly for Greasemonkey compatibility)
    227   if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
    228                        error, &UserScript::add_glob, result)) {
    229       return false;
    230   }
    231 
    232   if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
    233                        error, &UserScript::add_exclude_glob, result)) {
    234       return false;
    235   }
    236 
    237   // js and css keys
    238   const base::ListValue* js = NULL;
    239   if (content_script->HasKey(keys::kJs) &&
    240       !content_script->GetList(keys::kJs, &js)) {
    241     *error = ErrorUtils::FormatErrorMessageUTF16(
    242         errors::kInvalidJsList,
    243         base::IntToString(definition_index));
    244     return false;
    245   }
    246 
    247   const base::ListValue* css = NULL;
    248   if (content_script->HasKey(keys::kCss) &&
    249       !content_script->GetList(keys::kCss, &css)) {
    250     *error = ErrorUtils::
    251         FormatErrorMessageUTF16(errors::kInvalidCssList,
    252         base::IntToString(definition_index));
    253     return false;
    254   }
    255 
    256   // The manifest needs to have at least one js or css user script definition.
    257   if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
    258     *error = ErrorUtils::FormatErrorMessageUTF16(
    259         errors::kMissingFile,
    260         base::IntToString(definition_index));
    261     return false;
    262   }
    263 
    264   if (js) {
    265     for (size_t script_index = 0; script_index < js->GetSize();
    266          ++script_index) {
    267       const base::Value* value;
    268       std::string relative;
    269       if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
    270         *error = ErrorUtils::FormatErrorMessageUTF16(
    271             errors::kInvalidJs,
    272             base::IntToString(definition_index),
    273             base::IntToString(script_index));
    274         return false;
    275       }
    276       GURL url = extension->GetResourceURL(relative);
    277       ExtensionResource resource = extension->GetResource(relative);
    278       result->js_scripts().push_back(UserScript::File(
    279           resource.extension_root(), resource.relative_path(), url));
    280     }
    281   }
    282 
    283   if (css) {
    284     for (size_t script_index = 0; script_index < css->GetSize();
    285          ++script_index) {
    286       const base::Value* value;
    287       std::string relative;
    288       if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
    289         *error = ErrorUtils::FormatErrorMessageUTF16(
    290             errors::kInvalidCss,
    291             base::IntToString(definition_index),
    292             base::IntToString(script_index));
    293         return false;
    294       }
    295       GURL url = extension->GetResourceURL(relative);
    296       ExtensionResource resource = extension->GetResource(relative);
    297       result->css_scripts().push_back(UserScript::File(
    298           resource.extension_root(), resource.relative_path(), url));
    299     }
    300   }
    301 
    302   return true;
    303 }
    304 
    305 // Returns false and sets the error if script file can't be loaded,
    306 // or if it's not UTF-8 encoded.
    307 static bool IsScriptValid(const base::FilePath& path,
    308                           const base::FilePath& relative_path,
    309                           int message_id,
    310                           std::string* error) {
    311   std::string content;
    312   if (!base::PathExists(path) ||
    313       !base::ReadFileToString(path, &content)) {
    314     *error = l10n_util::GetStringFUTF8(
    315         message_id,
    316         relative_path.LossyDisplayName());
    317     return false;
    318   }
    319 
    320   if (!base::IsStringUTF8(content)) {
    321     *error = l10n_util::GetStringFUTF8(
    322         IDS_EXTENSION_BAD_FILE_ENCODING,
    323         relative_path.LossyDisplayName());
    324     return false;
    325   }
    326 
    327   return true;
    328 }
    329 
    330 struct EmptyUserScriptList {
    331   UserScriptList user_script_list;
    332 };
    333 
    334 static base::LazyInstance<EmptyUserScriptList> g_empty_script_list =
    335     LAZY_INSTANCE_INITIALIZER;
    336 
    337 }  // namespace
    338 
    339 ContentScriptsInfo::ContentScriptsInfo() {
    340 }
    341 
    342 ContentScriptsInfo::~ContentScriptsInfo() {
    343 }
    344 
    345 // static
    346 const UserScriptList& ContentScriptsInfo::GetContentScripts(
    347     const Extension* extension) {
    348   ContentScriptsInfo* info = static_cast<ContentScriptsInfo*>(
    349       extension->GetManifestData(keys::kContentScripts));
    350   return info ? info->content_scripts
    351               : g_empty_script_list.Get().user_script_list;
    352 }
    353 
    354 // static
    355 bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension* extension,
    356                                                  const GURL& url) {
    357   const UserScriptList& content_scripts = GetContentScripts(extension);
    358   for (UserScriptList::const_iterator iter = content_scripts.begin();
    359       iter != content_scripts.end(); ++iter) {
    360     if (iter->MatchesURL(url))
    361       return true;
    362   }
    363   return false;
    364 }
    365 
    366 // static
    367 URLPatternSet ContentScriptsInfo::GetScriptableHosts(
    368     const Extension* extension) {
    369   const UserScriptList& content_scripts = GetContentScripts(extension);
    370   URLPatternSet scriptable_hosts;
    371   for (UserScriptList::const_iterator content_script =
    372            content_scripts.begin();
    373        content_script != content_scripts.end();
    374        ++content_script) {
    375     URLPatternSet::const_iterator pattern =
    376         content_script->url_patterns().begin();
    377     for (; pattern != content_script->url_patterns().end(); ++pattern)
    378       scriptable_hosts.AddPattern(*pattern);
    379   }
    380   return scriptable_hosts;
    381 }
    382 
    383 ContentScriptsHandler::ContentScriptsHandler() {
    384 }
    385 
    386 ContentScriptsHandler::~ContentScriptsHandler() {
    387 }
    388 
    389 const std::vector<std::string> ContentScriptsHandler::Keys() const {
    390   static const char* keys[] = {
    391     keys::kContentScripts
    392   };
    393   return std::vector<std::string>(keys, keys + arraysize(keys));
    394 }
    395 
    396 bool ContentScriptsHandler::Parse(Extension* extension, base::string16* error) {
    397   scoped_ptr<ContentScriptsInfo> content_scripts_info(new ContentScriptsInfo);
    398   const base::ListValue* scripts_list = NULL;
    399   if (!extension->manifest()->GetList(keys::kContentScripts, &scripts_list)) {
    400     *error = base::ASCIIToUTF16(errors::kInvalidContentScriptsList);
    401     return false;
    402   }
    403 
    404   for (size_t i = 0; i < scripts_list->GetSize(); ++i) {
    405     const base::DictionaryValue* script_dict = NULL;
    406     if (!scripts_list->GetDictionary(i, &script_dict)) {
    407       *error = ErrorUtils::FormatErrorMessageUTF16(
    408           errors::kInvalidContentScript,
    409           base::IntToString(i));
    410       return false;
    411     }
    412 
    413     UserScript user_script;
    414     if (!LoadUserScriptFromDictionary(script_dict,
    415                                       i,
    416                                       extension,
    417                                       error,
    418                                       &user_script)) {
    419       return false;  // Failed to parse script context definition.
    420     }
    421 
    422     user_script.set_extension_id(extension->id());
    423     if (extension->converted_from_user_script()) {
    424       user_script.set_emulate_greasemonkey(true);
    425       // Greasemonkey matches all frames.
    426       user_script.set_match_all_frames(true);
    427     }
    428     user_script.set_id(UserScript::GenerateUserScriptID());
    429     content_scripts_info->content_scripts.push_back(user_script);
    430   }
    431   extension->SetManifestData(keys::kContentScripts,
    432                              content_scripts_info.release());
    433   PermissionsParser::SetScriptableHosts(
    434       extension, ContentScriptsInfo::GetScriptableHosts(extension));
    435   return true;
    436 }
    437 
    438 bool ContentScriptsHandler::Validate(
    439     const Extension* extension,
    440     std::string* error,
    441     std::vector<InstallWarning>* warnings) const {
    442   // Validate that claimed script resources actually exist,
    443   // and are UTF-8 encoded.
    444   ExtensionResource::SymlinkPolicy symlink_policy;
    445   if ((extension->creation_flags() &
    446        Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0) {
    447     symlink_policy = ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE;
    448   } else {
    449     symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
    450   }
    451 
    452   const UserScriptList& content_scripts =
    453       ContentScriptsInfo::GetContentScripts(extension);
    454   for (size_t i = 0; i < content_scripts.size(); ++i) {
    455     const UserScript& script = content_scripts[i];
    456 
    457     for (size_t j = 0; j < script.js_scripts().size(); j++) {
    458       const UserScript::File& js_script = script.js_scripts()[j];
    459       const base::FilePath& path = ExtensionResource::GetFilePath(
    460           js_script.extension_root(), js_script.relative_path(),
    461           symlink_policy);
    462       if (!IsScriptValid(path, js_script.relative_path(),
    463                          IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
    464         return false;
    465     }
    466 
    467     for (size_t j = 0; j < script.css_scripts().size(); j++) {
    468       const UserScript::File& css_script = script.css_scripts()[j];
    469       const base::FilePath& path = ExtensionResource::GetFilePath(
    470           css_script.extension_root(), css_script.relative_path(),
    471           symlink_policy);
    472       if (!IsScriptValid(path, css_script.relative_path(),
    473                          IDS_EXTENSION_LOAD_CSS_FAILED, error))
    474         return false;
    475     }
    476   }
    477 
    478   return true;
    479 }
    480 
    481 }  // namespace extensions
    482