Skip to content
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ add_library(
src/dullahan_callback_manager.cpp
src/dullahan_callback_manager.h
src/dullahan_debug.h
src/dullahan_embed_scheme.cpp
src/dullahan_embed_scheme.h
src/dullahan_impl.cpp
src/dullahan_impl.h
src/dullahan_version.h
Expand Down
6 changes: 3 additions & 3 deletions examples/opengl-example/src/opengl-example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ bool openglExample::init()

mDullahan->setOnPageChangedCallback(std::bind(&openglExample::onPageChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
mDullahan->setOnRequestExitCallback(std::bind(&openglExample::onRequestExitCallback, this));
mDullahan->setOnJStoCPPMsgCallback(std::bind(&openglExample::onJStoCPPMsgCallback, this, std::placeholders::_1, std::placeholders::_2));
mDullahan->setOnJStoCPPMsgCallback(std::bind(&openglExample::onJStoCPPMsgCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

mDullahan->navigate(mHomeUrl);
}
Expand Down Expand Up @@ -482,9 +482,9 @@ void openglExample::onRequestExitCallback()
glfwSetWindowShouldClose(mWindow, GLFW_TRUE);
}

std::string openglExample::onJStoCPPMsgCallback(const std::string id, const std::string msg)
std::string openglExample::onJStoCPPMsgCallback(const std::string id, const std::string msg, const std::string frame_url)
{
std::cout << "Received message with ID: " << id << " from JavaScript: " << msg << std::endl;
std::cout << "Received message with ID: " << id << " from JavaScript: " << msg << " (origin: " << frame_url << ")" << std::endl;
return "Message received loud and clear by C++!";
}

Expand Down
2 changes: 1 addition & 1 deletion examples/opengl-example/src/opengl-example.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class openglExample
// callbacks
void onPageChanged(const unsigned char* pixels, int x, int y, const int width, const int height);
void onRequestExitCallback();
std::string onJStoCPPMsgCallback(const std::string id, const std::string msg);
std::string onJStoCPPMsgCallback(const std::string id, const std::string msg, const std::string frame_url);

private:
GLFWwindow* mWindow;
Expand Down
22 changes: 21 additions & 1 deletion src/dullahan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,26 @@ std::vector<std::string>& dullahan::getCustomSchemes()
return mImpl->getCustomSchemes();
}

void dullahan::setEmbedSchemeRoot(const std::string& root_dir)
{
mImpl->setEmbedSchemeRoot(root_dir);
}

const std::string& dullahan::getEmbedSchemeRoot()
{
return mImpl->getEmbedSchemeRoot();
}

void dullahan::setEmbedRegistry(const std::vector<std::string>& allowed_paths)
{
mImpl->setEmbedRegistry(allowed_paths);
}

const std::vector<std::string>& dullahan::getEmbedRegistry()
{
return mImpl->getEmbedRegistry();
}

void dullahan::setOnAddressChangeCallback(std::function<void(const std::string url)> callback)
{
mImpl->getCallbackManager()->setOnAddressChangeCallback(callback);
Expand Down Expand Up @@ -447,7 +467,7 @@ void dullahan::setOnJSBeforeUnloadCallback(std::function<bool()> callback)
mImpl->getCallbackManager()->setOnJSBeforeUnloadCallback(callback);
}

void dullahan::setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg)> callback)
void dullahan::setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg, const std::string frame_url)> callback)
{
mImpl->getCallbackManager()->setOnJStoCPPMsgCallback(callback);
}
14 changes: 12 additions & 2 deletions src/dullahan.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,14 @@ class dullahan
void setCustomSchemes(std::vector<std::string> custom_schemes);
std::vector<std::string>& getCustomSchemes();

// The embed:// scheme is intended to host trusted local resources bundled by the host application.
// Resources are mapped as: embed://<host>/<path> => <root>/<host>/<path>
// Only paths that appear in the registry (relative to <root>, using forward slashes) are served.
void setEmbedSchemeRoot(const std::string& root_dir);
const std::string& getEmbedSchemeRoot();
void setEmbedRegistry(const std::vector<std::string>& allowed_paths);
const std::vector<std::string>& getEmbedRegistry();

//////////// callback setters ////////////
// URL changes - e.g. redirect
void setOnAddressChangeCallback(std::function<void(const std::string url)> callback);
Expand Down Expand Up @@ -397,8 +405,10 @@ class dullahan
// JS before unload callback (alert)
void setOnJSBeforeUnloadCallback(std::function<bool()> callback);

