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

#include "chrome/browser/ash/fileapi/external_file_url_loader_factory.h"

#include <stddef.h>

#include <memory>
#include <string_view>

#include "base/functional/bind.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "google_apis/gaia/gaia_id.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/test/test_url_loader_client.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash {
namespace {

constexpr char kExtensionId[] = "abc";
constexpr char kFileSystemId[] = "test-filesystem";
constexpr char kTestUrl[] =
    "externalfile:abc:test-filesystem:test-user-hash/hello.txt";
constexpr char kExpectedFileContents[] =
    "This is a testing file. Lorem ipsum dolor sit amet est.";

}  // namespace

class ExternalFileURLLoaderFactoryTest : public testing::Test {
 protected:
  ExternalFileURLLoaderFactoryTest()
      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}

  ~ExternalFileURLLoaderFactoryTest() override = default;

  void SetUp() override {
    // Create a testing profile.
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    Profile* const profile =
        profile_manager_->CreateTestingProfile("test-user");
    user_manager_.Reset(std::make_unique<FakeChromeUserManager>());
    user_manager_->AddUser(AccountId::FromUserEmailGaiaId(
        profile->GetProfileUserName(), GaiaId("12345")));
    render_process_host_ =
        std::make_unique<content::MockRenderProcessHost>(profile);

    auto* service = file_system_provider::Service::Get(profile);
    service->RegisterProvider(
        file_system_provider::FakeExtensionProvider::Create(kExtensionId));
    const auto kProviderId =
        file_system_provider::ProviderId::CreateFromExtensionId(kExtensionId);
    service->MountFileSystem(
        kProviderId,
        file_system_provider::MountOptions(kFileSystemId, "Test FileSystem"));

    // Create the URLLoaderFactory.
    url_loader_factory_.Bind(ExternalFileURLLoaderFactory::Create(
        profile, render_process_host_id()));
  }

  virtual int render_process_host_id() {
    return content::ChildProcessHost::kInvalidUniqueID;
  }

  content::MockRenderProcessHost* render_process_host() {
    return render_process_host_.get();
  }

  network::ResourceRequest CreateRequest(std::string url) {
    network::ResourceRequest request;
    request.method = "GET";
    request.url = GURL(url);
    return request;
  }

  mojo::PendingRemote<network::mojom::URLLoader> CreateURLLoaderAndStart(
      network::TestURLLoaderClient* client,
      const network::ResourceRequest& resource_request) {
    mojo::PendingRemote<network::mojom::URLLoader> loader;
    url_loader_factory_->CreateLoaderAndStart(
        loader.InitWithNewPipeAndPassReceiver(), 0 /* request_id */,
        network::mojom::kURLLoadOptionNone, resource_request,
        client->CreateRemote(),
        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
    return loader;
  }

 private:
  content::BrowserTaskEnvironment task_environment_;

  mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;

  std::unique_ptr<TestingProfileManager> profile_manager_;
  user_manager::TypedScopedUserManager<FakeChromeUserManager> user_manager_;
  // Used to register the profile with the ChildProcessSecurityPolicyImpl.
  std::unique_ptr<content::MockRenderProcessHost> render_process_host_;
};

TEST_F(ExternalFileURLLoaderFactoryTest, NonGetMethod) {
  network::TestURLLoaderClient client;
  network::ResourceRequest request = CreateRequest(kTestUrl);
  request.method = "POST";
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, request);

  client.RunUntilComplete();

  EXPECT_EQ(net::ERR_METHOD_NOT_SUPPORTED,
            client.completion_status().error_code);
}

TEST_F(ExternalFileURLLoaderFactoryTest, RegularFile) {
  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, CreateRequest(kTestUrl));

  client.RunUntilComplete();

  ASSERT_EQ(net::OK, client.completion_status().error_code);
  EXPECT_EQ("text/plain", client.response_head()->mime_type);
  std::string response_body;
  ASSERT_TRUE(mojo::BlockingCopyToString(client.response_body_release(),
                                         &response_body));
  EXPECT_EQ(kExpectedFileContents, response_body);
}

TEST_F(ExternalFileURLLoaderFactoryTest, RootDirectory) {
  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(
          &client,
          CreateRequest("externalfile:abc:test-filesystem:test-user-hash/"));

  client.RunUntilComplete();

  EXPECT_EQ(net::ERR_FAILED, client.completion_status().error_code);
}

TEST_F(ExternalFileURLLoaderFactoryTest, NonExistingFile) {
  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(
          &client,
          CreateRequest("externalfile:abc:test-filesystem:test-user-hash/"
                        "non-existing-file.txt"));

  client.RunUntilComplete();

  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, client.completion_status().error_code);
}

TEST_F(ExternalFileURLLoaderFactoryTest, WrongFormat) {
  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, CreateRequest("externalfile:"));

  client.RunUntilComplete();

  EXPECT_EQ(net::ERR_INVALID_URL, client.completion_status().error_code);
}

TEST_F(ExternalFileURLLoaderFactoryTest, DisconnectWhileReadPending) {
  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, CreateRequest(kTestUrl));

  // Wait until the response head and body pipe have been delivered. At this
  // point the loader has begun streaming file contents into the data pipe.
  client.RunUntilResponseBodyArrived();
  ASSERT_TRUE(client.response_body().is_valid());

  // Drop the URLLoader pipe to tear down the loader while file reads may still
  // be in flight, then drain any remaining work.
  loader.reset();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(client.has_received_completion());
}

TEST_F(ExternalFileURLLoaderFactoryTest, RangeHeader) {
  network::TestURLLoaderClient client;
  network::ResourceRequest request = CreateRequest(kTestUrl);
  request.headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=3-5");
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, request);

  client.RunUntilComplete();

  EXPECT_EQ(net::OK, client.completion_status().error_code);
  std::string response_body;
  ASSERT_TRUE(mojo::BlockingCopyToString(client.response_body_release(),
                                         &response_body));
  EXPECT_EQ(std::string_view(kExpectedFileContents).substr(3, 3),
            response_body);
}

TEST_F(ExternalFileURLLoaderFactoryTest, WrongRangeHeader) {
  network::TestURLLoaderClient client;
  network::ResourceRequest request = CreateRequest(kTestUrl);
  request.headers.SetHeader(net::HttpRequestHeaders::kRange, "Invalid range");
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, request);

  client.RunUntilComplete();

  EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
            client.completion_status().error_code);
}

class SubresourceExternalFileURLLoaderFactoryTest
    : public ExternalFileURLLoaderFactoryTest {
 protected:
  int render_process_host_id() override {
    return render_process_host()->GetDeprecatedID();
  }
};

TEST_F(SubresourceExternalFileURLLoaderFactoryTest, SubresourceAllowed) {
  content::ChildProcessSecurityPolicy::GetInstance()->GrantRequestScheme(
      render_process_host_id(), content::kExternalFileScheme);

  network::TestURLLoaderClient client;
  mojo::PendingRemote<network::mojom::URLLoader> loader =
      CreateURLLoaderAndStart(&client, CreateRequest(kTestUrl));

  client.RunUntilComplete();

  ASSERT_EQ(net::OK, client.completion_status().error_code);
}

TEST_F(SubresourceExternalFileURLLoaderFactoryTest, SubresourceNotAllowed) {
  mojo::test::BadMessageObserver bad_message_observer;
  network::TestURLLoaderClient client;
  CreateURLLoaderAndStart(&client, CreateRequest(kTestUrl));
  EXPECT_EQ("Unauthorized externalfile request",
            bad_message_observer.WaitForBadMessage());
}

}  // namespace ash
