I have read this and this questions which seems to suggest that the file MIME type could be checked using javascript on client side. Now, I understand that the real validation still has to be done on server side. I want to perform a client side checking to avoid unnecessary wastage of server resource.
To test whether this can be done on client side, I changed the extension of a JPEG
test file to .png
and choose the file for upload. Before sending the file, I query the file object using a javascript console:
document.getElementsByTagName('input')[0].files[0];
This is what I get on Chrome 28.0:
File {webkitRelativePath: "", lastModifiedDate: Tue Oct 16 2012 10:00:00 GMT+0000 (UTC), name: "test.png", type: "image/png", size: 500055…}
It shows type to be image/png
which seems to indicate that the checking is done based on file extension instead of MIME type. I tried Firefox 22.0 and it gives me the same result. But according to the W3C spec, MIME Sniffing should be implemented.
Am I right to say that there is no way to check the MIME type with javascript at the moment? Or am I missing something?
This question is related to
javascript
html
file-upload
mime-types
As Drake states this could be done with FileReader. However, what I present here is a functional version. Take in consideration that the big problem with doing this with JavaScript is to reset the input file. Well, this restricts to only JPG (for other formats you will have to change the mime type and the magic number):
<form id="form-id">
<input type="file" id="input-id" accept="image/jpeg"/>
</form>
<script type="text/javascript">
$(function(){
$("#input-id").on('change', function(event) {
var file = event.target.files[0];
if(file.size>=2*1024*1024) {
alert("JPG images of maximum 2MB");
$("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
return;
}
if(!file.type.match('image/jp.*')) {
alert("only JPG images");
$("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
return;
}
var fileReader = new FileReader();
fileReader.onload = function(e) {
var int32View = new Uint8Array(e.target.result);
//verify the magic number
// for JPG is 0xFF 0xD8 0xFF 0xE0 (see https://en.wikipedia.org/wiki/List_of_file_signatures)
if(int32View.length>4 && int32View[0]==0xFF && int32View[1]==0xD8 && int32View[2]==0xFF && int32View[3]==0xE0) {
alert("ok!");
} else {
alert("only valid JPG images");
$("#form-id").get(0).reset(); //the tricky part is to "empty" the input file here I reset the form.
return;
}
};
fileReader.readAsArrayBuffer(file);
});
});
</script>
Take in consideration that this was tested on latest versions of Firefox and Chrome, and on IExplore 10.
Here is an extension of Roberto14's answer that does the following:
THIS WILL ONLY ALLOW IMAGES
Checks if FileReader is available and falls back to extension checking if it is not available.
Gives an error alert if not an image
If it is an image it loads a preview
** You should still do server side validation, this is more a convenience for the end user than anything else. But it is handy!
<form id="myform">
<input type="file" id="myimage" onchange="readURL(this)" />
<img id="preview" src="#" alt="Image Preview" />
</form>
<script>
function readURL(input) {
if (window.FileReader && window.Blob) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
var img = new Image();
img.onload = function() {
var preview = document.getElementById('preview');
preview.src = e.target.result;
};
img.onerror = function() {
alert('error');
input.value = '';
};
img.src = e.target.result;
}
reader.readAsDataURL(input.files[0]);
}
}
else {
var ext = input.value.split('.');
ext = ext[ext.length-1].toLowerCase();
var arrayExtensions = ['jpg' , 'jpeg', 'png', 'bmp', 'gif'];
if (arrayExtensions.lastIndexOf(ext) == -1) {
alert('error');
input.value = '';
}
else {
var preview = document.getElementById('preview');
preview.setAttribute('alt', 'Browser does not support preview.');
}
}
}
</script>
This is what you have to do
var fileVariable =document.getElementsById('fileId').files[0];
If you want to check for image file types then
if(fileVariable.type.match('image.*'))
{
alert('its an image');
}
As stated in other answers, you can check the mime type by checking the signature of the file in the first bytes of the file.
But what other answers are doing is loading the entire file in memory in order to check the signature, which is very wasteful and could easily freeze your browser if you select a big file by accident or not.
/**_x000D_
* Load the mime type based on the signature of the first bytes of the file_x000D_
* @param {File} file A instance of File_x000D_
* @param {Function} callback Callback with the result_x000D_
* @author Victor www.vitim.us_x000D_
* @date 2017-03-23_x000D_
*/_x000D_
function loadMime(file, callback) {_x000D_
_x000D_
//List of known mimes_x000D_
var mimes = [_x000D_
{_x000D_
mime: 'image/jpeg',_x000D_
pattern: [0xFF, 0xD8, 0xFF],_x000D_
mask: [0xFF, 0xFF, 0xFF],_x000D_
},_x000D_
{_x000D_
mime: 'image/png',_x000D_
pattern: [0x89, 0x50, 0x4E, 0x47],_x000D_
mask: [0xFF, 0xFF, 0xFF, 0xFF],_x000D_
}_x000D_
// you can expand this list @see https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern_x000D_
];_x000D_
_x000D_
function check(bytes, mime) {_x000D_
for (var i = 0, l = mime.mask.length; i < l; ++i) {_x000D_
if ((bytes[i] & mime.mask[i]) - mime.pattern[i] !== 0) {_x000D_
return false;_x000D_
}_x000D_
}_x000D_
return true;_x000D_
}_x000D_
_x000D_
var blob = file.slice(0, 4); //read the first 4 bytes of the file_x000D_
_x000D_
var reader = new FileReader();_x000D_
reader.onloadend = function(e) {_x000D_
if (e.target.readyState === FileReader.DONE) {_x000D_
var bytes = new Uint8Array(e.target.result);_x000D_
_x000D_
for (var i=0, l = mimes.length; i<l; ++i) {_x000D_
if (check(bytes, mimes[i])) return callback("Mime: " + mimes[i].mime + " <br> Browser:" + file.type);_x000D_
}_x000D_
_x000D_
return callback("Mime: unknown <br> Browser:" + file.type);_x000D_
}_x000D_
};_x000D_
reader.readAsArrayBuffer(blob);_x000D_
}_x000D_
_x000D_
_x000D_
//when selecting a file on the input_x000D_
fileInput.onchange = function() {_x000D_
loadMime(fileInput.files[0], function(mime) {_x000D_
_x000D_
//print the output to the screen_x000D_
output.innerHTML = mime;_x000D_
});_x000D_
};
_x000D_
<input type="file" id="fileInput">_x000D_
<div id="output"></div>
_x000D_
If you just want to check if the file uploaded is an image you can just try to load it into <img>
tag an check for any error callback.
Example:
var input = document.getElementsByTagName('input')[0];
var reader = new FileReader();
reader.onload = function (e) {
imageExists(e.target.result, function(exists){
if (exists) {
// Do something with the image file..
} else {
// different file format
}
});
};
reader.readAsDataURL(input.files[0]);
function imageExists(url, callback) {
var img = new Image();
img.onload = function() { callback(true); };
img.onerror = function() { callback(false); };
img.src = url;
}
For anyone who's looking to not implement this themselves, Sindresorhus has create a utility that works in the browser and has the header-to-mime mappings for most documents you could want.
https://github.com/sindresorhus/file-type
You could combine Vitim.us's suggestion of only reading in the first X bytes to avoid loading everything into memory with using this utility (example in es6):
import fileType from 'file-type'; // or wherever you load the dependency
const blob = file.slice(0, fileType.minimumBytes);
const reader = new FileReader();
reader.onloadend = function(e) {
if (e.target.readyState !== FileReader.DONE) {
return;
}
const bytes = new Uint8Array(e.target.result);
const { ext, mime } = fileType.fromBuffer(bytes);
// ext is the desired extension and mime is the mimetype
};
reader.readAsArrayBuffer(blob);
Here is a Typescript implementation that supports webp. This is based on the JavaScript answer by Vitim.us.
interface Mime {
mime: string;
pattern: (number | undefined)[];
}
// tslint:disable number-literal-format
// tslint:disable no-magic-numbers
const imageMimes: Mime[] = [
{
mime: 'image/png',
pattern: [0x89, 0x50, 0x4e, 0x47]
},
{
mime: 'image/jpeg',
pattern: [0xff, 0xd8, 0xff]
},
{
mime: 'image/gif',
pattern: [0x47, 0x49, 0x46, 0x38]
},
{
mime: 'image/webp',
pattern: [0x52, 0x49, 0x46, 0x46, undefined, undefined, undefined, undefined, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50],
}
// You can expand this list @see https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
];
// tslint:enable no-magic-numbers
// tslint:enable number-literal-format
function isMime(bytes: Uint8Array, mime: Mime): boolean {
return mime.pattern.every((p, i) => !p || bytes[i] === p);
}
function validateImageMimeType(file: File, callback: (b: boolean) => void) {
const numBytesNeeded = Math.max(...imageMimes.map(m => m.pattern.length));
const blob = file.slice(0, numBytesNeeded); // Read the needed bytes of the file
const fileReader = new FileReader();
fileReader.onloadend = e => {
if (!e || !fileReader.result) return;
const bytes = new Uint8Array(fileReader.result as ArrayBuffer);
const valid = imageMimes.some(mime => isMime(bytes, mime));
callback(valid);
};
fileReader.readAsArrayBuffer(blob);
}
// When selecting a file on the input
fileInput.onchange = () => {
const file = fileInput.files && fileInput.files[0];
if (!file) return;
validateImageMimeType(file, valid => {
if (!valid) {
alert('Not a valid image file.');
}
});
};
<input type="file" id="fileInput">
_x000D_
Short answer is no.
As you note the browsers derive type
from the file extension. Mac preview also seems to run off the extension. I'm assuming its because its faster reading the file name contained in the pointer, rather than looking up and reading the file on disk.
I made a copy of a jpg renamed with png.
I was able to consistently get the following from both images in chrome (should work in modern browsers).
ÿØÿàJFIFÿþ;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90
Which you could hack out a String.indexOf('jpeg') check for image type.
Here is a fiddle to explore http://jsfiddle.net/bamboo/jkZ2v/1/
The ambigious line I forgot to comment in the example
console.log( /^(.*)$/m.exec(window.atob( image.src.split(',')[1] )) );
The fiddle code uses base64 decode which wont work in IE9, I did find a nice example using VB script that works in IE http://blog.nihilogic.dk/2008/08/imageinfo-reading-image-metadata-with.html
The code to load the image was taken from Joel Vardy, who is doing some cool image canvas resizing client side before uploading which may be of interest https://joelvardy.com/writing/javascript-image-upload
Source: Stackoverflow.com