// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_PUBLIC_BROWSER_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
#define CONTENT_PUBLIC_BROWSER_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_

#include <string>
#include <vector>

#if defined(UNIT_TEST)
#include <ostream>
#endif

#include "base/files/file_path.h"
#include "base/types/expected.h"
#include "content/public/browser/file_system_access_permission_grant.h"
#include "content/public/browser/file_system_access_write_item.h"
#include "content/public/browser/global_routing_id.h"
#include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom-forward.h"
#include "url/origin.h"

class GURL;

namespace storage {
class FileSystemURL;
}  // namespace storage

namespace content {
class RenderFrameHost;

// These values are used in json serialization. Entries should not be
// renumbered and numeric values should never be reused.
enum class PathType {
  // A path on the local file system. Files with these paths can be operated
  // on by base::File.
  kLocal = 0,

  // A path on an "external" file system. These paths can only be accessed via
  // the filesystem abstraction in //storage/browser/file_system, and a
  // storage::FileSystemURL of type storage::kFileSystemTypeExternal.
  // This path type should be used for paths retrieved via the `virtual_path`
  // member of a ui::SelectedFileInfo struct.
  kExternal = 1
};

struct PathInfo {
  PathType type = PathType::kLocal;
  // Full path of file or directory.
  base::FilePath path;
  // Display name of file or directory, must not be empty. This is usually
  // path.BaseName(), but in some cases such as android content-URIs the path is
  // unrelated to the display name.
  std::string display_name;

  PathInfo() = default;
  explicit PathInfo(base::FilePath path)
      : path(std::move(path)),
        display_name(this->path.BaseName().AsUTF8Unsafe()) {
    CHECK(!this->path.empty());
    CHECK(!this->display_name.empty());
  }
  explicit PathInfo(base::FilePath::StringViewType path)
      : PathInfo(base::FilePath(path)) {
    CHECK(!this->path.empty());
    CHECK(!this->display_name.empty());
  }
  PathInfo(PathType type, base::FilePath path)
      : type(type),
        path(std::move(path)),
        display_name(this->path.BaseName().AsUTF8Unsafe()) {
    CHECK(!this->path.empty());
    CHECK(!this->display_name.empty());
  }
  PathInfo(base::FilePath path, std::string display_name)
      : path(std::move(path)), display_name(std::move(display_name)) {
    CHECK(!this->path.empty());
    CHECK(!this->display_name.empty());
  }
  PathInfo(PathType type, base::FilePath path, std::string display_name)
      : type(type),
        path(std::move(path)),
        display_name(std::move(display_name)) {
    CHECK(!this->path.empty());
    CHECK(!this->display_name.empty());
  }

  auto operator<=>(const PathInfo& other) const = default;
};

// For testing only.
#if defined(UNIT_TEST)
inline std::ostream& operator<<(std::ostream& os, const PathInfo& path_info) {
  return os << (int)path_info.type << ':' << path_info.path << ':'
            << path_info.display_name;
}
#endif

// Entry point to an embedder implemented permission context for the File System
// Access API. Instances of this class can be retrieved via a BrowserContext.
// All these methods must always be called on the UI thread.
class FileSystemAccessPermissionContext {
 public:
  // The type of action a user took that resulted in needing a permission grant
  // for a particular path. This is used to signal to the permission context if
  // the path was the result of a "save" operation, which an implementation can
  // use to automatically grant write access to the path.
  enum class UserAction {
    // The path for which a permission grant is requested was the result of a
    // "open" dialog. As such, only read access to files should be automatically
    // granted, but read access to directories as well as write access to files
    // or directories should not be granted without needing to request it.
    kOpen,
    // The path for which a permission grant is requested was the result of a
    // "save" dialog, and as such it could make sense to return a grant that
    // immediately allows write access without needing to request it.
    kSave,
    // The path for which a permission grant is requested was the result of
    // loading a handle from storage. As such the grant should not start out
    // as granted, even for read access.
    kLoadFromStorage,
    // The path for which a permission grant is requested was the result of a
    // drag&drop operation. Read access should start out granted, but write
    // access will require a prompt.
    kDragAndDrop,
    // The path for which a permission grant is requested was not the result of
    // a user action. This is used for checking additional blocklist check of
    // a path when obtaining a handle, therefore no prompt needs to be shown.
    kNone,
  };

  // This enum helps distinguish between file or directory File System Access
  // handles.
  enum class HandleType { kFile, kDirectory };

  using EntriesAllowedByEnterprisePolicyCallback =
      base::OnceCallback<void(std::vector<PathInfo>)>;

  // Returns the read permission grant to use for a particular path.
  virtual scoped_refptr<FileSystemAccessPermissionGrant> GetReadPermissionGrant(
      const url::Origin& origin,
      const PathInfo& path_info,
      HandleType handle_type,
      UserAction user_action) = 0;

  // Returns the permission grant to use for a particular path. This could be a
  // grant that applies to more than just the path passed in, for example if a
  // user has already granted write access to a directory, this method could
  // return that existing grant when figuring the grant to use for a file in
  // that directory.
  virtual scoped_refptr<FileSystemAccessPermissionGrant>
  GetWritePermissionGrant(const url::Origin& origin,
                          const PathInfo& path_info,
                          HandleType handle_type,
                          UserAction user_action) = 0;

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class SensitiveEntryResult {
    kAllowed = 0,   // Access to entry is okay.
    kTryAgain = 1,  // User should pick a different entry.
    kAbort = 2,     // Abandon entirely, as if picking was cancelled.
    kMaxValue = kAbort
  };
  // Checks if access to the given `path` should be allowed or blocked. This is
  // used to implement blocks for certain sensitive directories such as the
  // "Windows" system directory, as well as the root of the "home" directory.
  // For downloads ("Save as") it also checks the file extension. Calls
  // `callback` with the result of the check, after potentially showing some UI
  // to the user if the path is dangerous or should not be accessed.
  virtual void ConfirmSensitiveEntryAccess(
      const url::Origin& origin,
      const PathInfo& path_info,
      HandleType handle_type,
      UserAction user_action,
      GlobalRenderFrameHostId frame_id,
      base::OnceCallback<void(SensitiveEntryResult)> callback) = 0;

  enum class AfterWriteCheckResult { kAllow, kBlock };
  // Runs a recently finished write operation through checks such as malware
  // or other security checks to determine if the write should be allowed.
  virtual void PerformAfterWriteChecks(
      std::unique_ptr<FileSystemAccessWriteItem> item,
      GlobalRenderFrameHostId frame_id,
      base::OnceCallback<void(AfterWriteCheckResult)> callback) = 0;

  // Returns whether the file type is considered dangerous. This is used to
  // block file operations from creating or accessing these file types.
  virtual bool IsFileTypeDangerous(const base::FilePath& path) = 0;

  // Returns whether the given RFH can use file picker.
  virtual base::expected<void, std::string> CanShowFilePicker(
      RenderFrameHost* rfh) = 0;

  // Returns whether the give |origin| already allows read permission, or it is
  // possible to request one. This is used to block file dialogs from being
  // shown if permission won't be granted anyway.
  virtual bool CanObtainReadPermission(const url::Origin& origin) = 0;

  // Returns whether the give |origin| already allows write permission, or it is
  // possible to request one. This is used to block save file dialogs from being
  // shown if there is no need to ask for it.
  virtual bool CanObtainWritePermission(const url::Origin& origin) = 0;

  // Store the directory recently chosen by a file picker. This can later be
  // retrieved via a call to |GetLastPickedDirectory| with the corresponding
  // |origin| and |id|.
  virtual void SetLastPickedDirectory(const url::Origin& origin,
                                      const std::string& id,
                                      const content::PathInfo& path_info) = 0;
  // Returns the directory recently chosen by a file picker for a given
  // |origin| and |id|.
  virtual PathInfo GetLastPickedDirectory(const url::Origin& origin,
                                          const std::string& id) = 0;

  // Return the path associated with well-known directories such as "desktop"
  // and "music", or a default path if the |directory| cannot be matched to a
  // well-known directory. When |directory| is WellKnownDirectory.DIR_DOWNLOADS,
  // |origin| is used to determine if browser-specified download directory
  // should be returned instead of OS default download directory.
  virtual base::FilePath GetWellKnownDirectoryPath(
      blink::mojom::WellKnownDirectory directory,
      const url::Origin& origin) = 0;

  // Return the desired title of the file picker for the given `options`.
  virtual std::u16string GetPickerTitle(
      const blink::mojom::FilePickerOptionsPtr& options) = 0;

  // Notifies that the underlying file or directory has been moved and updates
  // permission grants accordingly.
  virtual void NotifyEntryMoved(const url::Origin& origin,
                                const PathInfo& old_path,
                                const PathInfo& new_path) = 0;

  // Called after a file has been modified (e.g. through a write or move
  // operation) to restore read access that might have been revoked after a
  // `remove()` call.
  // See https://crbug.com/421690393.
  virtual void NotifyEntryModified(const url::Origin& origin,
                                   const PathInfo& path) = 0;

  // Notifies that the underlying file or directory has been removed and updates
  // permission grants accordingly.
  // See https://crbug.com/421690393.
  virtual void NotifyEntryRemoved(const url::Origin& origin,
                                  const PathInfo& path) = 0;

  // Invoked on file creation events originating from
  // `window.showSaveFilePicker()`.
  //
  // See `FileSystemAccessEntryFactory::BindingContext`.
  virtual void OnFileCreatedFromShowSaveFilePicker(
      const GURL& file_picker_binding_context,
      const storage::FileSystemURL& url) = 0;

  // Checks the paths listed in `entries` to determine if they should be allowed
  // or blocked within this context, for the given render frame host, based on
  // enterprise policies. Invokes `callback` with the list of entries which are
  // allowed.
  virtual void CheckPathsAgainstEnterprisePolicy(
      std::vector<PathInfo> entries,
      GlobalRenderFrameHostId frame_id,
      EntriesAllowedByEnterprisePolicyCallback callback) = 0;

 protected:
  virtual ~FileSystemAccessPermissionContext() = default;
};

}  // namespace content

#endif  // CONTENT_PUBLIC_BROWSER_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
