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