// 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 <memory>

#include "base/files/file_util.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "net/base/filename_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "ui/base/clipboard/clipboard_format_type.h"
#include "ui/base/clipboard/file_info.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/dragdrop/os_exchange_data_provider.h"
#include "ui/events/platform/platform_event_source.h"
#include "url/gurl.h"

namespace ui {

class OSExchangeDataTest : public PlatformTest {
 public:
  OSExchangeDataTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI),
        event_source_(PlatformEventSource::CreateDefault()) {}

 private:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<PlatformEventSource> event_source_;
};

TEST_F(OSExchangeDataTest, StringDataGetAndSet) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    EXPECT_FALSE(data.HasString());
    data.SetString(u"I can has cheezburger?");
    EXPECT_TRUE(data.HasString());
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasString());
  std::optional<std::u16string> string = copy.GetString();
  EXPECT_EQ(u"I can has cheezburger?", string);
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  // No URLs in `data` so no URLs should be read out.
  EXPECT_TRUE(url_infos.empty());
}

TEST_F(OSExchangeDataTest, TestURLExchangeFormats) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    EXPECT_FALSE(data.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
    data.SetURL(GURL("https://www.google.com/"), u"www.google.com");
    EXPECT_TRUE(data.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
    return data.provider().Clone();
  }());

  // URL spec and title should match
  EXPECT_TRUE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  EXPECT_FALSE(url_infos.empty());
  EXPECT_EQ("https://www.google.com/", url_infos.front().url.spec());
  EXPECT_EQ(u"www.google.com", url_infos.front().title);

  // URL should be the raw text response, even though no text was explicitly
  // set.
  std::optional<std::u16string> string = copy.GetString();
  EXPECT_EQ(u"https://www.google.com/", string);
}

// TODO(crbug.com/406978702): string -> URL conversion is only implemented on
// some platforms—and it is not consistently implemented across all platforms.
// Maybe this will be fixed one day...
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
static constexpr bool kSupportsStringToUrlCoercion = true;
static constexpr bool kStringToUrlCoercionPopulatesTitle = true;
#elif BUILDFLAG(IS_OZONE) && defined(USE_AURA)
static constexpr bool kSupportsStringToUrlCoercion = true;
static constexpr bool kStringToUrlCoercionPopulatesTitle = false;
#else
static constexpr bool kSupportsStringToUrlCoercion = false;
static constexpr bool kStringToUrlCoercionPopulatesTitle = false;
#endif

TEST_F(OSExchangeDataTest, URLFromString) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    data.SetString(u"https://www.google.com/");
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  if constexpr (kSupportsStringToUrlCoercion) {
    ASSERT_FALSE(url_infos.empty());
    EXPECT_EQ("https://www.google.com/", url_infos.front().url.spec());
    if constexpr (kStringToUrlCoercionPopulatesTitle) {
      EXPECT_EQ(u"www.google.com", url_infos.front().title);
    } else {
      EXPECT_EQ(u"", url_infos.front().title);
    }
  } else {
    EXPECT_TRUE(url_infos.empty());
  }
}

TEST_F(OSExchangeDataTest, URLFromRendererTaintedString) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    data.SetString(u"https://www.google.com/");
    data.MarkRendererTaintedFromOrigin(url::Origin());
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  if constexpr (kSupportsStringToUrlCoercion) {
    ASSERT_FALSE(url_infos.empty());
    EXPECT_EQ("https://www.google.com/", url_infos.front().url.spec());
    if constexpr (kStringToUrlCoercionPopulatesTitle) {
      EXPECT_EQ(u"www.google.com", url_infos.front().title);
    } else {
      EXPECT_EQ(u"", url_infos.front().title);
    }
  } else {
    EXPECT_TRUE(url_infos.empty());
  }
}

TEST_F(OSExchangeDataTest, NonHttpAndNonHttpsURLFromString) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    data.SetString(u"chrome://settings/");
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  if constexpr (kSupportsStringToUrlCoercion) {
    ASSERT_FALSE(url_infos.empty());
    EXPECT_EQ("chrome://settings/", url_infos.front().url.spec());
    if constexpr (kStringToUrlCoercionPopulatesTitle) {
      EXPECT_EQ(u"settings", url_infos.front().title);
    } else {
      EXPECT_EQ(u"", url_infos.front().title);
    }
  } else {
    EXPECT_TRUE(url_infos.empty());
  }
}

TEST_F(OSExchangeDataTest, NonHttpAndNonHttpsURLFromRendererTaintedString) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    data.SetString(u"chrome://settings/");
    data.MarkRendererTaintedFromOrigin(url::Origin());
    return data.provider().Clone();
  }());

  EXPECT_FALSE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  EXPECT_TRUE(url_infos.empty());
}

// Test that setting the URL does not overwrite a previously set custom string
// and that the synthesized URL shortcut file is ignored by GetFileContents().
TEST_F(OSExchangeDataTest, URLStringFileContents) {
  const OSExchangeData copy([] {
    OSExchangeData data;
    data.SetString(u"I can has cheezburger?");
    data.SetURL(GURL("https://www.google.com/"), u"www.google.com");
    return data.provider().Clone();
  }());

  std::optional<std::u16string> string = copy.GetString();
  EXPECT_EQ(u"I can has cheezburger?", string);

  std::vector<ui::ClipboardUrlInfo> url_infos =
      copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
  ASSERT_FALSE(url_infos.empty());
  EXPECT_EQ("https://www.google.com/", url_infos.front().url.spec());
  EXPECT_EQ(u"www.google.com", url_infos.front().title);

  // HasFileContents() should be false, and GetFileContents() should be empty
  // (https://crbug.com/1274395).
  EXPECT_FALSE(copy.HasFileContents());
  std::optional<OSExchangeData::FileContentsInfo> file_contents =
      copy.GetFileContents();
  EXPECT_FALSE(file_contents.has_value());
}

