Created
January 9, 2016 16:00
-
-
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
This file contains 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 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