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


#include "components/feedback/feedback_util.h"

#include <string>

#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_writer.h"
#include "base/rand_util.h"
#include "base/test/values_test_util.h"
#include "components/feedback/feedback_report.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace feedback_util {

// Note: This file is excluded from win build.
// See https://crbug.com/1119560.
class FeedbackUtilTest : public ::testing::Test {
 public:
  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }

  base::ScopedTempDir temp_dir_;
};

TEST_F(FeedbackUtilTest, ReadEndOfFileEmpty) {
  base::FilePath file_path = temp_dir_.GetPath().Append("test_empty.txt");

  EXPECT_TRUE(WriteFile(file_path, ""));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 10),
              testing::Optional(std::string()));
}

TEST_F(FeedbackUtilTest, ReadEndOfFileSmall) {
  const char kTestData[] = "0123456789";  // Length of 10
  base::FilePath file_path = temp_dir_.GetPath().Append("test_small.txt");

  EXPECT_TRUE(WriteFile(file_path, kTestData));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 15),
              testing::Optional(std::string(kTestData)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 10),
              testing::Optional(std::string(kTestData)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 2),
              testing::Optional(std::string("89")));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 3),
              testing::Optional(std::string("789")));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 5),
              testing::Optional(std::string("56789")));
}

TEST_F(FeedbackUtilTest, ReadEndOfFileWithZeros) {
  const size_t test_size = 10;
  std::string test_data("abcd\0\0\0\0hi", test_size);

  base::FilePath file_path = temp_dir_.GetPath().Append("test_zero.txt");

  EXPECT_TRUE(WriteFile(file_path, test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 15),
              testing::Optional(test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 10),
              testing::Optional(test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 2),
              testing::Optional(test_data.substr(test_size - 2, 2)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 3),
              testing::Optional(test_data.substr(test_size - 3, 3)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 5),
              testing::Optional(test_data.substr(test_size - 5, 5)));
}

TEST_F(FeedbackUtilTest, ReadEndOfFileMedium) {
  std::string test_data = base::RandBytesAsString(10000);  // 10KB data

  const size_t test_size = test_data.length();

  base::FilePath file_path = temp_dir_.GetPath().Append("test_med.txt");

  EXPECT_TRUE(WriteFile(file_path, test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 15000),
              testing::Optional(test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 10000),
              testing::Optional(test_data));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 1000),
              testing::Optional(test_data.substr(test_size - 1000, 1000)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 300),
              testing::Optional(test_data.substr(test_size - 300, 300)));

  EXPECT_THAT(feedback_util::ReadEndOfFile(file_path, 175),
              testing::Optional(test_data.substr(test_size - 175, 175)));
}

TEST_F(FeedbackUtilTest, LogsToStringShouldSkipFeedbackUserCtlConsentKey) {
  FeedbackCommon::SystemLogsMap sys_info;
  sys_info[feedback::FeedbackReport::kFeedbackUserCtlConsentKey] = "true";

  std::string logs = feedback_util::LogsToString(sys_info);
  EXPECT_TRUE(logs.empty());

  // Now add a fake key expected to be in system_logs.
  sys_info["fake_key"] = "fake_value";
  logs = feedback_util::LogsToString(sys_info);
  EXPECT_EQ("fake_key=fake_value\n", logs);
}

TEST_F(FeedbackUtilTest, RemoveUrlsFromAutofillData) {
  base::DictValue autofill_data = base::test::ParseJsonDict(
      R"({
        "formStructures": [
          {
            "formSignature": "123",
            "sourceUrl": "https://www.example.com",
            "mainFrameUrl": "https://www.example.com"
          },
          {
            "formSignature": "456",
            "sourceUrl": "https://www.another-example.com",
            "mainFrameUrl": "https://www.another-example.com"
          }
        ]})");
  std::string autofill_data_str = base::WriteJson(autofill_data).value_or("");

  base::ListValue* form_structures = autofill_data.FindList("formStructures");
  ASSERT_TRUE(form_structures);
  for (base::Value& item : *form_structures) {
    auto& dict = item.GetDict();
    dict.Remove("sourceUrl");
    dict.Remove("mainFrameUrl");
  }

  std::string expected_autofill_data_str =
      base::WriteJson(autofill_data).value_or("");

  feedback_util::RemoveUrlsFromAutofillData(autofill_data_str);
  EXPECT_EQ(autofill_data_str, expected_autofill_data_str);
}

TEST_F(FeedbackUtilTest, ZipStringTraversal) {
  // Create a temp directory, and target a file within it:
  base::ScopedTempDir root_dir;
  ASSERT_TRUE(root_dir.CreateUniqueTempDir());
  base::FilePath sensitive_file =
      root_dir.GetPath().AppendASCII("sensitive.txt");

  // Construct a traversal back to that file in a platform-dependent way:
  std::string sensitive_path_str = sensitive_file.AsUTF8Unsafe();
#if BUILDFLAG(IS_WIN)
  // Remove "C:" if present
  if (sensitive_path_str.size() >= 2 && sensitive_path_str[1] == ':') {
    sensitive_path_str = sensitive_path_str.substr(2);
  }
  // Remove leading backslash
  if (!sensitive_path_str.empty() && sensitive_path_str[0] == '\\') {
    sensitive_path_str = sensitive_path_str.substr(1);
  }
  base::FilePath traversal(
      FILE_PATH_LITERAL("..\\..\\..\\..\\..\\..\\..\\..\\"));
#else
  // Remove leading slash
  if (!sensitive_path_str.empty() && sensitive_path_str[0] == '/') {
    sensitive_path_str = sensitive_path_str.substr(1);
  }
  base::FilePath traversal(FILE_PATH_LITERAL("../../../../../../../../"));
#endif

  base::FilePath malicious_filename =
      traversal.Append(base::FilePath::FromUTF8Unsafe(sensitive_path_str));

  // Call ZipString.
  std::optional<std::string> result =
      feedback_util::ZipString(malicious_filename, "maliciousness");

  EXPECT_FALSE(result.has_value());
  EXPECT_FALSE(base::PathExists(sensitive_file));
}

}  // namespace feedback_util
