PHP multipart form data PUT request? PHP multipart form data PUT request? codeigniter codeigniter

PHP multipart form data PUT request?


First of all, $_FILES is not populated when handling PUT requests. It is only populated by PHP when handling POST requests.

You need to parse it manually. That goes for "regular" fields as well:

// Fetch content and determine boundary$raw_data = file_get_contents('php://input');$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));// Fetch each part$parts = array_slice(explode($boundary, $raw_data), 1);$data = array();foreach ($parts as $part) {    // If this is the last part, break    if ($part == "--\r\n") break;     // 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];         // handle your fields here        switch ($name) {            // this is a file upload            case 'userfile':                 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;        }     }}

At each iteration, the $data array will be populated with your parameters, and the $headers array will be populated with the headers for each part (e.g.: Content-Type, etc.), and $filename will contain the original filename, if supplied in the request and is applicable to the field.

Take note the above will only work for multipart content types. Make sure to check the request Content-Type header before using the above to parse the body.


Please don't delete this again, it's helpful to a majority of people coming here! All previous answers were partial answers that don't cover the solution as a majority of people asking this question would want.

This takes what has been said above and additionally handles multiple file uploads and places them in $_FILES as someone would expect. To get this to work, you have to add 'Script PUT /put.php' to your Virtual Host for the project per Documentation. I also suspect I'll have to setup a cron to cleanup any '.tmp' files.

private function _parsePut(  ){    global $_PUT;    /* PUT data comes in on the stdin stream */    $putdata = fopen("php://input", "r");    /* Open a file for writing */    // $fp = fopen("myputfile.ext", "w");    $raw_data = '';    /* Read the data 1 KB at a time       and write to the file */    while ($chunk = fread($putdata, 1024))        $raw_data .= $chunk;    /* Close the streams */    fclose($putdata);    // Fetch content and determine boundary    $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));    if(empty($boundary)){        parse_str($raw_data,$data);        $GLOBALS[ '_PUT' ] = $data;        return;    }    // Fetch each part    $parts = array_slice(explode($boundary, $raw_data), 1);    $data = array();    foreach ($parts as $part) {        // If this is the last part, break        if ($part == "--\r\n") break;        // 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;            $tmp_name = null;            preg_match(                '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',                $headers['content-disposition'],                $matches            );            list(, $type, $name) = $matches;            //Parse File            if( isset($matches[4]) )            {                //if labeled the same as previous, skip                if( isset( $_FILES[ $matches[ 2 ] ] ) )                {                    continue;                }                //get filename                $filename = $matches[4];                //get tmp name                $filename_parts = pathinfo( $filename );                $tmp_name = tempnam( ini_get('upload_tmp_dir'), $filename_parts['filename']);                //populate $_FILES with information, size may be off in multibyte situation                $_FILES[ $matches[ 2 ] ] = array(                    'error'=>0,                    'name'=>$filename,                    'tmp_name'=>$tmp_name,                    'size'=>strlen( $body ),                    'type'=>$value                );                //place in temporary directory                file_put_contents($tmp_name, $body);            }            //Parse Field            else            {                $data[$name] = substr($body, 0, strlen($body) - 2);            }        }    }    $GLOBALS[ '_PUT' ] = $data;    return;}


For whom using Apiato (Laravel) framework:create new Middleware like file below, then declair this file in your laravel kernel file within the protected $middlewareGroups variable (inside web or api, whatever you want) like this:

protected $middlewareGroups = [    'web' => [],    'api' => [HandlePutFormData::class],];

<?phpnamespace App\Ship\Middlewares\Http;use Closure;use Symfony\Component\HttpFoundation\ParameterBag;/** * @author Quang Pham */class HandlePutFormData{    /**     * Handle an incoming request.     *     * @param  \Illuminate\Http\Request $request     * @param  \Closure                 $next     *     * @return mixed     */    public function handle($request, Closure $next)    {        if ($request->method() == 'POST' or $request->method() == 'GET') {            return $next($request);        }        if (preg_match('/multipart\/form-data/', $request->headers->get('Content-Type')) or            preg_match('/multipart\/form-data/', $request->headers->get('content-type'))) {            $parameters = $this->decode();            $request->merge($parameters['inputs']);            $request->files->add($parameters['files']);        }        return $next($request);    }    public function decode()    {        $files = [];        $data  = [];        // Fetch content and determine boundary        $rawData  = file_get_contents('php://input');        $boundary = substr($rawData, 0, strpos($rawData, "\r\n"));        // Fetch and process each part        $parts = $rawData ? array_slice(explode($boundary, $rawData), 1) : [];        foreach ($parts as $part) {            // If this is the last part, break            if ($part == "--\r\n") {                break;            }            // Separate content from headers            $part = ltrim($part, "\r\n");            list($rawHeaders, $content) = explode("\r\n\r\n", $part, 2);            $content = substr($content, 0, strlen($content) - 2);            // Parse the headers list            $rawHeaders = explode("\r\n", $rawHeaders);            $headers    = array();            foreach ($rawHeaders 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(                    '/^form-data; *name="([^"]+)"(; *filename="([^"]+)")?/',                    $headers['content-disposition'],                    $matches                );                $fieldName = $matches[1];                $fileName  = (isset($matches[3]) ? $matches[3] : null);                // If we have a file, save it. Otherwise, save the data.                if ($fileName !== null) {                    $localFileName = tempnam(sys_get_temp_dir(), 'sfy');                    file_put_contents($localFileName, $content);                    $files = $this->transformData($files, $fieldName, [                        'name'     => $fileName,                        'type'     => $headers['content-type'],                        'tmp_name' => $localFileName,                        'error'    => 0,                        'size'     => filesize($localFileName)                    ]);                    // register a shutdown function to cleanup the temporary file                    register_shutdown_function(function () use ($localFileName) {                        unlink($localFileName);                    });                } else {                    $data = $this->transformData($data, $fieldName, $content);                }            }        }        $fields = new ParameterBag($data);        return ["inputs" => $fields->all(), "files" => $files];    }    private function transformData($data, $name, $value)    {        $isArray = strpos($name, '[]');        if ($isArray && (($isArray + 2) == strlen($name))) {            $name = str_replace('[]', '', $name);            $data[$name][]= $value;        } else {            $data[$name] = $value;        }        return $data;    }}

Pls note: Those codes above not all mine, some from above comment, some modified by me.