Detecting folders/directories in javascript FileList objects

19.5k views Asked by At

I have recently contributed some code to Moodle which uses some of the capabilities of HTML5 to allow files to be uploaded in forms via drag and drop from the desktop (the core part of the code is here: https://github.com/moodle/moodle/blob/master/lib/form/dndupload.js for reference).

This is working well, except for when a user drags a folder / directory instead of a real file. Garbage is then uploaded to the server, but with the filename matching the folder.

What I am looking for is an easy and reliable way to detect the presence of a folder in the FileList object, so I can skip it (and probably return a friendly error message as well).

I've looked through the documentation on MDN, as well as a more general web search, but not turned up anything. I've also looked through the data in the Chrome developer tools and it appears that the 'type' of the File object is consistently set to "" for folders. However, I'm not quite convinced this is the most reliable, cross-browser detection method.

Does anyone have any better suggestions?

7

There are 7 answers

15
gilly3 On BEST ANSWER

You cannot rely on file.type. A file without an extension will have a type of "". Save a text file with a .jpg extension and load it into a file control, and its type will display as image/jpeg. And, a folder named "someFolder.jpg" will also have its type as image/jpeg.

Instead, try to read the first byte of the file. If you are able to read the first byte, you have a file. If an error is thrown, you probably have a directory:

try {
    await file.slice(0, 1).arrayBuffer();
    // it's a file!
}
catch (err) {
    // it's a directory!
}

If you are in the unfortunate position of supporting IE11, The file will not have the arrayBuffer method. You have to resort to the FileReader object:

// use this code if you support IE11
var reader = new FileReader();
reader.onload = function (e) {
    // it's a file!
};
reader.onerror = function (e) {
    // it's a directory!
};
reader.readAsArrayBuffer(file.slice(0, 1));
6
Nick G. On

I also ran into this problem and below is my solution. Basically, I took have a two pronged approach:

(1) check whether the File object's size is large, and consider it to be a genuine file if it is over 1MB (I'm assuming folders themselves are never that large). (2) If the File object is smaller than 1MB, then I read it using FileReader's 'readAsArrayBuffer' method. Successful reads call 'onload' and I believe this indicates the file object is a genuine file. Failed reads call 'onerror' and I consider it a directory. Here is the code:

var isLikelyFile = null;
if (f.size > 1048576){ isLikelyFile = false; }
else{
    var reader = new FileReader();
    reader.onload = function (result) { isLikelyFile = true; };
    reader.onerror = function(){ isLikelyFile = false; };
    reader.readAsArrayBuffer(f);
}
//wait for reader to finish : should be quick as file size is < 1MB ;-)
var interval = setInterval(function() {
    if (isLikelyFile != null){
        clearInterval(interval);
        console.log('finished checking File object. isLikelyFile = ' + isLikelyFile);
    }
}, 100);

I tested this in FF 26, Chrome 31, and Safari 6 and three browsers call 'onerror' when attempting to read directories. Let me know if anyone can think of a use case where this fails.

1
Richie On

I proposing calling FileReader.readAsBinaryString on the File object. In Firefox, this will raise an Exception when the File is a Directory. I only do this if the File meets the conditions proposed by gilly3.

Please see my blog post at http://hs2n.wordpress.com/2012/08/13/detecting-folders-in-html-drop-area/ for more details.

Also, version 21 of Google Chrome now supports dropping folders. You can easily check if the dropped items are folders, and also read their contents.

Unfortunately, I donĀ“t have any (client-side) solution for older Chrome versions.

0
Nam Nguyen On

FYI, this post will tell you how to use dataTransfer API in Chrome to detect file type: http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available

0
CubicleSoft On

The best option is to use both the 'progress' and 'load' events on a FileReader instance.

var fr = new FileReader();
var type = '';

// Early terminate reading files.
fr.addEventListener('progress', function(e) {
    console.log('progress - valid file');

    fr.abort();

    type = 'file';
});

// The whole file loads before a progress event happens.
fr.addEventListener('load', function(e) {
    console.log('load - valid file');

    type = 'file';
});

// Not a file.  Possibly a directory.
fr.addEventListener('error', function(e) {
    console.log('error - not a file or is not readable by the web browser');
});

fr.readAsArrayBuffer(thefile);

This fires the error handler when presented with a directory and most files will fire the progress handler after reading just a few KB. I've seen both events fire. Triggering abort() in the progress handler stops the FileReader from reading more data off disk into RAM. That allows for really large files to be dropped without reading all of the data of such files into RAM just to determine that they are files.

It may be tempting to say that if an error happens that the File is a directory. However, a number of scenarios exist where the File is unreadable by the web browser. It is safest to just report the error to the user and ignore the item.

0
Brian Pipa On

One other note is that type is "" for any file that has an unknown extension. Try uploading a file named test.blah and the type will be empty. AND... try dragging and dropping a folder named test.jpg - type will be set to "image/jpeg". To be 100% correct, you can't depend on type solely (or if at all, really).

In my testing, folders have always been of size 0 (on FF and Chrome on 64-bit Windows 7 and under Linux Mint (Ubuntu essentially). So, my folder check is just checking if size is 0 and it seems to work for me in our environment. We also don't want 0-byte files uploaded either so if it's 0 byte the message comes back as "Skipped - 0 bytes (or folder)"

1
georg-un On

An easy method is the following:

  1. Check if the file's type is an empty string: type === ""
  2. Check if the file's size is 0, 4096, or a multiple of it: size % 4096 === 0.
if (file.type === "" && file.size % 4096 === 0) {
    // The file is a folder
} else {
    // The file is not a folder
}

Note: Just by chance, there could be files without a file extension that have the size of some multiple of 4096. Even though this will not happen very often, be aware of it.


For reference, please see the great answer from user Marco Bonelli to a similar topic. This is just a short summary of it.