TEST_F(OSExchangeDataTest, TestFileToURLConversion) {
  base::FilePath file_path;
  ASSERT_TRUE(base::CreateTemporaryFile(&file_path));

  const OSExchangeData copy([&] {
    OSExchangeData data;
    EXPECT_FALSE(data.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
    EXPECT_FALSE(data.HasURL(FilenameToURLPolicy::CONVERT_FILENAMES));
    EXPECT_FALSE(data.HasFile());

    data.SetFilename(file_path);

    return data.provider().Clone();
  }());

  {
    EXPECT_FALSE(copy.HasURL(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES));
    std::vector<ClipboardUrlInfo> no_converted_filenames_url_infos =
        copy.GetURLs(FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES);
    EXPECT_TRUE(no_converted_filenames_url_infos.empty());
  }

  {
    EXPECT_TRUE(copy.HasURL(FilenameToURLPolicy::CONVERT_FILENAMES));
    std::vector<ClipboardUrlInfo> converted_url_infos =
        copy.GetURLs(FilenameToURLPolicy::CONVERT_FILENAMES);
    ASSERT_FALSE(converted_url_infos.empty());
    EXPECT_EQ(net::FilePathToFileURL(file_path),
              converted_url_infos.front().url);
    EXPECT_EQ(std::u16string(), converted_url_infos.front().title);
  }
  EXPECT_TRUE(copy.HasFile());
  std::optional<std::vector<FileInfo>> actual_files = copy.GetFilenames();
  ASSERT_TRUE(actual_files.has_value());
  EXPECT_EQ(1u, actual_files.value().size());
  EXPECT_EQ(file_path, actual_files.value()[0].path);
}

TEST_F(OSExchangeDataTest, TestPickledData) {
  const ClipboardFormatType kTestFormat =
      ClipboardFormatType::CustomPlatformType("application/vnd.chromium.test");

  const OSExchangeData copy([&] {
    OSExchangeData data;
    base::Pickle saved_pickle;
    saved_pickle.WriteInt(1);
    saved_pickle.WriteInt(2);
    data.SetPickledData(kTestFormat, saved_pickle);
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasCustomFormat(kTestFormat));

  std::optional<base::Pickle> restored_pickle =
      copy.GetPickledData(kTestFormat);
  ASSERT_TRUE(restored_pickle.has_value());
  base::PickleIterator iterator(restored_pickle.value());
  int value;
  EXPECT_TRUE(iterator.ReadInt(&value));
  EXPECT_EQ(1, value);
  EXPECT_TRUE(iterator.ReadInt(&value));
  EXPECT_EQ(2, value);
}

TEST_F(OSExchangeDataTest, TestFilenames) {
#if BUILDFLAG(IS_WIN)
  const std::vector<FileInfo> kTestFilenames = {
      {base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file1")),
       base::FilePath()},
      {base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file2")),
       base::FilePath()},
  };
#else
  const std::vector<FileInfo> kTestFilenames = {
      {base::FilePath(FILE_PATH_LITERAL("/tmp/test_file1")), base::FilePath()},
      {base::FilePath(FILE_PATH_LITERAL("/tmp/test_file2")), base::FilePath()},
  };
#endif

  const OSExchangeData copy([&] {
    OSExchangeData data;
    data.SetFilenames(kTestFilenames);
    return data.provider().Clone();
  }());

  std::optional<std::vector<FileInfo>> dropped_filenames = copy.GetFilenames();
  ASSERT_TRUE(dropped_filenames.has_value());
  // Only check the paths, not the display names, as those might be synthesized
  // during the clone while reading from the clipboard.
  ASSERT_EQ(kTestFilenames.size(), dropped_filenames.value().size());
  for (size_t i = 0; i < kTestFilenames.size(); ++i) {
    EXPECT_EQ(kTestFilenames[i].path, dropped_filenames.value()[i].path);
  }
}

#if defined(USE_AURA)
TEST_F(OSExchangeDataTest, TestHTML) {
  const GURL url("http://www.google.com/");
  const std::u16string html =
      u"<HTML>\n<BODY>\n"
      u"<b>bold.</b> <i><b>This is bold italic.</b></i>\n"
      u"</BODY>\n</HTML>";

  const OSExchangeData copy([&] {
    OSExchangeData data;
    data.SetHtml(html, url);
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.HasHtml());
  std::optional<ui::OSExchangeData::HtmlInfo> html_content = copy.GetHtml();
  ASSERT_TRUE(html_content.has_value());
  EXPECT_EQ(html, html_content->html);
  EXPECT_EQ(url, html_content->base_url);
}
#endif

TEST_F(OSExchangeDataTest, NotRendererTainted) {
  const OSExchangeData copy([&] {
    OSExchangeData data;
    return data.provider().Clone();
  }());

  EXPECT_FALSE(copy.IsRendererTainted());
  EXPECT_EQ(std::nullopt, copy.GetRendererTaintedOrigin());
}

TEST_F(OSExchangeDataTest, RendererTaintedOpaqueOrigin) {
  const url::Origin tuple_origin =
      url::Origin::Create(GURL("https://www.google.com/"));
  const url::Origin opaque_origin = tuple_origin.DeriveNewOpaqueOrigin();
  ASSERT_TRUE(opaque_origin.opaque());

  const OSExchangeData copy([&] {
    OSExchangeData data;
    data.MarkRendererTaintedFromOrigin(opaque_origin);
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.IsRendererTainted());
  std::optional<url::Origin> origin = copy.GetRendererTaintedOrigin();
  EXPECT_TRUE(origin.has_value());
  EXPECT_TRUE(origin->opaque());
  // Currently, the actual value of an opaque origin is not actually serialized
  // into OSExchangeData, so expect a random opaque origin to be read out.
  EXPECT_NE(opaque_origin, origin);
  // And there should be no precursor tuple.
  EXPECT_FALSE(origin->GetTupleOrPrecursorTupleIfOpaque().IsValid());
}

TEST_F(OSExchangeDataTest, RendererTaintedTupleOrigin) {
  const url::Origin tuple_origin =
      url::Origin::Create(GURL("https://www.google.com/"));

  const OSExchangeData copy([&] {
    OSExchangeData data;
    data.MarkRendererTaintedFromOrigin(tuple_origin);
    return data.provider().Clone();
  }());

  EXPECT_TRUE(copy.IsRendererTainted());
  std::optional<url::Origin> origin = copy.GetRendererTaintedOrigin();
  EXPECT_TRUE(origin.has_value());
  EXPECT_EQ(tuple_origin, origin);
}

}  // namespace ui
