1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/chromeos/file_manager/path_util.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/barrier_closure.h"
11 #include "base/base64.h"
12 #include "base/bind.h"
13 #include "base/check_op.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/system/sys_info.h"
17 #include "chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h"
18 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.h"
19 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root_map.h"
20 #include "chrome/browser/chromeos/arc/fileapi/chrome_content_provider_url_util.h"
21 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
22 #include "chrome/browser/chromeos/crostini/crostini_util.h"
23 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
24 #include "chrome/browser/chromeos/drive/file_system_util.h"
25 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
26 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
27 #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
28 #include "chrome/browser/chromeos/profiles/profile_helper.h"
29 #include "chrome/browser/chromeos/smb_client/smb_service.h"
30 #include "chrome/browser/chromeos/smb_client/smb_service_factory.h"
31 #include "chrome/browser/chromeos/smb_client/smbfs_share.h"
32 #include "chrome/browser/download/download_dir_util.h"
33 #include "chrome/browser/download/download_prefs.h"
34 #include "chrome/browser/profiles/profile.h"
35 #include "chromeos/constants/chromeos_features.h"
36 #include "chromeos/disks/disk.h"
37 #include "chromeos/disks/disk_mount_manager.h"
38 #include "components/arc/arc_util.h"
39 #include "components/drive/file_system_core_util.h"
40 #include "components/user_manager/user.h"
41 #include "components/user_manager/user_manager.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "net/base/escape.h"
44 #include "net/base/filename_util.h"
45 #include "storage/browser/file_system/external_mount_points.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
48 #include "url/gurl.h"
49 
50 namespace file_manager {
51 namespace util {
52 
53 namespace {
54 
55 constexpr char kAndroidFilesMountPointName[] = "android_files";
56 constexpr char kCrostiniMapGoogleDrive[] = "GoogleDrive";
57 constexpr char kCrostiniMapLinuxFiles[] = "LinuxFiles";
58 constexpr char kCrostiniMapMyDrive[] = "MyDrive";
59 constexpr char kCrostiniMapPlayFiles[] = "PlayFiles";
60 constexpr char kCrostiniMapSmbFs[] = "SMB";
61 constexpr char kCrostiniMapTeamDrives[] = "SharedDrives";
62 constexpr char kFolderNameDownloads[] = "Downloads";
63 constexpr char kFolderNameMyFiles[] = "MyFiles";
64 constexpr char kDisplayNameGoogleDrive[] = "Google Drive";
65 constexpr char kDriveFsDirComputers[] = "Computers";
66 constexpr char kDriveFsDirRoot[] = "root";
67 constexpr char kDriveFsDirTeamDrives[] = "team_drives";
68 
69 // Sync with the root name defined with the file provider in ARC++ side.
70 constexpr base::FilePath::CharType kArcDownloadRoot[] =
71     FILE_PATH_LITERAL("/download");
72 constexpr base::FilePath::CharType kArcExternalFilesRoot[] =
73     FILE_PATH_LITERAL("/external_files");
74 // Sync with the volume provider in ARC++ side.
75 constexpr char kArcRemovableMediaContentUrlPrefix[] =
76     "content://org.chromium.arc.volumeprovider/";
77 // The dummy UUID of the MyFiles volume is taken from
78 // components/arc/volume_mounter/arc_volume_mounter_bridge.cc.
79 // TODO(crbug.com/929031): Move MyFiles constants to a common place.
80 constexpr char kArcMyFilesContentUrlPrefix[] =
81     "content://org.chromium.arc.volumeprovider/"
82     "0000000000000000000000000000CAFEF00D2019/";
83 constexpr char kArcDriveContentUrlPrefix[] =
84     "content://org.chromium.arc.volumeprovider/MyDrive/";
85 
GetPrimaryProfile()86 Profile* GetPrimaryProfile() {
87   if (!user_manager::UserManager::IsInitialized())
88     return nullptr;
89   const auto* primary_user = user_manager::UserManager::Get()->GetPrimaryUser();
90   if (!primary_user)
91     return nullptr;
92   return chromeos::ProfileHelper::Get()->GetProfileByUser(primary_user);
93 }
94 
95 // Helper function for |ConvertToContentUrls|.
OnSingleContentUrlResolved(const base::RepeatingClosure & barrier_closure,std::vector<GURL> * out_urls,size_t index,const GURL & url)96 void OnSingleContentUrlResolved(const base::RepeatingClosure& barrier_closure,
97                                 std::vector<GURL>* out_urls,
98                                 size_t index,
99                                 const GURL& url) {
100   (*out_urls)[index] = url;
101   barrier_closure.Run();
102 }
103 
104 // Helper function for |ConvertToContentUrls|.
OnAllContentUrlsResolved(ConvertToContentUrlsCallback callback,std::unique_ptr<std::vector<GURL>> urls)105 void OnAllContentUrlsResolved(ConvertToContentUrlsCallback callback,
106                               std::unique_ptr<std::vector<GURL>> urls) {
107   std::move(callback).Run(*urls);
108 }
109 
110 // On non-ChromeOS system (test+development), the primary profile uses
111 // $HOME/Downloads for ease access to local files for debugging.
ShouldMountPrimaryUserDownloads(Profile * profile)112 bool ShouldMountPrimaryUserDownloads(Profile* profile) {
113   if (!base::SysInfo::IsRunningOnChromeOS() &&
114       user_manager::UserManager::IsInitialized()) {
115     const user_manager::User* const user =
116         chromeos::ProfileHelper::Get()->GetUserByProfile(
117             profile->GetOriginalProfile());
118     const user_manager::User* const primary_user =
119         user_manager::UserManager::Get()->GetPrimaryUser();
120     return user == primary_user;
121   }
122 
123   return false;
124 }
125 
126 // Extracts the Drive path from the given path located under the legacy Drive
127 // mount point. Returns an empty path if |path| is not under the legacy Drive
128 // mount point.
129 // Example: ExtractLegacyDrivePath("/special/drive-xxx/foo.txt") =>
130 //   "drive/foo.txt"
ExtractLegacyDrivePath(const base::FilePath & path)131 base::FilePath ExtractLegacyDrivePath(const base::FilePath& path) {
132   std::vector<base::FilePath::StringType> components;
133   path.GetComponents(&components);
134   if (components.size() < 3)
135     return base::FilePath();
136   if (components[0] != FILE_PATH_LITERAL("/"))
137     return base::FilePath();
138   if (components[1] != FILE_PATH_LITERAL("special"))
139     return base::FilePath();
140   static const base::FilePath::CharType kPrefix[] = FILE_PATH_LITERAL("drive");
141   if (components[2].compare(0, base::size(kPrefix) - 1, kPrefix) != 0)
142     return base::FilePath();
143 
144   base::FilePath drive_path = drive::util::GetDriveGrandRootPath();
145   for (size_t i = 3; i < components.size(); ++i)
146     drive_path = drive_path.Append(components[i]);
147   return drive_path;
148 }
149 
150 // Extracts the volume name of a removable device. |relative_path| is expected
151 // to be of the form <volume name>/..., which is relative to /media/removable.
ExtractVolumeNameFromRelativePathForRemovableMedia(const base::FilePath & relative_path)152 std::string ExtractVolumeNameFromRelativePathForRemovableMedia(
153     const base::FilePath& relative_path) {
154   std::vector<base::FilePath::StringType> components;
155   relative_path.GetComponents(&components);
156   if (components.empty()) {
157     LOG(WARNING) << "Failed to extract volume name from relative path: "
158                  << relative_path;
159     return std::string();
160   }
161   return components[0];
162 }
163 
164 // Returns the source path of a removable device using its volume name as a key.
165 // An empty string is returned when it fails to get a valid mount point from
166 // DiskMountManager.
GetSourcePathForRemovableMedia(const std::string & volume_name)167 std::string GetSourcePathForRemovableMedia(const std::string& volume_name) {
168   const std::string mount_path(
169       base::StringPrintf("%s/%s", kRemovableMediaPath, volume_name.c_str()));
170   const auto& mount_points =
171       chromeos::disks::DiskMountManager::GetInstance()->mount_points();
172   const auto found = mount_points.find(mount_path);
173   return found == mount_points.end() ? std::string()
174                                      : found->second.source_path;
175 }
176 
177 // Returns the UUID of a removable device using its volume name as a key.
178 // An empty string is returned when it fails to get valid source path and disk
179 // from DiskMountManager.
GetFsUuidForRemovableMedia(const std::string & volume_name)180 std::string GetFsUuidForRemovableMedia(const std::string& volume_name) {
181   const std::string source_path = GetSourcePathForRemovableMedia(volume_name);
182   if (source_path.empty()) {
183     LOG(WARNING) << "No source path is found for volume name: " << volume_name;
184     return std::string();
185   }
186   const chromeos::disks::Disk* disk =
187       chromeos::disks::DiskMountManager::GetInstance()->FindDiskBySourcePath(
188           source_path);
189   std::string fs_uuid = disk == nullptr ? std::string() : disk->fs_uuid();
190   if (fs_uuid.empty())
191     LOG(WARNING) << "No UUID is found for volume name: " << volume_name;
192   return fs_uuid;
193 }
194 
195 // Same as parent.AppendRelativePath(child, path) except that it allows
196 // parent == child, in which case path is unchanged.
AppendRelativePath(const base::FilePath & parent,const base::FilePath & child,base::FilePath * path)197 bool AppendRelativePath(const base::FilePath& parent,
198                         const base::FilePath& child,
199                         base::FilePath* path) {
200   return child == parent || parent.AppendRelativePath(child, path);
201 }
202 
203 }  // namespace
204 
205 const base::FilePath::CharType kRemovableMediaPath[] =
206     FILE_PATH_LITERAL("/media/removable");
207 
208 const base::FilePath::CharType kAndroidFilesPath[] =
209     FILE_PATH_LITERAL("/run/arc/sdcard/write/emulated/0");
210 
211 const base::FilePath::CharType kSystemFontsPath[] =
212     FILE_PATH_LITERAL("/usr/share/fonts");
213 
214 const base::FilePath::CharType kArchiveMountPath[] =
215     FILE_PATH_LITERAL("/media/archive");
216 
GetDownloadsFolderForProfile(Profile * profile)217 base::FilePath GetDownloadsFolderForProfile(Profile* profile) {
218   // Check if FilesApp has a registered path already.  This happens for tests.
219   const std::string mount_point_name =
220       util::GetDownloadsMountPointName(profile);
221   storage::ExternalMountPoints* const mount_points =
222       storage::ExternalMountPoints::GetSystemInstance();
223   base::FilePath path;
224   if (mount_points->GetRegisteredPath(mount_point_name, &path))
225     return path.AppendASCII(kFolderNameDownloads);
226 
227   // Return $HOME/Downloads as Download folder.
228   if (ShouldMountPrimaryUserDownloads(profile))
229     return DownloadPrefs::GetDefaultDownloadDirectory();
230 
231   // Return <cryptohome>/MyFiles/Downloads if it feature is enabled.
232   return profile->GetPath()
233       .AppendASCII(kFolderNameMyFiles)
234       .AppendASCII(kFolderNameDownloads);
235 }
236 
GetMyFilesFolderForProfile(Profile * profile)237 base::FilePath GetMyFilesFolderForProfile(Profile* profile) {
238   // Check if FilesApp has a registered path already. This happens for tests.
239   const std::string mount_point_name =
240       util::GetDownloadsMountPointName(profile);
241   storage::ExternalMountPoints* const mount_points =
242       storage::ExternalMountPoints::GetSystemInstance();
243   base::FilePath path;
244   if (mount_points->GetRegisteredPath(mount_point_name, &path))
245     return path;
246 
247   // Return $HOME/Downloads as MyFiles folder.
248   if (ShouldMountPrimaryUserDownloads(profile))
249     return DownloadPrefs::GetDefaultDownloadDirectory();
250 
251   // Return <cryptohome>/MyFiles.
252   return profile->GetPath().AppendASCII(kFolderNameMyFiles);
253 }
254 
GetAndroidFilesPath()255 base::FilePath GetAndroidFilesPath() {
256   // Check if Android has a registered path already. This happens for tests.
257   const std::string mount_point_name = util::GetAndroidFilesMountPointName();
258   storage::ExternalMountPoints* const mount_points =
259       storage::ExternalMountPoints::GetSystemInstance();
260   base::FilePath path;
261   if (mount_points->GetRegisteredPath(mount_point_name, &path))
262     return path;
263   return base::FilePath(file_manager::util::kAndroidFilesPath);
264 }
265 
MigratePathFromOldFormat(Profile * profile,const base::FilePath & old_base,const base::FilePath & old_path,base::FilePath * new_path)266 bool MigratePathFromOldFormat(Profile* profile,
267                               const base::FilePath& old_base,
268                               const base::FilePath& old_path,
269                               base::FilePath* new_path) {
270   const base::FilePath new_base = GetMyFilesFolderForProfile(profile);
271 
272   // Special case, migrating /home/chronos/user which is set early (before a
273   // profile is attached to the browser process) to default to
274   // /home/chronos/u-{hash}/MyFiles/Downloads.
275   if (old_path == old_base &&
276       old_path == base::FilePath("/home/chronos/user")) {
277     *new_path = GetDownloadsFolderForProfile(profile);
278     return true;
279   }
280 
281   base::FilePath relative;
282   if (old_base.AppendRelativePath(old_path, &relative)) {
283     *new_path = new_base.Append(relative);
284     return old_path != *new_path;
285   }
286 
287   return false;
288 }
289 
MigrateFromDownloadsToMyFiles(Profile * profile,const base::FilePath & old_path,base::FilePath * new_path)290 bool MigrateFromDownloadsToMyFiles(Profile* profile,
291                                    const base::FilePath& old_path,
292                                    base::FilePath* new_path) {
293   const base::FilePath old_base =
294       profile->GetPath().Append(kFolderNameDownloads);
295   const base::FilePath new_base = GetDownloadsFolderForProfile(profile);
296   if (new_base == old_base)
297     return false;
298   base::FilePath relative;
299   if (AppendRelativePath(old_base, old_path, &relative)) {
300     *new_path = new_base.Append(relative);
301     return old_path != *new_path;
302   }
303   return false;
304 }
305 
MigrateToDriveFs(Profile * profile,const base::FilePath & old_path,base::FilePath * new_path)306 bool MigrateToDriveFs(Profile* profile,
307                       const base::FilePath& old_path,
308                       base::FilePath* new_path) {
309   const auto* user = chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
310   auto* integration_service =
311       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
312   if (!integration_service || !integration_service->is_enabled() || !user ||
313       !user->GetAccountId().HasAccountIdKey()) {
314     return false;
315   }
316   *new_path = integration_service->GetMountPointPath();
317   return drive::util::GetDriveGrandRootPath().AppendRelativePath(
318       ExtractLegacyDrivePath(old_path), new_path);
319 }
320 
GetDownloadsMountPointName(Profile * profile)321 std::string GetDownloadsMountPointName(Profile* profile) {
322   // To distinguish profiles in multi-profile session, we append user name hash
323   // to "Downloads". Note that some profiles (like login or test profiles)
324   // are not associated with an user account. In that case, no suffix is added
325   // because such a profile never belongs to a multi-profile session.
326   const user_manager::User* const user =
327       user_manager::UserManager::IsInitialized()
328           ? chromeos::ProfileHelper::Get()->GetUserByProfile(
329                 profile->GetOriginalProfile())
330           : nullptr;
331   const std::string id = user ? "-" + user->username_hash() : "";
332   return net::EscapeQueryParamValue(kFolderNameDownloads + id, false);
333 }
334 
GetAndroidFilesMountPointName()335 const std::string GetAndroidFilesMountPointName() {
336   return kAndroidFilesMountPointName;
337 }
338 
GetCrostiniMountPointName(Profile * profile)339 std::string GetCrostiniMountPointName(Profile* profile) {
340   // crostini_<hash>_termina_penguin
341   return base::JoinString(
342       {"crostini", crostini::CryptohomeIdForProfile(profile),
343        crostini::kCrostiniDefaultVmName,
344        crostini::kCrostiniDefaultContainerName},
345       "_");
346 }
347 
GetCrostiniMountDirectory(Profile * profile)348 base::FilePath GetCrostiniMountDirectory(Profile* profile) {
349   return base::FilePath("/media/fuse/" + GetCrostiniMountPointName(profile));
350 }
351 
GetCrostiniMountOptions(const std::string & hostname,const std::string & host_private_key,const std::string & container_public_key)352 std::vector<std::string> GetCrostiniMountOptions(
353     const std::string& hostname,
354     const std::string& host_private_key,
355     const std::string& container_public_key) {
356   const std::string port = "2222";
357   std::vector<std::string> options;
358   std::string base64_known_hosts;
359   std::string base64_identity;
360   base::Base64Encode(host_private_key, &base64_identity);
361   base::Base64Encode(
362       base::StringPrintf("[%s]:%s %s", hostname.c_str(), port.c_str(),
363                          container_public_key.c_str()),
364       &base64_known_hosts);
365   options.push_back("UserKnownHostsBase64=" + base64_known_hosts);
366   options.push_back("IdentityBase64=" + base64_identity);
367   options.push_back("Port=" + port);
368   return options;
369 }
370 
ConvertFileSystemURLToPathInsideVM(Profile * profile,const storage::FileSystemURL & file_system_url,const base::FilePath & vm_mount,bool map_crostini_home,base::FilePath * inside)371 bool ConvertFileSystemURLToPathInsideVM(
372     Profile* profile,
373     const storage::FileSystemURL& file_system_url,
374     const base::FilePath& vm_mount,
375     bool map_crostini_home,
376     base::FilePath* inside) {
377   const std::string& id(file_system_url.mount_filesystem_id());
378   // File system root requires strip trailing separator.
379   base::FilePath path =
380       base::FilePath(file_system_url.virtual_path()).StripTrailingSeparators();
381   // Include drive if using DriveFS.
382   std::string mount_point_name_drive;
383   auto* integration_service =
384       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
385   if (integration_service) {
386     mount_point_name_drive =
387         integration_service->GetMountPointPath().BaseName().value();
388   }
389 
390   // Reformat virtual_path() from:
391   //   <mount_label>/path/to/file
392   // To:
393   //   <vm_mount>/<mapping>/path/to/file
394   // If |map_crostini_home| is set, paths in crostini mount map to:
395   //   /<home-directory>/path/to/file
396   base::FilePath base_to_exclude(id);
397   if (id == GetDownloadsMountPointName(profile)) {
398     // MyFiles.
399     *inside = vm_mount.Append(kFolderNameMyFiles);
400   } else if (!mount_point_name_drive.empty() && id == mount_point_name_drive) {
401     // DriveFS has some more complicated mappings.
402     std::vector<base::FilePath::StringType> components;
403     path.GetComponents(&components);
404     *inside = vm_mount.Append(kCrostiniMapGoogleDrive);
405     if (components.size() >= 2 && components[1] == kDriveFsDirRoot) {
406       // root -> MyDrive.
407       base_to_exclude = base_to_exclude.Append(kDriveFsDirRoot);
408       *inside = inside->Append(kCrostiniMapMyDrive);
409     } else if (components.size() >= 2 &&
410                components[1] == kDriveFsDirTeamDrives) {
411       // team_drives -> SharedDrives.
412       base_to_exclude = base_to_exclude.Append(kDriveFsDirTeamDrives);
413       *inside = inside->Append(kCrostiniMapTeamDrives);
414     }
415     // Computers -> Computers
416   } else if (id == chromeos::kSystemMountNameRemovable) {
417     // Removable.
418     *inside = vm_mount.Append(chromeos::kSystemMountNameRemovable);
419   } else if (id == GetAndroidFilesMountPointName()) {
420     // PlayFiles.
421     *inside = vm_mount.Append(kCrostiniMapPlayFiles);
422   } else if (id == chromeos::kSystemMountNameArchive) {
423     // Archive.
424     *inside = vm_mount.Append(chromeos::kSystemMountNameArchive);
425   } else if (id == GetCrostiniMountPointName(profile)) {
426     // Crostini.
427     if (map_crostini_home) {
428       base::Optional<crostini::ContainerInfo> container_info =
429           crostini::CrostiniManager::GetForProfile(profile)->GetContainerInfo(
430               crostini::ContainerId::GetDefault());
431       if (!container_info) {
432         return false;
433       }
434       *inside = container_info->homedir;
435     } else {
436       *inside = vm_mount.Append(kCrostiniMapLinuxFiles);
437     }
438   } else if (file_system_url.type() == storage::kFileSystemTypeSmbFs) {
439     // SMB. Do not assume the share is currently accessible via SmbService
440     // as this function is called during unmount when SmbFsShare is
441     // destroyed. The only information safely available is the stable
442     // mount ID.
443     *inside = vm_mount.Append(kCrostiniMapSmbFs);
444     *inside = inside->Append(id);
445   } else {
446     return false;
447   }
448   return AppendRelativePath(base_to_exclude, path, inside);
449 }
450 
ConvertFileSystemURLToPathInsideCrostini(Profile * profile,const storage::FileSystemURL & file_system_url,base::FilePath * inside)451 bool ConvertFileSystemURLToPathInsideCrostini(
452     Profile* profile,
453     const storage::FileSystemURL& file_system_url,
454     base::FilePath* inside) {
455   return ConvertFileSystemURLToPathInsideVM(
456       profile, file_system_url, crostini::ContainerChromeOSBaseDirectory(),
457       /*map_crostini_home=*/true, inside);
458 }
459 
ConvertPathInsideVMToFileSystemURL(Profile * profile,const base::FilePath & inside,const base::FilePath & vm_mount,bool map_crostini_home,storage::FileSystemURL * file_system_url)460 bool ConvertPathInsideVMToFileSystemURL(
461     Profile* profile,
462     const base::FilePath& inside,
463     const base::FilePath& vm_mount,
464     bool map_crostini_home,
465     storage::FileSystemURL* file_system_url) {
466   storage::ExternalMountPoints* mount_points =
467       storage::ExternalMountPoints::GetSystemInstance();
468 
469   // Include drive if using DriveFS.
470   std::string mount_point_name_drive;
471   auto* integration_service =
472       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
473   if (integration_service) {
474     mount_point_name_drive =
475         integration_service->GetMountPointPath().BaseName().value();
476   }
477 
478   std::string mount_name;
479   base::FilePath path;
480   base::FilePath relative_path;
481 
482   if (map_crostini_home) {
483     base::Optional<crostini::ContainerInfo> container_info =
484         crostini::CrostiniManager::GetForProfile(profile)->GetContainerInfo(
485             crostini::ContainerId::GetDefault());
486     if (container_info &&
487         AppendRelativePath(container_info->homedir, inside, &relative_path)) {
488       *file_system_url = mount_points->CreateExternalFileSystemURL(
489           url::Origin(), GetCrostiniMountPointName(profile), relative_path);
490       return file_system_url->is_valid();
491     }
492   }
493 
494   if (!vm_mount.AppendRelativePath(inside, &path)) {
495     return false;
496   }
497 
498   if (AppendRelativePath(base::FilePath(kFolderNameMyFiles), path,
499                          &relative_path)) {
500     // MyFiles.
501     mount_name = GetDownloadsMountPointName(profile);
502     path = relative_path;
503   } else if (AppendRelativePath(base::FilePath(kCrostiniMapLinuxFiles), path,
504                                 &relative_path)) {
505     // LinuxFiles.
506     mount_name = GetCrostiniMountPointName(profile);
507     path = relative_path;
508   } else if (base::FilePath(kCrostiniMapGoogleDrive)
509                  .AppendRelativePath(path, &relative_path)) {
510     mount_name = mount_point_name_drive;
511     path = relative_path;
512     relative_path.clear();
513     // GoogleDrive
514     if (AppendRelativePath(base::FilePath(kCrostiniMapMyDrive), path,
515                            &relative_path)) {
516       // Special mapping for /GoogleDrive/MyDrive -> root
517       path = base::FilePath(kDriveFsDirRoot).Append(relative_path);
518     } else if (AppendRelativePath(base::FilePath(kCrostiniMapTeamDrives), path,
519                                   &relative_path)) {
520       // Special mapping for /GoogleDrive/SharedDrive -> team_drives
521       path = base::FilePath(kDriveFsDirTeamDrives).Append(relative_path);
522     }
523     // Computers -> Computers
524   } else if (base::FilePath(chromeos::kSystemMountNameRemovable)
525                  .AppendRelativePath(path, &relative_path)) {
526     // Removable subdirs only.
527     mount_name = chromeos::kSystemMountNameRemovable;
528     path = relative_path;
529   } else if (AppendRelativePath(base::FilePath(kCrostiniMapPlayFiles), path,
530                                 &relative_path)) {
531     // PlayFiles.
532     mount_name = GetAndroidFilesMountPointName();
533     path = relative_path;
534   } else if (base::FilePath(chromeos::kSystemMountNameArchive)
535                  .AppendRelativePath(path, &relative_path)) {
536     // Archive subdirs only.
537     mount_name = chromeos::kSystemMountNameArchive;
538     path = relative_path;
539   } else if (base::FilePath(kCrostiniMapSmbFs)
540                  .AppendRelativePath(path, &relative_path)) {
541     // SMB.
542     std::vector<base::FilePath::StringType> components;
543     relative_path.GetComponents(&components);
544     if (components.size() < 1) {
545       return false;
546     }
547     mount_name = components[0];
548     path.clear();
549     base::FilePath(mount_name).AppendRelativePath(relative_path, &path);
550   } else {
551     return false;
552   }
553 
554   *file_system_url = mount_points->CreateExternalFileSystemURL(
555       url::Origin(), mount_name, path);
556   return file_system_url->is_valid();
557 }
558 
ConvertPathToArcUrl(const base::FilePath & path,GURL * arc_url_out)559 bool ConvertPathToArcUrl(const base::FilePath& path, GURL* arc_url_out) {
560   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
561 
562   // Obtain the primary profile. This information is required because currently
563   // only the file systems for the primary profile is exposed to ARC.
564   Profile* primary_profile = GetPrimaryProfile();
565   if (!primary_profile)
566     return false;
567 
568   // Convert paths under primary profile's Downloads directory.
569   base::FilePath primary_downloads =
570       GetDownloadsFolderForProfile(primary_profile);
571   base::FilePath result_path(kArcDownloadRoot);
572   if (primary_downloads.AppendRelativePath(path, &result_path)) {
573     *arc_url_out = GURL(arc::kFileSystemFileproviderUrl)
574                        .Resolve(net::EscapePath(result_path.AsUTF8Unsafe()));
575     return true;
576   }
577 
578   // Convert paths under Android files root (/run/arc/sdcard/write/emulated/0).
579   result_path = base::FilePath(kArcExternalFilesRoot);
580   if (base::FilePath(kAndroidFilesPath)
581           .AppendRelativePath(path, &result_path)) {
582     *arc_url_out = GURL(arc::kFileSystemFileproviderUrl)
583                        .Resolve(net::EscapePath(result_path.AsUTF8Unsafe()));
584     return true;
585   }
586 
587   // Convert paths under /media/removable.
588   base::FilePath relative_path;
589   if (base::FilePath(kRemovableMediaPath)
590           .AppendRelativePath(path, &relative_path)) {
591     const std::string volume_name =
592         ExtractVolumeNameFromRelativePathForRemovableMedia(relative_path);
593     if (volume_name.empty())
594       return false;
595     const std::string fs_uuid = GetFsUuidForRemovableMedia(volume_name);
596     if (fs_uuid.empty())
597       return false;
598     // Replace the volume name in the relative path with the UUID.
599     base::FilePath relative_path_with_uuid = base::FilePath(fs_uuid);
600     if (!base::FilePath(volume_name)
601              .AppendRelativePath(relative_path, &relative_path_with_uuid)) {
602       LOG(WARNING) << "Failed to replace volume name \"" << volume_name
603                    << "\" in relative path \"" << relative_path
604                    << "\" with UUID \"" << fs_uuid << "\"";
605       return false;
606     }
607     *arc_url_out =
608         GURL(kArcRemovableMediaContentUrlPrefix)
609             .Resolve(net::EscapePath(relative_path_with_uuid.AsUTF8Unsafe()));
610     return true;
611   }
612 
613   // Convert paths under MyFiles.
614   if (base::FilePath(GetMyFilesFolderForProfile(primary_profile))
615           .AppendRelativePath(path, &relative_path)) {
616     *arc_url_out = GURL(kArcMyFilesContentUrlPrefix)
617                        .Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
618     return true;
619   }
620 
621   bool force_external = false;
622   // Convert paths under DriveFS.
623   const drive::DriveIntegrationService* integration_service =
624       drive::util::GetIntegrationServiceByProfile(primary_profile);
625   if (integration_service &&
626       integration_service->GetMountPointPath().AppendRelativePath(
627           path, &relative_path)) {
628     if (arc::IsArcVmEnabled()) {
629       guest_os::GuestOsSharePath::GetForProfile(primary_profile)
630           ->SharePath(arc::kArcVmName, path, /*persist=*/false,
631                       base::DoNothing());
632       *arc_url_out =
633           GURL(kArcDriveContentUrlPrefix)
634               .Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
635       return true;
636     } else {
637       // TODO(b/157297349): For backward compatibility with ARC++ P, force
638       // external URL for DriveFS.
639       force_external = true;
640     }
641   }
642 
643   // Force external URL for Crostini.
644   if (GetCrostiniMountDirectory(primary_profile)
645           .AppendRelativePath(path, &relative_path)) {
646     force_external = true;
647   }
648 
649   // Force external URL for files under /media/archive.
650   if (base::FilePath(kArchiveMountPath)
651           .AppendRelativePath(path, &relative_path)) {
652     force_external = true;
653   }
654 
655   // Force external URL for smbfs.
656   chromeos::smb_client::SmbService* smb_service =
657       chromeos::smb_client::SmbServiceFactory::Get(primary_profile);
658   if (smb_service) {
659     chromeos::smb_client::SmbFsShare* share =
660         smb_service->GetSmbFsShareForPath(path);
661     if (share && share->mount_path().AppendRelativePath(path, &relative_path)) {
662       force_external = true;
663     }
664   }
665 
666   // Convert paths under /special or other paths forced to use external URL.
667   GURL external_file_url = chromeos::CreateExternalFileURLFromPath(
668       primary_profile, path, force_external);
669 
670   if (!external_file_url.is_empty()) {
671     *arc_url_out = arc::EncodeToChromeContentProviderUrl(external_file_url);
672     return true;
673   }
674 
675   // TODO(kinaba): Add conversion logic once other file systems are supported.
676   return false;
677 }
678 
ConvertToContentUrls(Profile * profile,const std::vector<storage::FileSystemURL> & file_system_urls,ConvertToContentUrlsCallback callback)679 void ConvertToContentUrls(
680     Profile* profile,
681     const std::vector<storage::FileSystemURL>& file_system_urls,
682     ConvertToContentUrlsCallback callback) {
683   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
684 
685   if (file_system_urls.empty()) {
686     std::move(callback).Run(std::vector<GURL>());
687     return;
688   }
689 
690   auto* documents_provider_root_map =
691       profile ? arc::ArcDocumentsProviderRootMap::GetForBrowserContext(profile)
692               : nullptr;
693 
694   // To keep the original order, prefill |out_urls| with empty URLs and
695   // specify index when updating it like (*out_urls)[index] = url.
696   auto out_urls = std::make_unique<std::vector<GURL>>(file_system_urls.size());
697   auto* out_urls_ptr = out_urls.get();
698   auto barrier = base::BarrierClosure(
699       file_system_urls.size(),
700       base::BindOnce(&OnAllContentUrlsResolved, std::move(callback),
701                      std::move(out_urls)));
702   auto single_content_url_callback =
703       base::BindRepeating(&OnSingleContentUrlResolved, barrier, out_urls_ptr);
704 
705   for (size_t index = 0; index < file_system_urls.size(); ++index) {
706     const auto& file_system_url = file_system_urls[index];
707 
708     // Run DocumentsProvider check before running ConvertPathToArcUrl.
709     // Otherwise, DocumentsProvider file path would be encoded to a
710     // ChromeContentProvider URL (b/132314050).
711     if (documents_provider_root_map) {
712       base::FilePath file_path;
713       auto* documents_provider_root =
714           documents_provider_root_map->ParseAndLookup(file_system_url,
715                                                       &file_path);
716       if (documents_provider_root) {
717         documents_provider_root->ResolveToContentUrl(
718             file_path, base::BindOnce(single_content_url_callback, index));
719         continue;
720       }
721     }
722 
723     GURL arc_url;
724     if (file_system_url.mount_type() == storage::kFileSystemTypeExternal &&
725         ConvertPathToArcUrl(file_system_url.path(), &arc_url)) {
726       single_content_url_callback.Run(index, arc_url);
727       continue;
728     }
729 
730     single_content_url_callback.Run(index, GURL());
731   }
732 }
733 
ConvertToContentUrls(const std::vector<storage::FileSystemURL> & file_system_urls,ConvertToContentUrlsCallback callback)734 void ConvertToContentUrls(
735     const std::vector<storage::FileSystemURL>& file_system_urls,
736     ConvertToContentUrlsCallback callback) {
737   ConvertToContentUrls(GetPrimaryProfile(), file_system_urls,
738                        std::move(callback));
739 }
740 
ReplacePrefix(std::string * s,const std::string & prefix,const std::string & replacement)741 bool ReplacePrefix(std::string* s,
742                    const std::string& prefix,
743                    const std::string& replacement) {
744   if (base::StartsWith(*s, prefix, base::CompareCase::SENSITIVE)) {
745     base::ReplaceFirstSubstringAfterOffset(s, 0, prefix, replacement);
746     return true;
747   }
748   return false;
749 }
750 
GetPathDisplayTextForSettings(Profile * profile,const std::string & path)751 std::string GetPathDisplayTextForSettings(Profile* profile,
752                                           const std::string& path) {
753   std::string result(path);
754   auto* drive_integration_service =
755       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
756   if (drive_integration_service && !drive_integration_service->is_enabled()) {
757     drive_integration_service = nullptr;
758   }
759   if (ReplacePrefix(&result, "/home/chronos/user/Downloads",
760                     kFolderNameDownloads)) {
761   } else if (ReplacePrefix(&result,
762                            "/home/chronos/" +
763                                profile->GetPath().BaseName().value() +
764                                "/Downloads",
765                            kFolderNameDownloads)) {
766   } else if (ReplacePrefix(
767                  &result,
768                  std::string("/home/chronos/user/") + kFolderNameMyFiles,
769                  "My files")) {
770   } else if (ReplacePrefix(&result,
771                            "/home/chronos/" +
772                                profile->GetPath().BaseName().value() + "/" +
773                                kFolderNameMyFiles,
774                            "My files")) {
775   } else if (drive_integration_service &&
776              ReplacePrefix(&result,
777                            drive_integration_service->GetMountPointPath()
778                                .Append(kDriveFsDirRoot)
779                                .value(),
780                            base::FilePath(kDisplayNameGoogleDrive)
781                                .Append(l10n_util::GetStringUTF8(
782                                    IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL))
783                                .value())) {
784   } else if (ReplacePrefix(&result,
785                            download_dir_util::kDriveNamePolicyVariableName,
786                            base::FilePath(kDisplayNameGoogleDrive)
787                                .Append(l10n_util::GetStringUTF8(
788                                    IDS_FILE_BROWSER_DRIVE_MY_DRIVE_LABEL))
789                                .value())) {
790   } else if (drive_integration_service &&
791              ReplacePrefix(&result,
792                            drive_integration_service->GetMountPointPath()
793                                .Append(kDriveFsDirTeamDrives)
794                                .value(),
795                            base::FilePath(kDisplayNameGoogleDrive)
796                                .Append(l10n_util::GetStringUTF8(
797                                    IDS_FILE_BROWSER_DRIVE_SHARED_DRIVES_LABEL))
798                                .value())) {
799   } else if (drive_integration_service &&
800              ReplacePrefix(&result,
801                            drive_integration_service->GetMountPointPath()
802                                .Append(kDriveFsDirComputers)
803                                .value(),
804                            base::FilePath(kDisplayNameGoogleDrive)
805                                .Append(l10n_util::GetStringUTF8(
806                                    IDS_FILE_BROWSER_DRIVE_COMPUTERS_LABEL))
807                                .value())) {
808   } else if (ReplacePrefix(&result, kAndroidFilesPath,
809                            l10n_util::GetStringUTF8(
810                                IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL))) {
811   } else if (ReplacePrefix(&result, GetCrostiniMountDirectory(profile).value(),
812                            l10n_util::GetStringUTF8(
813                                IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL))) {
814   } else if (ReplacePrefix(&result,
815                            base::FilePath(kRemovableMediaPath)
816                                .AsEndingWithSeparator()
817                                .value(),
818                            "")) {
819     // Strip prefix of "/media/removable/" including trailing slash.
820   } else if (ReplacePrefix(&result,
821                            base::FilePath(kArchiveMountPath)
822                                .AsEndingWithSeparator()
823                                .value(),
824                            "")) {
825     // Strip prefix of "/media/archive/" including trailing slash.
826   }
827 
828   base::ReplaceChars(result, "/", " \u203a ", &result);
829   return result;
830 }
831 
ExtractMountNameFileSystemNameFullPath(const base::FilePath & absolute_path,std::string * mount_name,std::string * file_system_name,std::string * full_path)832 bool ExtractMountNameFileSystemNameFullPath(const base::FilePath& absolute_path,
833                                             std::string* mount_name,
834                                             std::string* file_system_name,
835                                             std::string* full_path) {
836   DCHECK(absolute_path.IsAbsolute());
837   DCHECK(mount_name);
838   DCHECK(full_path);
839   storage::ExternalMountPoints* mount_points =
840       storage::ExternalMountPoints::GetSystemInstance();
841   base::FilePath virtual_path;
842   if (!mount_points->GetVirtualPath(absolute_path, &virtual_path))
843     return false;
844   // |virtual_path| format is: <mount_name>/<full_path>, and
845   // |file_system_name| == |mount_name|, except for 'removable' and 'archive',
846   // |mount_name| is the first two segments, |file_system_name| is the second.
847   const std::string& value = virtual_path.value();
848   size_t fs_start = 0;
849   size_t slash_pos = value.find(base::FilePath::kSeparators[0]);
850   *mount_name = *file_system_name = value.substr(0, slash_pos);
851   if (*mount_name == chromeos::kSystemMountNameRemovable ||
852       *mount_name == chromeos::kSystemMountNameArchive) {
853     if (slash_pos == std::string::npos) {
854       return false;
855     }
856     fs_start = slash_pos + 1;
857     slash_pos = value.find(base::FilePath::kSeparators[0], fs_start);
858     *mount_name = value.substr(0, slash_pos);
859   }
860 
861   // Set full_path to '/' if |absolute_path| is a root.
862   if (slash_pos == std::string::npos) {
863     *file_system_name = value.substr(fs_start);
864     *full_path = "/";
865   } else {
866     *file_system_name = value.substr(fs_start, slash_pos - fs_start);
867     *full_path = value.substr(slash_pos);
868   }
869   return true;
870 }
871 
ReplacePathPrefix(const base::FilePath & input,const base::FilePath & old_prefix,const base::FilePath & new_prefix)872 base::FilePath ReplacePathPrefix(const base::FilePath& input,
873                                  const base::FilePath& old_prefix,
874                                  const base::FilePath& new_prefix) {
875   if (old_prefix.IsParent(input)) {
876     base::FilePath output = new_prefix;
877     old_prefix.AppendRelativePath(input, &output);
878     return output;
879   }
880   return input;
881 }
882 
883 }  // namespace util
884 }  // namespace file_manager
885