chromium/chrome/browser/ash/fileapi/file_system_backend.cc

// 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/fileapi/file_system_backend.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "ash/webui/file_manager/url_constants.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "base/strings/escape.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/fileapi/file_access_permissions.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/ash/fileapi/file_system_backend_delegate.h"
#include "chrome/browser/ash/fileapi/observable_file_system_operation_impl.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
#include "chrome/common/url_constants.h"
#include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
#include "components/file_access/scoped_file_access_delegate.h"
#include "components/user_manager/user.h"
#include "extensions/common/extension.h"
#include "storage/browser/file_system/async_file_util.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_stream_writer.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_operation.h"
#include "storage/browser/file_system/file_system_operation_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_mount_option.h"
#include "storage/common/file_system/file_system_types.h"
#include "storage/common/file_system/file_system_util.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace ash {
namespace {

// Returns the `AccountId` associated with the specified `profile`.
AccountId GetAccountId(Profile* profile) {
  user_manager::User* user =
      profile ? ProfileHelper::Get()->GetUserByProfile(profile) : nullptr;
  return user ? user->GetAccountId() : AccountId();
}

// Returns true if the BackendFunction and OperationType correspond to the
// GetMetadata() and CreateFileStreamReader() calls used when loading a
// filesystem: URL via FileSystemUrlLoaderFactory.
bool IsReadOperation(BackendFunction backend_function,
                     storage::OperationType operation_type) {
  if (backend_function == BackendFunction::kCreateFileSystemOperation &&
      operation_type == storage::OperationType::kGetMetadata) {
    return true;
  }
  if (backend_function == BackendFunction::kCreateFileStreamReader) {
    return true;
  }
  return false;
}

}  // namespace

// static
bool FileSystemBackend::CanHandleURL(const storage::FileSystemURL& url) {
  if (!url.is_valid())
    return false;
  return url.type() == storage::kFileSystemTypeLocal ||
         url.type() == storage::kFileSystemTypeProvided ||
         url.type() == storage::kFileSystemTypeDeviceMediaAsFileStorage ||
         url.type() == storage::kFileSystemTypeArcContent ||
         url.type() == storage::kFileSystemTypeArcDocumentsProvider ||
         url.type() == storage::kFileSystemTypeDriveFs ||
         url.type() == storage::kFileSystemTypeSmbFs ||
         url.type() == storage::kFileSystemTypeFuseBox;
}

FileSystemBackend::FileSystemBackend(
    Profile* profile,
    std::unique_ptr<FileSystemBackendDelegate> file_system_provider_delegate,
    std::unique_ptr<FileSystemBackendDelegate> mtp_delegate,
    std::unique_ptr<FileSystemBackendDelegate> arc_content_delegate,
    std::unique_ptr<FileSystemBackendDelegate> arc_documents_provider_delegate,
    std::unique_ptr<FileSystemBackendDelegate> drivefs_delegate,
    std::unique_ptr<FileSystemBackendDelegate> smbfs_delegate,
    scoped_refptr<storage::ExternalMountPoints> mount_points,
    storage::ExternalMountPoints* system_mount_points)
    : account_id_(GetAccountId(profile)),
      file_access_permissions_(new FileAccessPermissions()),
      local_file_util_(storage::AsyncFileUtil::CreateForLocalFileSystem()),
      file_system_provider_delegate_(std::move(file_system_provider_delegate)),
      mtp_delegate_(std::move(mtp_delegate)),
      arc_content_delegate_(std::move(arc_content_delegate)),
      arc_documents_provider_delegate_(
          std::move(arc_documents_provider_delegate)),
      drivefs_delegate_(std::move(drivefs_delegate)),
      smbfs_delegate_(std::move(smbfs_delegate)),
      mount_points_(mount_points),
      system_mount_points_(system_mount_points) {}

FileSystemBackend::~FileSystemBackend() {}

// static
FileSystemBackend* FileSystemBackend::Get(
    const storage::FileSystemContext& context) {
  return static_cast<FileSystemBackend*>(
      context.GetFileSystemBackend(storage::kFileSystemTypeExternal));
}

