| // 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 |