Skip to content

Instantly share code, notes, and snippets.

@terion-name
Created January 9, 2016 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save terion-name/5dfe6914612b39515a73 to your computer and use it in GitHub Desktop.
Save terion-name/5dfe6914612b39515a73 to your computer and use it in GitHub Desktop.
Get uploads from RAW multipart post-request that is sent by some frontend async uploaders
<?php
namespace App\Support;
/**
* Get files from raw multipart input generated by frontend ajax uploaders
*
* Class JsonUploader
* @package App\Support
* @link http://stackoverflow.com/a/9469615/1114816
*/
/**
* Class JsonUploader
* @package App\Support
*/
class JsonUploader
{
/**
* Where to store tmp files
* @var string
*/
protected $tmpDir;
/**
* @var array
*/
protected $data = [];
/**
* @var array
*/
protected $errors = [];
/**
* JsonUploader constructor.
*
* @param $validation
* @param string|null $tmpDir
*/
public function __construct($validation = null, $tmpDir = null)
{
$this->tmpDir = $tmpDir ?: sys_get_temp_dir();
//TODO: inject validation rules, or validator instance... Need to think
}
/**
* Get files from request
*
* @return array
*/
public function files(array $meta = null)
{
$this->parseRawRequest(file_get_contents('php://input'), $meta);
return $this->data;
}
/**
* Check signature of provided payload
*
* @param array $payload
* @return bool
*/
public function checkSignature(array $payload)
{
if (!isset($payload['signature'])) return false;
$signature = $payload['signature'];
unset($payload['signature']);
$signed = $this->sign($payload);
return $signature === $signed['signature'];
}
public function getTmpDir() {
return $this->tmpDir;
}
/**
* Check presence of upload validation errors
*
* @return bool
*/
public function hasErrors() {
return count($this->errors) > 0;
}
/**
* Get upload validation errors
*
* @return array
*/
public function getErrors() {
return $this->errors;
}
/**
* Parse raw request data
*
* @param string $raw_data
*/
protected function parseRawRequest($raw_data, array $meta = null)
{
$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));
// Fetch each part of multipart request
$parts = array_slice(explode($boundary, $raw_data), 1);
foreach ($parts as $part) {
// If this is the last part, break
if ($part == "--\r\n") break;
$payload = $this->parseRequestPart($part);
if (!$payload) continue;
try {
$this->validate($payload);
} catch (\Exception $e) {
$this->errors[] = $e->getMessage();
}
$payload = $this->sign($payload, $meta);
$this->data[] = $payload;
}
}
/**
* Parse separated part of multipart request, save file to tmp and return it's data
*
* @param string $part
* @return array
*/
protected function parseRequestPart($part)
{
// Separate content from headers
$part = ltrim($part, "\r\n");
list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);
// Parse the headers list
$raw_headers = explode("\r\n", $raw_headers);
$headers = array();
foreach ($raw_headers as $header) {
list($name, $value) = explode(':', $header);
$headers[strtolower($name)] = ltrim($value, ' ');
}
// Parse the Content-Disposition to get the field name, etc.
if (isset($headers['content-disposition'])) {
$filename = null;
preg_match(
'/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
$headers['content-disposition'],
$matches
);
list(, $type, $name) = $matches;
isset($matches[4]) and $filename = $matches[4];
$tmpfname = tempnam("/tmp", md5($filename . microtime()));
file_put_contents($tmpfname, $body);
$payload = [
'name' => $name,
'original-filename' => $filename,
'filename' => pathinfo($tmpfname, PATHINFO_FILENAME),
'content-type-provided' => array_get($headers, 'content-type'),
'content-type-parsed' => mime_content_type($tmpfname)
];
// other fields can be handled here
/*switch ($name) {
// this is a file upload
case 'file':
file_put_contents($filename, $body);
break;
// default for all other files is to populate $data
default:
$data[$name] = substr($body, 0, strlen($body) - 2);
break;
}*/
return $payload;
}
}
/**
* Validate payload over injected validation rules
*
* @param $payload
* @return bool
*/
protected function validate($payload)
{
// TODO: validate and throw exception on error
return true;
}
/**
* Sign the payload for preventing falsing the data in future
*
* @param $payload
* @return mixed
*/
protected function sign($payload, array $meta = null)
{
if ($meta) {
$payload['meta'] = $meta;
}
ksort($payload);
$signature = hash('sha256', base64_encode(json_encode($payload)) . config('app.key'));
$payload['signature'] = $signature;
return $payload;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment