text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
  
  #include "base/logging.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/message_loop/message_loop.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/synchronization/lock.h"
  #include "chrome/browser/chromeos/fileapi/file_access_permissions.h"
  #include "chrome/browser/chromeos/fileapi/file_system_backend_delegate.h"
  #include "chromeos/dbus/cros_disks_client.h"
  #include "webkit/browser/blob/file_stream_reader.h"
  #include "webkit/browser/fileapi/async_file_util.h"
  #include "webkit/browser/fileapi/external_mount_points.h"
  #include "webkit/browser/fileapi/file_stream_writer.h"
  #include "webkit/browser/fileapi/file_system_context.h"
  #include "webkit/browser/fileapi/file_system_operation.h"
  #include "webkit/browser/fileapi/file_system_operation_context.h"
  #include "webkit/browser/fileapi/file_system_url.h"
  #include "webkit/browser/fileapi/isolated_context.h"
  
  namespace {
  
  const char kChromeUIScheme[] = "chrome";
  
  }  // namespace
  
  namespace chromeos {
  
  // static
  bool FileSystemBackend::CanHandleURL(const fileapi::FileSystemURL& url) {
    if (!url.is_valid())
      return false;
    return url.type() == fileapi::kFileSystemTypeNativeLocal ||
           url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal ||
           url.type() == fileapi::kFileSystemTypeDrive;
  }
  
  FileSystemBackend::FileSystemBackend(
      FileSystemBackendDelegate* drive_delegate,
      scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy,
      scoped_refptr<fileapi::ExternalMountPoints> mount_points,
      fileapi::ExternalMountPoints* system_mount_points)
      : special_storage_policy_(special_storage_policy),
        file_access_permissions_(new FileAccessPermissions()),
        local_file_util_(fileapi::AsyncFileUtil::CreateForLocalFileSystem()),
        drive_delegate_(drive_delegate),
        mount_points_(mount_points),
        system_mount_points_(system_mount_points) {
  }
  
  FileSystemBackend::~FileSystemBackend() {
  }
  
  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(
        "archive",
        fileapi::kFileSystemTypeNativeLocal,
        fileapi::FileSystemMountOption(),
        chromeos::CrosDisksClient::GetArchiveMountPoint());
    system_mount_points_->RegisterFileSystem(
        "removable",
        fileapi::kFileSystemTypeNativeLocal,
        fileapi::FileSystemMountOption(fileapi::COPY_SYNC_OPTION_SYNC),
        chromeos::CrosDisksClient::GetRemovableDiskMountPoint());
    system_mount_points_->RegisterFileSystem(
        "oem",
        fileapi::kFileSystemTypeRestrictedNativeLocal,
        fileapi::FileSystemMountOption(),
        base::FilePath(FILE_PATH_LITERAL("/usr/share/oem")));
  }
  
  bool FileSystemBackend::CanHandleType(fileapi::FileSystemType type) const {
    switch (type) {
      case fileapi::kFileSystemTypeExternal:
      case fileapi::kFileSystemTypeDrive:
      case fileapi::kFileSystemTypeRestrictedNativeLocal:
      case fileapi::kFileSystemTypeNativeLocal:
      case fileapi::kFileSystemTypeNativeForPlatformApp:
        return true;
      default:
        return false;
    }
  }
  
  void FileSystemBackend::Initialize(fileapi::FileSystemContext* context) {
  }
  
  void FileSystemBackend::ResolveURL(const fileapi::FileSystemURL& url,
                                     fileapi::OpenFileSystemMode mode,
                                     const OpenFileSystemCallback& callback) {
    std::string id;
    fileapi::FileSystemType type;
    base::FilePath path;
    fileapi::FileSystemMountOption option;
    if (!mount_points_->CrackVirtualPath(
             url.virtual_path(), &id, &type, &path, &option) &&
        !system_mount_points_->CrackVirtualPath(
             url.virtual_path(), &id, &type, &path, &option)) {
      // Not under a mount point, so return an error, since the root is not
      // accessible.
      GURL root_url = GURL(fileapi::GetExternalFileSystemRootURIString(
          url.origin(), std::string()));
      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 =
        fileapi::GetExternalFileSystemRootURIString(url.origin(), 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(&components);
      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.
        callback.Run(
            GURL(root_url), std::string(), base::File::FILE_ERROR_SECURITY);
        return;
      }
      std::string inner_mount_name = components[1];
      root_url += inner_mount_name + "/";
      name = inner_mount_name;
    } else {
      name = id;
    }
  
    callback.Run(GURL(root_url), name, base::File::FILE_OK);
  }
  
  fileapi::FileSystemQuotaUtil* FileSystemBackend::GetQuotaUtil() {
    // No quota support.
    return NULL;
  }
  
  bool FileSystemBackend::IsAccessAllowed(
      const fileapi::FileSystemURL& url) const {
    if (!url.is_valid())
      return false;
  
    // Permit access to mount points from internal WebUI.
    const GURL& origin_url = url.origin();
    if (origin_url.SchemeIs(kChromeUIScheme))
      return true;
  
    // No extra check is needed for isolated file systems.
    if (url.mount_type() == fileapi::kFileSystemTypeIsolated)
      return true;
  
    if (!CanHandleURL(url))
      return false;
  
    std::string extension_id = origin_url.host();
    // TODO(mtomasz): Temporarily whitelist TimeScapes. Remove this in M-31.
    // See: crbug.com/271946
    if (extension_id == "mlbmkoenclnokonejhlfakkeabdlmpek" &&
        url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal) {
      return true;
    }
  
    // Check first to make sure this extension has fileBrowserHander permissions.
    if (!special_storage_policy_->IsFileHandler(extension_id))
      return false;
  
    return file_access_permissions_->HasAccessPermission(extension_id,
                                                         url.virtual_path());
  }
  
  void FileSystemBackend::GrantFullAccessToExtension(
      const std::string& extension_id) {
    DCHECK(special_storage_policy_->IsFileHandler(extension_id));
    if (!special_storage_policy_->IsFileHandler(extension_id))
      return;
    file_access_permissions_->GrantFullAccessPermission(extension_id);
  }
  
  void FileSystemBackend::GrantFileAccessToExtension(
      const std::string& extension_id, const base::FilePath& virtual_path) {
    // All we care about here is access from extensions for now.
    DCHECK(special_storage_policy_->IsFileHandler(extension_id));
    if (!special_storage_policy_->IsFileHandler(extension_id))
      return;
  
    std::string id;
    fileapi::FileSystemType type;
    base::FilePath path;
    fileapi::FileSystemMountOption option;
    if (!mount_points_->CrackVirtualPath(virtual_path,
                                         &id, &type, &path, &option) &&
        !system_mount_points_->CrackVirtualPath(virtual_path,
                                                &id, &type, &path, &option)) {
      return;
    }
  
    if (type == fileapi::kFileSystemTypeRestrictedNativeLocal) {
      LOG(ERROR) << "Can't grant access for restricted mount point";
      return;
    }
  
    file_access_permissions_->GrantAccessPermission(extension_id, virtual_path);
  }
  
  void FileSystemBackend::RevokeAccessForExtension(
        const std::string& extension_id) {
    file_access_permissions_->RevokePermissions(extension_id);
  }
  
  std::vector<base::FilePath> FileSystemBackend::GetRootDirectories() const {
    std::vector<fileapi::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;
  }
  
  fileapi::AsyncFileUtil* FileSystemBackend::GetAsyncFileUtil(
      fileapi::FileSystemType type) {
    if (type == fileapi::kFileSystemTypeDrive)
      return drive_delegate_->GetAsyncFileUtil(type);
  
    DCHECK(type == fileapi::kFileSystemTypeNativeLocal ||
           type == fileapi::kFileSystemTypeRestrictedNativeLocal);
    return local_file_util_.get();
  }
  
  fileapi::CopyOrMoveFileValidatorFactory*
  FileSystemBackend::GetCopyOrMoveFileValidatorFactory(
      fileapi::FileSystemType type, base::File::Error* error_code) {
    DCHECK(error_code);
    *error_code = base::File::FILE_OK;
    return NULL;
  }
  
  fileapi::FileSystemOperation* FileSystemBackend::CreateFileSystemOperation(
      const fileapi::FileSystemURL& url,
      fileapi::FileSystemContext* context,
      base::File::Error* error_code) const {
    DCHECK(url.is_valid());
  
    if (!IsAccessAllowed(url)) {
      *error_code = base::File::FILE_ERROR_SECURITY;
      return NULL;
    }
  
    DCHECK(url.type() == fileapi::kFileSystemTypeNativeLocal ||
           url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal ||
           url.type() == fileapi::kFileSystemTypeDrive);
    return fileapi::FileSystemOperation::Create(
        url, context,
        make_scoped_ptr(new fileapi::FileSystemOperationContext(context)));
  }
  
  bool FileSystemBackend::SupportsStreaming(
      const fileapi::FileSystemURL& url) const {
    return false;
  }
  
  scoped_ptr<webkit_blob::FileStreamReader>
  FileSystemBackend::CreateFileStreamReader(
      const fileapi::FileSystemURL& url,
      int64 offset,
      const base::Time& expected_modification_time,
      fileapi::FileSystemContext* context) const {
    DCHECK(url.is_valid());
  
    if (!IsAccessAllowed(url))
      return scoped_ptr<webkit_blob::FileStreamReader>();
  
    if (url.type() == fileapi::kFileSystemTypeDrive) {
      return drive_delegate_->CreateFileStreamReader(
          url, offset, expected_modification_time, context);
    }
  
    return scoped_ptr<webkit_blob::FileStreamReader>(
        webkit_blob::FileStreamReader::CreateForFileSystemFile(
            context, url, offset, expected_modification_time));
  }
  
  scoped_ptr<fileapi::FileStreamWriter>
  FileSystemBackend::CreateFileStreamWriter(
      const fileapi::FileSystemURL& url,
      int64 offset,
      fileapi::FileSystemContext* context) const {
    DCHECK(url.is_valid());
  
    if (!IsAccessAllowed(url))
      return scoped_ptr<fileapi::FileStreamWriter>();
  
    if (url.type() == fileapi::kFileSystemTypeDrive)
      return drive_delegate_->CreateFileStreamWriter(url, offset, context);
  
    if (url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal)
      return scoped_ptr<fileapi::FileStreamWriter>();
  
    DCHECK(url.type() == fileapi::kFileSystemTypeNativeLocal);
    return scoped_ptr<fileapi::FileStreamWriter>(
        fileapi::FileStreamWriter::CreateForLocalFile(
            context->default_file_task_runner(), url.path(), offset,
            fileapi::FileStreamWriter::OPEN_EXISTING_FILE));
  }
  
  bool FileSystemBackend::GetVirtualPath(
      const base::FilePath& filesystem_path,
      base::FilePath* virtual_path) {
    return mount_points_->GetVirtualPath(filesystem_path, virtual_path) ||
           system_mount_points_->GetVirtualPath(filesystem_path, virtual_path);
  }
  
  }  // namespace chromeos
  