Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-uploading a file with AJAX after it was changed causes net::ERR_UPLOAD_FILE_CHANGED in Chrome

I have a simple HTML form that sends an selected file via AJAX to an API endpoint.

If I do the following steps:

  1. Press the Upload button and make an POST request to upload the file
  2. Change the selected file on the filesystem (in a text editor), without picking it again in the file picker.
  3. Press the Upload button again, and make as second POST request

Chrome will fail the second request with an error: net::ERR_UPLOAD_FILE_CHANGED. Note that if you change the file before the initial upload, the file will be uploaded without a problem. The error happens only on the second upload, when you change the file after an initial successful upload. I am testing this with CSV files, and changing them in a text editor.

There does not seem to be a way to isolate that error.

Is there any way around this?

If not, is it possible to catch this specific error, and display a meaningful message to the user. The error that Fetch returns in the promise has no specific info about this. The only place where I see the ERR_UPLOAD_FILE_CHANGED is in the browser dev console.

I am pretty sure that there was not any problem with this about a year ago (early 2019), as the possibility to re-upload a changed file, played in nicely in our UI flow. Now we need to force the user to pick the file again. So my assumption that this was introduced with a recent chrome update.

Here is a simplified snippet of the code:

<html>
<head>
        <script type='text/javascript'>
        document.addEventListener("DOMContentLoaded", function(){

                const button = document.querySelector('#btnSubmit');

                button.addEventListener('click', () => {
                        const form = new FormData(document.querySelector('#theForm'));
                        const url = '/my-api'
                        const request = new Request(url, {
                                method: 'POST',
                                body: form
                        });
        
                        fetch(request).then(function() {
                                console.log("ok");
                        }).catch(function(err) {
                                console.log(err);
                        });
                });
        });
        </script>

</head>
<body>
        <form enctype="multipart/form-data" action="" method="post" id='theForm'>
                <input type="file" name="csvfile" id="csvfile" value="" /></td>
                <input type="button" name="uploadCSV" value="Upload" id='btnSubmit'/>
        </form>
</body>
</html>

EDIT: The bug is marked as WontFix on bugs.chromium.org: https://bugs.chromium.org/p/chromium/issues/detail?id=1086707#c7

like image 218
Martin Taleski Avatar asked May 20 '20 14:05

Martin Taleski


4 Answers

A quick workaround would be to include a complete parameter to AJAX call (or any equivalent of a finally call that always gets invoked) and add code that resets the form. I've noticed that attaching the file again solves it. While I appreciate it's not a real solution to the problem, it does provide handling or rather avoidance of that error.

This way, if the user for any reason needs to re-upload the file, they'll have to choose the file again.

Example below:

$.ajax({
    url: this.action,
    type: this.method,
    data: this.data,
    success: function (response) { 
        // success scenario
    },
    error: function (result) {
        // error scenario
    },
    complete: function (data) {
        $('#uploadForm')[0].reset(); // this will reset the form fields
    }
});
like image 119
Duck Ling Avatar answered Oct 09 '22 23:10

Duck Ling


I managed to get a more descriptive error using the javascript File Reader. After the the file is changed and the button is pressed for the second time, the FileReader throws a somewhat more descriptive message:

DOMException: The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.

At least this is an error that can be caught and displayed to the user, so they are aware that they need to re-select the file.

Here is the snippet:

<html>
    <head>
        <title>Test</title>
        <script type='text/javascript'>
        document.addEventListener("DOMContentLoaded", function(){

            const button = document.querySelector('#btnSubmit');

            button.addEventListener('click', () => {
                let inputElement = document.getElementById('csvfile');

                for (let i = 0; i < inputElement.files.length; i++) {
                    const file = inputElement.files[i];

                    console.log(file);

                    const reader = new FileReader();
                    reader.readAsText(file, "UTF-8");

                    reader.onload = function (evt) {
                        console.log(evt.target.result);
                    }

                    reader.onerror = function (err) {
                        // read error caught here
                        console.log(err);
                        console.log(err.target.error);
                    }

                }

            });
        });
        </script>

    </head>
    <body>
        <form enctype="multipart/form-data" action="" method="post" id='theForm'>
                <input type="file" name="csvfile" id="csvfile" value="" />
                <input type="button" name="uploadCSV" value="Upload" id='btnSubmit'/>
        </form>
    </body>
</html> 

It seems that there is no way around this at the moment (June 2020). There is an open bug on Chromium:

https://bugs.chromium.org/p/chromium/issues/detail?id=1084880&q=ERR_UPLOAD_FILE_CHANGED&can=2

like image 6
Martin Taleski Avatar answered Oct 09 '22 23:10

Martin Taleski


No, if the user has changed file it already another file so you can't (should not) get access to this file without user explicit user action otherwise it would be a security issue.

You can save the file in the memory when user uploads it.


document.getElementById('fileInput').addEventListener('change', function() {
   saveFileConentInMemory(this.files[0].arrayBuffer());
});

And when the user press the "Send" button just get this content from memory and send it

button.addEventListener('click', () => {
   const file = getFileContentFromMemeory();
   send(file);
})

Yes, you can't be sure that you send the latest version of the file but you should be sure that you send the content which was uploaded.

Also, you should aware of memory consumption and asynchronous API of reading file (so you still can get this error about changed content even when you write it in memory)

like image 1
maksimr Avatar answered Oct 09 '22 23:10

maksimr


I am using Angular, these 2 steps (workaround) solves the issue

1] Reset form on error -

this.input.reset()

2] Reset value of HTML input -

<input type="file" #fileInput (change)="OnFileSelected($event)" (click)="$event.target.value=null"/>
like image 2
Swapnil Patwa Avatar answered Oct 10 '22 00:10

Swapnil Patwa