javascriptphpformsxmlhttprequestasyncfileupload

XMLHttpRequest() not sending FormData() resulting in empty PHP $_FILES array and no file uploaded


What I am trying to do:

Essentially I am trying to upload a file to a directory on the server that a user uploads via a HTML <input type="file"> element.

To do this I am creating a new XMLHttpRequest on the <input> element's change event which should send the data of the uploaded file to the upload.php file which would then handle the uploaded file and upload it to the server asynchronously.

My Code:

HTML

<form class="js-upload-form" method="POST" action="upload.php" enctype="multipart/form-data">
    <input class="button js-uploaded-file" type="file" name="file" />
</form>

JS

document.querySelector('.js-uploaded-file').addEventListener('change', function() {

    let file = this.files[0];
    let formData = new FormData();

    formData.append('file', file);

    let xhr = new XMLHttpRequest();
    xhr.open('POST', 'upload.php', true);

    xhr.setRequestHeader('Content-type', 'multipart/form-data');

    xhr.upload.onprogress = function(e) {

        if (e.lengthComputable) {

            let percentComplete = (e.loaded / e.total) * 100;
            console.log(percentComplete + '% uploaded');

        }
    };

    xhr.onload = function() {

        if (this.status == 200) {

            console.info(this.response);

        }
    };

    xhr.send(formData);

}, false);

PHP (upload.php)

print_r($_FILES);

$currentDir = getcwd();
$uploadDirectory = "/uploads/";

$errors = []; // Store all foreseen and unforseen errors here

$fileName = preg_replace("/[^A-Z0-9._-]/i", "_", $_FILES['file']['name']);
$fileSize = $_FILES['file']['size'];
$fileTmpName = $_FILES['file']['tmp_name'];
$fileType = $_FILES['file']['type'];

$uploadPath = $currentDir . $uploadDirectory . $fileName;

if ($fileSize > 2000000) {
    $errors[] = "This file is more than 2MB. Sorry, it has to be less than or equal to 2MB";
}

if (empty($errors)) {
    $didUpload = move_uploaded_file($fileTmpName, $uploadPath);

    if ($didUpload) {
        echo "The file " . basename($fileName) . " has been uploaded";
    } else {
        echo "An error occurred somewhere. Try again or contact the admin";
    }
} else {
    foreach ($errors as $error) {
        echo $error . "These are the errors" . "\n";
    }
}

My Problem

This code is simply not working. Printing the $_FILES array returns an empty array, and console logging the xhr.response logs the error message set in the PHP ('An error occurred somewhere. Try again or contact the admin'). I would really appreciate any help on solving this issue as I have looked through countless other online resources regarding this issue and even though I feel like my code does exactly what they all say to do, it still doesn't work.

What I've tried:

I tried simply submitting the form instead of trying to do so using the FormData() object and <input> change event by adding a submit button and although the page redirected to ...url/upload.php and didn't work asynchronously, the $_FILES array contained the correct data of the uploaded file and the file was uploaded to the server, which makes me think there must be an issue in my Javascript code, either relating to the XMLHttpRequest or the FormData object.


Solution

  • Normally, XMLHttpRequest will generate an appropriate Content-Type from the FormData object.

    You, however, are overriding it with one you created manually:

    xhr.setRequestHeader('Content-type', 'multipart/form-data');
    

    However, the mandatory boundary attribute is missing, so PHP can't find the points to split the parts of the request up.

    Don't override the Content-Type. Remove the quoted line.