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

#include "net/url_request/redirect_util.h"

#include <string>

#include "net/http/http_request_headers.h"
#include "net/url_request/redirect_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace net {
namespace {

TEST(RedirectUtilTest, UpdateHttpRequest) {
  const GURL original_url("https://www.example.com/test.php");
  const char kContentLengthValue[] = "100";
  const char kContentTypeValue[] = "text/plain; charset=utf-8";
  const char kContentEncoding[] = "Content-Encoding";
  const char kContentEncodingValue[] = "gzip";
  const char kContentLanguage[] = "Content-Language";
  const char kContentLanguageValue[] = "tlh";
  const char kContentLocation[] = "Content-Location";
  const char kContentLocationValue[] = "https://somewhere.test/";
  const char kCustomHeader[] = "Custom-Header-For-Test";
  const char kCustomHeaderValue[] = "custom header value";

  struct TestCase {
    const char* original_method;
    const char* new_method;
    const char* new_url;
    const struct {
      const char* name;
      const char* value;
    } modified_headers[2];
    bool expected_should_clear_upload;
    // nullptr if the origin header should not exist
    const char* expected_origin_header;
  };
  const TestCase kTests[] = {
      {
          "POST" /* original_method */,
          "POST" /* new_method */,
          "https://www.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          false /* expected_should_clear_upload */,
          "https://origin.example.com" /* expected_origin_header */
      },
      {
          "POST" /* original_method */,
          "GET" /* new_method */,
          "https://www.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          true /* expected_should_clear_upload */,
          nullptr /* expected_origin_header */
      },
      {
          "POST" /* original_method */,
          "POST" /* new_method */,
          "https://other.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          false /* expected_should_clear_upload */,
          "null" /* expected_origin_header */
      },
      {
          "POST" /* original_method */,
          "GET" /* new_method */,
          "https://other.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          true /* expected_should_clear_upload */,
          nullptr /* expected_origin_header */
      },
      {
          "PUT" /* original_method */,
          "GET" /* new_method */,
          "https://www.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          true /* expected_should_clear_upload */,
          nullptr /* expected_origin_header */
      },
      {
          "FOOT" /* original_method */,
          "GET" /* new_method */,
          "https://www.example.com/redirected.php" /* new_url */,
          {{"Header1", "Value1"}, {"Header2", "Value2"}} /* modified_headers */,
          true /* expected_should_clear_upload */,
          nullptr /* expected_origin_header */
      },
  };

  for (const auto& test : kTests) {
    SCOPED_TRACE(::testing::Message()
                 << "original_method: " << test.original_method
                 << " new_method: " << test.new_method
                 << " new_url: " << test.new_url);
    RedirectInfo redirect_info;
    redirect_info.new_method = test.new_method;
    redirect_info.new_url = GURL(test.new_url);

    net::HttpRequestHeaders modified_headers;
    for (const auto& headers : test.modified_headers) {
      ASSERT_TRUE(!!headers.name);  // Currently all test case has this.
      modified_headers.SetHeader(headers.name, headers.value);
    }
    std::string expected_modified_header1, expected_modified_header2;
    modified_headers.GetHeader("Header1", &expected_modified_header1);
    modified_headers.GetHeader("Header2", &expected_modified_header2);

    HttpRequestHeaders request_headers;
    request_headers.SetHeader(HttpRequestHeaders::kOrigin,
                              "https://origin.example.com");
    request_headers.SetHeader(HttpRequestHeaders::kContentLength,
                              kContentLengthValue);
    request_headers.SetHeader(HttpRequestHeaders::kContentType,
                              kContentTypeValue);
    request_headers.SetHeader(kContentEncoding, kContentEncodingValue);
    request_headers.SetHeader(kContentLanguage, kContentLanguageValue);
    request_headers.SetHeader(kContentLocation, kContentLocationValue);
    request_headers.SetHeader(kCustomHeader, kCustomHeaderValue);
    request_headers.SetHeader("Header1", "Initial-Value1");

    bool should_clear_upload = !test.expected_should_clear_upload;

    RedirectUtil::UpdateHttpRequest(
        original_url, test.original_method, redirect_info,
        std::nullopt /* removed_headers */, modified_headers, &request_headers,
        &should_clear_upload);
    EXPECT_EQ(test.expected_should_clear_upload, should_clear_upload);

    std::string content_length;
    EXPECT_EQ(!test.expected_should_clear_upload,
              request_headers.GetHeader(HttpRequestHeaders::kContentLength,
                                        &content_length));
    std::string content_type;
    EXPECT_EQ(!test.expected_should_clear_upload,
              request_headers.GetHeader(HttpRequestHeaders::kContentType,
                                        &content_type));
    std::string content_encoding;
    EXPECT_EQ(!test.expected_should_clear_upload,
              request_headers.GetHeader(kContentEncoding, &content_encoding));
    std::string content_language;
    EXPECT_EQ(!test.expected_should_clear_upload,
              request_headers.GetHeader(kContentLanguage, &content_language));
    std::string content_location;
    EXPECT_EQ(!test.expected_should_clear_upload,
              request_headers.GetHeader(kContentLocation, &content_location));
    if (!test.expected_should_clear_upload) {
      EXPECT_EQ(kContentLengthValue, content_length);
      EXPECT_EQ(kContentTypeValue, content_type);
      EXPECT_EQ(kContentEncodingValue, content_encoding);
      EXPECT_EQ(kContentLanguageValue, content_language);
      EXPECT_EQ(kContentLocationValue, content_location);
    }

    std::string custom_header;
    EXPECT_TRUE(request_headers.GetHeader(kCustomHeader, &custom_header));
    EXPECT_EQ(kCustomHeaderValue, custom_header);

    std::string origin_header_value;
    EXPECT_EQ(test.expected_origin_header != nullptr,
              request_headers.GetHeader(HttpRequestHeaders::kOrigin,
                                        &origin_header_value));
    if (test.expected_origin_header) {
      EXPECT_EQ(test.expected_origin_header, origin_header_value);
    }

    std::string modified_header1, modified_header2;
    EXPECT_TRUE(request_headers.GetHeader("Header1", &modified_header1));
    EXPECT_EQ(expected_modified_header1, modified_header1);
    EXPECT_TRUE(request_headers.GetHeader("Header2", &modified_header2));
    EXPECT_EQ(expected_modified_header2, modified_header2);
  }
}

TEST(RedirectUtilTest, RemovedHeaders) {
  struct TestCase {
    std::vector<const char*> initial_headers;
    std::vector<const char*> modified_headers;
    std::vector<const char*> removed_headers;
    std::vector<const char*> final_headers;
  };
  const TestCase kTests[] = {
      // Remove no headers (empty vector).
      {
          {},  // Initial headers
          {},  // Modified headers
          {},  // Removed headers
          {},  // Final headers
      },
      // Remove an existing header.
      {
          {"A:0"},  // Initial headers
          {},       // Modified headers
          {"A"},    // Removed headers
          {},       // Final headers
      },
      // Remove a missing header.
      {
          {},     // Initial headers
          {},     // Modified headers
          {"A"},  // Removed headers
          {},     // Final headers
      },
      // Remove two different headers.
      {
          {"A:0", "B:0"},  // Initial headers
          {},              // Modified headers
          {"A", "B"},      // Removed headers
          {},              // Final headers
      },
      // Remove two times the same headers.
      {
          {"A:0"},     // Initial headers
          {},          // Modified headers
          {"A", "A"},  // Removed headers
          {},          // Final headers
      },
      // Remove an existing header that is also modified.
      {
          {"A:0"},  // Initial headers
          {"A:1"},  // Modified headers
          {"A"},    // Removed headers
          {"A:1"},  // Final headers
      },
      // Some headers are removed, some aren't.
      {
          {"A:0", "B:0"},  // Initial headers
          {},              // Modified headers
          {"A"},           // Removed headers
          {"B:0"},         // Final headers
      },
  };

  for (const auto& test : kTests) {
    HttpRequestHeaders initial_headers, modified_headers, final_headers;
    std::vector<std::string> removed_headers;
    for (const char* header : test.initial_headers)
      initial_headers.AddHeaderFromString(header);
    for (const char* header : test.modified_headers)
      modified_headers.AddHeaderFromString(header);
    for (const char* header : test.removed_headers)
      removed_headers.push_back(header);
    for (const char* header : test.final_headers)
      final_headers.AddHeaderFromString(header);
    bool should_clear_upload(false);  // unused.

    RedirectUtil::UpdateHttpRequest(GURL(),         // original_url
                                    std::string(),  // original_method
                                    RedirectInfo(), removed_headers,
                                    modified_headers, &initial_headers,
                                    &should_clear_upload);

    // The initial_headers have been updated and should match the expected final
    // headers.
    EXPECT_EQ(initial_headers.ToString(), final_headers.ToString());
  }
}

// Test with removed_headers = std::nullopt.
TEST(RedirectUtilTest, RemovedHeadersNullOpt) {
  HttpRequestHeaders initial_headers, final_headers;
  initial_headers.SetHeader("A", "0");
  final_headers.SetHeader("A", "0");
  std::optional<std::vector<std::string>> removed_headers(std::nullopt);
  std::optional<HttpRequestHeaders> modified_headers(std::in_place);
  bool should_clear_upload(false);  // unused.

  RedirectUtil::UpdateHttpRequest(GURL(),         // original_url
                                  std::string(),  // original_method
                                  RedirectInfo(), removed_headers,
                                  modified_headers, &initial_headers,
                                  &should_clear_upload);

  // The initial_headers have been updated and should match the expected final
  // headers.
  EXPECT_EQ(initial_headers.ToString(), final_headers.ToString());
}

// Test with modified_headers = std::nullopt.
TEST(RedirectUtilTest, ModifyHeadersNullopt) {
  HttpRequestHeaders initial_headers, final_headers;
  initial_headers.SetHeader("A", "0");
  final_headers.SetHeader("A", "0");
  std::optional<std::vector<std::string>> removed_headers(std::in_place);
  std::optional<HttpRequestHeaders> modified_headers(std::nullopt);
  bool should_clear_upload(false);  // unused.

  RedirectUtil::UpdateHttpRequest(GURL(),         // original_url
                                  std::string(),  // original_method
                                  RedirectInfo(), removed_headers,
                                  modified_headers, &initial_headers,
                                  &should_clear_upload);

  // The initial_headers have been updated and should match the expected final
  // headers.
  EXPECT_EQ(initial_headers.ToString(), final_headers.ToString());
}

}  // namespace
}  // namespace net
