| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "content/browser/devtools/devtools_http_handler.h" | |
| #include <stdint.h> | |
| #include <memory> | |
| #include <utility> | |
| #include "base/command_line.h" | |
| #include "base/files/file_util.h" | |
| #include "base/files/scoped_temp_dir.h" | |
| #include "base/functional/bind.h" | |
| #include "base/json/json_reader.h" | |
| #include "base/location.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/run_loop.h" | |
| #include "base/strings/escape.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_split.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/task/single_thread_task_runner.h" | |
| #include "base/test/task_environment.h" | |
| #include "base/values.h" | |
| #include "build/build_config.h" | |
| #include "content/browser/devtools/devtools_agent_host_impl.h" | |
| #include "content/browser/devtools/devtools_manager.h" | |
| #include "content/public/browser/browser_task_traits.h" | |
| #include "content/public/browser/browser_thread.h" | |
| #include "content/public/browser/content_browser_client.h" | |
| #include "content/public/browser/devtools_manager_delegate.h" | |
| #include "content/public/browser/devtools_socket_factory.h" | |
| #include "content/public/common/content_client.h" | |
| #include "content/public/common/content_constants.h" | |
| #include "content/public/common/content_switches.h" | |
| #include "content/public/test/browser_task_environment.h" | |
| #include "content/public/test/mock_devtools_agent_host.h" | |
| #include "content/public/test/test_utils.h" | |
| #include "net/base/completion_once_callback.h" | |
| #include "net/base/ip_address.h" | |
| #include "net/base/ip_endpoint.h" | |
| #include "net/base/net_errors.h" | |
| #include "net/base/network_handle.h" | |
| #include "net/socket/server_socket.h" | |
| #include "net/socket/tcp_server_socket.h" | |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | |
| #include "net/url_request/url_request_context.h" | |
| #include "net/url_request/url_request_context_builder.h" | |
| #include "net/url_request/url_request_test_util.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace content { | |
| namespace { | |
| using ::testing::HasSubstr; | |
| using ::testing::Not; | |
| using ::testing::Return; | |
| const uint16_t kDummyPort = 4321; | |
| class DummyServerSocket : public net::ServerSocket { | |
| public: | |
| // net::ServerSocket "implementation" | |
| int Listen(const net::IPEndPoint& address, | |
| int backlog, | |
| std::optional<bool> ipv6_only) override { | |
| return net::OK; | |
| } | |
| int GetLocalAddress(net::IPEndPoint* address) const override { | |
| *address = net::IPEndPoint(net::IPAddress::IPv4Localhost(), kDummyPort); | |
| return net::OK; | |
| } | |
| int Accept(std::unique_ptr<net::StreamSocket>* socket, | |
| net::CompletionOnceCallback callback) override { | |
| return net::ERR_IO_PENDING; | |
| } | |
| }; | |
| class DummyServerSocketFactory : public DevToolsSocketFactory { | |
| public: | |
| DummyServerSocketFactory(base::OnceClosure create_socket_callback, | |
| base::OnceClosure shutdown_callback) | |
| : create_socket_callback_(std::move(create_socket_callback)), | |
| shutdown_callback_(std::move(shutdown_callback)) {} | |
| ~DummyServerSocketFactory() override { | |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, | |
| std::move(shutdown_callback_)); | |
| } | |
| protected: | |
| std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { | |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, | |
| std::move(create_socket_callback_)); | |
| return std::make_unique<DummyServerSocket>(); | |
| } | |
| std::unique_ptr<net::ServerSocket> CreateForTethering( | |
| std::string* out_name) override { | |
| return nullptr; | |
| } | |
| base::OnceClosure create_socket_callback_; | |
| base::OnceClosure shutdown_callback_; | |
| }; | |
| class FailingServerSocketFactory : public DummyServerSocketFactory { | |
| public: | |
| FailingServerSocketFactory(base::OnceClosure create_socket_callback, | |
| base::OnceClosure shutdown_callback) | |
| : DummyServerSocketFactory(std::move(create_socket_callback), | |
| std::move(shutdown_callback)) {} | |
| private: | |
| std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { | |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, | |
| std::move(create_socket_callback_)); | |
| return nullptr; | |
| } | |
| }; | |
| class TCPServerSocketFactory : public DummyServerSocketFactory { | |
| public: | |
| TCPServerSocketFactory(base::OnceClosure create_socket_callback, | |
| base::OnceClosure shutdown_callback) | |
| : DummyServerSocketFactory(std::move(create_socket_callback), | |
| std::move(shutdown_callback)) {} | |
| int port() { return port_; } | |
| private: | |
| std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { | |
| std::unique_ptr<net::ServerSocket> socket( | |
| new net::TCPServerSocket(nullptr, net::NetLogSource())); | |
| while (socket->ListenWithAddressAndPort("127.0.0.1", port_, 10) != | |
| net::OK) { | |
| if (++port_ > kDummyPort + 100) | |
| return nullptr; | |
| } | |
| GetUIThreadTaskRunner({})->PostTask(FROM_HERE, | |
| std::move(create_socket_callback_)); | |
| return socket; | |
| } | |
| int port_ = kDummyPort; | |
| }; | |
| class MockDevToolsManagerDelegate : public DevToolsManagerDelegate { | |
| public: | |
| static MockDevToolsManagerDelegate* last_instance; | |
| MockDevToolsManagerDelegate() { | |
| last_instance = this; | |
| EXPECT_CALL(*this, SetActiveWebSocketConnections(testing::_)) | |
| .Times(testing::AtLeast(0)); | |
| ON_CALL(*this, AllowInspectingTarget(testing::_)) | |
| .WillByDefault(testing::Return(true)); | |
| } | |
| MOCK_METHOD(scoped_refptr<DevToolsAgentHost>, | |
| CreateNewTarget, | |
| (const GURL& url, TargetType target_type, bool new_window), | |
| (override)); | |
| MOCK_METHOD(DevToolsAgentHost::List, | |
| RemoteDebuggingTargets, | |
| (TargetType target_type), | |
| (override)); | |
| MOCK_METHOD(void, | |
| AcceptDebugging, | |
| (base::OnceCallback<void(AcceptConnectionResult)> callback), | |
| (override)); | |
| MOCK_METHOD(void, | |
| SetActiveWebSocketConnections, | |
| (size_t count), | |
| (override)); | |
| MOCK_METHOD(bool, | |
| AllowInspectingTarget, | |
| (DevToolsAgentHost*), | |
| (override)); | |
| }; | |
| MockDevToolsManagerDelegate* MockDevToolsManagerDelegate::last_instance = | |
| nullptr; | |
| class MockDevToolsAgentHostWithType : public MockDevToolsAgentHost { | |
| public: | |
| explicit MockDevToolsAgentHostWithType(const std::string& type) | |
| : type_(type) {} | |
| std::string GetType() override { return type_; } | |
| private: | |
| ~MockDevToolsAgentHostWithType() override = default; | |
| std::string type_; | |
| }; | |
| class BrowserClient : public ContentBrowserClient { | |
| public: | |
| BrowserClient() {} | |
| ~BrowserClient() override {} | |
| std::unique_ptr<content::DevToolsManagerDelegate> | |
| CreateDevToolsManagerDelegate() override { | |
| return std::make_unique<MockDevToolsManagerDelegate>(); | |
| } | |
| }; | |
| } | |
| class DevToolsHttpHandlerTest : public testing::Test { | |
| public: | |
| DevToolsHttpHandlerTest() | |
| : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {} | |
| void SetUp() override { | |
| content_client_ = std::make_unique<ContentClient>(); | |
| browser_content_client_ = std::make_unique<BrowserClient>(); | |
| original_client_ = | |
| SetBrowserClientForTesting(browser_content_client_.get()); | |
| DevToolsManager::ShutdownForTests(); | |
| } | |
| void TearDown() override { | |
| SetBrowserClientForTesting(original_client_); | |
| DevToolsManager::ShutdownForTests(); | |
| } | |
| private: | |
| std::unique_ptr<ContentClient> content_client_; | |
| std::unique_ptr<ContentBrowserClient> browser_content_client_; | |
| raw_ptr<ContentBrowserClient> original_client_ = nullptr; | |
| content::BrowserTaskEnvironment task_environment_; | |
| }; | |
| TEST_F(DevToolsHttpHandlerTest, TestStartStop) { | |
| base::RunLoop run_loop, run_loop_2; | |
| auto factory = std::make_unique<DummyServerSocketFactory>( | |
| run_loop.QuitClosure(), run_loop_2.QuitClosure()); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| std::move(factory), base::FilePath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| } | |
| TEST_F(DevToolsHttpHandlerTest, TestServerSocketFailed) { | |
| base::RunLoop run_loop, run_loop_2; | |
| auto factory = std::make_unique<FailingServerSocketFactory>( | |
| run_loop.QuitClosure(), run_loop_2.QuitClosure()); | |
| LOG(INFO) << "Following error message is expected:"; | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| std::move(factory), base::FilePath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| for (int i = 0; i < 5; i++) | |
| RunAllPendingInMessageLoop(BrowserThread::UI); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| } | |
| TEST_F(DevToolsHttpHandlerTest, TestDevToolsActivePort) { | |
| base::RunLoop run_loop, run_loop_2; | |
| base::ScopedTempDir temp_dir; | |
| EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| auto factory = std::make_unique<DummyServerSocketFactory>( | |
| run_loop.QuitClosure(), run_loop_2.QuitClosure()); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| std::move(factory), temp_dir.GetPath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| // Now make sure the DevToolsActivePort was written into the | |
| // temporary directory and its contents are as expected. | |
| base::FilePath active_port_file = | |
| temp_dir.GetPath().Append(kDevToolsActivePortFileName); | |
| EXPECT_TRUE(base::PathExists(active_port_file)); | |
| std::string file_contents; | |
| EXPECT_TRUE(base::ReadFileToString(active_port_file, &file_contents)); | |
| std::vector<std::string> tokens = base::SplitString( | |
| file_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | |
| int port = 0; | |
| EXPECT_TRUE(base::StringToInt(tokens[0], &port)); | |
| EXPECT_EQ(static_cast<int>(kDummyPort), port); | |
| } | |
| TEST_F(DevToolsHttpHandlerTest, MutatingActionsiRequireSafeVerb) { | |
| base::RunLoop run_loop, run_loop_2; | |
| auto* factory = new TCPServerSocketFactory(run_loop.QuitClosure(), | |
| run_loop_2.QuitClosure()); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| base::WrapUnique(factory), base::FilePath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| int port = factory->port(); | |
| net::TestDelegate delegate; | |
| GURL url(base::StringPrintf("http://127.0.0.1:%d/json/new", port)); | |
| auto request_context = net::CreateTestURLRequestContextBuilder()->Build(); | |
| auto request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| EXPECT_EQ(405, request->response_info().headers->response_code()); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("POST"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| EXPECT_EQ(405, request->response_info().headers->response_code()); | |
| EXPECT_CALL( | |
| *MockDevToolsManagerDelegate::last_instance, | |
| CreateNewTarget(GURL("about:blank"), DevToolsManagerDelegate::kFrame, | |
| /*new_window=*/false)) | |
| .WillOnce(Return(base::MakeRefCounted<MockDevToolsAgentHost>())); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| EXPECT_EQ(200, request->response_info().headers->response_code()); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| } | |
| TEST_F(DevToolsHttpHandlerTest, TestJsonNew) { | |
| base::RunLoop run_loop, run_loop_2; | |
| auto* factory = new TCPServerSocketFactory(run_loop.QuitClosure(), | |
| run_loop_2.QuitClosure()); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| base::WrapUnique(factory), base::FilePath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| int port = factory->port(); | |
| net::TestDelegate delegate; | |
| EXPECT_CALL( | |
| *MockDevToolsManagerDelegate::last_instance, | |
| CreateNewTarget(GURL("about:blank"), DevToolsManagerDelegate::kFrame, | |
| /*new_window=*/false)); | |
| GURL url(base::StringPrintf("http://127.0.0.1:%d/json/new", port)); | |
| auto request_context = net::CreateTestURLRequestContextBuilder()->Build(); | |
| auto request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| EXPECT_CALL( | |
| *MockDevToolsManagerDelegate::last_instance, | |
| CreateNewTarget(GURL("http://example.com"), | |
| DevToolsManagerDelegate::kFrame, /*new_window=*/false)); | |
| url = GURL(base::StringPrintf( | |
| "http://127.0.0.1:%d/json/new?%s", port, | |
| base::EscapeQueryParamValue("http://example.com", true).c_str())); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| EXPECT_CALL( | |
| *MockDevToolsManagerDelegate::last_instance, | |
| CreateNewTarget(GURL("http://example.com"), DevToolsManagerDelegate::kTab, | |
| /*new_window=*/false)); | |
| url = GURL(base::StringPrintf( | |
| "http://127.0.0.1:%d/json/new?%s&for_tab", port, | |
| base::EscapeQueryParamValue("http://example.com", true).c_str())); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| } | |
| TEST_F(DevToolsHttpHandlerTest, TestJsonList) { | |
| base::RunLoop run_loop, run_loop_2; | |
| auto* factory = new TCPServerSocketFactory(run_loop.QuitClosure(), | |
| run_loop_2.QuitClosure()); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| base::WrapUnique(factory), base::FilePath(), base::FilePath()); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop.Run(); | |
| int port = factory->port(); | |
| net::TestDelegate delegate; | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, | |
| RemoteDebuggingTargets(DevToolsManagerDelegate::kFrame)); | |
| GURL url(base::StringPrintf("http://127.0.0.1:%d/json", port)); | |
| auto request_context = net::CreateTestURLRequestContextBuilder()->Build(); | |
| auto request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, | |
| RemoteDebuggingTargets(DevToolsManagerDelegate::kFrame)) | |
| .WillOnce(Return(std::vector<scoped_refptr<DevToolsAgentHost>>{ | |
| base::MakeRefCounted<MockDevToolsAgentHostWithType>("service_worker"), | |
| base::MakeRefCounted<MockDevToolsAgentHostWithType>("tab")})); | |
| url = GURL(base::StringPrintf("http://127.0.0.1:%d/json/list", port)); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_THAT(delegate.data_received(), | |
| HasSubstr(R"("type": "service_worker")")); | |
| EXPECT_THAT(delegate.data_received(), Not(HasSubstr(R"("type": "tab")"))); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, | |
| RemoteDebuggingTargets(DevToolsManagerDelegate::kTab)) | |
| .WillOnce(Return(std::vector<scoped_refptr<DevToolsAgentHost>>{ | |
| base::MakeRefCounted<MockDevToolsAgentHostWithType>("service_worker"), | |
| base::MakeRefCounted<MockDevToolsAgentHostWithType>("tab")})); | |
| url = GURL(base::StringPrintf("http://127.0.0.1:%d/json/list?for_tab", port)); | |
| request = request_context->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->set_method("PUT"); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_THAT(delegate.data_received(), | |
| HasSubstr(R"("type": "service_worker")")); | |
| EXPECT_THAT(delegate.data_received(), HasSubstr(R"("type": "tab")")); | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| // Make sure the handler actually stops. | |
| run_loop_2.Run(); | |
| } | |
| class DevToolsHttpHandlerWithServerTest : public DevToolsHttpHandlerTest { | |
| public: | |
| DevToolsHttpHandlerWithServerTest() = default; | |
| int StartServer( | |
| DevToolsAgentHost::RemoteDebuggingServerMode mode = | |
| DevToolsAgentHost::RemoteDebuggingServerMode::kDefault) { | |
| std::unique_ptr<TCPServerSocketFactory> factory = | |
| std::make_unique<TCPServerSocketFactory>(run_loop_.QuitClosure(), | |
| run_loop_2_.QuitClosure()); | |
| TCPServerSocketFactory* factory_raw = factory.get(); | |
| DevToolsAgentHost::StartRemoteDebuggingServer( | |
| std::move(factory), base::FilePath(), base::FilePath(), mode); | |
| // Our dummy socket factory will post a quit message once the server will | |
| // become ready. | |
| run_loop_.Run(); | |
| return factory_raw->port(); | |
| } | |
| void StopServer() { | |
| DevToolsAgentHost::StopRemoteDebuggingServer(); | |
| run_loop_2_.Run(); | |
| } | |
| std::unique_ptr<net::URLRequest> RunRequestUntilCompletion( | |
| std::string url, | |
| std::map<std::string, std::string> headers = {}) { | |
| net::TestDelegate delegate; | |
| auto request = request_context_->CreateRequest( | |
| GURL(url), net::DEFAULT_PRIORITY, &delegate, | |
| TRAFFIC_ANNOTATION_FOR_TESTS, net::handles::kInvalidNetworkHandle); | |
| for (auto const& [key, value] : headers) { | |
| request->SetExtraRequestHeaderByName(key, value, true); | |
| } | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| return request; | |
| } | |
| protected: | |
| std::unique_ptr<net::URLRequestContext> request_context_ = | |
| net::CreateTestURLRequestContextBuilder()->Build(); | |
| base::RunLoop run_loop_; | |
| base::RunLoop run_loop_2_; | |
| }; | |
| TEST_F(DevToolsHttpHandlerWithServerTest, JsonVersionIncludesFilePathStyle) { | |
| int port = StartServer(); | |
| GURL url(base::StringPrintf("http://127.0.0.1:%d/json/version", port)); | |
| net::TestDelegate delegate; | |
| auto request = request_context_->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| std::string response_body = delegate.data_received(); | |
| StopServer(); | |
| std::optional<base::Value> response = base::JSONReader::Read( | |
| response_body, base::JSON_PARSE_CHROMIUM_EXTENSIONS); | |
| ASSERT_TRUE(response.has_value()); | |
| const base::DictValue* dict = response->GetIfDict(); | |
| ASSERT_NE(dict, nullptr); | |
| const std::string* file_path_style = dict->FindString("File-Path-Style"); | |
| ASSERT_NE(file_path_style, nullptr); | |
| #if BUILDFLAG(IS_WIN) | |
| EXPECT_EQ("windows", *file_path_style); | |
| #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) | |
| EXPECT_EQ("posix", *file_path_style); | |
| #else | |
| #error Unsupported native file path style | |
| #endif | |
| } | |
| class DevToolsWebSocketHandlerTest : public DevToolsHttpHandlerWithServerTest { | |
| public: | |
| DevToolsWebSocketHandlerTest() = default; | |
| std::string GetWebSocketDebuggingURL(int port) { | |
| GURL url(base::StringPrintf("http://127.0.0.1:%d/json/version", port)); | |
| net::TestDelegate delegate; | |
| auto request = request_context_->CreateRequest( | |
| url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS, | |
| net::handles::kInvalidNetworkHandle); | |
| request->Start(); | |
| delegate.RunUntilComplete(); | |
| EXPECT_GE(delegate.request_status(), 0); | |
| std::optional<base::Value> response = base::JSONReader::Read( | |
| delegate.data_received(), base::JSON_PARSE_CHROMIUM_EXTENSIONS); | |
| base::DictValue* dict = response->GetIfDict(); | |
| // Compute HTTP upgrade request URL. | |
| std::string debugging_url = *dict->FindString("webSocketDebuggerUrl"); | |
| std::string prefix = "ws://"; | |
| return "http://" + debugging_url.substr(prefix.length()); | |
| } | |
| }; | |
| TEST_F(DevToolsWebSocketHandlerTest, | |
| TestRejectsWebSocketConnectionsWithOrigin) { | |
| int port = StartServer(); | |
| std::string debugging_url = GetWebSocketDebuggingURL(port); | |
| // Accepts an upgrade request without an Origin header. | |
| auto request = | |
| RunRequestUntilCompletion(debugging_url, { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| }); | |
| // This error is expected because it's not a well-formed WS request. | |
| // It means that the request is accepted by the server though. | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| // Accepts an upgrade request with an Origin matching the server's origin. | |
| request = RunRequestUntilCompletion( | |
| debugging_url, | |
| {{"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", base::StringPrintf("http://127.0.0.1:%d", port)}}); | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| // Denies an upgrade request with an Origin header. | |
| request = RunRequestUntilCompletion(debugging_url, | |
| {{"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", "http://localhost"}}); | |
| EXPECT_EQ(request->GetResponseCode(), 403); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsWebSocketHandlerTest, TestAllowsCLIOverrideAllowsOriginsStar) { | |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( | |
| switches::kRemoteAllowOrigins, "*"); | |
| int port = StartServer(); | |
| std::string debugging_url = GetWebSocketDebuggingURL(port); | |
| auto request = RunRequestUntilCompletion(debugging_url, | |
| { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", "http://localhost"}, | |
| }); | |
| // This error is expected because it's not a well-formed WS request. | |
| // It means that the request is accepted by the server though. | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsWebSocketHandlerTest, TestRejectsForbiddenHostHeader) { | |
| int port = StartServer(); | |
| std::string debugging_url = GetWebSocketDebuggingURL(port); | |
| auto request = | |
| RunRequestUntilCompletion(debugging_url, { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"host", "attacker.com"}, | |
| }); | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsWebSocketHandlerTest, TestAllowsCLIOverrideAllowsOrigins) { | |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( | |
| switches::kRemoteAllowOrigins, "http://localhost"); | |
| int port = StartServer(); | |
| std::string debugging_url = GetWebSocketDebuggingURL(port); | |
| auto request = RunRequestUntilCompletion(debugging_url, | |
| { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", "http://localhost"}, | |
| }); | |
| // This error is expected because it's not a well-formed WS request. | |
| // It means that the request is accepted by the server though. | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| // Same origin as the server is still allowed even when --remote-allow-origins | |
| // specifies a different origin. | |
| request = RunRequestUntilCompletion( | |
| debugging_url, | |
| { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", base::StringPrintf("http://127.0.0.1:%d", port)}, | |
| }); | |
| EXPECT_EQ(request->GetResponseCode(), 500); | |
| request = RunRequestUntilCompletion(debugging_url, | |
| { | |
| {"connection", "upgrade"}, | |
| {"upgrade", "websocket"}, | |
| {"origin", "http://127.0.0.1"}, | |
| }); | |
| EXPECT_EQ(request->GetResponseCode(), 403); | |
| StopServer(); | |
| } | |
| class DevToolsHttpHandlerWithApprovalTest | |
| : public DevToolsHttpHandlerWithServerTest { | |
| public: | |
| int StartServer() { | |
| return DevToolsHttpHandlerWithServerTest::StartServer( | |
| DevToolsAgentHost::RemoteDebuggingServerMode::kWithApprovalOnly); | |
| } | |
| }; | |
| TEST_F(DevToolsHttpHandlerWithApprovalTest, JsonRequestsReturn404) { | |
| int port = StartServer(); | |
| auto request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/json", port)); | |
| EXPECT_EQ(404, request->response_info().headers->response_code()); | |
| request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/json/list", port)); | |
| EXPECT_EQ(404, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithApprovalTest, | |
| DiscoveryAndFrontendPagesReturn404) { | |
| int port = StartServer(); | |
| auto request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/", port)); | |
| EXPECT_EQ(404, request->response_info().headers->response_code()); | |
| request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/devtools/inspector.html", port)); | |
| EXPECT_EQ(404, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithApprovalTest, | |
| WebSocketConnectionRequiresApprovalAllow) { | |
| int port = StartServer(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, AcceptDebugging) | |
| .WillOnce( | |
| [](base::OnceCallback<void( | |
| DevToolsManagerDelegate::AcceptConnectionResult)> callback) { | |
| std::move(callback).Run( | |
| DevToolsManagerDelegate::AcceptConnectionResult::kAllow); | |
| }); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, | |
| SetActiveWebSocketConnections(1)); | |
| std::string url = | |
| base::StringPrintf("http://127.0.0.1:%d/devtools/browser", port); | |
| auto request = RunRequestUntilCompletion( | |
| url, {{"connection", "upgrade"}, {"upgrade", "websocket"}}); | |
| // This error is expected because it's not a well-formed WS request. | |
| // It means that the request is accepted by the server though. | |
| EXPECT_EQ(500, request->response_info().headers->response_code()); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, | |
| SetActiveWebSocketConnections(0)); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithApprovalTest, | |
| WebSocketConnectionRequiresApprovalDeny) { | |
| int port = StartServer(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, AcceptDebugging) | |
| .WillOnce( | |
| [](base::OnceCallback<void( | |
| DevToolsManagerDelegate::AcceptConnectionResult)> callback) { | |
| std::move(callback).Run( | |
| DevToolsManagerDelegate::AcceptConnectionResult::kDeny); | |
| }); | |
| std::string url = | |
| base::StringPrintf("http://127.0.0.1:%d/devtools/browser", port); | |
| auto request = RunRequestUntilCompletion( | |
| url, {{"connection", "upgrade"}, {"upgrade", "websocket"}}); | |
| EXPECT_EQ(403, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithApprovalTest, | |
| NonBrowserWebSocketConnectionIsRejected) { | |
| int port = StartServer(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, AcceptDebugging) | |
| .Times(0); | |
| std::string url = | |
| base::StringPrintf("http://127.0.0.1:%d/devtools/page/test", port); | |
| auto request = RunRequestUntilCompletion( | |
| url, {{"connection", "upgrade"}, {"upgrade", "websocket"}}); | |
| EXPECT_EQ(403, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithServerTest, JsonActivateCloseRespectsAllowlist) { | |
| scoped_refptr<DevToolsAgentHost> host = DevToolsAgentHost::CreateForDiscovery(); | |
| int port = StartServer(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, AllowInspectingTarget(testing::_)) | |
| .WillRepeatedly(testing::Return(false)); | |
| auto request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/json/activate/%s", port, host->GetId().c_str())); | |
| EXPECT_EQ(403, request->response_info().headers->response_code()); | |
| request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/json/close/%s", port, host->GetId().c_str())); | |
| EXPECT_EQ(403, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| TEST_F(DevToolsHttpHandlerWithServerTest, WebSocketRespectsAllowlist) { | |
| scoped_refptr<DevToolsAgentHost> host = DevToolsAgentHost::CreateForDiscovery(); | |
| int port = StartServer(); | |
| EXPECT_CALL(*MockDevToolsManagerDelegate::last_instance, AllowInspectingTarget(testing::_)) | |
| .WillRepeatedly(testing::Return(false)); | |
| auto request = RunRequestUntilCompletion( | |
| base::StringPrintf("http://127.0.0.1:%d/devtools/page/%s", port, host->GetId().c_str()), | |
| {{"connection", "upgrade"}, {"upgrade", "websocket"}}); | |
| // Expect HTTP 403 Forbidden because AllowInspectingTarget returned false | |
| EXPECT_EQ(403, request->response_info().headers->response_code()); | |
| StopServer(); | |
| } | |
| } // namespace content |