// Copyright 2015 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 <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/net/profile_network_context_service.h"
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/switches.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/base/url_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/ip_address_space_overrides_test_utils.h"
#include "services/network/public/cpp/network_switches.h"
#include "url/gurl.h"

namespace extensions {

namespace {

class FakeClientCertStore : public net::ClientCertStore {
 public:
  void GetClientCerts(
      scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
      ClientCertListCallback callback) override {
    base::ScopedAllowBlockingForTesting allow_blocking;
    std::unique_ptr<net::FakeClientCertIdentity> identity =
        net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
            net::GetTestCertsDirectory(), "client_1.pem", "client_1.pk8");
    EXPECT_TRUE(identity.get());
    std::vector<std::unique_ptr<net::ClientCertIdentity>> identities;
    identities.push_back(std::move(identity));
    std::move(callback).Run(std::move(identities));
  }
};

std::unique_ptr<net::ClientCertStore> CreateFakeClientCertStore() {
  return std::make_unique<FakeClientCertStore>();
}

}  // namespace

class BackgroundXhrTest : public ExtensionBrowserTest {
 protected:
  void RunTest(const std::string& path, const GURL& url) {
    const Extension* extension =
        LoadExtension(test_data_dir_.AppendASCII("background_xhr"));
    ASSERT_TRUE(extension);

    ResultCatcher catcher;
    GURL test_url = net::AppendQueryParameter(extension->GetResourceURL(path),
                                              "url", url.spec());
    ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), test_url));
    profile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting();
    static constexpr char kSendXHRScript[] = R"(
      var xhr = new XMLHttpRequest();
      xhr.open('GET', '%s');
      xhr.send();
      chrome.test.sendScriptResult('');
    )";
    ExecuteScriptInBackgroundPage(
        extension->id(),
        base::StringPrintf(kSendXHRScript, url.spec().c_str()));
    ASSERT_TRUE(catcher.GetNextResult());
  }
};

// Test that fetching a URL using TLS client auth doesn't crash, hang, or
// prompt.
IN_PROC_BROWSER_TEST_F(BackgroundXhrTest, TlsClientAuth) {
  // Install a FakeClientCertStore so the client auth prompt isn't bypassed due
  // to the system certificate store returning no certificates.
  ProfileNetworkContextServiceFactory::GetForContext(profile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateFakeClientCertStore));

  // Launch HTTPS server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(https_server.Start());

  ASSERT_NO_FATAL_FAILURE(
      RunTest("test_tls_client_auth.html", https_server.GetURL("/")));
}

// Test that fetching a URL using HTTP auth doesn't crash, hang, or prompt.
IN_PROC_BROWSER_TEST_F(BackgroundXhrTest, HttpAuth) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_NO_FATAL_FAILURE(RunTest(
      "test_http_auth.html", embedded_test_server()->GetURL("/auth-basic")));
}

class BackgroundFetchPolicyTest : public ExtensionApiTestWithManagementPolicy {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ExtensionApiTest::SetUpCommandLine(command_line);
    // Note: we need to start the embedded test server here specifically as it
    // needs to come after SetUp has been run in the superclass, but before any
    // subclasses need it in their own SetUpCommandLine functions.
    ASSERT_TRUE(embedded_test_server()->Start());
    // Treat the test server as public to bypass Local Network Access checks.
    network::AddPublicIpAddressSpaceOverrideToCommandLine(
        *embedded_test_server(), *command_line);
  }

  void SetUpOnMainThread() override {
    ExtensionApiTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  std::string ExecuteFetch(const Extension* extension, const GURL& url) {
    base::Value value = BackgroundScriptExecutor::ExecuteScript(
        profile(), extension->id(),
        content::JsReplace("executeFetch($1);", url),
        BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
    EXPECT_TRUE(value.is_string());
    std::string trimmed_result;
    base::TrimWhitespaceASCII(value.GetString(), base::TRIM_ALL,
                              &trimmed_result);
    return trimmed_result;
  }

  const Extension* LoadFetchExtension(const std::string& host) {
    ExtensionTestMessageListener listener("ready");
    TestExtensionDir test_dir;
    static constexpr char kManifestFile[] = R"(
    {
      "name": "Fetch Test",
      "manifest_version": 3,
      "version": "0.1",
      "background": {"service_worker": "background.js"},
      "host_permissions": ["%s"]
    })";
    constexpr char kBackgroundScriptFile[] = R"(
    function executeFetch(url) {
      console.warn('Fetching: ' + url);
      fetch(url)
          .then(response => response.text())
          .then(text => chrome.test.sendScriptResult(text))
          .catch(err => chrome.test.sendScriptResult('ERROR: ' + err));
    }
    chrome.test.sendMessage('ready');)";

    test_dir.WriteManifest(base::StringPrintf(kManifestFile, host.c_str()));
    test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
                       kBackgroundScriptFile);
    const Extension* extension = LoadExtension(test_dir.UnpackedPath());
    EXPECT_TRUE(listener.WaitUntilSatisfied());
    return extension;
  }
};

