blob: fbe3e452d3745a377ccbd5f7305d7f4776a6922b [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/file_manager/path_util.h"
#include <memory>
#include <string_view>
#include <utility>
#include "ash/constants/ash_switches.h"
#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/pickle.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "chrome/browser/ash/arc/fileapi/arc_documents_provider_root.h"
#include "chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map.h"
#include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h"
#include "chrome/browser/ash/arc/fileapi/chrome_content_provider_url_util.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drive_integration_service_factory.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/fileapi/external_file_url_util.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/ash/fusebox/fusebox_server.h"
#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
#include "chrome/browser/ash/guest_os/guest_os_session_tracker_factory.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service_factory.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/smb_client/smb_service.h"
#include "chrome/browser/ash/smb_client/smb_service_factory.h"
#include "chrome/browser/ash/smb_client/smbfs_share.h"
#include "chrome/browser/download/download_dir_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/experiences/arc/arc_features.h"
#include "chromeos/ash/experiences/arc/arc_util.h"
#include "components/drive/file_system_core_util.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/extension.h"
#include "net/base/filename_util.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/common/file_system/file_system_types.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/cros_system_api/dbus/fusebox/dbus-constants.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/clipboard/file_info.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace file_manager::util {
namespace {
using base::FilePath;
using base::StrCat;
using drive::DriveIntegrationService;
using drive::DriveIntegrationServiceFactory;
using l10n_util::GetStringUTF8;
constexpr char kAndroidFilesMountPointName[] = "android_files";
constexpr char kCrostiniMapGoogleDrive[] = "GoogleDrive";
constexpr char kCrostiniMapLinuxFiles[] = "LinuxFiles";
constexpr char kCrostiniMapMyDrive[] = "MyDrive";
constexpr char kCrostiniMapPlayFiles[] = "PlayFiles";
constexpr char kCrostiniMapSmbFs[] = "SMB";
constexpr char kCrostiniMapFusebox[] = "Fusebox";
constexpr char kCrostiniMapTeamDrives[] = "SharedDrives";
constexpr char kCrostiniMapSharedWithMe[] = "SharedWithMe";
constexpr char kCrostiniMapShortcutsSharedWithMe[] = "ShortcutsSharedWithMe";
constexpr char kFolderNameDownloads[] = "Downloads";
constexpr char kFolderNameMyFiles[] = "MyFiles";
constexpr char kFolderNamePvmDefault[] = "PvmDefault";
constexpr char kFolderNameCamera[] = "Camera";
constexpr char kFolderNameShareCache[] = "ShareCache";
constexpr char kDisplayNameGoogleDrive[] = "Google Drive";
constexpr char kDisplayNameMicrosoftOneDrive[] = "Microsoft OneDrive";
constexpr char kDriveFsDirComputers[] = "Computers";
constexpr char kDriveFsDirSharedWithMe[] = ".files-by-id";
constexpr char kDriveFsDirShortcutsSharedWithMe[] = ".shortcut-targets-by-id";
constexpr char kDriveFsDirRoot[] = "root";
constexpr char kDriveFsDirTeamDrives[] = "team_drives";
constexpr char16_t kFilesAppMimeSources[] = u"fs/sources";
constexpr char16_t kFilesAppSeparator16[] = u"\n";
// Sync with the root name defined with the file provider in ARC++ side.
constexpr FilePath::CharType kArcDownloadRoot[] =
FILE_PATH_LITERAL("/download");
constexpr FilePath::CharType kArcExternalFilesRoot[] =
FILE_PATH_LITERAL("/external_files");
// Sync with the volume provider in ARC++ side.
constexpr char kArcStorageContentUrlPrefix[] =
"content://org.chromium.arc.volumeprovider/";
// A predefined removable media UUID for testing. Defined in
// chromeos/ash/experiences/arc/volume_mounter/arc_volume_mounter_bridge.cc.
// TODO(crbug.com/1274481): Move ash-wide constants to a common place.
constexpr char kArcRemovableMediaUuidForTesting[] =
"00000000000000000000000000000000DEADBEEF";
// The dummy UUID of the MyFiles volume is taken from
// chromeos/ash/experiences/arc/volume_mounter/arc_volume_mounter_bridge.cc.
// TODO(crbug.com/929031): Move MyFiles constants to a common place.
constexpr char kArcMyFilesContentUrlPrefix[] =
"content://org.chromium.arc.volumeprovider/"
"0000000000000000000000000000CAFEF00D2019/";
// Helper function for |ConvertToContentUrls|.
void OnSingleContentUrlResolved(const base::RepeatingClosure& barrier_closure,
std::vector<GURL>* out_urls,
size_t index,
const GURL& url) {
(*out_urls)[index] = url;
barrier_closure.Run();
}
// Helper function for |ConvertToContentUrls|.
void OnAllContentUrlsResolved(
ConvertToContentUrlsCallback callback,
std::unique_ptr<std::vector<GURL>> urls,
std::unique_ptr<std::vector<FilePath>> paths_to_share) {
std::move(callback).Run(*urls, *paths_to_share);
}
// By default, in ChromeOS it uses the $profile_dir/MyFiles however,
// for manual tests/development in linux-chromeos it uses $HOME/Downloads for
// chrome binary and a temp dir for browser tests. The flag
// `--use-myfiles-in-user-data-dir-for-testing` forces the ChromeOS pattern,
// even for non-ChromeOS environments.
bool ShouldMountPrimaryUserDownloads(Profile* profile) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kUseMyFilesInUserDataDirForTesting)) {
return false;
}
if (!base::SysInfo::IsRunningOnChromeOS() &&
user_manager::UserManager::IsInitialized()) {
const user_manager::User* const user =
ash::ProfileHelper::Get()->GetUserByProfile(
profile->GetOriginalProfile());
const user_manager::User* const primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
return user == primary_user;
}
return false;
}
// Extracts the Drive path from the given path located under the legacy Drive
// mount point. Returns an empty path if |path| is not under the legacy Drive
// mount point.
// Example: ExtractLegacyDrivePath("/special/drive-xxx/foo.txt") =>
// "drive/foo.txt"
FilePath ExtractLegacyDrivePath(const FilePath& path) {
std::vector<FilePath::StringType> components = path.GetComponents();
if (components.size() < 3) {
return FilePath();
}
if (components[0] != FILE_PATH_LITERAL("/")) {
return FilePath();
}
if (components[1] != FILE_PATH_LITERAL("special")) {
return FilePath();
}
static const FilePath::CharType kPrefix[] = FILE_PATH_LITERAL("drive");
if (components[2].compare(0, std::size(kPrefix) - 1, kPrefix) != 0) {
return FilePath();
}
FilePath drive_path = drive::util::GetDriveGrandRootPath();
for (size_t i = 3; i < components.size(); ++i) {
drive_path = drive_path.Append(components[i]);
}
return drive_path;
}
// Extracts the volume name of a removable device. |relative_path| is expected
// to be of the form <volume name>/..., which is relative to /media/removable.
std::string ExtractVolumeNameFromRelativePathForRemovableMedia(
const FilePath& relative_path) {
std::vector<FilePath::StringType> components = relative_path.GetComponents();
if (components.empty()) {
LOG(WARNING) << "Failed to extract volume name from relative path: "
<< relative_path;
return std::string();
}
return components[0];
}
// Returns the source path of a removable device using its volume name as a key.
// An empty string is returned when it fails to get a valid mount point from
// DiskMountManager.
std::string GetSourcePathForRemovableMedia(const std::string& volume_name) {
const std::string mount_path(
base::StringPrintf("%s/%s", kRemovableMediaPath, volume_name.c_str()));
const auto& mount_points =
ash::disks::DiskMountManager::GetInstance()->mount_points();
const auto found = mount_points.find(mount_path);
return found == mount_points.end() ? std::string() : found->source_path;
}
// Returns the UUID of a removable device using its volume name as a key.
// An empty string is returned when it fails to get valid source path and disk
// from DiskMountManager.
std::string GetFsUuidForRemovableMedia(const std::string& volume_name) {
const std::string source_path = GetSourcePathForRemovableMedia(volume_name);
if (source_path.empty()) {
LOG(WARNING) << "No source path is found for volume name: " << volume_name;
return std::string();
}
const ash::disks::Disk* disk =
ash::disks::DiskMountManager::GetInstance()->FindDiskBySourcePath(
source_path);
std::string fs_uuid = disk == nullptr ? std::string() : disk->fs_uuid();
if (fs_uuid.empty()) {
LOG(WARNING) << "No UUID is found for volume name: " << volume_name;
}
return fs_uuid;
}
// Same as parent.AppendRelativePath(child, path) except that it allows
// parent == child, in which case path is unchanged.
bool AppendRelativePath(const FilePath& parent,
const FilePath& child,
FilePath* path) {
return child == parent || parent.AppendRelativePath(child, path);
}
// Same as parent.AppendRelativePath(child, path) except that the relative path
// to the child is just set to `path` (instead of being appended).
// Note that `path` is always cleared at the beginning, so it becomes empty when
// parent.IsParent(child) does not hold.
bool SetRelativePath(const FilePath& parent,
const FilePath& child,
FilePath* path) {
path->clear();
return parent.AppendRelativePath(child, path);
}
// Translates known DriveFS folders into their localized message id.
std::optional<int> DriveFsFolderToMessageId(std::string folder) {
if (folder == kDriveFsDirRoot) {
return IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL;
} else if (folder == kDriveFsDirTeamDrives) {
return IDS_FILE_BROWSER_DRIVE_SHARED_DRIVES_LABEL;
} else if (folder == kDriveFsDirComputers) {
return IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL;
} else if (folder == kDriveFsDirSharedWithMe) {
return IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL;
} else if (folder == kDriveFsDirShortcutsSharedWithMe) {
return IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL;
}
return std::nullopt;
}
// Translates special My Files folders into their localized message id.
std::optional<int> MyFilesFolderToMessageId(std::string folder) {
if (folder == kFolderNameDownloads) {
return IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_LABEL;
} else if (folder == kFolderNamePvmDefault) {
return IDS_FILE_BROWSER_PLUGIN_VM_DIRECTORY_LABEL;
} else if (folder == kFolderNameCamera) {
return IDS_FILE_BROWSER_CAMERA_DIRECTORY_LABEL;
}
return std::nullopt;
}
std::string GetMountPointNameForProfile(Profile* profile,
const std::string& folder_name) {
// To distinguish profiles in multi-profile session, we append user name hash
// to folder_name. Note that some profiles (like login or test profiles)
// are not associated with an user account. In that case, no suffix is added
// because such a profile never belongs to a multi-profile session.
const user_manager::User* const user =
user_manager::UserManager::IsInitialized()
? ash::ProfileHelper::Get()->GetUserByProfile(
profile->GetOriginalProfile())
: nullptr;
const std::string id = user ? "-" + user->username_hash() : "";
return base::EscapeQueryParamValue(folder_name + id, false);
}
} // namespace
const FilePath::CharType kFuseBoxMediaPath[] =
FILE_PATH_LITERAL("/media/fuse/fusebox");
const FilePath::CharType kFuseBoxMediaSlashPath[] =
FILE_PATH_LITERAL("/media/fuse/fusebox/");
const FilePath::CharType kRemovableMediaPath[] =
FILE_PATH_LITERAL("/media/removable");
const FilePath::CharType kAndroidFilesPath[] =
FILE_PATH_LITERAL("/run/arc/sdcard/write/emulated/0");
const FilePath::CharType kGuestOsAndroidFilesPath[] =
FILE_PATH_LITERAL("/media/fuse/android_files");
const FilePath::CharType kSystemFontsPath[] =
FILE_PATH_LITERAL("/usr/share/fonts");
const FilePath::CharType kArchiveMountPath[] =
FILE_PATH_LITERAL("/media/archive");
const char kFuseBox[] = "fusebox";
// The actual value of this string is arbitrary (other than, per the comments
// in external_mount_points.h, mount names should not contain '/'), but this
// nonsense-looking word (based on the first two letters of each of "fuse box
// mount name") is unique enough so that, when seeing "fubomona" in a log
// message (e.g. in a storage::FileSystemURL's debug string form), code-
// searching for that string should quickly find this definition here (and the
// kFuseBoxMountNamePrefix name that code-search can find references for).
//
// This is just a prefix. The complete mount name (for FuseBox mounts, not for
// storage::ExternalMountPoints generally) looks like "fubomona:volumetype.etc"
// where the volumetype (e.g. "adp", "fsp") acts like a namespace so that ADP's
// "etc" format won't accidentally conflict with FSP's "etc" format.
//
// This means that the "volumetype.etc" string value *can* be the same as a
// FuseBox subdir string value, as they're both prefixed with "volumetype.",
// but they don't *have* to be. Specifically, the "etc" may contain identifiers
// that other in-process Chromium code wants to parse but those identifiers
// might be longer than Linux's NAME_MAX.
const char kFuseBoxMountNamePrefix[] = "fubomona:";
const char kFuseBoxSubdirPrefixADP[] = "adp.";
const char kFuseBoxSubdirPrefixFSP[] = "fsp.";
const char kFuseBoxSubdirPrefixLOC[] = "loc.";
const char kFuseBoxSubdirPrefixMTP[] = "mtp.";
const char kFuseBoxSubdirPrefixTMP[] = "tmp.";
const char kShareCacheMountPointName[] = "ShareCache";
const url::Origin& GetFilesAppOrigin() {
static const base::NoDestructor<url::Origin> origin(
[] { return url::Origin::Create(GetFileManagerURL()); }());
return *origin;
}
FilePath GetDownloadsFolderForProfile(Profile* profile) {
// Check if FilesApp has a registered path already. This happens for tests.
const std::string mount_point_name =
util::GetDownloadsMountPointName(profile);
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
FilePath path;
if (mount_points->GetRegisteredPath(mount_point_name, &path)) {
return path.AppendASCII(kFolderNameDownloads);
}
// Return $HOME/Downloads as Download folder.
if (ShouldMountPrimaryUserDownloads(profile)) {
return DownloadPrefs::GetDefaultDownloadDirectory();
}
// Return <cryptohome>/MyFiles/Downloads if it feature is enabled.
return profile->GetPath()
.AppendASCII(kFolderNameMyFiles)
.AppendASCII(kFolderNameDownloads);
}
FilePath GetMyFilesFolderForProfile(Profile* profile) {
// Check if FilesApp has a registered path already. This happens for tests.
const std::string mount_point_name =
util::GetDownloadsMountPointName(profile);
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
FilePath path;
if (mount_points->GetRegisteredPath(mount_point_name, &path)) {
return path;
}
// Return $HOME/Downloads as MyFiles folder.
if (ShouldMountPrimaryUserDownloads(profile)) {
return DownloadPrefs::GetDefaultDownloadDirectory();
}
// Return <cryptohome>/MyFiles.
return profile->GetPath().AppendASCII(kFolderNameMyFiles);
}
FilePath GetShareCacheFilePath(Profile* profile) {
return profile->GetPath().AppendASCII(kFolderNameShareCache);
}
FilePath GetAndroidFilesPath() {
// Check if Android has a registered path already. This happens for tests.
const std::string mount_point_name = util::GetAndroidFilesMountPointName();
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
FilePath path;
if (mount_points->GetRegisteredPath(mount_point_name, &path)) {
return path;
}
if (arc::IsArcVmEnabled()) {
return FilePath(file_manager::util::kGuestOsAndroidFilesPath);
}
return FilePath(file_manager::util::kAndroidFilesPath);
}
bool MigratePathFromOldFormat(Profile* profile,
const FilePath& old_base,
const FilePath& old_path,
FilePath* new_path) {
// Special case, migrating /home/chronos/user which is set early (before a
// profile is attached to the browser process) to default to
// /home/chronos/u-{hash}/MyFiles/Downloads.
if (old_path == old_base && old_path == FilePath("/home/chronos/user")) {
*new_path = GetDownloadsFolderForProfile(profile);
return true;
}
// If the `new_base` is already parent of `old_path`, no need to migrate.
const FilePath new_base = GetMyFilesFolderForProfile(profile);
if (new_base.IsParent(old_path)) {
return false;
}
FilePath relative;
if (old_base.AppendRelativePath(old_path, &relative)) {
*new_path = new_base.Append(relative);
return old_path != *new_path;
}
return false;
}
bool MigrateToDriveFs(Profile* profile,
const FilePath& old_path,
FilePath* new_path) {
const auto* user = ash::ProfileHelper::Get()->GetUserByProfile(profile);
DriveIntegrationService* const service =
DriveIntegrationServiceFactory::FindForProfile(profile);
if (!service || !service->is_enabled() || !user ||
!user->GetAccountId().HasAccountIdKey()) {
return false;
}
*new_path = service->GetMountPointPath();
return drive::util::GetDriveGrandRootPath().AppendRelativePath(
ExtractLegacyDrivePath(old_path), new_path);
}
std::string GetDownloadsMountPointName(Profile* profile) {
return GetMountPointNameForProfile(profile, kFolderNameDownloads);
}
std::string GetShareCacheMountPointName(Profile* profile) {
return GetMountPointNameForProfile(profile, kFolderNameShareCache);
}
std::string GetAndroidFilesMountPointName() {
return kAndroidFilesMountPointName;
}
// Returns true if |name| is a known Bruschetta mount point name (e.g. as
// produced by GetGuestOsMountPointName), and populates |guest_id|.
bool IsBruschettaMountPointName(const std::string& name,
Profile* profile,
guest_os::GuestId* guest_id) {
auto* service = guest_os::GuestOsServiceFactory::GetForProfile(profile);
if (!service) {
return false;
}
auto* registry = service->MountProviderRegistry();
for (const auto id : registry->List()) {
auto* provider = registry->Get(id);
if (provider->vm_type() != vm_tools::apps::VmType::BRUSCHETTA) {
continue;
}
if (name == util::GetGuestOsMountPointName(profile, provider->GuestId())) {
*guest_id = provider->GuestId();
return true;
}
}
return false;
}
std::string GetCrostiniMountPointName(Profile* profile) {
// crostini_<hash>_termina_penguin
return base::JoinString(
{"crostini", crostini::CryptohomeIdForProfile(profile),
crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName},
"_");
}
std::string GetGuestOsMountPointName(Profile* profile,
const guest_os::GuestId& id) {
if (id.vm_type == guest_os::VmType::ARCVM) {
return kAndroidFilesMountPointName;
}
if (id == crostini::DefaultContainerId()) {
return GetCrostiniMountPointName(profile);
}
return base::JoinString(
{"guestos", ash::ProfileHelper::GetUserIdHashFromProfile(profile),
base::EscapeAllExceptUnreserved(id.vm_name),
base::EscapeAllExceptUnreserved(id.container_name)},
"+");
}
FilePath GetCrostiniMountDirectory(Profile* profile) {
return FilePath("/media/fuse/" + GetCrostiniMountPointName(profile));
}
FilePath GetGuestOsMountDirectory(std::string mountPointName) {
return FilePath("/media/fuse/" + mountPointName);
}
bool ConvertFileSystemURLToPathInsideVM(
Profile* profile,
const storage::FileSystemURL& file_system_url,
const FilePath& vm_mount,
bool map_crostini_home,
FilePath* inside) {
const std::string& id(file_system_url.mount_filesystem_id());
// File system root requires strip trailing separator.
FilePath path =
FilePath(file_system_url.virtual_path()).StripTrailingSeparators();
// Include drive if using DriveFS.
std::string mount_point_name_drive;
if (DriveIntegrationService* const service =
DriveIntegrationServiceFactory::FindForProfile(profile)) {
mount_point_name_drive = service->GetMountPointPath().BaseName().value();
}
// Reformat virtual_path() from:
// <mount_label>/path/to/file
// To:
// <vm_mount>/<mapping>/path/to/file
// If |map_crostini_home| is set, paths in crostini mount map to:
// /<home-directory>/path/to/file
FilePath base_to_exclude(id);
guest_os::GuestId guest_id("", "");
if (id == GetDownloadsMountPointName(profile)) {
// MyFiles.
*inside = vm_mount.Append(kFolderNameMyFiles);
} else if (!mount_point_name_drive.empty() && id == mount_point_name_drive) {
// DriveFS has some more complicated mappings.
std::vector<FilePath::StringType> components = path.GetComponents();
*inside = vm_mount.Append(kCrostiniMapGoogleDrive);
if (components.size() >= 2 && components[1] == kDriveFsDirRoot) {
// root -> MyDrive.
base_to_exclude = base_to_exclude.Append(kDriveFsDirRoot);
*inside = inside->Append(kCrostiniMapMyDrive);
} else if (components.size() >= 2 &&
components[1] == kDriveFsDirTeamDrives) {
// team_drives -> SharedDrives.
base_to_exclude = base_to_exclude.Append(kDriveFsDirTeamDrives);
*inside = inside->Append(kCrostiniMapTeamDrives);
} else if (components.size() >= 2 &&
components[1] == kDriveFsDirSharedWithMe) {
// .files-by-id -> SharedWithMe.
base_to_exclude = base_to_exclude.Append(kDriveFsDirSharedWithMe);
*inside = inside->Append(kCrostiniMapSharedWithMe);
} else if (components.size() >= 2 &&
components[1] == kDriveFsDirShortcutsSharedWithMe) {
// .shortcut-targets-by-id -> ShortcutsSharedWithMe.
base_to_exclude =
base_to_exclude.Append(kDriveFsDirShortcutsSharedWithMe);
*inside = inside->Append(kCrostiniMapShortcutsSharedWithMe);
}
// Computers -> Computers
} else if (id == ash::kSystemMountNameRemovable) {
// Removable.
*inside = vm_mount.Append(ash::kSystemMountNameRemovable);
} else if (id == GetAndroidFilesMountPointName()) {
// PlayFiles.
*inside = vm_mount.Append(kCrostiniMapPlayFiles);
} else if (id == ash::kSystemMountNameArchive) {
// Archive.
*inside = vm_mount.Append(ash::kSystemMountNameArchive);
} else if (id == GetCrostiniMountPointName(profile)) {
// Crostini.
if (map_crostini_home) {
auto container_info =
guest_os::GuestOsSessionTrackerFactory::GetForProfile(profile)
->GetInfo(crostini::DefaultContainerId());
if (!container_info) {
return false;
}
*inside = container_info->homedir;
} else {
*inside = vm_mount.Append(kCrostiniMapLinuxFiles);
}
} else if (IsBruschettaMountPointName(id, profile, &guest_id)) {
// Bruschetta: use path to homedir, which is currently the empty string
// because sftp-server inside the VM runs in the homedir.
auto container_info =
guest_os::GuestOsSessionTrackerFactory::GetForProfile(profile)->GetInfo(
guest_id);
if (!container_info) {
return false;
}
*inside = container_info->homedir;
} else if (file_system_url.type() == storage::kFileSystemTypeSmbFs) {
// SMB. Do not assume the share is currently accessible via SmbService
// as this function is called during unmount when SmbFsShare is
// destroyed. The only information safely available is the stable
// mount ID.
*inside = vm_mount.Append(kCrostiniMapSmbFs);
*inside = inside->Append(id);
} else if (file_system_url.type() == storage::kFileSystemTypeFuseBox) {
// `inside` should be of the form <vm_mount>/Fusebox/<subdir>/foo/bar.
// Since <subdir> is not available in either `id` or `path`, we look up
// `file_system_url.path()` (the raw VFS path), which is of the form
// /media/fuse/fusebox/<subdir>/foo/bar.
*inside = vm_mount.Append(kCrostiniMapFusebox);
return base::FilePath(kFuseBoxMediaPath)
.AppendRelativePath(file_system_url.path(), inside);
} else {
return false;
}
return AppendRelativePath(base_to_exclude, path, inside);
}
bool ConvertFileSystemURLToPathInsideCrostini(
Profile* profile,
const storage::FileSystemURL& file_system_url,
FilePath* inside) {
return ConvertFileSystemURLToPathInsideVM(
profile, file_system_url, crostini::ContainerChromeOSBaseDirectory(),
/*map_crostini_home=*/true, inside);
}
bool ConvertFuseboxMonikerPathToPathInsideVM(const base::FilePath& path,
const base::FilePath& vm_mount,
base::FilePath* inside) {
base::FilePath relative_path;
if (!base::FilePath(fusebox::kMonikerFilenamePrefixWithTrailingSlash)
.AppendRelativePath(path, &relative_path)) {
return false;
}
*inside = vm_mount.Append(kCrostiniMapFusebox)
.Append(fusebox::kMonikerSubdir)
.Append(relative_path);
return true;
}
bool ConvertPathInsideVMToFileSystemURL(
Profile* profile,
const FilePath& inside,
const FilePath& vm_mount,
bool map_crostini_home,
storage::FileSystemURL* file_system_url) {
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
// Include drive if using DriveFS.
std::string mount_point_name_drive;
if (DriveIntegrationService* const service =
DriveIntegrationServiceFactory::FindForProfile(profile)) {
mount_point_name_drive = service->GetMountPointPath().BaseName().value();
}
std::string mount_name;
FilePath path;
FilePath relative_path;
if (map_crostini_home) {
auto container_info =
guest_os::GuestOsSessionTrackerFactory::GetForProfile(profile)->GetInfo(
crostini::DefaultContainerId());
if (container_info &&
AppendRelativePath(container_info->homedir, inside, &relative_path)) {
*file_system_url = mount_points->CreateExternalFileSystemURL(
blink::StorageKey::CreateFirstParty(GetFilesAppOrigin()),
GetCrostiniMountPointName(profile), relative_path);
return file_system_url->is_valid();
}
}
if (!vm_mount.AppendRelativePath(inside, &path)) {
return false;
}
if (AppendRelativePath(FilePath(kFolderNameMyFiles), path, &relative_path)) {
// MyFiles.
mount_name = GetDownloadsMountPointName(profile);
path = relative_path;
} else if (AppendRelativePath(FilePath(kCrostiniMapLinuxFiles), path,
&relative_path)) {
// LinuxFiles.
mount_name = GetCrostiniMountPointName(profile);
path = relative_path;
} else if (FilePath(kCrostiniMapGoogleDrive)
.AppendRelativePath(path, &relative_path)) {
mount_name = mount_point_name_drive;
path = relative_path;
relative_path.clear();
// GoogleDrive
if (AppendRelativePath(FilePath(kCrostiniMapMyDrive), path,
&relative_path)) {
// /GoogleDrive/MyDrive -> root
path = FilePath(kDriveFsDirRoot).Append(relative_path);
} else if (AppendRelativePath(FilePath(kCrostiniMapTeamDrives), path,
&relative_path)) {
// /GoogleDrive/SharedDrive -> team_drives
path = FilePath(kDriveFsDirTeamDrives).Append(relative_path);
} else if (AppendRelativePath(FilePath(kCrostiniMapSharedWithMe), path,
&relative_path)) {
// /GoogleDrive/SharedWithMe -> .files-by-id
path = FilePath(kDriveFsDirSharedWithMe).Append(relative_path);
} else if (AppendRelativePath(FilePath(kCrostiniMapShortcutsSharedWithMe),
path, &relative_path)) {
// /GoogleDrive/ShortcutsSharedWithMe -> .shortcut-targets-by-id
path = FilePath(kDriveFsDirShortcutsSharedWithMe).Append(relative_path);
}
// Computers -> Computers
} else if (FilePath(ash::kSystemMountNameRemovable)
.AppendRelativePath(path, &relative_path)) {
// Removable subdirs only.
mount_name = ash::kSystemMountNameRemovable;
path = relative_path;
} else if (AppendRelativePath(FilePath(kCrostiniMapPlayFiles), path,
&relative_path)) {
// PlayFiles.
mount_name = GetAndroidFilesMountPointName();
path = relative_path;
} else if (FilePath(ash::kSystemMountNameArchive)
.AppendRelativePath(path, &relative_path)) {
// Archive subdirs only.
mount_name = ash::kSystemMountNameArchive;
path = relative_path;
} else if (FilePath(kCrostiniMapSmbFs)
.AppendRelativePath(path, &relative_path)) {
// SMB.
std::vector<FilePath::StringType> components =
relative_path.GetComponents();
if (components.size() < 1) {
return false;
}
mount_name = components[0];
path.clear();
FilePath(mount_name).AppendRelativePath(relative_path, &path);
} else if (FilePath(kCrostiniMapFusebox)
.AppendRelativePath(path, &relative_path)) {
// For Fusebox files, `mount_name` is the first component of the virtual
// path, and `path` is the remaining part of the virtual path.
const FilePath absolute_path =
FilePath(kFuseBoxMediaPath).Append(relative_path);
FilePath virtual_path;
if (!mount_points->GetVirtualPath(absolute_path, &virtual_path)) {
return false;
}
const std::vector<FilePath::StringType> components =
virtual_path.GetComponents();
if (components.size() < 1) {
return false;
}
mount_name = components[0];
SetRelativePath(FilePath(mount_name), virtual_path, &path);
} else {
return false;
}
*file_system_url = mount_points->CreateExternalFileSystemURL(
blink::StorageKey::CreateFirstParty(GetFilesAppOrigin()), mount_name,
path);
return file_system_url->is_valid();
}
bool ConvertPathToArcUrl(const FilePath& path,
GURL* const arc_url_out,
bool* const requires_sharing_out) {
DCHECK(arc_url_out);
DCHECK(requires_sharing_out);
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
*requires_sharing_out = false;
// Obtain the primary profile. This information is required because currently
// only the file systems for the primary profile is exposed to ARC.
Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
if (!primary_profile) {
return false;
}
// Convert paths under primary profile's Downloads directory.
FilePath primary_downloads = GetDownloadsFolderForProfile(primary_profile);
FilePath result_path(kArcDownloadRoot);
if (primary_downloads.AppendRelativePath(path, &result_path)) {
*arc_url_out = GURL(kArcStorageContentUrlPrefix)
.Resolve(base::EscapePath(result_path.AsUTF8Unsafe()));
return true;
}
// Convert paths under Android files root (e.g.,
// /run/arc/sdcard/write/emulated/0).
result_path = FilePath(kArcExternalFilesRoot);
if (GetAndroidFilesPath().AppendRelativePath(path, &result_path)) {
*arc_url_out = GURL(kArcStorageContentUrlPrefix)
.Resolve(base::EscapePath(result_path.AsUTF8Unsafe()));
return true;
}
// Convert paths under /media/removable.
FilePath relative_path;
if (SetRelativePath(FilePath(kRemovableMediaPath), path, &relative_path)) {
const std::string volume_name =
ExtractVolumeNameFromRelativePathForRemovableMedia(relative_path);
if (volume_name.empty()) {
return false;
}
const std::string fs_uuid = GetFsUuidForRemovableMedia(volume_name);
// Replace the volume name in the relative path with the UUID.
// When no UUID is found for the volume, use the predefined one for testing.
FilePath relative_path_with_uuid =
FilePath(fs_uuid.empty() ? kArcRemovableMediaUuidForTesting : fs_uuid);
if (!FilePath(volume_name)
.AppendRelativePath(relative_path, &relative_path_with_uuid)) {
LOG(WARNING) << "Failed to replace volume name \"" << volume_name
<< "\" in relative path \"" << relative_path
<< "\" with UUID \"" << fs_uuid << "\"";
return false;
}
*arc_url_out =
GURL(kArcStorageContentUrlPrefix)
.Resolve(base::EscapePath(relative_path_with_uuid.AsUTF8Unsafe()));
return true;
}
// Convert paths under MyFiles.
if (SetRelativePath(GetMyFilesFolderForProfile(primary_profile), path,
&relative_path)) {
*arc_url_out = GURL(kArcMyFilesContentUrlPrefix)
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
return true;
}
bool force_external = false;
// Convert paths under DriveFS.
const DriveIntegrationService* integration_service =
drive::util::GetIntegrationServiceByProfile(primary_profile);
if (integration_service &&
SetRelativePath(integration_service->GetMountPointPath(), path,
&relative_path)) {
// TODO(b/157297349) Remove this condition.
if (arc::IsArcVmEnabled()) {
*arc_url_out =
GURL("content://org.chromium.arc.volumeprovider/MyDrive/")
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
*requires_sharing_out = true;
return true;
}
// TODO(b/157297349): For backward compatibility with ARC++ P, force
// external URL for DriveFS.
force_external = true;
}
// Convert paths under /media/fuse/crostini_...
if (SetRelativePath(GetCrostiniMountDirectory(primary_profile), path,
&relative_path)) {
if (arc::IsArcVmEnabled()) {
// For ARCVM, we can use ArcVolumeProvider URL and ask Seneschal to share
// the path to ARCVM so ARCVM does not need to talk through Chrome.
*arc_url_out =
GURL("content://org.chromium.arc.volumeprovider/crostini/")
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
*requires_sharing_out = true;
return true;
}
// Use ChromeContentProvider for ARC++ Container to proxy through Chrome.
force_external = true;
}
// Convert path under /media/archive.
if (SetRelativePath(FilePath(kArchiveMountPath), path, &relative_path)) {
// TODO(b/157297349) Remove this condition.
if (arc::IsArcVmEnabled()) {
*arc_url_out =
GURL("content://org.chromium.arc.volumeprovider/archive/")
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
*requires_sharing_out = true;
return true;
}
force_external = true;
}
// Convert path under /media/fuse/smb-...
if (ash::smb_client::SmbService* const service =
ash::smb_client::SmbServiceFactory::Get(primary_profile)) {
if (const ash::smb_client::SmbFsShare* const share =
service->GetSmbFsShareForPath(path)) {
if (SetRelativePath(share->mount_path(), path, &relative_path)) {
// TODO(b/157297349) Remove this condition.
if (arc::IsArcVmEnabled()) {
*arc_url_out =
GURL(StrCat({"content://org.chromium.arc.volumeprovider/smb/",
share->mount_id(), "/"}))
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
*requires_sharing_out = true;
return true;
}
force_external = true;
}
}
}
// Convert path under /media/fuse/fusebox.
if (SetRelativePath(FilePath(kFuseBoxMediaPath), path, &relative_path)) {
if (arc::IsArcVmEnabled()) {
*arc_url_out =
GURL("content://org.chromium.arc.volumeprovider/fusebox/")
.Resolve(base::EscapePath(relative_path.AsUTF8Unsafe()));
*requires_sharing_out = true;
return true;
}
// Use ChromeContentProvider for ARC++ container.
force_external = true;
}
// ShareCache files are not available as mount-passthrough and must be shared
// through ChromeContentProvider.
if (GetShareCacheFilePath(primary_profile).IsParent(path)) {
force_external = true;
}
// Convert paths under /special or other paths forced to use external URL.
GURL external_file_url =
ash::CreateExternalFileURLFromPath(primary_profile, path, force_external);
if (!external_file_url.is_empty()) {
*arc_url_out = arc::EncodeToChromeContentProviderUrl(external_file_url);
return true;
}
// TODO(kinaba): Add conversion logic once other file systems are supported.
return false;
}
base::FilePath ConvertFileSystemURLToPathForSharingWithArc(
const storage::FileSystemURL& file_system_url) {
switch (file_system_url.type()) {
// Use Fusebox path for FSP and MTP.
case storage::kFileSystemTypeProvided:
case storage::kFileSystemTypeDeviceMediaAsFileStorage:
return fusebox::Server::SubstituteFuseboxFilePath(file_system_url);
default:
return file_system_url.path();
}
}
void ConvertToContentUrls(
Profile* profile,
const std::vector<storage::FileSystemURL>& file_system_urls,
ConvertToContentUrlsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (file_system_urls.empty()) {
std::move(callback).Run(std::vector<GURL>(), std::vector<FilePath>());
return;
}
auto* documents_provider_root_map =
profile ? arc::ArcDocumentsProviderRootMap::GetForBrowserContext(profile)
: nullptr;
// To keep the original order, prefill |out_urls| with empty URLs and
// specify index when updating it like (*out_urls)[index] = url.
auto out_urls = std::make_unique<std::vector<GURL>>(file_system_urls.size());
auto* out_urls_ptr = out_urls.get();
auto paths_to_share = std::make_unique<std::vector<FilePath>>();
auto* paths_to_share_ptr = paths_to_share.get();
auto barrier = base::BarrierClosure(
file_system_urls.size(),
base::BindOnce(&OnAllContentUrlsResolved, std::move(callback),
std::move(out_urls), std::move(paths_to_share)));
auto single_content_url_callback =
base::BindRepeating(&OnSingleContentUrlResolved, barrier, out_urls_ptr);
for (size_t index = 0; index < file_system_urls.size(); ++index) {
const auto& file_system_url = file_system_urls[index];
// Run DocumentsProvider check before running ConvertPathToArcUrl.
// Otherwise, DocumentsProvider file path would be encoded to a
// ChromeContentProvider URL (b/132314050).
if (documents_provider_root_map) {
FilePath file_path;
auto* documents_provider_root =
documents_provider_root_map->ParseAndLookup(file_system_url,
&file_path);
if (documents_provider_root) {
documents_provider_root->ResolveToContentUrl(
file_path, base::BindOnce(single_content_url_callback, index));
continue;
}
}
if (file_system_url.mount_type() == storage::kFileSystemTypeExternal) {
const base::FilePath path =
ConvertFileSystemURLToPathForSharingWithArc(file_system_url);
GURL arc_url;
bool requires_sharing = false;
if (ConvertPathToArcUrl(path, &arc_url, &requires_sharing)) {
if (requires_sharing) {
paths_to_share_ptr->push_back(path);
}
single_content_url_callback.Run(index, arc_url);
continue;
}
}
single_content_url_callback.Run(index, GURL());
}
}
bool ReplacePrefix(std::string* const s,
std::string_view prefix,
std::string_view replacement) {
DCHECK(s);
if (s->starts_with(prefix) &&
(prefix.ends_with('/') || s->size() <= prefix.size() ||
(*s)[prefix.size()] == '/')) {
s->replace(0, prefix.size(), replacement);
return true;
}
return false;
}
std::string GetPathDisplayTextForSettings(Profile* const profile,
std::string_view path) {
std::string result(path);
DriveIntegrationService* service =
DriveIntegrationServiceFactory::FindForProfile(profile);
if (service && !service->is_enabled()) {
service = nullptr;
}
bool is_odfs_mounted = ash::cloud_upload::IsODFSMounted(profile);
const std::string_view sep = " › ";
const std::string downloads_label =
StrCat({GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL), sep,
GetStringUTF8(IDS_FILE_BROWSER_DOWNLOADS_DIRECTORY_LABEL)});
if (ReplacePrefix(&result, "/home/chronos/user/MyFiles/Downloads",
downloads_label)) {
} else if (ReplacePrefix(
&result,
profile->GetPath().Append("MyFiles/Downloads").value(),
downloads_label)) {
} else if (ReplacePrefix(
&result, "/home/chronos/user/MyFiles",
GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL))) {
} else if (ReplacePrefix(
&result, profile->GetPath().Append(kFolderNameMyFiles).value(),
GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL))) {
} else if (service &&
ReplacePrefix(
&result,
service->GetMountPointPath().Append(kDriveFsDirRoot).value(),
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL))
.value())) {
} else if (ReplacePrefix(&result,
download_dir_util::kDriveNamePolicyVariableName,
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL))
.value())) {
} else if (service &&
ReplacePrefix(&result,
service->GetMountPointPath()
.Append(kDriveFsDirTeamDrives)
.value(),
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_SHARED_DRIVES_LABEL))
.value())) {
} else if (service &&
ReplacePrefix(&result,
service->GetMountPointPath()
.Append(kDriveFsDirComputers)
.value(),
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL))
.value())) {
} else if (
service &&
ReplacePrefix(
&result,
service->GetMountPointPath().Append(kDriveFsDirSharedWithMe).value(),
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL))
.value())) {
} else if (
service &&
ReplacePrefix(
&result,
service->GetMountPointPath()
.Append(kDriveFsDirShortcutsSharedWithMe)
.value(),
base::FilePath(kDisplayNameGoogleDrive)
.Append(l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_DRIVE_SHARED_WITH_ME_COLLECTION_LABEL))
.value())) {
} else if (ReplacePrefix(
&result, download_dir_util::kOneDriveNamePolicyVariableName,
base::FilePath(kDisplayNameMicrosoftOneDrive).value())) {
} else if (is_odfs_mounted &&
ReplacePrefix(
&result,
ash::cloud_upload::GetODFSFuseboxMount(profile).value(),
base::FilePath(kDisplayNameMicrosoftOneDrive).value())) {
} else if (ReplacePrefix(&result, GetAndroidFilesPath().value(),
l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL))) {
} else if (ReplacePrefix(&result, GetCrostiniMountDirectory(profile).value(),
l10n_util::GetStringUTF8(
IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL))) {
} else if (ReplacePrefix(&result,
base::FilePath(kRemovableMediaPath)
.AsEndingWithSeparator()
.value(),
"")) {
// Strip prefix of "/media/removable/" including trailing slash.
} else if (ReplacePrefix(&result,
base::FilePath(kArchiveMountPath)
.AsEndingWithSeparator()
.value(),
"")) {
// Strip prefix of "/media/archive/" including trailing slash.
}
base::ReplaceChars(result, "/", sep, &result);
return result;
}
bool ExtractMountNameFileSystemNameFullPath(const FilePath& absolute_path,
std::string* mount_name,
std::string* file_system_name,
std::string* full_path) {
DCHECK(absolute_path.IsAbsolute());
DCHECK(mount_name);
DCHECK(full_path);
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
FilePath virtual_path;
if (!mount_points->GetVirtualPath(absolute_path, &virtual_path)) {
return false;
}
// |virtual_path| format is: <mount_name>/<full_path>, and
// |file_system_name| == |mount_name|, except for 'removable' and 'archive',
// |mount_name| is the first two segments, |file_system_name| is the second.
const std::string& value = virtual_path.value();
size_t fs_start = 0;
size_t slash_pos = value.find(FilePath::kSeparators[0]);
*mount_name = *file_system_name = value.substr(0, slash_pos);
if (*mount_name == ash::kSystemMountNameRemovable ||
*mount_name == ash::kSystemMountNameArchive) {
if (slash_pos == std::string::npos) {
return false;
}
fs_start = slash_pos + 1;
slash_pos = value.find(FilePath::kSeparators[0], fs_start);
*mount_name = value.substr(0, slash_pos);
}
// Set full_path to '/' if |absolute_path| is a root.
if (slash_pos == std::string::npos) {
*file_system_name = value.substr(fs_start);
*full_path = "/";
} else {
*file_system_name = value.substr(fs_start, slash_pos - fs_start);
*full_path = value.substr(slash_pos);
}
return true;
}
std::string GetDisplayableFileName(GURL file_url) {
// Try to convert %20 to spaces, if this produces any invalid char, use the
// file name URL encoded.
std::string file_name;
if (!base::UnescapeBinaryURLComponentSafe(file_url.ExtractFileName(),
/*fail_on_path_separators=*/true,
&file_name)) {
file_name = file_url.ExtractFileName();
}
return file_name;
}
std::string GetDisplayableFileName(storage::FileSystemURL file_url) {
return GetDisplayableFileName(file_url.ToGURL());
}
std::u16string GetDisplayableFileName16(GURL file_url) {
return base::UTF8ToUTF16(GetDisplayableFileName(file_url));
}
std::u16string GetDisplayableFileName16(storage::FileSystemURL file_url) {
return base::UTF8ToUTF16(GetDisplayableFileName(file_url.ToGURL()));
}
std::optional<FilePath> GetDisplayablePath(Profile* profile, FilePath path) {
base::WeakPtr<Volume> volume =
file_manager::VolumeManager::Get(profile)->FindVolumeFromPath(path);
if (!volume) {
return std::nullopt;
}
FilePath mount_relative_path;
// AppendRelativePath fails if |mount_path| is the same as |path|, but in that
// case |mount_relative_path| will be empty, which is what we want.
volume->mount_path().AppendRelativePath(path, &mount_relative_path);
auto path_components = mount_relative_path.GetComponents();
auto cur_component = path_components.begin();
FilePath result;
switch (volume->type()) {
case VOLUME_TYPE_GOOGLE_DRIVE: {
// Start with the Google Drive root.
result = FilePath(volume->volume_label());
// The first directory indicates which Drive the path is in, so check it
// against the expected directories. e.g. My Drive, Shared with me, etc.
if (cur_component == path_components.end()) {
return std::nullopt;
}
auto maybe_id = DriveFsFolderToMessageId(*cur_component);
if (!maybe_id.has_value()) {
return std::nullopt;
}
result = result.Append(GetStringUTF8(*maybe_id));
cur_component++;
// Skip the first directory in the Shared With Me folders as those are
// just an opaque id.
if (cur_component != path_components.end() &&
(path_components[0] == kDriveFsDirSharedWithMe ||
path_components[0] == kDriveFsDirShortcutsSharedWithMe)) {
++cur_component;
}
break;
}
case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
// Start with My Files root.
result = FilePath(volume->volume_label());
// Handle special folders under My Files.
if (cur_component != path_components.end()) {
auto maybe_id = MyFilesFolderToMessageId(*cur_component);
if (maybe_id.has_value()) {
result = result.Append(GetStringUTF8(*maybe_id));
++cur_component;
}
}
break;
case VOLUME_TYPE_ANDROID_FILES:
case VOLUME_TYPE_CROSTINI:
case VOLUME_TYPE_GUEST_OS:
result = FilePath(GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL))
.Append(volume->volume_label());
break;
case VOLUME_TYPE_MEDIA_VIEW:
case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
case VOLUME_TYPE_PROVIDED:
case VOLUME_TYPE_DOCUMENTS_PROVIDER:
case VOLUME_TYPE_MTP:
case VOLUME_TYPE_SMB:
result = FilePath(volume->volume_label());
break;
case VOLUME_TYPE_TESTING:
case VOLUME_TYPE_SYSTEM_INTERNAL:
return std::nullopt;
case NUM_VOLUME_TYPE:
NOTREACHED();
}
while (cur_component != path_components.end()) {
result = result.Append(*cur_component);
cur_component++;
}
return result;
}
std::optional<FilePath> GetDisplayablePath(Profile* profile,
storage::FileSystemURL file_url) {
return GetDisplayablePath(profile, file_url.path());
}
std::vector<ui::FileInfo> ParseFileSystemSources(
const ui::DataTransferEndpoint* source,
const base::Pickle& pickle) {
std::vector<ui::FileInfo> file_info;
// We only promote 'fs/sources' custom data pickle to be filenames if it came
// from the trusted FilesApp.
if (!source || !source->GetURL() || !IsFileManagerURL(*source->GetURL())) {
return file_info;
}
std::optional<std::u16string> maybe_file_system_url_list =
ui::ReadCustomDataForType(pickle, kFilesAppMimeSources);
if (!maybe_file_system_url_list || maybe_file_system_url_list->empty()) {
return file_info;
}
storage::ExternalMountPoints* mount_points =
storage::ExternalMountPoints::GetSystemInstance();
for (std::u16string_view line : base::SplitStringPiece(
*maybe_file_system_url_list, kFilesAppSeparator16,
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
if (line.empty() || line[0] == '#') {
continue;
}
const GURL gurl(line);
storage::FileSystemURL url = mount_points->CrackURL(
gurl, blink::StorageKey::CreateFirstParty(url::Origin::Create(gurl)));
if (!url.is_valid()) {
LOG(WARNING) << "Invalid clipboard FileSystemURL: " << line;
continue;
} else if (url.TypeImpliesPathIsReal()) {
file_info.emplace_back(std::move(url.path()), FilePath());
} else if (FilePath path = fusebox::Server::SubstituteFuseboxFilePath(url);
!path.empty()) {
file_info.emplace_back(std::move(path), FilePath());
}
}
return file_info;
}
} // namespace file_manager::util