Fine Uploader to S3 bucket getting 405 Method Not Allowed error - php

I have been banging my head against the wall on this and am entirely stumped. I am trying to use FineUploader to upload files directly to my Amazon S3 bucket. I have essentially copied the code from the fineuploader.com web page (Upload Files Directly to Amazon S3) and the server-side PHP.
When I attempt to upload a file I see the post to the signature endpoint seems to work successfully but when it attempts to upload to S3 I get a 405 "Method Not Allowed" error.
HTML
<!DOCTYPE html>
<html >
<head >
<meta charset = "utf-8" >
<link href = "http://fineuploader.com/source/fineuploader-3.9.1.min.css" rel = "stylesheet" >
</head >
<body >
<div id = "fine-uploader" ></div >
<script src = "http://code.jquery.com/jquery-latest.js" ></script >
<script src = "js/uploader.js" ></script >
<script >
$(document).ready(function () {
$("#fine-uploader").fineUploaderS3({
debug: true,
request: {
endpoint: 'upload.roughdrag.com',
accessKey: 'AKIAJL37USSCV......'
},
signature: {
endpoint: 'handlers/uploadHandler.php'
},
uploadSuccess: {
endpoint: 'index.php'
},
iframeSupport: {
localBlankPagePath: 'blank.html'
},
retry: {
enableAuto: true // defaults to false
},
paste: {
targetElement: $(document),
promptForName: true
},
deleteFile: {
enabled: true,
endpoint: 'handlers/uploadHandler.php'
}
});
});
</script >
</body >
</html >
PHP Signature Endpoint - uploadHandler.php
<?php
/**
* PHP Server-Side Example for Fine Uploader S3.
* Maintained by Widen Enterprises.
*
* Note: This is the exact server-side code used by the S3 example
* on fineuploader.com.
*
* This example:
* - handles both CORS and non-CORS environments
* - handles delete file requests for both DELETE and POST methods
* - Performs basic inspections on the policy documents and REST headers before signing them
* - Ensures again the file size does not exceed the max (after file is in S3)
* - signs policy documents (simple uploads) and REST requests
* (chunked/multipart uploads)
*
* Requirements:
* - PHP 5.3 or newer
* - Amazon PHP SDK (only if utilizing the AWS SDK for deleting files or otherwise examining them)
*
* If you need to install the AWS SDK, see http://docs.aws.amazon.com/aws-sdk-php-2/guide/latest/installation.html.
*/
// You can remove these two lines if you are not using Fine Uploader's
// delete file feature
require('../../includes/functions.php');
use Aws\S3\S3Client;
// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = '{removed}';
// 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 = '{removed}';
$serverPrivateKey = '{removed}';
// The following variables are used when validating the policy document
// sent by the uploader:
$expectedBucketName = "upload.roughdrag.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 = 5000000;
$method = getRequestMethod();
// This first conditional will only ever evaluate to true in a
// CORS environment
if ($method == 'OPTIONS') {
handlePreflight();
} // This second conditional will only ever evaluate to true if
// the delete file feature is enabled
else if ($method == "DELETE") {
handleCorsRequest(); // only needed in a CORS environment
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') {
handleCorsRequest();
// 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()
{
global $HTTP_RAW_POST_DATA;
// This should only evaluate to true if the Content-Type is undefined
// or unrecognized, such as when XDomainRequest has been used to
// send the request.
if (isset($HTTP_RAW_POST_DATA)) {
parse_str($HTTP_RAW_POST_DATA, $_POST);
}
if ($_POST['_method'] != null) {
return $_POST['_method'];
}
return $_SERVER['REQUEST_METHOD'];
}
// Only needed in cross-origin setups
function handleCorsRequest()
{
// If you are relying on CORS, you will need to adjust the allowed domain here.
header('Access-Control-Allow-Origin: http://www.roughdrag.com');
}
// Only needed in cross-origin setups
function handlePreflight()
{
handleCorsRequest();
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
}
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" => "Your 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'];
}
?>
Amazon S3 CORS Configuration
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<ExposeHeader>ETag</ExposeHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
IAM Group Security Policy
{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":"s3:PutObject",
"Resource":"arn:aws:s3:::upload.roughdrag.com/*"
}]
}
uploader.js was captured from http://fineuploader.com/source/all.fineuploader-3.9.1.min.js
Console response
[FineUploader 3.9.0-3] Grabbed 1 dropped files.
[FineUploader 3.9.0-3] Received 1 files or inputs.
[FineUploader 3.9.0-3] Submitting S3 signature request for 0
[FineUploader 3.9.0-3] Sending POST request for 0
POST http://www.roughdrag.com/handlers/uploadHandler.php 200 OK 195ms
[FineUploader 3.9.0-3] Sending upload request for 0
POST http://upload.roughdrag.com/ 405 Method Not Allowed 559ms
"NetworkError: 405 Method Not Allowed - http://upload.roughdrag.com/"
[FineUploader 3.9.0-3] Received response status 405 with body: <html>
<head><title>405 Method Not Allowed</title></head>
<body>
<h1>405 Method Not Allowed</h1>
<ul>
<li>Code: MethodNotAllowed</li>
<li>Message: The specified method is not allowed against this resource.</li>
<li>ResourceType: OBJECT</li>
<li>Method: POST</li>
<li>RequestId: 3493FE605B461EAF</li>
<li>HostId: HDXmtSpHufy6LDIH1Nsp0oYkLDvTC3XKFRRIadw66gmaMsF53Z3WYsCWooOoRcw2</li>
</ul>
<hr/>
</body>
</html>
[FineUploader 3.9.0-3] Waiting 5 seconds before retrying breakout.jpg...
[FineUploader 3.9.0-3] Detected valid cancel, retry, or delete click event on file 'breakout.jpg', ID: 0.
[FineUploader 3.9.0-3] Cancelling 0
The software looks amazing but I just can't get past this. Any help is appreciated.

