blob: 5f909b682ffe147df11922605e14d7aac8c94462 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/services/app_service/public/cpp/intent.h"
#include <algorithm>
#include "base/files/safe_base_name.h"
#include "base/notreached.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/intent_util.h"
namespace apps {
IntentFile::IntentFile(const GURL& url) : url(url) {}
IntentFile::~IntentFile() = default;
std::unique_ptr<IntentFile> IntentFile::Clone() const {
auto intent_file = std::make_unique<IntentFile>(url);
if (mime_type.has_value()) {
intent_file->mime_type = mime_type.value();
}
if (file_name.has_value()) {
intent_file->file_name = file_name.value();
}
intent_file->file_size = file_size;
intent_file->is_directory = is_directory;
intent_file->dlp_source_url = dlp_source_url;
return intent_file;
}
bool IntentFile::MatchConditionValue(const ConditionValuePtr& condition_value) {
switch (condition_value->match_type) {
case PatternMatchType::kLiteral:
case PatternMatchType::kPrefix:
case PatternMatchType::kSuffix: {
NOTREACHED();
}
case PatternMatchType::kGlob: {
return apps_util::MatchGlob(url.spec(), condition_value->value);
}
case PatternMatchType::kMimeType: {
return mime_type.has_value() &&
apps_util::MimeTypeMatched(mime_type.value(),
condition_value->value);
}
case PatternMatchType::kFileExtension: {
return apps_util::ExtensionMatched(url.ExtractFileName(),
condition_value->value);
}
case PatternMatchType::kIsDirectory: {
return is_directory.value_or(false);
}
}
}
bool IntentFile::MatchAnyConditionValue(
const std::vector<ConditionValuePtr>& condition_values) {
return std::ranges::any_of(condition_values,
[this](const ConditionValuePtr& condition_value) {
return MatchConditionValue(condition_value);
});
}
Intent::Intent(const std::string& action) : action(action) {}
Intent::Intent(const std::string& action, const GURL& url)
: action(action), url(url) {}
Intent::Intent(const std::string& action, std::vector<IntentFilePtr> files)
: action(action), files(std::move(files)) {}
Intent::~Intent() = default;
bool Intent::operator==(const Intent& other) const {
for (int i = 0; i < static_cast<int>(files.size()); i++) {
if ((*files[i]) != (*other.files[i])) {
return false;
}
}
return action == other.action && url == other.url &&
mime_type == other.mime_type && activity_name == other.activity_name &&
drive_share_url == other.drive_share_url &&
share_text == other.share_text && share_title == other.share_title &&
start_type == other.start_type && categories == other.categories &&
data == other.data && ui_bypassed == other.ui_bypassed &&
extras == other.extras;
}
std::unique_ptr<Intent> Intent::Clone() const {
auto intent = std::make_unique<Intent>(action);
if (url.has_value()) {
intent->url = url.value();
}
if (mime_type.has_value()) {
intent->mime_type = mime_type.value();
}
for (const auto& file : files) {
intent->files.push_back(file->Clone());
}
if (activity_name.has_value()) {
intent->activity_name = activity_name.value();
}
if (drive_share_url.has_value()) {
intent->drive_share_url = drive_share_url.value();
}
if (share_text.has_value()) {
intent->share_text = share_text.value();
}
if (share_title.has_value()) {
intent->share_title = share_title.value();
}
if (start_type.has_value()) {
intent->start_type = start_type.value();
}
for (const auto& category : categories) {
intent->categories.push_back(category);
}
if (data.has_value()) {
intent->data = data.value();
}
intent->ui_bypassed = ui_bypassed;
for (const auto& extra : extras) {
intent->extras[extra.first] = extra.second;
}
return intent;
}
std::optional<std::string> Intent::GetIntentConditionValueByType(
ConditionType condition_type) {
switch (condition_type) {
case ConditionType::kAction: {
return action;
}
case ConditionType::kScheme: {
return url.has_value() ? std::optional<std::string>(url->GetScheme())
: std::nullopt;
}
case ConditionType::kPath: {
return url.has_value() ? std::optional<std::string>(url->GetPath())
: std::nullopt;
}
case ConditionType::kMimeType: {
return mime_type;
}
// Handled in MatchAuthorityCondition.
case ConditionType::kAuthority:
// Handled in MatchFileCondition.
case ConditionType::kFile: {
NOTREACHED();
}
}
}
bool Intent::MatchAuthorityCondition(const ConditionPtr& condition) {
CHECK_EQ(condition->condition_type, ConditionType::kAuthority);
if (!url.has_value()) {
return false;
}
std::optional<std::string> port =
apps_util::AuthorityView::PortToString(url.value());
return std::ranges::any_of(
condition->condition_values,
[this, &port](const ConditionValuePtr& condition_value) {
apps_util::AuthorityView match_authority =
apps_util::AuthorityView::Decode(condition_value->value);
if (!apps_util::PatternMatchValue(url->GetHost(),
condition_value->match_type,
match_authority.host)) {
return false;
}
// No port filtering.
if (!match_authority.port.has_value()) {
return true;
}
// URL has no port but port is expected.
if (!port.has_value()) {
return false;
}
return match_authority.port.value() == port.value();
});
}
bool Intent::MatchFileCondition(const ConditionPtr& condition) {
DCHECK_EQ(condition->condition_type, ConditionType::kFile);
return !files.empty() &&
std::ranges::all_of(files, [&condition](const IntentFilePtr& file) {
return file->MatchAnyConditionValue(condition->condition_values);
});
}
bool Intent::MatchCondition(const ConditionPtr& condition) {
if (condition->condition_type == ConditionType::kAuthority) {
return MatchAuthorityCondition(condition);
}
if (condition->condition_type == ConditionType::kFile) {
return MatchFileCondition(condition);
}
std::optional<std::string> value_to_match =
GetIntentConditionValueByType(condition->condition_type);
return value_to_match.has_value() &&
std::ranges::any_of(condition->condition_values,
[&value_to_match](const auto& condition_value) {
return apps_util::ConditionValueMatches(
value_to_match.value(), condition_value);
});
}
bool Intent::MatchFilter(const IntentFilterPtr& filter) {
// Intent matches with this intent filter when all of the existing conditions
// match.
for (const auto& condition : filter->conditions) {
if (!MatchCondition(condition)) {
return false;
}
}
return true;
}
bool Intent::IsShareIntent() {
return action == apps_util::kIntentActionSend ||
action == apps_util::kIntentActionSendMultiple;
}
bool Intent::OnlyShareToDrive() {
return IsShareIntent() && drive_share_url && !share_text && files.empty();
}
bool Intent::IsIntentValid() {
if (IsShareIntent()) {
return share_text || !files.empty();
}
return true;
}
} // namespace apps