diff --git a/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt b/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt index f338a6a4ed..7241da81c7 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt +++ b/android/src/main/java/com/reactnativecommunity/webview/extension/file/BlobFileDownloader.kt @@ -4,6 +4,7 @@ import android.webkit.JavascriptInterface import com.reactnativecommunity.webview.RNCWebView import com.reactnativecommunity.webview.extension.file.Base64FileDownloader.downloadBase64File import java.io.IOException +import org.json.JSONObject internal fun RNCWebView.addBlobFileDownloaderJavascriptInterface(downloadingMessage: String, requestFilePermission: (String) -> Unit) { this.addJavascriptInterface( @@ -33,7 +34,8 @@ internal class BlobFileDownloader( /** * Invokes JS method downloadBlob from [getBlobFileInterceptor] */ - fun getDownloadBlobInterceptor(url: String): String = "downloadBlob('$url');" + fun getDownloadBlobInterceptor(url: String): String = + "downloadBlob(${JSONObject.quote(url)});" /** * This script handles Blob downloading in two ways: diff --git a/apple/RNCWebViewImpl.m b/apple/RNCWebViewImpl.m index a631557cc1..d6cd98a6bc 100644 --- a/apple/RNCWebViewImpl.m +++ b/apple/RNCWebViewImpl.m @@ -17,6 +17,7 @@ #endif // !TARGET_OS_OSX #import "objc/runtime.h" +#import static NSTimer *keyboardTimer; static NSString *const HistoryShimName = @"ReactNativeHistoryShim"; @@ -26,6 +27,16 @@ NSString *const CUSTOM_SELECTOR = @"_CUSTOM_SELECTOR_"; +/** + * JSON-serialize a value as a JavaScript literal so attacker-controlled + * strings cannot break out of evaluateJavaScript templates. + * Returns nil if serialization fails. + */ +static NSString *RNCJavaScriptJSONLiteral(id value) +{ + return RCTJSONStringify(value, NULL); +} + #if TARGET_OS_IOS // runtime trick to remove WKWebView keyboard default toolbar // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 @@ -1531,14 +1542,17 @@ - (void) webView:(WKWebView *)webView self->_onLoadingStart(event); } else { // In aditional to IFrameDetector report all navigated iFrames to the app - NSString *reportIframeUrlsScript = [NSString stringWithFormat: - @"if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {\n" - @" window.ReactNativeWebView.postMessage(JSON.stringify({\n" - @" type: 'IFRAME_DETECTED',\n" - @" iframeUrls: ['%@']\n" - @"}));\n" - @"}", urlString]; - [self.webView evaluateJavaScript:reportIframeUrlsScript completionHandler:^(id result, NSError *error) {}]; + NSString *serializedUrl = RNCJavaScriptJSONLiteral(urlString); + if (serializedUrl != nil) { + NSString *reportIframeUrlsScript = [NSString stringWithFormat: + @"if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {\n" + @" window.ReactNativeWebView.postMessage(JSON.stringify({\n" + @" type: 'IFRAME_DETECTED',\n" + @" iframeUrls: [%@]\n" + @"}));\n" + @"}", serializedUrl]; + [self.webView evaluateJavaScript:reportIframeUrlsScript completionHandler:^(id result, NSError *error) {}]; + } } } @@ -2169,9 +2183,14 @@ - (NSURLRequest *)requestForSource:(id)json { } - (void)handleBlobDownloadUrl:(NSString *)urlString { + NSString *serializedUrl = RNCJavaScriptJSONLiteral(urlString); + if (serializedUrl == nil) { + NSLog(@"Error downloading blob: failed to serialize URL"); + return; + } NSString *jsCode = [NSString stringWithFormat: @"var xhr = new XMLHttpRequest();" - "xhr.open('GET', '%@', true);" + "xhr.open('GET', %@, true);" "xhr.responseType = 'blob';" "xhr.onload = function(e) {" " if (this.status == 200) {" @@ -2184,7 +2203,7 @@ - (void)handleBlobDownloadUrl:(NSString *)urlString { " };" " }" "};" - "xhr.send();", urlString]; + "xhr.send();", serializedUrl]; [self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {}]; } @@ -2207,8 +2226,13 @@ - (BOOL)isDownloadableFileURL:(NSURL *)url { } - (void)handleRegularFileDownload:(NSString *)urlString { + NSString *serializedUrl = RNCJavaScriptJSONLiteral(urlString); + if (serializedUrl == nil) { + NSLog(@"Error downloading file: failed to serialize URL"); + return; + } NSString *jsCode = [NSString stringWithFormat: - @"fetch('%@')" + @"fetch(%@)" ".then(response => {" " if (!response.ok) throw new Error('Unable to download file');" " return response.blob();" @@ -2222,7 +2246,7 @@ - (void)handleRegularFileDownload:(NSString *)urlString { "})" ".catch(error => {" " console.error('Download failed:', error);" - "});", urlString]; + "});", serializedUrl]; [self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) { if (error) {