diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index ff231909d2..37278db4de 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -23,6 +23,7 @@ #include "Wrapper\Browser.h" #include "..\CefSharp.Core.Runtime\Internals\Messaging\Messages.h" #include "..\CefSharp.Core.Runtime\Internals\Serialization\Primitives.h" +#include using namespace System; using namespace System::Diagnostics; @@ -87,7 +88,7 @@ namespace CefSharp //Using LegacyBinding with multiple ChromiumWebBrowser instances that share the same //render process and using LegacyBinding will cause problems for the limited caching implementation //that exists at the moment, for now we'll remove an object if already exists, same behaviour - //as the new binding method. + //as the new binding method. //TODO: This should be removed when https://github.com/cefsharp/CefSharp/issues/2306 //Is complete as objects will be stored at the browser level if (_javascriptObjects->ContainsKey(obj->JavascriptName)) @@ -98,16 +99,33 @@ namespace CefSharp } } } + } - _jsBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); + if (extraInfo->HasKey("JavascriptBindingApiEnabled")) + { + wrapper->JavascriptBindingApiEnabled = extraInfo->GetBool("JavascriptBindingApiEnabled"); + } + + if (extraInfo->HasKey("JavascriptBindingApiHasAllowOrigins")) + { + wrapper->JavascriptBindingApiHasAllowOrigins = extraInfo->GetBool("JavascriptBindingApiHasAllowOrigins"); - if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) + if (wrapper->JavascriptBindingApiHasAllowOrigins) { - //TODO: Create constant for these and legacy binding strings above - _jsBindingPropertyName = extraInfo->GetString("JsBindingPropertyName"); - _jsBindingPropertyNameCamelCase = extraInfo->GetString("JsBindingPropertyNameCamelCase"); + auto allowOrigins = extraInfo->GetList("JavascriptBindingApiAllowOrigins"); + if (allowOrigins.get() && allowOrigins->IsValid()) + { + wrapper->JavascriptBindingApiAllowOrigins = allowOrigins->Copy(); + } } } + + if (extraInfo->HasKey("JsBindingPropertyName") || extraInfo->HasKey("JsBindingPropertyNameCamelCase")) + { + //TODO: Create constant for these and legacy binding strings above + _jsBindingPropertyName = extraInfo->GetString("JsBindingPropertyName"); + _jsBindingPropertyNameCamelCase = extraInfo->GetString("JsBindingPropertyNameCamelCase"); + } } void CefAppUnmanagedWrapper::OnBrowserDestroyed(CefRefPtr browser) @@ -147,11 +165,12 @@ namespace CefSharp } } - if (_jsBindingApiEnabled) + auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); + + if (browserWrapper != nullptr && browserWrapper->JavascriptBindingApiEnabled && IsJavascriptBindingApiAllowed(frame)) { //TODO: Look at adding some sort of javascript mapping layer to reduce the code duplication auto global = context->GetGlobal(); - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier()); auto processId = System::Diagnostics::Process::GetCurrentProcess()->Id; //TODO: JSB: Split functions into their own classes @@ -328,6 +347,58 @@ namespace CefSharp return rootObject; } + bool CefAppUnmanagedWrapper::IsJavascriptBindingApiAllowed(CefRefPtr frame) + { + auto browserWrapper = FindBrowserWrapper(frame->GetBrowser()->GetIdentifier()); + + if (browserWrapper == nullptr || !browserWrapper->JavascriptBindingApiHasAllowOrigins) + { + return true; + } + + auto allowOrigins = browserWrapper->JavascriptBindingApiAllowOrigins; + if (!allowOrigins.get()) + { + return false; + } + + auto frameUrl = frame->GetURL(); + + CefURLParts frameUrlParts; + + if (CefParseURL(frameUrl, frameUrlParts)) + { + auto originStr = frameUrlParts.origin.str; + auto originLen = frameUrlParts.origin.length; + + if (originLen > 0 && originStr[originLen - 1] == L'/') + { + originLen--; + } + + auto frameUrlOrigin = CefString(originStr, originLen); + + auto size = static_cast(allowOrigins->GetSize()); + + for (int i = 0; i < size; i++) + { + auto origin = allowOrigins->GetString(i); + auto frameOriginPtr = reinterpret_cast(frameUrlOrigin.c_str()); + auto allowedOriginPtr = reinterpret_cast(origin.c_str()); + + if (frameOriginPtr != nullptr && allowedOriginPtr != nullptr) + { + if (_wcsicmp(frameOriginPtr, allowedOriginPtr) == 0) + { + return true; + } + } + } + } + + return false; + } + CefBrowserWrapper^ CefAppUnmanagedWrapper::FindBrowserWrapper(int browserId) { CefBrowserWrapper^ wrapper = nullptr; diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 7915412fba..470cfc3b0a 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -29,7 +29,6 @@ namespace CefSharp gcroot^> _jsRootObjectWrappersByFrameId; bool _focusedNodeChangedEnabled; bool _legacyBindingEnabled; - bool _jsBindingApiEnabled = true; // The property names used to call bound objects CefString _jsBindingPropertyName; @@ -39,6 +38,7 @@ namespace CefSharp gcroot^> _javascriptObjects; gcroot _registerBoundObjectRegistry; + bool IsJavascriptBindingApiAllowed(CefRefPtr frame); public: static const CefString kPromiseCreatorScript; diff --git a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h index 37f03c1dd0..50b9e304a8 100644 --- a/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefBrowserWrapper.h @@ -22,12 +22,13 @@ namespace CefSharp { namespace BrowserSubprocess { - // "Master class" for wrapping everything that the Cef Subprocess needs + // "Master class" for wrapping everything that the Cef Subprocess needs // for ONE CefBrowser. public ref class CefBrowserWrapper { private: MCefRefPtr _cefBrowser; + MCefRefPtr _javascriptBindingApiAllowOrigins; public: CefBrowserWrapper(const CefRefPtr &cefBrowser) @@ -35,11 +36,17 @@ namespace CefSharp _cefBrowser = cefBrowser.get(); BrowserId = cefBrowser->GetIdentifier(); IsPopup = cefBrowser->IsPopup(); + + JavascriptBindingApiEnabled = true; + JavascriptBindingApiHasAllowOrigins = false; + JavascriptBindingApiAllowOrigins = nullptr; } !CefBrowserWrapper() { _cefBrowser = nullptr; + + _javascriptBindingApiAllowOrigins = nullptr; } ~CefBrowserWrapper() @@ -49,6 +56,13 @@ namespace CefSharp property int BrowserId; property bool IsPopup; + property bool JavascriptBindingApiEnabled; + property bool JavascriptBindingApiHasAllowOrigins; + property CefRefPtr JavascriptBindingApiAllowOrigins + { + CefRefPtr get() { return _javascriptBindingApiAllowOrigins.get(); } + void set(CefRefPtr value) { _javascriptBindingApiAllowOrigins = value.get(); } + } #ifndef NETCOREAPP // This allows us to create the WCF proxies back to our parent process. diff --git a/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp b/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp index 47daea5dd0..7a3a202291 100644 --- a/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp +++ b/CefSharp.Core.Runtime/ManagedCefBrowserAdapter.cpp @@ -84,6 +84,22 @@ namespace CefSharp extraInfo->SetBool("JavascriptBindingApiEnabled", objectRepositorySettings->JavascriptBindingApiEnabled); + auto hasJavascriptBindingApiAllowOrigins = objectRepositorySettings->HasJavascriptBindingApiAllowOrigins(); + + extraInfo->SetBool("JavascriptBindingApiHasAllowOrigins", hasJavascriptBindingApiAllowOrigins); + + if (hasJavascriptBindingApiAllowOrigins) + { + auto allowOriginList = CefListValue::Create(); + + for (int i = 0; i < objectRepositorySettings->JavascriptBindingApiAllowOrigins->Length; i++) + { + allowOriginList->SetString(i, StringUtils::ToNative(objectRepositorySettings->JavascriptBindingApiAllowOrigins[i])); + } + + extraInfo->SetList("JavascriptBindingApiAllowOrigins", allowOriginList); + } + CefRefPtr requestCtx; if (requestContext != nullptr) diff --git a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs index 3bfbdd13da..dfac95edf4 100644 --- a/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs +++ b/CefSharp.Test/JavascriptBinding/JavascriptBindingTests.cs @@ -130,6 +130,123 @@ public async Task ShouldDisableJsBindingApi() } } + [Theory] + [InlineData("notallowed")] + [InlineData("notallowed", "alsonotallowed")] + [InlineData("notallowed", "alsonotallowed", "stillnotallowed")] + public async Task ShouldDisableJsBindingApiForOrigin(params string[] origins) + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = origins; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.True((bool)response1.Result); + + Assert.True(response2.Success); + Assert.True((bool)response2.Result); + } + } + + [Fact] + public async Task ShouldEnableJsBindingApiWhenOriginsListIsEmpty() + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = new string[0]; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } + + [Theory] + [InlineData(CefExample.BaseUrl)] + [InlineData(CefExample.BaseUrl + "/")] + public async Task ShouldEnableJsBindingApiForOriginWithOrWithoutTrailingSlash(string configuredOrigin) + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = new string[] { configuredOrigin }; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } + [Theory] + [InlineData(CefExample.BaseUrl + "/")] + [InlineData("someorigin", CefExample.BaseUrl + "/")] + [InlineData(CefExample.BaseUrl + "/", "someorigin")] + [InlineData("firstorigin", "secondorigin", CefExample.BaseUrl + "/")] + [InlineData("firstorigin", CefExample.BaseUrl + "/", "secondorigin")] + public async Task ShouldEnableJsBindingApiForOrigin(params string[] origins) + { + using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl, automaticallyCreateBrowser: false)) + { + var settings = browser.JavascriptObjectRepository.Settings; + settings.JavascriptBindingApiEnabled = true; + settings.JavascriptBindingApiAllowOrigins = origins; + + //To modify the settings we need to defer browser creation slightly + browser.CreateBrowser(); + + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); + + var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); + var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); + + Assert.True(response1.Success); + Assert.False((bool)response1.Result); + + Assert.True(response2.Success); + Assert.False((bool)response2.Result); + } + } + [Fact] public async Task ShouldEnableJsBindingApi() { @@ -141,7 +258,9 @@ public async Task ShouldEnableJsBindingApi() //To modify the settings we need to defer browser creation slightly browser.CreateBrowser(); - await browser.WaitForInitialLoadAsync(); + var loadResponse = await browser.WaitForInitialLoadAsync(); + + Assert.True(loadResponse.Success); var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'"); var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'"); diff --git a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs index 7d89aedd64..1b2d6678e9 100644 --- a/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs +++ b/CefSharp/JavascriptBinding/JavascriptBindingSettings.cs @@ -2,7 +2,9 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +using System.Collections.Generic; using CefSharp.Internals; +using System; namespace CefSharp.JavascriptBinding { @@ -15,6 +17,7 @@ public class JavascriptBindingSettings : FreezableBase private bool legacyBindingEnabled; private string jsBindingGlobalObjectName; private bool jsBindingApiEnabled = true; + private string[] javascriptBindingApiAllowOrigins; /// /// The Javascript methods that CefSharp provides in relation to JavaScript Binding are @@ -33,6 +36,33 @@ public bool JavascriptBindingApiEnabled } } + /// + /// When is set to true, set a collection + /// of origins to limit which origins CefSharp will create it's global (window) object. + /// + /// + /// If you wish to create the CefSharp object for a limited set of origins then set this property + /// + public string[] JavascriptBindingApiAllowOrigins + { + get { return javascriptBindingApiAllowOrigins; } + set + { + ThrowIfFrozen(); + if (value != null) + { + javascriptBindingApiAllowOrigins = Array.ConvertAll( + value, + origin => origin?.TrimEnd('/') + ); + } + else + { + javascriptBindingApiAllowOrigins = null; + } + } + } + /// /// The Javascript methods that CefSharp provides in relation to JavaScript Binding are /// created using a Global (window) Object. Settings this property allows you to customise @@ -95,5 +125,17 @@ public bool AlwaysInterceptAsynchronously alwaysInterceptAsynchronously = value; } } + + /// + /// HasJavascriptBindingApiAllowOrigins + /// + /// bool true if is non empty collection. + public bool HasJavascriptBindingApiAllowOrigins() + { + if (javascriptBindingApiAllowOrigins == null) + return false; + + return javascriptBindingApiAllowOrigins.Length > 0; + } } }