Directory Uploads in PHP 8.1

Published On28 Jul 2021

Directory-uploads in PHP

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>

Directory-uploads in field rendered in browser Directory-uploads window

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.

Recent Articles on PHP.Watch

All ArticlesFeed 
PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

PHP 8.4 Installation and Upgrade guide for Ubuntu and Debian

A guide for Debian and Ubuntu on how to install PHP 8.4 on a new server or how to upgrade an existing PHP setup to PHP 8.4.
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