Skip to content

Instantly share code, notes, and snippets.

@n0script
Forked from devmycloud/ParseInputStream.php
Created January 10, 2020 12:04
Show Gist options
  • Save n0script/2d15692372f775596325e9fa259e84ae to your computer and use it in GitHub Desktop.
Save n0script/2d15692372f775596325e9fa259e84ae to your computer and use it in GitHub Desktop.

Revisions

  1. @devmycloud devmycloud revised this gist Nov 5, 2017. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions ParseInputStream.php
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,5 @@
    <?php

    namespace App\Library;

    use Illuminate\Support\Facades\Log;
    use Symfony\Component\HttpFoundation\File\UploadedFile;

  2. @devmycloud devmycloud created this gist Nov 5, 2017.
    359 changes: 359 additions & 0 deletions ParseInputStream.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,359 @@
    <?php

    namespace App\Library;

    use Illuminate\Support\Facades\Log;
    use Symfony\Component\HttpFoundation\File\UploadedFile;

    /**
    * 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 [email protected]
    * @license http://www.gnu.org/licenses/gpl.html GPL License 3
    *
    * Massive modifications by TGE ([email protected]) to support
    * proper parameter name processing and Laravel compatible UploadedFile
    * support. Class name changed to be more descriptive and less likely to
    * collide.
    *
    * Original Gist at:
    * https://gist.github.com/jas-/5c3fdc26fedd11cb9fb5#file-class-stream-php
    *
    */

    class ParseInputStream
    {
    /**
    * @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 = [
    'parameters' => $this->parse(),
    'files' => []
    ];
    } 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 = [];

    foreach($array as $key => $value)
    {
    if (empty($value))
    continue;

    $block = $this->decide($value);

    foreach ( $block['parameters'] as $key => $val ) {
    $this->parse_parameter( $results, $key, $val );
    }

    foreach ( $block['files'] as $key => $val ) {
    $this->parse_parameter( $results, $key, $val );
    }
    }

    return $results;
    }

    /**
    * @function decide
    * @param $string string
    * @returns array
    */
    private function decide($string)
    {
    if (strpos($string, 'application/octet-stream') !== FALSE)
    {
    return [
    'parameters' => $this->file($string),
    'files' => []
    ];
    }

    if (strpos($string, 'filename') !== FALSE)
    {
    return [
    'parameters' => [],
    'files' => $this->file_stream($string)
    ];
    }

    return [
    'parameters' => $this->parameter($string),
    'files' => []
    ];
    }

    /**
    * @function file
    *
    * @param $string
    *
    * @return array
    */
    private function file($string)
    {
    preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $string, $match);
    return [
    $match[1] => ($match[2] !== NULL ? $match[2] : '')
    ];
    }

    /**
    * @function file_stream
    *
    * @param $string
    *
    * @return array
    */
    private function file_stream($data)
    {
    $result = [];
    $data = ltrim($data);

    $idx = strpos( $data, "\r\n\r\n" );
    if ( $idx === FALSE ) {
    Log::warning( "ParseInputStream.file_stream(): Could not locate header separator in data:" );
    Log::warning( $data );
    } else {
    $headers = substr( $data, 0, $idx );
    $content = substr( $data, $idx + 4, -2 ); // Skip the leading \r\n and strip the final \r\n

    $name = '-unknown-';
    $filename = '-unknown-';
    $filetype = 'application/octet-stream';

    $header = strtok( $headers, "\r\n" );
    while ( $header !== FALSE ) {
    if ( substr($header, 0, strlen("Content-Disposition: ")) == "Content-Disposition: " ) {
    // Content-Disposition: form-data; name="attach_file[TESTING]"; filename="label2.jpg"
    if ( preg_match('/name=\"([^\"]*)\"/', $header, $nmatch ) ) {
    $name = $nmatch[1];
    }
    if ( preg_match('/filename=\"([^\"]*)\"/', $header, $nmatch ) ) {
    $filename = $nmatch[1];
    }
    } elseif ( substr($header, 0, strlen("Content-Type: ")) == "Content-Type: " ) {
    // Content-Type: image/jpg
    $filetype = trim( substr($header, strlen("Content-Type: ")) );
    } else {
    Log::debug( "PARSEINPUTSTREAM: Skipping Header: " . $header );
    }

    $header = strtok("\r\n");
    }

    if ( substr($data, -2) === "\r\n" ) {
    $data = substr($data, 0, -2);
    }

    $path = sys_get_temp_dir() . '/php' . substr( sha1(rand()), 0, 6 );

    $bytes = file_put_contents( $path, $content );

    if ( $bytes !== FALSE ) {
    $file = new UploadedFile( $path, $filename, $filetype, $bytes, UPLOAD_ERR_OK );
    $result = array( $name => $file );
    }
    }

    return $result;
    }

    /**
    * @function parameter
    *
    * @param $string
    *
    * @return array
    */
    private function parameter($string)
    {
    $data = [];

    if ( preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $string, $match) ) {
    if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
    $data[$tmp[1]][] = ($match[2] !== NULL ? $match[2] : '');
    } else {
    $data[$match[1]] = ($match[2] !== NULL ? $match[2] : '');
    }
    }

    return $data;
    }

    /**
    * @function merge
    * @param $array array
    *
    * Ugly ugly ugly
    *
    * @returns array
    */
    private function merge($array)
    {
    $results = [
    'parameters' => [],
    'files' => []
    ];

    if (count($array['parameters']) > 0) {
    foreach($array['parameters'] as $key => $value) {
    foreach($value as $k => $v) {
    if (is_array($v)) {
    foreach($v as $kk => $vv) {
    $results['parameters'][$k][] = $vv;
    }
    } else {
    $results['parameters'][$k] = $v;
    }
    }
    }
    }

    if (count($array['files']) > 0) {
    foreach($array['files'] 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['files'][$k][$kk] = $vv[0];
    } else {
    $results['files'][$k][$kk][] = $vv[0];
    }
    }
    } else {
    $results['files'][$k][$key] = $v;
    }
    }
    }
    }

    return $results;
    }

    function parse_parameter( &$params, $parameter, $value ) {
    if ( strpos($parameter, '[') !== FALSE ) {
    $matches = array();
    if ( preg_match( '/^([^[]*)\[([^]]*)\](.*)$/', $parameter, $match ) ) {
    $name = $match[1];
    $key = $match[2];
    $rem = $match[3];

    if ( $name !== '' && $name !== NULL ) {
    if ( ! isset($params[$name]) || ! is_array($params[$name]) ) {
    $params[$name] = array();
    } else {
    }
    if ( strlen($rem) > 0 ) {
    if ( $key === '' || $key === NULL ) {
    $arr = array();
    $this->parse_parameter( $arr, $rem, $value );
    $params[$name][] = $arr;
    } else {
    if ( !isset($params[$name][$key]) || !is_array($params[$name][$key]) ) {
    $params[$name][$key] = array();
    }
    $this->parse_parameter( $params[$name][$key], $rem, $value );
    }
    } else {
    if ( $key === '' || $key === NULL ) {
    $params[$name][] = $value;
    } else {
    $params[$name][$key] = $value;
    }
    }
    } else {
    if ( strlen($rem) > 0 ) {
    if ( $key === '' || $key === NULL ) {
    // REVIEW Is this logic correct?!
    $this->parse_parameter( $params, $rem, $value );
    } else {
    if ( ! isset($params[$key]) || ! is_array($params[$key]) ) {
    $params[$key] = array();
    }
    $this->parse_parameter( $params[$key], $rem, $value );
    }
    } else {
    if ( $key === '' || $key === NULL ) {
    $params[] = $value;
    } else {
    $params[$key] = $value;
    }
    }
    }
    } else {
    Log::warning( "ParseInputStream.parse_parameter() Parameter name regex failed: '" . $parameter . "'" );
    }
    } else {
    $params[$parameter] = $value;
    }
    }
    }