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