Created
August 25, 2013 19:13
-
-
Save andheiberg/6335657 to your computer and use it in GitHub Desktop.
Server side part of FineUploader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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']; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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