Update UploadedFile's tests for SAPI and use of *_uploaded_file functions

This commit is contained in:
PJ Dietz 2015-05-02 10:11:08 -04:00
parent 257f2b7610
commit a93b37a548
3 changed files with 117 additions and 36 deletions

View File

@ -19,11 +19,35 @@ class UploadedFile implements UploadedFileInterface
private $tmpName; private $tmpName;
/** /**
* @param string $name * Create a new Uri. The arguments correspond with keys from arrays
* @param string $type * provided by $_FILES. For example, given this structure for $_FILES:
* @param int $size *
* @param string $tmpName * array(
* @param int $error * 'avatar' => arrary(
* 'name' => 'my-avatar.png',
* 'type' => 'image/png',
* 'size' => 90996,
* 'tmp_name' => 'phpUxcOty',
* 'error' => 0
* )
* )
*
* ...use this call:
*
* new UploadedFile(
* $_FILES['avatar']['name'],
* $_FILES['avatar']['type'],
* $_FILES['avatar']['size'],
* $_FILES['avatar']['tmp_name'],
* $_FILES['avatar']['error']
* );
*
* @param string $name Name of the file; provided by the client
* @param string $type Media type of the file; provided by the client
* @param int $size The file size in bytes.
* @param string $tmpName Local filesystem name of the file
* @param int $error One of PHP's UPLOAD_ERR_XXX constants.
* @see http://php.net/manual/en/features.file-upload.errors.php
*/ */
public function __construct($name, $type, $size, $tmpName, $error) public function __construct($name, $type, $size, $tmpName, $error)
{ {
@ -44,31 +68,34 @@ class UploadedFile implements UploadedFileInterface
* Retrieve a stream representing the uploaded file. * Retrieve a stream representing the uploaded file.
* *
* This method returns a StreamInterface instance, representing the * This method returns a StreamInterface instance, representing the
* uploaded file. The purpose of this method is to allow utilizing native PHP * uploaded file. The purpose of this method is to allow using native PHP
* stream functionality to manipulate the file upload, such as * stream functionality to manipulate the file upload, such as
* stream_copy_to_stream() (though the result will need to be decorated in a * stream_copy_to_stream() (though the result will need to be decorated in
* native PHP stream wrapper to work with such functions). * a native PHP stream wrapper to work with such functions).
* *
* If the moveTo() method has been called previously, this method will raise * If the moveTo() method has been called previously, this method will
* an exception. * raise an exception.
* *
* @return StreamInterface Stream representation of the uploaded file. * @return StreamInterface Stream representation of the uploaded file.
* @throws \RuntimeException in cases when no stream is available or can be * @throws \RuntimeException in cases when no stream is available or can
* created. * be created.
*/ */
public function getStream() public function getStream()
{ {
if ($this->moved) { if ($this->moved) {
throw new \RuntimeException("File has already been moved"); throw new \RuntimeException("File has already been moved");
} }
if (php_sapi_name() !== "cli" && !is_uploaded_file($this->tmpName)) {
throw new \RuntimeException("File is not an uploaded file.");
}
return $this->stream; return $this->stream;
} }
/** /**
* Move the uploaded file to a new location. * Move the uploaded file to a new location.
* *
* Use this method as an alternative to move_uploaded_file(). This method is * Use this method as an alternative to move_uploaded_file(). This method
* guaranteed to work in both SAPI and non-SAPI environments. * is guaranteed to work in both SAPI and non-SAPI environments.
* *
* The original file or stream will be removed on completion. * The original file or stream will be removed on completion.
* *
@ -87,14 +114,10 @@ class UploadedFile implements UploadedFileInterface
if ($this->tmpName === null || !file_exists($this->tmpName)) { if ($this->tmpName === null || !file_exists($this->tmpName)) {
throw new \RuntimeException("File " . $this->tmpName . " does not exist."); throw new \RuntimeException("File " . $this->tmpName . " does not exist.");
} }
$sapi = php_sapi_name(); if (php_sapi_name() === "cli") {
$whitelist = ["apache", "apache2filter", "apache2handler", "cgi", "fpm-fcgi", "cgi-fcgi"];
if (in_array($sapi, $whitelist)) {
// @codeCoverageIgnoreStart
move_uploaded_file($this->tmpName, $path);
} else {
// @codeCoverageIgnoreEnd
rename($this->tmpName, $path); rename($this->tmpName, $path);
} else {
move_uploaded_file($this->tmpName, $path);
} }
$this->moved = true; $this->moved = true;
} }

View File

@ -0,0 +1,24 @@
<?php
namespace WellRESTed\Message;
class UploadedFileState
{
public static $php_sapi_name;
public static $is_uploaded_file;
}
function php_sapi_name()
{
return UploadedFileState::$php_sapi_name;
}
function move_uploaded_file($source, $target)
{
return rename($source, $target);
}
function is_uploaded_file($file)
{
return UploadedFileState::$is_uploaded_file;
}

View File

@ -3,8 +3,13 @@
namespace WellRESTed\Test\Message; namespace WellRESTed\Test\Message;
use WellRESTed\Message\UploadedFile; use WellRESTed\Message\UploadedFile;
use WellRESTed\Message\UploadedFileState;
// Hides several php core functions for testing.
require_once __DIR__ . "/../../../src/UploadedFileState.php";
/** /**
* @coversDefaultClass WellRESTed\Message\UploadedFile
* @uses WellRESTed\Message\UploadedFile * @uses WellRESTed\Message\UploadedFile
* @uses WellRESTed\Message\Stream * @uses WellRESTed\Message\Stream
* @uses WellRESTed\Message\NullStream * @uses WellRESTed\Message\NullStream
@ -17,6 +22,7 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
UploadedFileState::$php_sapi_name = "cli";
$this->tmpName = tempnam(sys_get_temp_dir(), "tst"); $this->tmpName = tempnam(sys_get_temp_dir(), "tst");
$this->movePath = tempnam(sys_get_temp_dir(), "tst"); $this->movePath = tempnam(sys_get_temp_dir(), "tst");
} }
@ -36,8 +42,8 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
// getStream // getStream
/** /**
* @covers WellRESTed\Message\UploadedFile::__construct * @covers ::__construct
* @covers WellRESTed\Message\UploadedFile::getStream * @covers ::getStream
*/ */
public function testGetStreamReturnsStreamInterface() public function testGetStreamReturnsStreamInterface()
{ {
@ -46,22 +52,21 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @covers WellRESTed\Message\UploadedFile::__construct * @covers ::__construct
* @covers WellRESTed\Message\UploadedFile::getStream * @covers ::getStream
*/ */
public function testGetStreamReturnsStreamWrappingUploadedFile() public function testGetStreamReturnsStreamWrappingUploadedFile()
{ {
$content = "Hello, World!"; $content = "Hello, World!";
file_put_contents($this->tmpName, $content); file_put_contents($this->tmpName, $content);
$file = new UploadedFile("", "", 0, $this->tmpName, ""); $file = new UploadedFile("", "", 0, $this->tmpName, "");
$stream = $file->getStream(); $stream = $file->getStream();
$this->assertEquals($content, (string) $stream); $this->assertEquals($content, (string) $stream);
} }
/** /**
* @covers WellRESTed\Message\UploadedFile::__construct * @covers ::__construct
* @covers WellRESTed\Message\UploadedFile::getStream * @covers ::getStream
*/ */
public function testGetStreamReturnsEmptyStreamForNoFile() public function testGetStreamReturnsEmptyStreamForNoFile()
{ {
@ -70,27 +75,56 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @covers WellRESTed\Message\UploadedFile::__construct * @covers ::__construct
* @covers WellRESTed\Message\UploadedFile::getStream * @covers ::getStream
* @expectedException \RuntimeException * @expectedException \RuntimeException
*/ */
public function testGetStreamThrowsExceptionAfterMoveTo() public function testGetStreamThrowsExceptionAfterMoveTo()
{ {
$content = "Hello, World!"; $content = "Hello, World!";
file_put_contents($this->tmpName, $content); file_put_contents($this->tmpName, $content);
$file = new UploadedFile("", "", 0, $this->tmpName, ""); $file = new UploadedFile("", "", 0, $this->tmpName, "");
$file->moveTo($this->movePath); $file->moveTo($this->movePath);
$file->getStream(); $file->getStream();
} }
/**
* @covers ::__construct
* @covers ::getStream
* @expectedException \RuntimeException
*/
public function testGetStreamThrowsExceptionForNonUploadedFile()
{
UploadedFileState::$php_sapi_name = "apache";
UploadedFileState::$is_uploaded_file = false;
$file = new UploadedFile("", "", 0, "", 0);
$file->getStream();
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// move // moveTo
/** /**
* @covers WellRESTed\Message\UploadedFile::moveTo * @covers ::moveTo
*/ */
public function testMoveToRelocatesUploadedFileToDestiationIfExists() public function testMoveToSapiRelocatesUploadedFileToDestiationIfExists()
{
UploadedFileState::$php_sapi_name = "fpm-fcgi";
$content = "Hello, World!";
file_put_contents($this->tmpName, $content);
$originalMd5 = md5_file($this->tmpName);
$file = new UploadedFile("", "", 0, $this->tmpName, "");
$file->moveTo($this->movePath);
$this->assertEquals($originalMd5, md5_file($this->movePath));
}
/**
* @covers ::moveTo
*/
public function testMoveToNonSapiRelocatesUploadedFileToDestiationIfExists()
{ {
$content = "Hello, World!"; $content = "Hello, World!";
file_put_contents($this->tmpName, $content); file_put_contents($this->tmpName, $content);
@ -103,10 +137,10 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @covers WellRESTed\Message\UploadedFile::moveTo * @covers ::moveTo
* @expectedException \RuntimeException * @expectedException \RuntimeException
*/ */
public function testThrowsExcpetionOnSubsequentCallToMoveTo() public function testMoveToThrowsExcpetionOnSubsequentCall()
{ {
$content = "Hello, World!"; $content = "Hello, World!";
file_put_contents($this->tmpName, $content); file_put_contents($this->tmpName, $content);