-
Notifications
You must be signed in to change notification settings - Fork 3
feature/cp-10963-add-bridge-function-in-mobile-sdk-to-send-subscription #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,7 @@ | |
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
@@ -667,14 +668,25 @@ public void onPageFinished(WebView view, String url) { | |
| "};\n" + | ||
| "CleverPush.trackClick = function(buttonId, customData) {\n" + | ||
| " CleverPush.trackClickStringified(buttonId, customData ? JSON.stringify(customData) : null);\n" + | ||
| "};\n" + | ||
| "CleverPush.getSubscriptionContext = function() {\n" + | ||
| " return new Promise(function(resolve) {\n" + | ||
| " window.CleverPush._resolveSubscriptionContext = resolve;\n" + | ||
| " CleverPush.getSubscriptionContextRequest();\n" + | ||
| " });\n" + | ||
| "};\n", | ||
| null | ||
| ); | ||
| String contextJson = getSubscriptionContextJson(); | ||
| webView.evaluateJavascript( | ||
| "if (typeof CleverPush !== 'undefined') { CleverPush.subscriptionContext = " + contextJson + "; }", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. XSS vulnerability from unescaped JSON in JavaScriptHigh Severity The raw JSON from Additional Locations (2) |
||
| null | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| webView.loadUrl(block.getUrl()); | ||
| webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Promise never rejects leading to unhandled hangsMedium Severity The Additional Locations (1) |
||
| webView.addJavascriptInterface(new CleverpushInterface(webView), "CleverPush"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaScript interface added after URL loadHigh Severity The Additional Locations (1) |
||
|
|
||
| LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( | ||
| LinearLayout.LayoutParams.MATCH_PARENT, | ||
|
|
@@ -725,6 +737,7 @@ private void composeHtmlBanner(LinearLayout body, String htmlContent) { | |
| activity.runOnUiThread(() -> { | ||
| String html = VoucherCodeUtils.replaceVoucherCodeString(htmlContent, voucherCode); | ||
| String lower = html.toLowerCase(Locale.ROOT); | ||
| String contextJson = getSubscriptionContextJson(); | ||
| String jsToInject = "" + | ||
| "<script type=\"text/javascript\">\n" + | ||
| "// Below conditions will take care of all ids and classes which contains defined keywords at start and end of string\n" | ||
|
|
@@ -741,6 +754,13 @@ private void composeHtmlBanner(LinearLayout body, String htmlContent) { | |
| "for (var i = 0; i < closeBtns.length; i++) {\n" + | ||
| " closeBtns[i].addEventListener('click', onCloseClick);\n" + | ||
| "}\n" + | ||
| "if (typeof CleverPush !== 'undefined') { CleverPush.subscriptionContext = " + contextJson + "; }\n" + | ||
| "CleverPush.getSubscriptionContext = function() {\n" + | ||
| " return new Promise(function(resolve) {\n" + | ||
| " window.CleverPush._resolveSubscriptionContext = resolve;\n" + | ||
| " CleverPush.getSubscriptionContextRequest();\n" + | ||
| " });\n" + | ||
| "};\n" + | ||
| "CleverPush.trackEvent = function(eventId, properties) {\n" + | ||
| " CleverPush.trackEventStringified(eventId, properties ? JSON.stringify(properties) : null);\n" + | ||
| "};\n" + | ||
|
|
@@ -770,7 +790,7 @@ private void composeHtmlBanner(LinearLayout body, String htmlContent) { | |
| WebView webView = webLayout.findViewById(R.id.webView); | ||
| webView.getSettings().setJavaScriptEnabled(true); | ||
| webView.getSettings().setLoadsImagesAutomatically(true); | ||
| webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush"); | ||
| webView.addJavascriptInterface(new CleverpushInterface(webView), "CleverPush"); | ||
| webView.setWebViewClient(new AppBannerWebViewClient()); | ||
|
|
||
| webView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { | ||
|
|
@@ -813,6 +833,12 @@ public static int pxToDp(int px) { | |
| } | ||
|
|
||
| public class CleverpushInterface { | ||
| private final WebView webView; | ||
|
|
||
| public CleverpushInterface(WebView webView) { | ||
| this.webView = webView; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Memory leak from circular WebView referenceMedium Severity The Additional Locations (1) |
||
|
|
||
| @JavascriptInterface | ||
| public void subscribe() { | ||
| CleverPush.getInstance(CleverPush.context).subscribe(); | ||
|
|
@@ -959,14 +985,55 @@ public void handleLinkBySystem(String link) { | |
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Called by JS when getSubscriptionContext() Promise is used. Resolves the Promise with | ||
| * subscription context (same behavior as iOS). | ||
| */ | ||
| @JavascriptInterface | ||
| public void getSubscriptionContextRequest() { | ||
| activity.runOnUiThread(() -> { | ||
| if (webView == null) return; | ||
| String contextJson = getSubscriptionContextJson(); | ||
| String escaped = contextJson.replace("\\", "\\\\").replace("\"", "\\\"") | ||
| .replace("\r", "\\r").replace("\n", "\\n"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incomplete character escaping for JavaScript injectionMedium Severity The manual escaping only handles backslash, quote, carriage return, and newline characters, but omits tab ( Additional Locations (1) |
||
| String jsCallback = "try { if (window.CleverPush && window.CleverPush._resolveSubscriptionContext) { " | ||
| + "window.CleverPush._resolveSubscriptionContext(JSON.parse(\"" + escaped + "\")); " | ||
| + "window.CleverPush._resolveSubscriptionContext = null; } } catch (e) {}"; | ||
| webView.evaluateJavascript(jsCallback, null); | ||
| }); | ||
| } | ||
|
|
||
| @JavascriptInterface | ||
| public void copyToClipboard(String text) { | ||
| ClipboardManager clipboard = (ClipboardManager) CleverPush.context.getSystemService(Context.CLIPBOARD_SERVICE); | ||
| if (clipboard != null) { | ||
| ClipData clip = ClipData.newPlainText("label", text); | ||
| clipboard.setPrimaryClip(clip); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @JavascriptInterface | ||
| public void copyToClipboard(String text) { | ||
| ClipboardManager clipboard = (ClipboardManager) CleverPush.context.getSystemService(Context.CLIPBOARD_SERVICE); | ||
| if (clipboard != null) { | ||
| ClipData clip = ClipData.newPlainText("label", text); | ||
| clipboard.setPrimaryClip(clip); | ||
| /** | ||
| * Returns subscription ID and channel ID as JSON for survey workflow / JS layer (iOS-compatible: | ||
| * null for missing values). Used when resolving getSubscriptionContext Promise and when | ||
| * injecting subscriptionContext via evaluateJavascript. | ||
| */ | ||
| private String getSubscriptionContextJson() { | ||
| try { | ||
| CleverPush cleverPush = CleverPush.getInstance(activity); | ||
| String subscriptionId = cleverPush.getSubscriptionId(CleverPush.context); | ||
| String channelId = cleverPush.getChannelId(CleverPush.context); | ||
| Map<String, Object> context = new LinkedHashMap<>(); | ||
| context.put("subscriptionId", subscriptionId); | ||
| context.put("channelId", channelId); | ||
| return new Gson().toJson(context); | ||
| } catch (Exception ex) { | ||
| Logger.e(TAG, "Error in getSubscriptionContextJson.", ex); | ||
| Map<String, Object> empty = new LinkedHashMap<>(); | ||
| empty.put("subscriptionId", (Object) null); | ||
| empty.put("channelId", (Object) null); | ||
| return new Gson().toJson(empty); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gson silently drops null values from subscription context JSONHigh Severity
Additional Locations (1) |
||
| } | ||
unnaticleverpush marked this conversation as resolved.
Show resolved
Hide resolved
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concurrent Promise calls overwrite resolver
Medium Severity
If JavaScript calls
CleverPush.getSubscriptionContext()multiple times concurrently, the second call overwriteswindow.CleverPush._resolveSubscriptionContext, causing the first Promise to never resolve. Only the most recent call's Promise will resolve; all previous Promises hang indefinitely, leaking memory and blocking any code waiting on those Promises.Additional Locations (1)
cleverpush/src/main/java/com/cleverpush/banner/AppBannerCarouselAdapter.java#L757-L762