// Extensions should not be able to bypass same-origin despite declaring
// <all_urls> for hosts restricted by enterprise policy.
IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest, PolicyBlockedFetch) {
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
    pref.AddPolicyAllowedHost("*", "*://public.example.com");
  }

  const Extension* extension = LoadFetchExtension("<all_urls>");

  // Should block due to "runtime_blocked_hosts" section of policy.
  GURL protected_url_to_fetch =
      embedded_test_server()->GetURL("example.com", "/simple.html");
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, protected_url_to_fetch));

  // Should allow due to "runtime_allowed_hosts" section of policy.
  GURL exempted_url_to_fetch =
      embedded_test_server()->GetURL("public.example.com", "/simple.html");
  EXPECT_THAT(ExecuteFetch(extension, exempted_url_to_fetch),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
}

// Make sure the blocklist and allowlist update for both Default and Individual
// scope policies. Testing with all host permissions granted (<all_urls>).
IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest, PolicyUpdateFetch) {
  const Extension* extension = LoadFetchExtension("<all_urls>");

  GURL example_url =
      embedded_test_server()->GetURL("example.com", "/simple.html");
  GURL public_example_url =
      embedded_test_server()->GetURL("public.example.com", "/simple.html");

  // Sanity check: Without restrictions all fetches should work.
  EXPECT_THAT(ExecuteFetch(extension, public_example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_THAT(ExecuteFetch(extension, example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));

  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
    pref.AddPolicyAllowedHost("*", "*://public.example.com");
  }

  // Default policies propagate.
  EXPECT_THAT(ExecuteFetch(extension, public_example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example_url));
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost(extension->id(), "*://*.example2.com");
    pref.AddPolicyAllowedHost(extension->id(), "*://public.example2.com");
  }

  // Default policies overridden when individual scope policies applied.
  EXPECT_THAT(ExecuteFetch(extension, public_example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_THAT(ExecuteFetch(extension, example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));

  GURL example2_url =
      embedded_test_server()->GetURL("example2.com", "/simple.html");
  GURL public_example2_url =
      embedded_test_server()->GetURL("public.example2.com", "/simple.html");

  // Individual scope policies propagate.
  EXPECT_THAT(ExecuteFetch(extension, public_example2_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example2_url));
}

// Make sure the allowlist entries added due to host permissions are removed
// when a more generic blocklist policy is updated and contains them.
// This tests the default policy scope update.
IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest, PolicyUpdateDefaultFetch) {
  const Extension* extension = LoadFetchExtension("*://public.example.com/*");

  GURL example_url =
      embedded_test_server()->GetURL("example.com", "/simple.html");
  GURL public_example_url =
      embedded_test_server()->GetURL("public.example.com", "/simple.html");

  // Sanity check: Without restrictions only public.example.com should work.
  EXPECT_THAT(ExecuteFetch(extension, public_example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example_url));

  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost("*", "*://*.example.com");
  }

  // The blocklist of example.com overrides allowlist of public.example.com.
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example_url));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, public_example_url));
}

// Make sure the allowlist entries added due to host permissions are removed
// when a more generic blocklist policy is updated and contains them.
// This tests an individual policy scope update.
IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest, PolicyUpdateIndividualFetch) {
  const Extension* extension = LoadFetchExtension("*://public.example.com/*");

  GURL example_url =
      embedded_test_server()->GetURL("example.com", "/simple.html");
  GURL public_example_url =
      embedded_test_server()->GetURL("public.example.com", "/simple.html");

  // Sanity check: Without restrictions only public.example.com should work.
  EXPECT_THAT(ExecuteFetch(extension, public_example_url),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example_url));

  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyBlockedHost(extension->id(), "*://*.example.com");
  }

  // The blocklist of example.com overrides allowlist of public.example.com.
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, example_url));
  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, public_example_url));
}

IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest, FetchAnyPortPermission) {
  const Extension* extension = LoadFetchExtension("http://example.com:*/*");

  GURL permitted_url_to_fetch =
      embedded_test_server()->GetURL("example.com", "/simple.html");

  EXPECT_THAT(ExecuteFetch(extension, permitted_url_to_fetch),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
}

IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest,
                       FetchPortSpecificPermissionAllow) {
  const Extension* extension = LoadFetchExtension(
      "http://example.com:" +
      base::NumberToString(embedded_test_server()->port()) + "/*");

  GURL permitted_url_to_fetch =
      embedded_test_server()->GetURL("example.com", "/simple.html");

  EXPECT_THAT(ExecuteFetch(extension, permitted_url_to_fetch),
              ::testing::HasSubstr("<head><title>OK</title></head>"));
}

IN_PROC_BROWSER_TEST_F(BackgroundFetchPolicyTest,
                       FetchPortSpecificPermissionBlock) {
  const Extension* extension = LoadFetchExtension(
      "https://example.com:" +
      base::NumberToString(embedded_test_server()->port() + 1) + "/*");

  GURL not_permitted_url_to_fetch =
      embedded_test_server()->GetURL("example.com", "/simple.html");

  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, not_permitted_url_to_fetch));
}

// URL the new webstore is associated with in production.
constexpr char kNewWebstoreURL[] = "https://chromewebstore.google.com/";
// URL the webstore hosted app is associated with in production, minus the
// /webstore/ path which is added in the tests themselves.
constexpr char kWebstoreAppBaseURL[] = "https://chrome.google.com/";
// URL to test the command line override for the webstore.
constexpr char kWebstoreOverrideURL[] = "https://chrome.webstore.test.com/";
constexpr char kNonWebstoreURL[] = "https://google.com";
constexpr char kWebstorePath[] = "/webstore/mock_store.html";

class BackgroundFetchWebstoreTest : public BackgroundFetchPolicyTest,
                                    public testing::WithParamInterface<GURL> {
 public:
  BackgroundFetchWebstoreTest() {
    UseHttpsTestServer();
    // Override the test server SSL config with the webstore domain under test
    // and another non-webstore domain used in the tests.
    net::EmbeddedTestServer::ServerCertificateConfig cert_config;
    cert_config.dns_names = {GetParam().GetHost(), "google.com"};
    embedded_test_server()->SetSSLConfig(cert_config);
    // Add the extensions directory to the test server as it has a /webstore/
    // directory to serve files from, which the webstore hosted app requires as
    // part of the URL it is associated with.
    embedded_test_server()->ServeFilesFromSourceDirectory(
        "chrome/test/data/extensions");
  }
  ~BackgroundFetchWebstoreTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    BackgroundFetchPolicyTest::SetUpCommandLine(command_line);
    // Add a host resolver rule to map all outgoing requests to the test server.
    // This allows us to use "real" hostnames and standard ports in URLs (i.e.,
    // without having to inject the port number into all URLs).
    command_line->AppendSwitchASCII(
        network::switches::kHostResolverRules,
        "MAP * " + embedded_test_server()->host_port_pair().ToString());
    // Only override the webstore URL if this test case is testing the override.
    if (GetParam().spec() == kWebstoreOverrideURL) {
      command_line->AppendSwitchASCII(switches::kAppsGalleryURL,
                                      kWebstoreOverrideURL);
    }
  }
};

// Extensions should not be able to fetch() to the webstore.
IN_PROC_BROWSER_TEST_P(BackgroundFetchWebstoreTest, FetchToWebstore) {
  const Extension* extension = LoadFetchExtension("<all_urls>");

  GURL webstore_url_to_fetch = GetParam().Resolve(kWebstorePath);

  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, webstore_url_to_fetch));

  // Sanity check: the extension should be able to fetch the page if it's not on
  // the webstore.
  GURL non_webstore_url = GURL(kNonWebstoreURL).Resolve(kWebstorePath);
  EXPECT_THAT(ExecuteFetch(extension, non_webstore_url),
              ::testing::HasSubstr("<body>blank</body>"));
}

// Extensions should not be able to fetch() to the webstore regardless of
// policy.
IN_PROC_BROWSER_TEST_P(BackgroundFetchWebstoreTest, FetchToWebstorePolicy) {
  {
    ExtensionManagementPolicyUpdater pref(&policy_provider_);
    pref.AddPolicyAllowedHost("*", "*://" + GetParam().GetHost());
  }

  const Extension* extension = LoadFetchExtension("<all_urls>");

  GURL webstore_url_to_fetch = GetParam().Resolve(kWebstorePath);

  EXPECT_EQ("ERROR: TypeError: Failed to fetch",
            ExecuteFetch(extension, webstore_url_to_fetch));

  // Sanity check: the extension should be able to fetch the page if it's not on
  // the webstore.
  GURL non_webstore_url = GURL(kNonWebstoreURL).Resolve(kWebstorePath);
  EXPECT_THAT(ExecuteFetch(extension, non_webstore_url),
              ::testing::HasSubstr("<body>blank</body>"));
}

INSTANTIATE_TEST_SUITE_P(WebstoreNewURL,
                         BackgroundFetchWebstoreTest,
                         testing::Values(GURL(kNewWebstoreURL)));
INSTANTIATE_TEST_SUITE_P(WebstoreHostedAppURL,
                         BackgroundFetchWebstoreTest,
                         testing::Values(GURL(kWebstoreAppBaseURL)));
INSTANTIATE_TEST_SUITE_P(WebstoreOverrideURL,
                         BackgroundFetchWebstoreTest,
                         testing::Values(GURL(kWebstoreOverrideURL)));

}  // namespace extensions