void FileSystemBackend::AddSystemMountPoints() {
  // RegisterFileSystem() is no-op if the mount point with the same name
  // already exists, hence it's safe to call without checking if a mount
  // point already exists or not.
  system_mount_points_->RegisterFileSystem(
      kSystemMountNameArchive, storage::kFileSystemTypeLocal,
      storage::FileSystemMountOption(),
      CrosDisksClient::GetArchiveMountPoint());
  system_mount_points_->RegisterFileSystem(
      kSystemMountNameRemovable, storage::kFileSystemTypeLocal,
      storage::FileSystemMountOption(storage::FlushPolicy::FLUSH_ON_COMPLETION),
      CrosDisksClient::GetRemovableDiskMountPoint());
}

bool FileSystemBackend::CanHandleType(storage::FileSystemType type) const {
  switch (type) {
    case storage::kFileSystemTypeExternal:
    case storage::kFileSystemTypeLocal:
    case storage::kFileSystemTypeLocalForPlatformApp:
    case storage::kFileSystemTypeDeviceMediaAsFileStorage:
    case storage::kFileSystemTypeProvided:
    case storage::kFileSystemTypeArcContent:
    case storage::kFileSystemTypeArcDocumentsProvider:
    case storage::kFileSystemTypeDriveFs:
    case storage::kFileSystemTypeSmbFs:
    case storage::kFileSystemTypeFuseBox:
      return true;
    default:
      return false;
  }
}

void FileSystemBackend::Initialize(storage::FileSystemContext* context) {}

void FileSystemBackend::ResolveURL(const storage::FileSystemURL& url,
                                   storage::OpenFileSystemMode mode,
                                   ResolveURLCallback callback) {
  std::string id;
  storage::FileSystemType type;
  std::string cracked_id;
  base::FilePath path;
  storage::FileSystemMountOption option;
  if (!mount_points_->CrackVirtualPath(url.virtual_path(), &id, &type,
                                       &cracked_id, &path, &option) &&
      !system_mount_points_->CrackVirtualPath(url.virtual_path(), &id, &type,
                                              &cracked_id, &path, &option)) {
    // Not under a mount point, so return an error, since the root is not
    // accessible.
    GURL root_url = GURL(storage::GetExternalFileSystemRootURIString(
        url.origin().GetURL(), std::string()));
    std::move(callback).Run(root_url, std::string(),
                            base::File::FILE_ERROR_SECURITY);
    return;
  }

  std::string name;
  // Construct a URL restricted to the found mount point.
  std::string root_url =
      storage::GetExternalFileSystemRootURIString(url.origin().GetURL(), id);

  // For removable and archives, the file system root is the external mount
  // point plus the inner mount point.
  if (id == "archive" || id == "removable") {
    std::vector<std::string> components = url.virtual_path().GetComponents();
    DCHECK_EQ(id, components.at(0));
    if (components.size() < 2) {
      // Unable to access /archive and /removable directories directly. The
      // inner mount name must be specified.
      std::move(callback).Run(GURL(root_url), std::string(),
                              base::File::FILE_ERROR_SECURITY);
      return;
    }
    std::string inner_mount_name = base::EscapePath(components[1]);
    root_url += inner_mount_name + "/";
    name = inner_mount_name;
  } else if (id == arc::kDocumentsProviderMountPointName) {
    // For ARC documents provider file system, volumes are mounted per document
    // provider root, so we need to fix up |root_url| to point to an individual
    // root.
    std::string authority;
    std::string root_id;
    base::FilePath unused_path;
    if (!arc::ParseDocumentsProviderUrl(url, &authority, &root_id,
                                        &unused_path)) {
      std::move(callback).Run(GURL(root_url), std::string(),
                              base::File::FILE_ERROR_SECURITY);
      return;
    }
    base::FilePath mount_path =
        arc::GetDocumentsProviderMountPath(authority, root_id);
    base::FilePath relative_mount_path;
    base::FilePath(arc::kDocumentsProviderMountPointPath)
        .AppendRelativePath(mount_path, &relative_mount_path);
    root_url +=
        base::EscapePath(storage::FilePathToString(relative_mount_path)) + "/";
    name = authority + ":" + root_id;
  } else {
    name = id;
  }

  std::move(callback).Run(GURL(root_url), name, base::File::FILE_OK);
}

