html5 - How to check file MIME type with javascript before upload? -
i have read this , this questions seems suggest file mime type checked using javascript on client side. now, understand real validation still has done on server side. want perform client side checking avoid unnecessary wastage of server resource.
to test whether can done on client side, changed extension of jpeg
test file .png
, choose file upload. before sending file, query file object using javascript console:
document.getelementsbytagname('input')[0].files[0];
this 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 image/png
seems indicate checking done based on file extension instead of mime type. tried firefox 22.0 , gives me same result. according the w3c spec, mime sniffing should implemented.
am right there no way check mime type javascript @ moment? or missing something?
you can determine file mime type javascript's filereader
before uploading server. agree should prefer server-side checking on client-side, client-side checking still possible. i'll show how , provide working demo @ bottom.
check browser supports both file
, blob
. major ones should.
if (window.filereader && window.blob) { // file apis supported. } else { // file , blob not supported }
step 1:
you can retrieve file
information <input>
element (ref):
<input type="file" id="your-files" multiple> <script> var control = document.getelementbyid("your-files"); control.addeventlistener("change", function(event) { // when control has changed, there new files var files = control.files, (var = 0; < 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 drag-and-drop version of 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, (var = 0; < files.length; i++) { console.log("filename: " + files[i].name); console.log("type: " + files[i].type); console.log("size: " + files[i].size + " bytes"); } }, false); </script>
step 2:
we can inspect files , tease out headers , mime types.
✘ quick method
you can naïvely ask blob mime type of whatever file represents using pattern:
var blob = files[i]; // see step 1 above console.log(blob.type);
for images, mime types come following:
image/jpeg
image/png
...
caveat: mime type detected file extension , can fooled or spoofed. 1 can rename .jpg
.png
, mime type be reported image/png
.
✓ proper header-inspecting method
to bonafide mime type of client-side file can go step further , inspect first few bytes of given file compare against so-called magic numbers. warned it's not entirely straightforward because, instance, jpeg has few "magic numbers". because format has evolved since 1991. might away checking first 2 bytes, prefer checking @ least 4 bytes 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 essential code retrieve 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 = 0; < arr.length; i++) { header += arr[i].tostring(16); } console.log(header); // check file signature against known types }; filereader.readasarraybuffer(blob);
you can determine real mime type (more file signatures here , here):
switch (header) { case "89504e47": type = "image/png"; break; case "47494638": type = "image/gif"; break; case "ffd8ffe0": case "ffd8ffe1": case "ffd8ffe2": type = "image/jpeg"; break; default: type = "unknown"; // or can use blob.type fallback break; }
accept or reject file uploads based on mime types expected.
demo
here working demo local files and remote files (i had bypass cors demo). open snippet, run it, , should see 3 remote images of different types displayed. @ top can select local image or data file, , file signature and/or mime type displayed.
notice if image renamed, true mime type can determined. see below.
screenshot
// return first few bytes of file hex string function getblobfileheader(url, blob, callback) { var filereader = new filereader(); filereader.onloadend = function(e) { var arr = (new uint8array(e.target.result)).subarray(0, 4); var header = ""; (var = 0; < arr.length; i++) { header += arr[i].tostring(16); } callback(url, header); }; filereader.readasarraybuffer(blob); } function getremotefileheader(url, callback) { var xhr = new xmlhttprequest(); // bypass cors demo - naughty, drakes xhr.open('get', '//cors-anywhere.herokuapp.com/' + url); xhr.responsetype = "blob"; xhr.onload = function() { callback(url, xhr.response); }; xhr.onerror = function() { alert('a network error occurred!'); }; xhr.send(); } function headercallback(url, headerstring) { printheaderinfo(url, headerstring); } function remotecallback(url, blob) { printimage(blob); getblobfileheader(url, blob, headercallback); } function printimage(blob) { // add image document body proof of success var fr = new filereader(); fr.onloadend = function() { $("hr").after($("<img>").attr("src", fr.result)) .after($("<div>").text("blob mime type: " + blob.type)); }; fr.readasdataurl(blob); } // add more http://en.wikipedia.org/wiki/list_of_file_signatures function mimetype(headerstring) { switch (headerstring) { case "89504e47": type = "image/png"; break; case "47494638": type = "image/gif"; break; case "ffd8ffe0": case "ffd8ffe1": case "ffd8ffe2": type = "image/jpeg"; break; default: type = "unknown"; break; } return type; } function printheaderinfo(url, headerstring) { $("hr").after($("<div>").text("real mime type: " + mimetype(headerstring))) .after($("<div>").text("file header: 0x" + headerstring)) .after($("<div>").text(url)); } /* demo driver code */ 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"]; // check filereader support if (window.filereader && window.blob) { // load remote images urls array (var = 0; < imageurlsarray.length; i++) { getremotefileheader(imageurlsarray[i], remotecallback); } /* handle local files */ $("input").on('change', function(event) { var file = event.target.files[0]; if (file.size >= 2 * 1024 * 1024) { alert("file size must @ 2mb"); return; } remotecallback(escape(file.name), file); }); } else { // file , blob not supported $("hr").after( $("<div>").text("it seems browser doesn't support filereader") ); } /* drakes, 2015 */
img { max-height: 200px } div { height: 26px; font: arial; font-size: 12pt } form { height: 40px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <form> <input type="file" /> <div>choose image see file signature.</div> </form> <hr/>