React-native-fetch-blob: Uploading image to S3 using presigned URL fails on Android

Created on 11 Jul 2017  ·  9Comments  ·  Source: wkh237/react-native-fetch-blob

I have an implementation using RNFetchBlob.fetch that works flawlessly for iOS but always fails on Android, and I've been banging my head against this wall for a few days now.

I am uploading an image to AWS S3 using a presigned URL. The presigned URL comes from my server and generates new URLs for each request. However, on Android S3 responds with SignatureDoesNotMatch. I have set permissions in my AndroidManifest.xml as well as getting 'granted' back from PermissionsAndroid.request.

This is a simplified version of my code. I removed event logging etc to make things clearer.

setImage(uploadUrl) {
    const options = {mediaType: 'photo', noData: true, maxWidth: 1920, maxHeight: 1920, quality: 0.2};

    const setImage = new Promise((resolve, reject) => {
        return ImagePicker.launchImageLibrary(options, (response) => resolve(response));
    }).then((response) => {
        if (response.didCancel) return;
        if (response.error) throw new Error(response.error);

        const body = 'RNFetchBlob-' + response.uri;
        RNFetchBlob.fetch('PUT', uploadUrl, {'Content-Type': ''}, body).then((result) => {
            console.log('imagepicker RESPONSE');
            console.log(response);
            console.log('BODY');
            console.log(body);
            console.log('UPLOADURL');
            console.log(uploadUrl);
            console.log('RNFetchBlob RESULT');
            console.log(result);
        }).catch((error) => console.log(error));
    }).catch((error) => console.log(error));
}

On iOS (device and simulator), as mentioned, everything works well.
On Android (simulator, Marshmallow 6.0, api 23), this produces the following output:

imagepicker RESPONSE
{
    fileName: "image-874f9bae-d686-4b4e-b89c-a83f3e02b578.jpg",
    fileSize: 46513,
    height: 1024,
    isVertical: true,
    originalRotation: 0,
    path: "/storage/emulated/0/Android/data/com.client/files/Pictures/image-874f9bae-d686-4b4e-b89c-a83f3e02b578.jpg",
    type: "image/jpeg",
    uri: "file:///storage/emulated/0/Android/data/com.client/files/Pictures/image-874f9bae-d686-4b4e-b89c-a83f3e02b578.jpg",
    width: 683
}

BODY
"RNFetchBlob-file:///storage/emulated/0/Android/data/com.client/files/Pictures/image-874f9bae-d686-4b4e-b89c-a83f3e02b578.jpg"

UPLOADURL
"https://REDACTED-FOR-GITHUB.s3.amazonaws.com/production/asset/2ea438b8-0ba1-4680-b040-5493db3aac26/--unverified?Signature=78gf5edjILenQfttVB8ehSO5pvDvo%3D&x-amz-acl=public-read&Expires=1499637874&AWSAccessKeyId=REDACTED-FOR-GITHUB"

