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