-
-
Save jas-/5c3fdc26fedd11cb9fb5 to your computer and use it in GitHub Desktop.
<?php | |
/** | |
* stream - Handle raw input stream | |
* | |
* LICENSE: This source file is subject to version 3.01 of the GPL license | |
* that is available through the world-wide-web at the following URI: | |
* http://www.gnu.org/licenses/gpl.html. If you did not receive a copy of | |
* the GPL License and are unable to obtain it through the web, please | |
* | |
* @author jason.gerfen@gmail.com | |
* @license http://www.gnu.org/licenses/gpl.html GPL License 3 | |
*/ | |
class stream | |
{ | |
/** | |
* @var input | |
* @abstract Raw input stream | |
*/ | |
protected $input; | |
/** | |
* @function __construct | |
* @param $data stream | |
*/ | |
public function __construct(array &$data) | |
{ | |
$this->input = file_get_contents('php://input'); | |
$boundary = $this->boundary(); | |
if (!count($boundary)) { | |
return array( | |
'post' => $this->parse($this->input), | |
'file' => array() | |
); | |
} | |
$blocks = $this->split($boundary); | |
$data = $this->blocks($blocks); | |
return $data; | |
} | |
/** | |
* @function boundary | |
* @returns Array | |
*/ | |
private function boundary() | |
{ | |
preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); | |
return $matches[1]; | |
} | |
/** | |
* @function parse | |
* @returns Array | |
*/ | |
private function parse() | |
{ | |
parse_str(urldecode($this->input), $result); | |
return $result; | |
} | |
/** | |
* @function split | |
* @param $boundary string | |
* @returns Array | |
*/ | |
private function split($boundary) | |
{ | |
$result = preg_split("/-+$boundary/", $this->input); | |
array_pop($result); | |
return $result; | |
} | |
/** | |
* @function blocks | |
* @param $array array | |
* @returns Array | |
*/ | |
private function blocks($array) | |
{ | |
$results = array( | |
'post' => array(), | |
'file' => array() | |
); | |
foreach($array as $key => $value) | |
{ | |
if (empty($value)) | |
continue; | |
$block = $this->decide($value); | |
if (count($block['post']) > 0) | |
array_push($results['post'], $block['post']); | |
if (count($block['file']) > 0) | |
array_push($results['file'], $block['file']); | |
} | |
return $this->merge($results); | |
} | |
/** | |
* @function decide | |
* @param $string string | |
* @returns Array | |
*/ | |
private function decide($string) | |
{ | |
if (strpos($string, 'application/octet-stream') !== FALSE) | |
{ | |
return array( | |
'post' => $this->file($string), | |
'file' => array() | |
); | |
} | |
if (strpos($string, 'filename') !== FALSE) | |
{ | |
return array( | |
'post' => array(), | |
'file' => $this->file_stream($string) | |
); | |
} | |
return array( | |
'post' => $this->post($string), | |
'file' => array() | |
); | |
} | |
/** | |
* @function file | |
* @param $boundary string | |
* @returns Array | |
*/ | |
private function file($string) | |
{ | |
preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $string, $match); | |
return array( | |
$match[1] => $match[2] | |
); | |
} | |
/** | |
* @function file_stream | |
* @param $boundary string | |
* @returns Array | |
*/ | |
private function file_stream($string) | |
{ | |
$data = array(); | |
preg_match('/name=\"([^\"]*)\"; filename=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match); | |
preg_match('/Content-Type: (.*)?/', $match[3], $mime); | |
$image = preg_replace('/Content-Type: (.*)[^\n\r]/', '', $match[3]); | |
$path = sys_get_temp_dir().'/php'.substr(sha1(rand()), 0, 6); | |
$err = file_put_contents($path, $image); | |
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) { | |
$index = $tmp[1]; | |
} else { | |
$index = $match[1]; | |
} | |
$data[$index]['name'][] = $match[2]; | |
$data[$index]['type'][] = $mime[1]; | |
$data[$index]['tmp_name'][] = $path; | |
$data[$index]['error'][] = ($err === FALSE) ? $err : 0; | |
$data[$index]['size'][] = filesize($path); | |
return $data; | |
} | |
/** | |
* @function post | |
* @param $boundary string | |
* @returns Array | |
*/ | |
private function post($string) | |
{ | |
$data = array(); | |
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match); | |
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) { | |
$data[$tmp[1]][] = $match[2]; | |
} else { | |
$data[$match[1]] = $match[2]; | |
} | |
return $data; | |
} | |
/** | |
* @function merge | |
* @param $array array | |
* | |
* Ugly ugly ugly | |
* | |
* @returns Array | |
*/ | |
private function merge($array) | |
{ | |
$results = array( | |
'post' => array(), | |
'file' => array() | |
); | |
if (count($array['post'] > 0)) { | |
foreach($array['post'] as $key => $value) { | |
foreach($value as $k => $v) { | |
if (is_array($v)) { | |
foreach($v as $kk => $vv) { | |
$results['post'][$k][] = $vv; | |
} | |
} else { | |
$results['post'][$k] = $v; | |
} | |
} | |
} | |
} | |
if (count($array['file'] > 0)) { | |
foreach($array['file'] as $key => $value) { | |
foreach($value as $k => $v) { | |
if (is_array($v)) { | |
foreach($v as $kk => $vv) { | |
$results['file'][$kk][] = $vv[0]; | |
} | |
} else { | |
$results['file'][$key] = $v; | |
} | |
} | |
} | |
} | |
return $results; | |
} | |
} |
<?php | |
/** | |
* Parse raw HTTP request data | |
* http://www.chlab.ch/blog/archives/php/manually-parse-raw-http-data-php | |
* | |
* Pass in $a_data as an array. This is done by reference to avoid copying | |
* the data around too much. | |
* | |
* Any files found in the request will be added by their field name to the | |
* $data['files'] array. | |
* | |
* @param array Empty array to fill with data | |
* @return array Associative array of request data | |
*/ | |
function parse_raw_http_request(array &$a_data) | |
{ | |
// read incoming data | |
$input = file_get_contents('php://input'); | |
// grab multipart boundary from content type header | |
preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); | |
// content type is probably regular form-encoded | |
if (!count($matches)) | |
{ | |
// we expect regular puts to containt a query string containing data | |
parse_str(urldecode($input), $a_data); | |
return $a_data; | |
} | |
$boundary = $matches[1]; | |
// split content by boundary and get rid of last -- element | |
$a_blocks = preg_split("/-+$boundary/", $input); | |
array_pop($a_blocks); | |
// loop data blocks | |
foreach ($a_blocks as $id => $block) | |
{ | |
if (empty($block)) | |
continue; | |
// you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char | |
// parse uploaded files | |
if (strpos($block, 'application/octet-stream') !== FALSE) | |
{ | |
// match "name", then everything after "stream" (optional) except for prepending newlines | |
preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches); | |
$a_data['files'][$matches[1]] = $matches[2]; | |
} | |
// parse all other fields | |
else | |
{ | |
if (strpos($block, 'filename') !== FALSE) | |
{ | |
// match "name" and optional value in between newline sequences | |
preg_match('/name=\"([^\"]*)\"; filename=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches); | |
preg_match('/Content-Type: (.*)?/', $matches[3], $mime); | |
// match the mime type supplied from the browser | |
$image = preg_replace('/Content-Type: (.*)[^\n\r]/', '', $matches[3]); | |
// get current system path and create tempory file name & path | |
$path = sys_get_temp_dir().'/php'.substr(sha1(rand()), 0, 6); | |
// write temporary file to emulate $_FILES super global | |
$err = file_put_contents($path, $image); | |
// Did the user use the infamous <input name="array[]" for multiple file uploads? | |
if (preg_match('/^(.*)\[\]$/i', $matches[1], $tmp)) { | |
$a_data[$tmp[1]]['name'][] = $matches[2]; | |
} else { | |
$a_data[$matches[1]]['name'][] = $matches[2]; | |
} | |
// Create the remainder of the $_FILES super global | |
$a_data[$tmp[1]]['type'][] = $mime[1]; | |
$a_data[$tmp[1]]['tmp_name'][] = $path; | |
$a_data[$tmp[1]]['error'][] = ($err === FALSE) ? $err : 0; | |
$a_data[$tmp[1]]['size'][] = filesize($path); | |
} | |
else | |
{ | |
// match "name" and optional value in between newline sequences | |
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches); | |
if (preg_match('/^(.*)\[\]$/i', $matches[1], $tmp)) { | |
$a_data[$tmp[1]][] = $matches[2]; | |
} else { | |
$a_data[$matches[1]] = $matches[2]; | |
} | |
} | |
} | |
} | |
} |
<?php | |
include_once('class.stream.php'); | |
$data = array(); | |
new stream($data); | |
$_PUT = $data['post']; | |
$_FILES = $data['file']; | |
/* Handle moving the file(s) */ | |
if (count($_FILES) > 0) { | |
foreach($_FILES as $key => $value) { | |
if (!is_uploaded_file($value['tmp_name'])) { | |
rename($value['tmp_name'], '/path/to/uploads/'.$value['name']); | |
} else { | |
move_uploaded_file($value['tmp_name'], '/path/to/uploads/'.$value['name']); | |
} | |
} | |
} |
This doesn't seem to work for me using a multipart form and from reading http://php.net/manual/en/wrappers.php.php it says php://input is not available with an encoding type of "multipart/form-data" so it seems like it would never work.
Am I confusing the two use cases here?
Just to add incase anybody else gets confused by this, it seems if you turn "enable_post_data_reading" off in php.ini then all post data (including multipart-form-data) goes into php://input
Great work!
Small corrections for "class.stream.php":
// line 146 (replace line) - some notice error in case of empty array
$match[1] => (!empty($match[2]) ? $match[2] : '')
// line 166 (replace line) - space were added at the beginning of the image raw data
$err = file_put_contents($path, ltrim($image));
// line 197 (replace line) - some notice error in case of empty array
$data[$match[1]] = (!empty($match[2]) ? $match[2] : '');
Small improvement for "class.stream.php" (in order to keep the file input name in the $_FILES var like $_FILES["toto"] instead of $_FILES[0]):
// line 232 (replace whole block)
if (count($array['file'] > 0)) {
foreach($array['file'] as $key => $value) {
foreach($value as $k => $v) {
if (is_array($v)) {
foreach($v as $kk => $vv) {
if(
is_array($vv)
&& (count($vv) == 1)
) {
$results['file'][$k][$kk] = $vv[0];
} else {
$results['file'][$k][$kk][] = $vv[0];
}
}
} else {
$results['file'][$k][$key] = $v;
}
}
}
}
This is the final version I am using:
<?php
/**
* stream - Handle raw input stream
*
* LICENSE: This source file is subject to version 3.01 of the GPL license
* that is available through the world-wide-web at the following URI:
* http://www.gnu.org/licenses/gpl.html. If you did not receive a copy of
* the GPL License and are unable to obtain it through the web, please
*
* @author jason.gerfen@gmail.com
* @license http://www.gnu.org/licenses/gpl.html GPL License 3
*/
class Stream
{
/**
* @abstract Raw input stream
*/
protected $input;
/**
* @function __construct
*
* @param array $data stream
*/
public function __construct(array &$data)
{
$this->input = file_get_contents('php://input');
$boundary = $this->boundary();
if (!strlen($boundary)) {
return array(
'post' => $this->parse(),
'file' => array()
);
}
$blocks = $this->split($boundary);
$data = $this->blocks($blocks);
return $data;
}
/**
* @function boundary
* @returns string
*/
private function boundary()
{
preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches);
return $matches[1];
}
/**
* @function parse
* @returns array
*/
private function parse()
{
parse_str(urldecode($this->input), $result);
return $result;
}
/**
* @function split
* @param $boundary string
* @returns array
*/
private function split($boundary)
{
$result = preg_split("/-+$boundary/", $this->input);
array_pop($result);
return $result;
}
/**
* @function blocks
* @param $array array
* @returns array
*/
private function blocks($array)
{
$results = array(
'post' => array(),
'file' => array()
);
foreach($array as $key => $value)
{
if (empty($value))
continue;
$block = $this->decide($value);
if (count($block['post']) > 0)
array_push($results['post'], $block['post']);
if (count($block['file']) > 0)
array_push($results['file'], $block['file']);
}
return $this->merge($results);
}
/**
* @function decide
* @param $string string
* @returns array
*/
private function decide($string)
{
if (strpos($string, 'application/octet-stream') !== FALSE)
{
return array(
'post' => $this->file($string),
'file' => array()
);
}
if (strpos($string, 'filename') !== FALSE)
{
return array(
'post' => array(),
'file' => $this->file_stream($string)
);
}
return array(
'post' => $this->post($string),
'file' => array()
);
}
/**
* @function file
*
* @param $string
*
* @return array
*/
private function file($string)
{
preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $string, $match);
return array(
$match[1] => (!empty($match[2]) ? $match[2] : '')
);
}
/**
* @function file_stream
*
* @param $string
*
* @return array
*/
private function file_stream($string)
{
$data = array();
preg_match('/name=\"([^\"]*)\"; filename=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);
preg_match('/Content-Type: (.*)?/', $match[3], $mime);
$image = preg_replace('/Content-Type: (.*)[^\n\r]/', '', $match[3]);
$path = sys_get_temp_dir().'/php'.substr(sha1(rand()), 0, 6);
$err = file_put_contents($path, ltrim($image));
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
$index = $tmp[1];
} else {
$index = $match[1];
}
$data[$index]['name'][] = $match[2];
$data[$index]['type'][] = $mime[1];
$data[$index]['tmp_name'][] = $path;
$data[$index]['error'][] = ($err === FALSE) ? $err : 0;
$data[$index]['size'][] = filesize($path);
return $data;
}
/**
* @function post
*
* @param $string
*
* @return array
*/
private function post($string)
{
$data = array();
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
$data[$tmp[1]][] = (!empty($match[2]) ? $match[2] : '');
} else {
$data[$match[1]] = (!empty($match[2]) ? $match[2] : '');
}
return $data;
}
/**
* @function merge
* @param $array array
*
* Ugly ugly ugly
*
* @returns array
*/
private function merge($array)
{
$results = array(
'post' => array(),
'file' => array()
);
if (count($array['post']) > 0) {
foreach($array['post'] as $key => $value) {
foreach($value as $k => $v) {
if (is_array($v)) {
foreach($v as $kk => $vv) {
$results['post'][$k][] = $vv;
}
} else {
$results['post'][$k] = $v;
}
}
}
}
if (count($array['file']) > 0) {
foreach($array['file'] as $key => $value) {
foreach($value as $k => $v) {
if (is_array($v)) {
foreach($v as $kk => $vv) {
if(is_array($vv) && (count($vv) === 1)) {
$results['file'][$k][$kk] = $vv[0];
} else {
$results['file'][$k][$kk][] = $vv[0];
}
}
} else {
$results['file'][$k][$key] = $v;
}
}
}
}
return $results;
}
}
With a couple minor validations:
<?php
/**
* stream - Handle raw input stream
*
* LICENSE: This source file is subject to version 3.01 of the GPL license
* that is available through the world-wide-web at the following URI:
* http://www.gnu.org/licenses/gpl.html. If you did not receive a copy of
* the GPL License and are unable to obtain it through the web, please
*
* @author jason.gerfen@gmail.com
* @license http://www.gnu.org/licenses/gpl.html GPL License 3
*/
class Stream
{
/**
* @abstract Raw input stream
*/
protected $input;
/**
* @function __construct
*
* @param array $data stream
*/
public function __construct(array &$data)
{
$this->input = file_get_contents('php://input');
$boundary = $this->boundary();
if (!strlen($boundary)) {
$data = [
'post' => $this->parse(),
'file' => []
];
} else {
$blocks = $this->split($boundary);
$data = $this->blocks($blocks);
}
return $data;
}
/**
* @function boundary
* @returns string
*/
private function boundary()
{
if(!isset($_SERVER['CONTENT_TYPE'])) {
return null;
}
preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches);
return $matches[1];
}
/**
* @function parse
* @returns array
*/
private function parse()
{
parse_str(urldecode($this->input), $result);
return $result;
}
/**
* @function split
* @param $boundary string
* @returns array
*/
private function split($boundary)
{
$result = preg_split("/-+$boundary/", $this->input);
array_pop($result);
return $result;
}
/**
* @function blocks
* @param $array array
* @returns array
*/
private function blocks($array)
{
$results = [
'post' => [],
'file' => []
];
foreach($array as $key => $value)
{
if (empty($value))
continue;
$block = $this->decide($value);
if (count($block['post']) > 0)
array_push($results['post'], $block['post']);
if (count($block['file']) > 0)
array_push($results['file'], $block['file']);
}
return $this->merge($results);
}
/**
* @function decide
* @param $string string
* @returns array
*/
private function decide($string)
{
if (strpos($string, 'application/octet-stream') !== FALSE)
{
return [
'post' => $this->file($string),
'file' => []
];
}
if (strpos($string, 'filename') !== FALSE)
{
return [
'post' => [],
'file' => $this->file_stream($string)
];
}
return [
'post' => $this->post($string),
'file' => []
];
}
/**
* @function file
*
* @param $string
*
* @return array
*/
private function file($string)
{
preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $string, $match);
return [
$match[1] => (!empty($match[2]) ? $match[2] : '')
];
}
/**
* @function file_stream
*
* @param $string
*
* @return array
*/
private function file_stream($string)
{
$data = [];
preg_match('/name=\"([^\"]*)\"; filename=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);
preg_match('/Content-Type: (.*)?/', $match[3], $mime);
$image = preg_replace('/Content-Type: (.*)[^\n\r]/', '', $match[3]);
$path = sys_get_temp_dir().'/php'.substr(sha1(rand()), 0, 6);
$err = file_put_contents($path, ltrim($image));
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
$index = $tmp[1];
} else {
$index = $match[1];
}
$data[$index]['name'][] = $match[2];
$data[$index]['type'][] = $mime[1];
$data[$index]['tmp_name'][] = $path;
$data[$index]['error'][] = ($err === FALSE) ? $err : 0;
$data[$index]['size'][] = filesize($path);
return $data;
}
/**
* @function post
*
* @param $string
*
* @return array
*/
private function post($string)
{
$data = [];
preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match);
if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
$data[$tmp[1]][] = (!empty($match[2]) ? $match[2] : '');
} else {
$data[$match[1]] = (!empty($match[2]) ? $match[2] : '');
}
return $data;
}
/**
* @function merge
* @param $array array
*
* Ugly ugly ugly
*
* @returns array
*/
private function merge($array)
{
$results = [
'post' => [],
'file' => []
];
if (count($array['post']) > 0) {
foreach($array['post'] as $key => $value) {
foreach($value as $k => $v) {
if (is_array($v)) {
foreach($v as $kk => $vv) {
$results['post'][$k][] = $vv;
}
} else {
$results['post'][$k] = $v;
}
}
}
}
if (count($array['file']) > 0) {
foreach($array['file'] as $key => $value) {
foreach($value as $k => $v) {
if (is_array($v)) {
foreach($v as $kk => $vv) {
if(is_array($vv) && (count($vv) === 1)) {
$results['file'][$k][$kk] = $vv[0];
} else {
$results['file'][$k][$kk][] = $vv[0];
}
}
} else {
$results['file'][$k][$key] = $v;
}
}
}
}
return $results;
}
}
nice work, but I think there is a little error in the stream.php
on line 71-81:
when the pattern isn't found in the subject and the preg_match returns 0, you can't use $tmp[1] in the lines 78-81
possible fix:
$name = "name";
// Did the user use the infamous for multiple file uploads?
if (preg_match('/^(.*)[]$/i', $matches[1], $tmp)) {
$name = $tmp[1];
} else {
$name = $matches[1];
}
$a_data[$name]['name'][] = $matches[2];
// Create the remainder of the $_FILES super global
$a_data[$name]['type'][] = $mime[1];
$a_data[$name]['tmp_name'][] = $path;
$a_data[$name]['error'][] = ($err === FALSE) ? $err : 0;
$a_data[$name]['size'][] = filesize($path);