blob: dacfa73e600e7437778d7057ab099612db88267c [file] [log] [blame]
// Copyright 2023 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/api/scripts_internal/script_serialization.h"
#include <optional>
#include "base/strings/string_util.h"
#include "base/types/optional_util.h"
#include "extensions/common/api/scripts_internal.h"
#include "extensions/common/user_script.h"
#include "extensions/common/utils/content_script_utils.h"
#include "extensions/common/utils/extension_types_utils.h"
namespace extensions::script_serialization {
std::vector<api::scripts_internal::ScriptSource> GetSourcesFromFileNames(
std::vector<std::string> file_names) {
std::vector<api::scripts_internal::ScriptSource> script_sources;
script_sources.reserve(file_names.size());
for (auto& file : file_names) {
api::scripts_internal::ScriptSource script_source;
script_source.file = std::move(file);
script_sources.push_back(std::move(script_source));
}
return script_sources;
}
api::scripts_internal::SerializedUserScript SerializeUserScript(
const UserScript& user_script) {
api::scripts_internal::SerializedUserScript serialized_script;
// `allFrames`.
serialized_script.all_frames = user_script.match_all_frames();
// `css`.
if (!user_script.css_scripts().empty()) {
serialized_script.css.emplace();
serialized_script.css->reserve(user_script.css_scripts().size());
for (const auto& css_script : user_script.css_scripts()) {
// TODO(crbug.com/40061759): Handle `code`.
api::scripts_internal::ScriptSource source;
source.file = css_script->relative_path().AsUTF8Unsafe();
serialized_script.css->push_back(std::move(source));
}
}
// `excludeMatches`.
if (!user_script.exclude_url_patterns().is_empty()) {
serialized_script.exclude_matches.emplace();
serialized_script.exclude_matches->reserve(
user_script.exclude_url_patterns().size());
for (const URLPattern& pattern : user_script.exclude_url_patterns()) {
serialized_script.exclude_matches->push_back(pattern.GetAsString());
}
}
// `excludeGlobs`.
if (!user_script.exclude_globs().empty()) {
serialized_script.exclude_globs.emplace();
serialized_script.exclude_globs->reserve(
user_script.exclude_globs().size());
for (const std::string& exclude_glob : user_script.exclude_globs()) {
serialized_script.exclude_globs->push_back(exclude_glob);
}
}
// `id`.
serialized_script.id = user_script.id();
// `includeGlobs`.
if (!user_script.globs().empty()) {
serialized_script.include_globs.emplace();
serialized_script.include_globs->reserve(user_script.globs().size());
for (const std::string& glob : user_script.globs()) {
serialized_script.include_globs->push_back(glob);
}
}
// `js`.
if (!user_script.js_scripts().empty()) {
serialized_script.js.emplace();
serialized_script.js->reserve(user_script.js_scripts().size());
for (const auto& js_script : user_script.js_scripts()) {
api::scripts_internal::ScriptSource source;
switch (js_script->source()) {
case UserScript::Content::Source::kFile:
source.file = js_script->relative_path().AsUTF8Unsafe();
break;
case UserScript::Content::Source::kInlineCode:
// Inline code is only serialized for user scripts.
CHECK_EQ(user_script.GetSource(),
UserScript::Source::kDynamicUserScript);
source.code = js_script->GetContent();
break;
}
serialized_script.js->push_back(std::move(source));
}
}
// `matches`.
serialized_script.matches.reserve(user_script.url_patterns().size());
for (const URLPattern& pattern : user_script.url_patterns()) {
serialized_script.matches.push_back(pattern.GetAsString());
}
// `matchOriginAsFallback`.
serialized_script.match_origin_as_fallback =
user_script.match_origin_as_fallback() ==
mojom::MatchOriginAsFallbackBehavior::kAlways;
// `runAt`.
serialized_script.run_at =
ConvertRunLocationForAPI(user_script.run_location());
// `source`.
auto source_to_serialized_source = [](UserScript::Source source) {
switch (source) {
case UserScript::Source::kDynamicContentScript:
return api::scripts_internal::Source::kDynamicContentScript;
case UserScript::Source::kDynamicUserScript:
return api::scripts_internal::Source::kDynamicUserScript;
case UserScript::Source::kStaticContentScript:
case UserScript::Source::kWebUIScript:
// We shouldn't be serialized these script types, ever.
NOTREACHED();
}
};
serialized_script.source =
source_to_serialized_source(user_script.GetSource());
// `world`.
serialized_script.world =
ConvertExecutionWorldForAPI(user_script.execution_world());
// `worldId`.
serialized_script.world_id = user_script.world_id();
return serialized_script;
}
std::unique_ptr<UserScript> ParseSerializedUserScript(
const api::scripts_internal::SerializedUserScript& serialized_script,
const Extension& extension,
bool allowed_in_incognito,
std::u16string* error_out,
bool* wants_file_access_out,
SerializedUserScriptParseOptions parse_options) {
bool source_matches_id = true;
switch (serialized_script.source) {
case api::scripts_internal::Source::kDynamicContentScript:
source_matches_id = base::StartsWith(
serialized_script.id, UserScript::kDynamicContentScriptPrefix);
break;
case api::scripts_internal::Source::kDynamicUserScript:
source_matches_id = base::StartsWith(
serialized_script.id, UserScript::kDynamicUserScriptPrefix);
break;
case api::scripts_internal::Source::kManifestContentScript:
source_matches_id = base::StartsWith(
serialized_script.id, UserScript::kManifestContentScriptPrefix);
break;
case api::scripts_internal::Source::kNone:
NOTREACHED(); // This should have been caught by our parsing.
}
if (!source_matches_id) {
return nullptr;
}
auto user_script = std::make_unique<UserScript>();
user_script->set_host_id(
mojom::HostID(mojom::HostID::HostType::kExtensions, extension.id()));
// Note: `id` must be set here since error messages from the helper methods
// use the script ID in their output.
user_script->set_id(serialized_script.id);
std::u16string error;
if (!error_out) {
error_out = &error;
}
// `allFrames`.
if (serialized_script.all_frames) {
user_script->set_match_all_frames(*serialized_script.all_frames);
}
// `css`/`js`.
if (!script_parsing::ParseFileSources(
&extension, base::OptionalToPtr(serialized_script.js),
base::OptionalToPtr(serialized_script.css),
parse_options.index_for_error, user_script.get(), error_out)) {
return nullptr;
}
// `excludeMatches`/`matches`.
if (!script_parsing::ParseMatchPatterns(
serialized_script.matches,
base::OptionalToPtr(serialized_script.exclude_matches),
extension.creation_flags(),
parse_options.can_execute_script_everywhere,
parse_options.all_urls_includes_chrome_urls,
parse_options.index_for_error, user_script.get(), error_out,
wants_file_access_out)) {
return nullptr;
}
// `excludeGlobs`/`includeGlobs`.
script_parsing::ParseGlobs(
base::OptionalToPtr(serialized_script.include_globs),
base::OptionalToPtr(serialized_script.exclude_globs), user_script.get());
// Note: `id` was handled above.
// `matchOriginsAsFallback`.
if (serialized_script.match_origin_as_fallback.has_value()) {
user_script->set_match_origin_as_fallback(
*serialized_script.match_origin_as_fallback
? mojom::MatchOriginAsFallbackBehavior::kAlways
: mojom::MatchOriginAsFallbackBehavior::kNever);
}
// `runAt`.
user_script->set_run_location(ConvertRunLocation(serialized_script.run_at));
// Note: `source` is implicitly handled through setting the ID.
// `world`.
user_script->set_execution_world(
ConvertExecutionWorld(serialized_script.world));
// `worldId`.
user_script->set_world_id(serialized_script.world_id);
// Post-parse validation (these rely on multiple fields).
if (!script_parsing::ValidateMatchOriginAsFallback(
user_script->match_origin_as_fallback(), user_script->url_patterns(),
error_out)) {
return nullptr;
}
user_script->set_incognito_enabled(allowed_in_incognito);
return user_script;
}
} // namespace extensions::script_serialization