简体   繁体   中英

cordova-plugin-file-transfer: How do you upload a file to S3 using a signed URL?

I am able to upload to S3 using a file picker and regular XMLHttpRequest (which I was using to test the S3 setup), but cannot figure out how to do it successfully using the cordova file transfer plugin.

I believe it is either to do with the plugin not constructing the correct signable request, or not liking the local file uri given. I have tried playing with every single parameter from headers to uri types, but the docs aren't much help, and the plugin source is bolognese.

The string the request needs to sign match is like:

PUT


1391784394
x-amz-acl:public-read
/the-app/317fdf654f9e3299f238d97d39f10fb1

Any ideas, or possibly a working code example?

A bit late, but I just spent a couple of days struggling with this so in case anybody else is having problems, this is how managed to upload an image using the javascript version of the AWS SDK to create the presigned URL.

The key to solving the problem is in the StringToSign element of the XML SignatureDoesNotMatch error that comes back from Amazon. In my case it looked something like this:

<StringToSign>
    PUT\n\nmultipart/form-data; boundary=+++++org.apache.cordova.formBoundary\n1481366396\n/bucketName/fileName.jpg
</StringToSign>

When you use the aws-sdk to generate a presigned URL for upload to S3, internally it will build a string based on various elements of the request you want to make, then create an SHA1 hash of it using your AWS secret. This hash is the signature that gets appended to the URL as a parameter, and what doesn't match when you get the SignatureDoesNotMatch error.

So you've created your presigned URL, and passed it to cordova-plugin-file-transfer to make your HTTP request to upload a file. When that request hits Amazon's server, the server will itself build a string based on the request headers etc, hash it and compare that hash to the signature on the URL. If the hashes don't match then it returns the dreaded...

The request signature we calculated does not match the signature you provided. Check your key and signing method.

The contents of the StringToSign element I mentioned above is the string that the server builds and hashes to compare against the signature on the presigned URL. So to avoid getting the error, you need to make sure that the string built by the aws-sdk is the same as the one built by the server.

After some digging about, I eventually found the code responsible for creating the string to hash in the aws-sdk . It is located (as of version 2.7.12) in:

node_modules/aws-sdk/lib/signers/s3.js

Down the bottom at line 168 there is a sign method:

sign: function sign(secret, string) { return AWS.util.crypto.hmac(secret, string, 'base64', 'sha1'); }

If you put a console.log in there, string is what you're after. Once you make the string that gets passed into this method the same as the contents of StringToSign in the error message coming back from Amazon, the heavens will open and your files will flow effortlessly into your bucket.

On my server running node.js, I originally created my presigned URL like this:

var AWS = require('aws-sdk');

var s3 = new AWS.S3(options = {
    endpoint: 'https://s3-eu-west-1.amazonaws.com',
    accessKeyId: "ACCESS_KEY",
    secretAccessKey: "SECRET_KEY"
});

var params = {
    Bucket: 'bucketName',
    Key: imageName,
    Expires: 60
};

var signedUrl = s3.getSignedUrl('putObject', params);

//return signedUrl

This produced a signing string like this, similar to the OP's:

PUT


1481366396
/bucketName/fileName.jpg

On the client side, I used this presigned URL with cordova-plugin-file-transfer like so (I'm using Ionic 2 so the plugin is wrapped in their native wrapper):

let success = (result: any) : void => { 
    console.log("upload success");
}

let failed = (err: any) : void => {
    let code = err.code;
    alert("upload error - " + code);
}

let ft = new Transfer();

var options = {
    fileName: filename,
    mimeType: 'image/jpeg',
    chunkedMode: false,
    httpMethod:'PUT',
    encodeURI: false,
};

ft.upload(localDataURI, presignedUrlFromServer, options, false)
.then((result: any) => {
    success(result);
}).catch((error: any) => {
    failed(error);
});

Running the code produced the signature doesn't match error, and the string in the <StringToSign> element looks like this:

PUT

multipart/form-data; boundary=+++++org.apache.cordova.formBoundary
1481366396
/bucketName/fileName.jpg

So we can see that cordova-plugin-file-transfer has added in its own Content-Type header which has caused a discrepancy in the signing strings. In the docs relating to the options object that get passed into the upload method it says:

headers: A map of header name/header values. Use an array to specify more than one value. On iOS, FireOS, and Android, if a header named Content-Type is present, multipart form data will NOT be used. (Object)

so basically, if no Content-Type header is set it will default to multipart form data.

Ok so now we know the cause of the problem, it's a pretty simple fix. On the server side I added a ContentType to the params object passed to the S3 getSignedUrl method:

var params = {
    Bucket: 'bucketName',
    Key: imageName,
    Expires: 60,
    ContentType: 'image/jpeg' // <---- content type added here
};

and on the client added a headers object to the options passed to cordova-plugin-file-transfer 's upload method:

var options = {
    fileName: filename,
    mimeType: 'image/jpeg',
    chunkedMode: false,
    httpMethod:'PUT',
    encodeURI: false,
    headers: { // <----- headers object added here
        'Content-Type': 'image/jpeg',
    }
};

and hey presto! The uploads now work as expected.

I run into such issues with this plugin

The only working way I found to upload a file with a signature is the method of Christophe Coenraets : http://coenraets.org/blog/2013/09/how-to-upload-pictures-from-a-phonegap-app-to-amazon-s3/

With this method you will be able to upload your files using the cordova-plugin-file-transfer

First, I wanted to use the aws-sdk on my server to sign with getSignedUrl() It returns the signed link and you only have to upload to it.

But, using the plugin it always end with 403 : signatures don't match

It may be related to the content length parameter but I didn't found for now a working solution with aws-sdk and the plugin

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM