| // Copyright (c) 2017 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/server_impl.h" |
| |
| #include "libcef/browser/thread_util.h" |
| #include "libcef/common/request_impl.h" |
| #include "libcef/common/task_runner_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/format_macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/server/http_server_request_info.h" |
| #include "net/server/http_server_response_info.h" |
| #include "net/socket/server_socket.h" |
| #include "net/socket/tcp_server_socket.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| |
| #define CEF_CURRENTLY_ON_HT() CurrentlyOnHandlerThread() |
| #define CEF_REQUIRE_HT() DCHECK(CEF_CURRENTLY_ON_HT()) |
| |
| #define CEF_REQUIRE_HT_RETURN(var) \ |
| if (!CEF_CURRENTLY_ON_HT()) { \ |
| NOTREACHED() << "called on invalid thread"; \ |
| return var; \ |
| } |
| |
| #define CEF_REQUIRE_HT_RETURN_VOID() \ |
| if (!CEF_CURRENTLY_ON_HT()) { \ |
| NOTREACHED() << "called on invalid thread"; \ |
| return; \ |
| } |
| |
| #define CEF_POST_TASK_HT(task) task_runner_->PostTask(FROM_HERE, task); |
| |
| namespace { |
| |
| const char kReferrerLowerCase[] = "referer"; |
| |
| // Wrap a string in a unique_ptr to avoid extra copies. |
| std::unique_ptr<std::string> CreateUniqueString(const void* data, |
| size_t data_size) { |
| std::unique_ptr<std::string> ptr; |
| if (data && data_size > 0) { |
| ptr.reset(new std::string(static_cast<const char*>(data), data_size)); |
| } else { |
| ptr.reset(new std::string()); |
| } |
| return ptr; |
| } |
| |
| CefRefPtr<CefRequest> CreateRequest(const std::string& address, |
| const net::HttpServerRequestInfo& info, |
| bool is_websocket) { |
| DCHECK(!address.empty()); |
| DCHECK(!info.method.empty()); |
| DCHECK(!info.path.empty()); |
| |
| CefRefPtr<CefPostData> post_data; |
| if (!info.data.empty()) { |
| post_data = CefPostData::Create(); |
| CefRefPtr<CefPostDataElement> post_element = CefPostDataElement::Create(); |
| post_element->SetToBytes(info.data.size(), info.data.data()); |
| post_data->AddElement(post_element); |
| } |
| |
| std::string referer; |
| |
| CefRequest::HeaderMap header_map; |
| if (!info.headers.empty()) { |
| net::HttpServerRequestInfo::HeadersMap::const_iterator it = |
| info.headers.begin(); |
| for (; it != info.headers.end(); ++it) { |
| // Don't include Referer in the header map. |
| if (base::LowerCaseEqualsASCII(it->first, kReferrerLowerCase)) { |
| referer = it->second; |
| } else { |
| header_map.insert(std::make_pair(it->first, it->second)); |
| } |
| } |
| } |
| |
| CefRefPtr<CefRequestImpl> request = new CefRequestImpl(); |
| request->Set((is_websocket ? "ws://" : "http://") + address + info.path, |
| info.method, post_data, header_map); |
| if (!referer.empty()) |
| request->SetReferrer(referer, REFERRER_POLICY_DEFAULT); |
| request->SetReadOnly(true); |
| return request; |
| } |
| |
| // Callback implementation for WebSocket acceptance. Always executes on the UI |
| // thread so we can avoid multiple execution by clearing the |impl_| reference. |
| class AcceptWebSocketCallback : public CefCallback { |
| public: |
| AcceptWebSocketCallback(CefRefPtr<CefServerImpl> impl, |
| int connection_id, |
| net::HttpServerRequestInfo request_info) |
| : impl_(impl), |
| connection_id_(connection_id), |
| request_info_(request_info) {} |
| |
| ~AcceptWebSocketCallback() override { |
| if (impl_) |
| impl_->ContinueWebSocketRequest(connection_id_, request_info_, false); |
| } |
| |
| void Continue() override { |
| if (!CEF_CURRENTLY_ON_UIT()) { |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&AcceptWebSocketCallback::Continue, this)); |
| return; |
| } |
| if (!impl_) |
| return; |
| impl_->ContinueWebSocketRequest(connection_id_, request_info_, true); |
| impl_ = nullptr; |
| } |
| |
| void Cancel() override { |
| if (!CEF_CURRENTLY_ON_UIT()) { |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&AcceptWebSocketCallback::Cancel, this)); |
| return; |
| } |
| if (!impl_) |
| return; |
| impl_->ContinueWebSocketRequest(connection_id_, request_info_, false); |
| impl_ = nullptr; |
| } |
| |
| private: |
| CefRefPtr<CefServerImpl> impl_; |
| int connection_id_; |
| net::HttpServerRequestInfo request_info_; |
| |
| IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(AcceptWebSocketCallback); |
| DISALLOW_COPY_AND_ASSIGN(AcceptWebSocketCallback); |
| }; |
| |
| } // namespace |
| |
| // CefServer |
| |
| // static |
| void CefServer::CreateServer(const CefString& address, |
| uint16 port, |
| int backlog, |
| CefRefPtr<CefServerHandler> handler) { |
| CefRefPtr<CefServerImpl> server(new CefServerImpl(handler)); |
| server->Start(address, port, backlog); |
| } |
| |
| // CefServerImpl |
| |
| struct CefServerImpl::ConnectionInfo { |
| ConnectionInfo() : is_websocket(false), is_websocket_pending(false) {} |
| |
| // True if this connection is a WebSocket connection. |
| bool is_websocket; |
| bool is_websocket_pending; |
| }; |
| |
| CefServerImpl::CefServerImpl(CefRefPtr<CefServerHandler> handler) |
| : handler_(handler) { |
| DCHECK(handler_); |
| } |
| |
| void CefServerImpl::Start(const std::string& address, |
| uint16 port, |
| int backlog) { |
| DCHECK(!address.empty()); |
| CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefServerImpl::StartOnUIThread, this, |
| address, port, backlog)); |
| } |
| |
| CefRefPtr<CefTaskRunner> CefServerImpl::GetTaskRunner() { |
| if (task_runner_) |
| return new CefTaskRunnerImpl(task_runner_); |
| return nullptr; |
| } |
| |
| void CefServerImpl::Shutdown() { |
| CEF_POST_TASK_HT( |
| base::BindOnce(&CefServerImpl::ShutdownOnHandlerThread, this)); |
| } |
| |
| bool CefServerImpl::IsRunning() { |
| CEF_REQUIRE_HT_RETURN(false); |
| return !!server_.get(); |
| } |
| |
| CefString CefServerImpl::GetAddress() { |
| return address_; |
| } |
| |
| bool CefServerImpl::HasConnection() { |
| CEF_REQUIRE_HT_RETURN(false); |
| return !connection_info_map_.empty(); |
| } |
| |
| bool CefServerImpl::IsValidConnection(int connection_id) { |
| CEF_REQUIRE_HT_RETURN(false); |
| return connection_info_map_.find(connection_id) != connection_info_map_.end(); |
| } |
| |
| void CefServerImpl::SendHttp200Response(int connection_id, |
| const CefString& content_type, |
| const void* data, |
| size_t data_size) { |
| SendHttp200ResponseInternal(connection_id, content_type, |
| CreateUniqueString(data, data_size)); |
| } |
| |
| void CefServerImpl::SendHttp404Response(int connection_id) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp404Response, this, |
| connection_id)); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| if (info->is_websocket) { |
| LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id " |
| << connection_id; |
| return; |
| } |
| |
| server_->Send404(connection_id, MISSING_TRAFFIC_ANNOTATION); |
| server_->Close(connection_id); |
| } |
| |
| void CefServerImpl::SendHttp500Response(int connection_id, |
| const CefString& error_message) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp500Response, this, |
| connection_id, error_message)); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| if (info->is_websocket) { |
| LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id " |
| << connection_id; |
| return; |
| } |
| |
| server_->Send500(connection_id, error_message, MISSING_TRAFFIC_ANNOTATION); |
| server_->Close(connection_id); |
| } |
| |
| void CefServerImpl::SendHttpResponse(int connection_id, |
| int response_code, |
| const CefString& content_type, |
| int64 content_length, |
| const HeaderMap& extra_headers) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttpResponse, this, |
| connection_id, response_code, content_type, |
| content_length, extra_headers)); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| if (info->is_websocket) { |
| LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id " |
| << connection_id; |
| return; |
| } |
| |
| net::HttpServerResponseInfo response( |
| static_cast<net::HttpStatusCode>(response_code)); |
| |
| HeaderMap::const_iterator it = extra_headers.begin(); |
| for (; it != extra_headers.end(); ++it) |
| response.AddHeader(it->first, it->second); |
| |
| response.AddHeader(net::HttpRequestHeaders::kContentType, content_type); |
| if (content_length >= 0) { |
| response.AddHeader( |
| net::HttpRequestHeaders::kContentLength, |
| base::StringPrintf("%" PRIuS, static_cast<size_t>(content_length))); |
| } |
| |
| server_->SendResponse(connection_id, response, MISSING_TRAFFIC_ANNOTATION); |
| if (content_length == 0) { |
| server_->Close(connection_id); |
| } |
| } |
| |
| void CefServerImpl::SendRawData(int connection_id, |
| const void* data, |
| size_t data_size) { |
| if (!data || data_size == 0) |
| return; |
| SendRawDataInternal(connection_id, CreateUniqueString(data, data_size)); |
| } |
| |
| void CefServerImpl::CloseConnection(int connection_id) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT( |
| base::BindOnce(&CefServerImpl::CloseConnection, this, connection_id)); |
| return; |
| } |
| |
| if (ValidateServer() && GetConnectionInfo(connection_id)) { |
| server_->Close(connection_id); |
| } |
| } |
| |
| void CefServerImpl::SendWebSocketMessage(int connection_id, |
| const void* data, |
| size_t data_size) { |
| if (!data || data_size == 0) |
| return; |
| SendWebSocketMessageInternal(connection_id, |
| CreateUniqueString(data, data_size)); |
| } |
| |
| void CefServerImpl::ContinueWebSocketRequest( |
| int connection_id, |
| const net::HttpServerRequestInfo& request_info, |
| bool allow) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::ContinueWebSocketRequest, |
| this, connection_id, request_info, allow)); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| DCHECK(info); |
| if (!info) |
| return; |
| |
| DCHECK(info->is_websocket); |
| DCHECK(info->is_websocket_pending); |
| if (!info->is_websocket || !info->is_websocket_pending) |
| return; |
| |
| info->is_websocket_pending = false; |
| |
| if (allow) { |
| server_->AcceptWebSocket(connection_id, request_info, |
| MISSING_TRAFFIC_ANNOTATION); |
| handler_->OnWebSocketConnected(this, connection_id); |
| } else { |
| server_->Close(connection_id); |
| } |
| } |
| |
| void CefServerImpl::SendHttp200ResponseInternal( |
| int connection_id, |
| const CefString& content_type, |
| std::unique_ptr<std::string> data) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp200ResponseInternal, |
| this, connection_id, content_type, |
| std::move(data))); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| if (info->is_websocket) { |
| LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id " |
| << connection_id; |
| return; |
| } |
| |
| server_->Send200(connection_id, *data, content_type, |
| MISSING_TRAFFIC_ANNOTATION); |
| server_->Close(connection_id); |
| } |
| |
| void CefServerImpl::SendRawDataInternal(int connection_id, |
| std::unique_ptr<std::string> data) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendRawDataInternal, this, |
| connection_id, std::move(data))); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| if (!GetConnectionInfo(connection_id)) |
| return; |
| |
| server_->SendRaw(connection_id, *data, MISSING_TRAFFIC_ANNOTATION); |
| } |
| |
| void CefServerImpl::SendWebSocketMessageInternal( |
| int connection_id, |
| std::unique_ptr<std::string> data) { |
| if (!CEF_CURRENTLY_ON_HT()) { |
| CEF_POST_TASK_HT( |
| base::BindOnce(&CefServerImpl::SendWebSocketMessageInternal, this, |
| connection_id, std::move(data))); |
| return; |
| } |
| |
| if (!ValidateServer()) |
| return; |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| if (!info->is_websocket || info->is_websocket_pending) { |
| LOG(ERROR) << "Invalid attempt to send WebSocket message for connection_id " |
| << connection_id; |
| return; |
| } |
| |
| server_->SendOverWebSocket(connection_id, *data, MISSING_TRAFFIC_ANNOTATION); |
| } |
| |
| void CefServerImpl::OnConnect(int connection_id) { |
| CEF_REQUIRE_HT(); |
| |
| CreateConnectionInfo(connection_id); |
| handler_->OnClientConnected(this, connection_id); |
| } |
| |
| void CefServerImpl::OnHttpRequest( |
| int connection_id, |
| const net::HttpServerRequestInfo& request_info) { |
| CEF_REQUIRE_HT(); |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| DCHECK(info); |
| if (!info) |
| return; |
| |
| DCHECK(!info->is_websocket); |
| |
| handler_->OnHttpRequest(this, connection_id, request_info.peer.ToString(), |
| CreateRequest(address_, request_info, false)); |
| } |
| |
| void CefServerImpl::OnWebSocketRequest( |
| int connection_id, |
| const net::HttpServerRequestInfo& request_info) { |
| CEF_REQUIRE_HT(); |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| DCHECK(info); |
| if (!info) |
| return; |
| |
| DCHECK(!info->is_websocket); |
| info->is_websocket = true; |
| info->is_websocket_pending = true; |
| |
| // Will eventually result in a call to ContinueWebSocketRequest. |
| CefRefPtr<CefCallback> callback = |
| new AcceptWebSocketCallback(this, connection_id, request_info); |
| handler_->OnWebSocketRequest( |
| this, connection_id, request_info.peer.ToString(), |
| CreateRequest(address_, request_info, true), callback); |
| } |
| |
| void CefServerImpl::OnWebSocketMessage(int connection_id, std::string data) { |
| CEF_REQUIRE_HT(); |
| |
| ConnectionInfo* info = GetConnectionInfo(connection_id); |
| if (!info) |
| return; |
| |
| DCHECK(info->is_websocket); |
| DCHECK(!info->is_websocket_pending); |
| |
| handler_->OnWebSocketMessage(this, connection_id, data.data(), data.size()); |
| } |
| |
| void CefServerImpl::OnClose(int connection_id) { |
| CEF_REQUIRE_HT(); |
| |
| RemoveConnectionInfo(connection_id); |
| handler_->OnClientDisconnected(this, connection_id); |
| } |
| |
| void CefServerImpl::StartOnUIThread(const std::string& address, |
| uint16 port, |
| int backlog) { |
| CEF_REQUIRE_UIT(); |
| DCHECK(!thread_); |
| |
| std::unique_ptr<base::Thread> thread( |
| new base::Thread(base::StringPrintf("%s:%d", address.c_str(), port))); |
| base::Thread::Options options; |
| options.message_pump_type = base::MessagePumpType::IO; |
| if (thread->StartWithOptions(options)) { |
| // Add a reference that will be released in ShutdownOnUIThread(). |
| AddRef(); |
| |
| thread_ = std::move(thread); |
| task_runner_ = thread_->task_runner(); |
| |
| CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::StartOnHandlerThread, this, |
| address, port, backlog)); |
| } |
| } |
| |
| void CefServerImpl::StartOnHandlerThread(const std::string& address, |
| uint16 port, |
| int backlog) { |
| CEF_REQUIRE_HT(); |
| |
| std::unique_ptr<net::ServerSocket> socket( |
| new net::TCPServerSocket(nullptr, net::NetLogSource())); |
| if (socket->ListenWithAddressAndPort(address, port, backlog) == net::OK) { |
| server_.reset(new net::HttpServer(std::move(socket), this)); |
| |
| net::IPEndPoint address; |
| if (server_->GetLocalAddress(&address) == net::OK) |
| address_ = address.ToString(); |
| } |
| |
| handler_->OnServerCreated(this); |
| |
| if (!server_) { |
| // Server failed to start. |
| handler_->OnServerDestroyed(this); |
| |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&CefServerImpl::ShutdownOnUIThread, this)); |
| } |
| } |
| |
| void CefServerImpl::ShutdownOnHandlerThread() { |
| CEF_REQUIRE_HT(); |
| |
| if (server_) { |
| // Stop the server. |
| server_.reset(); |
| |
| if (!connection_info_map_.empty()) { |
| // Clear |connection_info_map_| first so any calls from |
| // OnClientDisconnected will fail as expected. |
| ConnectionInfoMap temp_map; |
| temp_map.swap(connection_info_map_); |
| |
| // OnClose won't be called for clients that are connected when the server |
| // shuts down, so send the disconnected notification here. |
| ConnectionInfoMap::const_iterator it = temp_map.begin(); |
| for (; it != temp_map.end(); ++it) { |
| handler_->OnClientDisconnected(this, it->first); |
| } |
| } |
| |
| handler_->OnServerDestroyed(this); |
| } |
| |
| CEF_POST_TASK(CEF_UIT, |
| base::BindOnce(&CefServerImpl::ShutdownOnUIThread, this)); |
| } |
| |
| void CefServerImpl::ShutdownOnUIThread() { |
| CEF_REQUIRE_UIT(); |
| |
| handler_ = nullptr; |
| |
| if (thread_) { |
| // Stop the handler thread as a background task so the UI thread isn't |
| // blocked. |
| CEF_POST_BACKGROUND_TASK(BindOnce( |
| [](std::unique_ptr<base::Thread> thread) { |
| // Calling PlatformThread::Join() on the UI thread is otherwise |
| // disallowed. |
| base::ScopedAllowBaseSyncPrimitivesForTesting |
| scoped_allow_sync_primitives; |
| thread.reset(); |
| }, |
| std::move(thread_))); |
| |
| // Release the reference that was added in StartupOnUIThread(). |
| Release(); |
| } |
| } |
| |
| bool CefServerImpl::ValidateServer() const { |
| CEF_REQUIRE_HT(); |
| if (!server_) { |
| LOG(ERROR) << "Server is not running"; |
| return false; |
| } |
| return true; |
| } |
| |
| CefServerImpl::ConnectionInfo* CefServerImpl::CreateConnectionInfo( |
| int connection_id) { |
| CEF_REQUIRE_HT(); |
| |
| #if DCHECK_IS_ON() |
| ConnectionInfoMap::const_iterator it = |
| connection_info_map_.find(connection_id); |
| DCHECK(it == connection_info_map_.end()); |
| #endif |
| |
| ConnectionInfo* info = new ConnectionInfo(); |
| connection_info_map_.insert( |
| std::make_pair(connection_id, base::WrapUnique(info))); |
| return info; |
| } |
| |
| CefServerImpl::ConnectionInfo* CefServerImpl::GetConnectionInfo( |
| int connection_id) const { |
| CEF_REQUIRE_HT(); |
| ConnectionInfoMap::const_iterator it = |
| connection_info_map_.find(connection_id); |
| if (it != connection_info_map_.end()) |
| return it->second.get(); |
| |
| LOG(ERROR) << "Invalid connection_id " << connection_id; |
| return nullptr; |
| } |
| |
| void CefServerImpl::RemoveConnectionInfo(int connection_id) { |
| CEF_REQUIRE_HT(); |
| ConnectionInfoMap::iterator it = connection_info_map_.find(connection_id); |
| DCHECK(it != connection_info_map_.end()); |
| if (it != connection_info_map_.end()) |
| connection_info_map_.erase(it); |
| } |
| |
| bool CefServerImpl::CurrentlyOnHandlerThread() const { |
| return task_runner_ && task_runner_->BelongsToCurrentThread(); |
| } |