diff --git a/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm b/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm index b7b2db1..baf5b3c 100644 --- a/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm +++ b/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm @@ -8,11 +8,13 @@ #import #import - +#import #import #import +#import #import "RCTNetworkPlugins.h" +#import "SecureStorage.h" @interface RCTHTTPRequestHandler () @@ -64,6 +66,102 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request return [schemes containsObject:request.URL.scheme.lowercaseString]; } +-(NSURLCredential *)getUrlCredential:(NSURLAuthenticationChallenge *)challenge path:(NSString *)path password:(NSString *)password +{ + NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + + if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || path == nil || password == nil) { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } else if (path && password) { + NSMutableArray *policies = [NSMutableArray array]; + [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)challenge.protectionSpace.host)]; + SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); + + SecTrustResultType result; + SecTrustEvaluate(serverTrust, &result); + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) + { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } + + NSData *p12data = [NSData dataWithContentsOfFile:path]; + NSDictionary* options = @{ (id)kSecImportExportPassphrase:password }; + CFArrayRef rawItems = NULL; + OSStatus status = SecPKCS12Import((__bridge CFDataRef)p12data, + (__bridge CFDictionaryRef)options, + &rawItems); + + if (status != noErr) { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } + + NSArray* items = (NSArray*)CFBridgingRelease(rawItems); + NSDictionary* firstItem = nil; + if ((status == errSecSuccess) && ([items count]>0)) { + firstItem = items[0]; + } + + SecIdentityRef identity = (SecIdentityRef)CFBridgingRetain(firstItem[(id)kSecImportItemIdentity]); + SecCertificateRef certificate = NULL; + if (identity) { + SecIdentityCopyCertificate(identity, &certificate); + if (certificate) { CFRelease(certificate); } + } + + NSMutableArray *certificates = [[NSMutableArray alloc] init]; + [certificates addObject:CFBridgingRelease(certificate)]; + + [SDWebImageDownloader sharedDownloader].config.urlCredential = [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone]; + + return [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone]; + } + + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; +} + +- (NSString *)stringToHex:(NSString *)string +{ + char *utf8 = (char *)[string UTF8String]; + NSMutableString *hex = [NSMutableString string]; + while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF]; + + return [[NSString stringWithFormat:@"%@", hex] lowercaseString]; +} + +-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { + + NSString *host = challenge.protectionSpace.host; + + // Read the clientSSL info from MMKV + __block NSString *clientSSL; + SecureStorage *secureStorage = [[SecureStorage alloc] init]; + + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 + NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]]; + NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + + if (key == NULL) { + return completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential); + } + + NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; + MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; + clientSSL = [mmkv getStringForKey:host]; + + if (clientSSL) { + NSData *data = [clientSSL dataUsingEncoding:NSUTF8StringEncoding]; + id dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSString *path = [dict objectForKey:@"path"]; + NSString *password = [dict objectForKey:@"password"]; + credential = [self getUrlCredential:challenge path:path password:password]; + } + + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); +} + + - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate { std::lock_guard lock(_mutex); diff --git a/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m b/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m index 925596f..d23e727 100644 --- a/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m +++ b/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m @@ -24,6 +24,9 @@ #import #import +#import +#import "SecureStorage.h" + typedef NS_ENUM(NSInteger, RCTSROpCode) { RCTSROpCodeTextFrame = 0x1, RCTSROpCodeBinaryFrame = 0x2, @@ -513,6 +516,38 @@ - (void)didConnect [self _readHTTPHeader]; } +- (void)setClientSSL:(NSString *)path password:(NSString *)password options:(NSMutableDictionary *)options; +{ + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) + { + NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:path]; + NSDictionary* certOptions = @{ (id)kSecImportExportPassphrase:password }; + CFArrayRef keyref = NULL; + OSStatus sanityChesk = SecPKCS12Import((__bridge CFDataRef)pkcs12data, + (__bridge CFDictionaryRef)certOptions, + &keyref); + if (sanityChesk == noErr) { + CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0); + SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); + SecCertificateRef cert = NULL; + OSStatus status = SecIdentityCopyCertificate(identityRef, &cert); + if (!status) { + NSArray *certificates = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)cert, nil]; + [options setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates]; + } + } + } +} + +- (NSString *)stringToHex:(NSString *)string +{ + char *utf8 = (char *)[string UTF8String]; + NSMutableString *hex = [NSMutableString string]; + while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF]; + + return [[NSString stringWithFormat:@"%@", hex] lowercaseString]; +} + - (void)_initializeStreams { assert(_url.port.unsignedIntValue <= UINT32_MAX); @@ -550,6 +585,26 @@ - (void)_initializeStreams RCTLogInfo(@"SocketRocket: In debug mode. Allowing connection to any root cert"); #endif + // Read the clientSSL info from MMKV + __block NSString *clientSSL; + SecureStorage *secureStorage = [[SecureStorage alloc] init]; + + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 + NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]]; + + if (key != NULL) { + NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; + MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; + clientSSL = [mmkv getStringForKey:host]; + if (clientSSL) { + NSData *data = [clientSSL dataUsingEncoding:NSUTF8StringEncoding]; + id dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSString *path = [dict objectForKey:@"path"]; + NSString *password = [dict objectForKey:@"password"]; + [self setClientSSL:path password:password options:SSLOptions]; + } + } + [_outputStream setProperty:SSLOptions forKey:(__bridge id)kCFStreamPropertySSLSettings]; } @@ -634,6 +689,7 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason } } + [self.delegate webSocket:self didCloseWithCode:code reason:reason wasClean:YES]; [self _sendFrameWithOpcode:RCTSROpCodeConnectionClose data:payload]; }); } diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java index 18a7ce9..9644a5b 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.java @@ -22,11 +22,25 @@ import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; -class ReactOkHttpNetworkFetcher extends OkHttpNetworkFetcher { +import android.os.Looper; +import com.facebook.imagepipeline.common.BytesRange; +import com.facebook.imagepipeline.image.EncodedImage; +import com.facebook.imagepipeline.producers.BaseNetworkFetcher; +import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks; +import com.facebook.imagepipeline.producers.Consumer; +import com.facebook.imagepipeline.producers.FetchState; +import com.facebook.imagepipeline.producers.ProducerContext; +import java.io.IOException; +import javax.annotation.Nullable; +import okhttp3.Call; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class ReactOkHttpNetworkFetcher extends OkHttpNetworkFetcher { private static final String TAG = "ReactOkHttpNetworkFetcher"; - private final OkHttpClient mOkHttpClient; + private static OkHttpClient mOkHttpClient; private final Executor mCancellationExecutor; /** @param okHttpClient client to use */ @@ -36,6 +50,10 @@ class ReactOkHttpNetworkFetcher extends OkHttpNetworkFetcher { mCancellationExecutor = okHttpClient.dispatcher().executorService(); } + public static void setOkHttpClient(OkHttpClient okHttpClient) { + mOkHttpClient = okHttpClient; + } + private Map getHeaders(ReadableMap readableMap) { if (readableMap == null) { return null; @@ -75,4 +93,88 @@ class ReactOkHttpNetworkFetcher extends OkHttpNetworkFetcher { fetchWithRequest(fetchState, callback, request); } + + @Override + protected void fetchWithRequest( + final OkHttpNetworkFetchState fetchState, + final NetworkFetcher.Callback callback, + final Request request) { + final Call call = mOkHttpClient.newCall(request); + + fetchState + .getContext() + .addCallbacks( + new BaseProducerContextCallbacks() { + @Override + public void onCancellationRequested() { + if (Looper.myLooper() != Looper.getMainLooper()) { + call.cancel(); + } else { + mCancellationExecutor.execute( + new Runnable() { + @Override + public void run() { + call.cancel(); + } + }); + } + } + }); + + call.enqueue( + new okhttp3.Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + fetchState.responseTime = SystemClock.elapsedRealtime(); + final ResponseBody body = response.body(); + try { + if (!response.isSuccessful()) { + handleException( + call, new IOException("Unexpected HTTP code " + response), callback); + return; + } + + BytesRange responseRange = + BytesRange.fromContentRangeHeader(response.header("Content-Range")); + if (responseRange != null + && !(responseRange.from == 0 + && responseRange.to == BytesRange.TO_END_OF_CONTENT)) { + // Only treat as a partial image if the range is not all of the content + fetchState.setResponseBytesRange(responseRange); + fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT); + } + + long contentLength = body.contentLength(); + if (contentLength < 0) { + contentLength = 0; + } + callback.onResponse(body.byteStream(), (int) contentLength); + } catch (Exception e) { + handleException(call, e, callback); + } finally { + body.close(); + } + } + + @Override + public void onFailure(Call call, IOException e) { + handleException(call, e, callback); + } + }); + } + + /** + * Handles exceptions. + * + *

