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