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