// Copyright 2022 The Chromium Embedded Framework 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 "libcef/browser/test/test_server_impl.h" #include "libcef/common/net/http_header_utils.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/thread_checker.h" #include "net/http/http_request_headers.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_response.h" using namespace net::test_server; namespace { class CefTestServerConnectionImpl : public CefTestServerConnection { public: explicit CefTestServerConnectionImpl( base::WeakPtr delegate) : delegate_(delegate), task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) { DCHECK(delegate_); DCHECK(task_runner_); } void SendHttp200Response(const CefString& content_type, const void* data, size_t data_size) override { auto response = std::make_unique(); response->set_code(net::HTTP_OK); response->set_content_type(base::StringPiece(content_type.ToString())); response->set_content( base::StringPiece(reinterpret_cast(data), data_size)); SendBasicHttpResponse(std::move(response)); } void SendHttp404Response() override { auto response = std::make_unique(); response->set_code(net::HTTP_NOT_FOUND); SendBasicHttpResponse(std::move(response)); } void SendHttp500Response(const CefString& error_message) override { auto response = std::make_unique(); response->set_code(net::HTTP_INTERNAL_SERVER_ERROR); response->set_content_type(base::StringPiece("text/html")); response->set_content(base::StringPiece(error_message.ToString())); SendBasicHttpResponse(std::move(response)); } void SendHttpResponse(int response_code, const CefString& content_type, const void* data, size_t data_size, const HeaderMap& extra_headers) override { auto response = std::make_unique(); response->set_code(static_cast(response_code)); response->set_content_type(base::StringPiece(content_type.ToString())); response->set_content( base::StringPiece(reinterpret_cast(data), data_size)); for (const auto& [key, value] : extra_headers) { response->AddCustomHeader(key.ToString(), value.ToString()); } SendBasicHttpResponse(std::move(response)); } private: void SendBasicHttpResponse(std::unique_ptr response) { if (!task_runner_->BelongsToCurrentThread()) { task_runner_->PostTask( FROM_HERE, base::BindOnce(&CefTestServerConnectionImpl::SendBasicHttpResponse, this, std::move(response))); return; } if (delegate_) { response->SendResponse(delegate_); } } base::WeakPtr delegate_; scoped_refptr task_runner_; IMPLEMENT_REFCOUNTING(CefTestServerConnectionImpl); }; class CefHttpResponse : public HttpResponse { public: CefHttpResponse(CefRefPtr server, CefRefPtr handler, CefRefPtr request) : server_(server), handler_(handler), request_(request) { DCHECK(server_); DCHECK(handler_); DCHECK(request_); } CefHttpResponse(const CefHttpResponse&) = delete; CefHttpResponse& operator=(const CefHttpResponse&) = delete; void SendResponse(base::WeakPtr delegate) override { CefRefPtr connection( new CefTestServerConnectionImpl(delegate)); const bool handled = handler_->OnTestServerRequest(server_, request_, connection.get()); if (handled) { return; } LOG(WARNING) << "Request not handled. Returning 404: " << request_->GetURL().ToString(); connection->SendHttp404Response(); } private: CefRefPtr server_; CefRefPtr handler_; CefRefPtr request_; }; CefRefPtr CreateCefRequest(const HttpRequest& request) { CefRefPtr post_data; if (!request.content.empty()) { post_data = CefPostData::Create(); auto element = CefPostDataElement::Create(); element->SetToBytes(request.content.size(), request.content.c_str()); post_data->AddElement(element); } CefRequest::HeaderMap header_map; CefString referer; HttpHeaderUtils::ParseHeaders(request.all_headers, header_map); // CefRequest will strip the Referer header from the map, so we don't need to // do that here. for (const auto& [key, value] : header_map) { if (base::EqualsCaseInsensitiveASCII(key.ToString(), net::HttpRequestHeaders::kReferer)) { referer = value; } } auto cef_request = CefRequest::Create(); cef_request->Set(request.GetURL().spec(), request.method_string, post_data, header_map); if (!referer.empty()) { cef_request->SetReferrer(referer, REFERRER_POLICY_DEFAULT); } return cef_request; } } // namespace class CefTestServerImpl::Context { public: Context(CefRefPtr server, CefRefPtr handler) : server_(server), handler_(handler) { DCHECK(server_); DCHECK(handler_); } Context(const Context&) = delete; Context& operator=(const Context&) = delete; ~Context() { // The server should not be running. DCHECK(!test_server_); } bool Start(uint16_t port, bool https_server, cef_test_cert_type_t https_cert_type) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!test_server_); test_server_ = std::make_unique( https_server ? EmbeddedTestServer::TYPE_HTTPS : EmbeddedTestServer::TYPE_HTTP); // Unretained is safe because Stop is called before |this| is destroyed. test_server_->RegisterRequestHandler( base::BindRepeating(&Context::HandleRequest, base::Unretained(this))); if (https_server) { switch (https_cert_type) { case CEF_TEST_CERT_OK_IP: // Default value. break; case CEF_TEST_CERT_OK_DOMAIN: test_server_->SetSSLConfig( EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN); break; case CEF_TEST_CERT_EXPIRED: test_server_->SetSSLConfig(EmbeddedTestServer::CERT_EXPIRED); break; } } test_server_handle_ = test_server_->StartAndReturnHandle(static_cast(port)); if (!test_server_handle_) { test_server_.reset(); return false; } origin_ = test_server_->base_url(); return true; } void Stop() { // Should be called on the creation thread. DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(test_server_); // Destruction of |test_server_handle_| will stop the server and block until // the dedicated server thread has shut down. test_server_handle_ = EmbeddedTestServerHandle(); test_server_.reset(); server_ = nullptr; handler_ = nullptr; } const GURL& origin() const { return origin_; } private: std::unique_ptr HandleRequest(const HttpRequest& request) { // Should be on the dedicated server thread. DCHECK(!thread_checker_.CalledOnValidThread()); return std::make_unique(server_, handler_, CreateCefRequest(request)); } // Safe to access on any thread. CefRefPtr server_; CefRefPtr handler_; GURL origin_; base::ThreadChecker thread_checker_; // Only accessed on the creation thread. std::unique_ptr test_server_; EmbeddedTestServerHandle test_server_handle_; }; bool CefTestServerImpl::Start(uint16_t port, bool https_server, cef_test_cert_type_t https_cert_type, CefRefPtr handler) { DCHECK(!context_); context_ = std::make_unique(this, handler); if (context_->Start(port, https_server, https_cert_type)) { const auto& origin = context_->origin().spec(); // Remove the trailing '/' origin_ = origin.substr(0, origin.length() - 1); return true; } context_.reset(); return false; } void CefTestServerImpl::Stop() { DCHECK(context_); context_->Stop(); context_.reset(); } CefString CefTestServerImpl::GetOrigin() { return origin_; } // static CefRefPtr CefTestServer::CreateAndStart( uint16_t port, bool https_server, cef_test_cert_type_t https_cert_type, CefRefPtr handler) { CefRefPtr server(new CefTestServerImpl()); if (server->Start(port, https_server, https_cert_type, handler)) { return server; } return nullptr; }