Skip to content
This repository has been archived by the owner on Mar 16, 2019. It is now read-only.

Support content URIs properly for uploads on Android #546

Open
wants to merge 28 commits into
base: 0.11.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8121dbf
Apply possible fix to fs.readFile and fs.readStream for #287
wkh237 Mar 27, 2017
4d15c0f
Merge branch '0.10.5' into issue-287-wip
wkh237 Apr 16, 2017
40fefd4
Add Android fs.readFile app provider URI support #287
wkh237 Apr 16, 2017
50a4d06
Merge branch 'issue-287-merged' into uri
wkh237 Jul 2, 2017
a8cfeb1
Correct app content provider URI handle #287
wkh237 Jul 2, 2017
bb7fec4
Merge branch 'issue-287' of github.com:wkh237/react-native-fetch-blob…
wkh237 Jul 2, 2017
08f8403
Fixed a bug which causes XMLHttpRequest getting incorrect header when…
wkh237 Jul 3, 2017
c217fab
Merge branch 'uri' into 0.10.7
wkh237 Jul 3, 2017
8a75a9b
Set mime if set in addAndroidDownloads (#421)
jsm Jul 3, 2017
ed2732a
Fix Download Manager bug when the file is not a multimedia #391
wkh237 Jul 5, 2017
dd4dbb2
Merge branch '0.10.7' of github.com:wkh237/react-native-fetch-blob in…
wkh237 Jul 5, 2017
6bb7c65
Add support for TLS 1.2 when running Android 4 (#430)
jhellman Jul 14, 2017
b70a124
Update README.md
wkh237 Jul 14, 2017
712c8a3
[iOS] Fix for RNFetchBlob.writeChunk failing to write base64 encoded …
joshbax Jul 17, 2017
d83d800
Add missing API Blob.safeClose()
wkh237 Jul 25, 2017
d1d07d0
Fix Compilation Error in React Native 0.47.0 (#452)
Hizoul Aug 2, 2017
206588f
Merge branch 'master' of github.com:wkh237/react-native-fetch-blob in…
wkh237 Aug 2, 2017
6bde516
Bump to 0.10.7
wkh237 Aug 2, 2017
5f3c018
Merge branch '0.10.7' of github.com:wkh237/react-native-fetch-blob in…
wkh237 Aug 2, 2017
50c1573
Update PULL_REQUEST_TEMPLATE
wkh237 Aug 2, 2017
55009f1
Correct unterminated string #455
wkh237 Aug 3, 2017
9ab4ebb
bump to 0.10.8
bcpclone Aug 3, 2017
1336555
Update PULL_REQUEST_TEMPLATE
wkh237 Aug 3, 2017
2aea0b5
Fix path argument in iOS excludeFromBackupKey (#473)
grylance Aug 9, 2017
821eeb0
Fix README (#501)
moschan Aug 24, 2017
c7b9e2e
Handle content URIs correctly
Sep 20, 2017
703880b
Remove unused warning event
tombailey Sep 29, 2017
c7f0c23
Don't enforce app label on library consumer
tombailey Jan 24, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Thank you for making a pull request ! Just a gentle reminder :)

1. If the PR is offering a feature please make the request to our "Feature Branch" 0.11.0
2. Bug fix request to "Bug Fix Branch" 0.10.7
2. Bug fix request to "Bug Fix Branch" 0.10.9
3. Correct README.md can directly to master
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ A project committed to making file access and data transfer easier and more effi
* [Android Media Scanner, and Download Manager Support](#user-content-android-media-scanner-and-download-manager-support)
* [Self-Signed SSL Server](#user-content-self-signed-ssl-server)
* [Transfer Encoding](#user-content-transfer-encoding)
* [RNFetchBlob as Fetch](#user-content-rnfetchblob-as-fetch)
* [Drop-in Fetch Replacement](#user-content-drop-in-fetch-replacement)
* [File System](#user-content-file-system)
* [File access](#user-content-file-access)
* [File stream](#user-content-file-stream)
* [Manage cached files](#user-content-cache-file-management)
* [Web API Polyfills](#user-content-web-api-polyfills)
* [Performance Tips](#user-content-performance-tipsd)
* [Performance Tips](#user-content-performance-tips)
* [API References](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API)
* [Caveats](#user-content-caveats)
* [Development](#user-content-development)
Expand Down Expand Up @@ -236,7 +236,7 @@ RNFetchBlob
console.log('The file saved to ', res.path())
// Beware that when using a file path as Image source on Android,
// you must prepend "file://"" before the file path
imageView = <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path() : '' + res.path() }}/>
imageView = <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path() : '' + res.path() }}/>
})
```

Expand Down Expand Up @@ -452,11 +452,11 @@ task.cancel((err) => { ... })

```

### RNFetchBlob as Fetch
### Drop-in Fetch Replacement

0.9.0

If you have existing code that uses `whatwg-fetch`(the official **fetch**), you don't have to change them after 0.9.0, just use fetch replacement. The difference between Official fetch and fetch replacement is, official fetch uses [whatwg-fetch](https://github.com/github/fetch) js library which wraps XMLHttpRequest polyfill under the hood it's a great library for web developers, however that does not play very well with RN. Our implementation is simply a wrapper of RNFetchBlob.fetch and fs APIs, so you can access all the features we provide.
If you have existing code that uses `whatwg-fetch`(the official **fetch**), it's not necessary to replace them with `RNFetchblob.fetch`, you can simply use our **Fetch Replacement**. The difference between Official them is official fetch uses [whatwg-fetch](https://github.com/github/fetch) which wraps XMLHttpRequest polyfill under the hood. It's a great library for web developers, but does not play very well with RN. Our implementation is simply a wrapper of our `fetch` and `fs` APIs, so you can access all the features we provided.

[See document and examples](https://github.com/wkh237/react-native-fetch-blob/wiki/Fetch-API#fetch-replacement)

Expand Down Expand Up @@ -613,6 +613,8 @@ In `v0.5.0` we've added `writeStream` and `readStream`, which allows your app r

When calling `readStream` method, you have to `open` the stream, and start to read data. When the file is large, consider using an appropriate `bufferSize` and `interval` to reduce the native event dispatching overhead (see [Performance Tips](#user-content-performance-tips))

> The file stream event has a default throttle(10ms) and buffer size which preventing it cause too much overhead to main thread, yo can also [tweak these values](#user-content-performance-tips).

```js
let data = ''
RNFetchBlob.fs.readStream(
Expand Down
6 changes: 0 additions & 6 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.RNFetchBlob">

<application
android:label="@string/app_name">

</application>

</manifest>
32 changes: 32 additions & 0 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.RNFetchBlob;

import android.net.Uri;
import android.util.Base64;

import com.facebook.react.bridge.Arguments;
Expand All @@ -21,8 +22,13 @@
import okhttp3.RequestBody;
import okio.BufferedSink;

import static com.RNFetchBlob.RNFetchBlobConst.CONTENT_PREFIX;

public class RNFetchBlobBody extends RequestBody{

private static final int BYTE_SKIP_LENGTH = 1024 * 1024; //1024 * 1024 = 1MB


InputStream requestStream;
long contentLength = 0;
ReadableArray form;
Expand Down Expand Up @@ -160,6 +166,14 @@ private InputStream getReuqestStream() throws Exception {
}
}
}
else if (rawBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
try {
String contentUri = rawBody.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
return RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(contentUri));
} catch (IOException e) {
throw new Exception("error when getting request stream from content uri: " +e.getLocalizedMessage());
}
}
// base 64 encoded
else {
try {
Expand Down Expand Up @@ -226,6 +240,16 @@ private File createMultipartBodyCache() throws IOException {
}
}
}
else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
try {
String contentUri = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
InputStream inputStream = ctx.getContentResolver().openInputStream(Uri.parse(contentUri));

pipeStreamToFileStream(inputStream, os);
} catch (IOException e) {
RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
}
}
// base64 embedded file content
else {
byte[] b = Base64.decode(data, 0);
Expand Down Expand Up @@ -324,6 +348,14 @@ else if (field.filename != null) {
File file = new File(RNFetchBlobFS.normalizePath(orgPath));
total += file.length();
}
} else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
try {
String contentUri = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
long length = ctx.getContentResolver().openInputStream(Uri.parse(contentUri)).available();
total += length;
} catch (IOException e) {
RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
}
}
// base64 embedded file content
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public class RNFetchBlobConst {
public static final String RNFB_RESPONSE_UTF8 = "utf8";
public static final String RNFB_RESPONSE_PATH = "path";
public static final Integer GET_CONTENT_INTENT = 99900;
public static final String CONTENT_PREFIX = "RNFetchBlob-content://";

}
29 changes: 23 additions & 6 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobFS.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,28 @@ static public void writeFile(String path, ReadableArray data, final boolean appe
* @param promise
*/
static public void readFile(String path, String encoding, final Promise promise ) {
path = normalizePath(path);
String resolved = normalizePath(path);
if(resolved != null)
path = resolved;
try {
byte[] bytes;

if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
if(resolved != null && resolved.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
String assetName = path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
long length = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
bytes = new byte[(int) length];
InputStream in = RNFetchBlob.RCTContext.getAssets().open(assetName);
in.read(bytes, 0, (int) length);
in.close();
}
// issue 287
else if(resolved == null) {
InputStream in = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
int length = (int) in.available();
bytes = new byte[length];
in.read(bytes);
in.close();
}
else {
File f = new File(path);
int length = (int) f.length();
Expand Down Expand Up @@ -226,17 +236,24 @@ static public String getTmpPath(ReactApplicationContext ctx, String taskId) {
* @param bufferSize Buffer size of read stream, default to 4096 (4095 when encode is `base64`)
*/
public void readStream(String path, String encoding, int bufferSize, int tick, final String streamId) {
path = normalizePath(path);
String resolved = normalizePath(path);
if(resolved != null)
path = resolved;
try {

int chunkSize = encoding.equalsIgnoreCase("base64") ? 4095 : 4096;
if(bufferSize > 0)
chunkSize = bufferSize;

InputStream fs;
if(path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
fs = RNFetchBlob.RCTContext.getAssets()
.open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));

if(resolved != null && path.startsWith(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET)) {
fs = RNFetchBlob.RCTContext.getAssets().open(path.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, ""));

}
// fix issue 287
else if(resolved == null) {
fs = RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(path));
}
else {
fs = new FileInputStream(new File(path));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
return modules;
}

@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
Expand Down
49 changes: 46 additions & 3 deletions android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.util.Base64;

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;
Expand All @@ -21,6 +23,7 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.TLSSocketFactory;

import java.io.File;
import java.io.FileOutputStream;
Expand All @@ -35,11 +38,14 @@
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;

import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.ConnectionPool;
import okhttp3.ConnectionSpec;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
Expand All @@ -48,6 +54,8 @@
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.TlsVersion;


public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {

Expand Down Expand Up @@ -148,8 +156,15 @@ public void run() {
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") == true ) {
req.allowScanningByMediaScanner();
}
while (it.hasNextKey()) {
String key = it.nextKey();
req.addRequestHeader(key, headers.getString(key));
Expand Down Expand Up @@ -359,9 +374,10 @@ public Response intercept(Chain chain) throws IOException {
clientBuilder.retryOnConnectionFailure(false);
clientBuilder.followRedirects(options.followRedirect);
clientBuilder.followSslRedirects(options.followRedirect);
clientBuilder.retryOnConnectionFailure(true);

OkHttpClient client = enableTls12OnPreLollipop(clientBuilder).build();

OkHttpClient client = clientBuilder.retryOnConnectionFailure(true).build();
Call call = client.newCall(req);
taskTable.put(taskId, call);
call.enqueue(new okhttp3.Callback() {
Expand Down Expand Up @@ -636,16 +652,20 @@ public void onReceive(Context context, Intent intent) {
return;
}
String contentUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (contentUri != null) {
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

// use default destination of DownloadManager
if (cursor != null) {
cursor.moveToFirst();
filePath = cursor.getString(0);
}
}
}

// When the file is not found in media content database, check if custom path exists
if (options.addAndroidDownloads.hasKey("path")) {
try {
Expand All @@ -672,5 +692,28 @@ public void onReceive(Context context, Intent intent) {
}
}

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 {
client.sslSocketFactory(new TLSSocketFactory());

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;
}


}
22 changes: 18 additions & 4 deletions android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ else if (isMediaDocument(uri)) {

return getDataColumn(context, contentUri, selection, selectionArgs);
}
else if ("content".equalsIgnoreCase(uri.getScheme())) {

// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();

return getDataColumn(context, uri, null, null);
}
// Other Providers
else {
else{
try {
InputStream attachment = context.getContentResolver().openInputStream(uri);
if (attachment != null) {
Expand Down Expand Up @@ -131,6 +139,7 @@ public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {

Cursor cursor = null;
String result = null;
final String column = "_data";
final String[] projection = {
column
Expand All @@ -141,13 +150,18 @@ public static String getDataColumn(Context context, Uri uri, String selection,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
result = cursor.getString(index);
}
} finally {
}
catch (Exception ex) {
ex.printStackTrace();
return null;
}
finally {
if (cursor != null)
cursor.close();
}
return null;
return result;
}


Expand Down
6 changes: 5 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ if(!RNFetchBlob || !RNFetchBlob.fetchBlobForm || !RNFetchBlob.fetchBlob) {
}

function wrap(path:string):string {
return 'RNFetchBlob-file://' + path
if (path.startsWith('content://')) {
return 'RNFetchBlob-content://' + path
} else {
return 'RNFetchBlob-file://' + path
}
}

/**
Expand Down
Loading