blob: e3f111e4e968d1922edb7afe2dcba0a8b499ca89 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/automation.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/api/extensions_manifest_types.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/api_permission_set.h"
#include "extensions/common/permissions/manifest_permission.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_utils.h"
namespace extensions {
namespace automation_errors {
const char kErrorDesktopTrueInteractFalse[] =
"Cannot specify interactive=false if desktop=true is specified; "
"interactive=false will be ignored.";
const char kErrorDesktopTrueMatchesSpecified[] =
"Cannot specify matches for Automation if desktop=true is specified; "
"matches will be ignored.";
const char kErrorInvalidMatch[] = "Invalid match pattern '*': *";
const char kErrorNoMatchesProvided[] = "No valid match patterns provided.";
} // namespace automation_errors
namespace errors = manifest_errors;
namespace keys = extensions::manifest_keys;
using api::extensions_manifest_types::Automation;
class AutomationManifestPermission : public ManifestPermission {
public:
explicit AutomationManifestPermission(
std::unique_ptr<const AutomationInfo> automation_info)
: automation_info_(std::move(automation_info)) {}
// extensions::ManifestPermission overrides.
std::string name() const override;
std::string id() const override;
PermissionIDSet GetPermissions() const override;
bool FromValue(const base::Value* value) override;
std::unique_ptr<base::Value> ToValue() const override;
std::unique_ptr<ManifestPermission> Diff(
const ManifestPermission* rhs) const override;
std::unique_ptr<ManifestPermission> Union(
const ManifestPermission* rhs) const override;
std::unique_ptr<ManifestPermission> Intersect(
const ManifestPermission* rhs) const override;
private:
std::unique_ptr<const AutomationInfo> automation_info_;
};
std::string AutomationManifestPermission::name() const {
return keys::kAutomation;
}
std::string AutomationManifestPermission::id() const {
return keys::kAutomation;
}
PermissionIDSet AutomationManifestPermission::GetPermissions() const {
// Meant to mimic the behavior of GetMessages().
PermissionIDSet permissions;
if (automation_info_->desktop) {
permissions.insert(APIPermission::kFullAccess);
} else if (automation_info_->matches.MatchesAllURLs()) {
if (automation_info_->interact) {
permissions.insert(APIPermission::kHostsAll);
} else {
permissions.insert(APIPermission::kHostsAllReadOnly);
}
} else {
// Check if we get any additional permissions from FilterHostPermissions.
URLPatternSet regular_hosts;
ExtensionsClient::Get()->FilterHostPermissions(
automation_info_->matches, &regular_hosts, &permissions);
std::set<std::string> hosts =
permission_message_util::GetDistinctHosts(regular_hosts, true, true);
APIPermission::ID permission_id = automation_info_->interact
? APIPermission::kHostReadWrite
: APIPermission::kHostReadOnly;
for (const auto& host : hosts)
permissions.insert(permission_id, base::UTF8ToUTF16(host));
}
return permissions;
}
bool AutomationManifestPermission::FromValue(const base::Value* value) {
base::string16 error;
automation_info_.reset(
AutomationInfo::FromValue(*value, NULL /* install_warnings */, &error)
.release());
return error.empty();
}
std::unique_ptr<base::Value> AutomationManifestPermission::ToValue() const {
return AutomationInfo::ToValue(*automation_info_);
}
std::unique_ptr<ManifestPermission> AutomationManifestPermission::Diff(
const ManifestPermission* rhs) const {
const AutomationManifestPermission* other =
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop && !other->automation_info_->desktop;
bool interact =
automation_info_->interact && !other->automation_info_->interact;
URLPatternSet matches = URLPatternSet::CreateDifference(
automation_info_->matches, other->automation_info_->matches);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches, interact)));
}
std::unique_ptr<ManifestPermission> AutomationManifestPermission::Union(
const ManifestPermission* rhs) const {
const AutomationManifestPermission* other =
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop || other->automation_info_->desktop;
bool interact =
automation_info_->interact || other->automation_info_->interact;
URLPatternSet matches = URLPatternSet::CreateUnion(
automation_info_->matches, other->automation_info_->matches);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches, interact)));
}
std::unique_ptr<ManifestPermission> AutomationManifestPermission::Intersect(
const ManifestPermission* rhs) const {
const AutomationManifestPermission* other =
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop && other->automation_info_->desktop;
bool interact =
automation_info_->interact && other->automation_info_->interact;
URLPatternSet matches = URLPatternSet::CreateIntersection(
automation_info_->matches, other->automation_info_->matches,
URLPatternSet::IntersectionBehavior::kStringComparison);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches, interact)));
}
AutomationHandler::AutomationHandler() {}
AutomationHandler::~AutomationHandler() {}
bool AutomationHandler::Parse(Extension* extension, base::string16* error) {
const base::Value* automation = NULL;
CHECK(extension->manifest()->Get(keys::kAutomation, &automation));
std::vector<InstallWarning> install_warnings;
std::unique_ptr<AutomationInfo> info =
AutomationInfo::FromValue(*automation, &install_warnings, error);
if (!error->empty())
return false;
extension->AddInstallWarnings(std::move(install_warnings));
if (!info)
return true;
extension->SetManifestData(keys::kAutomation, std::move(info));
return true;
}
base::span<const char* const> AutomationHandler::Keys() const {
static constexpr const char* kKeys[] = {keys::kAutomation};
return kKeys;
}
ManifestPermission* AutomationHandler::CreatePermission() {
return new AutomationManifestPermission(
base::WrapUnique(new const AutomationInfo));
}
ManifestPermission* AutomationHandler::CreateInitialRequiredPermission(
const Extension* extension) {
const AutomationInfo* info = AutomationInfo::Get(extension);
if (info) {
return new AutomationManifestPermission(
base::WrapUnique(new const AutomationInfo(info->desktop, info->matches,
info->interact)));
}
return NULL;
}
// static
const AutomationInfo* AutomationInfo::Get(const Extension* extension) {
return static_cast<AutomationInfo*>(
extension->GetManifestData(keys::kAutomation));
}
// static
std::unique_ptr<AutomationInfo> AutomationInfo::FromValue(
const base::Value& value,
std::vector<InstallWarning>* install_warnings,
base::string16* error) {
std::unique_ptr<Automation> automation = Automation::FromValue(value, error);
if (!automation)
return nullptr;
if (automation->as_boolean) {
if (*automation->as_boolean)
return base::WrapUnique(new AutomationInfo());
return nullptr;
}
const Automation::Object& automation_object = *automation->as_object;
bool desktop = false;
bool interact = false;
if (automation_object.desktop && *automation_object.desktop) {
desktop = true;
interact = true;
if (automation_object.interact && !*automation_object.interact) {
// TODO(aboxhall): Do we want to allow this?
install_warnings->push_back(
InstallWarning(automation_errors::kErrorDesktopTrueInteractFalse));
}
} else if (automation_object.interact && *automation_object.interact) {
interact = true;
}
URLPatternSet matches;
bool specified_matches = false;
if (automation_object.matches) {
if (desktop) {
install_warnings->push_back(
InstallWarning(automation_errors::kErrorDesktopTrueMatchesSpecified));
} else {
specified_matches = true;
for (auto it = automation_object.matches->begin();
it != automation_object.matches->end(); ++it) {
// TODO(aboxhall): Refactor common logic from content_scripts_handler,
// manifest_url_handler and user_script.cc into a single location and
// re-use here.
URLPattern pattern(URLPattern::SCHEME_ALL &
~URLPattern::SCHEME_CHROMEUI);
URLPattern::ParseResult parse_result = pattern.Parse(*it);
if (parse_result != URLPattern::ParseResult::kSuccess) {
install_warnings->push_back(
InstallWarning(ErrorUtils::FormatErrorMessage(
automation_errors::kErrorInvalidMatch, *it,
URLPattern::GetParseResultString(parse_result))));
continue;
}
matches.AddPattern(pattern);
}
}
}
if (specified_matches && matches.is_empty()) {
install_warnings->push_back(
InstallWarning(automation_errors::kErrorNoMatchesProvided));
}
return base::WrapUnique(new AutomationInfo(desktop, matches, interact));
}
// static
std::unique_ptr<base::Value> AutomationInfo::ToValue(
const AutomationInfo& info) {
return AsManifestType(info)->ToValue();
}
// static
std::unique_ptr<Automation> AutomationInfo::AsManifestType(
const AutomationInfo& info) {
std::unique_ptr<Automation> automation(new Automation);
if (!info.desktop && !info.interact && info.matches.size() == 0) {
automation->as_boolean.reset(new bool(true));
return automation;
}
Automation::Object* as_object = new Automation::Object;
as_object->desktop.reset(new bool(info.desktop));
as_object->interact.reset(new bool(info.interact));
if (info.matches.size() > 0)
as_object->matches = info.matches.ToStringVector();
automation->as_object.reset(as_object);
return automation;
}
AutomationInfo::AutomationInfo() : desktop(false), interact(false) {}
AutomationInfo::AutomationInfo(bool desktop,
const URLPatternSet& matches,
bool interact)
: desktop(desktop), matches(matches.Clone()), interact(interact) {}
AutomationInfo::~AutomationInfo() {}
} // namespace extensions