910e62b5创建于 1月15日历史提交
// 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