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