Qafoo GmbH - passion for software quality
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Author: Manuel Pichler
:Date: Thu, 09 Dec 2010 09:37:11 +0100
:Revision: 20
:Copyright: All rights reserved
=============================
Testing file uploads with PHP
=============================
:Keywords: php, test, file, upload, phpt, phpunit, automated testing
:Description:
An extensive tutorial on how to test file uploads in PHP using PHPT and
PHPUnit.
:Abstract:
A question I am asked on a regular basis is, how you can test a file upload
with PHP. In this blog post, I take a precise look at this topic and show
you how to test your file uploads from within your standard testing
environment, using widely unknown testing framework for PHP.
A question I am asked on a regular basis is, how you can test a file upload
with PHP. In this blog post, I take a precise look at this topic and show you
how to test your file uploads from within your standard testing environment,
using widely unknown testing framework for PHP.
Let me begin with the testing framework that we all know and use in our daily
work, `PHPUnit`__. With this testing framework you have the opportunity to test
almost every part of your software. This also applies to file uploads, as long
as the application under test realizes uploads only via the PHP's magic
``$_FILES`` variable. In this case you can easily prepare an appropriately
crafted $_FILES array in the test's ``setUp()`` method, that can be accessed by
the software under test:
__ http://phpunit.de
.. note::
Get `20% discount`__ on PHP OOP/OOD trainings in January!
__ /blog/012_20_discount_oop_ood_training_january_2011.html
::
// ...
protected function setUp()
{
parent::setUp();
$_FILES = array(
'file_valid' => array(
'name' => 'foo.txt',
'tmp_name' => '/tmp/php42up23',
'type' => 'text/plain',
'size' => 42,
'error' => 0
)
);
}
// ...
But fortunately, in most cases this is not quite so simple because the software
to be tested utilizes the safer PHP functions ``is_uploaded_file()`` and
``move_uploaded_file()``. And in this case the manipulation of the ``$_FILES``
array does not work, because both functions operate on another level, so that
you cannot manipulate the input data within userland code::
class UploadExample
{
protected $dest;
public function __construct($dest)
{
$this->dest = rtrim($dest, '/') . '/';
}
public function handle($name)
{
if (is_uploaded_file($_FILES[$name]['tmp_name'])) {
move_uploaded_file(
$_FILES[$name]['tmp_name'],
$this->dest . $_FILES[$name]['name']
);
}
}
}
.. note::
Need a `PHP software quality expert`__? Qafoo provides consulting and
training in this area!
__ /services/consulting.html
Of course, you can still test this part of the application with a heavyweight
framework like `Selenium`__. Which, however, brings a number of disadvantages:
You must prepare and integrate a variety of other infrastructure components.
Beginning with a webserver, the Selenium server, a graphical user interface
with a browser and other infrastructure + bootstrap code that is required for a
working version of the software. All this increases the required effort to
execute a single test and the execution time of the test itself. This carries
the danger that false positives arise, caused as a side effects from the
complex test infrastructure.
__ http://seleniumhq.org
An alternative is the *PHP Testing Framework* or in short *PHPT*, which the
core developers use to test `PHP`__ itself and that is used by various other
PHP related projects like `PEAR`__ and `PECL`__. I would describe PHPT as a
lightweight testing framework, that is easy to learn due to its simple syntax
and a very expressive description format. In this article I will only take a
look at a limited set of the PHPT syntax. A complete documentation of PHPT can
be found on the `PHP Quality Assurance`__ website. The following example shows
the three base elements of a minimal PHPT test case.
__ http://php.net
__ http://pear.php.net
__ http://pecl.php.net
__ http://qa.php.net/phpt_details.php
# A description of the test itself::
--TEST--
Example test case
# The code under test(CUT)::
--FILE--
handle('file');
var_dump(file_exists('/tmp/example.rst'));
?>
--EXPECT--
bool(true)
And that's it. You have a complete PHPT test. Of course, you verify that it
works by calling the ``run-tests`` command::
~ $ pear run-tests upload-example.phpt
Running 1 tests
PASS Example test emulating a file upload[upload-example.phpt]
TOTAL TIME: 00:00
1 PASSED TESTS
0 SKIPPED TESTS
The test runs and the task has been completed successfully.
Maybe you wonder, how you can integrate these PHPT tests into an existing test
infrastructure? A fairly simple solutionexists: A relatively unknown feature of
`PHPUnit`__ is the built-in support for PHPT tests. This provides the great
benefit that you must not worry about the integration, your tests must only
inherit from the appropriate test classes:
__ http://phpunit.de
- PHPUnit_Extensions_PhptTestCase
- PHPUnit_Extensions_PhptTestSuite
The ``PHPUnit_Extensions_PhptTestCase`` class can be used to reference a single
PHPT test file, which is then executed by the PHPUnit testing framework. It
has to be noted that the absolute path to the PHPT file must be specified::
assertEquals(2, \MyClass::add(1,1)); }
public function testUploadedFile(){
$this->assertTrue(is_uploaded_file('foo')); }
} }
Tyrael
- Sebastian Bergmann at Thu, 09 Dec 2010 22:56:31 +0100
Instead of Runkit you may want to have a look at
https://github.com/sebastianbergmann/php-test-helpers
- Jean-Marc Fontaine at Thu, 09 Dec 2010 23:26:41 +0100
Internet is great! Yesterday, I was asking myself how to unit test file
uploads and the next day you publish this article. :)
Thanks!
- Tyrael at Fri, 10 Dec 2010 14:07:32 +0100
Thanks Sebastian, I missed the rename_function method in your extension.
Tyrael
- Ralf at Tue, 14 Dec 2010 10:51:35 +0100
Hello,
using extensions to redefine function names is considered as anti pattern
"The Local Hero" here:
http://blog.james-carr.org/2006/11/03/tdd-anti-patterns/
Would'nt it be better to wrap an API around these build-in PHP functions?
Ralf
- Tyrael at Tue, 14 Dec 2010 16:12:47 +0100
I can't see the renaming to be explicitly mentioned here. you could
configure your test cases to check for the required dependency and skip
them, if isn't available.
of course you can put in some effort and refactor the code to be more easy
to test, hence you can spare yourself from the above mentioned techniques.
Tyrael
- Thomas H at Wed, 15 Dec 2010 11:33:16 +0100
Hello, I need to test an upload file and I am really interested in your
solution. Unluckily I have problems with it.
I'm working under zend framework and test with PHPUnit. I tried the simple
example and it works prefectly. My problems begin with the file. I track
package with Wireshark and get :
------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition:
form-data; name="Filename"
example-text.phtml ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="folder"
/admin/configuration/ ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="Filedata";
filename="example-text.phtml" Content-Type: application/octet-stream
Ceci est un example ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="Upload"
Submit Query ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0--
My test is a part of TestSuite but it is not a problem cause I declare
directly the PHPT object in it. Here is my test : public function
testUploadLayout() {
$phpt = new PHPUnit_Extensions_PhptTestCase(
__DIR__.'/upload-layout.phpt', array('cgi' => 'php-cgi') ); $result =
$phpt->run(); $this->assertTrue($result->wasSuccessful()); }
This work with the simple example but not with upload. $result->errors()
returns me an empty array and $result->failures returns something which
hasn't end with the tracking of all my zend structure. I don't understand
anything.
My phpt file : --TEST-- Example test emulating a file upload
--POST_RAW-- ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0 Content-Disposition:
form-data; name="Filename"
example-text.phtml ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="folder"
/admin/configuration/ ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="Filedata";
filename="example-text.phtml" Content-Type: application/octet-stream
Ceci est un example ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0
Content-Disposition: form-data; name="Upload"
Submit Query ------------ae0GI3ae0ei4gL6Ij5KM7cH2GI3ae0-- --FILE-- --EXPECT-- test
It doesn't work either with a copy paste of your example.
I will be very grateful if you find anything to help me.
Thank you.
Thomas
- Matt Schuckmann at Fri, 02 Mar 2012 23:51:18 +0100
I can't get the upload_example.phpt test to work at all. For one in order
for the --POST_RAW-- to even have a chance at working in the plain old pear
run-tests environment you must included the --cgi=PHPCGI option like so pear
run-tests --cgi=PHPCGI upload-example.phpt
However, even with this option the test always.
Furthermore when I try run it through phpunit cli I must explicitly tell it
to run the Alltests.php script or else it confused by UploadExampleTest
(UploadExampleTest.php) which does not derive from
PHPUnit_Framework_TestCase. And even if I run Alltests.php the tests are
just skipped, presumably because the cgi environment is not setup.
There really isn't much info out there on this stuff, has anyone gotten this
to work?
- Glenn at Sun, 09 Feb 2014 21:23:25 +0100
After searching for an answer to how to test file uploads in zend framework
using phpunit, I eventually found this article and thought, 'Great, finally,
I've found the answer!'.
Well, I've got as far as downloading Wireshark and now I'm stuck. From what
I can see, there's nothing in the Wireshark manual to tell me how to capture
--POST_RAW-- data. And nothing from my initial playing with the software
seems to produce anything remotely resembling the data you have above.
As this is a key feature of this article, some pointers would be
appreciated! :)
- Alex at Mon, 29 Sep 2014 05:43:14 +0200
You can capture the POST message using web-browser with Chromium Developer
Tools - go to the Network section where request to the submitted page will
be listed. Click the request and the complete message will be listed there.
- Rasmus Schultz at Wed, 03 May 2017 08:11:11 +0200
Unless you're writing your own implementation of PSR-7, you shouldn't need
this. Use PSR-7 and all of this is neatly testable - someone else has done
the hard work of testing their PSR-7 implementation so that you will never
need to worry or struggle with these kinds of tests again :-)
- Oliver Russell at Mon, 12 Mar 2018 14:24:09 +0100
While adding PHP file upload function
(https://www.cloudways.com/blog/the-basics-of-file-upload-in-php/ ) to your
website, it is also necessary to validate the file type and other things.
Also, check for any sql injection vulnerabilities. Forms like these tend to
have such vulnerabilities.