// Message from JS to CPP
void setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg)> callback);
// Message from JS to CPP.
// The frame_url argument identifies the origin of the calling frame,
// allowing consumers to filter messages by origin if needed.
void setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg, const std::string frame_url)> callback);

private:
std::unique_ptr <dullahan_impl> mImpl;
Expand Down
101 changes: 100 additions & 1 deletion src/dullahan_browser_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,16 @@ bool dullahan_browser_client::OnProcessMessageReceived(CefRefPtr<CefBrowser> bro
CefRefPtr<CefListValue> args = message->GetArgumentList();
if (args)
{
// Args: [0] id, [1] json, [2] frame_url (added for embed:// origin checks).
std::string frame_url;
if (args->GetSize() >= 3 && args->GetType(2) == VTYPE_STRING)
{
frame_url = args->GetString(2).ToString();
}
//std::cout << ">>> Received JSONtoCPP_MSG from render process: " << args->GetString(0).ToString() << std::endl;
mParent->getCallbackManager()->onJStoCPPMsgCallback(args->GetString(0).ToString(), args->GetString(1).ToString());
mParent->getCallbackManager()->onJStoCPPMsgCallback(args->GetString(0).ToString(),
args->GetString(1).ToString(),
frame_url);
}

// Indicate we processed this message and it should not be sent to other handlers
Expand Down Expand Up @@ -305,6 +313,25 @@ bool dullahan_browser_client::OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
return static_cast<char>(tolower(c));
});

// Block any user-initiated navigation or redirect into embed://.
// Only the host process may load embed:// URLs directly.
static const std::string embed_scheme("embed://");
if (url.compare(0, embed_scheme.size(), embed_scheme) == 0)
{
if (user_gesture || isRedirect)
{
return true; // cancel navigation
}
}

// Track whether the main frame is currently loading embed:// content so
// OnBeforeResourceLoad has a reliable trust-context signal that doesn't
// depend on the racy frame URL update visible on the IO thread.
if (frame && frame->IsMain())
{
mEmbedScoped = (url.compare(0, embed_scheme.size(), embed_scheme) == 0);
}

std::vector<std::string>::iterator iter = mParent->getCustomSchemes().begin();
while (iter != mParent->getCustomSchemes().end())
{
Expand Down Expand Up @@ -355,6 +382,78 @@ bool dullahan_browser_client::GetAuthCredentials(CefRefPtr<CefBrowser> browser,
}
}

// CefRequestHandler override - route resource requests through this same object.
CefRefPtr<CefResourceRequestHandler> dullahan_browser_client::GetResourceRequestHandler(
CefRefPtr<CefBrowser> /*browser*/,
CefRefPtr<CefFrame> /*frame*/,
CefRefPtr<CefRequest> /*request*/,
bool /*is_navigation*/,
bool /*is_download*/,
const CefString& /*request_initiator*/,
bool& /*disable_default_handling*/)
{
return this;
}

// CefResourceRequestHandler override - recursive inheritance for embed://.
//
// A browser whose top-level document is an embed:// page must not make sub-
// resource requests to any other origin.
// Conversely, a browser NOT running embed:// content must not fetch embed://
// resources.
// Top-level main-frame navigations are excluded here, because those are gated by
// OnBeforeBrowse, so the initial embed:// document load is not blocked.
cef_return_value_t dullahan_browser_client::OnBeforeResourceLoad(
CefRefPtr<CefBrowser> /*browser*/,
CefRefPtr<CefFrame> /*frame*/,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> /*callback*/)
{
// Skip the main-frame document itself; OnBeforeBrowse governs it.
if (request->GetResourceType() == RT_MAIN_FRAME)
{
return RV_CONTINUE;
}

static const std::string embed_prefix("embed://");
static const std::string data_prefix("data:");

auto starts_with = [](const std::string& s, const std::string& prefix)
{
if (s.size() < prefix.size()) return false;
for (size_t i = 0; i < prefix.size(); ++i)
{
if (tolower(static_cast<unsigned char>(s[i])) != prefix[i]) return false;
}
return true;
};

// Trust context is set by OnBeforeBrowse when the main frame navigates.
// OnBeforeBrowse is guaranteed to fire before any sub-resource of the new
// page, so this is not subject to the IO-thread staleness of frame URLs.
const bool embed_context = mEmbedScoped.load(std::memory_order_relaxed);

std::string req_url = request->GetURL();
const bool req_is_embed = starts_with(req_url, embed_prefix);
const bool req_is_data = starts_with(req_url, data_prefix);

if (embed_context)
{
// embed:// documents may only load embed:// or data: resources.
if (!req_is_embed && !req_is_data)
{
return RV_CANCEL;
}
}
else if (req_is_embed)
{
// Non-embed documents may not touch embed:// resources at all.
return RV_CANCEL;
}

return RV_CONTINUE;
}

// CefDownloadHandler overrides
bool dullahan_browser_client::OnBeforeDownload(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDownloadItem> download_item,
Expand Down
14 changes: 14 additions & 0 deletions src/dullahan_browser_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#ifndef _DULLAHAN_BROWSER_CLIENT
#define _DULLAHAN_BROWSER_CLIENT

#include <atomic>
#include <list>

#include "cef_client.h"
Expand All @@ -40,6 +41,7 @@ class dullahan_browser_client :
public CefDisplayHandler,
public CefLoadHandler,
public CefRequestHandler,
public CefResourceRequestHandler,
public CefDownloadHandler,
public CefDialogHandler,
public CefJSDialogHandler
Expand Down Expand Up @@ -126,6 +128,16 @@ class dullahan_browser_client :
const CefString& host, int port, const CefString& realm,
const CefString& scheme, CefRefPtr<CefAuthCallback> callback) override;

// CefRequestHandler -> CefResourceRequestHandler routing
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request, bool is_navigation,
bool is_download, const CefString& request_initiator,
bool& disable_default_handling) override;

// CefResourceRequestHandler override - enforces recursive inheritance for the embed:// scheme
cef_return_value_t OnBeforeResourceLoad(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request, CefRefPtr<CefCallback> callback) override;

// CefDownloadHandler overrides
CefRefPtr<CefDownloadHandler> GetDownloadHandler() override
{
Expand Down Expand Up @@ -177,6 +189,8 @@ class dullahan_browser_client :
typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
BrowserList mBrowserList;

std::atomic<bool> mEmbedScoped{false};

public:
IMPLEMENT_REFCOUNTING(dullahan_browser_client);
};
Expand Down
6 changes: 3 additions & 3 deletions src/dullahan_callback_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,16 +293,16 @@ bool dullahan_callback_manager::onJSBeforeUnloadCallback()
}

void dullahan_callback_manager::setOnJStoCPPMsgCallback(
std::function<std::string(const std::string id, const std::string msg)> callback)
std::function<std::string(const std::string id, const std::string msg, const std::string frame_url)> callback)
{
mOnJStoCPPMsgCallbackFunc = callback;
}

std::string dullahan_callback_manager::onJStoCPPMsgCallback(const std::string id, const std::string msg)
std::string dullahan_callback_manager::onJStoCPPMsgCallback(const std::string id, const std::string msg, const std::string frame_url)
{
if (mOnJStoCPPMsgCallbackFunc)
{
return mOnJStoCPPMsgCallbackFunc(id, msg);
return mOnJStoCPPMsgCallbackFunc(id, msg, frame_url);
}

return std::string();
Expand Down
6 changes: 3 additions & 3 deletions src/dullahan_callback_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ class dullahan_callback_manager
void setOnJSBeforeUnloadCallback(std::function<bool()> callback);
bool onJSBeforeUnloadCallback();

void setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg)> callback);
std::string onJStoCPPMsgCallback(const std::string id, const std::string msg);
void setOnJStoCPPMsgCallback(std::function<std::string(const std::string id, const std::string msg, const std::string frame_url)> callback);
std::string onJStoCPPMsgCallback(const std::string id, const std::string msg, const std::string frame_url);

private:
std::function<void(const std::string)> mOnAddressChangeCallbackFunc;
Expand All @@ -114,7 +114,7 @@ class dullahan_callback_manager
std::function<const std::vector<std::string>(dullahan::EFileDialogType, const std::string, const std::string, const std::string, bool&)> mOnFileDialogCallbackFunc;
std::function<bool(const std::string, const std::string, const std::string)> mOnJSDialogCallbackFunc;
std::function<bool()> mOnJSBeforeUnloadCallbackFunc;
std::function<std::string(const std::string id, const std::string)> mOnJStoCPPMsgCallbackFunc;
std::function<std::string(const std::string id, const std::string msg, const std::string frame_url)> mOnJStoCPPMsgCallbackFunc;
};

#endif //_DULLAHAN_CALLBACK_MANAGER
Loading
Loading