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