blob: 6bf22da90fec8be1a14f440c1b37a29f344509b6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/apps/app_service/intent_util.h"
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/debug/dump_without_crashing.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/file_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "components/services/app_service/public/cpp/file_handler_info.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "extensions/common/api/app_runtime.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/file_handler_info.h"
#include "extensions/common/manifest_handlers/web_file_handlers_info.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "third_party/re2/src/re2/re2.h"
#include "url/gurl.h"
#include "url/url_constants.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/files/file_path.h"
#include "chrome/browser/ash/app_list/arc/intent.h"
#include "chrome/browser/ash/fusebox/fusebox_server.h"
#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
#include "chromeos/ash/experiences/arc/intent_helper/arc_intent_helper_bridge.h"
#include "chromeos/ash/experiences/arc/intent_helper/intent_constants.h"
#include "chromeos/ash/experiences/arc/intent_helper/intent_filter.h"
#include "chromeos/ash/experiences/arc/mojom/intent_helper.mojom-shared.h"
#include "chromeos/ash/experiences/arc/mojom/intent_helper.mojom.h"
#include "storage/browser/file_system/file_system_url.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace apps_util {
namespace {
#if BUILDFLAG(IS_CHROMEOS)
apps::IntentFilterPtr CreateFileURLFilter(
const std::vector<std::string>& patterns,
const std::string& activity_name,
const std::string& activity_label) {
DCHECK(!patterns.empty());
auto intent_filter = std::make_unique<apps::IntentFilter>();
// kAction == View.
apps::ConditionValues action_condition_values;
action_condition_values.push_back(std::make_unique<apps::ConditionValue>(
kIntentActionView, apps::PatternMatchType::kLiteral));
auto action_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kAction, std::move(action_condition_values));
intent_filter->conditions.push_back(std::move(action_condition));
// URL patterns.
apps::ConditionValues condition_values;
for (const std::string& pattern : patterns) {
condition_values.push_back(std::make_unique<apps::ConditionValue>(
pattern, apps::PatternMatchType::kGlob));
}
auto file_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kFile, std::move(condition_values));
intent_filter->conditions.push_back(std::move(file_condition));
intent_filter->activity_name = activity_name;
intent_filter->activity_label = activity_label;
return intent_filter;
}
// Takes a URL pattern that represents a path like *.pdf and returns a string
// representing a pattern matching a file system URL spec. If |legacy| flag is
// set to true the function returns a pattern that matches URLs generated by the
// legacy Files app (e.g., "filesystem:chrome-extension://.*/.*\.pdf"). If
// |legacy| flag is set to false, the pattern matches URLs generated by the
// Files System Web App (e.g., "filesystem:chrome://file-manager/.*\.pdf).
const std::string URLPatternToFileSystemPattern(const URLPattern& pattern,
bool legacy) {
const char* scheme =
legacy ? "chrome-extension://*" : "chrome://file-manager";
std::string path =
base::StrCat({url::kFileSystemScheme, ":", scheme, pattern.path()});
base::ReplaceChars(path, ".", R"(\.)", &path);
base::ReplaceChars(path, "*", ".*", &path);
return path;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS)
constexpr char kIntentExtraText[] = "S.android.intent.extra.TEXT";
constexpr char kIntentExtraSubject[] = "S.android.intent.extra.SUBJECT";
constexpr char kIntentExtraStartType[] = "S.org.chromium.arc.start_type";
constexpr char kIntentActionPrefix[] = "android.intent.action";
constexpr char kType[] = "type";
constexpr int kIntentPrefixLength = 2;
const char* ConvertAppServiceToArcIntentAction(const std::string& action) {
if (action == kIntentActionMain) {
return arc::kIntentActionMain;
} else if (action == kIntentActionView) {
return arc::kIntentActionView;
} else if (action == kIntentActionSend) {
return arc::kIntentActionSend;
} else if (action == kIntentActionSendMultiple) {
return arc::kIntentActionSendMultiple;
} else if (action.compare(0, strlen(kIntentActionPrefix),
kIntentActionPrefix) == 0) {
return action.c_str();
} else {
return arc::kIntentActionView;
}
}
// Returns true if |pattern| is a Glob (as in PatternMatchType::kGlob) which
// behaves like a Prefix pattern. That is, the only special characters are a
// ".*" at the end of the string.
bool IsPrefixOnlyGlob(std::string_view pattern) {
if (!base::EndsWith(pattern, ".*")) {
return false;
}
size_t i = 0;
while (i < pattern.size() - 2) {
if (pattern[i] == '.' || pattern[i] == '*') {
return false;
}
i++;
}
return true;
}
apps::ConditionValuePtr ConvertArcPatternMatcherToConditionValue(
const arc::IntentFilter::PatternMatcher& path) {
apps::PatternMatchType match_type;
if (!arc::IsKnownPatternType(path.match_type())) {
LOG(ERROR)
<< " Received an ARC intent filter with unsupported PatternType: "
<< path.match_type()
<< " for the filter path, need to update ARC code to support new "
"pattern types.";
base::debug::DumpWithoutCrashing();
return nullptr;
}
switch (path.match_type()) {
case arc::PatternType::kLiteral:
match_type = apps::PatternMatchType::kLiteral;
break;
case arc::PatternType::kPrefix:
match_type = apps::PatternMatchType::kPrefix;
break;
case arc::PatternType::kSimpleGlob:
match_type = apps::PatternMatchType::kGlob;
// It's common for Globs to be used to encode patterns which are actually
// prefixes. Detect and convert these, since prefix matching is easier &
// cheaper.
if (IsPrefixOnlyGlob(path.pattern())) {
DCHECK_GE(path.pattern().size(), 2u);
return std::make_unique<apps::ConditionValue>(
path.pattern().substr(0, path.pattern().size() - 2),
apps::PatternMatchType::kPrefix);
}
break;
// TODO(crbug.com/40275407): support the new pattern types.
case arc::PatternType::kAdvancedGlob:
case arc::PatternType::kSuffix:
case arc::PatternType::kUnknown:
LOG(ERROR)
<< " Received an ARC intent filter with unsupported PatternType: "
<< path.match_type()
<< " for the filter path. Need to update code to support new pattern "
"types.";
return nullptr;
}
return std::make_unique<apps::ConditionValue>(path.pattern(), match_type);
}
std::string ExtractExtensionType(std::string path) {
// Look for a valid set of characters at the end of the string that directly
// follow the characters ".*\.", e.g. the regex should capture "tar.gz" in
// this string: ".*\..*\..*\..*\.tar.gz".
// TODO(b/270483199): Make this regex stricter to check for invalid characters
// at the start and middle of the path, and any invalid characters at the
// start of the extension.
re2::RE2 extension_regex_pattern("\\.\\*\\\\.([a-zA-Z0-9_\\-.]*)$");
std::string extension_capture;
RE2::PartialMatch(path, extension_regex_pattern, &extension_capture);
return extension_capture;
}
apps::ConditionValues ConvertPathToExtensionConditionValues(
apps::ConditionValues path_condition_values) {
base::flat_set<std::string> unique_extensions;
// Go through all the path values and extract any file extensions.
for (auto& path_condition_value : path_condition_values) {
if (path_condition_value->match_type != apps::PatternMatchType::kGlob) {
continue;
}
std::string extension_type =
ExtractExtensionType(path_condition_value->value);
if (extension_type.empty()) {
continue;
}
// If we found an extension type, save it in the set.
unique_extensions.insert(extension_type);
}
// Convert all the unique extension types into condition values.
apps::ConditionValues ext_condition_values;
for (const std::string& ext : unique_extensions) {
ext_condition_values.push_back(std::make_unique<apps::ConditionValue>(
ext, apps::PatternMatchType::kFileExtension));
}
return ext_condition_values;
}
bool IsFileExtensionFilter(const arc::IntentFilter& arc_intent_filter) {
// Check that we have the correct fields available.
if (arc_intent_filter.paths().size() == 0 ||
arc_intent_filter.schemes().size() == 0) {
return false;
}
// Check that the filter has a view action.
if (!base::Contains(arc_intent_filter.actions(), arc::kIntentActionView)) {
return false;
}
// Check that the scheme is generic or has a value related to files.
bool has_generic_scheme = std::ranges::any_of(
arc_intent_filter.schemes(), [](const std::string& scheme) {
return scheme == "content" || scheme == "file" || scheme == "*";
});
if (!has_generic_scheme) {
return false;
}
// Check that the host is generic or doesn't exist.
bool has_generic_host = std::ranges::any_of(
arc_intent_filter.authorities(),
[](const arc::IntentFilter::AuthorityEntry& authority) {
return authority.wild();
});
if (arc_intent_filter.authorities().size() != 0 && !has_generic_host) {
return false;
}
// Check that the mime type is generic or doesn't exist.
bool has_generic_mime = std::ranges::any_of(
arc_intent_filter.mime_types(), [](const std::string& mime_type) {
return mime_type == "*" || mime_type == "*/*";
});
if (arc_intent_filter.mime_types().size() != 0 && !has_generic_mime) {
return false;
}
return true;
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
apps::IntentFilterPtr CreateFileFilter(
const std::vector<std::string>& intent_actions,
const std::vector<std::string>& mime_types,
const std::vector<std::string>& file_extensions,
const std::string& activity_name,
bool include_directories) {
DCHECK(!mime_types.empty() || !file_extensions.empty());
auto intent_filter = std::make_unique<apps::IntentFilter>();
// kAction == View, Share etc.
apps::ConditionValues action_condition_values;
for (auto& action : intent_actions) {
action_condition_values.push_back(std::make_unique<apps::ConditionValue>(
action, apps::PatternMatchType::kLiteral));
}
if (!action_condition_values.empty()) {
intent_filter->conditions.push_back(std::make_unique<apps::Condition>(
apps::ConditionType::kAction, std::move(action_condition_values)));
}
apps::ConditionValues file_condition_values;
// Mime types.
for (auto& mime_type : mime_types) {
file_condition_values.push_back(std::make_unique<apps::ConditionValue>(
mime_type, apps::PatternMatchType::kMimeType));
}
// And file extensions.
for (const std::string& extension : file_extensions) {
file_condition_values.push_back(std::make_unique<apps::ConditionValue>(
extension, apps::PatternMatchType::kFileExtension));
}
if (include_directories) {
file_condition_values.push_back(std::make_unique<apps::ConditionValue>(
"", apps::PatternMatchType::kIsDirectory));
}
DCHECK(!file_condition_values.empty());
if (!file_condition_values.empty()) {
intent_filter->conditions.push_back(std::make_unique<apps::Condition>(
apps::ConditionType::kFile, std::move(file_condition_values)));
}
if (!activity_name.empty()) {
intent_filter->activity_name = activity_name;
}
return intent_filter;
}
#if BUILDFLAG(IS_CHROMEOS)
apps::IntentFilters CreateIntentFiltersFromArcBridge(
const std::string& package_name,
arc::ArcIntentHelperBridge* intent_helper_bridge) {
apps::IntentFilters filters;
const std::vector<arc::IntentFilter>& arc_intent_filters =
intent_helper_bridge->GetIntentFilterForPackage(package_name);
for (const auto& arc_intent_filter : arc_intent_filters) {
apps::IntentFilterPtr filter = CreateIntentFilterForArc(arc_intent_filter);
if (filter) {
filters.push_back(std::move(filter));
}
}
return filters;
}
#endif // BUILDFLAG(IS_CHROMEOS)
apps::IntentFilters CreateIntentFiltersForChromeApp(
const extensions::Extension* extension) {
apps::IntentFilters filters;
// Check that the extension can be launched with files. This includes all
// platform apps and allowlisted extensions.
if (!CanLaunchViaEvent(extension)) {
return filters;
}
const extensions::FileHandlersInfo* file_handlers =
extensions::FileHandlers::GetFileHandlers(extension);
if (!file_handlers) {
return filters;
}
for (const apps::FileHandlerInfo& handler : *file_handlers) {
// "share_with", "add_to" and "pack_with" are ignored in the Files app
// frontend.
if (handler.verb != apps::file_handler_verbs::kOpenWith) {
continue;
}
std::vector<std::string> mime_types(handler.types.begin(),
handler.types.end());
std::vector<std::string> file_extensions(handler.extensions.begin(),
handler.extensions.end());
filters.push_back(CreateFileFilter({kIntentActionView}, mime_types,
file_extensions, handler.id,
handler.include_directories));
filters.back()->activity_label = extension->name();
}
return filters;
}
apps::IntentFilters CreateIntentFiltersForExtension(
const extensions::Extension* extension) {
#if BUILDFLAG(IS_CHROMEOS)
// MV3+ manifest support for the `file_handlers` key.
DCHECK(extension);
const extensions::WebFileHandlersInfo* intent_filter_data =
extensions::WebFileHandlers::GetFileHandlers(*extension);
if (intent_filter_data && !intent_filter_data->empty()) {
apps::IntentFilters filters;
for (const auto& web_file_handler : *intent_filter_data) {
// Flatten mime_types and file_extensions.
std::vector<std::string> mime_types;
std::vector<std::string> file_extensions;
for (const auto [mime_type, file_extension_list] :
web_file_handler.file_handler.accept.additional_properties) {
mime_types.emplace_back(mime_type);
for (const auto& file_extension : file_extension_list.GetList()) {
file_extensions.emplace_back(file_extension.GetString());
}
}
filters.push_back(CreateFileFilter({kIntentActionView}, mime_types,
file_extensions,
web_file_handler.file_handler.action));
}
return filters;
}
FileBrowserHandler::List* handler_list =
FileBrowserHandler::GetHandlers(extension);
if (!handler_list) {
return {};
}
apps::IntentFilters filters;
for (const std::unique_ptr<FileBrowserHandler>& handler : *handler_list) {
std::vector<std::string> patterns;
for (const URLPattern& pattern : handler->file_url_patterns()) {
patterns.push_back(URLPatternToFileSystemPattern(pattern, true));
patterns.push_back(URLPatternToFileSystemPattern(pattern, false));
}
filters.push_back(
CreateFileURLFilter(patterns, handler->id(), handler->title()));
}
return filters;
#else
return {};
#endif // BUILDFLAG(IS_CHROMEOS)
}
apps::IntentFilterPtr CreateNoteTakingFilter() {
auto intent_filter = std::make_unique<apps::IntentFilter>();
intent_filter->AddSingleValueCondition(apps::ConditionType::kAction,
kIntentActionCreateNote,
apps::PatternMatchType::kLiteral);
return intent_filter;
}
apps::IntentFilterPtr CreateLockScreenFilter() {
auto intent_filter = std::make_unique<apps::IntentFilter>();
intent_filter->AddSingleValueCondition(apps::ConditionType::kAction,
kIntentActionStartOnLockScreen,
apps::PatternMatchType::kLiteral);
return intent_filter;
}
#if BUILDFLAG(IS_CHROMEOS)
apps::IntentPtr CreateShareIntentFromFiles(
Profile* profile,
const std::vector<base::FilePath>& file_paths,
const std::vector<std::string>& mime_types) {
auto file_urls = apps::GetFileSystemUrls(profile, file_paths);
return MakeShareIntent(file_urls, mime_types);
}
apps::IntentPtr CreateShareIntentFromFiles(
Profile* profile,
const std::vector<base::FilePath>& file_paths,
const std::vector<std::string>& mime_types,
const std::string& share_text,
const std::string& share_title) {
auto file_urls = apps::GetFileSystemUrls(profile, file_paths);
return MakeShareIntent(file_urls, mime_types, share_text, share_title);
}
base::flat_map<std::string, std::string> CreateArcIntentExtras(
const apps::IntentPtr& intent) {
auto extras = base::flat_map<std::string, std::string>();
if (intent->share_text.has_value()) {
// Slice off the "S." prefix for the key.
extras.insert(std::make_pair(kIntentExtraText + kIntentPrefixLength,
intent->share_text.value()));
}
if (intent->share_title.has_value()) {
// Slice off the "S." prefix for the key.
extras.insert(std::make_pair(kIntentExtraSubject + kIntentPrefixLength,
intent->share_title.value()));
}
if (intent->start_type.has_value()) {
// Slice off the "S." prefix for the key.
extras.insert(std::make_pair(kIntentExtraStartType + kIntentPrefixLength,
intent->start_type.value()));
}
if (!intent->extras.empty()) {
extras.insert(intent->extras.begin(), intent->extras.end());
}
return extras;
}
arc::mojom::IntentInfoPtr ConvertAppServiceToArcIntent(
const apps::IntentPtr& intent) {
arc::mojom::IntentInfoPtr arc_intent;
if (!intent->url.has_value() && !intent->share_text.has_value() &&
!intent->activity_name.has_value()) {
return arc_intent;
}
arc_intent = arc::mojom::IntentInfo::New();
arc_intent->action = ConvertAppServiceToArcIntentAction(intent->action);
if (intent->url.has_value()) {
arc_intent->data = intent->url->spec();
}
if (intent->share_text.has_value() || intent->share_title.has_value() ||
intent->start_type.has_value() || !intent->extras.empty()) {
arc_intent->extras = CreateArcIntentExtras(intent);
}
if (!intent->categories.empty()) {
arc_intent->categories = intent->categories;
}
if (intent->data.has_value()) {
arc_intent->data = intent->data;
}
if (intent->mime_type.has_value()) {
arc_intent->type = intent->mime_type;
}
if (intent->ui_bypassed.has_value()) {
arc_intent->ui_bypassed = intent->ui_bypassed.value();
}
return arc_intent;
}
const char* ConvertArcToAppServiceIntentAction(const std::string& arc_action) {
if (arc_action == arc::kIntentActionMain) {
return kIntentActionMain;
} else if (arc_action == arc::kIntentActionView) {
return kIntentActionView;
} else if (arc_action == arc::kIntentActionSend) {
return kIntentActionSend;
} else if (arc_action == arc::kIntentActionSendMultiple) {
return kIntentActionSendMultiple;
}
return nullptr;
}
std::string CreateLaunchIntent(const std::string& package_name,
const apps::IntentPtr& intent) {
// If |intent| has |ui_bypassed|, |url| or |data|, it is too complex to
// convert to a string, so return the empty string.
if (intent->ui_bypassed.has_value() || intent->url.has_value() ||
intent->data.has_value()) {
return std::string();
}
std::string ret = base::StringPrintf("%s;", arc::kIntentPrefix);
// Convert action.
std::string action = ConvertAppServiceToArcIntentAction(intent->action);
ret += base::StringPrintf("%s=%s;", arc::kAction,
ConvertAppServiceToArcIntentAction(intent->action));
// Convert categories.
for (const auto& category : intent->categories) {
ret += base::StringPrintf("%s=%s;", arc::kCategory, category.c_str());
}
// Set launch flags.
ret +=
base::StringPrintf("%s=0x%x;", arc::kLaunchFlags,
arc::Intent::FLAG_ACTIVITY_NEW_TASK |
arc::Intent::FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
// Convert activity_name.
if (intent->activity_name.has_value()) {
// Remove the |package_name| prefix, if activity starts with it.
const std::string& activity = intent->activity_name.value();
const char* activity_compact_name =
activity.find(package_name.c_str()) == 0
? activity.c_str() + package_name.length()
: activity.c_str();
ret += base::StringPrintf("%s=%s/%s;", arc::kComponent,
package_name.c_str(), activity_compact_name);
} else {
ret += base::StringPrintf("%s=%s/;", arc::kComponent, package_name.c_str());
}
if (intent->mime_type.has_value()) {
ret +=
base::StringPrintf("%s=%s;", kType, intent->mime_type.value().c_str());
}
if (intent->share_text.has_value()) {
ret += base::StringPrintf("%s=%s;", kIntentExtraText,
intent->share_text.value().c_str());
}
if (intent->share_title.has_value()) {
ret += base::StringPrintf("%s=%s;", kIntentExtraSubject,
intent->share_title.value().c_str());
}
if (intent->start_type.has_value()) {
ret += base::StringPrintf("%s=%s;", kIntentExtraStartType,
intent->start_type.value().c_str());
}
for (auto it : intent->extras) {
ret += base::StringPrintf("%s=%s;", it.first.c_str(), it.second.c_str());
}
ret += arc::kEndSuffix;
DCHECK(!ret.empty());
return ret;
}
arc::IntentFilter ConvertAppServiceToArcIntentFilter(
const std::string& package_name,
const apps::IntentFilterPtr& intent_filter) {
std::vector<std::string> actions;
std::vector<std::string> schemes;
std::vector<arc::IntentFilter::AuthorityEntry> authorities;
std::vector<arc::IntentFilter::PatternMatcher> paths;
std::vector<std::string> mime_types;
for (auto& condition : intent_filter->conditions) {
switch (condition->condition_type) {
case apps::ConditionType::kScheme:
for (auto& condition_value : condition->condition_values) {
schemes.push_back(condition_value->value);
}
break;
case apps::ConditionType::kAuthority:
for (auto& condition_value : condition->condition_values) {
authorities.emplace_back(
/*host=*/condition_value->value, /*port=*/0);
}
break;
case apps::ConditionType::kPath:
for (auto& condition_value : condition->condition_values) {
arc::PatternType match_type;
switch (condition_value->match_type) {
case apps::PatternMatchType::kLiteral:
match_type = arc::PatternType::kLiteral;
break;
case apps::PatternMatchType::kPrefix:
match_type = arc::PatternType::kPrefix;
break;
case apps::PatternMatchType::kGlob:
match_type = arc::PatternType::kSimpleGlob;
break;
case apps::PatternMatchType::kMimeType:
case apps::PatternMatchType::kFileExtension:
case apps::PatternMatchType::kIsDirectory:
case apps::PatternMatchType::kSuffix:
NOTREACHED();
}
paths.emplace_back(condition_value->value, match_type);
}
break;
case apps::ConditionType::kAction:
for (auto& condition_value : condition->condition_values) {
actions.push_back(
ConvertAppServiceToArcIntentAction(condition_value->value));
}
break;
case apps::ConditionType::kMimeType:
for (auto& condition_value : condition->condition_values) {
mime_types.push_back(condition_value->value);
}
break;
case apps::ConditionType::kFile:
NOTREACHED();
}
}
return arc::IntentFilter(package_name, std::move(actions),
std::move(authorities), std::move(paths),
std::move(schemes), std::move(mime_types));
}
apps::IntentFilterPtr CreateIntentFilterForArc(
const arc::IntentFilter& arc_intent_filter) {
auto intent_filter = std::make_unique<apps::IntentFilter>();
bool has_view_action = false;
apps::ConditionValues action_condition_values;
for (auto& arc_action : arc_intent_filter.actions()) {
const char* action = ConvertArcToAppServiceIntentAction(arc_action);
has_view_action = has_view_action || action == kIntentActionView;
if (!action) {
continue;
}
action_condition_values.push_back(std::make_unique<apps::ConditionValue>(
action, apps::PatternMatchType::kLiteral));
}
if (!action_condition_values.empty()) {
auto action_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kAction, std::move(action_condition_values));
intent_filter->conditions.push_back(std::move(action_condition));
}
bool is_mime_file_filter =
has_view_action && arc_intent_filter.mime_types().size() > 0;
bool is_file_extension_filter = IsFileExtensionFilter(arc_intent_filter);
bool is_file_filter = is_mime_file_filter || is_file_extension_filter;
// Don't allow scheme/ host for ARC view file filters.
if (!is_file_filter) {
apps::ConditionValues scheme_condition_values;
for (auto& scheme : arc_intent_filter.schemes()) {
scheme_condition_values.push_back(std::make_unique<apps::ConditionValue>(
scheme, apps::PatternMatchType::kLiteral));
}
if (!scheme_condition_values.empty()) {
auto scheme_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kScheme, std::move(scheme_condition_values));
intent_filter->conditions.push_back(std::move(scheme_condition));
}
apps::ConditionValues host_condition_values;
for (auto& authority : arc_intent_filter.authorities()) {
auto match_type = authority.wild() ? apps::PatternMatchType::kSuffix
: apps::PatternMatchType::kLiteral;
host_condition_values.push_back(
std::make_unique<apps::ConditionValue>(authority.host(), match_type));
}
if (!host_condition_values.empty()) {
// It's common for Android apps to include duplicate host conditions, we
// can de-duplicate these to reduce time/space usage down the line.
std::sort(
host_condition_values.begin(), host_condition_values.end(),
[](const apps::ConditionValuePtr& v1,
const apps::ConditionValuePtr& v2) -> bool {
return v1->value < v2->value ||
(v1->value == v2->value && v1->match_type < v2->match_type);
});
host_condition_values.erase(
std::unique(host_condition_values.begin(),
host_condition_values.end(),
[](const apps::ConditionValuePtr& v1,
const apps::ConditionValuePtr& v2) -> bool {
return *v1 == *v2;
}),
host_condition_values.end());
auto host_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kAuthority, std::move(host_condition_values));
intent_filter->conditions.push_back(std::move(host_condition));
}
}
apps::ConditionValues path_condition_values;
bool has_invalid_path = false;
for (auto& path : arc_intent_filter.paths()) {
apps::ConditionValuePtr path_condition_value =
ConvertArcPatternMatcherToConditionValue(path);
if (path_condition_value) {
path_condition_values.push_back(std::move(path_condition_value));
} else {
has_invalid_path = true;
}
}
// If there is path condition set in ARC app, but we cannot get valid path,
// it is likely that the only path condition set in ARC is value that we
// cannot handle. We should not create this intent filter because empty path
// condition means it matches with any path, which is different from what it
// is expected.
if (path_condition_values.empty() && has_invalid_path) {
return nullptr;
}
// For ARC apps, specifying a path is optional. For any intent filters which
// match every URL on a host with a "view" action, add a path which matches
// everything to ensure the filter is treated as a supported link.
if (path_condition_values.empty() && has_view_action &&
arc_intent_filter.authorities().size() > 0 &&
arc_intent_filter.schemes().size() > 0) {
path_condition_values.push_back(std::make_unique<apps::ConditionValue>(
"/", apps::PatternMatchType::kPrefix));
}
// For path file filters, extract the desired file extension from the path
// fields listed in the intent filter and add it to the new filter as a
// general kFile condition.
if (is_file_extension_filter) {
// Convert the path condition values into extension condition values.
apps::ConditionValues ext_condition_values =
ConvertPathToExtensionConditionValues(std::move(path_condition_values));
// If this is a path file filter without any valid file extensions, then the
// entire intent filter is invalid.
if (ext_condition_values.size() == 0) {
return nullptr;
}
// Wrap any found extension condition values into one file condition.
auto file_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kFile, std::move(ext_condition_values));
intent_filter->conditions.push_back(std::move(file_condition));
} else if (!path_condition_values.empty()) {
auto path_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kPath, std::move(path_condition_values));
intent_filter->conditions.push_back(std::move(path_condition));
}
if (!is_file_extension_filter) {
apps::ConditionValues mime_type_condition_values;
for (auto& mime_type : arc_intent_filter.mime_types()) {
mime_type_condition_values.push_back(
std::make_unique<apps::ConditionValue>(
mime_type, apps::PatternMatchType::kMimeType));
}
if (!mime_type_condition_values.empty()) {
// For ARC view file intents, save the mime type conditions under kFile
// instead of kMimeType to maintain consistency with view file intents of
// other app types.
if (is_mime_file_filter) {
auto file_type_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kFile, std::move(mime_type_condition_values));
intent_filter->conditions.push_back(std::move(file_type_condition));
} else {
auto mime_type_condition = std::make_unique<apps::Condition>(
apps::ConditionType::kMimeType,
std::move(mime_type_condition_values));
intent_filter->conditions.push_back(std::move(mime_type_condition));
}
}
}
if (!arc_intent_filter.activity_name().empty()) {
intent_filter->activity_name = arc_intent_filter.activity_name();
}
if (!arc_intent_filter.activity_label().empty()) {
intent_filter->activity_label = arc_intent_filter.activity_label();
}
return intent_filter;
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace apps_util