5

I'm using Valum's file uploader to upload images with AJAX. This script submits the file to my server in a way that I don't fully understand, so it's probably best to explain by showing my server-side code:

$pathToFile = $path . $filename;

//Here I get a file not found error, because the file is not yet at this address
getimagesize($pathToFile);

$input = fopen('php://input', 'r');
$temp = tmpfile();
$realSize = stream_copy_to_stream($input, $temp);

//Here I get a string expected, resource given error 
getimagesize($input);

fclose($input);

$target = fopen($pathToFile, 'w');
fseek($temp, 0, SEEK_SET);

//Here I get a file not found error, because the image is not at the $target yet
getimagesize($pathToFile);

stream_copy_to_stream($temp, $target);
fclose($target);

//Here it works, because the image is at the desired location so I'm able to access it with $pathToFile. However, the (potentially) malicious file is already in my server.
getimagesize($pathToFile);

The problem is that I want to perform some file validation here, using getimagesize(). getimagesize only supports a string, and I only have resources available, which result in the error: getimagesize expects a string, resource given.

It does work when I perform getimagesize($pathTofile) at the end of the script, but then the image is already uploaded and the damage could already have been done. Doing this and performing the check afterwards and then maybe deleting te file seems like bad practice to me.

The only thing thats in $_REQUEST is the filename, which i use for the var $pathToFile. $_FILES is empty.

How can I perform file validation on streams?

EDIT: the solution is to first place the file in a temporary directory, and perform the validation on the temporary file before copying it to the destination directory.

// Store the file in tmp dir, to validate it before storing it in destination dir
$input = fopen('php://input', 'r');
$tmpPath = tempnam(sys_get_temp_dir(), 'upl'); // upl is 3-letter prefix for upload
$tmpStream = fopen($tmpPath, 'w'); // For writing it to tmp dir
stream_copy_to_stream($input, $tmpStream);
fclose($input);
fclose($tmpStream);

// Store the file in destination dir, after validation
$pathToFile = $path . $filename;
$destination = fopen($pathToFile, 'w');
$tmpStream = fopen($tmpPath, 'r'); // For reading it from tmp dir
stream_copy_to_stream($tmpStream, $destination);
fclose($destination);
fclose($tmpStream);
Thomas
  • 20,731
  • 6
  • 22
  • 23
  • Please add the getimagesize code as well to your question, otherwise it's hard to answer. From your question it looks like that you have a string as well to the file and you could use the string instead of the resource-id. So would be good to know what prevents you doing so. – hakre Nov 21 '11 at 11:24
  • Done, I hope this clarifies it a bit. – Thomas Nov 21 '11 at 11:58

2 Answers2

7

PHP 5.4 now supports getimagesizefromstring

See the docs: http://php.net/manual/pt_BR/function.getimagesizefromstring.php

You could try:

$input = fopen('php://input', 'r');
$string = stream_get_contents($input);
fclose($input);

getimagesizefromstring($string);
Samin
  • 610
  • 3
  • 11
  • 22
2

Instead of using tmpfile() you could make use of tempnam() and sys_get_temp_dir() to create a temporary path.

Then use fopen() to get a handle to it, copy over the stream.

Then you've got a string and a handle for the operations you need to do.

//Copy PHP's input stream data into a temporary file

$inputStream   = fopen('php://input', 'r');
$tempDir       = sys_get_temp_dir();
$tempExtension = '.upload';

$tempFile   = tempnam($tempDir, $tempExtension);
$tempStream = fopen($tempFile, "w");
$realSize   = stream_copy_to_stream($inputStream, $tempStream);

fclose($tempStream);

getimagesize($tempFile);
hakre
  • 193,403
  • 52
  • 435
  • 836
  • I think this is going in the right direction, but tempnam() returns a string instead of a resource, so how can I use stream_copy_to_stream()? Should I use fwrite? Sorry I'm a bit lost here, I dont even really understand what a stream is. – Thomas Nov 21 '11 at 12:25
  • use `fopen()` on that string. Then you got a resource as well. – hakre Nov 21 '11 at 12:44
  • I think I'm almost there, the only problem is now that the file in the target directory has 0 bytes. Do you know what I'm doing wrong? I placed my current code in the question. – Thomas Nov 21 '11 at 15:04
  • have you copied the stream already? Then it should be larger than 0 bytes. – hakre Nov 21 '11 at 15:46
  • I added some example code as well, it adds the `fflush` command as well in case there is some unwritten buffer. Could be the issue as well. Take a look. – hakre Nov 21 '11 at 15:52
  • Unfortunately still no luck, the uploaded file is still empty. I edited my code to show how I'm using fflush. – Thomas Nov 23 '11 at 19:39
  • Fixed! I had to reopen the file from the tmp dir with fopen, instead of using the same resource for writing it there and reading it from there. Doesn't make sense to me, but that was the problem. – Thomas Nov 23 '11 at 20:12
  • Probably because there is a file pointer which then pointed at the end of the file, see as well [`fseek`](http://php.net/fseek) and [`rewind`](http://php.net/rewind). – hakre Nov 24 '11 at 07:42