RNFetchBlob RESULT
FetchBlobResponse {data: "<?xml version="1.0" encoding="UTF-8"?>↵<Error><Cod…519/AxHazwJv34A4oQzpAg20k53LEP4=</HostId></Error>", taskId: "q742yws2pngu48hzk5bbjg", type: "utf8", respInfo: Object, info: function…}array: function ()base64: function ()blob: function ()data: "<?xml version="1.0" encoding="UTF-8"?>↵<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><AWSAccessKeyId>REDACTED-FOR-GITHUB</AWSAccessKeyId><StringToSign>PUT↵↵application/octet-stream↵1499637117↵x-amz-acl:public-read↵/REDACTED-FOR-GITHUB.s3/production/asset/a0245dd0-f72d-4199-9d29-3bb4de45b19d/--unverified</StringToSign><SignatureProvided> 78gf5edjILenQfttVB8ehSO5pvDvo =</SignatureProvided><StringToSignBytes>50 55 54 0a 0a 61 70 70 6c 69 63 61 74 69 6f 6e 2f 6f 63 74 65 74 2d 73 74 72 65 61 6d 0a 31 34 39 39 32 33 37 31 31 37 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 2f 6d 69 6e 75 74 65 73 2d 73 74 6f 72 61 67 65 2f 70 72 6f 64 75 63 74 69 6f 6e 2f 61 73 71 65 74 2f 61 30 32 34 35 64 64 30 2d 66 37 32 64 2d 34 31 39 39 2d 39 64 32 39 2d 33 62 62 34 64 65 31 35 62 31 39 64 2f 2d 2d 75 6e 76 65 72 45 66 69 65 64</StringToSignBytes><RequestId>BC2909B1F6FA5AB2</RequestId><HostId>H/lwKoVq0GCJB74bslVFiwPhHFZRopoUi7Iv6FDsH+N0519/AxHazwJv34A4oQzpAg20k53LEP4=</HostId></Error>"flush: function ()info: function ()json: function ()path: function ()readFile: function (encode)readStream: function (encode)respInfo: Objectsession: function (name)taskId: "q742yws2pngu48hzk5bbjg"text: function ()type: "utf8"__proto__: Object

Keep in mind that it seems unlikely that there is something wrong with the generated URL since it works 100% on iOS and 0% on Android.

Does anyone know what I'm doing wrong here?
Does anyone have a working solution for uploading images from library and camera on Android to S3 using a presigned URL?

Thank you 😌

android bug ready to merge

Most helpful comment

This issue has been verified which is caused by the Android native code where replace the Content-Type with application/octet-stream when it has an empty string value ( like "" ), will merge branch issue-425 after it passed test cases.

All 9 comments

@robindowling , thanks for reporting the issue, I'm not sure if this is caused by the problem mentioned in #389, on Android, the file path will need a scheme prefix like file:// which makes the URL passed to native context like this:

RNFetchBlob-file://file://storage/emulated/0/Android/data/....

Yes I noticed this, but I am compensating for it by concatenating strings instead of using .wrap. My resulting body looks like this:
"RNFetchBlob-file:///storage/emulated/0/Android/data/com.client/files/Pictures/image-874f9bae-d686-4b4e-b89c-a83f3e02b578.jpg".

Okay, but I think on Android the correct format would be like RNFetchBlob-file://file:///storage/emulate/0/..... not RNFetchBlob-file:///storage/emulate/0/.....

Ok, but I have tried this as well. Using the RNFetchBlob.wrap() produces a RNFetchBlob-file://file:///storage/emulate/0/... string. Using this resulting string as body leads to the same failure as mentioned in the original post.

Do you possibly have a working example of this? Can I provide you with presigned URLs if you wanted to try it out and hopefully prove me wrong? 😌

@robindowling , I'd like to try it if possible 👍 Let's figure out what's happened.

Thank you @wkh237 for being so forthcoming with this, much appreciated!

As mentioned, I can send you presigned URLs for uploading to S3. However these URLs have a TTL and are usable once only so I'd rather not post them publicly.

Should we continue our conversation over email? And then post a working solution in this thread if we are able to find one.

@robindowling , no problem, let's continue this in the email.

This issue has been verified which is caused by the Android native code where replace the Content-Type with application/octet-stream when it has an empty string value ( like "" ), will merge branch issue-425 after it passed test cases.

I can confirm that the change in branch issue-425 solves my issue 100% and I am now able to upload to S3 from Android. The corner case here was that from my understanding, or at least in my implementation, S3 expects the Content-Type to be "", an empty string, not the actual content type.

Kudos to @wkh237 for solving this issue within hours ✊🏼😃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Noitidart picture Noitidart  ·  4Comments

nicholasstephan picture nicholasstephan  ·  3Comments

atasmohammadi picture atasmohammadi  ·  3Comments

gonglong picture gonglong  ·  3Comments

NarendraSingh88 picture NarendraSingh88  ·  3Comments