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

Uploading image to S3 using presigned URL fails on Android #425

Open
robindowling opened this issue Jul 11, 2017 · 9 comments
Open

Uploading image to S3 using presigned URL fails on Android #425

robindowling opened this issue Jul 11, 2017 · 9 comments

Comments

@robindowling
Copy link

robindowling commented Jul 11, 2017

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 😌

@wkh237
Copy link
Owner

wkh237 commented Jul 12, 2017

@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/....

@robindowling
Copy link
Author

robindowling commented Jul 12, 2017

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".

@wkh237
Copy link
Owner

wkh237 commented Jul 12, 2017

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

@robindowling
Copy link
Author

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? 😌

@wkh237
Copy link
Owner

wkh237 commented Jul 12, 2017

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

@robindowling
Copy link
Author

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.

@wkh237
Copy link
Owner

wkh237 commented Jul 12, 2017

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

@wkh237
Copy link
Owner

wkh237 commented Jul 13, 2017

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.

@robindowling
Copy link
Author

robindowling commented Jul 13, 2017

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 ✊🏼😃

cipriancaba added a commit to cipriancaba/react-native-fetch-blob that referenced this issue Aug 21, 2018
Fixes the issue described here in relation to uploading to S3 wkh237#425
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants