Directory Uploads in PHP 8.1
One of the new features in PHP 8.1 is that PHP's $_FILES
super global variable now contains the path to the files uploaded by the user. This information is provided by browsers that support entire directory uploads, and from PHP 8.1, this information is made available to support directory-uploads in PHP applications.
An HTML form with a file
field, containing a webkitdirectory
attribute is considered a directory-upload field. Browsers supporting this feature allows the user to select an entire directory instead of individual files. Once the user selects the directory to upload, browser submits all files inside that directory, including all sub directories and their files, to the server.
In a PHP application running PHP 8.1 or later, the file path is made available from the $_FILES
super global, and the application can store the file paths in addition to other information such as the file name, size, etc.
Note that the file path provided by browsers is user-input, and must be treated as such. Such fields are prone to path traversal attacks, which are not that uncommon.
webkitdirectory
attribute
webkitdirectory
is an HTML attribute that can be used in input
HTML elements with type=file
. It is not a browser-standard, but all major browsers support it, including Chrome 6+, Firefox 50+, Edge 13+, and Safari 11.1+.
HTML directory upload field
Directory uploads reuse the standard input
elements with type=file
. Browsers that support the webkitdirectory
will offer a directory-upload window, and the rest will offer a standard file upload window.
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="dir_upload[]" webkitdirectory multiple />
<input type="submit" value="Upload"/>
</form>
Accessing file path in PHP
When the user uploads a directory using a directory-upload field, browser submits the relative path to that file along with the file name, file size, and file contents.
In PHP, file upload information is accessed from the $_FILES
super global. Prior to PHP 8.1, the relative file path provided by the browser was not passed to $_FILES
, making it impossible to determine the directory hierarchy of a directory-upload.
From PHP 8.1, this information is passed to $_FILES
, and can be used to store a directory-upload along with their relative paths, or even recreate the directory in the server.
The $_FILES
array contains file upload information indexed by the name
attribute of the HTML input field. In the example above, the input
field is named name=dir_upload[]
. It means that the $_FILES
array contains a direct $_FILES['dir_upload']
array, which in turn contains associative arrays with keys such as name
, tmp_name
, etc.
project_files
├── data
│ └── data.csv
├── outlines
│ ├── intro.doc
│ └── welcome.doc
├── presentation.pdf
├── presentations
│ ├── ayesh.ppt
│ ├── mentor.doc
│ └── phpwatch.ppt
└── report.xls
With a directory structure similar to above, the $_FILES
array only returns the base file name from $_FILES['dir_upload']['name']
.
var_dump($_FILES);
array(1) {
["dir_upload"]=> array(6) {
["name"]=> array(8) {
[0]=> string(8) "data.csv"
[1]=> string(16) "presentation.pdf"
[2]=> string(9) "intro.doc"
[3]=> string(11) "welcome.doc"
[4]=> string(10) "report.xls"
[5]=> string(12) "phpwatch.ppt"
[6]=> string(9) "ayesh.ppt"
[7]=> string(10) "mentor.doc"
}
...type, tmp_name, error, size
Notice how the relative paths are missing from the $_FILES['dir_upload']['name']
array, which makes it impossible to retrieve the relative path to the files from the user file system.
From PHP 8.1 and later, a new array is added to each file upload field in $_FILES
, which contains the relative path to the files.
var_dump($_FILES);
array(1) {
["dir_upload"]=> array(6) {
["name"]=> array(8) {
[0]=> string(8) "data.csv"
[1]=> string(16) "presentation.pdf"
...
}
+ ["full_path"]=> array(8) {
+ [0]=> string(27) "project-files/data/data.csv"
+ [1]=> string(30) "project-files/presentation.pdf"
+ [2]=> string(32) "project-files/outlines/intro.doc"
+ [3]=> string(34) "project-files/outlines/welcome.doc"
+ [4]=> string(24) "project-files/report.xls"
+ [5]=> string(40) "project-files/presentations/phpwatch.ppt"
+ [6]=> string(37) "project-files/presentations/ayesh.ppt"
+ [7]=> string(38) "project-files/presentations/mentor.doc"
}
...type, tmp_name, error, size
The new full_path
array in each file upload field lists the complete path to the file.
On directory uploads, it is now possible to store the full path to each uploaded file. Applications that prefer to store the uploaded files in the same directory structure as the user who uploaded the files can do so by moving the files recursively to the relevant directories on the server.
Security Hardening
The full_path
array in each $_FILES
field is not safe, because it is direct user input. All values from this array must be validated to prevent potential vulnerabilities, especially when storing the uploaded files in sub directories on the server.
- The submitted
full_path
value might trigger path traversal. For example, if the server saves the user uploaded files, make sure to reject values that might attempt path traversal - Validate nested levels and lengths of the file paths. Deeply nested sub directories, or excessively long file paths can lead to errors and may lead to file system failures.