storage::FileSystemQuotaUtil* FileSystemBackend::GetQuotaUtil() {
  // No quota support.
  return nullptr;
}

const storage::UpdateObserverList* FileSystemBackend::GetUpdateObservers(
    storage::FileSystemType type) const {
  return nullptr;
}

const storage::ChangeObserverList* FileSystemBackend::GetChangeObservers(
    storage::FileSystemType type) const {
  return nullptr;
}

const storage::AccessObserverList* FileSystemBackend::GetAccessObservers(
    storage::FileSystemType type) const {
  return nullptr;
}

bool FileSystemBackend::IsAccessAllowed(
    BackendFunction backend_function,
    storage::OperationType operation_type,
    const storage::FileSystemURL& url) const {
  if (!url.is_valid())
    return false;

  // No extra check is needed for isolated file systems.
  if (url.mount_type() == storage::kFileSystemTypeIsolated)
    return true;

  if (!CanHandleURL(url))
    return false;

  const url::Origin origin = url.origin();
  // If there is no origin set, then it's an internal access.
  if (origin.opaque())
    return true;

  // The chrome://file-manager can access its filesystem origin.
  if (origin.GetURL() == file_manager::kChromeUIFileManagerURL) {
    return true;
  }

  // ImageLoader extension has read-only access via FileSystemUrlLoaderFactory.
  if (origin.GetURL() == extensions::Extension::GetBaseURLFromExtensionId(
                             ::file_manager::kImageLoaderExtensionId) &&
      IsReadOperation(backend_function, operation_type)) {
    return true;
  }

  return file_access_permissions_->HasAccessPermission(origin,
                                                       url.virtual_path());
}

void FileSystemBackend::GrantFileAccessToOrigin(
    const url::Origin& origin,
    const base::FilePath& virtual_path) {
  std::string id;
  storage::FileSystemType type;
  std::string cracked_id;
  base::FilePath path;
  storage::FileSystemMountOption option;
  if (!mount_points_->CrackVirtualPath(virtual_path, &id, &type, &cracked_id,
                                       &path, &option) &&
      !system_mount_points_->CrackVirtualPath(virtual_path, &id, &type,
                                              &cracked_id, &path, &option)) {
    return;
  }

  file_access_permissions_->GrantAccessPermission(origin, virtual_path);
}

void FileSystemBackend::RevokeAccessForOrigin(const url::Origin& origin) {
  file_access_permissions_->RevokePermissions(origin);
}

std::vector<base::FilePath> FileSystemBackend::GetRootDirectories() const {
  std::vector<storage::MountPoints::MountPointInfo> mount_points;
  mount_points_->AddMountPointInfosTo(&mount_points);
  system_mount_points_->AddMountPointInfosTo(&mount_points);

  std::vector<base::FilePath> root_dirs;
  for (size_t i = 0; i < mount_points.size(); ++i)
    root_dirs.push_back(mount_points[i].path);
  return root_dirs;
}

storage::AsyncFileUtil* FileSystemBackend::GetAsyncFileUtil(
    storage::FileSystemType type) {
  switch (type) {
    case storage::kFileSystemTypeProvided:
      return file_system_provider_delegate_->GetAsyncFileUtil(type);
    case storage::kFileSystemTypeLocal:
    case storage::kFileSystemTypeFuseBox:
      return local_file_util_.get();
    case storage::kFileSystemTypeDeviceMediaAsFileStorage:
      return mtp_delegate_->GetAsyncFileUtil(type);
    case storage::kFileSystemTypeArcContent:
      return arc_content_delegate_->GetAsyncFileUtil(type);
    case storage::kFileSystemTypeArcDocumentsProvider:
      return arc_documents_provider_delegate_->GetAsyncFileUtil(type);
    case storage::kFileSystemTypeDriveFs:
      return drivefs_delegate_->GetAsyncFileUtil(type);
    case storage::kFileSystemTypeSmbFs:
      return smbfs_delegate_->GetAsyncFileUtil(type);
    default:
      NOTREACHED_IN_MIGRATION();
  }
  return nullptr;
}