I'm guessing this is a DNS issue created when you mapped your custom domain name to your S3 bucket. After resolving upload.roughdrag.com, it looks like you have mapped this CNAME to "upload.roughdrag.com.s3-website-us-east-1.amazonaws.com". Try mapping that CNAME to "upload.roughdrag.com.s3.amazaonaws.com" instead.
Update 1:
If you are still seeing issues after this change, I would post in the AWS S3 forums. Hopefully an employee will answer. There may be an issue with your bucket/CNAME that I cannot see from my end. It looks like a POST request to upload.roughdrag.com.s3.amazonaws.com goes though, but there are issues sending a POST request to upload.roughdrag.com. I verified this with Postman.
Update 2:
With your latest CNAME change, it looks like POST requests are being accepted by S3.

I had the same issue. Fixed it by adding the region to the function below:
function getS3Client() {
global $serverPublicKey, $serverPrivateKey;
return S3Client::factory(array(
'key' => $serverPublicKey,
'secret' => $serverPrivateKey,
'region' => 'ap-southeast-2' //Revomve from non Sydney bucket
));
}
Find the region of your bucket by clicking on properties. Then Static website hosting and only take the details from the endpoint from ap-xxxxx-x

Related

Correct code to set storage class on S3 objects when using Transfer

I'm using v3 of the SDK and working out the code to transfer a folder of files from a local drive to s3. Per the documentation for Transfer I'm trying to set up my options array to set the storage class to something other than the default. I found further documentation that suggests the following for setting similar parameters:
$uploader = new Transfer($s3Client, $source, $dest, [
'before' => function (\Aws\Command $command) {
// Commands can vary for multipart uploads, so check which command
// is being processed
if (in_array($command->getName(), ['PutObject', 'CreateMultipartUpload'])) {
// Set custom cache-control metadata
$command['CacheControl'] = 'max-age=3600';
// Apply a canned ACL
$command['ACL'] = strpos($command['Key'], 'CONFIDENTIAL') ### false ? 'public-read' : 'private';
}
},
]);
If I replace the Cache-Control and ACL variable settings with $command['StorageClass'] = 'REDUCED_REDUNDANCY'; I'm still not getting the desired result.
My code looks like this:
$uploader = new Transfer($s3Client, $source, $dest, [
'before' => function (\Aws\Command $command) {
// Commands can vary for multipart uploads, so check which command
// is being processed
if (in_array($command->getName(), ['PutObject', 'CreateMultipartUpload'])) {
$command['StorageClass'] = 'REDUCED_REDUNDANCY';
}
},
]);
What am I doing wrong?

Angular9 http post CORS issue

I am new to Angular9, I have tried to save data by calling a php API from my angular9 application, but geting the following error.
I have test this service from postman, which is working fine and save data successfully
Access to XMLHttpRequest at 'http://127.0.0.1/angularCRUDservices/user/saveEmployee' from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
**Please Note:** get method is working fine to fetch data,
my Angular service:
reqHeader = new HttpHeaders(
{
'Content-Type': 'application/json',
'No-Auth':'True',
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Header':'Access-Control-Allow-Origin,X-Requested-With,Content-Type,Access',
'Access-Control-Allow-Methods':'GET,POST,DELETE,PATCH,PUT,OPTIONS'
}
);
constructor(private httpClient:HttpClient){}
saveEmployee(formData:Employee){
console.log(formData);
this.httpClient.post<{response:any,success:boolean}>("http://127.0.0.1/angularCRUDservices/user/saveEmployee",formData,{
headers : this.reqHeader
})
.subscribe((response)=>{
console.log(response);
})
}
php service code:
setting headers in constructor:
public function __construct()
{
parent::__construct();
$this->load->helper('url');
$this->load->model('user_model','usr');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Header: Access-Control-Allow-Origin,X-Requested-With,Content-Type,Access');
header("Access-Control-Allow-Methods: GET,POST,DELETE,PATCH,PUT,OPTIONS");
header('Content-Type: application/json, charset=utf-8');
}
service:
public function saveEmployee()
{
$input = array();
try
{
$UserID = $this->input->post('id');
$input['name'] = $this->input->post('name');
$input['gender'] = $this->input->post('gender');
$input['email'] = $this->input->post('email');
$input['mobile'] = $this->input->post('mobile');
$input['department'] = 'test';//$this->input->post('department');
$input['isActive'] = 1;//$this->input->post('isActive');
$input['photo'] = 'testphoto';//$this->input->post('photo');
$output["response"] = $this->usr->saveUser($input,$UserID); // call model query to save data into DB
$output["success"] = true;
}
catch (Exception $ex)
{
$output["success"] = false;
$output["error"] = $ex->getMessage();
}
echo json_encode((object)$output);
exit;
}
if this is for development and not production,you can use a proxy to get arround the issue. add a json file to your root project named 'proxy.configuration.json'
remember to change the target to your api in the example below
{
"/api/*": {
"target": "https://localhost:44362",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
then run ng serve --proxy-config proxy.config.json
you have to add Authorization header key in Access-Control-Allow-Headers(in the backend)
Access-Control-Allow-Headers: Authorization (when you give like this it accepts specified headers only.means if you want to accept another key like authorization you have to mention that key name)
or
you can give like this also
Access-Control-Allow-Headers: *

Laravel 5 Session Flash not working in Safari

I have minor security in place to not allow users to download MP3s off my site. Ajax sends a request for a token which is a one time download, this token is attached to the URL I feed into soundmanager2. This security works fine except in Safari.
Front End Request
streamSong: function(song)
{
$.ajax({
url: '/streamsong/'+song.id,
type: 'get',
success: function(token) {
var stream = '/streamsong/'+song.id+'/'+token;
Player.sendSongToPlayer(song, stream);
}
});
}
Route
Route::get('/streamsong/{id}/{token?}', 'StreamController#setupStream');
Controller
class StreamController extends Controller {
public function setupStream($id, $token = null)
{
$stream = new Stream();
if ($token == null) {
if (Request::ajax()) {
$sessionToken = $stream->setToken(str_random(40));
return response($sessionToken);
} else {
return 'no way jose';
}
}
if ($token == $stream->getToken() ) {
return($stream->sendStream($id));
}
}
}
Stream class
public function setToken($token)
{
Session::flash('songToken', $token);
return($token);
}
public function getToken()
{
$token = Session::get('songToken');
return($token);
}
public function sendStream($id)
{
$post = Post::find($id);
$pathToFile = base_path().'/storage/app/mp3/'.$post->song_path;
$fileSize = filesize($pathToFile);
$name = $post->song_path;
$headers = array(
'Content-Type'=>'audio/mpeg',
'Pragma'=>'public',
'Content-Transfer-Encoding' => 'binary',
'Expires'=> 0,
'Cache-Control'=> 'must-revalidate, post-check=0, pre-check=0',
'Filename'=>$name,
'Content-Length'=>$fileSize,
'Connection'=> 'keep-alive'
);
return response()->download($pathToFile, $name, $headers);
}
The only conclusion I've come to is that Safari makes more than one request for the file download so the token is being destroyed on the first attempt. However I only see one GET request in the timeline console. If I set the Session::flash to Session::set it works fine in Safari but this bypasses the security measures. Even with Session::set I can't remove the session token variable until after the response to download has been sent out, which seems very strange.
Has anyone else experience behavior like this in Safari? I'm pretty stumped on this.
Can you check if the browser does an OPTIONS request before it does the actual request? Sometimes this request is performed to check the capabilities of a service.
Session might also not be your best option here, you could create a JWT that hold all the information you need in its payload to stream a song once. It's easy and solid.

Fine Uploader Invalid Response

Current Page: http://www.typhooncloud.com/fineuploader
When attempting to upload a simple jpg file, i receive the error:
'Invalid policy document or request headers!'
Here is my index.html, s3handler.php, CORS policy and console output after attempting an upload.
All changes that had been recommended have been made.
HTML:
<!DOCTYPE html>
<head>
<link href="http://typhooncloud.com/fineuploader/fineuploader-3.9.0-3.css" rel="stylesheet">
</head>
<body>
<!-- The element where Fine Uploader will exist. -->
<div id="fine-uploader">
</div>
<!-- jQuery version 1.10.x (if you are using the jQuery plugin -->
<script src="http://code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>
<!-- Fine Uploader-jQuery -->
<script src="http://typhooncloud.com/fineuploader/s3.jquery.fineuploader-3.9.0-3.js"></script>
<script type="text/javascript">
$(document).ready(function ()
{
$('#fine-uploader').fineUploaderS3({
debug: true,
request: {
endpoint: "getalink1001.s3.amazonaws.com",
accessKey: "MYACCESSKEY"
},
signature: {
endpoint: "http://typhooncloud.com/fineuploader/s3handler.php"
},
uploadSuccess: {
endpoint: "http://typhooncloud.com/fineuploader/s3handler.php?success"
},
iframeSupport: {
localBlankPagePath: "http://typhooncloud.com/fineuploader/success.html"
},
validation: {
allowedExtensions: ["gif", "jpeg", "jpg", "png"],
acceptFiles: "image/gif, image/jpeg, image/png",
sizeLimit: 5000000,
itemLimit: 3
},
retry: {
showButton: true
},
chunking: {
enabled: true
},
resume: {
enabled: true
},
deleteFile: {
enabled: true,
method: "POST",
endpoint: "http://typhooncloud.com/fineuploader/s3handler.php"
},
paste: {
targetElement: $(document),
promptForName: true
}
});
});
</script>
</body>
</html>
SERVER:
<?php
/**
* PHP Server-Side Example for Fine Uploader S3.
* Maintained by Widen Enterprises.
*
* Note: This is the exact server-side code used by the S3 example
* on fineuploader.com.
*
* This example:
* - handles both CORS and non-CORS environments
* - handles delete file requests for both DELETE and POST methods
* - Performs basic inspections on the policy documents and REST headers before signing them
* - Ensures again the file size does not exceed the max (after file is in S3)
* - signs policy documents (simple uploads) and REST requests
* (chunked/multipart uploads)
*
* Requirements:
* - PHP 5.3 or newer
* - Amazon PHP SDK (only if utilizing the AWS SDK for deleting files or otherwise examining them)
*
* If you need to install the AWS SDK, see http://docs.aws.amazon.com/aws-sdk-php-2/guide/latest/installation.html.
*/
// You can remove these two lines if you are not using Fine Uploader's
// delete file feature
require("aws/aws-autoloader.php");
use Aws\S3\S3Client;
// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = $_SERVER['MYSECRETKEY'];
// 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['MYPRIVATEKEY'];
$serverPrivateKey = $_SERVER['MYSECRETKEY'];
// The following variables are used when validating the policy document
// sent by the uploader:
$expectedBucketName = "getalink1001";
// $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 = 15000000;
$method = getRequestMethod();
// This first conditional will only ever evaluate to true in a
// CORS environment
if ($method == 'OPTIONS') {
handlePreflight();
}
// This second conditional will only ever evaluate to true if
// the delete file feature is enabled
else if ($method == "DELETE") {
handleCorsRequest(); // only needed in a CORS environment
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') {
handleCorsRequest();
// 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() {
global $HTTP_RAW_POST_DATA;
// This should only evaluate to true if the Content-Type is undefined
// or unrecognized, such as when XDomainRequest has been used to
// send the request.
if(isset($HTTP_RAW_POST_DATA)) {
parse_str($HTTP_RAW_POST_DATA, $_POST);
}
if ($_POST['_method'] != null) {
return $_POST['_method'];
}
return $_SERVER['REQUEST_METHOD'];
}
// Only needed in cross-origin setups
function handleCorsRequest() {
// If you are relying on CORS, you will need to adjust the allowed domain here.
header('Access-Control-Allow-Origin: http://typhoonupload.com');
}
// Only needed in cross-origin setups
function handlePreflight() {
handleCorsRequest();
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
}
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'];
}
?>
The CORS buckey policy is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<ExposeHeader>ETag</ExposeHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Chrome console:
[FineUploader 3.9.0-3] Received 1 files or inputs. s3.jquery.fineuploader-3.9.0-3.js:164
[FineUploader 3.9.0-3] Submitting S3 signature request for 0 s3.jquery.fineuploader-3.9.0-3.js:164
[FineUploader 3.9.0-3] Sending POST request for 0 s3.jquery.fineuploader-3.9.0-3.js:164
[FineUploader 3.9.0-3] Invalid policy document or request headers! s3.jquery.fineuploader-3.9.0-3.js:169
[FineUploader 3.9.0-3] Policy signing failed. Invalid policy document or request headers! s3.jquery.fineuploader-3.9.0-3.js:169
[FineUploader 3.9.0-3] Received response status 0 with body: s3.jquery.fineuploader-3.9.0-3.js:164
[FineUploader 3.9.0-3] Upload attempt for file ID 0 to S3 is complete s3.jquery.fineuploader-3.9.0-3.js:164
Fiddler Output:
POST http://typhooncloud.com/fineuploader/s3handler.php HTTP/1.1
Host: typhooncloud.com
Connection: keep-alive
Content-Length: 287
Origin: http://typhooncloud.com
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Content-Type: application/json; charset=UTF-8
Accept: */*
Referer: http://typhooncloud.com/fineuploader/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: __utma=33700260.900109978.1379619256.1379708113.1380038992.6; __utmb=33700260.1.10.1380038992; __utmc=33700260; __utmz=33700260.1380038992.6.2.utmcsr=stackoverflow.com|utmccn=(referral)|utmcmd=referral|utmcct=/questions/18971119/fine-uploader-invalid-response
AlexaToolbar-ALX_NS_PH: AlexaToolbar/alxg-3.2
{"expiration":"2013-09-24T16:26:35.948Z","conditions":[{"acl":"private"},{"bucket":"getalink1001"},{"Content-Type":"image/gif"},{"success_action_status":"200"},{"key":"f7e280c4-8cae-4bd7-82c4-d19e71186def.gif"},{"x-amz-meta-qqfilename":"319.gif"},["content-length-range","0","5000000"]]}
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 16:21:49 GMT
Server: Apache
Access-Control-Allow-Origin: http://typhoonupload.com
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
10
{"invalid":true}
0
It looks like when you copy and pasted the PHP example server code, you neglected to adjust some of the values. In this case, the handlePreflightedRequest method was not modified to reflect your domain.
It should read:
function handlePreflightedRequest() {
// If you are relying on CORS, you will need to adjust the allowed domain here.
header('Access-Control-Allow-Origin: http://typhooncloud.com');
}
Also, the handlePreflightedRequest method should probably be renamed handleCorsRequest to avoid confusion. I've done this just now in the server repo. You don't really need to do this though, as it won't affect the behavior of the code.
Side note. Let me point out a crucial difference. In the javascript code you write sizeLimit: 5000000, but in the PHP you write $expectedMaxSize = 15000000. These values must match exactly.

Error using Amazon S3 with FineUploader

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

Categories