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