OkHttp notifies callers of cancellations via an IOException. If IOException is caught after + * request cancellation, then the exception is interpreted as successful cancellation and + * onCancellation is called. Otherwise onFailure is called. + */ + private void handleException(final Call call, final Exception e, final Callback callback) { + if (call.isCanceled()) { + callback.onCancellation(); + } else { + callback.onFailure(e); + } + } } diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/CustomClientBuilder.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/CustomClientBuilder.java new file mode 100644 index 0000000..db81d65 --- /dev/null +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/CustomClientBuilder.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.modules.network; + +import okhttp3.OkHttpClient; + +public interface CustomClientBuilder { + public void apply(OkHttpClient.Builder builder); +} diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index f80b1c6..49b649e 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -170,10 +170,6 @@ public final class NetworkingModule extends NativeNetworkingAndroidSpec { customClientBuilder = ccb; } - public static interface CustomClientBuilder { - public void apply(OkHttpClient.Builder builder); - } - private static void applyCustomBuilder(OkHttpClient.Builder builder) { if (customClientBuilder != null) { customClientBuilder.apply(builder); diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java index 50e6f9d..280e604 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java @@ -21,6 +21,8 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.network.ForwardingCookieHandler; +import com.facebook.react.modules.network.OkHttpClientProvider; +import com.facebook.react.modules.network.CustomClientBuilder; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -53,11 +55,23 @@ public final class WebSocketModule extends NativeWebSocketModuleSpec { private ForwardingCookieHandler mCookieHandler; + private static @Nullable CustomClientBuilder customClientBuilder = null; + public WebSocketModule(ReactApplicationContext context) { super(context); mCookieHandler = new ForwardingCookieHandler(context); } + public static void setCustomClientBuilder(CustomClientBuilder ccb) { + customClientBuilder = ccb; + } + + private static void applyCustomBuilder(OkHttpClient.Builder builder) { + if (customClientBuilder != null) { + customClientBuilder.apply(builder); + } + } + @Override public void invalidate() { for (WebSocket socket : mWebSocketConnections.values()) { @@ -96,12 +110,15 @@ public final class WebSocketModule extends NativeWebSocketModuleSpec { @Nullable final ReadableMap options, final double socketID) { final int id = (int) socketID; - OkHttpClient client = + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) - .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read - .build(); + .readTimeout(0, TimeUnit.MINUTES); // Disable timeouts for read + + applyCustomBuilder(clientBuilder); + + OkHttpClient client = clientBuilder.build(); Request.Builder builder = new Request.Builder().tag(id).url(url);