You can easily determine the file MIME type with JavaScript's FileReader
before uploading it to a server. I agree that we should prefer server-side checking over client-side, but client-side checking is still possible. I'll show you how and provide a working demo at the bottom.
Check that your browser supports both File
and Blob
. All major ones should.
if (window.FileReader && window.Blob) {
// All the File APIs are supported.
} else {
// File and Blob are not supported
}
You can retrieve the File
information from an <input>
element like this (ref):
<input type="file" id="your-files" multiple>
<script>
var control = document.getElementById("your-files");
control.addEventListener("change", function(event) {
// When the control has changed, there are new files
var files = control.files,
for (var i = 0; i < files.length; i++) {
console.log("Filename: " + files[i].name);
console.log("Type: " + files[i].type);
console.log("Size: " + files[i].size + " bytes");
}
}, false);
</script>
Here is a drag-and-drop version of the above (ref):
<div id="your-files"></div>
<script>
var target = document.getElementById("your-files");
target.addEventListener("dragover", function(event) {
event.preventDefault();
}, false);
target.addEventListener("drop", function(event) {
// Cancel default actions
event.preventDefault();
var files = event.dataTransfer.files,
for (var i = 0; i < files.length; i++) {
console.log("Filename: " + files[i].name);
console.log("Type: " + files[i].type);
console.log("Size: " + files[i].size + " bytes");
}
}, false);
</script>
We can now inspect the files and tease out headers and MIME types.
✘ Quick method
You can naïvely ask Blob for the MIME type of whatever file it represents using this pattern:
var blob = files[i]; // See step 1 above
console.log(blob.type);
For images, MIME types come back like the following:
image/jpeg
image/png
...
Caveat: The MIME type is detected from the file extension and can be fooled or spoofed. One can rename a .jpg
to a .png
and the MIME type will be be reported as image/png
.
✓ Proper header-inspecting method
To get the bonafide MIME type of a client-side file we can go a step further and inspect the first few bytes of the given file to compare against so-called magic numbers. Be warned that it's not entirely straightforward because, for instance, JPEG has a few "magic numbers". This is because the format has evolved since 1991. You might get away with checking only the first two bytes, but I prefer checking at least 4 bytes to reduce false positives.
Example file signatures of JPEG (first 4 bytes):
FF D8 FF E0 (SOI + ADD0)
FF D8 FF E1 (SOI + ADD1)
FF D8 FF E2 (SOI + ADD2)
Here is the essential code to retrieve the file header:
var blob = files[i]; // See step 1 above
var fileReader = new FileReader();
fileReader.onloadend = function(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var header = "";
for(var i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
console.log(header);
// Check the file signature against known types
};
fileReader.readAsArrayBuffer(blob);
You can then determine the real MIME type like so (more file signatures here and here):
switch (header) {
case "89504e47":
type = "image/png";
break;
case "47494638":
type = "image/gif";
break;
case "ffd8ffe0":
case "ffd8ffe1":
case "ffd8ffe2":
case "ffd8ffe3":
case "ffd8ffe8":
type = "image/jpeg";
break;
default:
type = "unknown"; // Or you can use the blob.type as fallback
break;
}
Accept or reject file uploads as you like based on the MIME types expected.
Here is a working demo for local files and remote files (I had to bypass CORS just for this demo). Open the snippet, run it, and you should see three remote images of different types displayed. At the top you can select a local image or data file, and the file signature and/or MIME type will be displayed.
Notice that even if an image is renamed, its true MIME type can be determined. See below.
Screenshot
// Return the first few bytes of the file as a hex string_x000D_
function getBLOBFileHeader(url, blob, callback) {_x000D_
var fileReader = new FileReader();_x000D_
fileReader.onloadend = function(e) {_x000D_
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);_x000D_
var header = "";_x000D_
for (var i = 0; i < arr.length; i++) {_x000D_
header += arr[i].toString(16);_x000D_
}_x000D_
callback(url, header);_x000D_
};_x000D_
fileReader.readAsArrayBuffer(blob);_x000D_
}_x000D_
_x000D_
function getRemoteFileHeader(url, callback) {_x000D_
var xhr = new XMLHttpRequest();_x000D_
// Bypass CORS for this demo - naughty, Drakes_x000D_
xhr.open('GET', '//cors-anywhere.herokuapp.com/' + url);_x000D_
xhr.responseType = "blob";_x000D_
xhr.onload = function() {_x000D_
callback(url, xhr.response);_x000D_
};_x000D_
xhr.onerror = function() {_x000D_
alert('A network error occurred!');_x000D_
};_x000D_
xhr.send();_x000D_
}_x000D_
_x000D_
function headerCallback(url, headerString) {_x000D_
printHeaderInfo(url, headerString);_x000D_
}_x000D_
_x000D_
function remoteCallback(url, blob) {_x000D_
printImage(blob);_x000D_
getBLOBFileHeader(url, blob, headerCallback);_x000D_
}_x000D_
_x000D_
function printImage(blob) {_x000D_
// Add this image to the document body for proof of GET success_x000D_
var fr = new FileReader();_x000D_
fr.onloadend = function() {_x000D_
$("hr").after($("<img>").attr("src", fr.result))_x000D_
.after($("<div>").text("Blob MIME type: " + blob.type));_x000D_
};_x000D_
fr.readAsDataURL(blob);_x000D_
}_x000D_
_x000D_
// Add more from http://en.wikipedia.org/wiki/List_of_file_signatures_x000D_
function mimeType(headerString) {_x000D_
switch (headerString) {_x000D_
case "89504e47":_x000D_
type = "image/png";_x000D_
break;_x000D_
case "47494638":_x000D_
type = "image/gif";_x000D_
break;_x000D_
case "ffd8ffe0":_x000D_
case "ffd8ffe1":_x000D_
case "ffd8ffe2":_x000D_
type = "image/jpeg";_x000D_
break;_x000D_
default:_x000D_
type = "unknown";_x000D_
break;_x000D_
}_x000D_
return type;_x000D_
}_x000D_
_x000D_
function printHeaderInfo(url, headerString) {_x000D_
$("hr").after($("<div>").text("Real MIME type: " + mimeType(headerString)))_x000D_
.after($("<div>").text("File header: 0x" + headerString))_x000D_
.after($("<div>").text(url));_x000D_
}_x000D_
_x000D_
/* Demo driver code */_x000D_
_x000D_
var imageURLsArray = ["http://media2.giphy.com/media/8KrhxtEsrdhD2/giphy.gif", "http://upload.wikimedia.org/wikipedia/commons/e/e9/Felis_silvestris_silvestris_small_gradual_decrease_of_quality.png", "http://static.giantbomb.com/uploads/scale_small/0/316/520157-apple_logo_dec07.jpg"];_x000D_
_x000D_
// Check for FileReader support_x000D_
if (window.FileReader && window.Blob) {_x000D_
// Load all the remote images from the urls array_x000D_
for (var i = 0; i < imageURLsArray.length; i++) {_x000D_
getRemoteFileHeader(imageURLsArray[i], remoteCallback);_x000D_
}_x000D_
_x000D_
/* Handle local files */_x000D_
$("input").on('change', function(event) {_x000D_
var file = event.target.files[0];_x000D_
if (file.size >= 2 * 1024 * 1024) {_x000D_
alert("File size must be at most 2MB");_x000D_
return;_x000D_
}_x000D_
remoteCallback(escape(file.name), file);_x000D_
});_x000D_
_x000D_
} else {_x000D_
// File and Blob are not supported_x000D_
$("hr").after( $("<div>").text("It seems your browser doesn't support FileReader") );_x000D_
} /* Drakes, 2015 */
_x000D_
img {_x000D_
max-height: 200px_x000D_
}_x000D_
div {_x000D_
height: 26px;_x000D_
font: Arial;_x000D_
font-size: 12pt_x000D_
}_x000D_
form {_x000D_
height: 40px;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>_x000D_
<form>_x000D_
<input type="file" />_x000D_
<div>Choose an image to see its file signature.</div>_x000D_
</form>_x000D_
<hr/>
_x000D_