storage::WatcherManager* FileSystemBackend::GetWatcherManager(
    storage::FileSystemType type) {
  if (type == storage::kFileSystemTypeProvided)
    return file_system_provider_delegate_->GetWatcherManager(type);

  if (type == storage::kFileSystemTypeDeviceMediaAsFileStorage) {
    return mtp_delegate_->GetWatcherManager(type);
  }

  if (type == storage::kFileSystemTypeArcDocumentsProvider)
    return arc_documents_provider_delegate_->GetWatcherManager(type);

  // TODO(mtomasz): Add support for other backends.
  return nullptr;
}

storage::CopyOrMoveFileValidatorFactory*
FileSystemBackend::GetCopyOrMoveFileValidatorFactory(
    storage::FileSystemType type,
    base::File::Error* error_code) {
  DCHECK(error_code);
  *error_code = base::File::FILE_OK;
  return nullptr;
}

std::unique_ptr<storage::FileSystemOperation>
FileSystemBackend::CreateFileSystemOperation(
    storage::OperationType type,
    const storage::FileSystemURL& url,
    storage::FileSystemContext* context,
    base::File::Error* error_code) const {
  DCHECK(url.is_valid());

  if (!IsAccessAllowed(BackendFunction::kCreateFileSystemOperation, type,
                       url)) {
    *error_code = base::File::FILE_ERROR_SECURITY;
    return nullptr;
  }

  if (url.type() == storage::kFileSystemTypeDeviceMediaAsFileStorage) {
    // MTP file operations run on MediaTaskRunner.
    return std::make_unique<ObservableFileSystemOperationImpl>(
        account_id_, type, url, context,
        std::make_unique<storage::FileSystemOperationContext>(
            context, MediaFileSystemBackend::MediaTaskRunner().get()));
  }
  if (url.type() == storage::kFileSystemTypeLocal ||
      url.type() == storage::kFileSystemTypeDriveFs ||
      url.type() == storage::kFileSystemTypeSmbFs ||
      url.type() == storage::kFileSystemTypeFuseBox) {
    return std::make_unique<ObservableFileSystemOperationImpl>(
        account_id_, type, url, context,
        std::make_unique<storage::FileSystemOperationContext>(
            context, base::ThreadPool::CreateSequencedTaskRunner(
                         {base::MayBlock(), base::TaskPriority::USER_VISIBLE})
                         .get()));
  }

  DCHECK(url.type() == storage::kFileSystemTypeProvided ||
         url.type() == storage::kFileSystemTypeArcContent ||
         url.type() == storage::kFileSystemTypeArcDocumentsProvider);
  return std::make_unique<ObservableFileSystemOperationImpl>(
      account_id_, type, url, context,
      std::make_unique<storage::FileSystemOperationContext>(context));
}

bool FileSystemBackend::SupportsStreaming(
    const storage::FileSystemURL& url) const {
  return url.type() == storage::kFileSystemTypeProvided ||
         url.type() == storage::kFileSystemTypeDeviceMediaAsFileStorage ||
         url.type() == storage::kFileSystemTypeArcContent ||
         url.type() == storage::kFileSystemTypeArcDocumentsProvider;
}

bool FileSystemBackend::HasInplaceCopyImplementation(
    storage::FileSystemType type) const {
  switch (type) {
    case storage::kFileSystemTypeProvided:
    case storage::kFileSystemTypeDeviceMediaAsFileStorage:
    case storage::kFileSystemTypeDriveFs:
      return true;
    // TODO(fukino): Support in-place copy for DocumentsProvider.
    // crbug.com/953603.
    case storage::kFileSystemTypeArcDocumentsProvider:
    case storage::kFileSystemTypeLocal:
    case storage::kFileSystemTypeArcContent:
    // TODO(crbug.com/41445433): Implement in-place copy in SmbFs.
    case storage::kFileSystemTypeSmbFs:
    case storage::kFileSystemTypeFuseBox:
      return false;
    default:
      NOTREACHED_IN_MIGRATION();
  }
  return true;
}

