| // 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/extension_builder.h" |
| |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/json/json_reader.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/crx_file/id_util.h" |
| #include "extensions/common/api/content_scripts.h" |
| #include "extensions/common/api/extension_action/action_info.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest_constants.h" |
| |
| namespace extensions { |
| |
| constexpr char ExtensionBuilder::kServiceWorkerScriptFile[]; |
| |
| struct ExtensionBuilder::ManifestData { |
| Type type; |
| std::string name; |
| std::vector<std::string> permissions; |
| std::vector<std::string> optional_permissions; |
| std::optional<ActionInfo::Type> action; |
| std::optional<BackgroundContext> background_context; |
| std::optional<std::string> version; |
| std::optional<int> manifest_version; |
| |
| // A ContentScriptEntry includes a string name, and a vector of string |
| // match patterns. |
| using ContentScriptEntry = std::pair<std::string, std::vector<std::string>>; |
| std::vector<ContentScriptEntry> content_scripts; |
| |
| std::optional<base::Value::Dict> extra; |
| |
| base::Value::Dict GetValue() const { |
| auto manifest = |
| base::Value::Dict() |
| .Set(manifest_keys::kName, name) |
| .Set(manifest_keys::kManifestVersion, manifest_version.value_or(2)) |
| .Set(manifest_keys::kVersion, version.value_or("0.1")) |
| .Set(manifest_keys::kDescription, "some description"); |
| |
| switch (type) { |
| case Type::EXTENSION: |
| break; // Sufficient already. |
| case Type::PLATFORM_APP: { |
| base::Value::Dict background; |
| background.Set("scripts", base::Value::List().Append("test.js")); |
| manifest.Set("app", base::Value::Dict().Set("background", |
| std::move(background))); |
| break; |
| } |
| } |
| |
| if (!permissions.empty()) { |
| base::Value::List permissions_builder; |
| for (const std::string& permission : permissions) |
| permissions_builder.Append(permission); |
| manifest.Set(manifest_keys::kPermissions, std::move(permissions_builder)); |
| } |
| |
| if (!optional_permissions.empty()) { |
| base::Value::List permissions_builder; |
| for (const std::string& permission : optional_permissions) { |
| permissions_builder.Append(permission); |
| } |
| manifest.Set(manifest_keys::kOptionalPermissions, |
| std::move(permissions_builder)); |
| } |
| |
| if (action) { |
| const char* action_key = ActionInfo::GetManifestKeyForActionType(*action); |
| manifest.Set(action_key, base::Value(base::Value::Dict())); |
| } |
| |
| if (background_context) { |
| base::Value::Dict background; |
| std::optional<bool> persistent; |
| switch (*background_context) { |
| case BackgroundContext::BACKGROUND_PAGE: |
| background.Set("page", "background_page.html"); |
| persistent = true; |
| break; |
| case BackgroundContext::EVENT_PAGE: |
| background.Set("page", "background_page.html"); |
| persistent = false; |
| break; |
| case BackgroundContext::SERVICE_WORKER: |
| background.Set("service_worker", kServiceWorkerScriptFile); |
| break; |
| } |
| if (persistent) { |
| background.Set("persistent", *persistent); |
| } |
| manifest.Set("background", std::move(background)); |
| } |
| |
| if (!content_scripts.empty()) { |
| base::Value::List scripts_value; |
| scripts_value.reserve(content_scripts.size()); |
| for (const auto& [script_name, pattern_matches] : content_scripts) { |
| base::Value::List matches; |
| matches.reserve(pattern_matches.size()); |
| for (const auto& pattern_match : pattern_matches) { |
| matches.Append(pattern_match); |
| } |
| scripts_value.Append( |
| base::Value::Dict() |
| .Set(api::content_scripts::ContentScript::kJs, |
| base::Value::List().Append(script_name)) |
| .Set(api::content_scripts::ContentScript::kMatches, |
| std::move(matches))); |
| } |
| manifest.Set(api::content_scripts::ManifestKeys::kContentScripts, |
| std::move(scripts_value)); |
| } |
| |
| base::Value::Dict result = std::move(manifest); |
| if (extra) |
| result.Merge(extra->Clone()); |
| |
| return result; |
| } |
| |
| base::Value::Dict& get_extra() { |
| if (!extra) |
| extra.emplace(); |
| return *extra; |
| } |
| }; |
| |
| ExtensionBuilder::ExtensionBuilder() |
| : location_(mojom::ManifestLocation::kUnpacked), |
| flags_(Extension::NO_FLAGS) {} |
| |
| ExtensionBuilder::ExtensionBuilder(const std::string& name, Type type) |
| : ExtensionBuilder() { |
| manifest_data_ = std::make_unique<ManifestData>(); |
| manifest_data_->name = name; |
| manifest_data_->type = type; |
| } |
| |
| ExtensionBuilder::~ExtensionBuilder() = default; |
| |
| ExtensionBuilder::ExtensionBuilder(ExtensionBuilder&& other) = default; |
| ExtensionBuilder& ExtensionBuilder::operator=(ExtensionBuilder&& other) = |
| default; |
| |
| scoped_refptr<const Extension> ExtensionBuilder::Build() { |
| CHECK(manifest_data_ || manifest_value_); |
| |
| if (id_.empty() && manifest_data_) |
| id_ = crx_file::id_util::GenerateId(manifest_data_->name); |
| |
| std::string error; |
| |
| // This allows `*manifest_value` to be passed as a reference instead of |
| // needing to be cloned. |
| std::optional<base::Value::Dict> manifest_data_value; |
| if (manifest_data_) { |
| manifest_data_value = manifest_data_->GetValue(); |
| } |
| scoped_refptr<const Extension> extension = Extension::Create( |
| path_, location_, |
| manifest_data_value ? *manifest_data_value : *manifest_value_, flags_, |
| id_, &error); |
| |
| CHECK(error.empty()) << error; |
| CHECK(extension); |
| |
| return extension; |
| } |
| |
| base::Value ExtensionBuilder::BuildManifest() { |
| CHECK(manifest_data_ || manifest_value_); |
| return base::Value(manifest_data_ ? manifest_data_->GetValue() |
| : manifest_value_->Clone()); |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddPermission( |
| const std::string& permission) { |
| CHECK(manifest_data_); |
| manifest_data_->permissions.push_back(permission); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddPermissions( |
| const std::vector<std::string>& permissions) { |
| CHECK(manifest_data_); |
| manifest_data_->permissions.insert(manifest_data_->permissions.end(), |
| permissions.begin(), permissions.end()); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddOptionalPermission( |
| const std::string& permission) { |
| CHECK(manifest_data_); |
| manifest_data_->optional_permissions.push_back(permission); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddOptionalPermissions( |
| const std::vector<std::string>& permissions) { |
| CHECK(manifest_data_); |
| manifest_data_->optional_permissions.insert( |
| manifest_data_->optional_permissions.end(), permissions.begin(), |
| permissions.end()); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetAction(ActionInfo::Type type) { |
| CHECK(manifest_data_); |
| manifest_data_->action = type; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetBackgroundContext( |
| BackgroundContext background_context) { |
| CHECK(manifest_data_); |
| manifest_data_->background_context = background_context; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddContentScript( |
| const std::string& script_name, |
| const std::vector<std::string>& match_patterns) { |
| CHECK(manifest_data_); |
| manifest_data_->content_scripts.emplace_back(script_name, match_patterns); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetVersion(const std::string& version) { |
| CHECK(manifest_data_); |
| manifest_data_->version = version; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetManifestVersion(int manifest_version) { |
| CHECK(manifest_data_); |
| manifest_data_->manifest_version = manifest_version; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddJSON(std::string_view json) { |
| CHECK(manifest_data_); |
| std::string wrapped_json = base::StringPrintf("{%s}", json.data()); |
| auto parsed = base::JSONReader::ReadAndReturnValueWithError(wrapped_json); |
| CHECK(parsed.has_value()) |
| << "Failed to parse json for extension '" << manifest_data_->name |
| << "':" << parsed.error().message; |
| return MergeManifest(std::move(*parsed).TakeDict()); |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetPath(const base::FilePath& path) { |
| path_ = path; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetLocation( |
| mojom::ManifestLocation location) { |
| location_ = location; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetManifest(base::Value::Dict manifest) { |
| CHECK(!manifest_data_); |
| manifest_value_ = std::move(manifest); |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::MergeManifest(base::Value::Dict to_merge) { |
| if (manifest_data_) { |
| manifest_data_->get_extra().Merge(std::move(to_merge)); |
| } else { |
| manifest_value_->Merge(std::move(to_merge)); |
| } |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::AddFlags(int init_from_value_flags) { |
| flags_ |= init_from_value_flags; |
| return *this; |
| } |
| |
| ExtensionBuilder& ExtensionBuilder::SetID(const std::string& id) { |
| id_ = id; |
| return *this; |
| } |
| |
| void ExtensionBuilder::SetManifestKeyImpl(std::string_view key, |
| base::Value value) { |
| CHECK(manifest_data_); |
| manifest_data_->get_extra().Set(key, std::move(value)); |
| } |
| |
| void ExtensionBuilder::SetManifestPathImpl(std::string_view path, |
| base::Value value) { |
| CHECK(manifest_data_); |
| manifest_data_->get_extra().SetByDottedPath(path, std::move(value)); |
| } |
| |
| } // namespace extensions |