Skip to content

Use of wp_unslash() breaks file uploads on IIS hosts #1356

Description

@juliolopez78

Hello,

When using Directorist on Windows IIS hosted WordPress, file uploads are broken due to the use of wp_unslash() on lines 577 and 578 of includes/classes/class-add-listing.php. The result is a broken file path to the temp uploaded file. For example, the file C:\Program Files\PHPv8.1\upload\phpAC39.tmp loses all the slashes and ends up being C:Program FilesPHPv8.1uploadphpAC39.tmp. So when the file gets passed over to wp_handle_upload() it fails the validation because the temp_name is not a valid file path.

$files = ! empty( $_FILES['listing_img'] ) ? directorist_clean( wp_unslash( $_FILES['listing_img'] ) ) : array();

$files_meta = ! empty( $_POST['files_meta'] ) ? directorist_clean( wp_unslash( $_POST['files_meta'] ) ) : array();

Is it absolutely necessary to unslash the temp_name for the uploaded files? If you're really concerned about validating the values in the $file array ('name', 'tmp_name', 'type', etc.) I suggest writing validation that is specific for the array and then validate the value for each key instead of the brute-force approach of running the whole set of data through wp_unslash().

You can set up a simple validation using array_filter() to remove invalid files. In the example below, isFileUploadValid() is run for every uploaded file. If it returns true, the file is added to the resulting array, otherwise it is not, essentially cleaning out the invalid uploads.

/**
 * Filter callback for use with array_filter(). Receives a file array. Ensures it has all the required keys and 
 * that all the values pass the required validations. Any arrays that fail, will be removed, thus that file will
 * not be uploaded
 */
function isFileUploadValid(array $file): bool
{
    // An array of keys that MUST be in the $file array
    $validKeys = ['name', 'type', 'tmp_name', 'error', 'size',];
    
    // Configure the validations you want to run on each key in $file. The keys in this array 
    // match the names of the keys in $file, the values are the names of callbacks you can
    // set up to validate the key's value. They return true if the value passes, false otherwise
    $validations = [
        'name' => 'fileNameIsValid',
        'type' => 'fileTypeIsValid',
        'tmp_name' => 'fileTmpNameIsValid',
        'error' => 'fileErrorIsValid',
        'size' => 'fileSizeIsValid',
    ];
    
    // Loop through the keys and make sure the $file array has all the ones we want
    foreach ($validKeys as $key) {
        if (!array_key_exists($key, $file)) {
            return false;
        }
    }
    
    // unset $key to avoid any cross-contamination from the previous foreach
    unset($key);
    
    // Loop through the validations and apply them to each value in $file
    foreach($validations as $key => $callback) {
        if (call_user_func($callback, $file[$key]) === false) {
            return false;
        }
    }
    
    // do any other validation you deem necessary
    
    // return true if you get this far
    return true;
}

/*
 * Normalizes the $_FILES array. Necessary when multiple files are uploaded under the same input name, i.e.
 * name="my_input[]". The result is a 2D array where the values for each key are arrays too.
 * [
 *     'my_input' => [
 *         'name' => [
 *             'somefile.jpg',
 *             'somefile2.jpg',
 *         ],
 *         'type' => [
 *             'image/jpg',
 *             'image/jpg',
 *         ],
 *         // etc.
 *     ]
 * ]
 * This function returns a 2D array that looks like this:
 * [
 *     'my_input_0' => [
 *         'name' => 'somefile.jpg',
 *         'type' => 'image/jpg',
 *         // etc.
 *     ],
 *     'my_input_1' => [
 *         'name' => 'somefile2.jpg',
 *         'type' => 'image/jpg',
 *         // etc.
 *     ],
 * ]
 */
function normalizeFiles(array $files): array
{
    $returnArray = [];

    foreach ($files as $file => $fileData) {
        foreach ($fileData as $fileKey => $fileKeyValues) {
            if (is_array($fileKeyValues)) {
                foreach ($fileKeyValues as $index => $value) {
                    $fileName = "{$file}_$index";

                    if (!array_key_exists($fileName, $returnArray)) {
                        $returnArray[$fileName] = [];
                    }

                    $returnArray[$fileName][$fileKey] = $value;
                }
            } elseif (is_scalar($fileKeyValues)) {
                if (!array_key_exists($file, $returnArray)) {
                    $returnArray[$file] = [];
                }
                
                $returnArray[$file][$fileKey] = $fileKeyValues;
            }
        }
    }
    
    return $returnArray;
}

// In the event that you have multiple files uploaded, you'll need to normalize the $_FILES array
$filesToValidate = normalizeFiles($_FILES);

$validUploads = array_filter($filesToValidate, isFileUploadValid);

// continue uploading the file to the DB

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions