chromium/chrome/browser/apps/app_service/intent_util.cc

// 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/common/extensions/api/file_browser_handlers/file_browser_handler.h"
#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
#include "extensions/common/manifest_handlers/action_handlers_handler.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/components/arc/mojom/intent_helper.mojom-shared.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "chrome/browser/ash/app_list/arc/intent.h"
#include "chrome/browser/ash/fusebox/fusebox_server.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"
#include "components/arc/intent_helper/intent_constants.h"
#include "components/arc/intent_helper/intent_filter.h"
#include "net/base/filename_util.h"
#include "storage/browser/file_system/file_system_url.h"
#endif

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_ASH)
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 (path.match_type() < arc::mojom::PatternType::kMinValue ||
      path.match_type() > arc::mojom::PatternType::kMaxValue) {
    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::mojom::PatternType::PATTERN_LITERAL:
      match_type = apps::PatternMatchType::kLiteral;
      break;
    case arc::mojom::PatternType::PATTERN_PREFIX:
      match_type = apps::PatternMatchType::kPrefix;
      break;
    case arc::mojom::PatternType::PATTERN_SIMPLE_GLOB:
      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::mojom::PatternType::PATTERN_ADVANCED_GLOB:
    case arc::mojom::PatternType::PATTERN_SUFFIX:
    case arc::mojom::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 = base::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 = base::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 = base::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_ASH)

}  // 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) { … }

#if BUILDFLAG(IS_CHROMEOS_ASH)
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_ASH)

apps::IntentFilters CreateIntentFiltersForChromeApp(
    const extensions::Extension* extension) { … }

apps::IntentFilters CreateIntentFiltersForExtension(
    const extensions::Extension* extension) { … }

apps::IntentFilterPtr CreateNoteTakingFilter() { … }

apps::IntentFilterPtr CreateLockScreenFilter() { … }

#if BUILDFLAG(IS_CHROMEOS_ASH)
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::mojom::PatternType match_type;
          switch (condition_value->match_type) {
            case apps::PatternMatchType::kLiteral:
              match_type = arc::mojom::PatternType::PATTERN_LITERAL;
              break;
            case apps::PatternMatchType::kPrefix:
              match_type = arc::mojom::PatternType::PATTERN_PREFIX;
              break;
            case apps::PatternMatchType::kGlob:
              match_type = arc::mojom::PatternType::PATTERN_SIMPLE_GLOB;
              break;
            case apps::PatternMatchType::kMimeType:
            case apps::PatternMatchType::kFileExtension:
            case apps::PatternMatchType::kIsDirectory:
            case apps::PatternMatchType::kSuffix:
              NOTREACHED_IN_MIGRATION();
              return arc::IntentFilter();
          }
          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_IN_MIGRATION();
        return arc::IntentFilter();
    }
  }
  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_ASH)

#if BUILDFLAG(IS_CHROMEOS)
crosapi::mojom::IntentPtr ConvertAppServiceToCrosapiIntent(
    const apps::IntentPtr& app_service_intent,
    Profile* profile) {
  auto crosapi_intent = crosapi::mojom::Intent::New();
  crosapi_intent->action = app_service_intent->action;
  if (app_service_intent->url.has_value()) {
    crosapi_intent->url = app_service_intent->url.value();
  }
  if (app_service_intent->mime_type.has_value()) {
    crosapi_intent->mime_type = app_service_intent->mime_type.value();
  }
  if (app_service_intent->share_text.has_value()) {
    crosapi_intent->share_text = app_service_intent->share_text.value();
  }
  if (app_service_intent->share_title.has_value()) {
    crosapi_intent->share_title = app_service_intent->share_title.value();
  }
#if BUILDFLAG(IS_CHROMEOS_ASH)
  if (!app_service_intent->files.empty() && profile) {
    std::vector<crosapi::mojom::IntentFilePtr> crosapi_files;
    for (const auto& file : app_service_intent->files) {
      if (file->url.SchemeIsFile()) {
        auto crosapi_file = crosapi::mojom::IntentFile::New();
        net::FileURLToFilePath(file->url, &crosapi_file->file_path);
        crosapi_file->mime_type = file->mime_type;
        crosapi_files.push_back(std::move(crosapi_file));
      } else if (file->url.SchemeIsFileSystem()) {
        auto file_system_url = apps::GetFileSystemURL(profile, file->url);
        if (file_system_url.is_valid()) {
          base::FilePath path =
              file_system_url.TypeImpliesPathIsReal()
                  ? file_system_url.path()
                  : fusebox::Server::SubstituteFuseboxFilePath(file_system_url);
          if (!path.empty()) {
            auto crosapi_file = crosapi::mojom::IntentFile::New();
            crosapi_file->file_path = std::move(path);
            crosapi_file->mime_type = file->mime_type;
            crosapi_files.push_back(std::move(crosapi_file));
          }
        }
      }
    }
    crosapi_intent->files = std::move(crosapi_files);
  }
#endif
  if (app_service_intent->activity_name.has_value()) {
    crosapi_intent->activity_name = app_service_intent->activity_name.value();
  }
  if (app_service_intent->data.has_value()) {
    crosapi_intent->data = app_service_intent->data.value();
  }
  if (app_service_intent->ui_bypassed.has_value()) {
    crosapi_intent->ui_bypassed = app_service_intent->ui_bypassed.value();
  }
  if (!app_service_intent->extras.empty()) {
    crosapi_intent->extras = app_service_intent->extras;
  }

  return crosapi_intent;
}

apps::IntentPtr CreateAppServiceIntentFromCrosapi(
    const crosapi::mojom::IntentPtr& crosapi_intent,
    Profile* profile) {
  auto app_service_intent =
      std::make_unique<apps::Intent>(crosapi_intent->action);
  if (crosapi_intent->url.has_value()) {
    app_service_intent->url = crosapi_intent->url.value();
  }
  if (crosapi_intent->mime_type.has_value()) {
    app_service_intent->mime_type = crosapi_intent->mime_type.value();
  }
  if (crosapi_intent->share_text.has_value()) {
    app_service_intent->share_text = crosapi_intent->share_text.value();
  }
  if (crosapi_intent->share_title.has_value()) {
    app_service_intent->share_title = crosapi_intent->share_title.value();
  }
  if (crosapi_intent->files.has_value() && profile) {
    std::vector<apps::IntentFilePtr> intent_files;
    for (const auto& file : crosapi_intent->files.value()) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
      auto file_url = apps::GetFileSystemUrl(profile, file->file_path);
      if (file_url.is_empty()) {
        continue;
      }
      auto intent_file = std::make_unique<apps::IntentFile>(file_url);
#else
      auto intent_file = std::make_unique<apps::IntentFile>(GURL());
      // The directory is omitted from the human readable file name.
      intent_file->file_name =
          base::SafeBaseName::Create(file->file_path.BaseName());
#endif
      intent_file->mime_type = file->mime_type;

      intent_files.push_back(std::move(intent_file));
    }
    if (intent_files.size() > 0) {
      app_service_intent->files = std::move(intent_files);
    }
  }
  if (crosapi_intent->activity_name.has_value()) {
    app_service_intent->activity_name = crosapi_intent->activity_name.value();
  }
  if (crosapi_intent->data.has_value()) {
    app_service_intent->data = crosapi_intent->data.value();
  }
  if (crosapi_intent->ui_bypassed.has_value()) {
    app_service_intent->ui_bypassed = crosapi_intent->ui_bypassed.value();
  }
  if (crosapi_intent->extras.has_value()) {
    app_service_intent->extras = crosapi_intent->extras.value();
  }

  return app_service_intent;
}

crosapi::mojom::IntentPtr CreateCrosapiIntentForViewFiles(
    std::vector<base::FilePath> file_paths) {
  auto intent = crosapi::mojom::Intent::New();
  intent->action = kIntentActionView;
  std::vector<crosapi::mojom::IntentFilePtr> crosapi_files;
  for (const auto& file_path : file_paths) {
    auto crosapi_file = crosapi::mojom::IntentFile::New();
    crosapi_file->file_path = file_path;
    crosapi_files.push_back(std::move(crosapi_file));
  }
  intent->files = std::move(crosapi_files);
  return intent;
}

#endif

}  // namespace apps_util