Skip to content

Instantly share code, notes, and snippets.

@andheiberg
Created August 25, 2013 19:13
Show Gist options
  • Save andheiberg/6335657 to your computer and use it in GitHub Desktop.
Save andheiberg/6335657 to your computer and use it in GitHub Desktop.
Server side part of FineUploader
<?php namespace Models;
use \AWS;
class File extends BaseModel {
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'files';
/**
* The attributes that can be set with Mass Assignment.
*
* @var array
*/
protected $fillable = ['path', 'type', 'bucket', 'key'];
/**
* The private key for the client side. The client side has it's own user with it's own permissions.
*
* @var string
*/
protected $clientPrivateKey = 'secret-key-for-the-user-used-on-the-client';
/**
* Expected bucket name.... This is a relic from from FineUploads script. What's the point of having this? at that point you can't use different buckets
*
* @var string
*/
protected $expectedBucketName = "bucket-name";
/**
* Expected max size.... This is a relic from from FineUploads script. Validation shouldn't be hardcoded into the model like that
*
* @var string
*/
protected $expectedMaxSize = 15000000;
public function fileable()
{
return $this->morphTo();
}
/**
* Find a file by bucket and S3 key.
*
* @param string $bucket
* @param string $key
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|Collection|static
*/
public static function findS3($bucket, $key, $columns = array('*'))
{
$instance = new static;
return $instance->newQuery()
->where('bucket', $bucket)
->where('key', $key)
->first($columns);
}
/**
* Find a file by bucket and S3 key or throw an exception.
*
* @param string $bucket
* @param string $key
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|Collection|static
*/
public static function findS3OrFail($bucket, $key, $columns = array('*'))
{
if ( ! is_null($model = static::findS3($bucket, $key, $columns))) return $model;
throw new ModelNotFoundException;
}
/**
* Delete the model from the database.
*
* @return bool|null
*/
public function delete()
{
if ($this->isStoredOnS3())
{
AWS::get('s3')->deleteObject([
'Bucket' => $this->bucket,
'Key' => $this->key
]);
}
return parent::delete();
}
/**
* Check to see if the file is stored on Amazon S3
*
* @return bool|null
*/
public function isStoredOnS3()
{
return !! $this->bucket;
}
/**
* Verify that a file was uploaded
*
* @param string $bucket
* @param string $key
* @return \Illuminate\Database\Eloquent\Model|static|null
*/
public static function fileWasUploaded($bucket, $key)
{
$instance = new static;
$instance->bucket = $bucket;
$instance->key = $key;
if ($instance->getSize() > $instance->expectedMaxSize)
{
AWS::get('s3')->deleteObject([
'Bucket' => $instance->bucket,
'Key' => $instance->key
]);
return false;
}
return true;
}
/**
* Sign upload request
*
* @param string $requestBody
* @return array
*/
public static function signRequest($requestBody)
{
$instance = new static;
$requestBody = json_decode($requestBody, true);
$headersStr = isset($requestBody["headers"]) ? $requestBody["headers"] : false;
if ($headersStr)
{
return $instance->signRESTRequest($headersStr);
}
return $instance->signPolicy($requestBody);
}
/**
* Sign REST upload request
*
* @param string $headersStr
* @return array
*/
protected function signRESTRequest($headersStr) {
if ( ! $this->isValidRESTRequest($headersStr))
{
return ["invalid" => true];
}
return ['signature' => $this->sign($headersStr)];
}
/**
* Sign a policy according to AWS S3 specification
*
* @param object $policy
* @return array
*/
protected function signPolicy($policy) {
if ( ! $this->isValidPolicy($policy))
{
return ["invalid" => true];
}
$encodedPolicy = base64_encode(json_encode($policy));
return [
'policy' => $encodedPolicy,
'signature' => $this->sign($encodedPolicy)
];
}
/**
* Validate REST request
*
* @param string $headersStr
* @return bool
*/
protected function isValidRESTRequest($headersStr) {
$pattern = "/\/{$this->expectedBucketName}\/.+$/";
preg_match($pattern, $headersStr, $matches);
return count($matches) > 0;
}
/**
* Validate policy
*
* @param object $policy
* @return bool
*/
protected function isValidPolicy($policy) {
$conditions = $policy["conditions"];
$bucket = null;
$parsedMaxSize = null;
$valid = true;
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];
}
}
if ($bucket != $this->expectedBucketName)
{
$valid = false;
}
if ( !! $this->expectedMaxSize and $parsedMaxSize != (string) $this->expectedMaxSize)
{
$valid = false;
}
return $valid;
}
/**
* Sign a request according to AWS S3 Specification
*
* @param string $stringToSign
* @return string
*/
protected function sign($stringToSign) {
return base64_encode(hash_hmac(
'sha1',
$stringToSign,
$this->clientPrivateKey,
true
));
}
/**
* Get a link to the file
*
* @param string $expiration
* @return string
*/
public function link($expiration = null) {
if ($this->isStoredOnS3())
{
return AWS::get('s3')->getObjectUrl($this->bucket, $this->key, $expiration);
}
return $this->path;
}
/**
* Get the size of the file
*
* @return int
*/
public function getSize() {
$fileInfo = AWS::get('s3')->headObject([
'Bucket' => $this->bucket,
'Key' => $this->key
]);
return $fileInfo['ContentLength'];
}
}
<?php
use Models\File;
class UploadController extends BaseController {
// FineUpload, AWS S3 and Laravel
// Fine loader can be found here http://docs.fineuploader.com/
// There is a long but far from dommy proof explaination of how to set it up here http://blog.fineuploader.com/2013/08/16/fine-uploader-s3-upload-directly-to-amazon-s3-from-your-browser/
public function deleteIndex()
{
$bucket = Input::get('bucket');
$key = Input::get('key');
if ( ! $bucket or ! $key)
{
App::abort(400);
}
return Response::json(File::findS3OrFail($bucket, $key)->delete());
}
public function postIndex()
{
$bucket = Input::get('bucket');
$key = Input::get('key');
if (Input::get('success'))
{
if ( ! File::fileWasUploaded($bucket, $key))
{
return Response::json(["error" => "File is too big!"], 500);
}
$file = File::create(compact('bucket', 'key'));
return Response::json(["tempLink" => $file->link('+15 minutes')]);
}
$signedRequest = File::signRequest(file_get_contents('php://input'));
return Response::json($signedRequest);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment