diff --git a/modules/ensemble/lib/widget/fintech/tabapay_post_message.dart b/modules/ensemble/lib/widget/fintech/tabapay_post_message.dart new file mode 100644 index 000000000..49f9d1a10 --- /dev/null +++ b/modules/ensemble/lib/widget/fintech/tabapay_post_message.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; + +/// Builds JavaScript that forwards `postMessage` payloads to the WebView +/// [messageHandler] channel only when `event.origin` matches the origin of +/// [pageUrl]. +/// +/// Returns `null` when [pageUrl] is not a valid absolute `http`/`https` URI so +/// callers can fail closed instead of installing an unrestricted listener. +String? buildTabaPayPostMessageListenerScript(String pageUrl) { + final uri = Uri.tryParse(pageUrl); + if (uri == null || !uri.hasScheme || !uri.hasAuthority) { + return null; + } + final scheme = uri.scheme.toLowerCase(); + if (scheme != 'http' && scheme != 'https') { + return null; + } + final origin = uri.origin; + if (origin.isEmpty) { + return null; + } + final originLiteral = jsonEncode(origin); + return ''' +window.addEventListener("message", function(event) { + if (event.origin !== $originLiteral) { + return; + } + messageHandler.postMessage(event.data); +}); +'''; +} diff --git a/modules/ensemble/lib/widget/fintech/tabapayconnect.dart b/modules/ensemble/lib/widget/fintech/tabapayconnect.dart index f95afbd4d..0eebb8fbd 100644 --- a/modules/ensemble/lib/widget/fintech/tabapayconnect.dart +++ b/modules/ensemble/lib/widget/fintech/tabapayconnect.dart @@ -10,6 +10,7 @@ import 'package:webview_flutter/webview_flutter.dart'; import '../../framework/action.dart'; import '../../screen_controller.dart'; import '../../util/utils.dart'; +import 'tabapay_post_message.dart'; class TabaPayConnectController extends WidgetController { String uri = @@ -86,8 +87,11 @@ class TabaPayConnectState extends EWidgetState { onMessageReceived: _handleTabaPayMessage) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (String url) { - _webViewController?.runJavaScript( - 'window.addEventListener("message", (event) => messageHandler.postMessage(event.data))'); + final listenerScript = + buildTabaPayPostMessageListenerScript(widget.controller.uri); + if (listenerScript != null) { + _webViewController?.runJavaScript(listenerScript); + } })); _webViewController?.loadRequest(Uri.parse(widget.controller.uri)); } diff --git a/modules/ensemble/test/tabapay_post_message_test.dart b/modules/ensemble/test/tabapay_post_message_test.dart new file mode 100644 index 000000000..9a71904a2 --- /dev/null +++ b/modules/ensemble/test/tabapay_post_message_test.dart @@ -0,0 +1,33 @@ +import 'package:ensemble/widget/fintech/tabapay_post_message.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('buildTabaPayPostMessageListenerScript', () { + test('includes origin check for valid https iframe URL', () { + final script = buildTabaPayPostMessageListenerScript( + 'https://iframe.tabapay.com/frame', + ); + expect(script, isNotNull); + expect(script, contains('"https://iframe.tabapay.com"')); + expect(script, contains('event.origin !==')); + expect(script, contains('messageHandler.postMessage(event.data)')); + }); + + test('returns null for invalid or non-http(s) URLs', () { + expect(buildTabaPayPostMessageListenerScript(''), isNull); + expect(buildTabaPayPostMessageListenerScript('not-a-url'), isNull); + expect( + buildTabaPayPostMessageListenerScript('javascript:alert(1)'), + isNull, + ); + expect(buildTabaPayPostMessageListenerScript('file:///etc/passwd'), isNull); + }); + + test('JSON-encodes origin literal for safe embedding in JavaScript', () { + final script = buildTabaPayPostMessageListenerScript( + 'https://pay.example.com/frame', + ); + expect(script, contains('"https://pay.example.com"')); + }); + }); +}