3
votes

I'm trying to use Amazon S3 for the storage of my files. Also I want to use FineUploader for uploading images to Amazon (I'm using Zend Framework).

This is my view:

<div id="fineuploader-s3"></div>

And this is my javascript file: (changed keys/bucket name)

$(document).ready(function () {
$('#fineuploader-s3').fineUploaderS3({
    request: {
        // REQUIRED: We are using a custom domain
        // for our S3 bucket, in this case.  You can
        // use any valid URL that points to your bucket.
        endpoint: "mybucket.s3.amazonaws.com",

        // REQUIRED: The AWS public key for the client-side user
        // we provisioned.
        accessKey: "mykey"
    },

    // REQUIRED: Path to our local server where requests
    // can be signed.
    signature: {
        endpoint: "/s3/s3demo.php"
    },

    // OPTIONAL: An endopint for Fine Uploader to POST to
    // after the file has been successfully uploaded.
    // Server-side, we can declare this upload a failure
    // if something is wrong with the file.
    uploadSuccess: {
        endpoint: "/s3demo.php?success"
    },

    // USUALLY REQUIRED: Blank file on the same domain
    // as this page, for IE9 and older support.
    iframeSupport: {
        localBlankPagePath: "/server/success.html"
    },

    // optional feature
    retry: {
        showButton: true
    },

    // optional feature
    chunking: {
        enabled: true
    },

    // optional feature
    resume: {
        enabled: true
    },

    // optional feature
    deleteFile: {
        enabled: true,
        method: "POST",
        endpoint: "/s3demo.php"
    },

    // optional feature
    validation: {
        itemLimit: 5,
        sizeLimit: 15000000
    }
});
});

This is my 's3demo.php' file in the folder s3 (in public file, root file): (changed the keys/bucketname)

<?php

// You can remove these two lines if you are not using Fine Uploader's
// delete file feature
require 'AWSSDKforPHP/aws.phar';
use Aws\S3\S3Client;

// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = $_SERVER['mykey'];
// These two keys are only needed if the delete file feature is enabled
// or if you are, for example, confirming the file size in a successEndpoint
// handler via S3's SDK, as we are doing in this example.
//$serverPublicKey = $_SERVER['PARAM1'];
//$serverPrivateKey = $_SERVER['PARAM2'];

// The following variables are used when validating the policy document
// sent by the uploader. 
$expectedBucketName = "mybucket.s3.amazonaws.com";
// $expectedMaxSize is the value you set the sizeLimit property of the 
// validation option. We assume it is `null` here. If you are performing
// validation, then change this to match the integer value you specified
// otherwise your policy document will be invalid.
// http://docs.fineuploader.com/branch/develop/api/options.html#validation-option
$expectedMaxSize = null;

$method = getRequestMethod();

// This second conditional will only ever evaluate to true if
// the delete file feature is enabled
if ($method == "DELETE") {
    deleteObject();
}
// This is all you really need if not using the delete file feature
// and not working in a CORS environment
else if ($method == 'POST') {

    // Assumes the successEndpoint has a parameter of "success" associated with it,
    // to allow the server to differentiate between a successEndpoint request
    // and other POST requests (all requests are sent to the same endpoint in this example).
    // This condition is not needed if you don't require a callback on upload success.
    if (isset($_REQUEST["success"])) {
        verifyFileInS3();
    }
    else {
        signRequest();
    }
}

// This will retrieve the "intended" request method.  Normally, this is the
// actual method of the request.  Sometimes, though, the intended request method
// must be hidden in the parameters of the request.  For example, when attempting to
// send a DELETE request in a cross-origin environment in IE9 or older, it is not
// possible to send a DELETE request.  So, we send a POST with the intended method,
// DELETE, in a "_method" parameter.
function getRequestMethod() {

    if ($_POST['_method'] != null) {
        return $_POST['_method'];
    }

    return $_SERVER['REQUEST_METHOD'];
}

function getS3Client() {
    global $serverPublicKey, $serverPrivateKey;

    return S3Client::factory(array(
        'key' => $serverPublicKey,
        'secret' => $serverPrivateKey
    ));
}

// Only needed if the delete file feature is enabled
function deleteObject() {
    getS3Client()->deleteObject(array(
        'Bucket' => $_POST['bucket'],
        'Key' => $_POST['key']
    ));
}

function signRequest() {
    header('Content-Type: application/json');

    $responseBody = file_get_contents('php://input');
    $contentAsObject = json_decode($responseBody, true);
    $jsonContent = json_encode($contentAsObject);

    $headersStr = $contentAsObject["headers"];
    if ($headersStr) {
        signRestRequest($headersStr);
    }
    else {
        signPolicy($jsonContent);
    }
}

function signRestRequest($headersStr) {
    if (isValidRestRequest($headersStr)) {
        $response = array('signature' => sign($headersStr));
        echo json_encode($response);
    }
    else {
        echo json_encode(array("invalid" => true));
    }
}

function isValidRestRequest($headersStr) {
    global $expectedBucketName;

    $pattern = "/\/$expectedBucketName\/.+$/";
    preg_match($pattern, $headersStr, $matches);

    return count($matches) > 0;
}

function signPolicy($policyStr) {
    $policyObj = json_decode($policyStr, true);

    if (isPolicyValid($policyObj)) {
        $encodedPolicy = base64_encode($policyStr);
        $response = array('policy' => $encodedPolicy, 'signature' => sign($encodedPolicy));
        echo json_encode($response);
    }
    else {
        echo json_encode(array("invalid" => true));
    }
}

function isPolicyValid($policy) {
    global $expectedMaxSize, $expectedBucketName;

    $conditions = $policy["conditions"];
    $bucket = null;
    $parsedMaxSize = null;

    for ($i = 0; $i < count($conditions); ++$i) {
        $condition = $conditions[$i];

        if (isset($condition["bucket"])) {
            $bucket = $condition["bucket"];
        }
        else if (isset($condition[0]) && $condition[0] == "content-length-range") {
            $parsedMaxSize = $condition[2];
        }
    }

    return $bucket == $expectedBucketName && $parsedMaxSize == (string)$expectedMaxSize;
}

function sign($stringToSign) {
    global $clientPrivateKey;

    return base64_encode(hash_hmac(
            'sha1',
            $stringToSign,
            $clientPrivateKey,
            true
        ));
}

// This is not needed if you don't require a callback on upload success.
function verifyFileInS3() {
    global $expectedMaxSize;

    $bucket = $_POST["bucket"];
    $key = $_POST["key"];

    // If utilizing CORS, we return a 200 response with the error message in the body
    // to ensure Fine Uploader can parse the error message in IE9 and IE8,
    // since XDomainRequest is used on those browsers for CORS requests.  XDomainRequest
    // does not allow access to the response body for non-success responses.
    if (getObjectSize($bucket, $key) > $expectedMaxSize) {
        // You can safely uncomment this next line if you are not depending on CORS
        header("HTTP/1.0 500 Internal Server Error");
        deleteObject();
        echo json_encode(array("error" => "File is too big!"));
    }
    else {
        echo json_encode(array("tempLink" => getTempLink($bucket, $key)));
    }
}

// Provide a time-bombed public link to the file.
function getTempLink($bucket, $key) {
    $client = getS3Client();
    $url = "{$bucket}/{$key}";
    $request = $client->get($url);

    return $client->createPresignedUrl($request, '+15 minutes');
}

function getObjectSize($bucket, $key) {
    $objInfo = getS3Client()->headObject(array(
            'Bucket' => $bucket,
            'Key' => $key
        ));
    return $objInfo['ContentLength'];
}
?>

I get this errors:

[FineUploader 3.8.0] Error attempting to parse signature response: SyntaxError: Unexpected token < s3.jquery.fineuploader-3.8.0.min.js:16
[FineUploader 3.8.0] Received an empty or invalid response from the server! s3.jquery.fineuploader-3.8.0.min.js:16
[FineUploader 3.8.0] Policy signing failed. Received an empty or invalid response from the server! s3.jquery.fineuploader-3.8.0.min.js:16

The first error shows a problem with my s3demo.php file ... (token < -> first of my php file)

Response - Header:

Request URL:http://www.link.com/s3/s3demo.php
Request Method:POST
Status Code:200 OK
Request Headersview source
Accept:/
Accept-Encoding:gzip,deflate,sdch
Accept-Language:nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:no-cache
Connection:keep-alive
Content-Length:292
Content-Type:application/json; charset=UTF-8
Cookie:PHPSESSID=
Host:www.link.com
Origin:http://www.link.com
Pragma:no-cache
Referer:http://www.link.com/quiz/design
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36
Request Payloadview source
{expiration:2013-08-28T15:35:56.160Z,…}
conditions: [{acl:private}, {bucket:mybucket}, {Content-Type:image/png}, {success_action_status:200},…] expiration: "2013-08-28T15:35:56.160Z"
Response Headers view source
Connection:Keep-Alive
Content-Length:2504
Content-Type:application/json
Date:Wed, 28 Aug 2013 15:30:55 GMT
Keep-Alive:timeout=5, max=90
Server:Apache/2.2.22 (Debian)
X-Powered-By:PHP/5.5.1-1~dotdeb.1

Actual Response:


( ! ) Notice: Undefined index: _method in /var/www/site/public/s3/s3demo.php on line 79 Call Stack #TimeMemoryFunctionLocation 10.0047235456{main}( )../s3demo.php:0 20.0323825744getRequestMethod( )../s3demo.php:48
( ! ) Notice: Undefined index: headers in s3/s3demo.php on line 110 Call Stack #TimeMemoryFunctionLocation 10.0047235456{main}( )../s3demo.php:0 20.0325825896signRequest( )../s3demo.php:67 {"invalid":true}

1

1 Answers

1
votes

I don't think you quite understood the documentation or the examples. You need a server side component to sign the requests that Fine Uploader sends. It looks like you are pointing Fine Uploader at a JSON file, for some reason. I'm guessing you copied the example signature file from the blog post and are pointing Fine Uploader at that? The example signature file was detailed in the blog post to give you some insight into how Fine Uploader S3 generates the policy document for simple uploads for you. You don't need to store a copy of this anywhere.

You need a proper server side component to sign the requests Fine Uploader sends to S3, at the very least. There are already 5 fully functional server side examples for Fine Uploader S3 in https://github.com/Widen/fine-uploader-server. In fact, the blog post links to examples written in node, python, php, and java. You should also really take a much closer look at the blog post or the documentation.