react-native-blob-util 0.22.0 → 0.22.2

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