react-native-blob-util 0.24.5 → 0.24.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.eslintrc.json +96 -96
  2. package/LICENSE +21 -21
  3. package/Migration.md +41 -41
  4. package/NuGet.config +11 -11
  5. package/README.md +1109 -1109
  6. package/android/gradle.properties +1 -1
  7. package/android/src/main/AndroidManifest.xml +37 -37
  8. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilConfig.java +59 -59
  9. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilConst.java +20 -20
  10. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilFileTransformer.java +9 -9
  11. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilImpl.java +431 -431
  12. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilProgressConfig.java +39 -39
  13. package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java +1092 -1092
  14. package/android/src/main/java/com/ReactNativeBlobUtil/Response/ReactNativeBlobUtilDefaultResp.java +98 -98
  15. package/android/src/main/java/com/ReactNativeBlobUtil/Response/ReactNativeBlobUtilFileResp.java +166 -166
  16. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/FileDescription.java +19 -19
  17. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/FileProvider.java +4 -4
  18. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/MimeType.java +70 -70
  19. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/PathResolver.java +223 -223
  20. package/android/src/main/java/com/ReactNativeBlobUtil/Utils/Tls12SocketFactory.java +68 -68
  21. package/android/src/main/res/values/strings.xml +3 -3
  22. package/android/src/main/res/xml/provider_paths.xml +12 -12
  23. package/android.js +58 -58
  24. package/class/ReactNativeBlobUtilCanceledFetchError.js +9 -9
  25. package/class/ReactNativeBlobUtilFile.js +5 -5
  26. package/class/ReactNativeBlobUtilReadStream.js +84 -84
  27. package/class/ReactNativeBlobUtilSession.js +68 -68
  28. package/class/ReactNativeBlobUtilWriteStream.js +50 -50
  29. package/class/StatefulPromise.js +7 -7
  30. package/codegenSpecs/NativeBlobUtils.js +86 -86
  31. package/components/Fetch.onPress.js +10 -10
  32. package/components/Fetch.when.js +15 -15
  33. package/fetch.js +350 -350
  34. package/fs.js +480 -480
  35. package/index.d.ts +880 -880
  36. package/index.js +64 -64
  37. package/index.js.flow +192 -192
  38. package/index.web.js +1 -1
  39. package/ios/PrivacyInfo.xcprivacy +28 -28
  40. package/ios/ReactNativeBlobUtil/ReactNativeBlobUtil.h +55 -55
  41. package/ios/ReactNativeBlobUtil/ReactNativeBlobUtil.mm +1013 -1013
  42. package/ios/ReactNativeBlobUtil.xcodeproj/project.pbxproj +405 -405
  43. package/ios/ReactNativeBlobUtilConst.h +58 -58
  44. package/ios/ReactNativeBlobUtilConst.mm +47 -47
  45. package/ios/ReactNativeBlobUtilFS.h +108 -108
  46. package/ios/ReactNativeBlobUtilFS.mm +1080 -1080
  47. package/ios/ReactNativeBlobUtilFileTransformer.h +23 -23
  48. package/ios/ReactNativeBlobUtilFileTransformer.mm +20 -20
  49. package/ios/ReactNativeBlobUtilNetwork.h +49 -49
  50. package/ios/ReactNativeBlobUtilNetwork.mm +159 -159
  51. package/ios/ReactNativeBlobUtilProgress.h +32 -32
  52. package/ios/ReactNativeBlobUtilProgress.mm +57 -57
  53. package/ios/ReactNativeBlobUtilReqBuilder.h +37 -37
  54. package/ios/ReactNativeBlobUtilReqBuilder.mm +292 -292
  55. package/ios/ReactNativeBlobUtilRequest.h +48 -48
  56. package/ios/ReactNativeBlobUtilRequest.mm +584 -584
  57. package/ios.js +61 -61
  58. package/json-stream.js +42 -42
  59. package/lib/oboe-browser.js +2703 -2703
  60. package/mediacollection.js +36 -36
  61. package/package.json +77 -77
  62. package/polyfill/Blob.js +362 -362
  63. package/polyfill/Event.js +11 -11
  64. package/polyfill/EventTarget.js +78 -78
  65. package/polyfill/Fetch.js +219 -219
  66. package/polyfill/File.js +27 -27
  67. package/polyfill/FileReader.js +91 -91
  68. package/polyfill/ProgressEvent.js +32 -32
  69. package/polyfill/XMLHttpRequest.js +466 -466
  70. package/polyfill/XMLHttpRequestEventTarget.js +126 -126
  71. package/polyfill/index.js +11 -11
  72. package/react-native-blob-util.podspec +48 -48
  73. package/types.js +69 -69
  74. package/utils/log.js +40 -40
  75. package/utils/unicode.js +7 -7
  76. package/utils/uri.js +33 -33
  77. package/utils/uuid.js +4 -4
  78. package/windows/ExperimentalFeatures.props +33 -33
  79. package/windows/README.md +2 -2
  80. package/windows/ReactNativeBlobUtil/PropertySheet.props +15 -15
  81. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.cpp +1836 -1836
  82. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.def +3 -3
  83. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.h +372 -372
  84. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.vcxproj +139 -139
  85. package/windows/ReactNativeBlobUtil/ReactNativeBlobUtil.vcxproj.filters +43 -43
  86. package/windows/ReactNativeBlobUtil/ReactPackageProvider.cpp +20 -20
  87. package/windows/ReactNativeBlobUtil/ReactPackageProvider.h +24 -24
  88. package/windows/ReactNativeBlobUtil/ReactPackageProvider.idl +9 -9
  89. package/windows/ReactNativeBlobUtil/codegen/.clang-format +1 -1
  90. package/windows/ReactNativeBlobUtil/codegen/NativeBlobUtilsDataTypes.g.h +42 -42
  91. package/windows/ReactNativeBlobUtil/codegen/NativeBlobUtilsSpec.g.h +353 -353
  92. package/windows/ReactNativeBlobUtil/packages.config +3 -3
  93. package/windows/ReactNativeBlobUtil/packages.lock.json +59 -59
  94. package/windows/ReactNativeBlobUtil/pch.cpp +1 -1
  95. package/windows/ReactNativeBlobUtil/pch.h +30 -30
  96. package/windows/ReactNativeBlobUtil/resource.h +5 -5
  97. package/windows/ReactNativeBlobUtil/targetver.h +8 -8
  98. package/windows/ReactNativeBlobUtil.sln +43 -43
@@ -1,1092 +1,1092 @@
1
- package com.ReactNativeBlobUtil;
2
-
3
- import android.app.DownloadManager;
4
- import android.content.BroadcastReceiver;
5
- import android.content.Context;
6
- import android.content.Intent;
7
- import android.content.IntentFilter;
8
- import android.database.Cursor;
9
- import android.net.ConnectivityManager;
10
- import android.net.Network;
11
- import android.net.NetworkCapabilities;
12
- import android.net.NetworkInfo;
13
- import android.net.Uri;
14
- import android.net.LinkProperties;
15
- import android.net.RouteInfo;
16
- import java.net.Inet4Address;
17
- import java.net.InetAddress;
18
- import android.os.Build;
19
- import android.os.Bundle;
20
- import android.os.Environment;
21
- import android.os.Handler;
22
- import android.os.Message;
23
- import android.util.Base64;
24
- import android.webkit.CookieManager;
25
-
26
- import androidx.annotation.NonNull;
27
-
28
- import com.ReactNativeBlobUtil.Response.ReactNativeBlobUtilDefaultResp;
29
- import com.ReactNativeBlobUtil.Response.ReactNativeBlobUtilFileResp;
30
- import com.ReactNativeBlobUtil.Utils.Tls12SocketFactory;
31
- import com.ReactNativeBlobUtil.ReactNativeBlobUtilFS;
32
- import com.facebook.common.logging.FLog;
33
- import com.facebook.react.bridge.Arguments;
34
- import com.facebook.react.bridge.Callback;
35
- import com.facebook.react.bridge.ReadableArray;
36
- import com.facebook.react.bridge.ReadableMap;
37
- import com.facebook.react.bridge.ReadableMapKeySetIterator;
38
- import com.facebook.react.bridge.WritableArray;
39
- import com.facebook.react.bridge.WritableMap;
40
- import com.facebook.react.bridge.ReactApplicationContext;
41
- import com.facebook.react.modules.core.DeviceEventManagerModule;
42
-
43
- import java.io.File;
44
- import java.io.FileOutputStream;
45
- import java.io.IOException;
46
- import java.io.InputStream;
47
- import java.net.MalformedURLException;
48
- import java.net.UnknownHostException;
49
- import java.net.Proxy;
50
- import java.net.SocketException;
51
- import java.net.SocketTimeoutException;
52
- import java.net.URL;
53
- import java.nio.ByteBuffer;
54
- import java.nio.charset.CharacterCodingException;
55
- import java.nio.charset.Charset;
56
- import java.nio.charset.CharsetDecoder;
57
- import java.util.ArrayList;
58
- import java.util.HashMap;
59
-
60
- import java.util.UUID;
61
-
62
- import java.util.List;
63
- import java.util.Locale;
64
- import java.util.concurrent.Executors;
65
- import java.util.concurrent.Future;
66
- import java.util.concurrent.ScheduledExecutorService;
67
- import java.util.concurrent.TimeUnit;
68
-
69
- import javax.net.ssl.SSLContext;
70
-
71
- import okhttp3.Call;
72
- import okhttp3.ConnectionPool;
73
- import okhttp3.ConnectionSpec;
74
- import okhttp3.Headers;
75
- import okhttp3.Interceptor;
76
- import okhttp3.MediaType;
77
- import okhttp3.OkHttpClient;
78
- import okhttp3.Request;
79
- import okhttp3.RequestBody;
80
- import okhttp3.Response;
81
- import okhttp3.ResponseBody;
82
- import okhttp3.TlsVersion;
83
-
84
-
85
- public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnable {
86
-
87
- enum RequestType {
88
- Form,
89
- SingleFile,
90
- AsIs,
91
- WithoutBody,
92
- Others
93
- }
94
-
95
- enum ResponseType {
96
- KeepInMemory,
97
- FileStorage
98
- }
99
-
100
- enum ResponseFormat {
101
- Auto,
102
- UTF8,
103
- BASE64
104
- }
105
-
106
- private boolean shouldTransformFile() {
107
- return this.options.transformFile &&
108
- // Can only process if it's written to a file
109
- (this.options.fileCache || this.options.path != null);
110
- }
111
-
112
- public static HashMap<String, Call> taskTable = new HashMap<>();
113
- public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>();
114
- static HashMap<String, ReactNativeBlobUtilProgressConfig> progressReport = new HashMap<>();
115
- static HashMap<String, ReactNativeBlobUtilProgressConfig> uploadProgressReport = new HashMap<>();
116
- static ConnectionPool pool = new ConnectionPool();
117
-
118
- ReactNativeBlobUtilConfig options;
119
- String taskId;
120
- String method;
121
- String url;
122
- String rawRequestBody;
123
- String destPath;
124
- String customPath;
125
- ReadableArray rawRequestBodyArray;
126
- ReadableMap headers;
127
- Callback callback;
128
- long contentLength;
129
- long downloadManagerId;
130
- ReactNativeBlobUtilBody requestBody;
131
- RequestType requestType;
132
- ResponseType responseType;
133
- ResponseFormat responseFormat = ResponseFormat.Auto;
134
- WritableMap respInfo;
135
- boolean timeout = false;
136
- ArrayList<String> redirects = new ArrayList<>();
137
- OkHttpClient client;
138
- boolean callbackfired;
139
-
140
- public ReactNativeBlobUtilReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, OkHttpClient client, final Callback callback) {
141
- this.method = method.toUpperCase(Locale.ROOT);
142
- this.options = new ReactNativeBlobUtilConfig(options);
143
- this.taskId = taskId;
144
- this.url = url;
145
- this.headers = headers;
146
- this.callback = callback;
147
- this.rawRequestBody = body;
148
- this.rawRequestBodyArray = arrayBody;
149
- this.client = client;
150
- this.callbackfired = false;
151
-
152
- // If transformFile is specified, we first want to get the response back in memory so we can
153
- // encrypt it wholesale and at that point, write it into the file storage.
154
- if((this.options.fileCache || this.options.path != null) && !this.shouldTransformFile())
155
- responseType = ResponseType.FileStorage;
156
- else
157
- responseType = ResponseType.KeepInMemory;
158
-
159
-
160
- if (body != null)
161
- requestType = RequestType.SingleFile;
162
- else if (arrayBody != null)
163
- requestType = RequestType.Form;
164
- else
165
- requestType = RequestType.WithoutBody;
166
- }
167
-
168
- public static void cancelTask(String taskId) {
169
- Call call = taskTable.get(taskId);
170
- if (call != null) {
171
- call.cancel();
172
- taskTable.remove(taskId);
173
- }
174
-
175
- if (androidDownloadManagerTaskTable.containsKey(taskId)) {
176
- long downloadManagerIdForTaskId = androidDownloadManagerTaskTable.get(taskId).longValue();
177
- Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
178
- DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
179
- dm.remove(downloadManagerIdForTaskId);
180
- }
181
- }
182
-
183
- private void invoke_callback(Object... args) {
184
- if (this.callbackfired) return;
185
- this.callback.invoke(args);
186
- this.callbackfired = true;
187
- }
188
-
189
- private final int QUERY = 1314;
190
- private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
191
- private Future<?> future;
192
- private Handler mHandler = new Handler(new Handler.Callback() {
193
- public boolean handleMessage(Message msg) {
194
- switch (msg.what) {
195
-
196
- case QUERY:
197
-
198
- Bundle data = msg.getData();
199
- long id = data.getLong("downloadManagerId");
200
- if (id == downloadManagerId) {
201
-
202
- Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
203
-
204
- DownloadManager downloadManager = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
205
-
206
- DownloadManager.Query query = new DownloadManager.Query();
207
- query.setFilterById(downloadManagerId);
208
-
209
- Cursor cursor = downloadManager.query(query);
210
-
211
- if (cursor != null && cursor.moveToFirst()) {
212
-
213
- long written = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
214
-
215
- long total = cursor.getLong(cursor.getColumnIndex(
216
- DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
217
- cursor.close();
218
-
219
- ReactNativeBlobUtilProgressConfig reportConfig = getReportProgress(taskId);
220
- float progress = (total > 0) ? written / total : 0;
221
-
222
- if (reportConfig != null && reportConfig.shouldReport(progress /* progress */)) {
223
- WritableMap args = Arguments.createMap();
224
- args.putString("taskId", String.valueOf(taskId));
225
- args.putString("written", String.valueOf(written));
226
- args.putString("total", String.valueOf(total));
227
- args.putString("chunk", "");
228
- ReactNativeBlobUtilImpl.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
229
- .emit(ReactNativeBlobUtilConst.EVENT_PROGRESS, args);
230
-
231
- }
232
-
233
- if (total == written) {
234
- future.cancel(true);
235
- }
236
- }
237
- }
238
- }
239
- return true;
240
- }
241
- });
242
-
243
- @Override
244
- public void run() {
245
- Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
246
- // use download manager instead of default HTTP implementation
247
- if (options.addAndroidDownloads != null && options.addAndroidDownloads.hasKey("useDownloadManager")) {
248
-
249
- if (options.addAndroidDownloads.getBoolean("useDownloadManager")) {
250
- Uri uri = Uri.parse(url);
251
- DownloadManager.Request req = new DownloadManager.Request(uri);
252
- if (options.addAndroidDownloads.hasKey("notification") && options.addAndroidDownloads.getBoolean("notification")) {
253
- req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
254
- } else {
255
- req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
256
- }
257
- if (options.addAndroidDownloads.hasKey("title")) {
258
- req.setTitle(options.addAndroidDownloads.getString("title"));
259
- }
260
- if (options.addAndroidDownloads.hasKey("description")) {
261
- req.setDescription(options.addAndroidDownloads.getString("description"));
262
- }
263
- if (options.addAndroidDownloads.hasKey("path")) {
264
- String path = options.addAndroidDownloads.getString("path");
265
- File f = new File(path);
266
- File dir = f.getParentFile();
267
-
268
- if (!f.exists()) {
269
- if (dir != null && !dir.exists()) {
270
- if (!dir.mkdirs() && !dir.exists()) {
271
- invoke_callback( "Failed to create parent directory of '" + path + "'", null, null);
272
- return;
273
- }
274
- }
275
- }
276
- req.setDestinationUri(Uri.parse("file://" + path));
277
-
278
- customPath = path;
279
- }
280
-
281
-
282
- if (options.addAndroidDownloads.hasKey("storeLocal") && options.addAndroidDownloads.getBoolean("storeLocal")) {
283
- String path = (String) ReactNativeBlobUtilFS.getSystemfolders(ReactNativeBlobUtilImpl.RCTContext).get("DownloadDir");
284
- path = path + UUID.randomUUID().toString();
285
-
286
- File f = new File(path);
287
- File dir = f.getParentFile();
288
- if (!f.exists()) {
289
- if (dir != null && !dir.exists()) {
290
- if (!dir.mkdirs() && !dir.exists()) {
291
- invoke_callback( "Failed to create parent directory of '" + path + "'", null, null);
292
- return;
293
- }
294
- }
295
- }
296
- req.setDestinationUri(Uri.parse("file://" + path));
297
- customPath = path;
298
- }
299
-
300
- if (options.addAndroidDownloads.hasKey("mime")) {
301
- req.setMimeType(options.addAndroidDownloads.getString("mime"));
302
- }
303
-
304
- if (options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.getBoolean("mediaScannable")) {
305
- req.allowScanningByMediaScanner();
306
- }
307
-
308
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.addAndroidDownloads.hasKey("storeInDownloads") && options.addAndroidDownloads.getBoolean("storeInDownloads")) {
309
- String t = options.addAndroidDownloads.getString("title");
310
- if(t == null || t.isEmpty())
311
- t = UUID.randomUUID().toString();
312
- if(this.options.appendExt != null && !this.options.appendExt.isEmpty())
313
- t += "." + this.options.appendExt;
314
-
315
- req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, t);
316
- }
317
-
318
- // set headers
319
- ReadableMapKeySetIterator it = headers.keySetIterator();
320
- while (it.hasNextKey()) {
321
- String key = it.nextKey();
322
- req.addRequestHeader(key, headers.getString(key));
323
- }
324
-
325
- // Attempt to add cookie, if it exists
326
- URL urlObj;
327
- try {
328
- urlObj = new URL(url);
329
- String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
330
- String cookie = CookieManager.getInstance().getCookie(baseUrl);
331
- req.addRequestHeader("Cookie", cookie);
332
- } catch (MalformedURLException e) {
333
- e.printStackTrace();
334
- }
335
-
336
- DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
337
- downloadManagerId = dm.enqueue(req);
338
- androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
339
- if(Build.VERSION.SDK_INT >= 34 ){
340
- appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);
341
- }else{
342
- appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
343
- }
344
- future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
345
- @Override
346
- public void run() {
347
- Message msg = mHandler.obtainMessage();
348
- Bundle data = new Bundle();
349
- data.putLong("downloadManagerId", downloadManagerId);
350
- msg.setData(data);
351
- msg.what = QUERY;
352
- mHandler.sendMessage(msg);
353
- }
354
- }, 0, 100, TimeUnit.MILLISECONDS);
355
- return;
356
- }
357
-
358
- }
359
-
360
- // find cached result if `key` property exists
361
- String cacheKey = this.taskId;
362
- String ext = (this.options.appendExt == null || this.options.appendExt.isEmpty()) ? "" : "." + this.options.appendExt;
363
-
364
- if (this.options.key != null) {
365
- cacheKey = ReactNativeBlobUtilUtils.getMD5(this.options.key);
366
- if (cacheKey == null) {
367
- cacheKey = this.taskId;
368
- }
369
-
370
- File file = new File(ReactNativeBlobUtilFS.getTmpPath(cacheKey) + ext);
371
-
372
- if (file.exists()) {
373
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
374
- return;
375
- }
376
- }
377
-
378
- if (this.options.path != null)
379
- this.destPath = this.options.path;
380
- else if (this.options.fileCache)
381
- this.destPath = ReactNativeBlobUtilFS.getTmpPath(cacheKey) + ext;
382
-
383
-
384
- OkHttpClient.Builder clientBuilder;
385
-
386
- try {
387
- // use trusty SSL socket
388
- if (this.options.trusty) {
389
- clientBuilder = ReactNativeBlobUtilUtils.getUnsafeOkHttpClient(client);
390
- } else {
391
- clientBuilder = client.newBuilder();
392
- }
393
-
394
- // wifi only, need ACCESS_NETWORK_STATE permission
395
- // and API level >= 21
396
- final String targetHostIp = this.options.targetHostIp;
397
- boolean targetHostIpAvailable = (targetHostIp != null && !targetHostIp.isEmpty());
398
-
399
- if (this.options.wifiOnly) {
400
- boolean found = false;
401
-
402
- // convert targetHostIp from String into InetAddress
403
- InetAddress targetHostAddr = null;
404
- if (targetHostIpAvailable) {
405
- try {
406
- targetHostAddr = InetAddress.getByName(targetHostIp);
407
- } catch (UnknownHostException e) {
408
- targetHostAddr = null; // skip
409
- }
410
- }
411
-
412
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
413
- ConnectivityManager connectivityManager = (ConnectivityManager) ReactNativeBlobUtilImpl.RCTContext.getSystemService(ReactNativeBlobUtilImpl.RCTContext.CONNECTIVITY_SERVICE);
414
- Network[] networks = connectivityManager.getAllNetworks();
415
- Network selectedNetwork;
416
-
417
- for (Network network : networks) {
418
-
419
- if (!isValidWifiNetwork(connectivityManager, network)) {
420
- continue;
421
- }
422
-
423
- // if targetHostIpAvailable does not match, fallback to any wifi
424
- if (targetHostIpAvailable) {
425
- if (networkMatchesTargetIp(connectivityManager, network, targetHostIp, targetHostAddr)) {
426
- clientBuilder.proxy(Proxy.NO_PROXY);
427
- clientBuilder.socketFactory(network.getSocketFactory());
428
- found = true;
429
- break;
430
- }
431
- }
432
-
433
- // wifiOnly. selects the first interface with wifi transport
434
- // if targetHostIp is available and it matches, this selection will be overriden
435
- if (!found) {
436
- clientBuilder.proxy(Proxy.NO_PROXY);
437
- clientBuilder.socketFactory(network.getSocketFactory());
438
- found = true;
439
-
440
- if (!targetHostIpAvailable) {
441
- break;
442
- }
443
- }
444
- }
445
-
446
- if (!found) {
447
- invoke_callback("No available WiFi connections.", null, null);
448
- releaseTaskResource();
449
- return;
450
- }
451
-
452
-
453
- } else {
454
- ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil: wifiOnly or targetHostIp was set, but SDK < 21. wifiOnly was ignored.");
455
- }
456
- }
457
-
458
- final Request.Builder builder = new Request.Builder();
459
- try {
460
- builder.url(new URL(url));
461
- } catch (MalformedURLException e) {
462
- e.printStackTrace();
463
- }
464
-
465
- HashMap<String, String> mheaders = new HashMap<>();
466
- // set headers
467
- if (headers != null) {
468
- ReadableMapKeySetIterator it = headers.keySetIterator();
469
- while (it.hasNextKey()) {
470
- String key = it.nextKey();
471
- String value = headers.getString(key);
472
- if (key.equalsIgnoreCase("RNFB-Response")) {
473
- if (value.equalsIgnoreCase("base64"))
474
- responseFormat = ResponseFormat.BASE64;
475
- else if (value.equalsIgnoreCase("utf8"))
476
- responseFormat = ResponseFormat.UTF8;
477
- } else {
478
- builder.header(key.toLowerCase(Locale.ROOT), value);
479
- mheaders.put(key.toLowerCase(Locale.ROOT), value);
480
- }
481
- }
482
- }
483
-
484
- if (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
485
- String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase(Locale.ROOT);
486
-
487
- if (rawRequestBodyArray != null) {
488
- requestType = RequestType.Form;
489
- } else if (cType == null || cType.isEmpty()) {
490
- if (!cType.equalsIgnoreCase("")) {
491
- builder.header("Content-Type", "application/octet-stream");
492
- }
493
- requestType = RequestType.SingleFile;
494
- }
495
- if (rawRequestBody != null) {
496
- if (rawRequestBody.startsWith(ReactNativeBlobUtilConst.FILE_PREFIX)
497
- || rawRequestBody.startsWith(ReactNativeBlobUtilConst.CONTENT_PREFIX)) {
498
- requestType = RequestType.SingleFile;
499
- } else if (cType.toLowerCase(Locale.ROOT).contains(";base64") || cType.toLowerCase(Locale.ROOT).startsWith("application/octet")) {
500
- cType = cType.replace(";base64", "").replace(";BASE64", "");
501
- if (mheaders.containsKey("content-type"))
502
- mheaders.put("content-type", cType);
503
- if (mheaders.containsKey("Content-Type"))
504
- mheaders.put("Content-Type", cType);
505
- requestType = RequestType.SingleFile;
506
- } else {
507
- requestType = RequestType.AsIs;
508
- }
509
- }
510
- } else {
511
- requestType = RequestType.WithoutBody;
512
- }
513
-
514
- boolean isChunkedRequest = getHeaderIgnoreCases(mheaders, "Transfer-Encoding").equalsIgnoreCase("chunked");
515
-
516
- // set request body
517
- switch (requestType) {
518
- case SingleFile:
519
- requestBody = new ReactNativeBlobUtilBody(taskId)
520
- .chunkedEncoding(isChunkedRequest)
521
- .setRequestType(requestType)
522
- .setBody(rawRequestBody)
523
- .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
524
- builder.method(method, requestBody);
525
- break;
526
- case AsIs:
527
- requestBody = new ReactNativeBlobUtilBody(taskId)
528
- .chunkedEncoding(isChunkedRequest)
529
- .setRequestType(requestType)
530
- .setBody(rawRequestBody)
531
- .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
532
- builder.method(method, requestBody);
533
- break;
534
- case Form:
535
- String boundary = "ReactNativeBlobUtil-" + taskId;
536
- requestBody = new ReactNativeBlobUtilBody(taskId)
537
- .chunkedEncoding(isChunkedRequest)
538
- .setRequestType(requestType)
539
- .setBody(rawRequestBodyArray)
540
- .setMIME(MediaType.parse("multipart/form-data; boundary=" + boundary));
541
- builder.method(method, requestBody);
542
- break;
543
-
544
- case WithoutBody:
545
- if (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
546
- builder.method(method, RequestBody.create(null, new byte[0]));
547
- } else
548
- builder.method(method, null);
549
- break;
550
- }
551
-
552
- // #156 fix cookie issue
553
- final Request req = builder.build();
554
- clientBuilder.addNetworkInterceptor(new Interceptor() {
555
- @NonNull
556
- @Override
557
- public Response intercept(@NonNull Chain chain) throws IOException {
558
- redirects.add(chain.request().url().toString());
559
- return chain.proceed(chain.request());
560
- }
561
- });
562
- // Add request interceptor for upload progress event
563
- clientBuilder.addInterceptor(new Interceptor() {
564
- @NonNull
565
- @Override
566
- public Response intercept(@NonNull Chain chain) throws IOException {
567
- Response originalResponse = null;
568
- try {
569
- originalResponse = chain.proceed(req);
570
- ResponseBody extended;
571
- switch (responseType) {
572
- case KeepInMemory:
573
- extended = new ReactNativeBlobUtilDefaultResp(
574
- ReactNativeBlobUtilImpl.RCTContext,
575
- taskId,
576
- originalResponse.body(),
577
- options.increment);
578
- break;
579
- case FileStorage:
580
- extended = new ReactNativeBlobUtilFileResp(
581
- ReactNativeBlobUtilImpl.RCTContext,
582
- taskId,
583
- originalResponse.body(),
584
- destPath,
585
- options.overwrite);
586
- break;
587
- default:
588
- extended = new ReactNativeBlobUtilDefaultResp(
589
- ReactNativeBlobUtilImpl.RCTContext,
590
- taskId,
591
- originalResponse.body(),
592
- options.increment);
593
- break;
594
- }
595
- return originalResponse.newBuilder().body(extended).build();
596
- } catch (SocketException e) {
597
- timeout = true;
598
- if (originalResponse != null) {
599
- originalResponse.close();
600
- }
601
- } catch (SocketTimeoutException e) {
602
- timeout = true;
603
- if (originalResponse != null) {
604
- originalResponse.close();
605
- }
606
- //ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil error when sending request : " + e.getLocalizedMessage());
607
- } catch (Exception ex) {
608
- if (originalResponse != null) {
609
- originalResponse.close();
610
- }
611
- }
612
-
613
- return chain.proceed(chain.request());
614
- }
615
- });
616
-
617
-
618
- if (options.timeout >= 0) {
619
- clientBuilder.connectTimeout(options.timeout, TimeUnit.MILLISECONDS);
620
- clientBuilder.readTimeout(options.timeout, TimeUnit.MILLISECONDS);
621
- }
622
-
623
- clientBuilder.connectionPool(pool);
624
- clientBuilder.retryOnConnectionFailure(false);
625
- clientBuilder.followRedirects(options.followRedirect);
626
- clientBuilder.followSslRedirects(options.followRedirect);
627
- clientBuilder.retryOnConnectionFailure(true);
628
-
629
- OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build();
630
-
631
- Call call = client.newCall(req);
632
- taskTable.put(taskId, call);
633
- call.enqueue(new okhttp3.Callback() {
634
-
635
- @Override
636
- public void onFailure(@NonNull Call call, @NonNull IOException e) {
637
- cancelTask(taskId);
638
- if (respInfo == null) {
639
- respInfo = Arguments.createMap();
640
- }
641
-
642
- // check if this error caused by socket timeout
643
- if (e.getClass().equals(SocketTimeoutException.class)) {
644
- respInfo.putBoolean("timeout", true);
645
- invoke_callback("The request timed out.", null, null);
646
- } else
647
- invoke_callback(e.getLocalizedMessage(), null, null);
648
- releaseTaskResource();
649
- }
650
-
651
- @Override
652
- public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
653
- ReadableMap notifyConfig = options.addAndroidDownloads;
654
- // Download manager settings
655
- if (notifyConfig != null) {
656
- String title = "", desc = "", mime = "text/plain";
657
- boolean scannable = false, notification = false;
658
- if (notifyConfig.hasKey("title"))
659
- title = options.addAndroidDownloads.getString("title");
660
- if (notifyConfig.hasKey("description"))
661
- desc = notifyConfig.getString("description");
662
- if (notifyConfig.hasKey("mime"))
663
- mime = notifyConfig.getString("mime");
664
- if (notifyConfig.hasKey("mediaScannable"))
665
- scannable = notifyConfig.getBoolean("mediaScannable");
666
- if (notifyConfig.hasKey("notification"))
667
- notification = notifyConfig.getBoolean("notification");
668
- DownloadManager dm = (DownloadManager) ReactNativeBlobUtilImpl.RCTContext.getSystemService(ReactNativeBlobUtilImpl.RCTContext.DOWNLOAD_SERVICE);
669
- dm.addCompletedDownload(title, desc, scannable, mime, destPath, contentLength, notification);
670
- }
671
-
672
- done(response);
673
- }
674
- });
675
-
676
-
677
- } catch (Exception error) {
678
- error.printStackTrace();
679
- releaseTaskResource();
680
- invoke_callback("ReactNativeBlobUtil request error: " + error.getMessage() + error.getCause());
681
- }
682
- }
683
-
684
- /**
685
- * Remove cached information of the HTTP task
686
- */
687
- private void releaseTaskResource() {
688
- if (taskTable.containsKey(taskId))
689
- taskTable.remove(taskId);
690
- if (androidDownloadManagerTaskTable.containsKey(taskId))
691
- androidDownloadManagerTaskTable.remove(taskId);
692
- if (uploadProgressReport.containsKey(taskId))
693
- uploadProgressReport.remove(taskId);
694
- if (progressReport.containsKey(taskId))
695
- progressReport.remove(taskId);
696
- if (requestBody != null)
697
- requestBody.clearRequestBody();
698
- }
699
-
700
- /**
701
- * Send response data back to javascript context.
702
- *
703
- * @param resp OkHttp response object
704
- */
705
- private void done(Response resp) {
706
- boolean isBlobResp = isBlobResponse(resp);
707
- WritableMap respmap = getResponseInfo(resp,isBlobResp);
708
- emitStateEvent(respmap.copy());
709
-
710
- emitStateEvent(getResponseInfo(resp, isBlobResp));
711
- switch (responseType) {
712
- case KeepInMemory:
713
- try {
714
- // For XMLHttpRequest, automatic response data storing strategy, when response
715
- // data is considered as binary data, write it to file system
716
- if (isBlobResp && options.auto) {
717
- String dest = ReactNativeBlobUtilFS.getTmpPath(taskId);
718
- InputStream ins = resp.body().byteStream();
719
- FileOutputStream os = new FileOutputStream(new File(dest));
720
- int read;
721
- byte[] buffer = new byte[10240];
722
- while ((read = ins.read(buffer)) != -1) {
723
- os.write(buffer, 0, read);
724
- }
725
- ins.close();
726
- os.flush();
727
- os.close();
728
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, dest, respmap.copy());
729
- }
730
- // response data directly pass to JS context as string.
731
- else {
732
- byte[] b = resp.body().bytes();
733
- // If process file option is turned on, we first keep response in memory and then stream that content
734
- // after processing
735
- if (this.shouldTransformFile()) {
736
- if (ReactNativeBlobUtilFileTransformer.sharedFileTransformer == null) {
737
- throw new IllegalStateException("Write file with transform was specified but the shared file transformer is not set");
738
- }
739
- this.destPath = this.destPath.replace("?append=true", "");
740
- File file = new File(this.destPath);
741
- if (!file.exists()) {
742
- file.createNewFile();
743
- }
744
- try (FileOutputStream fos = new FileOutputStream(file)) {
745
- fos.write(ReactNativeBlobUtilFileTransformer.sharedFileTransformer.onWriteFile(b));
746
- } catch (Exception e) {
747
- invoke_callback("Error from file transformer:" + e.getLocalizedMessage(), respmap.copy());
748
- return;
749
- }
750
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, this.destPath, respmap.copy());
751
- return;
752
- }
753
- if (responseFormat == ResponseFormat.BASE64) {
754
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP), respmap.copy());
755
- return;
756
- }
757
- try {
758
- // Attempt to decode the incoming response data to determine whether it contains a valid UTF8 string
759
- Charset charSet = Charset.forName("UTF-8");
760
- CharsetDecoder decoder = charSet.newDecoder();
761
- decoder.decode(ByteBuffer.wrap(b));
762
- // If the data contains invalid characters the following lines will be skipped.
763
- String utf8 = new String(b, charSet);
764
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, utf8);
765
- }
766
- // This usually means the data contains invalid unicode characters but still valid data,
767
- // it's binary data, so send it as a normal string
768
- catch (CharacterCodingException ignored) {
769
- if (responseFormat == ResponseFormat.UTF8) {
770
- String utf8 = new String(b);
771
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, utf8, respmap.copy());
772
- } else {
773
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP), respmap.copy());
774
- }
775
- }
776
- }
777
- } catch (IOException e) {
778
- invoke_callback("ReactNativeBlobUtil failed to encode response data to BASE64 string.", respmap.copy());
779
- }
780
- break;
781
- case FileStorage:
782
- ResponseBody responseBody = resp.body();
783
-
784
- try {
785
- // In order to write response data to `destPath` we have to invoke this method.
786
- // It uses customized response body which is able to report download progress
787
- // and write response data to destination path.
788
- responseBody.bytes();
789
- } catch (Exception ignored) {
790
- // ignored.printStackTrace();
791
- }
792
-
793
- ReactNativeBlobUtilFileResp ReactNativeBlobUtilFileResp;
794
-
795
- try {
796
- ReactNativeBlobUtilFileResp = (ReactNativeBlobUtilFileResp) responseBody;
797
- } catch (ClassCastException ex) {
798
- // unexpected response type
799
- if (responseBody != null) {
800
- String responseBodyString = null;
801
- try {
802
- boolean isBufferDataExists = responseBody.source().buffer().size() > 0;
803
- boolean isContentExists = responseBody.contentLength() > 0;
804
- if (isBufferDataExists && isContentExists) {
805
- responseBodyString = responseBody.string();
806
- }
807
- } catch (IOException exception) {
808
- exception.printStackTrace();
809
- }
810
- invoke_callback("Unexpected FileStorage response file: " + responseBodyString, respmap.copy());
811
- } else {
812
- invoke_callback("Unexpected FileStorage response with no file.", respmap.copy());
813
- }
814
- return;
815
- }
816
-
817
- if (ReactNativeBlobUtilFileResp != null && !ReactNativeBlobUtilFileResp.isDownloadComplete()) {
818
- invoke_callback("Download interrupted.", respmap.copy());
819
- } else {
820
- this.destPath = this.destPath.replace("?append=true", "");
821
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, this.destPath, respmap.copy());
822
- }
823
-
824
- break;
825
- default:
826
- try {
827
- invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, new String(resp.body().bytes(), "UTF-8"), respmap.copy());
828
- } catch (IOException e) {
829
- invoke_callback("ReactNativeBlobUtil failed to encode response data to UTF8 string.", respmap.copy());
830
- }
831
- break;
832
- }
833
- // if(!resp.isSuccessful())
834
- resp.body().close();
835
- releaseTaskResource();
836
- }
837
-
838
- /**
839
- * Invoke this method to enable download progress reporting.
840
- *
841
- * @param taskId Task ID of the HTTP task.
842
- * @return Task ID of the target task
843
- */
844
- public static ReactNativeBlobUtilProgressConfig getReportProgress(String taskId) {
845
- if (!progressReport.containsKey(taskId)) return null;
846
- return progressReport.get(taskId);
847
- }
848
-
849
- /**
850
- * Invoke this method to enable download progress reporting.
851
- *
852
- * @param taskId Task ID of the HTTP task.
853
- * @return Task ID of the target task
854
- */
855
- public static ReactNativeBlobUtilProgressConfig getReportUploadProgress(String taskId) {
856
- if (!uploadProgressReport.containsKey(taskId)) return null;
857
- return uploadProgressReport.get(taskId);
858
- }
859
-
860
- /**
861
- * Create response information object, contains status code, headers, etc.
862
- *
863
- * @param resp Response object
864
- * @param isBlobResp If the response is binary data
865
- * @return Get RCT bridge object contains response information.
866
- */
867
- private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
868
- WritableMap info = Arguments.createMap();
869
- info.putInt("status", resp.code());
870
- info.putString("state", "2");
871
- info.putString("taskId", this.taskId);
872
- info.putBoolean("timeout", timeout);
873
- WritableMap headers = Arguments.createMap();
874
- for (int i = 0; i < resp.headers().size(); i++) {
875
- headers.putString(resp.headers().name(i), resp.headers().value(i));
876
- }
877
- WritableArray redirectList = Arguments.createArray();
878
- for (String r : redirects) {
879
- redirectList.pushString(r);
880
- }
881
- info.putArray("redirects", redirectList);
882
- info.putMap("headers", headers);
883
- Headers h = resp.headers();
884
- if (isBlobResp) {
885
- info.putString("respType", "blob");
886
- } else if (getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/")) {
887
- info.putString("respType", "text");
888
- } else if (getHeaderIgnoreCases(h, "content-type").contains("application/json")) {
889
- info.putString("respType", "json");
890
- } else {
891
- info.putString("respType", "");
892
- }
893
- return info;
894
- }
895
-
896
- /**
897
- * Check if response data is binary data.
898
- *
899
- * @param resp OkHttp response.
900
- * @return If the response data contains binary bytes
901
- */
902
- private boolean isBlobResponse(Response resp) {
903
- Headers h = resp.headers();
904
- String ctype = getHeaderIgnoreCases(h, "Content-Type");
905
- boolean isText = !ctype.equalsIgnoreCase("text/");
906
- boolean isJSON = !ctype.equalsIgnoreCase("application/json");
907
- boolean isCustomBinary = false;
908
- if (options.binaryContentTypes != null) {
909
- for (int i = 0; i < options.binaryContentTypes.size(); i++) {
910
- if (ctype.toLowerCase(Locale.ROOT).contains(options.binaryContentTypes.getString(i).toLowerCase(Locale.ROOT))) {
911
- isCustomBinary = true;
912
- break;
913
- }
914
- }
915
- }
916
- return (!(isJSON || isText)) || isCustomBinary;
917
- }
918
-
919
- private String getHeaderIgnoreCases(Headers headers, String field) {
920
- String val = headers.get(field);
921
- if (val != null) return val;
922
- return headers.get(field.toLowerCase(Locale.ROOT)) == null ? "" : headers.get(field.toLowerCase(Locale.ROOT));
923
- }
924
-
925
- private String getHeaderIgnoreCases(HashMap<String, String> headers, String field) {
926
- String val = headers.get(field);
927
- if (val != null) return val;
928
- String lowerCasedValue = headers.get(field.toLowerCase(Locale.ROOT));
929
- return lowerCasedValue == null ? "" : lowerCasedValue;
930
- }
931
-
932
- private void emitStateEvent(WritableMap args) {
933
- ReactNativeBlobUtilImpl.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
934
- .emit(ReactNativeBlobUtilConst.EVENT_HTTP_STATE, args);
935
- }
936
-
937
- @Override
938
- public void onReceive(Context context, Intent intent) {
939
- String action = intent.getAction();
940
- if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
941
- Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
942
- long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
943
- if (id == this.downloadManagerId) {
944
- releaseTaskResource(); // remove task ID from task map
945
-
946
- DownloadManager.Query query = new DownloadManager.Query();
947
- query.setFilterById(downloadManagerId);
948
- DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
949
- dm.query(query);
950
- Cursor c = dm.query(query);
951
- if (c == null) {
952
- this.invoke_callback("Download manager failed to download from " + this.url + ". Query was unsuccessful ", null, null);
953
- return;
954
- }
955
-
956
- String filePath = null;
957
- try {
958
- // the file exists in media content database
959
- if (c.moveToFirst()) {
960
- int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
961
- if (statusCode == DownloadManager.STATUS_FAILED) {
962
- this.invoke_callback("Download manager failed to download from " + this.url + ". Status Code = " + statusCode, null, null);
963
- return;
964
- }
965
- String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
966
- if (contentUri != null) {
967
- Uri uri = Uri.parse(contentUri);
968
- Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Files.FileColumns.DATA}, null, null, null);
969
- // use default destination of DownloadManager
970
- if (cursor != null) {
971
- cursor.moveToFirst();
972
- filePath = cursor.getString(0);
973
- cursor.close();
974
- }
975
- }
976
- }
977
- } finally {
978
- if (c != null) {
979
- c.close();
980
- }
981
- }
982
-
983
- // When the file is not found in media content database, check if custom path exists
984
- if (options.addAndroidDownloads.hasKey("path") || options.addAndroidDownloads.hasKey("storeLocal")) {
985
- try {
986
- String customDest = customPath;
987
- boolean exists = new File(customDest).exists();
988
- if (!exists)
989
- throw new Exception("Download manager download failed, the file does not downloaded to destination.");
990
- else
991
- this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, customDest);
992
-
993
- } catch (Exception ex) {
994
- ex.printStackTrace();
995
- this.invoke_callback(ex.getLocalizedMessage(), null);
996
- }
997
- }
998
- else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.addAndroidDownloads.hasKey("storeInDownloads") && options.addAndroidDownloads.getBoolean("storeInDownloads")){
999
- Uri downloadeduri = dm.getUriForDownloadedFile(downloadManagerId);
1000
- if(downloadeduri == null)
1001
- this.invoke_callback("Download manager could not resolve downloaded file uri.", ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, null);
1002
- else
1003
- this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, downloadeduri.toString());
1004
- }
1005
- else {
1006
- if (filePath == null)
1007
- this.invoke_callback("Download manager could not resolve downloaded file path.", ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, null);
1008
- else
1009
- this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, filePath);
1010
- }
1011
-
1012
- }
1013
- }
1014
- }
1015
-
1016
- public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
1017
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
1018
- try {
1019
- // Code from https://github.com/square/okhttp/issues/2372#issuecomment-244807676
1020
- SSLContext sc = SSLContext.getInstance("TLSv1.2");
1021
- sc.init(null, null, null);
1022
- client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));
1023
-
1024
- ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
1025
- .tlsVersions(TlsVersion.TLS_1_2)
1026
- .build();
1027
-
1028
- List<ConnectionSpec> specs = new ArrayList<>();
1029
- specs.add(cs);
1030
- specs.add(ConnectionSpec.COMPATIBLE_TLS);
1031
- specs.add(ConnectionSpec.CLEARTEXT);
1032
-
1033
- client.connectionSpecs(specs);
1034
- } catch (Exception exc) {
1035
- FLog.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
1036
- }
1037
- }
1038
-
1039
- return client;
1040
- }
1041
-
1042
- /**
1043
- * Check if a network is a valid connected WiFi network
1044
- */
1045
- private boolean isValidWifiNetwork(ConnectivityManager cm, Network network) {
1046
- NetworkInfo netInfo = cm.getNetworkInfo(network);
1047
- NetworkCapabilities caps = cm.getNetworkCapabilities(network);
1048
-
1049
- if (caps == null || netInfo == null) {
1050
- return false;
1051
- }
1052
-
1053
- if (!netInfo.isConnected()) {
1054
- return false;
1055
- }
1056
-
1057
- return caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
1058
- }
1059
-
1060
- /**
1061
- * Check if a network matches the target host IP address
1062
- */
1063
- private boolean networkMatchesTargetIp(ConnectivityManager cm, Network network, String targetHostIp, InetAddress targetHostAddr) {
1064
- LinkProperties lp = cm.getLinkProperties(network);
1065
- if (lp == null) return false;
1066
-
1067
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
1068
- // For Android 11 and above, use DHCP server address
1069
- Inet4Address dhcpServer = lp.getDhcpServerAddress();
1070
- if (dhcpServer != null && dhcpServer.getHostAddress().equals(targetHostIp)) {
1071
- return true;
1072
- }
1073
- }
1074
-
1075
- // For older versions or Android 11+ if DHCP does not match, check routing table
1076
- if (targetHostAddr != null) {
1077
- List<RouteInfo> routes = lp.getRoutes();
1078
- if (routes != null) {
1079
- for (RouteInfo route : routes) {
1080
- if (route.isDefaultRoute()) {
1081
- continue;
1082
- }
1083
- if (route.matches(targetHostAddr)) {
1084
- return true;
1085
- }
1086
- }
1087
- }
1088
- }
1089
-
1090
- return false;
1091
- }
1092
- }
1
+ package com.ReactNativeBlobUtil;
2
+
3
+ import android.app.DownloadManager;
4
+ import android.content.BroadcastReceiver;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.content.IntentFilter;
8
+ import android.database.Cursor;
9
+ import android.net.ConnectivityManager;
10
+ import android.net.Network;
11
+ import android.net.NetworkCapabilities;
12
+ import android.net.NetworkInfo;
13
+ import android.net.Uri;
14
+ import android.net.LinkProperties;
15
+ import android.net.RouteInfo;
16
+ import java.net.Inet4Address;
17
+ import java.net.InetAddress;
18
+ import android.os.Build;
19
+ import android.os.Bundle;
20
+ import android.os.Environment;
21
+ import android.os.Handler;
22
+ import android.os.Message;
23
+ import android.util.Base64;
24
+ import android.webkit.CookieManager;
25
+
26
+ import androidx.annotation.NonNull;
27
+
28
+ import com.ReactNativeBlobUtil.Response.ReactNativeBlobUtilDefaultResp;
29
+ import com.ReactNativeBlobUtil.Response.ReactNativeBlobUtilFileResp;
30
+ import com.ReactNativeBlobUtil.Utils.Tls12SocketFactory;
31
+ import com.ReactNativeBlobUtil.ReactNativeBlobUtilFS;
32
+ import com.facebook.common.logging.FLog;
33
+ import com.facebook.react.bridge.Arguments;
34
+ import com.facebook.react.bridge.Callback;
35
+ import com.facebook.react.bridge.ReadableArray;
36
+ import com.facebook.react.bridge.ReadableMap;
37
+ import com.facebook.react.bridge.ReadableMapKeySetIterator;
38
+ import com.facebook.react.bridge.WritableArray;
39
+ import com.facebook.react.bridge.WritableMap;
40
+ import com.facebook.react.bridge.ReactApplicationContext;
41
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
42
+
43
+ import java.io.File;
44
+ import java.io.FileOutputStream;
45
+ import java.io.IOException;
46
+ import java.io.InputStream;
47
+ import java.net.MalformedURLException;
48
+ import java.net.UnknownHostException;
49
+ import java.net.Proxy;
50
+ import java.net.SocketException;
51
+ import java.net.SocketTimeoutException;
52
+ import java.net.URL;
53
+ import java.nio.ByteBuffer;
54
+ import java.nio.charset.CharacterCodingException;
55
+ import java.nio.charset.Charset;
56
+ import java.nio.charset.CharsetDecoder;
57
+ import java.util.ArrayList;
58
+ import java.util.HashMap;
59
+
60
+ import java.util.UUID;
61
+
62
+ import java.util.List;
63
+ import java.util.Locale;
64
+ import java.util.concurrent.Executors;
65
+ import java.util.concurrent.Future;
66
+ import java.util.concurrent.ScheduledExecutorService;
67
+ import java.util.concurrent.TimeUnit;
68
+
69
+ import javax.net.ssl.SSLContext;
70
+
71
+ import okhttp3.Call;
72
+ import okhttp3.ConnectionPool;
73
+ import okhttp3.ConnectionSpec;
74
+ import okhttp3.Headers;
75
+ import okhttp3.Interceptor;
76
+ import okhttp3.MediaType;
77
+ import okhttp3.OkHttpClient;
78
+ import okhttp3.Request;
79
+ import okhttp3.RequestBody;
80
+ import okhttp3.Response;
81
+ import okhttp3.ResponseBody;
82
+ import okhttp3.TlsVersion;
83
+
84
+
85
+ public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnable {
86
+
87
+ enum RequestType {
88
+ Form,
89
+ SingleFile,
90
+ AsIs,
91
+ WithoutBody,
92
+ Others
93
+ }
94
+
95
+ enum ResponseType {
96
+ KeepInMemory,
97
+ FileStorage
98
+ }
99
+
100
+ enum ResponseFormat {
101
+ Auto,
102
+ UTF8,
103
+ BASE64
104
+ }
105
+
106
+ private boolean shouldTransformFile() {
107
+ return this.options.transformFile &&
108
+ // Can only process if it's written to a file
109
+ (this.options.fileCache || this.options.path != null);
110
+ }
111
+
112
+ public static HashMap<String, Call> taskTable = new HashMap<>();
113
+ public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>();
114
+ static HashMap<String, ReactNativeBlobUtilProgressConfig> progressReport = new HashMap<>();
115
+ static HashMap<String, ReactNativeBlobUtilProgressConfig> uploadProgressReport = new HashMap<>();
116
+ static ConnectionPool pool = new ConnectionPool();
117
+
118
+ ReactNativeBlobUtilConfig options;
119
+ String taskId;
120
+ String method;
121
+ String url;
122
+ String rawRequestBody;
123
+ String destPath;
124
+ String customPath;
125
+ ReadableArray rawRequestBodyArray;
126
+ ReadableMap headers;
127
+ Callback callback;
128
+ long contentLength;
129
+ long downloadManagerId;
130
+ ReactNativeBlobUtilBody requestBody;
131
+ RequestType requestType;
132
+ ResponseType responseType;
133
+ ResponseFormat responseFormat = ResponseFormat.Auto;
134
+ WritableMap respInfo;
135
+ boolean timeout = false;
136
+ ArrayList<String> redirects = new ArrayList<>();
137
+ OkHttpClient client;
138
+ boolean callbackfired;
139
+
140
+ public ReactNativeBlobUtilReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, OkHttpClient client, final Callback callback) {
141
+ this.method = method.toUpperCase(Locale.ROOT);
142
+ this.options = new ReactNativeBlobUtilConfig(options);
143
+ this.taskId = taskId;
144
+ this.url = url;
145
+ this.headers = headers;
146
+ this.callback = callback;
147
+ this.rawRequestBody = body;
148
+ this.rawRequestBodyArray = arrayBody;
149
+ this.client = client;
150
+ this.callbackfired = false;
151
+
152
+ // If transformFile is specified, we first want to get the response back in memory so we can
153
+ // encrypt it wholesale and at that point, write it into the file storage.
154
+ if((this.options.fileCache || this.options.path != null) && !this.shouldTransformFile())
155
+ responseType = ResponseType.FileStorage;
156
+ else
157
+ responseType = ResponseType.KeepInMemory;
158
+
159
+
160
+ if (body != null)
161
+ requestType = RequestType.SingleFile;
162
+ else if (arrayBody != null)
163
+ requestType = RequestType.Form;
164
+ else
165
+ requestType = RequestType.WithoutBody;
166
+ }
167
+
168
+ public static void cancelTask(String taskId) {
169
+ Call call = taskTable.get(taskId);
170
+ if (call != null) {
171
+ call.cancel();
172
+ taskTable.remove(taskId);
173
+ }
174
+
175
+ if (androidDownloadManagerTaskTable.containsKey(taskId)) {
176
+ long downloadManagerIdForTaskId = androidDownloadManagerTaskTable.get(taskId).longValue();
177
+ Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
178
+ DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
179
+ dm.remove(downloadManagerIdForTaskId);
180
+ }
181
+ }
182
+
183
+ private void invoke_callback(Object... args) {
184
+ if (this.callbackfired) return;
185
+ this.callback.invoke(args);
186
+ this.callbackfired = true;
187
+ }
188
+
189
+ private final int QUERY = 1314;
190
+ private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
191
+ private Future<?> future;
192
+ private Handler mHandler = new Handler(new Handler.Callback() {
193
+ public boolean handleMessage(Message msg) {
194
+ switch (msg.what) {
195
+
196
+ case QUERY:
197
+
198
+ Bundle data = msg.getData();
199
+ long id = data.getLong("downloadManagerId");
200
+ if (id == downloadManagerId) {
201
+
202
+ Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
203
+
204
+ DownloadManager downloadManager = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
205
+
206
+ DownloadManager.Query query = new DownloadManager.Query();
207
+ query.setFilterById(downloadManagerId);
208
+
209
+ Cursor cursor = downloadManager.query(query);
210
+
211
+ if (cursor != null && cursor.moveToFirst()) {
212
+
213
+ long written = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
214
+
215
+ long total = cursor.getLong(cursor.getColumnIndex(
216
+ DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
217
+ cursor.close();
218
+
219
+ ReactNativeBlobUtilProgressConfig reportConfig = getReportProgress(taskId);
220
+ float progress = (total > 0) ? written / total : 0;
221
+
222
+ if (reportConfig != null && reportConfig.shouldReport(progress /* progress */)) {
223
+ WritableMap args = Arguments.createMap();
224
+ args.putString("taskId", String.valueOf(taskId));
225
+ args.putString("written", String.valueOf(written));
226
+ args.putString("total", String.valueOf(total));
227
+ args.putString("chunk", "");
228
+ ReactNativeBlobUtilImpl.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
229
+ .emit(ReactNativeBlobUtilConst.EVENT_PROGRESS, args);
230
+
231
+ }
232
+
233
+ if (total == written) {
234
+ future.cancel(true);
235
+ }
236
+ }
237
+ }
238
+ }
239
+ return true;
240
+ }
241
+ });
242
+
243
+ @Override
244
+ public void run() {
245
+ Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
246
+ // use download manager instead of default HTTP implementation
247
+ if (options.addAndroidDownloads != null && options.addAndroidDownloads.hasKey("useDownloadManager")) {
248
+
249
+ if (options.addAndroidDownloads.getBoolean("useDownloadManager")) {
250
+ Uri uri = Uri.parse(url);
251
+ DownloadManager.Request req = new DownloadManager.Request(uri);
252
+ if (options.addAndroidDownloads.hasKey("notification") && options.addAndroidDownloads.getBoolean("notification")) {
253
+ req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
254
+ } else {
255
+ req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
256
+ }
257
+ if (options.addAndroidDownloads.hasKey("title")) {
258
+ req.setTitle(options.addAndroidDownloads.getString("title"));
259
+ }
260
+ if (options.addAndroidDownloads.hasKey("description")) {
261
+ req.setDescription(options.addAndroidDownloads.getString("description"));
262
+ }
263
+ if (options.addAndroidDownloads.hasKey("path")) {
264
+ String path = options.addAndroidDownloads.getString("path");
265
+ File f = new File(path);
266
+ File dir = f.getParentFile();
267
+
268
+ if (!f.exists()) {
269
+ if (dir != null && !dir.exists()) {
270
+ if (!dir.mkdirs() && !dir.exists()) {
271
+ invoke_callback( "Failed to create parent directory of '" + path + "'", null, null);
272
+ return;
273
+ }
274
+ }
275
+ }
276
+ req.setDestinationUri(Uri.parse("file://" + path));
277
+
278
+ customPath = path;
279
+ }
280
+
281
+
282
+ if (options.addAndroidDownloads.hasKey("storeLocal") && options.addAndroidDownloads.getBoolean("storeLocal")) {
283
+ String path = (String) ReactNativeBlobUtilFS.getSystemfolders(ReactNativeBlobUtilImpl.RCTContext).get("DownloadDir");
284
+ path = path + UUID.randomUUID().toString();
285
+
286
+ File f = new File(path);
287
+ File dir = f.getParentFile();
288
+ if (!f.exists()) {
289
+ if (dir != null && !dir.exists()) {
290
+ if (!dir.mkdirs() && !dir.exists()) {
291
+ invoke_callback( "Failed to create parent directory of '" + path + "'", null, null);
292
+ return;
293
+ }
294
+ }
295
+ }
296
+ req.setDestinationUri(Uri.parse("file://" + path));
297
+ customPath = path;
298
+ }
299
+
300
+ if (options.addAndroidDownloads.hasKey("mime")) {
301
+ req.setMimeType(options.addAndroidDownloads.getString("mime"));
302
+ }
303
+
304
+ if (options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.getBoolean("mediaScannable")) {
305
+ req.allowScanningByMediaScanner();
306
+ }
307
+
308
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.addAndroidDownloads.hasKey("storeInDownloads") && options.addAndroidDownloads.getBoolean("storeInDownloads")) {
309
+ String t = options.addAndroidDownloads.getString("title");
310
+ if(t == null || t.isEmpty())
311
+ t = UUID.randomUUID().toString();
312
+ if(this.options.appendExt != null && !this.options.appendExt.isEmpty())
313
+ t += "." + this.options.appendExt;
314
+
315
+ req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, t);
316
+ }
317
+
318
+ // set headers
319
+ ReadableMapKeySetIterator it = headers.keySetIterator();
320
+ while (it.hasNextKey()) {
321
+ String key = it.nextKey();
322
+ req.addRequestHeader(key, headers.getString(key));
323
+ }
324
+
325
+ // Attempt to add cookie, if it exists
326
+ URL urlObj;
327
+ try {
328
+ urlObj = new URL(url);
329
+ String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
330
+ String cookie = CookieManager.getInstance().getCookie(baseUrl);
331
+ req.addRequestHeader("Cookie", cookie);
332
+ } catch (MalformedURLException e) {
333
+ e.printStackTrace();
334
+ }
335
+
336
+ DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
337
+ downloadManagerId = dm.enqueue(req);
338
+ androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
339
+ if(Build.VERSION.SDK_INT >= 34 ){
340
+ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);
341
+ }else{
342
+ appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
343
+ }
344
+ future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
345
+ @Override
346
+ public void run() {
347
+ Message msg = mHandler.obtainMessage();
348
+ Bundle data = new Bundle();
349
+ data.putLong("downloadManagerId", downloadManagerId);
350
+ msg.setData(data);
351
+ msg.what = QUERY;
352
+ mHandler.sendMessage(msg);
353
+ }
354
+ }, 0, 100, TimeUnit.MILLISECONDS);
355
+ return;
356
+ }
357
+
358
+ }
359
+
360
+ // find cached result if `key` property exists
361
+ String cacheKey = this.taskId;
362
+ String ext = (this.options.appendExt == null || this.options.appendExt.isEmpty()) ? "" : "." + this.options.appendExt;
363
+
364
+ if (this.options.key != null) {
365
+ cacheKey = ReactNativeBlobUtilUtils.getMD5(this.options.key);
366
+ if (cacheKey == null) {
367
+ cacheKey = this.taskId;
368
+ }
369
+
370
+ File file = new File(ReactNativeBlobUtilFS.getTmpPath(cacheKey) + ext);
371
+
372
+ if (file.exists()) {
373
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, file.getAbsolutePath());
374
+ return;
375
+ }
376
+ }
377
+
378
+ if (this.options.path != null)
379
+ this.destPath = this.options.path;
380
+ else if (this.options.fileCache)
381
+ this.destPath = ReactNativeBlobUtilFS.getTmpPath(cacheKey) + ext;
382
+
383
+
384
+ OkHttpClient.Builder clientBuilder;
385
+
386
+ try {
387
+ // use trusty SSL socket
388
+ if (this.options.trusty) {
389
+ clientBuilder = ReactNativeBlobUtilUtils.getUnsafeOkHttpClient(client);
390
+ } else {
391
+ clientBuilder = client.newBuilder();
392
+ }
393
+
394
+ // wifi only, need ACCESS_NETWORK_STATE permission
395
+ // and API level >= 21
396
+ final String targetHostIp = this.options.targetHostIp;
397
+ boolean targetHostIpAvailable = (targetHostIp != null && !targetHostIp.isEmpty());
398
+
399
+ if (this.options.wifiOnly) {
400
+ boolean found = false;
401
+
402
+ // convert targetHostIp from String into InetAddress
403
+ InetAddress targetHostAddr = null;
404
+ if (targetHostIpAvailable) {
405
+ try {
406
+ targetHostAddr = InetAddress.getByName(targetHostIp);
407
+ } catch (UnknownHostException e) {
408
+ targetHostAddr = null; // skip
409
+ }
410
+ }
411
+
412
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
413
+ ConnectivityManager connectivityManager = (ConnectivityManager) ReactNativeBlobUtilImpl.RCTContext.getSystemService(ReactNativeBlobUtilImpl.RCTContext.CONNECTIVITY_SERVICE);
414
+ Network[] networks = connectivityManager.getAllNetworks();
415
+ Network selectedNetwork;
416
+
417
+ for (Network network : networks) {
418
+
419
+ if (!isValidWifiNetwork(connectivityManager, network)) {
420
+ continue;
421
+ }
422
+
423
+ // if targetHostIpAvailable does not match, fallback to any wifi
424
+ if (targetHostIpAvailable) {
425
+ if (networkMatchesTargetIp(connectivityManager, network, targetHostIp, targetHostAddr)) {
426
+ clientBuilder.proxy(Proxy.NO_PROXY);
427
+ clientBuilder.socketFactory(network.getSocketFactory());
428
+ found = true;
429
+ break;
430
+ }
431
+ }
432
+
433
+ // wifiOnly. selects the first interface with wifi transport
434
+ // if targetHostIp is available and it matches, this selection will be overriden
435
+ if (!found) {
436
+ clientBuilder.proxy(Proxy.NO_PROXY);
437
+ clientBuilder.socketFactory(network.getSocketFactory());
438
+ found = true;
439
+
440
+ if (!targetHostIpAvailable) {
441
+ break;
442
+ }
443
+ }
444
+ }
445
+
446
+ if (!found) {
447
+ invoke_callback("No available WiFi connections.", null, null);
448
+ releaseTaskResource();
449
+ return;
450
+ }
451
+
452
+
453
+ } else {
454
+ ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil: wifiOnly or targetHostIp was set, but SDK < 21. wifiOnly was ignored.");
455
+ }
456
+ }
457
+
458
+ final Request.Builder builder = new Request.Builder();
459
+ try {
460
+ builder.url(new URL(url));
461
+ } catch (MalformedURLException e) {
462
+ e.printStackTrace();
463
+ }
464
+
465
+ HashMap<String, String> mheaders = new HashMap<>();
466
+ // set headers
467
+ if (headers != null) {
468
+ ReadableMapKeySetIterator it = headers.keySetIterator();
469
+ while (it.hasNextKey()) {
470
+ String key = it.nextKey();
471
+ String value = headers.getString(key);
472
+ if (key.equalsIgnoreCase("RNFB-Response")) {
473
+ if (value.equalsIgnoreCase("base64"))
474
+ responseFormat = ResponseFormat.BASE64;
475
+ else if (value.equalsIgnoreCase("utf8"))
476
+ responseFormat = ResponseFormat.UTF8;
477
+ } else {
478
+ builder.header(key.toLowerCase(Locale.ROOT), value);
479
+ mheaders.put(key.toLowerCase(Locale.ROOT), value);
480
+ }
481
+ }
482
+ }
483
+
484
+ if (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
485
+ String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase(Locale.ROOT);
486
+
487
+ if (rawRequestBodyArray != null) {
488
+ requestType = RequestType.Form;
489
+ } else if (cType == null || cType.isEmpty()) {
490
+ if (!cType.equalsIgnoreCase("")) {
491
+ builder.header("Content-Type", "application/octet-stream");
492
+ }
493
+ requestType = RequestType.SingleFile;
494
+ }
495
+ if (rawRequestBody != null) {
496
+ if (rawRequestBody.startsWith(ReactNativeBlobUtilConst.FILE_PREFIX)
497
+ || rawRequestBody.startsWith(ReactNativeBlobUtilConst.CONTENT_PREFIX)) {
498
+ requestType = RequestType.SingleFile;
499
+ } else if (cType.toLowerCase(Locale.ROOT).contains(";base64") || cType.toLowerCase(Locale.ROOT).startsWith("application/octet")) {
500
+ cType = cType.replace(";base64", "").replace(";BASE64", "");
501
+ if (mheaders.containsKey("content-type"))
502
+ mheaders.put("content-type", cType);
503
+ if (mheaders.containsKey("Content-Type"))
504
+ mheaders.put("Content-Type", cType);
505
+ requestType = RequestType.SingleFile;
506
+ } else {
507
+ requestType = RequestType.AsIs;
508
+ }
509
+ }
510
+ } else {
511
+ requestType = RequestType.WithoutBody;
512
+ }
513
+
514
+ boolean isChunkedRequest = getHeaderIgnoreCases(mheaders, "Transfer-Encoding").equalsIgnoreCase("chunked");
515
+
516
+ // set request body
517
+ switch (requestType) {
518
+ case SingleFile:
519
+ requestBody = new ReactNativeBlobUtilBody(taskId)
520
+ .chunkedEncoding(isChunkedRequest)
521
+ .setRequestType(requestType)
522
+ .setBody(rawRequestBody)
523
+ .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
524
+ builder.method(method, requestBody);
525
+ break;
526
+ case AsIs:
527
+ requestBody = new ReactNativeBlobUtilBody(taskId)
528
+ .chunkedEncoding(isChunkedRequest)
529
+ .setRequestType(requestType)
530
+ .setBody(rawRequestBody)
531
+ .setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type")));
532
+ builder.method(method, requestBody);
533
+ break;
534
+ case Form:
535
+ String boundary = "ReactNativeBlobUtil-" + taskId;
536
+ requestBody = new ReactNativeBlobUtilBody(taskId)
537
+ .chunkedEncoding(isChunkedRequest)
538
+ .setRequestType(requestType)
539
+ .setBody(rawRequestBodyArray)
540
+ .setMIME(MediaType.parse("multipart/form-data; boundary=" + boundary));
541
+ builder.method(method, requestBody);
542
+ break;
543
+
544
+ case WithoutBody:
545
+ if (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) {
546
+ builder.method(method, RequestBody.create(null, new byte[0]));
547
+ } else
548
+ builder.method(method, null);
549
+ break;
550
+ }
551
+
552
+ // #156 fix cookie issue
553
+ final Request req = builder.build();
554
+ clientBuilder.addNetworkInterceptor(new Interceptor() {
555
+ @NonNull
556
+ @Override
557
+ public Response intercept(@NonNull Chain chain) throws IOException {
558
+ redirects.add(chain.request().url().toString());
559
+ return chain.proceed(chain.request());
560
+ }
561
+ });
562
+ // Add request interceptor for upload progress event
563
+ clientBuilder.addInterceptor(new Interceptor() {
564
+ @NonNull
565
+ @Override
566
+ public Response intercept(@NonNull Chain chain) throws IOException {
567
+ Response originalResponse = null;
568
+ try {
569
+ originalResponse = chain.proceed(req);
570
+ ResponseBody extended;
571
+ switch (responseType) {
572
+ case KeepInMemory:
573
+ extended = new ReactNativeBlobUtilDefaultResp(
574
+ ReactNativeBlobUtilImpl.RCTContext,
575
+ taskId,
576
+ originalResponse.body(),
577
+ options.increment);
578
+ break;
579
+ case FileStorage:
580
+ extended = new ReactNativeBlobUtilFileResp(
581
+ ReactNativeBlobUtilImpl.RCTContext,
582
+ taskId,
583
+ originalResponse.body(),
584
+ destPath,
585
+ options.overwrite);
586
+ break;
587
+ default:
588
+ extended = new ReactNativeBlobUtilDefaultResp(
589
+ ReactNativeBlobUtilImpl.RCTContext,
590
+ taskId,
591
+ originalResponse.body(),
592
+ options.increment);
593
+ break;
594
+ }
595
+ return originalResponse.newBuilder().body(extended).build();
596
+ } catch (SocketException e) {
597
+ timeout = true;
598
+ if (originalResponse != null) {
599
+ originalResponse.close();
600
+ }
601
+ } catch (SocketTimeoutException e) {
602
+ timeout = true;
603
+ if (originalResponse != null) {
604
+ originalResponse.close();
605
+ }
606
+ //ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil error when sending request : " + e.getLocalizedMessage());
607
+ } catch (Exception ex) {
608
+ if (originalResponse != null) {
609
+ originalResponse.close();
610
+ }
611
+ }
612
+
613
+ return chain.proceed(chain.request());
614
+ }
615
+ });
616
+
617
+
618
+ if (options.timeout >= 0) {
619
+ clientBuilder.connectTimeout(options.timeout, TimeUnit.MILLISECONDS);
620
+ clientBuilder.readTimeout(options.timeout, TimeUnit.MILLISECONDS);
621
+ }
622
+
623
+ clientBuilder.connectionPool(pool);
624
+ clientBuilder.retryOnConnectionFailure(false);
625
+ clientBuilder.followRedirects(options.followRedirect);
626
+ clientBuilder.followSslRedirects(options.followRedirect);
627
+ clientBuilder.retryOnConnectionFailure(true);
628
+
629
+ OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build();
630
+
631
+ Call call = client.newCall(req);
632
+ taskTable.put(taskId, call);
633
+ call.enqueue(new okhttp3.Callback() {
634
+
635
+ @Override
636
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
637
+ cancelTask(taskId);
638
+ if (respInfo == null) {
639
+ respInfo = Arguments.createMap();
640
+ }
641
+
642
+ // check if this error caused by socket timeout
643
+ if (e.getClass().equals(SocketTimeoutException.class)) {
644
+ respInfo.putBoolean("timeout", true);
645
+ invoke_callback("The request timed out.", null, null);
646
+ } else
647
+ invoke_callback(e.getLocalizedMessage(), null, null);
648
+ releaseTaskResource();
649
+ }
650
+
651
+ @Override
652
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
653
+ ReadableMap notifyConfig = options.addAndroidDownloads;
654
+ // Download manager settings
655
+ if (notifyConfig != null) {
656
+ String title = "", desc = "", mime = "text/plain";
657
+ boolean scannable = false, notification = false;
658
+ if (notifyConfig.hasKey("title"))
659
+ title = options.addAndroidDownloads.getString("title");
660
+ if (notifyConfig.hasKey("description"))
661
+ desc = notifyConfig.getString("description");
662
+ if (notifyConfig.hasKey("mime"))
663
+ mime = notifyConfig.getString("mime");
664
+ if (notifyConfig.hasKey("mediaScannable"))
665
+ scannable = notifyConfig.getBoolean("mediaScannable");
666
+ if (notifyConfig.hasKey("notification"))
667
+ notification = notifyConfig.getBoolean("notification");
668
+ DownloadManager dm = (DownloadManager) ReactNativeBlobUtilImpl.RCTContext.getSystemService(ReactNativeBlobUtilImpl.RCTContext.DOWNLOAD_SERVICE);
669
+ dm.addCompletedDownload(title, desc, scannable, mime, destPath, contentLength, notification);
670
+ }
671
+
672
+ done(response);
673
+ }
674
+ });
675
+
676
+
677
+ } catch (Exception error) {
678
+ error.printStackTrace();
679
+ releaseTaskResource();
680
+ invoke_callback("ReactNativeBlobUtil request error: " + error.getMessage() + error.getCause());
681
+ }
682
+ }
683
+
684
+ /**
685
+ * Remove cached information of the HTTP task
686
+ */
687
+ private void releaseTaskResource() {
688
+ if (taskTable.containsKey(taskId))
689
+ taskTable.remove(taskId);
690
+ if (androidDownloadManagerTaskTable.containsKey(taskId))
691
+ androidDownloadManagerTaskTable.remove(taskId);
692
+ if (uploadProgressReport.containsKey(taskId))
693
+ uploadProgressReport.remove(taskId);
694
+ if (progressReport.containsKey(taskId))
695
+ progressReport.remove(taskId);
696
+ if (requestBody != null)
697
+ requestBody.clearRequestBody();
698
+ }
699
+
700
+ /**
701
+ * Send response data back to javascript context.
702
+ *
703
+ * @param resp OkHttp response object
704
+ */
705
+ private void done(Response resp) {
706
+ boolean isBlobResp = isBlobResponse(resp);
707
+ WritableMap respmap = getResponseInfo(resp,isBlobResp);
708
+ emitStateEvent(respmap.copy());
709
+
710
+ emitStateEvent(getResponseInfo(resp, isBlobResp));
711
+ switch (responseType) {
712
+ case KeepInMemory:
713
+ try {
714
+ // For XMLHttpRequest, automatic response data storing strategy, when response
715
+ // data is considered as binary data, write it to file system
716
+ if (isBlobResp && options.auto) {
717
+ String dest = ReactNativeBlobUtilFS.getTmpPath(taskId);
718
+ InputStream ins = resp.body().byteStream();
719
+ FileOutputStream os = new FileOutputStream(new File(dest));
720
+ int read;
721
+ byte[] buffer = new byte[10240];
722
+ while ((read = ins.read(buffer)) != -1) {
723
+ os.write(buffer, 0, read);
724
+ }
725
+ ins.close();
726
+ os.flush();
727
+ os.close();
728
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, dest, respmap.copy());
729
+ }
730
+ // response data directly pass to JS context as string.
731
+ else {
732
+ byte[] b = resp.body().bytes();
733
+ // If process file option is turned on, we first keep response in memory and then stream that content
734
+ // after processing
735
+ if (this.shouldTransformFile()) {
736
+ if (ReactNativeBlobUtilFileTransformer.sharedFileTransformer == null) {
737
+ throw new IllegalStateException("Write file with transform was specified but the shared file transformer is not set");
738
+ }
739
+ this.destPath = this.destPath.replace("?append=true", "");
740
+ File file = new File(this.destPath);
741
+ if (!file.exists()) {
742
+ file.createNewFile();
743
+ }
744
+ try (FileOutputStream fos = new FileOutputStream(file)) {
745
+ fos.write(ReactNativeBlobUtilFileTransformer.sharedFileTransformer.onWriteFile(b));
746
+ } catch (Exception e) {
747
+ invoke_callback("Error from file transformer:" + e.getLocalizedMessage(), respmap.copy());
748
+ return;
749
+ }
750
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, this.destPath, respmap.copy());
751
+ return;
752
+ }
753
+ if (responseFormat == ResponseFormat.BASE64) {
754
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP), respmap.copy());
755
+ return;
756
+ }
757
+ try {
758
+ // Attempt to decode the incoming response data to determine whether it contains a valid UTF8 string
759
+ Charset charSet = Charset.forName("UTF-8");
760
+ CharsetDecoder decoder = charSet.newDecoder();
761
+ decoder.decode(ByteBuffer.wrap(b));
762
+ // If the data contains invalid characters the following lines will be skipped.
763
+ String utf8 = new String(b, charSet);
764
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, utf8);
765
+ }
766
+ // This usually means the data contains invalid unicode characters but still valid data,
767
+ // it's binary data, so send it as a normal string
768
+ catch (CharacterCodingException ignored) {
769
+ if (responseFormat == ResponseFormat.UTF8) {
770
+ String utf8 = new String(b);
771
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, utf8, respmap.copy());
772
+ } else {
773
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP), respmap.copy());
774
+ }
775
+ }
776
+ }
777
+ } catch (IOException e) {
778
+ invoke_callback("ReactNativeBlobUtil failed to encode response data to BASE64 string.", respmap.copy());
779
+ }
780
+ break;
781
+ case FileStorage:
782
+ ResponseBody responseBody = resp.body();
783
+
784
+ try {
785
+ // In order to write response data to `destPath` we have to invoke this method.
786
+ // It uses customized response body which is able to report download progress
787
+ // and write response data to destination path.
788
+ responseBody.bytes();
789
+ } catch (Exception ignored) {
790
+ // ignored.printStackTrace();
791
+ }
792
+
793
+ ReactNativeBlobUtilFileResp ReactNativeBlobUtilFileResp;
794
+
795
+ try {
796
+ ReactNativeBlobUtilFileResp = (ReactNativeBlobUtilFileResp) responseBody;
797
+ } catch (ClassCastException ex) {
798
+ // unexpected response type
799
+ if (responseBody != null) {
800
+ String responseBodyString = null;
801
+ try {
802
+ boolean isBufferDataExists = responseBody.source().buffer().size() > 0;
803
+ boolean isContentExists = responseBody.contentLength() > 0;
804
+ if (isBufferDataExists && isContentExists) {
805
+ responseBodyString = responseBody.string();
806
+ }
807
+ } catch (IOException exception) {
808
+ exception.printStackTrace();
809
+ }
810
+ invoke_callback("Unexpected FileStorage response file: " + responseBodyString, respmap.copy());
811
+ } else {
812
+ invoke_callback("Unexpected FileStorage response with no file.", respmap.copy());
813
+ }
814
+ return;
815
+ }
816
+
817
+ if (ReactNativeBlobUtilFileResp != null && !ReactNativeBlobUtilFileResp.isDownloadComplete()) {
818
+ invoke_callback("Download interrupted.", respmap.copy());
819
+ } else {
820
+ this.destPath = this.destPath.replace("?append=true", "");
821
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, this.destPath, respmap.copy());
822
+ }
823
+
824
+ break;
825
+ default:
826
+ try {
827
+ invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_UTF8, new String(resp.body().bytes(), "UTF-8"), respmap.copy());
828
+ } catch (IOException e) {
829
+ invoke_callback("ReactNativeBlobUtil failed to encode response data to UTF8 string.", respmap.copy());
830
+ }
831
+ break;
832
+ }
833
+ // if(!resp.isSuccessful())
834
+ resp.body().close();
835
+ releaseTaskResource();
836
+ }
837
+
838
+ /**
839
+ * Invoke this method to enable download progress reporting.
840
+ *
841
+ * @param taskId Task ID of the HTTP task.
842
+ * @return Task ID of the target task
843
+ */
844
+ public static ReactNativeBlobUtilProgressConfig getReportProgress(String taskId) {
845
+ if (!progressReport.containsKey(taskId)) return null;
846
+ return progressReport.get(taskId);
847
+ }
848
+
849
+ /**
850
+ * Invoke this method to enable download progress reporting.
851
+ *
852
+ * @param taskId Task ID of the HTTP task.
853
+ * @return Task ID of the target task
854
+ */
855
+ public static ReactNativeBlobUtilProgressConfig getReportUploadProgress(String taskId) {
856
+ if (!uploadProgressReport.containsKey(taskId)) return null;
857
+ return uploadProgressReport.get(taskId);
858
+ }
859
+
860
+ /**
861
+ * Create response information object, contains status code, headers, etc.
862
+ *
863
+ * @param resp Response object
864
+ * @param isBlobResp If the response is binary data
865
+ * @return Get RCT bridge object contains response information.
866
+ */
867
+ private WritableMap getResponseInfo(Response resp, boolean isBlobResp) {
868
+ WritableMap info = Arguments.createMap();
869
+ info.putInt("status", resp.code());
870
+ info.putString("state", "2");
871
+ info.putString("taskId", this.taskId);
872
+ info.putBoolean("timeout", timeout);
873
+ WritableMap headers = Arguments.createMap();
874
+ for (int i = 0; i < resp.headers().size(); i++) {
875
+ headers.putString(resp.headers().name(i), resp.headers().value(i));
876
+ }
877
+ WritableArray redirectList = Arguments.createArray();
878
+ for (String r : redirects) {
879
+ redirectList.pushString(r);
880
+ }
881
+ info.putArray("redirects", redirectList);
882
+ info.putMap("headers", headers);
883
+ Headers h = resp.headers();
884
+ if (isBlobResp) {
885
+ info.putString("respType", "blob");
886
+ } else if (getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/")) {
887
+ info.putString("respType", "text");
888
+ } else if (getHeaderIgnoreCases(h, "content-type").contains("application/json")) {
889
+ info.putString("respType", "json");
890
+ } else {
891
+ info.putString("respType", "");
892
+ }
893
+ return info;
894
+ }
895
+
896
+ /**
897
+ * Check if response data is binary data.
898
+ *
899
+ * @param resp OkHttp response.
900
+ * @return If the response data contains binary bytes
901
+ */
902
+ private boolean isBlobResponse(Response resp) {
903
+ Headers h = resp.headers();
904
+ String ctype = getHeaderIgnoreCases(h, "Content-Type");
905
+ boolean isText = !ctype.equalsIgnoreCase("text/");
906
+ boolean isJSON = !ctype.equalsIgnoreCase("application/json");
907
+ boolean isCustomBinary = false;
908
+ if (options.binaryContentTypes != null) {
909
+ for (int i = 0; i < options.binaryContentTypes.size(); i++) {
910
+ if (ctype.toLowerCase(Locale.ROOT).contains(options.binaryContentTypes.getString(i).toLowerCase(Locale.ROOT))) {
911
+ isCustomBinary = true;
912
+ break;
913
+ }
914
+ }
915
+ }
916
+ return (!(isJSON || isText)) || isCustomBinary;
917
+ }
918
+
919
+ private String getHeaderIgnoreCases(Headers headers, String field) {
920
+ String val = headers.get(field);
921
+ if (val != null) return val;
922
+ return headers.get(field.toLowerCase(Locale.ROOT)) == null ? "" : headers.get(field.toLowerCase(Locale.ROOT));
923
+ }
924
+
925
+ private String getHeaderIgnoreCases(HashMap<String, String> headers, String field) {
926
+ String val = headers.get(field);
927
+ if (val != null) return val;
928
+ String lowerCasedValue = headers.get(field.toLowerCase(Locale.ROOT));
929
+ return lowerCasedValue == null ? "" : lowerCasedValue;
930
+ }
931
+
932
+ private void emitStateEvent(WritableMap args) {
933
+ ReactNativeBlobUtilImpl.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
934
+ .emit(ReactNativeBlobUtilConst.EVENT_HTTP_STATE, args);
935
+ }
936
+
937
+ @Override
938
+ public void onReceive(Context context, Intent intent) {
939
+ String action = intent.getAction();
940
+ if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
941
+ Context appCtx = ReactNativeBlobUtilImpl.RCTContext.getApplicationContext();
942
+ long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
943
+ if (id == this.downloadManagerId) {
944
+ releaseTaskResource(); // remove task ID from task map
945
+
946
+ DownloadManager.Query query = new DownloadManager.Query();
947
+ query.setFilterById(downloadManagerId);
948
+ DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
949
+ dm.query(query);
950
+ Cursor c = dm.query(query);
951
+ if (c == null) {
952
+ this.invoke_callback("Download manager failed to download from " + this.url + ". Query was unsuccessful ", null, null);
953
+ return;
954
+ }
955
+
956
+ String filePath = null;
957
+ try {
958
+ // the file exists in media content database
959
+ if (c.moveToFirst()) {
960
+ int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
961
+ if (statusCode == DownloadManager.STATUS_FAILED) {
962
+ this.invoke_callback("Download manager failed to download from " + this.url + ". Status Code = " + statusCode, null, null);
963
+ return;
964
+ }
965
+ String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
966
+ if (contentUri != null) {
967
+ Uri uri = Uri.parse(contentUri);
968
+ Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Files.FileColumns.DATA}, null, null, null);
969
+ // use default destination of DownloadManager
970
+ if (cursor != null) {
971
+ cursor.moveToFirst();
972
+ filePath = cursor.getString(0);
973
+ cursor.close();
974
+ }
975
+ }
976
+ }
977
+ } finally {
978
+ if (c != null) {
979
+ c.close();
980
+ }
981
+ }
982
+
983
+ // When the file is not found in media content database, check if custom path exists
984
+ if (options.addAndroidDownloads.hasKey("path") || options.addAndroidDownloads.hasKey("storeLocal")) {
985
+ try {
986
+ String customDest = customPath;
987
+ boolean exists = new File(customDest).exists();
988
+ if (!exists)
989
+ throw new Exception("Download manager download failed, the file does not downloaded to destination.");
990
+ else
991
+ this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, customDest);
992
+
993
+ } catch (Exception ex) {
994
+ ex.printStackTrace();
995
+ this.invoke_callback(ex.getLocalizedMessage(), null);
996
+ }
997
+ }
998
+ else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && options.addAndroidDownloads.hasKey("storeInDownloads") && options.addAndroidDownloads.getBoolean("storeInDownloads")){
999
+ Uri downloadeduri = dm.getUriForDownloadedFile(downloadManagerId);
1000
+ if(downloadeduri == null)
1001
+ this.invoke_callback("Download manager could not resolve downloaded file uri.", ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, null);
1002
+ else
1003
+ this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, downloadeduri.toString());
1004
+ }
1005
+ else {
1006
+ if (filePath == null)
1007
+ this.invoke_callback("Download manager could not resolve downloaded file path.", ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, null);
1008
+ else
1009
+ this.invoke_callback(null, ReactNativeBlobUtilConst.RNFB_RESPONSE_PATH, filePath);
1010
+ }
1011
+
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
1017
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
1018
+ try {
1019
+ // Code from https://github.com/square/okhttp/issues/2372#issuecomment-244807676
1020
+ SSLContext sc = SSLContext.getInstance("TLSv1.2");
1021
+ sc.init(null, null, null);
1022
+ client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));
1023
+
1024
+ ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
1025
+ .tlsVersions(TlsVersion.TLS_1_2)
1026
+ .build();
1027
+
1028
+ List<ConnectionSpec> specs = new ArrayList<>();
1029
+ specs.add(cs);
1030
+ specs.add(ConnectionSpec.COMPATIBLE_TLS);
1031
+ specs.add(ConnectionSpec.CLEARTEXT);
1032
+
1033
+ client.connectionSpecs(specs);
1034
+ } catch (Exception exc) {
1035
+ FLog.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
1036
+ }
1037
+ }
1038
+
1039
+ return client;
1040
+ }
1041
+
1042
+ /**
1043
+ * Check if a network is a valid connected WiFi network
1044
+ */
1045
+ private boolean isValidWifiNetwork(ConnectivityManager cm, Network network) {
1046
+ NetworkInfo netInfo = cm.getNetworkInfo(network);
1047
+ NetworkCapabilities caps = cm.getNetworkCapabilities(network);
1048
+
1049
+ if (caps == null || netInfo == null) {
1050
+ return false;
1051
+ }
1052
+
1053
+ if (!netInfo.isConnected()) {
1054
+ return false;
1055
+ }
1056
+
1057
+ return caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
1058
+ }
1059
+
1060
+ /**
1061
+ * Check if a network matches the target host IP address
1062
+ */
1063
+ private boolean networkMatchesTargetIp(ConnectivityManager cm, Network network, String targetHostIp, InetAddress targetHostAddr) {
1064
+ LinkProperties lp = cm.getLinkProperties(network);
1065
+ if (lp == null) return false;
1066
+
1067
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
1068
+ // For Android 11 and above, use DHCP server address
1069
+ Inet4Address dhcpServer = lp.getDhcpServerAddress();
1070
+ if (dhcpServer != null && dhcpServer.getHostAddress().equals(targetHostIp)) {
1071
+ return true;
1072
+ }
1073
+ }
1074
+
1075
+ // For older versions or Android 11+ if DHCP does not match, check routing table
1076
+ if (targetHostAddr != null) {
1077
+ List<RouteInfo> routes = lp.getRoutes();
1078
+ if (routes != null) {
1079
+ for (RouteInfo route : routes) {
1080
+ if (route.isDefaultRoute()) {
1081
+ continue;
1082
+ }
1083
+ if (route.matches(targetHostAddr)) {
1084
+ return true;
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ return false;
1091
+ }
1092
+ }