Last active
December 31, 2019 11:31
-
-
Save nikilarigela/9b74b652e4db11a68d7d9252af782337 to your computer and use it in GitHub Desktop.
rn-fetch-blob -> android -> src -> java -> RNFetchBlobReq.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.RNFetchBlob; | |
import android.app.DownloadManager; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
import android.database.Cursor; | |
import android.net.Uri; | |
import android.os.Build; | |
import android.support.annotation.NonNull; | |
import android.util.Base64; | |
import android.util.Log; | |
import com.RNFetchBlob.Response.RNFetchBlobDefaultResp; | |
import com.RNFetchBlob.Response.RNFetchBlobFileResp; | |
import com.facebook.common.logging.FLog; | |
import com.facebook.react.bridge.Arguments; | |
import com.facebook.react.bridge.Callback; | |
import com.facebook.react.bridge.ReactApplicationContext; | |
import com.facebook.react.bridge.ReadableArray; | |
import com.facebook.react.bridge.ReadableMap; | |
import com.facebook.react.bridge.ReadableMapKeySetIterator; | |
import com.facebook.react.bridge.WritableArray; | |
import com.facebook.react.bridge.WritableMap; | |
import com.facebook.react.modules.core.DeviceEventManagerModule; | |
import javax.net.ssl.TrustManagerFactory; | |
import javax.net.ssl.TrustManager; | |
import javax.net.ssl.X509TrustManager; | |
import javax.net.ssl.SSLContext; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.net.MalformedURLException; | |
import java.net.SocketException; | |
import java.net.SocketTimeoutException; | |
import java.net.URL; | |
import java.nio.ByteBuffer; | |
import java.nio.charset.CharacterCodingException; | |
import java.nio.charset.Charset; | |
import java.nio.charset.CharsetEncoder; | |
import java.security.KeyStore; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.HashMap; | |
import java.util.concurrent.TimeUnit; | |
import javax.net.ssl.SSLSocketFactory; | |
import okhttp3.Call; | |
import okhttp3.ConnectionPool; | |
import okhttp3.ConnectionSpec; | |
import okhttp3.Headers; | |
import okhttp3.Interceptor; | |
import okhttp3.MediaType; | |
import okhttp3.OkHttpClient; | |
import okhttp3.Request; | |
import okhttp3.RequestBody; | |
import okhttp3.Response; | |
import okhttp3.ResponseBody; | |
import okhttp3.TlsVersion; | |
public class RNFetchBlobReq extends BroadcastReceiver implements Runnable { | |
enum RequestType { | |
Form, | |
SingleFile, | |
AsIs, | |
WithoutBody, | |
Others | |
} | |
enum ResponseType { | |
KeepInMemory, | |
FileStorage | |
} | |
enum ResponseFormat { | |
Auto, | |
UTF8, | |
BASE64 | |
} | |
public static HashMap<String, Call> taskTable = new HashMap<>(); | |
public static HashMap<String, Long> androidDownloadManagerTaskTable = new HashMap<>(); | |
static HashMap<String, RNFetchBlobProgressConfig> progressReport = new HashMap<>(); | |
static HashMap<String, RNFetchBlobProgressConfig> uploadProgressReport = new HashMap<>(); | |
static ConnectionPool pool = new ConnectionPool(); | |
RNFetchBlobConfig options; | |
String taskId; | |
String method; | |
String url; | |
String rawRequestBody; | |
String destPath; | |
ReadableArray rawRequestBodyArray; | |
ReadableMap headers; | |
Callback callback; | |
long contentLength; | |
long downloadManagerId; | |
RNFetchBlobBody requestBody; | |
RequestType requestType; | |
ResponseType responseType; | |
ResponseFormat responseFormat = ResponseFormat.Auto; | |
WritableMap respInfo; | |
boolean timeout = false; | |
ArrayList<String> redirects = new ArrayList<>(); | |
OkHttpClient client; | |
public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, OkHttpClient client, final Callback callback) { | |
this.method = method.toUpperCase(); | |
this.options = new RNFetchBlobConfig(options); | |
this.taskId = taskId; | |
this.url = url; | |
this.headers = headers; | |
this.callback = callback; | |
this.rawRequestBody = body; | |
this.rawRequestBodyArray = arrayBody; | |
this.client = client; | |
if(this.options.fileCache || this.options.path != null) | |
responseType = ResponseType.FileStorage; | |
else | |
responseType = ResponseType.KeepInMemory; | |
if (body != null) | |
requestType = RequestType.SingleFile; | |
else if (arrayBody != null) | |
requestType = RequestType.Form; | |
else | |
requestType = RequestType.WithoutBody; | |
} | |
public static void cancelTask(String taskId) { | |
if(taskTable.containsKey(taskId)) { | |
Call call = taskTable.get(taskId); | |
call.cancel(); | |
taskTable.remove(taskId); | |
} | |
if (androidDownloadManagerTaskTable.containsKey(taskId)) { | |
long downloadManagerIdForTaskId = androidDownloadManagerTaskTable.get(taskId).longValue(); | |
Context appCtx = RNFetchBlob.RCTContext.getApplicationContext(); | |
DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE); | |
dm.remove(downloadManagerIdForTaskId); | |
} | |
} | |
@Override | |
public void run() { | |
// use download manager instead of default HTTP implementation | |
if (options.addAndroidDownloads != null && options.addAndroidDownloads.hasKey("useDownloadManager")) { | |
if (options.addAndroidDownloads.getBoolean("useDownloadManager")) { | |
Uri uri = Uri.parse(url); | |
DownloadManager.Request req = new DownloadManager.Request(uri); | |
if(options.addAndroidDownloads.getBoolean("notification")) { | |
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); | |
} else { | |
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); | |
} | |
if(options.addAndroidDownloads.hasKey("title")) { | |
req.setTitle(options.addAndroidDownloads.getString("title")); | |
} | |
if(options.addAndroidDownloads.hasKey("description")) { | |
req.setDescription(options.addAndroidDownloads.getString("description")); | |
} | |
if(options.addAndroidDownloads.hasKey("path")) { | |
req.setDestinationUri(Uri.parse("file://" + options.addAndroidDownloads.getString("path"))); | |
} | |
// #391 Add MIME type to the request | |
if(options.addAndroidDownloads.hasKey("mime")) { | |
req.setMimeType(options.addAndroidDownloads.getString("mime")); | |
} | |
// set headers | |
ReadableMapKeySetIterator it = headers.keySetIterator(); | |
if(options.addAndroidDownloads.hasKey("mediaScannable") && options.addAndroidDownloads.hasKey("mediaScannable")) { | |
req.allowScanningByMediaScanner(); | |
} | |
while (it.hasNextKey()) { | |
String key = it.nextKey(); | |
req.addRequestHeader(key, headers.getString(key)); | |
} | |
// On some devices, redownloading a file from the same url returns the same download id. | |
// onReceive never fires, and the promise never resolves. | |
// Workaround: delete the existing entry using the download id. | |
// The new download will have a running count. On some models, the existing file is deleted. | |
Context appCtx = RNFetchBlob.RCTContext.getApplicationContext(); | |
DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE); | |
downloadManagerId = dm.enqueue(req); | |
Log.d("RNFetchBlob", "enqueued to download manager with id " + downloadManagerId); | |
// Check if request already exists | |
DownloadManager.Query query = new DownloadManager.Query(); | |
query.setFilterById(downloadManagerId); | |
Cursor c = dm.query(query); | |
if (c != null && c.moveToFirst()) { | |
int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); | |
if (statusCode == DownloadManager.STATUS_SUCCESSFUL) { | |
Log.d("RNFetchBlobReq", "The previous request has been cached"); | |
dm.remove(downloadManagerId); | |
// Enqueue the new download request | |
downloadManagerId = dm.enqueue(req); | |
Log.d("RNFetchBlob", "enqueued to download manager with new id " + downloadManagerId); | |
} | |
} | |
androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId)); | |
appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); | |
return; | |
} | |
} | |
// find cached result if `key` property exists | |
String cacheKey = this.taskId; | |
String ext = this.options.appendExt.isEmpty() ? "" : "." + this.options.appendExt; | |
if (this.options.key != null) { | |
cacheKey = RNFetchBlobUtils.getMD5(this.options.key); | |
if (cacheKey == null) { | |
cacheKey = this.taskId; | |
} | |
File file = new File(RNFetchBlobFS.getTmpPath(cacheKey) + ext); | |
if (file.exists()) { | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, file.getAbsolutePath()); | |
return; | |
} | |
} | |
if(this.options.path != null) | |
this.destPath = this.options.path; | |
else if(this.options.fileCache) | |
this.destPath = RNFetchBlobFS.getTmpPath(cacheKey) + ext; | |
OkHttpClient.Builder clientBuilder; | |
try { | |
// use trusty SSL socket | |
if (this.options.trusty) { | |
clientBuilder = RNFetchBlobUtils.getUnsafeOkHttpClient(client); | |
} else { | |
clientBuilder = client.newBuilder(); | |
} | |
final Request.Builder builder = new Request.Builder(); | |
try { | |
builder.url(new URL(url)); | |
} catch (MalformedURLException e) { | |
e.printStackTrace(); | |
} | |
HashMap<String, String> mheaders = new HashMap<>(); | |
// set headers | |
if (headers != null) { | |
ReadableMapKeySetIterator it = headers.keySetIterator(); | |
while (it.hasNextKey()) { | |
String key = it.nextKey(); | |
String value = headers.getString(key); | |
if(key.equalsIgnoreCase("RNFB-Response")) { | |
if(value.equalsIgnoreCase("base64")) | |
responseFormat = ResponseFormat.BASE64; | |
else if (value.equalsIgnoreCase("utf8")) | |
responseFormat = ResponseFormat.UTF8; | |
} | |
else { | |
builder.header(key.toLowerCase(), value); | |
mheaders.put(key.toLowerCase(), value); | |
} | |
} | |
} | |
if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) { | |
String cType = getHeaderIgnoreCases(mheaders, "Content-Type").toLowerCase(); | |
if(rawRequestBodyArray != null) { | |
requestType = RequestType.Form; | |
} | |
else if(cType.isEmpty()) { | |
if(!cType.equalsIgnoreCase("")) { | |
builder.header("Content-Type", "application/octet-stream"); | |
} | |
requestType = RequestType.SingleFile; | |
} | |
if(rawRequestBody != null) { | |
if (rawRequestBody.startsWith(RNFetchBlobConst.FILE_PREFIX) | |
|| rawRequestBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) { | |
requestType = RequestType.SingleFile; | |
} | |
else if (cType.toLowerCase().contains(";base64") || cType.toLowerCase().startsWith("application/octet")) { | |
cType = cType.replace(";base64","").replace(";BASE64",""); | |
if(mheaders.containsKey("content-type")) | |
mheaders.put("content-type", cType); | |
if(mheaders.containsKey("Content-Type")) | |
mheaders.put("Content-Type", cType); | |
requestType = RequestType.SingleFile; | |
} else { | |
requestType = RequestType.AsIs; | |
} | |
} | |
} | |
else { | |
requestType = RequestType.WithoutBody; | |
} | |
boolean isChunkedRequest = getHeaderIgnoreCases(mheaders, "Transfer-Encoding").equalsIgnoreCase("chunked"); | |
// set request body | |
switch (requestType) { | |
case SingleFile: | |
requestBody = new RNFetchBlobBody(taskId) | |
.chunkedEncoding(isChunkedRequest) | |
.setRequestType(requestType) | |
.setBody(rawRequestBody) | |
.setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))); | |
builder.method(method, requestBody); | |
break; | |
case AsIs: | |
requestBody = new RNFetchBlobBody(taskId) | |
.chunkedEncoding(isChunkedRequest) | |
.setRequestType(requestType) | |
.setBody(rawRequestBody) | |
.setMIME(MediaType.parse(getHeaderIgnoreCases(mheaders, "content-type"))); | |
builder.method(method, requestBody); | |
break; | |
case Form: | |
String boundary = "RNFetchBlob-" + taskId; | |
requestBody = new RNFetchBlobBody(taskId) | |
.chunkedEncoding(isChunkedRequest) | |
.setRequestType(requestType) | |
.setBody(rawRequestBodyArray) | |
.setMIME(MediaType.parse("multipart/form-data; boundary="+ boundary)); | |
builder.method(method, requestBody); | |
break; | |
case WithoutBody: | |
if(method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put") || method.equalsIgnoreCase("patch")) | |
{ | |
builder.method(method, RequestBody.create(null, new byte[0])); | |
} | |
else | |
builder.method(method, null); | |
break; | |
} | |
// #156 fix cookie issue | |
final Request req = builder.build(); | |
clientBuilder.addNetworkInterceptor(new Interceptor() { | |
@Override | |
public Response intercept(Chain chain) throws IOException { | |
redirects.add(chain.request().url().toString()); | |
return chain.proceed(chain.request()); | |
} | |
}); | |
// Add request interceptor for upload progress event | |
clientBuilder.addInterceptor(new Interceptor() { | |
@Override | |
public Response intercept(@NonNull Chain chain) throws IOException { | |
try { | |
Response originalResponse = chain.proceed(req); | |
ResponseBody extended; | |
switch (responseType) { | |
case KeepInMemory: | |
extended = new RNFetchBlobDefaultResp( | |
RNFetchBlob.RCTContext, | |
taskId, | |
originalResponse.body(), | |
options.increment); | |
break; | |
case FileStorage: | |
extended = new RNFetchBlobFileResp( | |
RNFetchBlob.RCTContext, | |
taskId, | |
originalResponse.body(), | |
destPath, | |
options.overwrite); | |
break; | |
default: | |
extended = new RNFetchBlobDefaultResp( | |
RNFetchBlob.RCTContext, | |
taskId, | |
originalResponse.body(), | |
options.increment); | |
break; | |
} | |
return originalResponse.newBuilder().body(extended).build(); | |
} | |
catch(SocketException e) { | |
timeout = true; | |
} | |
catch (SocketTimeoutException e ){ | |
timeout = true; | |
RNFetchBlobUtils.emitWarningEvent("RNFetchBlob error when sending request : " + e.getLocalizedMessage()); | |
} catch(Exception ex) { | |
} | |
return chain.proceed(chain.request()); | |
} | |
}); | |
if(options.timeout >= 0) { | |
clientBuilder.connectTimeout(options.timeout, TimeUnit.MILLISECONDS); | |
clientBuilder.readTimeout(options.timeout, TimeUnit.MILLISECONDS); | |
} | |
clientBuilder.connectionPool(pool); | |
clientBuilder.retryOnConnectionFailure(false); | |
clientBuilder.followRedirects(options.followRedirect); | |
clientBuilder.followSslRedirects(options.followRedirect); | |
clientBuilder.retryOnConnectionFailure(true); | |
OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build(); | |
Call call = client.newCall(req); | |
taskTable.put(taskId, call); | |
call.enqueue(new okhttp3.Callback() { | |
@Override | |
public void onFailure(@NonNull Call call, IOException e) { | |
cancelTask(taskId); | |
if(respInfo == null) { | |
respInfo = Arguments.createMap(); | |
} | |
// check if this error caused by socket timeout | |
if(e.getClass().equals(SocketTimeoutException.class)) { | |
respInfo.putBoolean("timeout", true); | |
callback.invoke("request timed out.", null, null); | |
} | |
else | |
callback.invoke(e.getLocalizedMessage(), null, null); | |
releaseTaskResource(); | |
} | |
@Override | |
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { | |
ReadableMap notifyConfig = options.addAndroidDownloads; | |
// Download manager settings | |
if(notifyConfig != null ) { | |
String title = "", desc = "", mime = "text/plain"; | |
boolean scannable = false, notification = false; | |
if(notifyConfig.hasKey("title")) | |
title = options.addAndroidDownloads.getString("title"); | |
if(notifyConfig.hasKey("description")) | |
desc = notifyConfig.getString("description"); | |
if(notifyConfig.hasKey("mime")) | |
mime = notifyConfig.getString("mime"); | |
if(notifyConfig.hasKey("mediaScannable")) | |
scannable = notifyConfig.getBoolean("mediaScannable"); | |
if(notifyConfig.hasKey("notification")) | |
notification = notifyConfig.getBoolean("notification"); | |
DownloadManager dm = (DownloadManager)RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE); | |
dm.addCompletedDownload(title, desc, scannable, mime, destPath, contentLength, notification); | |
} | |
done(response); | |
} | |
}); | |
} catch (Exception error) { | |
error.printStackTrace(); | |
releaseTaskResource(); | |
callback.invoke("RNFetchBlob request error: " + error.getMessage() + error.getCause()); | |
} | |
} | |
/** | |
* Remove cached information of the HTTP task | |
*/ | |
private void releaseTaskResource() { | |
if(taskTable.containsKey(taskId)) | |
taskTable.remove(taskId); | |
if(androidDownloadManagerTaskTable.containsKey(taskId)) | |
androidDownloadManagerTaskTable.remove(taskId); | |
if(uploadProgressReport.containsKey(taskId)) | |
uploadProgressReport.remove(taskId); | |
if(progressReport.containsKey(taskId)) | |
progressReport.remove(taskId); | |
if(requestBody != null) | |
requestBody.clearRequestBody(); | |
} | |
/** | |
* Send response data back to javascript context. | |
* @param resp OkHttp response object | |
*/ | |
private void done(Response resp) { | |
boolean isBlobResp = isBlobResponse(resp); | |
emitStateEvent(getResponseInfo(resp, isBlobResp)); | |
switch (responseType) { | |
case KeepInMemory: | |
try { | |
// For XMLHttpRequest, automatic response data storing strategy, when response | |
// data is considered as binary data, write it to file system | |
if(isBlobResp && options.auto) { | |
String dest = RNFetchBlobFS.getTmpPath(taskId); | |
InputStream ins = resp.body().byteStream(); | |
FileOutputStream os = new FileOutputStream(new File(dest)); | |
int read; | |
byte[] buffer = new byte[10240]; | |
while ((read = ins.read(buffer)) != -1) { | |
os.write(buffer, 0, read); | |
} | |
ins.close(); | |
os.flush(); | |
os.close(); | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, dest); | |
} | |
// response data directly pass to JS context as string. | |
else { | |
// #73 Check if the response data contains valid UTF8 string, since BASE64 | |
// encoding will somehow break the UTF8 string format, to encode UTF8 | |
// string correctly, we should do URL encoding before BASE64. | |
byte[] b = resp.body().bytes(); | |
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); | |
if(responseFormat == ResponseFormat.BASE64) { | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP)); | |
return; | |
} | |
try { | |
encoder.encode(ByteBuffer.wrap(b).asCharBuffer()); | |
// if the data contains invalid characters the following lines will be | |
// skipped. | |
String utf8 = new String(b); | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, utf8); | |
} | |
// This usually mean the data is contains invalid unicode characters, it's | |
// binary data | |
catch(CharacterCodingException ignored) { | |
if(responseFormat == ResponseFormat.UTF8) { | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, ""); | |
} | |
else { | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_BASE64, android.util.Base64.encodeToString(b, Base64.NO_WRAP)); | |
} | |
} | |
} | |
} catch (IOException e) { | |
callback.invoke("RNFetchBlob failed to encode response data to BASE64 string.", null); | |
} | |
break; | |
case FileStorage: | |
try { | |
// In order to write response data to `destPath` we have to invoke this method. | |
// It uses customized response body which is able to report download progress | |
// and write response data to destination path. | |
resp.body().bytes(); | |
} catch (Exception ignored) { | |
// ignored.printStackTrace(); | |
} | |
this.destPath = this.destPath.replace("?append=true", ""); | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, this.destPath); | |
break; | |
default: | |
try { | |
callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_UTF8, new String(resp.body().bytes(), "UTF-8")); | |
} catch (IOException e) { | |
callback.invoke("RNFetchBlob failed to encode response data to UTF8 string.", null); | |
} | |
break; | |
} | |
// if(!resp.isSuccessful()) | |
resp.body().close(); | |
releaseTaskResource(); | |
} | |
/** | |
* Invoke this method to enable download progress reporting. | |
* @param taskId Task ID of the HTTP task. | |
* @return Task ID of the target task | |
*/ | |
public static RNFetchBlobProgressConfig getReportProgress(String taskId) { | |
if(!progressReport.containsKey(taskId)) return null; | |
return progressReport.get(taskId); | |
} | |
/** | |
* Invoke this method to enable download progress reporting. | |
* @param taskId Task ID of the HTTP task. | |
* @return Task ID of the target task | |
*/ | |
public static RNFetchBlobProgressConfig getReportUploadProgress(String taskId) { | |
if(!uploadProgressReport.containsKey(taskId)) return null; | |
return uploadProgressReport.get(taskId); | |
} | |
/** | |
* Create response information object, contains status code, headers, etc. | |
* @param resp Response object | |
* @param isBlobResp If the response is binary data | |
* @return Get RCT bridge object contains response information. | |
*/ | |
private WritableMap getResponseInfo(Response resp, boolean isBlobResp) { | |
WritableMap info = Arguments.createMap(); | |
info.putInt("status", resp.code()); | |
info.putString("state", "2"); | |
info.putString("taskId", this.taskId); | |
info.putBoolean("timeout", timeout); | |
WritableMap headers = Arguments.createMap(); | |
for(int i =0;i< resp.headers().size();i++) { | |
headers.putString(resp.headers().name(i), resp.headers().value(i)); | |
} | |
WritableArray redirectList = Arguments.createArray(); | |
for(String r : redirects) { | |
redirectList.pushString(r); | |
} | |
info.putArray("redirects", redirectList); | |
info.putMap("headers", headers); | |
Headers h = resp.headers(); | |
if(isBlobResp) { | |
info.putString("respType", "blob"); | |
} | |
else if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/")) { | |
info.putString("respType", "text"); | |
} | |
else if(getHeaderIgnoreCases(h, "content-type").contains("application/json")) { | |
info.putString("respType", "json"); | |
} | |
else { | |
info.putString("respType", ""); | |
} | |
return info; | |
} | |
/** | |
* Check if response data is binary data. | |
* @param resp OkHttp response. | |
* @return If the response data contains binary bytes | |
*/ | |
private boolean isBlobResponse(Response resp) { | |
Headers h = resp.headers(); | |
String ctype = getHeaderIgnoreCases(h, "Content-Type"); | |
boolean isText = !ctype.equalsIgnoreCase("text/"); | |
boolean isJSON = !ctype.equalsIgnoreCase("application/json"); | |
boolean isCustomBinary = false; | |
if(options.binaryContentTypes != null) { | |
for(int i = 0; i< options.binaryContentTypes.size();i++) { | |
if(ctype.toLowerCase().contains(options.binaryContentTypes.getString(i).toLowerCase())) { | |
isCustomBinary = true; | |
break; | |
} | |
} | |
} | |
return (!(isJSON || isText)) || isCustomBinary; | |
} | |
private String getHeaderIgnoreCases(Headers headers, String field) { | |
String val = headers.get(field); | |
if(val != null) return val; | |
return headers.get(field.toLowerCase()) == null ? "" : headers.get(field.toLowerCase()); | |
} | |
private String getHeaderIgnoreCases(HashMap<String,String> headers, String field) { | |
String val = headers.get(field); | |
if(val != null) return val; | |
String lowerCasedValue = headers.get(field.toLowerCase()); | |
return lowerCasedValue == null ? "" : lowerCasedValue; | |
} | |
private void emitStateEvent(WritableMap args) { | |
RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) | |
.emit(RNFetchBlobConst.EVENT_HTTP_STATE, args); | |
} | |
@Override | |
public void onReceive(Context context, Intent intent) { | |
String action = intent.getAction(); | |
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { | |
Context appCtx = RNFetchBlob.RCTContext.getApplicationContext(); | |
long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); | |
// handle downloads by download manager id | |
if (id == this.downloadManagerId) { | |
releaseTaskResource(); // remove task ID from task map | |
DownloadManager.Query query = new DownloadManager.Query(); | |
query.setFilterById(downloadManagerId); | |
DownloadManager dm = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE); | |
dm.query(query); | |
Cursor c = dm.query(query); | |
// #236 unhandled null check for DownloadManager.query() return value | |
if (c == null) { | |
this.callback.invoke("Download manager failed to download from " + this.url + ". Query was unsuccessful ", null, null); | |
return; | |
} | |
String contentUri = null; | |
String filePath = null; | |
try { | |
// the file exists in media content database | |
if (c.moveToFirst()) { | |
// #297 handle failed request | |
int statusCode = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); | |
if(statusCode == DownloadManager.STATUS_FAILED) { | |
this.callback.invoke("Download manager failed to download from " + this.url + ". Status Code = " + statusCode, null, null); | |
return; | |
} | |
contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); | |
if ( contentUri != null && | |
options.addAndroidDownloads.hasKey("mime") && | |
options.addAndroidDownloads.getString("mime").contains("image")) { | |
Uri uri = Uri.parse(contentUri); | |
Cursor cursor = appCtx.getContentResolver().query(uri, new String[]{android.provider.MediaStore.Images.ImageColumns.DATA}, null, null, null); | |
// use default destination of DownloadManager | |
if (cursor != null) { | |
cursor.moveToFirst(); | |
filePath = cursor.getString(0); | |
cursor.close(); | |
} | |
} | |
} | |
} finally { | |
if (c != null) { | |
c.close(); | |
} | |
} | |
// When the file is not found in media content database, check if custom path exists | |
if (options.addAndroidDownloads.hasKey("path")) { | |
try { | |
String customDest = options.addAndroidDownloads.getString("path"); | |
// if not overwriting, we always return the path provided by download manager as the authoritative uri to access the file | |
if (options.addAndroidDownloads.hasKey("overwrite") | |
&& !options.addAndroidDownloads.getBoolean("overwrite") | |
&& contentUri != null) { | |
customDest = contentUri.replace("file://", ""); | |
} | |
boolean exists = new File(customDest).exists(); | |
if(!exists) | |
throw new Exception("Download manager download failed, the file does not downloaded to destination."); | |
else | |
this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, customDest); | |
} catch(Exception ex) { | |
ex.printStackTrace(); | |
this.callback.invoke(ex.getLocalizedMessage(), null); | |
} | |
} | |
// only handle callback successfully if is an image (with valid file path) | |
// any other file types without user-specified path will not work | |
else { | |
if(filePath == null) | |
this.callback.invoke("Download manager could not resolve downloaded file path.", RNFetchBlobConst.RNFB_RESPONSE_PATH, null); | |
else | |
this.callback.invoke(null, RNFetchBlobConst.RNFB_RESPONSE_PATH, filePath); | |
} | |
// Unregister listener after callback is resolved to avoid single callback invocation error | |
appCtx.unregisterReceiver(this); | |
} | |
} | |
} | |
public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { | |
try { | |
// Code from https://stackoverflow.com/a/40874952/544779 | |
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
trustManagerFactory.init((KeyStore) null); | |
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); | |
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { | |
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); | |
} | |
X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; | |
SSLContext sslContext = SSLContext.getInstance("SSL"); | |
sslContext.init(null, new TrustManager[] { trustManager }, null); | |
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); | |
client.sslSocketFactory(sslSocketFactory, trustManager); | |
ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) | |
.tlsVersions(TlsVersion.TLS_1_2) | |
.build(); | |
List< ConnectionSpec > specs = new ArrayList < > (); | |
specs.add(cs); | |
specs.add(ConnectionSpec.COMPATIBLE_TLS); | |
specs.add(ConnectionSpec.CLEARTEXT); | |
client.connectionSpecs(specs); | |
} catch (Exception exc) { | |
FLog.e("OkHttpClientProvider", "Error while enabling TLS 1.2", exc); | |
} | |
} | |
return client; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment