blob: bf5f808daba1681c2775a3ecf0aed86b60dc0d2f [file] [log] [blame]
// Copyright 2021 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 "headless/test/headless_policy_browsertest.h"
#include <fcntl.h>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/public/test/browser_test.h"
#include "headless/lib/browser/policy/headless_mode_policy.h"
#include "headless/public/headless_browser.h"
#include "headless/test/headless_browser_test.h"
#include "net/base/host_port_pair.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !BUILDFLAG(IS_WIN)
#include <unistd.h>
#endif
namespace headless {
// The following enum values must match HeadlessMode policy template in
// components/policy/resources/policy_templates.json
enum {
kHeadlessModePolicyEnabled = 1,
kHeadlessModePolicyDisabled = 2,
kHeadlessModePolicyUnset = -1, // not in the template
};
class HeadlessBrowserTestWithHeadlessModePolicy
: public HeadlessBrowserTestWithPolicy<HeadlessBrowserTest>,
public testing::WithParamInterface<std::tuple<int, bool>> {
protected:
void SetPolicy() override {
int headless_mode_policy = std::get<0>(GetParam());
if (headless_mode_policy != kHeadlessModePolicyUnset) {
SetHeadlessModePolicy(
static_cast<policy::HeadlessModePolicy::HeadlessMode>(
headless_mode_policy));
}
}
void SetHeadlessModePolicy(
policy::HeadlessModePolicy::HeadlessMode headless_mode) {
policy::PolicyMap policy;
policy.Set("HeadlessMode", policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
base::Value(static_cast<int>(headless_mode)),
/*external_data_fetcher=*/nullptr);
mock_provider_->UpdateChromePolicy(policy);
}
bool expected_enabled() { return std::get<1>(GetParam()); }
bool actual_enabled() {
return !policy::HeadlessModePolicy::IsHeadlessDisabled(GetPrefs());
}
};
INSTANTIATE_TEST_SUITE_P(
HeadlessBrowserTestWithHeadlessModePolicy,
HeadlessBrowserTestWithHeadlessModePolicy,
testing::Values(std::make_tuple(kHeadlessModePolicyEnabled, true),
std::make_tuple(kHeadlessModePolicyDisabled, false),
std::make_tuple(kHeadlessModePolicyUnset, true)));
IN_PROC_BROWSER_TEST_P(HeadlessBrowserTestWithHeadlessModePolicy,
HeadlessModePolicySettings) {
EXPECT_EQ(actual_enabled(), expected_enabled());
}
class HeadlessBrowserTestWithUrlBlockPolicy
: public HeadlessBrowserTestWithPolicy<HeadlessBrowserTest> {
protected:
void SetPolicy() override {
base::Value value(base::Value::Type::LIST);
value.Append("*/blocked.html");
policy::PolicyMap policy;
policy.Set("URLBlocklist", policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
std::move(value), /*external_data_fetcher=*/nullptr);
mock_provider_->UpdateChromePolicy(policy);
}
};
IN_PROC_BROWSER_TEST_F(HeadlessBrowserTestWithUrlBlockPolicy, BlockUrl) {
EXPECT_TRUE(embedded_test_server()->Start());
HeadlessBrowserContext* browser_context =
browser()->CreateBrowserContextBuilder().Build();
GURL url = embedded_test_server()->GetURL("/blocked.html");
HeadlessWebContents* web_contents =
browser_context->CreateWebContentsBuilder().SetInitialURL(url).Build();
net::Error error = net::OK;
EXPECT_FALSE(WaitForLoad(web_contents, &error));
EXPECT_EQ(error, net::ERR_BLOCKED_BY_ADMINISTRATOR);
}
namespace {
class CaptureStdErr {
public:
CaptureStdErr() {
#if BUILDFLAG(IS_WIN)
CHECK_EQ(_pipe(pipes_, 4096, O_BINARY), 0);
#else
CHECK_EQ(pipe(pipes_), 0);
#endif
stderr_ = dup(fileno(stderr));
CHECK_NE(stderr_, -1);
}
~CaptureStdErr() {
StopCapture();
close(pipes_[kReadPipe]);
close(pipes_[kWritePipe]);
close(stderr_);
}
void StartCapture() {
if (capturing_)
return;
fflush(stderr);
CHECK_NE(dup2(pipes_[kWritePipe], fileno(stderr)), -1);
capturing_ = true;
}
void StopCapture() {
if (!capturing_)
return;
char eop = kPipeEnd;
CHECK_NE(write(pipes_[kWritePipe], &eop, sizeof(eop)), -1);
fflush(stderr);
CHECK_NE(dup2(stderr_, fileno(stderr)), -1);
capturing_ = false;
}
std::string ReadCapturedData() {
CHECK(!capturing_);
std::string captured_data;
for (;;) {
constexpr size_t kChunkSize = 256;
char buffer[kChunkSize];
int bytes_read = read(pipes_[kReadPipe], buffer, kChunkSize);
CHECK_NE(bytes_read, -1);
captured_data.append(buffer, bytes_read);
if (captured_data.rfind(kPipeEnd) != std::string::npos)
break;
}
return captured_data;
}
std::vector<std::string> ReadCapturedLines() {
return base::SplitString(ReadCapturedData(), "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
private:
enum { kReadPipe, kWritePipe };
static constexpr char kPipeEnd = '\xff';
base::ScopedAllowBlockingForTesting allow_blocking_calls_;
bool capturing_ = false;
int pipes_[2] = {-1, -1};
int stderr_ = -1;
};
} // namespace
class HeadlessBrowserTestWithRemoteDebuggingAllowedPolicy
: public HeadlessBrowserTestWithPolicy<HeadlessBrowserTest>,
public testing::WithParamInterface<bool> {
protected:
void SetPolicy() override {
policy::PolicyMap policy;
policy.Set("RemoteDebuggingAllowed", policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
base::Value(expect_remote_debugging_available()),
/*external_data_fetcher=*/nullptr);
mock_provider_->UpdateChromePolicy(policy);
}
void SetUpInProcessBrowserTestFixture() override {
HeadlessBrowserTestWithPolicy<
HeadlessBrowserTest>::SetUpInProcessBrowserTestFixture();
options()->devtools_endpoint = net::HostPortPair("localhost", 0);
capture_stderr_.StartCapture();
}
bool expect_remote_debugging_available() { return GetParam(); }
CaptureStdErr capture_stderr_;
};
INSTANTIATE_TEST_SUITE_P(HeadlessBrowserTestWithRemoteDebuggingAllowedPolicy,
HeadlessBrowserTestWithRemoteDebuggingAllowedPolicy,
testing::Values(true, false));
// Remote debugging with ephemeral port is not working on Fuchsia, see
// crbug.com/1209251.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_RemoteDebuggingDisallowed DISABLED_RemoteDebuggingDisallowed
#else
#define MAYBE_RemoteDebuggingDisallowed RemoteDebuggingDisallowed
#endif
IN_PROC_BROWSER_TEST_P(HeadlessBrowserTestWithRemoteDebuggingAllowedPolicy,
MAYBE_RemoteDebuggingDisallowed) {
// DevTools starts its remote debugging port listener asynchronously and
// there is no reliable way to know when it is started, so resort to an
// ugly wait then check captured stderr.
base::PlatformThread::Sleep(TestTimeouts::action_timeout());
capture_stderr_.StopCapture();
enum { kUnknown, kDisallowed, kListening } remote_debugging_state = kUnknown;
for (const std::string& line : capture_stderr_.ReadCapturedLines()) {
LOG(INFO) << "stderr: " << line;
if (base::MatchPattern(line, "DevTools remote debugging is disallowed *")) {
EXPECT_EQ(remote_debugging_state, kUnknown);
remote_debugging_state = kDisallowed;
} else if (base::MatchPattern(line, "DevTools listening on *")) {
EXPECT_EQ(remote_debugging_state, kUnknown);
remote_debugging_state = kListening;
}
}
EXPECT_NE(remote_debugging_state, kUnknown);
if (expect_remote_debugging_available())
EXPECT_EQ(remote_debugging_state, kListening);
else
EXPECT_EQ(remote_debugging_state, kDisallowed);
}
} // namespace headless