mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
[V2] Add origin verification for bindings (#4480)
* Update go-webview2 to v1.0.22 --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
ac00787202
commit
857f7b7518
12 changed files with 318 additions and 27 deletions
|
|
@ -37,7 +37,7 @@ require (
|
|||
github.com/tc-hib/winres v0.3.1
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
github.com/tkrajina/go-reflector v0.5.8
|
||||
github.com/wailsapp/go-webview2 v1.0.19
|
||||
github.com/wailsapp/go-webview2 v1.0.22
|
||||
github.com/wailsapp/mimetype v1.4.1
|
||||
github.com/wzshiming/ctc v1.2.3
|
||||
golang.org/x/mod v0.23.0
|
||||
|
|
|
|||
|
|
@ -76,10 +76,6 @@ github.com/flytam/filenamify v1.2.0 h1:7RiSqXYR4cJftDQ5NuvljKMfd/ubKnW/j9C6iekCh
|
|||
github.com/flytam/filenamify v1.2.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
|
|
@ -248,8 +244,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
|
||||
|
|
@ -296,7 +292,6 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
|||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
|||
|
|
@ -477,6 +477,15 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
|||
}
|
||||
|
||||
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
|
||||
// Get the origin from the message's frame
|
||||
NSString *origin = nil;
|
||||
if (message.frameInfo && message.frameInfo.request && message.frameInfo.request.URL) {
|
||||
NSURL *url = message.frameInfo.request.URL;
|
||||
if (url.scheme && url.host) {
|
||||
origin = [url absoluteString];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *m = message.body;
|
||||
|
||||
// Check for drag
|
||||
|
|
@ -491,11 +500,11 @@ typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
|||
}
|
||||
|
||||
const char *_m = [m UTF8String];
|
||||
const char *_origin = [origin UTF8String];
|
||||
|
||||
processMessage(_m);
|
||||
processBindingMessage(_m, _origin, message.frameInfo.isMainFrame);
|
||||
}
|
||||
|
||||
|
||||
/***** Dialogs ******/
|
||||
-(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength {
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
|
@ -38,13 +39,20 @@ import (
|
|||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
isMainFrame bool
|
||||
}
|
||||
|
||||
var (
|
||||
messageBuffer = make(chan string, 100)
|
||||
requestBuffer = make(chan webview.Request, 100)
|
||||
callbackBuffer = make(chan uint, 10)
|
||||
openFilepathBuffer = make(chan string, 100)
|
||||
openUrlBuffer = make(chan string, 100)
|
||||
secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
messageBuffer = make(chan string, 100)
|
||||
bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
requestBuffer = make(chan webview.Request, 100)
|
||||
callbackBuffer = make(chan uint, 10)
|
||||
openFilepathBuffer = make(chan string, 100)
|
||||
openUrlBuffer = make(chan string, 100)
|
||||
secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
|
||||
)
|
||||
|
||||
type Frontend struct {
|
||||
|
|
@ -67,6 +75,8 @@ type Frontend struct {
|
|||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
|
|
@ -86,15 +96,18 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
// this should be initialized as early as possible to handle first instance launch
|
||||
C.StartCustomProtocolHandler()
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
|
|
@ -119,6 +132,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
go result.startCallbackProcessor()
|
||||
go result.startFileOpenProcessor()
|
||||
go result.startUrlOpenProcessor()
|
||||
|
|
@ -154,6 +168,30 @@ func (f *Frontend) startMessageProcessor() {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
// Apple webkit doesn't provide origin of main frame. So we can't verify in case of iFrame that top level origin is allowed.
|
||||
if !msg.isMainFrame {
|
||||
f.logger.Error("Blocked request from not main frame")
|
||||
continue
|
||||
}
|
||||
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
|
|
@ -453,6 +491,17 @@ func processMessage(message *C.char) {
|
|||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char, fromMainFrame bool) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
isMainFrame: fromMainFrame,
|
||||
}
|
||||
}
|
||||
|
||||
//export processCallback
|
||||
func processCallback(callbackID uint) {
|
||||
callbackBuffer <- callbackID
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ extern "C"
|
|||
#endif
|
||||
|
||||
void processMessage(const char *);
|
||||
void processBindingMessage(const char *, const char *, bool);
|
||||
void processURLRequest(void *, void*);
|
||||
void processMessageDialogResponse(int);
|
||||
void processOpenFileDialogResponse(const char*);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
|
||||
#cgo webkit2_41 pkg-config: webkit2gtk-4.1
|
||||
|
||||
|
|
@ -95,6 +95,7 @@ import (
|
|||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
|
@ -124,6 +125,8 @@ type Frontend struct {
|
|||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
}
|
||||
|
||||
func (f *Frontend) RunMainLoop() {
|
||||
|
|
@ -156,12 +159,15 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
ctx: ctx,
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
} else {
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
|
|
@ -184,6 +190,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startBindingsMessageProcessor()
|
||||
|
||||
var _debug = ctx.Value("debug")
|
||||
var _devtoolsEnabled = ctx.Value("devtoolsEnabled")
|
||||
|
|
@ -216,6 +223,24 @@ func (f *Frontend) startMessageProcessor() {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) startBindingsMessageProcessor() {
|
||||
for msg := range bindingsMessageBuffer {
|
||||
origin, err := f.originValidator.GetOriginFromURL(msg.source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err))
|
||||
continue
|
||||
}
|
||||
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
continue
|
||||
}
|
||||
|
||||
f.processMessage(msg.message)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
}
|
||||
|
|
@ -507,7 +532,13 @@ func (f *Frontend) ExecJS(js string) {
|
|||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
type bindingsMessage struct {
|
||||
message string
|
||||
source string
|
||||
}
|
||||
|
||||
var messageBuffer = make(chan string, 100)
|
||||
var bindingsMessageBuffer = make(chan *bindingsMessage, 100)
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
|
|
@ -515,6 +546,16 @@ func processMessage(message *C.char) {
|
|||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processBindingMessage
|
||||
func processBindingMessage(message *C.char, source *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
goSource := C.GoString(source)
|
||||
bindingsMessageBuffer <- &bindingsMessage{
|
||||
message: goMessage,
|
||||
source: goSource,
|
||||
}
|
||||
}
|
||||
|
||||
var requestBuffer = make(chan webview.Request, 100)
|
||||
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
|
|
|
|||
|
|
@ -45,11 +45,17 @@ GtkBox *GTKBOX(void *pointer)
|
|||
}
|
||||
|
||||
extern void processMessage(char *);
|
||||
extern void processBindingMessage(char *, char *);
|
||||
|
||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
void *data)
|
||||
{
|
||||
// Retrieve webview from content manager
|
||||
WebKitWebView *webview = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview"));
|
||||
const char *current_uri = webview ? webkit_web_view_get_uri(webview) : NULL;
|
||||
char *uri = current_uri ? g_strdup(current_uri) : NULL;
|
||||
|
||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
||||
char *message = jsc_value_to_string(value);
|
||||
|
|
@ -62,8 +68,11 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
|||
JSStringGetUTF8CString(js, message, messageSize);
|
||||
JSStringRelease(js);
|
||||
#endif
|
||||
processMessage(message);
|
||||
processBindingMessage(message, uri);
|
||||
g_free(message);
|
||||
if (uri) {
|
||||
g_free(uri);
|
||||
}
|
||||
}
|
||||
|
||||
static bool isNULLRectangle(GdkRectangle input)
|
||||
|
|
@ -78,7 +87,7 @@ static gboolean onWayland()
|
|||
case -1:
|
||||
{
|
||||
char *gdkBackend = getenv("XDG_SESSION_TYPE");
|
||||
if(gdkBackend != NULL && strcmp(gdkBackend, "wayland") == 0)
|
||||
if(gdkBackend != NULL && strcmp(gdkBackend, "wayland") == 0)
|
||||
{
|
||||
wmIsWayland = 1;
|
||||
return TRUE;
|
||||
|
|
@ -273,7 +282,7 @@ void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_wid
|
|||
size.min_width = min_width;
|
||||
|
||||
// On Wayland window manager get the decorators and calculate the differences from the windows' size.
|
||||
if(onWayland())
|
||||
if(onWayland())
|
||||
{
|
||||
if(decoratorWidth == -1 && decoratorHeight == -1)
|
||||
{
|
||||
|
|
@ -284,9 +293,9 @@ void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_wid
|
|||
gtk_widget_get_allocation(GTK_WIDGET(window), &windowAllocation);
|
||||
|
||||
decoratorWidth = (windowAllocation.width-windowWidth);
|
||||
decoratorHeight = (windowAllocation.height-windowHeight);
|
||||
decoratorHeight = (windowAllocation.height-windowHeight);
|
||||
}
|
||||
|
||||
|
||||
// Add the decorator difference to the window so fullscreen and maximise can fill the window.
|
||||
size.max_height = decoratorHeight+size.max_height;
|
||||
size.max_width = decoratorWidth+size.max_width;
|
||||
|
|
@ -549,6 +558,9 @@ static gboolean onDragDrop(GtkWidget* self, GdkDragContext* context, gint x, gin
|
|||
GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop)
|
||||
{
|
||||
GtkWidget *webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager *)contentManager);
|
||||
|
||||
// Store webview reference in the content manager
|
||||
g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview);
|
||||
// gtk_container_add(GTK_CONTAINER(window), webview);
|
||||
WebKitWebContext *context = webkit_web_context_get_default();
|
||||
webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL);
|
||||
|
|
@ -876,4 +888,4 @@ void InstallF12Hotkey(void *window)
|
|||
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
|
||||
GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL);
|
||||
gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/originvalidator"
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
|
|
@ -62,6 +63,8 @@ type Frontend struct {
|
|||
|
||||
hasStarted bool
|
||||
|
||||
originValidator *originvalidator.OriginValidator
|
||||
|
||||
// Windows build number
|
||||
versionInfo *operatingsystem.WindowsVersionInfo
|
||||
resizeDebouncer func(f func())
|
||||
|
|
@ -89,14 +92,17 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
|
||||
// We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url.
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
|
||||
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
||||
result.startURL = _starturl
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
return result
|
||||
}
|
||||
|
||||
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
||||
result.startURL.Host = net.JoinHostPort(result.startURL.Host, port)
|
||||
result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
|
||||
}
|
||||
|
||||
var bindings string
|
||||
|
|
@ -680,7 +686,24 @@ var edgeMap = map[string]uintptr{
|
|||
"nw-resize": w32.HTTOPLEFT,
|
||||
}
|
||||
|
||||
func (f *Frontend) processMessage(message string) {
|
||||
func (f *Frontend) processMessage(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) {
|
||||
topSource, err := sender.GetSource()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Unable to get source from sender: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
senderSource, err := args.GetSource()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Unable to get source from args: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// verify both topSource and sender are allowed origins
|
||||
if !f.validBindingOrigin(topSource) || !f.validBindingOrigin(senderSource) {
|
||||
return
|
||||
}
|
||||
|
||||
if message == "drag" {
|
||||
if !f.mainWindow.IsFullScreen() {
|
||||
err := f.startDrag()
|
||||
|
|
@ -725,6 +748,23 @@ func (f *Frontend) processMessage(message string) {
|
|||
}
|
||||
|
||||
func (f *Frontend) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) {
|
||||
topSource, err := sender.GetSource()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Unable to get source from sender: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
senderSource, err := args.GetSource()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Unable to get source from args: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// verify both topSource and sender are allowed origins
|
||||
if !f.validBindingOrigin(topSource) || !f.validBindingOrigin(senderSource) {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "file:drop") {
|
||||
if !f.frontendOptions.DragAndDrop.EnableFileDrop {
|
||||
return
|
||||
|
|
@ -783,6 +823,20 @@ func (f *Frontend) processMessageWithAdditionalObjects(message string, sender *e
|
|||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) validBindingOrigin(source string) bool {
|
||||
origin, err := f.originValidator.GetOriginFromURL(source)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Error parsing source URL %s: %v", source, err.Error()))
|
||||
return false
|
||||
}
|
||||
allowed := f.originValidator.IsOriginAllowed(origin)
|
||||
if !allowed {
|
||||
f.logger.Error("Blocked request from unauthorized origin: %s", origin)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Frontend) dispatchMessage(message string) {
|
||||
result, err := f.dispatcher.ProcessMessage(message, f)
|
||||
if err != nil {
|
||||
|
|
|
|||
116
v2/internal/frontend/originvalidator/originValidator.go
Normal file
116
v2/internal/frontend/originvalidator/originValidator.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package originvalidator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type OriginValidator struct {
|
||||
allowedOrigins []string
|
||||
}
|
||||
|
||||
// NewOriginValidator creates a new validator from a comma-separated string of allowed origins
|
||||
func NewOriginValidator(startUrl *url.URL, allowedOriginsString string) *OriginValidator {
|
||||
allowedOrigins := startUrl.Scheme + "://" + startUrl.Host
|
||||
if allowedOriginsString != "" {
|
||||
allowedOrigins += "," + allowedOriginsString
|
||||
}
|
||||
validator := &OriginValidator{}
|
||||
validator.parseAllowedOrigins(allowedOrigins)
|
||||
return validator
|
||||
}
|
||||
|
||||
// parseAllowedOrigins parses the comma-separated origins string
|
||||
func (v *OriginValidator) parseAllowedOrigins(originsString string) {
|
||||
if originsString == "" {
|
||||
v.allowedOrigins = []string{}
|
||||
return
|
||||
}
|
||||
|
||||
origins := strings.Split(originsString, ",")
|
||||
var trimmedOrigins []string
|
||||
|
||||
for _, origin := range origins {
|
||||
trimmed := strings.TrimSuffix(strings.TrimSpace(origin), "/")
|
||||
if trimmed != "" {
|
||||
trimmedOrigins = append(trimmedOrigins, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
v.allowedOrigins = trimmedOrigins
|
||||
}
|
||||
|
||||
// IsOriginAllowed checks if the given origin is allowed
|
||||
func (v *OriginValidator) IsOriginAllowed(origin string) bool {
|
||||
if origin == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, allowedOrigin := range v.allowedOrigins {
|
||||
if v.matchesOriginPattern(allowedOrigin, origin) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// matchesOriginPattern checks if origin matches the pattern (supports wildcards)
|
||||
func (v *OriginValidator) matchesOriginPattern(pattern, origin string) bool {
|
||||
// Exact match
|
||||
if pattern == origin {
|
||||
return true
|
||||
}
|
||||
|
||||
// Wildcard pattern matching
|
||||
if strings.Contains(pattern, "*") {
|
||||
regexPattern := v.wildcardPatternToRegex(pattern)
|
||||
matched, err := regexp.MatchString(regexPattern, origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// wildcardPatternToRegex converts wildcard pattern to regex
|
||||
func (v *OriginValidator) wildcardPatternToRegex(wildcardPattern string) string {
|
||||
// Escape special regex characters except *
|
||||
specialChars := []string{"\\", ".", "+", "?", "^", "$", "{", "}", "(", ")", "|", "[", "]"}
|
||||
|
||||
escaped := wildcardPattern
|
||||
for _, specialChar := range specialChars {
|
||||
escaped = strings.ReplaceAll(escaped, specialChar, "\\"+specialChar)
|
||||
}
|
||||
|
||||
// Replace * with .* (matches any characters)
|
||||
escaped = strings.ReplaceAll(escaped, "*", ".*")
|
||||
|
||||
// Anchor the pattern to match the entire string
|
||||
return "^" + escaped + "$"
|
||||
}
|
||||
|
||||
// GetOriginFromURL extracts origin from URL string
|
||||
func (v *OriginValidator) GetOriginFromURL(urlString string) (string, error) {
|
||||
if urlString == "" {
|
||||
return "", fmt.Errorf("empty URL")
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid URL: %v", err)
|
||||
}
|
||||
|
||||
if parsedURL.Scheme == "" || parsedURL.Host == "" {
|
||||
return "", fmt.Errorf("URL missing scheme or host")
|
||||
}
|
||||
|
||||
// Build origin (scheme + host)
|
||||
origin := parsedURL.Scheme + "://" + parsedURL.Host
|
||||
|
||||
return origin, nil
|
||||
}
|
||||
|
|
@ -101,6 +101,9 @@ type App struct {
|
|||
|
||||
// DisablePanicRecovery disables the panic recovery system in messages processing
|
||||
DisablePanicRecovery bool
|
||||
|
||||
// List of additional allowed origins for bindings in format "https://*.myapp.com,https://example.com"
|
||||
BindingsAllowedOrigins string
|
||||
}
|
||||
|
||||
type ErrorFormatter func(error) any
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ func main() {
|
|||
Debug: options.Debug{
|
||||
OpenInspectorOnStartup: false,
|
||||
},
|
||||
BindingsAllowedOrigins: "https://my.topapp,https://*.wails.isgreat",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -619,8 +620,8 @@ Type: `bool`
|
|||
|
||||
Prevents window contents from being captured by other applications.
|
||||
|
||||
On Windows it calls SetWindowDisplayAffinity with `WDA_EXCLUDEFROMCAPTURE`.
|
||||
For Windows 10 version 2004 and later the window will be completely removed from capture.
|
||||
On Windows it calls SetWindowDisplayAffinity with `WDA_EXCLUDEFROMCAPTURE`.
|
||||
For Windows 10 version 2004 and later the window will be completely removed from capture.
|
||||
Older Windows versions will call SetWindowDisplayAffinity with `WDA_MONITOR`, capturing a black window.
|
||||
|
||||
Name: ContentProtection<br/>
|
||||
|
|
@ -940,7 +941,7 @@ Type: `bool`
|
|||
|
||||
Prevents window contents from being captured by other applications.
|
||||
|
||||
On MacOS it sets the NSWindow's sharingType to NSWindowSharingNone, removing the window from capture entirely.
|
||||
On MacOS it sets the NSWindow's sharingType to NSWindowSharingNone, removing the window from capture entirely.
|
||||
|
||||
Name: ContentProtection<br/>
|
||||
Type: `bool`
|
||||
|
|
@ -1129,3 +1130,12 @@ Setting this to `true` will open the WebInspector on startup of the application.
|
|||
|
||||
Name: OpenInspectorOnStartup<br/>
|
||||
Type: `bool`
|
||||
|
||||
### BindingsAllowedOrigins
|
||||
|
||||
Comma-separated list of additional allowed origins for JS ↔ Go bindings.
|
||||
Supports “*” wildcards in hostnames for subdomain matching.
|
||||
Example: `"https://*.myapp.com, https://example.com"`
|
||||
|
||||
Name: BindingsAllowedOrigins<br/>
|
||||
Type: `string`<br/>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Add origin verification for bindings by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4480)
|
||||
- Configure Vite timeout by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4374)
|
||||
- Added `ContentProtection` option to allow hiding the application window from screen sharing software [#4241](https://github.com/wailsapp/wails/pull/4241) by [@Taiterbase](https://github.com/Taiterbase)
|
||||
- Added `build:tags` to project specification for automatically adding compilation tags by @symball in [PR](https://github.com/wailsapp/wails/pull/4439)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue