上传前如何使用 JavaScript 检查文件 MIME 类型?

新手上路,请多包涵

我已经阅读 了这个这个 问题,这似乎表明可以在客户端使用 JavaScript 检查文件 MIME 类型。现在,我知道真正的验证仍然必须在服务器端完成。我想执行客户端检查以避免不必要的服务器资源浪费。

为了测试这是否可以在客户端完成,我将 JPEG 测试文件的扩展名更改为 .png 并选择要上传的文件。在发送文件之前,我使用 JavaScript 控制台查询文件对象:

document.getElementsByTagName('input')[0].files[0];

这是我在 Chrome 28.0 上得到的:

文件 {webkitRelativePath: “”, lastModifiedDate: 2012 年 10 月 16 日星期二

10:00:00 GMT+0000 (UTC),名称:“test.png”,类型:“image/png”,大小:

500055…}

它显示类型为 image/png ,这似乎表明检查是基于文件扩展名而不是 MIME 类型完成的。我尝试了 Firefox 22.0,它给了我相同的结果。但是根据 W3C 规范,应该实现 MIME 嗅探

我是否可以说目前无法使用 JavaScript 检查 MIME 类型?还是我错过了什么?

原文由 Question Overflow 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.8k
2 个回答

在将文件上传到服务器之前,您可以使用 JavaScript 的 FileReader 轻松确定文件 MIME 类型。我同意我们应该更喜欢服务器端检查而不是客户端检查,但客户端检查仍然是可能的。我将向您展示如何操作并在底部提供一个工作演示。


检查您的浏览器是否同时支持 FileBlob 。所有主要的都应该。

 if (window.FileReader && window.Blob) {
 // All the File APIs are supported.
 } else {
 // File and Blob are not supported
 }

步骤1:

您可以像这样 ( ref ) 从 <input> 元素中检索 File 信息:

 <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>

这是上述( 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>


第2步:

我们现在可以检查文件并梳理出标题和 MIME 类型。

✘ 快速方法

您可以使用此模式天真地向 Blob 询问它所代表的任何文件的 MIME 类型:

 var blob = files[i]; // See step 1 above
 console.log(blob.type);

对于图像,MIME 类型返回如下:

图片/JPEG

图片/png

警告: 从文件扩展名中检测到 MIME 类型,可能会被欺骗或欺骗。可以将 .jpg 重命名为 .png 并且 MIME 类型将报告为 image/png


✓ 正确的标头检查方法

要获得客户端文件的真正 MIME 类型,我们可以更进一步,检查给定文件的前几个字节,以与所谓的 幻数 进行比较。请注意,这并不完全简单,因为例如 JPEG 有一些“幻数”。这是因为格式自 1991 年以来已经发展。您可能只检查前两个字节就可以逃脱,但我更喜欢检查至少 4 个字节以减少误报。

JPEG 的示例文件签名(前 4 个字节):

FF D8 FF E0 (SOI + ADD0)

FF D8 FF E1 (SOI + ADD1)

FF D8 FF E2 (SOI + ADD2)

这是检索文件头的基本代码:

 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);

然后,您可以像这样确定真正的 MIME 类型(更多文件签名 herehere ):

 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;
 }

根据预期的 MIME 类型,根据需要接受或拒绝文件上传。


演示

这是本地文件 远程文件的工作演示(为了这个演示,我不得不绕过 CORS)。打开代码片段,运行它,您应该会看到显示的三个不同类型的远程图像。在顶部,您可以选择本地图像 数据文件,并显示文件签名和/或 MIME 类型。

请注意,即使图像被重命名,也可以确定其真正的 MIME 类型。见下文。

截屏

演示的预期输出


 // Return the first few bytes of the file as a 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 = "";
 for (var i = 0; i < arr.length; i++) {
 header += arr[i].toString(16);
 }
 callback(url, header);
 };
 fileReader.readAsArrayBuffer(blob);
 }

 function getRemoteFileHeader(url, callback) {
 var xhr = new XMLHttpRequest();
 // Bypass CORS for this 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 this image to the document body for proof of GET 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 from 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 for FileReader support
 if (window.FileReader && window.Blob) {
 // Load all the remote images from the urls array
 for (var i = 0; i < 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 be at most 2MB");
 return;
 }
 remoteCallback(escape(file.name), file);
 });

 } else {
 // File and Blob are not supported
 $("hr").after( $("<div>").text("It seems your 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 an image to see its file signature.</div>
 </form>
 <hr/>

原文由 Drakes 发布,翻译遵循 CC BY-SA 3.0 许可协议

如其他答案所述,您可以通过检查文件前几个字节中的文件 签名 来检查 MIME 类型。

但是其他答案正在做的是 将整个文件加载到内存 中以检查签名,这是非常浪费的,如果您不小心选择了一个大文件,很容易冻结您的浏览器。

 /**
 * Load the mime type based on the signature of the first bytes of the file
 * @param  {File}   file        A instance of File
 * @param  {Function} callback  Callback with the result
 * @author Victor www.vitim.us
 * @date   2017-03-23
 */
function loadMime(file, callback) {

    //List of known mimes
    var mimes = [
        {
            mime: 'image/jpeg',
            pattern: [0xFF, 0xD8, 0xFF],
            mask: [0xFF, 0xFF, 0xFF],
        },
        {
            mime: 'image/png',
            pattern: [0x89, 0x50, 0x4E, 0x47],
            mask: [0xFF, 0xFF, 0xFF, 0xFF],
        }
        // you can expand this list @see https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
    ];

    function check(bytes, mime) {
        for (var i = 0, l = mime.mask.length; i < l; ++i) {
            if ((bytes[i] & mime.mask[i]) - mime.pattern[i] !== 0) {
                return false;
            }
        }
        return true;
    }

    var blob = file.slice(0, 4); //read the first 4 bytes of the file

    var reader = new FileReader();
    reader.onloadend = function(e) {
        if (e.target.readyState === FileReader.DONE) {
            var bytes = new Uint8Array(e.target.result);

            for (var i=0, l = mimes.length; i<l; ++i) {
                if (check(bytes, mimes[i])) return callback("Mime: " + mimes[i].mime + " <br> Browser:" + file.type);
            }

            return callback("Mime: unknown <br> Browser:" + file.type);
        }
    };
    reader.readAsArrayBuffer(blob);
}

//when selecting a file on the input
fileInput.onchange = function() {
    loadMime(fileInput.files[0], function(mime) {

        //print the output to the screen
        output.innerHTML = mime;
    });
};
 <input type="file" id="fileInput">
<div id="output"></div>

原文由 Vitim.us 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题