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

#ifndef UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_
#define UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_

#include <windows.h>
#undef LoadBitmap  // Conflicts with ui/ API name.

#include <objidl.h>
#include <shlobj.h>
#include <stddef.h>
#include <wrl/client.h>

#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/win/scoped_co_mem.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/dragdrop/os_exchange_data_provider.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image_skia.h"

namespace ui {

// Placeholder path used for virtual file drag metadata on dragenter,
// before actual temp files are created. See GetVirtualFilenames().
inline constexpr wchar_t kVirtualFileTempPlaceholderPath[] =
    FILE_PATH_LITERAL("temp.tmp");

// A wrapper class to manage the lifetime of the memory allocated for a
// `DVTARGETDEVICE`.
//
// The `FORMATETC` struct is defined by Windows and it defines `ptd` as a simple
// pointer to a `DVTARGETDEVICE` allocated with `CoTaskMemAlloc` of size
// `tdSize`. When copying a `FORMATETC` struct, its target device must be deep
// copied.
//
// `ScopedTargetDevice` encapsulates allocating the required size for the target
// device and managing the lifetime and copying of the memory.
//
// Note: We cannot use `base::win::ScopedCoMem` here because it does not support
// a `release()` method to transfer ownership of the underlying memory to COM
// callers (e.g. in `FormatEtcEnumerator::Next`, where the caller outside of
// Chromium becomes responsible for freeing the memory).
class COMPONENT_EXPORT(UI_BASE) ScopedTargetDevice {
 public:
  ScopedTargetDevice();
  explicit ScopedTargetDevice(const DVTARGETDEVICE* source);
  ScopedTargetDevice(const ScopedTargetDevice& other);
  ScopedTargetDevice& operator=(const ScopedTargetDevice& other);
  ScopedTargetDevice(ScopedTargetDevice&& other) noexcept;
  ScopedTargetDevice& operator=(ScopedTargetDevice&& other) noexcept;
  ~ScopedTargetDevice();

  const DVTARGETDEVICE* get() const { return device_; }
  DVTARGETDEVICE* get() { return device_; }
  void Reset(const DVTARGETDEVICE* source);
  DVTARGETDEVICE* release();

 private:
  RAW_PTR_EXCLUSION DVTARGETDEVICE* device_ = nullptr;
};

// A wrapper around the `FORMATETC` struct that ensures the associated target
// device is deep copied and its lifetime is managed correctly via
// `ScopedTargetDevice`.
struct COMPONENT_EXPORT(UI_BASE) ScopedFormatEtc {
 public:
  ScopedFormatEtc();
  explicit ScopedFormatEtc(const FORMATETC& source);
  ScopedFormatEtc(const ScopedFormatEtc& other);
  ScopedFormatEtc& operator=(const ScopedFormatEtc& other);
  ScopedFormatEtc(ScopedFormatEtc&& other) noexcept;
  ScopedFormatEtc& operator=(ScopedFormatEtc&& other) noexcept;
  ~ScopedFormatEtc();

  const FORMATETC* operator->() const { return &format_etc; }
  FORMATETC* operator->() { return &format_etc; }

