// Copyright (c) 2023 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_dll/wrapper/cef_message_router_utils.h" #include #include "include/cef_shared_process_message_builder.h" namespace cef_message_router_utils { namespace { constexpr int kNoError = 0; constexpr size_t kContextId = 0; constexpr size_t kRequestId = 1; constexpr size_t kRendererPayload = 2; constexpr size_t kIsSuccess = 2; constexpr size_t kBrowserPayload = 3; constexpr size_t kIsPersistent = 3; struct BrowserMsgHeader { int context_id; int request_id; bool is_binary; }; static_assert( std::is_trivially_copyable_v, "Copying non-trivially-copyable object across memory spaces is dangerous"); struct RendererMsgHeader { int context_id; int request_id; bool is_persistent; bool is_binary; }; static_assert( std::is_trivially_copyable_v, "Copying non-trivially-copyable object across memory spaces is dangerous"); // // This is a workaround for handling empty CefBinaryValues, as it's not possible // to create an empty one directly. We use this empty struct as a tag to invoke // the SetNull function within the BuildBrowserListMsg and BuildRendererListMsg // functions. // struct Empty {}; size_t GetByteLength(const CefString& value) { return value.size() * sizeof(CefString::char_type); } size_t GetByteLength(const CefRefPtr& value) { return value->GetArrayBufferByteLength(); } const CefString& GetListRepresentation(const CefString& value) { return value; } CefRefPtr GetListRepresentation( const CefRefPtr& value) { return CefBinaryValue::Create(value->GetArrayBufferData(), value->GetArrayBufferByteLength()); } template size_t GetMessageSize(const T& value) { return sizeof(Header) + GetByteLength(value); } template void CopyIntoMemory(void* memory, const void* data, size_t bytes) { if (bytes > 0) { void* dest = static_cast(memory) + sizeof(Header); memcpy(dest, data, bytes); } } template void CopyIntoMemory(void* memory, const CefRefPtr& value) { CopyIntoMemory
(memory, value->GetArrayBufferData(), value->GetArrayBufferByteLength()); } template void CopyIntoMemory(void* memory, const CefString& value) { const size_t bytes = GetByteLength(value); CopyIntoMemory
(memory, value.c_str(), bytes); } template CefString GetStringFromMemory(const void* memory, size_t size) { const size_t bytes = size - sizeof(Header); const size_t string_len = bytes / sizeof(CefString::char_type); const void* string_data = static_cast(memory) + sizeof(Header); const CefString::char_type* src = static_cast(string_data); CefString result; result.FromString(src, string_len, /*copy=*/true); return result; } template constexpr bool IsCefString() { return std::is_same_v, CefString>; } template constexpr bool IsEmpty() { return std::is_same_v, Empty>; } template CefRefPtr BuildBrowserListMsg(const CefString& name, int context_id, int request_id, const ResponseType& response) { auto message = CefProcessMessage::Create(name); CefRefPtr args = message->GetArgumentList(); args->SetInt(kContextId, context_id); args->SetInt(kRequestId, request_id); args->SetBool(kIsSuccess, true); if constexpr (IsCefString()) { args->SetString(kBrowserPayload, response); } else if constexpr (IsEmpty()) { args->SetNull(kBrowserPayload); } else { args->SetBinary(kBrowserPayload, response); } return message; } class EmptyResponseBuilder final : public BrowserResponseBuilder { public: explicit EmptyResponseBuilder(const std::string& name) : name_(name) {} EmptyResponseBuilder(const EmptyResponseBuilder&) = delete; EmptyResponseBuilder& operator=(const EmptyResponseBuilder&) = delete; CefRefPtr Build(int context_id, int request_id) override { return BuildBrowserListMsg(name_, context_id, request_id, Empty{}); } private: const CefString name_; IMPLEMENT_REFCOUNTING(EmptyResponseBuilder); }; class BinaryResponseBuilder final : public BrowserResponseBuilder { public: BinaryResponseBuilder(const std::string& name, const void* data, size_t size) : name_(name), value_(CefBinaryValue::Create(data, size)) {} BinaryResponseBuilder(const BinaryResponseBuilder&) = delete; BinaryResponseBuilder& operator=(const BinaryResponseBuilder&) = delete; CefRefPtr Build(int context_id, int request_id) override { return BuildBrowserListMsg(name_, context_id, request_id, value_); } private: const CefString name_; const CefRefPtr value_; IMPLEMENT_REFCOUNTING(BinaryResponseBuilder); }; class StringResponseBuilder final : public BrowserResponseBuilder { public: StringResponseBuilder(const std::string& name, const CefString& value) : name_(name), value_(value) {} StringResponseBuilder(const StringResponseBuilder&) = delete; StringResponseBuilder& operator=(const StringResponseBuilder&) = delete; CefRefPtr Build(int context_id, int request_id) override { return BuildBrowserListMsg(name_, context_id, request_id, value_); } private: const CefString name_; const CefString value_; IMPLEMENT_REFCOUNTING(StringResponseBuilder); }; // SharedProcessMessageResponseBuilder class SPMResponseBuilder final : public BrowserResponseBuilder { public: SPMResponseBuilder(const SPMResponseBuilder&) = delete; SPMResponseBuilder& operator=(const SPMResponseBuilder&) = delete; static CefRefPtr Create(const std::string& name, const void* data, size_t size) { const size_t message_size = sizeof(BrowserMsgHeader) + size; auto builder = CefSharedProcessMessageBuilder::Create(name, message_size); if (!builder->IsValid()) { LOG(ERROR) << "Failed to allocate shared memory region of size " << message_size; return new BinaryResponseBuilder(name, data, size); } CopyIntoMemory(builder->Memory(), data, size); return new SPMResponseBuilder(builder, /*is_binary=*/true); } static CefRefPtr Create(const std::string& name, const CefString& value) { const size_t message_size = GetMessageSize(value); auto builder = CefSharedProcessMessageBuilder::Create(name, message_size); if (!builder->IsValid()) { LOG(ERROR) << "Failed to allocate shared memory region of size " << message_size; return new StringResponseBuilder(name, value); } CopyIntoMemory(builder->Memory(), value); return new SPMResponseBuilder(builder, /*is_binary=*/false); } CefRefPtr Build(int context_id, int request_id) override { auto header = static_cast(builder_->Memory()); header->context_id = context_id; header->request_id = request_id; header->is_binary = is_binary_; return builder_->Build(); } private: explicit SPMResponseBuilder( const CefRefPtr& builder, bool is_binary) : builder_(builder), is_binary_(is_binary) {} CefRefPtr builder_; const bool is_binary_; IMPLEMENT_REFCOUNTING(SPMResponseBuilder); }; class EmptyBinaryBuffer final : public CefBinaryBuffer { public: EmptyBinaryBuffer() = default; EmptyBinaryBuffer(const EmptyBinaryBuffer&) = delete; EmptyBinaryBuffer& operator=(const EmptyBinaryBuffer&) = delete; const void* GetData() const override { return nullptr; } void* GetData() override { return nullptr; } size_t GetSize() const override { return 0; } private: IMPLEMENT_REFCOUNTING(EmptyBinaryBuffer); }; class BinaryValueBuffer final : public CefBinaryBuffer { public: BinaryValueBuffer(CefRefPtr message, CefRefPtr value) : message_(std::move(message)), value_(std::move(value)) {} BinaryValueBuffer(const BinaryValueBuffer&) = delete; BinaryValueBuffer& operator=(const BinaryValueBuffer&) = delete; const void* GetData() const override { return value_->GetRawData(); } void* GetData() override { // This is not UB as long as underlying storage is vector return const_cast(value_->GetRawData()); } size_t GetSize() const override { return value_->GetSize(); } private: const CefRefPtr message_; const CefRefPtr value_; IMPLEMENT_REFCOUNTING(BinaryValueBuffer); }; class SharedMemoryRegionBuffer final : public CefBinaryBuffer { public: SharedMemoryRegionBuffer(const CefRefPtr& region, size_t offset) : region_(region), data_(static_cast(region->Memory()) + offset), size_(region->Size() - offset) {} SharedMemoryRegionBuffer(const SharedMemoryRegionBuffer&) = delete; SharedMemoryRegionBuffer& operator=(const SharedMemoryRegionBuffer&) = delete; const void* GetData() const override { return data_; } void* GetData() override { return data_; } size_t GetSize() const override { return size_; } private: const CefRefPtr region_; void* const data_; const size_t size_; IMPLEMENT_REFCOUNTING(SharedMemoryRegionBuffer); }; template CefRefPtr BuildRendererListMsg(const std::string& name, int context_id, int request_id, const RequestType& request, bool persistent) { auto message = CefProcessMessage::Create(name); CefRefPtr args = message->GetArgumentList(); args->SetInt(kContextId, context_id); args->SetInt(kRequestId, request_id); if constexpr (IsCefString()) { args->SetString(kRendererPayload, request); } else if constexpr (IsEmpty()) { args->SetNull(kRendererPayload); } else { args->SetBinary(kRendererPayload, request); } args->SetBool(kIsPersistent, persistent); return message; } template CefRefPtr BuildRendererSharedMsg(const std::string& name, int context_id, int request_id, const RequestType& request, bool persistent) { const size_t message_size = GetMessageSize(request); auto builder = CefSharedProcessMessageBuilder::Create(name, message_size); if (!builder->IsValid()) { LOG(ERROR) << "Failed to allocate shared memory region of size " << message_size; return BuildRendererListMsg(name, context_id, request_id, GetListRepresentation(request), persistent); } auto header = static_cast(builder->Memory()); header->context_id = context_id; header->request_id = request_id; header->is_persistent = persistent; header->is_binary = !IsCefString(); CopyIntoMemory(builder->Memory(), request); return builder->Build(); } CefRefPtr BuildRendererMsg(size_t threshold, const std::string& name, int context_id, int request_id, const CefString& request, bool persistent) { if (GetByteLength(request) < threshold) { return BuildRendererListMsg(name, context_id, request_id, request, persistent); } return BuildRendererSharedMsg(name, context_id, request_id, request, persistent); } } // namespace CefRefPtr CreateBrowserResponseBuilder( size_t threshold, const std::string& name, const CefString& response) { if (GetByteLength(response) < threshold) { return new StringResponseBuilder(name, response); } return SPMResponseBuilder::Create(name, response); } CefRefPtr CreateBrowserResponseBuilder( size_t threshold, const std::string& name, const void* data, size_t size) { if (size == 0) { return new EmptyResponseBuilder(name); } if (size < threshold) { return new BinaryResponseBuilder(name, data, size); } return SPMResponseBuilder::Create(name, data, size); } CefRefPtr BuildRendererMsg( size_t threshold, const std::string& name, int context_id, int request_id, const CefRefPtr& request, bool persistent) { if (request->IsString()) { return BuildRendererMsg(threshold, name, context_id, request_id, request->GetStringValue(), persistent); } const auto size = request->GetArrayBufferByteLength(); if (size == 0) { return BuildRendererListMsg(name, context_id, request_id, Empty{}, persistent); } if (size < threshold) { return BuildRendererListMsg(name, context_id, request_id, GetListRepresentation(request), persistent); } return BuildRendererSharedMsg(name, context_id, request_id, request, persistent); } BrowserMessage ParseBrowserMessage( const CefRefPtr& message) { if (auto args = message->GetArgumentList()) { DCHECK_GT(args->GetSize(), 3U); const int context_id = args->GetInt(kContextId); const int request_id = args->GetInt(kRequestId); const bool is_success = args->GetBool(kIsSuccess); if (is_success) { DCHECK_EQ(args->GetSize(), 4U); const auto payload_type = args->GetType(kBrowserPayload); if (payload_type == CefValueType::VTYPE_STRING) { return {context_id, request_id, is_success, kNoError, args->GetString(kBrowserPayload)}; } if (payload_type == CefValueType::VTYPE_BINARY) { return { context_id, request_id, is_success, kNoError, new BinaryValueBuffer(message, args->GetBinary(kBrowserPayload))}; } DCHECK(payload_type == CefValueType::VTYPE_NULL); return {context_id, request_id, is_success, kNoError, new EmptyBinaryBuffer()}; } DCHECK_EQ(args->GetSize(), 5U); return {context_id, request_id, is_success, args->GetInt(3), args->GetString(4)}; } const auto region = message->GetSharedMemoryRegion(); if (region && region->IsValid()) { DCHECK_GE(region->Size(), sizeof(BrowserMsgHeader)); auto header = static_cast(region->Memory()); if (header->is_binary) { return {header->context_id, header->request_id, true, kNoError, new SharedMemoryRegionBuffer(region, sizeof(BrowserMsgHeader))}; } return {header->context_id, header->request_id, true, kNoError, GetStringFromMemory(region->Memory(), region->Size())}; } NOTREACHED(); return {}; } RendererMessage ParseRendererMessage( const CefRefPtr& message) { if (auto args = message->GetArgumentList()) { DCHECK_EQ(args->GetSize(), 4U); const int context_id = args->GetInt(kContextId); const int request_id = args->GetInt(kRequestId); const auto payload_type = args->GetType(kRendererPayload); const bool persistent = args->GetBool(kIsPersistent); if (payload_type == CefValueType::VTYPE_STRING) { return {context_id, request_id, persistent, args->GetString(kRendererPayload)}; } if (payload_type == CefValueType::VTYPE_BINARY) { return { context_id, request_id, persistent, new BinaryValueBuffer(message, args->GetBinary(kRendererPayload))}; } DCHECK(payload_type == CefValueType::VTYPE_NULL); return {context_id, request_id, persistent, new EmptyBinaryBuffer()}; } const auto region = message->GetSharedMemoryRegion(); if (region && region->IsValid()) { DCHECK_GE(region->Size(), sizeof(RendererMsgHeader)); auto header = static_cast(region->Memory()); if (header->is_binary) { return {header->context_id, header->request_id, header->is_persistent, new SharedMemoryRegionBuffer(region, sizeof(RendererMsgHeader))}; } return { header->context_id, header->request_id, header->is_persistent, GetStringFromMemory(region->Memory(), region->Size()), }; } NOTREACHED(); return {}; } } // namespace cef_message_router_utils