Quiet the old question but it did help me a bit to get it finally done.
My solution is based on PHP and JavaScript with jQuery.
I have the entire solution nicely wrapped at https://github.com/JoernBerkefeld/s3SignedUpload but here are the essentials:
api.php:
<?php
require_once '/server/path/to/aws-autoloader.php';
use Aws\Common\Aws;
$BUCKET = "my-bucket";
$CONFIG = "path-to-iam-credentials-file-relative-to-root.php"
function getSignedUrl($filename, $mime) {
$S3 = Aws::factory( $CONFIG )->get('S3');
if(!$filename) {
return $this->error('filename missing');
}
if(!$mime) {
return $this->error('mime-type missing');
}
$final_filename = $this->get_file_name($filename);
try {
$signedUrl = $S3->getCommand('PutObject', array(
'Bucket' => $BUCKET,
'Key' => $this->folder . $final_filename,
'ContentType' => $mime,
'Body' => '',
'ContentMD5' => false
))->createPresignedUrl('+30 minutes');
} catch (S3Exception $e) {
echo $e->getMessage() . "\n";
}
$signedUrl .= '&Content-Type='.urlencode($mime);
return $signedUrl;
}
echo getSignedUrl($_GET['filename'],$_GET['mimetype']);
please make sure to add user authentication to your api.php. Else everyone who knows the path to that file could upload files to your bucket.
credentials.inc.php:
<?php
return array(
'includes' => array('_aws'),
'services' => array(
'default_settings' => array(
'params' => array(
'key' => 'MY-ACCESS-KEY',
'secret' => 'MY-SECRECT',
'region' => 'eu-west-1' // set to your region
)
)
)
);
client.js:
$("input[type=file]").onchange = function () {
for (var file, i = 0; i < this.files.length; i++) {
file = this.files[i];
$.ajax({
url : s3presignedApiUri,
data: 'file='+ file.name + '&mime=' + file.type,
type : "GET",
dataType : "json",
cache : false,
})
.done(function(s3presignedUrl) {
$.ajax({
url : s3presignedUrl,
type : "PUT",
data : file,
dataType : "text",
cache : false,
contentType : file.type,
processData : false
})
.done(function(){
console.info('YEAH', s3presignedUrl.split('?')[0].substr(6));
}
.fail(function(){
console.error('damn...');
}
})
}
};
s3 cors settings (PUT & OPTIONS are actually needed, but cannot enable OPTIONS directly...):
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Well, if anyone else has any trouble with this like I did, here is the answer, I went into the amazon php development forums and got help from the profesionals.
It seems you may be flip-flopping between Version 2 and Version 3 of the SDK or looking at the wrong document. Make sure you are getting the one you intend to use and are looking at the correct documentation. They are different.
V3
- Composer Requirement: {"aws/aws-sdk-php": "~3.0"}
- User Guide: http://docs.aws.amazon.com/aws-sdk-php/v3/guide/index.html
- API Docs: http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html
- Pre-signed URL Docs: http://docs.aws.amazon.com/aws-sdk-php/v3/guide/service/s3-presigned-url.html
V2
- Composer Requirement: {"aws/aws-sdk-php": "~2.8"}
- User Guide: http://docs.aws.amazon.com/aws-sdk-php/v2/guide/index.html
- API Docs: http://docs.aws.amazon.com/aws-sdk-php/v2/api/index.html
- Pre-signed URL Docs: http://docs.aws.amazon.com/aws-sdk-php/v2/guide/service-s3.html#creating-a-pre-signed-url
Mini step-by-step guide of what you have to do:
1.Install composer, preferably using sudo:
sudo curl -sS https://getcomposer.org/installer | sudo php
2.Go to your project folder and create a composer.json file, with the version you want/need, you can find releases here: https://github.com/aws/aws-sdk-php/releases, commands for each version seem to be very version specific, be careful, this was my main problem.
{
"require": {
"aws/aws-sdk-php": "~3.0"
}
}
3.Then go to your project folder in the terminal, and install sdk via composer and update afterward like: (if you change version you have to update again.)
sudo php composer.phar install
sudo php composer.phar update
4.Then everything is ready for you to follow proper version documentation, in my case for version "aws/aws-sdk-php": "~3.0" and for presigned url, what worked was:
require 'vendor/autoload.php';
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;
$sharedConfig = [
'region' => 'us-west-1',
'version' => 'latest'
]; //I have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables
$s3Client = new Aws\S3\S3Client($sharedConfig);
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => $keyname
]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');
$presignedUrl = (string) $request->getUri();
echo $presignedUrl;
I hope this helps anyone facing the same problems as I did.
Best Answer
The V4 signing protocol offers the option to include arbitrary headers in the signature. See: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html So, if you know the exact Content-Length in advance, you can include that in the signed URL. Based on some experiments with CURL, S3 will truncate the file if you send more than specified in the Content-Length header. Here is an example V4 signature with multiple headers in the signature http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html