  FORMATETC format_etc = {};
  ScopedTargetDevice target_device;
};

class DataObjectImpl : public DownloadFileObserver,
                       public IDataObject,
                       public IDataObjectAsyncCapability {
 public:
  DataObjectImpl();

  // Accessors.
  void set_in_drag_loop(bool in_drag_loop) { in_drag_loop_ = in_drag_loop; }

  // Number of known formats.
  size_t size() const { return contents_.size(); }

  // DownloadFileObserver implementation:
  void OnDownloadCompleted(const base::FilePath& file_path) override;
  void OnDownloadAborted() override;

  // IDataObject implementation:
  HRESULT __stdcall GetData(FORMATETC* format_etc, STGMEDIUM* medium) override;
  HRESULT __stdcall GetDataHere(FORMATETC* format_etc,
                                STGMEDIUM* medium) override;
  HRESULT __stdcall QueryGetData(FORMATETC* format_etc) override;
  HRESULT __stdcall GetCanonicalFormatEtc(FORMATETC* format_etc,
                                          FORMATETC* result) override;
  HRESULT __stdcall SetData(FORMATETC* format_etc,
                            STGMEDIUM* medium,
                            BOOL should_release) override;
  HRESULT __stdcall EnumFormatEtc(DWORD direction,
                                  IEnumFORMATETC** enumerator) override;
  HRESULT __stdcall DAdvise(FORMATETC* format_etc,
                            DWORD advf,
                            IAdviseSink* sink,
                            DWORD* connection) override;
  HRESULT __stdcall DUnadvise(DWORD connection) override;
  HRESULT __stdcall EnumDAdvise(IEnumSTATDATA** enumerator) override;

  // IDataObjectAsyncCapability implementation:
  HRESULT __stdcall EndOperation(HRESULT result,
                                 IBindCtx* reserved,
                                 DWORD effects) override;
  HRESULT __stdcall GetAsyncMode(BOOL* is_op_async) override;
  HRESULT __stdcall InOperation(BOOL* in_async_op) override;
  HRESULT __stdcall SetAsyncMode(BOOL do_op_async) override;
  HRESULT __stdcall StartOperation(IBindCtx* reserved) override;

  // IUnknown implementation:
  HRESULT __stdcall QueryInterface(const IID& iid, void** object) override;
  ULONG __stdcall AddRef() override;
  ULONG __stdcall Release() override;

 private:
  // FormatEtcEnumerator only likes us for our StoredDataMap typedef.
  friend class FormatEtcEnumerator;
  friend class OSExchangeDataProviderWin;

  ~DataObjectImpl() override;

  void StopDownloads();

  // Removes from contents_ the first data that matches |format|.
  void RemoveData(const FORMATETC& format);

  // Our internal representation of stored data & type info.
  struct StoredDataInfo {
   public:
    ScopedFormatEtc format_etc;
    STGMEDIUM medium;
    std::unique_ptr<DownloadFileProvider> downloader;

    ~StoredDataInfo();
    StoredDataInfo(const StoredDataInfo&) = delete;
    StoredDataInfo& operator=(const StoredDataInfo&) = delete;

    // Takes ownership of and nullifies `medium` to approximate moving from
    // STGMEDIUM.
    static std::unique_ptr<StoredDataInfo> TakeStorageMedium(
        const FORMATETC& format_etc,
        STGMEDIUM& medium);

   private:
    // STGMEDIUM is just a POD, it does not guarantee `medium` is no longer be
    // used after calling this constructor while the ownership of `medium` is
    // passed.
    StoredDataInfo(const FORMATETC& format_etc, const STGMEDIUM& medium);
  };

  typedef std::vector<std::unique_ptr<StoredDataInfo>> StoredData;
  StoredData contents_;

  Microsoft::WRL::ComPtr<IDataObject> source_object_;

  bool is_aborting_;
  bool in_drag_loop_;
  bool in_async_mode_;
  bool async_operation_started_;
};

class COMPONENT_EXPORT(UI_BASE) OSExchangeDataProviderWin
    : public OSExchangeDataProvider {
 public:
  static DataObjectImpl* GetDataObjectImpl(const OSExchangeData& data);
  static IDataObject* GetIDataObject(const OSExchangeData& data);

  explicit OSExchangeDataProviderWin(IDataObject* source);
  OSExchangeDataProviderWin();

  OSExchangeDataProviderWin(const OSExchangeDataProviderWin&) = delete;
  OSExchangeDataProviderWin& operator=(const OSExchangeDataProviderWin&) =
      delete;

  ~OSExchangeDataProviderWin() override;

  IDataObject* data_object() const { return data_.get(); }
  IDataObjectAsyncCapability* async_operation() const { return data_.get(); }

  // OSExchangeDataProvider methods.
  std::unique_ptr<OSExchangeDataProvider> Clone() const override;
  void MarkRendererTaintedFromOrigin(const url::Origin& origin) override;
  bool IsRendererTainted() const override;
  std::optional<url::Origin> GetRendererTaintedOrigin() const override;
  void MarkAsFromPrivileged() override;
  bool IsFromPrivileged() const override;
  void SetString(std::u16string_view data) override;
  void SetURLs(base::span<const ClipboardUrlInfo> url_infos) override;
  void SetFilename(const base::FilePath& path) override;
  void SetFilenames(const std::vector<FileInfo>& filenames) override;
  // Test only method for adding virtual file content to the data store. The
  // first value in the pair is the file display name, the second is a string
  // providing the file content. If `show_cfhdrop_without_data` is true,
  // CF_HDROP will be advertised via QueryGetData but GetData will fail -
  // simulating ZIP Shell Folder behavior.
  void SetVirtualFileContentsForTesting(
      const std::vector<std::pair<base::FilePath, base::span<const uint8_t>>>&
          filenames_and_contents,
      DWORD tymed,
      bool show_cfhdrop_without_data) override;
  // Test only method that builds a virtual file descriptor where the entries at
  // `directory_indices` are flagged as folders (FILE_ATTRIBUTE_DIRECTORY) with
  // no backing content stream, mirroring how a ZIP "compressed folder"
  // advertises a directory entry alongside its files.
  void SetVirtualFileContentsWithDirectoriesForTesting(
      const std::vector<std::pair<base::FilePath, base::span<const uint8_t>>>&
          filenames_and_contents,
      const std::vector<size_t>& directory_indices,
      DWORD tymed);
  void SetPickledData(const ClipboardFormatType& format,
                      const base::Pickle& data) override;
  void SetFileContents(const base::FilePath& filename,
                       base::span<const uint8_t> file_contents) override;
  void SetHtml(const std::u16string& html, const GURL& base_url) override;

  std::optional<std::u16string> GetString() const override;
  std::vector<ClipboardUrlInfo> GetURLs(
      FilenameToURLPolicy policy) const override;
  std::optional<std::vector<FileInfo>> GetFilenames() const override;
  bool HasVirtualFilenames() const override;
  std::optional<std::vector<FileInfo>> GetVirtualFilenames() const override;
  void GetVirtualFilesAsTempFiles(
      base::OnceCallback<
          void(const std::vector<std::pair<base::FilePath, base::FilePath>>&)>
          callback) const override;
  std::optional<base::Pickle> GetPickledData(
      const ClipboardFormatType& format) const override;
  std::optional<FileContentsInfo> GetFileContents() const override;
  std::optional<HtmlInfo> GetHtml() const override;
  bool HasString() const override;
  bool HasURL(FilenameToURLPolicy policy) const override;
  bool HasFile() const override;
  bool HasFileContents() const override;
  bool HasHtml() const override;
  bool HasCustomFormat(const ClipboardFormatType& format) const override;
  void SetDownloadFileInfo(DownloadFileInfo* download_info) override;
  void SetDragImage(const gfx::ImageSkia& image_skia,
                    const gfx::Vector2d& cursor_offset) override;
  gfx::ImageSkia GetDragImage() const override;
  gfx::Vector2d GetDragImageOffset() const override;

  void SetSource(std::unique_ptr<DataTransferEndpoint> data_source) override;
  DataTransferEndpoint* GetSource() const override;

 private:
  // Returns true if `GetPlainTextURL()` would return a GURL and false
  // otherwise.
  bool HasPlainTextURL() const;

  // Returns a GURL if text is present and that text is a valid URL, and if
  // `IsRendererTainted()` is true, that the URL has an HTTP or HTTPS scheme;
  // otherwise, returns `std::nullopt`.
  std::optional<GURL> GetPlainTextURL() const;

  void SetVirtualFileContentAtIndexForTesting(base::span<const uint8_t> data,
                                              DWORD tymed,
                                              LONG index);

  scoped_refptr<DataObjectImpl> data_;
  Microsoft::WRL::ComPtr<IDataObject> source_object_;
};

}  // namespace ui

#endif  // UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_
