Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
48 changes: 36 additions & 12 deletions apple/RNCWebViewImpl.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#endif // !TARGET_OS_OSX

#import "objc/runtime.h"
#import <React/RCTUtils.h>

static NSTimer *keyboardTimer;
static NSString *const HistoryShimName = @"ReactNativeHistoryShim";
Expand All @@ -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
Expand Down Expand Up @@ -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) {}];
}
}
}

Expand Down Expand Up @@ -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) {"
Expand All @@ -2184,7 +2203,7 @@ - (void)handleBlobDownloadUrl:(NSString *)urlString {
" };"
" }"
"};"
"xhr.send();", urlString];
"xhr.send();", serializedUrl];
[self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {}];
}

Expand All @@ -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();"
Expand All @@ -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) {
Expand Down
Loading