php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #68605 $_FILES should not only be filled when POST
Submitted: 2014-12-14 17:20 UTC Modified: 2014-12-15 06:44 UTC
From: lewiscowles at me dot com Assigned:
Status: Closed Package: PHP Language Specification
PHP Version: Irrelevant OS: ALL
Private report: No CVE-ID: None
 [2014-12-14 17:20 UTC] lewiscowles at me dot com
Description:
------------
basically I am going to have to switch a major part of my app to Python because I cannot PUT multiple files via CURL custom request. Made worse by the fact PHP seems to have a bug that cuts off php:/input after the first file (probably to do with C strings and EOF)

Test script:
---------------
// server receiving data (putEcho.php was used)
<?php $reqMeth = ( isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : 'GET' ); ?>
<?php
$_PUT = array();
if( $reqMeth == 'PUT' ) {
	$putF = fopen( "php://input", "r" );
	$tmp = fread( $putF, MAX_POST_SIZE*10 ); // limit maximum PUT size to maximum POST size (seems reasonable)
	fclose($putF);
	$_PUT =  parse_raw_http_request( $tmp );
}
?>
put:
<?php var_dump( $_PUT ); ?>
file:
<?php var_dump( $_FILES ); ?>
<?php
//
// Functions
//
function parse_raw_http_request($input) {
	//var_dump($input);
	$a_data = array();
	// grab multipart boundary from content type header
	$matches = explode( str_ireplace( 'multipart/form-data; boundary=', '', $_SERVER['CONTENT_TYPE'] ), $input ); //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[0];
	// split content by boundary and get rid of last -- element
	$a_blocks = explode($boundary,$input);//preg_split("/-+$boundary/", $input);
	//array_pop($a_blocks);
	// loop data blocks
	foreach ($a_blocks as $id => $block) {
		if (empty($block)) { continue; }
		//var_dump( $block );
		// you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
		// parse uploaded files
		if (mb_strpos($block, 'application/octet-stream') !== FALSE) {
			//var_dump( $block );
			// match "name", then everything after "stream" (optional) except for prepending newlines
			preg_match("/name=\"([^\"]*)\".*filename=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/ms", $block, $matches);
			$a_data['files'][$matches[1]]['filename'] = basename( $matches[2] ); //for some reason CURL can send files with an absolute path
			$a_data['files'][$matches[1]]['data'] = $matches[3];
		} else { // parse all other fields
			// match "name" and optional value in between newline sequences
			preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/su', $block, $matches);
			$a_data[$matches[1]] = $matches[2];
		}
	}
	return $a_data;
}

// client to send data (name does not matter)
<?php

$data = array(
	'text'=>'bob
    bob
	漢字</textarea>
	ghghg
表外字?
表外字?表外字?',
	'file1' =>	new CURLFile( __DIR__.'/need a second monicle.jpg' ),
	'kanji'=>'漢字表外字?表外字?表外字?',
	'file2' => new CURLFile( __DIR__.'/10620539_301923573265336_9157682394656180014_n.jpg' )
);
?>
<pre><?php var_dump( json_decode( CURL_REQ( 'http://censored/putEcho.php', 'PUT', $data/*$_POST*/ ), true ) ); ?></pre>
<?php
//
// CURL lightweight helper Function...
//
function CURL_REQ( $url, $method, &$data ) {
	$ch = curl_init( $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
	curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $method );
	curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
	$response = curl_exec( $ch );
	return $response;
}

Expected result:
----------------
I expected (although I think I understand why) to be able to manually parse the results of my request, even if PHP did not support populating the $_FILES array...

Basically within 15minutes I was able to get this to work with Python, but a large part of my system is PHP. I am always telling people PHP is a great language and developers are part of the problem, but it seems to me that the decision to not populate $_FILES is a bit mad

for desired outcome, I like how python FLASK is able to give me 
request.args ($_GET)
request.form ($_PUT or $_POST)
request.files ($_FILES)

and wondered if it was something that could be easily patched into PHP.

In any case I have the C code and will try custom compiling, then patching, but my weekend is now dead, so it may be some time before I come up with something and after 20mins python working, it might just be easier for me to convert HTTP methods to python =(

Actual result:
--------------
well in the example given everything after the first file is missing from php://input stream, so not even the second var gets through... I can mitigate this by moving the posted form field values easily, but I never get more than one file, and I am unsure if it is complete (seems to end if 255 and 0 if I run ord on the last two bytes)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-12-15 06:44 UTC] lewiscowles at me dot com
-Status: Open +Status: Closed
 [2014-12-15 06:44 UTC] lewiscowles at me dot com
Bug closed due to extreme stupidity... 

switched to 
stream_get_contents instead of file_get_contents and everything just works...

must have been a long weekend really sorry
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Tue Dec 01 18:01:23 2020 UTC