blob: da459669a5e353e7e8999a7f26e51ae3927fec4e [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/subresource_filter/content/browser/ruleset_publisher_impl.h"
#include <stddef.h>
#include <string>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/numerics/safe_conversions.h"
#include "base/run_loop.h"
#include "base/task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/prefs/testing_pref_service.h"
#include "components/subresource_filter/content/browser/ruleset_service.h"
#include "components/subresource_filter/content/common/subresource_filter_messages.h"
#include "components/subresource_filter/core/common/test_ruleset_creator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_platform_file.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace subresource_filter {
namespace {
using MockClosureTarget =
::testing::StrictMock<::testing::MockFunction<void()>>;
class NotifyingMockRenderProcessHost : public content::MockRenderProcessHost {
public:
explicit NotifyingMockRenderProcessHost(
content::BrowserContext* browser_context)
: content::MockRenderProcessHost(browser_context) {
content::NotificationService::current()->Notify(
content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::Source<content::RenderProcessHost>(this),
content::NotificationService::NoDetails());
}
};
std::string ReadFileContentsToString(base::File* file) {
size_t length = base::checked_cast<size_t>(file->GetLength());
std::string contents(length, 0);
file->Read(0, &contents[0], base::checked_cast<int>(length));
return contents;
}
// Extracts and takes ownership of the ruleset file handle in the IPC message.
base::File ExtractRulesetFromMessage(const IPC::Message* message) {
std::tuple<IPC::PlatformFileForTransit> arg;
SubresourceFilterMsg_SetRulesetForProcess::Read(message, &arg);
return IPC::PlatformFileForTransitToFile(std::get<0>(arg));
}
} // namespace
class SubresourceFilterRulesetPublisherImplTest : public ::testing::Test {
public:
SubresourceFilterRulesetPublisherImplTest()
: existing_renderer_(&browser_context_) {}
protected:
void SetUp() override { ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); }
void TearDown() override {}
content::TestBrowserContext* browser_context() { return &browser_context_; }
base::FilePath scoped_temp_file() const {
return scoped_temp_dir_.GetPath().AppendASCII("data");
}
void AssertSetRulesetForProcessMessageWithContent(
const IPC::Message* message,
const std::string& expected_contents) {
ASSERT_EQ(
static_cast<uint32_t>(SubresourceFilterMsg_SetRulesetForProcess::ID),
message->type());
base::File ruleset_file = ExtractRulesetFromMessage(message);
ASSERT_TRUE(ruleset_file.IsValid());
ASSERT_EQ(expected_contents, ReadFileContentsToString(&ruleset_file));
}
private:
base::ScopedTempDir scoped_temp_dir_;
content::TestBrowserThreadBundle thread_bundle_;
content::TestBrowserContext browser_context_;
NotifyingMockRenderProcessHost existing_renderer_;
DISALLOW_COPY_AND_ASSIGN(SubresourceFilterRulesetPublisherImplTest);
};
TEST_F(SubresourceFilterRulesetPublisherImplTest, NoRuleset_NoIPCMessages) {
NotifyingMockRenderProcessHost existing_renderer(browser_context());
RulesetPublisherImpl service(nullptr, base::ThreadTaskRunnerHandle::Get());
NotifyingMockRenderProcessHost new_renderer(browser_context());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, existing_renderer.sink().message_count());
EXPECT_EQ(0u, new_renderer.sink().message_count());
}
TEST_F(SubresourceFilterRulesetPublisherImplTest,
PublishedRuleset_IsDistributedToExistingAndNewRenderers) {
const char kTestFileContents[] = "foobar";
base::WriteFile(scoped_temp_file(), kTestFileContents,
strlen(kTestFileContents));
base::File file;
file.Initialize(scoped_temp_file(),
base::File::FLAG_OPEN | base::File::FLAG_READ);
NotifyingMockRenderProcessHost existing_renderer(browser_context());
MockClosureTarget publish_callback_target;
RulesetPublisherImpl service(nullptr, base::ThreadTaskRunnerHandle::Get());
service.SetRulesetPublishedCallbackForTesting(base::BindOnce(
&MockClosureTarget::Call, base::Unretained(&publish_callback_target)));
EXPECT_CALL(publish_callback_target, Call()).Times(1);
service.PublishNewRulesetVersion(std::move(file));
base::RunLoop().RunUntilIdle();
::testing::Mock::VerifyAndClearExpectations(&publish_callback_target);
ASSERT_EQ(1u, existing_renderer.sink().message_count());
ASSERT_NO_FATAL_FAILURE(AssertSetRulesetForProcessMessageWithContent(
existing_renderer.sink().GetMessageAt(0), kTestFileContents));
NotifyingMockRenderProcessHost second_renderer(browser_context());
base::RunLoop().RunUntilIdle();
ASSERT_EQ(1u, second_renderer.sink().message_count());
ASSERT_NO_FATAL_FAILURE(AssertSetRulesetForProcessMessageWithContent(
second_renderer.sink().GetMessageAt(0), kTestFileContents));
}
TEST_F(SubresourceFilterRulesetPublisherImplTest,
PublishesRulesetInOnePostTask) {
// Regression test for crbug.com/817308. Test verifies that ruleset is
// published on browser startup via exactly one PostTask.
// Create a temporary directory for the indexed ruleset data.
base::ScopedTempDir scoped_temp_dir;
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
const base::FilePath base_dir =
scoped_temp_dir.GetPath().AppendASCII("Rules").AppendASCII("Indexed");
// Create a testing ruleset.
testing::TestRulesetPair ruleset;
ASSERT_NO_FATAL_FAILURE(
testing::TestRulesetCreator().CreateRulesetToDisallowURLsWithPathSuffix(
"foo", &ruleset));
// Create local state and save the ruleset version to emulate invariant that
// the version of the last indexed ruleset is stored in the local state.
TestingPrefServiceSimple prefs;
IndexedRulesetVersion::RegisterPrefs(prefs.registry());
IndexedRulesetVersion current_version(
"1.2.3.4", IndexedRulesetVersion::CurrentFormatVersion());
current_version.SaveToPrefs(&prefs);
// Create ruleset data on a disk.
const base::FilePath version_dir_path =
IndexedRulesetLocator::GetSubdirectoryPathForVersion(base_dir,
current_version);
ASSERT_EQ(RulesetService::IndexAndWriteRulesetResult::SUCCESS,
RulesetService::WriteRuleset(version_dir_path,
/* license_path =*/base::FilePath(),
ruleset.indexed.contents.data(),
ruleset.indexed.contents.size()));
// Create a ruleset service and its harness.
scoped_refptr<base::TestSimpleTaskRunner> blocking_task_runner =
base::MakeRefCounted<base::TestSimpleTaskRunner>();
scoped_refptr<base::TestSimpleTaskRunner> background_task_runner =
base::MakeRefCounted<base::TestSimpleTaskRunner>();
NotifyingMockRenderProcessHost renderer_host(browser_context());
base::RunLoop callback_waiter;
auto content_service =
std::make_unique<RulesetPublisherImpl>(nullptr, blocking_task_runner);
content_service->SetRulesetPublishedCallbackForTesting(
callback_waiter.QuitClosure());
// |RulesetService| constructor should read the last indexed ruleset version
// and post ruleset setup on |blocking_task_runner|.
ASSERT_EQ(0u, blocking_task_runner->NumPendingTasks());
auto service =
std::make_unique<RulesetService>(&prefs, background_task_runner, base_dir,
nullptr, std::move(content_service));
// The key test assertion is that ruleset data is published via exactly one
// post task on |blocking_task_runner|. It is important to run pending tasks
// only once here.
ASSERT_EQ(1u, blocking_task_runner->NumPendingTasks());
blocking_task_runner->RunPendingTasks();
callback_waiter.Run();
// Check that the ruleset data is delivered to the renderer.
EXPECT_EQ(1u, renderer_host.sink().message_count());
const std::string expected_data(ruleset.indexed.contents.begin(),
ruleset.indexed.contents.end());
ASSERT_NO_FATAL_FAILURE(AssertSetRulesetForProcessMessageWithContent(
renderer_host.sink().GetMessageAt(0), expected_data));
//
// |RulesetPublisherImpl| destruction requires additional tricks. Its member
// |VerifiedRulesetDealer::Handle| posts task upon destruction on
// |blocking_task_runner|.
service.reset();
// Need to wait for |VerifiedRulesetDealer| destruction on the
// |blocking_task_runner|.
blocking_task_runner->RunPendingTasks();
}
} // namespace subresource_filter