| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/common/manifest_handlers/content_scripts_handler.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/lazy_instance.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/types/optional_util.h" |
| #include "extensions/common/api/content_scripts.h" |
| #include "extensions/common/api/extension_types.h" |
| #include "extensions/common/api/scripts_internal.h" |
| #include "extensions/common/api/scripts_internal/script_serialization.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/mojom/host_id.mojom.h" |
| #include "extensions/common/mojom/match_origin_as_fallback.mojom-shared.h" |
| #include "extensions/common/mojom/run_location.mojom-shared.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/common/url_pattern.h" |
| #include "extensions/common/url_pattern_set.h" |
| #include "extensions/common/utils/content_script_utils.h" |
| #include "extensions/common/utils/extension_types_utils.h" |
| #include "url/gurl.h" |
| |
| namespace extensions { |
| |
| namespace errors = manifest_errors; |
| namespace content_scripts_api = api::content_scripts; |
| using ContentScriptsKeys = content_scripts_api::ManifestKeys; |
| |
| namespace { |
| |
| // Helper method that converts a parsed ContentScript object into a UserScript |
| // object. |
| std::unique_ptr<UserScript> CreateUserScript( |
| content_scripts_api::ContentScript content_script, |
| int definition_index, |
| bool can_execute_script_everywhere, |
| bool all_urls_includes_chrome_urls, |
| Extension* extension, |
| std::u16string* error) { |
| // We first convert to a `SerializedScript` to then convert that to a |
| // `UserScript` through shared logic. We need a bit of custom handling for |
| // match_origin_as_fallback, since manifest content scripts support |
| // "match_about_blank". |
| api::scripts_internal::SerializedUserScript serialized_script; |
| serialized_script.source = |
| api::scripts_internal::Source::kManifestContentScript; |
| serialized_script.id = UserScript::GenerateUserScriptID(); |
| |
| serialized_script.matches = std::move(content_script.matches); |
| serialized_script.exclude_matches = std::move(content_script.exclude_matches); |
| if (content_script.css) { |
| serialized_script.css = script_serialization::GetSourcesFromFileNames( |
| std::move(*content_script.css)); |
| } |
| if (content_script.js) { |
| serialized_script.js = script_serialization::GetSourcesFromFileNames( |
| std::move(*content_script.js)); |
| } |
| serialized_script.all_frames = content_script.all_frames; |
| |
| // match_origin_as_fallback and match_about_blank. |
| // Note: `match_about_blank` is ignored if `match_origin_as_fallback` was |
| // specified. |
| if (content_script.match_origin_as_fallback) { |
| serialized_script.match_origin_as_fallback = |
| content_script.match_origin_as_fallback; |
| } |
| // Manifest content scripts support `match_about_blank` (unlike |
| // `SerializedUserScript`). If `match_about_blank` is specified, we'll |
| // override the `match_origin_as_fallback` behavior on the user script later. |
| std::optional<mojom::MatchOriginAsFallbackBehavior> |
| match_origin_as_fallback_override; |
| if (!serialized_script.match_origin_as_fallback.has_value() && |
| content_script.match_about_blank && *content_script.match_about_blank) { |
| match_origin_as_fallback_override = |
| mojom::MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree; |
| } |
| |
| serialized_script.include_globs = std::move(content_script.include_globs); |
| serialized_script.exclude_globs = std::move(content_script.exclude_globs); |
| serialized_script.run_at = content_script.run_at; |
| |
| // Parse execution world. This should only be possible for MV3. |
| if (content_script.world != api::extension_types::ExecutionWorld::kNone) { |
| if (extension->manifest_version() >= 3) { |
| serialized_script.world = content_script.world; |
| } else { |
| extension->AddInstallWarning( |
| InstallWarning(errors::kExecutionWorldRestrictedToMV3, |
| ContentScriptsKeys::kContentScripts)); |
| } |
| } |
| |
| // At this point, no script is allowed in incognito. If the extension is |
| // allowed to run in incognito, this will be updated when loading the |
| // script content. |
| const bool allowed_in_incognito = false; |
| bool wants_file_access = false; |
| |
| script_serialization::SerializedUserScriptParseOptions parse_options; |
| parse_options.index_for_error = definition_index; |
| parse_options.can_execute_script_everywhere = can_execute_script_everywhere; |
| parse_options.all_urls_includes_chrome_urls = all_urls_includes_chrome_urls; |
| |
| std::unique_ptr<UserScript> user_script = |
| script_serialization::ParseSerializedUserScript( |
| serialized_script, *extension, allowed_in_incognito, error, |
| &wants_file_access, parse_options); |
| |
| if (!user_script) { |
| // Parsing failed. `error` should be properly populated. |
| return nullptr; |
| } |
| |
| if (match_origin_as_fallback_override) { |
| // Note: No need to call `ValidateMatchOriginAsFallback()` since this |
| // override is restricted to `kMatchForAboutSchemeAndClimbTree`, which |
| // doesn't require validation. |
| user_script->set_match_origin_as_fallback( |
| *match_origin_as_fallback_override); |
| } |
| |
| // Note: Not just `extension->set_wants_file_access(wants_file_access);` to |
| // avoid overwriting a previous `true` value. |
| if (wants_file_access) { |
| extension->set_wants_file_access(true); |
| } |
| |
| return user_script; |
| } |
| |
| struct EmptyUserScriptList { |
| UserScriptList user_script_list; |
| }; |
| |
| static base::LazyInstance<EmptyUserScriptList>::DestructorAtExit |
| g_empty_script_list = LAZY_INSTANCE_INITIALIZER; |
| |
| } // namespace |
| |
| ContentScriptsInfo::ContentScriptsInfo() = default; |
| |
| ContentScriptsInfo::~ContentScriptsInfo() = default; |
| |
| // static |
| const UserScriptList& ContentScriptsInfo::GetContentScripts( |
| const Extension* extension) { |
| ContentScriptsInfo* info = static_cast<ContentScriptsInfo*>( |
| extension->GetManifestData(ContentScriptsKeys::kContentScripts)); |
| return info ? info->content_scripts |
| : g_empty_script_list.Get().user_script_list; |
| } |
| |
| // static |
| bool ContentScriptsInfo::ExtensionHasScriptAtURL(const Extension* extension, |
| const GURL& url) { |
| for (const std::unique_ptr<UserScript>& script : |
| GetContentScripts(extension)) { |
| if (script->MatchesURL(url)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // static |
| URLPatternSet ContentScriptsInfo::GetScriptableHosts( |
| const Extension* extension) { |
| URLPatternSet scriptable_hosts; |
| for (const std::unique_ptr<UserScript>& script : |
| GetContentScripts(extension)) { |
| for (const URLPattern& pattern : script->url_patterns()) { |
| scriptable_hosts.AddPattern(pattern); |
| } |
| } |
| return scriptable_hosts; |
| } |
| |
| ContentScriptsHandler::ContentScriptsHandler() = default; |
| |
| ContentScriptsHandler::~ContentScriptsHandler() = default; |
| |
| base::span<const char* const> ContentScriptsHandler::Keys() const { |
| static constexpr const char* kKeys[] = {ContentScriptsKeys::kContentScripts}; |
| return kKeys; |
| } |
| |
| bool ContentScriptsHandler::Parse(Extension* extension, std::u16string* error) { |
| ContentScriptsKeys manifest_keys; |
| if (!ContentScriptsKeys::ParseFromDictionary( |
| extension->manifest()->available_values(), manifest_keys, *error)) { |
| return false; |
| } |
| |
| auto content_scripts_info = std::make_unique<ContentScriptsInfo>(); |
| |
| const bool can_execute_script_everywhere = |
| PermissionsData::CanExecuteScriptEverywhere(extension->id(), |
| extension->location()); |
| const bool all_urls_includes_chrome_urls = |
| PermissionsData::AllUrlsIncludesChromeUrls(extension->id()); |
| for (size_t i = 0; i < manifest_keys.content_scripts.size(); ++i) { |
| std::unique_ptr<UserScript> user_script = |
| CreateUserScript(std::move(manifest_keys.content_scripts[i]), i, |
| can_execute_script_everywhere, |
| all_urls_includes_chrome_urls, extension, error); |
| if (!user_script) { |
| return false; // Failed to parse script context definition. |
| } |
| |
| std::string mime_type_error; |
| if (!script_parsing::ValidateUserScriptMimeTypesFromFileExtensions( |
| *user_script, &mime_type_error)) { |
| // Issue a warning and ignore this file. This is a warning and not a |
| // hard-error to preserve both backwards compatibility and potential |
| // future-compatibility if mime types change. |
| extension->AddInstallWarning(InstallWarning( |
| base::StringPrintf(manifest_errors::kInvalidUserScriptMimeType, |
| mime_type_error.c_str()), |
| ContentScriptsKeys::kContentScripts)); |
| continue; |
| } |
| |
| user_script->set_host_id( |
| mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id())); |
| if (extension->converted_from_user_script()) { |
| user_script->set_emulate_greasemonkey(true); |
| // Greasemonkey matches all frames. |
| user_script->set_match_all_frames(true); |
| } |
| content_scripts_info->content_scripts.push_back(std::move(user_script)); |
| } |
| |
| extension->SetManifestData(ContentScriptsKeys::kContentScripts, |
| std::move(content_scripts_info)); |
| PermissionsParser::SetScriptableHosts( |
| extension, ContentScriptsInfo::GetScriptableHosts(extension)); |
| return true; |
| } |
| |
| bool ContentScriptsHandler::Validate( |
| const Extension& extension, |
| std::string* error, |
| std::vector<InstallWarning>* warnings) const { |
| // Validate that claimed script resources actually exist, |
| // and are UTF-8 encoded. |
| return script_parsing::ValidateFileSources( |
| ContentScriptsInfo::GetContentScripts(&extension), |
| script_parsing::GetSymlinkPolicy(&extension), error, warnings); |
| } |
| |
| } // namespace extensions |