From 0986715fc5c90f465df3e9eeeaa46653a8468cea Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 12 Jul 2023 16:05:51 -0300 Subject: [PATCH] SSL Pinning working on Android --- .../reactnative/ReactNativeFlipper.java | 4 +- .../rocket/reactnative/MainApplication.java | 4 +- ...ningModule.java_ => SSLPinningModule.java} | 2 +- ...ngPackage.java_ => SSLPinningPackage.java} | 0 android/settings.gradle | 8 + patches/react-native+0.71.7.patch | 167 +++++++ patches/react-native+0.71.7.patch_ | 433 ------------------ patches/rn-fetch-blob+0.12.0.patch | 24 + patches/rn-fetch-blob+0.12.0.patch_ | 153 ------- 9 files changed, 204 insertions(+), 591 deletions(-) rename android/app/src/main/java/chat/rocket/reactnative/networking/{SSLPinningModule.java_ => SSLPinningModule.java} (98%) rename android/app/src/main/java/chat/rocket/reactnative/networking/{SSLPinningPackage.java_ => SSLPinningPackage.java} (100%) create mode 100644 patches/react-native+0.71.7.patch delete mode 100644 patches/react-native+0.71.7.patch_ create mode 100644 patches/rn-fetch-blob+0.12.0.patch delete mode 100644 patches/rn-fetch-blob+0.12.0.patch_ diff --git a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java index e9263895b..1cb53dd48 100644 --- a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java +++ b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java @@ -16,7 +16,7 @@ import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; -// import com.facebook.react.modules.network.CustomClientBuilder; +import com.facebook.react.modules.network.CustomClientBuilder; import okhttp3.OkHttpClient; /** @@ -34,7 +34,7 @@ public class ReactNativeFlipper { client.addPlugin(CrashReporterPlugin.getInstance()); NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); NetworkingModule.setCustomClientBuilder( - new NetworkingModule.CustomClientBuilder() { + new CustomClientBuilder() { @Override public void apply(OkHttpClient.Builder builder) { builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index f6bcf66c6..9ae14b69a 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -21,7 +21,7 @@ import expo.modules.ReactNativeHostWrapper; import java.util.Arrays; import java.util.List; -// import chat.rocket.reactnative.networking.SSLPinningPackage; +import chat.rocket.reactnative.networking.SSLPinningPackage; public class MainApplication extends Application implements ReactApplication { @@ -36,7 +36,7 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); - // packages.add(new SSLPinningPackage()); + packages.add(new SSLPinningPackage()); packages.add(new RNCViewPagerPackage()); List additionalModules = new AdditionalModules().getAdditionalModules(MainApplication.this); packages.addAll(additionalModules); diff --git a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java_ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java similarity index 98% rename from android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java_ rename to android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java index aad807859..a8bdd8638 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java_ +++ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java @@ -111,7 +111,7 @@ public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyC // FastImage Glide network layer FastImageOkHttpUrlLoader.setOkHttpClient(getOkHttpClient()); // Expo AV network layer - SharedCookiesDataSourceFactory.setOkHttpClient(getOkHttpClient()); + // SharedCookiesDataSourceFactory.setOkHttpClient(getOkHttpClient()); promise.resolve(null); } diff --git a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java_ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java similarity index 100% rename from android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java_ rename to android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java diff --git a/android/settings.gradle b/android/settings.gradle index cfd459e79..ae0e49e8e 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,6 +19,14 @@ includeBuild('../node_modules/@react-native/gradle-plugin') // substitute(module("com.facebook.react:hermes-engine")).using(project(":ReactAndroid:hermes-engine")) // } // } +includeBuild('../node_modules/react-native') { + dependencySubstitution { + substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid")) + substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid")) + substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) + substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) + } +} apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") useExpoModules() \ No newline at end of file diff --git a/patches/react-native+0.71.7.patch b/patches/react-native+0.71.7.patch new file mode 100644 index 000000000..0268d7ba4 --- /dev/null +++ b/patches/react-native+0.71.7.patch @@ -0,0 +1,167 @@ +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/patches/react-native+0.71.7.patch_ b/patches/react-native+0.71.7.patch_ deleted file mode 100644 index 7c0569716..000000000 --- a/patches/react-native+0.71.7.patch_ +++ /dev/null @@ -1,433 +0,0 @@ -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); - diff --git a/patches/rn-fetch-blob+0.12.0.patch b/patches/rn-fetch-blob+0.12.0.patch new file mode 100644 index 000000000..a44ab9cad --- /dev/null +++ b/patches/rn-fetch-blob+0.12.0.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +index 602d51d..920d975 100644 +--- a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java ++++ b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java +@@ -38,7 +38,7 @@ import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT; + + public class RNFetchBlob extends ReactContextBaseJavaModule { + +- private final OkHttpClient mClient; ++ static private OkHttpClient mClient; + + static ReactApplicationContext RCTContext; + private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); +@@ -75,6 +75,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { + }); + } + ++ public static void applyCustomOkHttpClient(OkHttpClient client) { ++ mClient = client; ++ } ++ + @Override + public String getName() { + return "RNFetchBlob"; diff --git a/patches/rn-fetch-blob+0.12.0.patch_ b/patches/rn-fetch-blob+0.12.0.patch_ deleted file mode 100644 index 1269040fb..000000000 --- a/patches/rn-fetch-blob+0.12.0.patch_ +++ /dev/null @@ -1,153 +0,0 @@ -diff --git a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java -index 602d51d..920d975 100644 ---- a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java -+++ b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java -@@ -38,7 +38,7 @@ import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT; - - public class RNFetchBlob extends ReactContextBaseJavaModule { - -- private final OkHttpClient mClient; -+ static private OkHttpClient mClient; - - static ReactApplicationContext RCTContext; - private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); -@@ -75,6 +75,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule { - }); - } - -+ public static void applyCustomOkHttpClient(OkHttpClient client) { -+ mClient = client; -+ } -+ - @Override - public String getName() { - return "RNFetchBlob"; -diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m -index cdbe6b1..04e5e7b 100644 ---- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m -+++ b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m -@@ -15,6 +15,9 @@ - #import "IOS7Polyfill.h" - #import - -+#import "SecureStorage.h" -+#import -+ - - typedef NS_ENUM(NSUInteger, ResponseFormat) { - UTF8, -@@ -450,16 +453,108 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSen - } - } - -- --- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler -+-(NSURLCredential *)getUrlCredential:(NSURLAuthenticationChallenge *)challenge path:(NSString *)path password:(NSString *)password - { -- if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { -- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); -- } else { -- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); -+ 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)]; -+ -+ 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 length] != 0) { -+ 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); -+} -+ -+// - (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler -+// { -+// if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { -+// completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); -+// } else { -+// completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); -+// } -+// } -+ - - - (void) URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session - {