std::unique_ptr<storage::FileStreamReader>
FileSystemBackend::CreateFileStreamReader(
    const storage::FileSystemURL& url,
    int64_t offset,
    int64_t max_bytes_to_read,
    const base::Time& expected_modification_time,
    storage::FileSystemContext* context,
    file_access::ScopedFileAccessDelegate::RequestFilesAccessIOCallback
        file_access) const {
  DCHECK(url.is_valid());

  if (!IsAccessAllowed(BackendFunction::kCreateFileStreamReader,
                       storage::OperationType::kNone, url)) {
    return nullptr;
  }

  switch (url.type()) {
    case storage::kFileSystemTypeProvided:
      return file_system_provider_delegate_->CreateFileStreamReader(
          url, offset, max_bytes_to_read, expected_modification_time, context);
    // The dlp file_access callback is needed for the local filesystem only.
    case storage::kFileSystemTypeLocal:
      return storage::FileStreamReader::CreateForLocalFile(
          base::ThreadPool::CreateTaskRunner(
              {base::MayBlock(), base::TaskPriority::USER_VISIBLE})
              .get(),
          url.path(), offset, expected_modification_time,
          std::move(file_access));
    case storage::kFileSystemTypeDriveFs:
    case storage::kFileSystemTypeSmbFs:
    case storage::kFileSystemTypeFuseBox:
      return storage::FileStreamReader::CreateForLocalFile(
          base::ThreadPool::CreateTaskRunner(
              {base::MayBlock(), base::TaskPriority::USER_VISIBLE})
              .get(),
          url.path(), offset, expected_modification_time);
    case storage::kFileSystemTypeDeviceMediaAsFileStorage:
      return mtp_delegate_->CreateFileStreamReader(
          url, offset, max_bytes_to_read, expected_modification_time, context);
    case storage::kFileSystemTypeArcContent:
      return arc_content_delegate_->CreateFileStreamReader(
          url, offset, max_bytes_to_read, expected_modification_time, context);
    case storage::kFileSystemTypeArcDocumentsProvider:
      return arc_documents_provider_delegate_->CreateFileStreamReader(
          url, offset, max_bytes_to_read, expected_modification_time, context);
    default:
      NOTREACHED_IN_MIGRATION();
  }
  return nullptr;
}

std::unique_ptr<storage::FileStreamWriter>
FileSystemBackend::CreateFileStreamWriter(
    const storage::FileSystemURL& url,
    int64_t offset,
    storage::FileSystemContext* context) const {
  DCHECK(url.is_valid());

  if (!IsAccessAllowed(BackendFunction::kCreateFileStreamWriter,
                       storage::OperationType::kNone, url)) {
    return nullptr;
  }

  switch (url.type()) {
    case storage::kFileSystemTypeProvided:
      return file_system_provider_delegate_->CreateFileStreamWriter(url, offset,
                                                                    context);
    case storage::kFileSystemTypeLocal:
    case storage::kFileSystemTypeDriveFs:
    case storage::kFileSystemTypeSmbFs:
    case storage::kFileSystemTypeFuseBox:
      return storage::FileStreamWriter::CreateForLocalFile(
          base::ThreadPool::CreateTaskRunner(
              {base::MayBlock(), base::TaskPriority::USER_VISIBLE})
              .get(),
          url.path(), offset, storage::FileStreamWriter::OPEN_EXISTING_FILE);
    case storage::kFileSystemTypeDeviceMediaAsFileStorage:
      return mtp_delegate_->CreateFileStreamWriter(url, offset, context);
    case storage::kFileSystemTypeArcDocumentsProvider:
      return arc_documents_provider_delegate_->CreateFileStreamWriter(
          url, offset, context);
    // Read only file systems.
    case storage::kFileSystemTypeArcContent:
      return nullptr;
    default:
      NOTREACHED_IN_MIGRATION();
  }
  return nullptr;
}

bool FileSystemBackend::GetVirtualPath(const base::FilePath& filesystem_path,
                                       base::FilePath* virtual_path) const {
  return mount_points_->GetVirtualPath(filesystem_path, virtual_path) ||
         system_mount_points_->GetVirtualPath(filesystem_path, virtual_path);
}

storage::FileSystemURL FileSystemBackend::CreateInternalURL(
    storage::FileSystemContext* context,
    const base::FilePath& entry_path) const {
  base::FilePath virtual_path;
  if (!GetVirtualPath(entry_path, &virtual_path))
    return storage::FileSystemURL();

  return context->CreateCrackedFileSystemURL(
      blink::StorageKey(), storage::kFileSystemTypeExternal, virtual_path);
}

}  // namespace ash