上传前如何使用javascript检查文件MIME类型? [英] How to check file MIME type with javascript before upload?

查看:40
本文介绍了上传前如何使用javascript检查文件MIME类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

这是我在 Chrome 28.0 上得到的:

<块引用>

文件 {webkitRelativePath: "", lastModifiedDate: Tue Oct 16 201210:00:00 GMT+0000 (UTC),名称:test.png",类型:image/png",大小:500055…}

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

我说目前无法使用 javascript 检查 MIME 类型是否正确?还是我遗漏了什么?

解决方案

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

<小时>

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

if (window.FileReader && window.Blob) {//支持所有文件 API.} 别的 {//不支持文件和 Blob}

第 1 步:

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

<脚本>var control = document.getElementById("你的文件");control.addEventListener("change", function(event) {//当控件发生变化时,有新文件var 文件 = control.files,for (var i = 0; i 

这是上面的拖放版本(ref):

<div id="your-files"></div><脚本>var target = document.getElementById("你的文件");target.addEventListener(拖拽",函数(事件){event.preventDefault();}, 错误的);target.addEventListener("drop", function(event) {//取消默认动作event.preventDefault();var 文件 = event.dataTransfer.files,for (var i = 0; i 

<小时>

第 2 步:

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

✘快速方法

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

var blob = files[i];//参见上面的步骤 1控制台日志(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];//参见上面的步骤 1var fileReader = new FileReader();fileReader.onloadend = 函数(e){var arr = (new Uint8Array(e.target.result)).subarray(0, 4);var header = "";for(var i = 0; i < arr.length; i++) {标头 += arr[i].toString(16);}控制台日志(标题);//根据已知类型检查文件签名};fileReader.readAsArrayBuffer(blob);

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

开关(标题){案例89504e47":type = "图像/png";休息;案例47494638":类型=图像/gif";休息;案例ffd8ffe0":案例ffd8ffe1":案例ffd8ffe2":案例ffd8ffe3":案例ffd8ffe8":type = "图像/jpeg";休息;默认:类型=未知";//或者你可以使用 blob.type 作为后备休息;}

根据预期的 MIME 类型接受或拒绝文件上传.

<小时>

演示

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

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

截图

<小时>

//将文件的前几个字节作为十六进制字符串返回函数 getBLOBFileHeader(url, blob, callback) {var fileReader = new FileReader();fileReader.onloadend = 函数(e){var arr = (new Uint8Array(e.target.result)).subarray(0, 4);var header = "";for (var i = 0; i < arr.length; i++) {标头 += arr[i].toString(16);}回调(网址,标题);};fileReader.readAsArrayBuffer(blob);}函数 getRemoteFileHeader(url, callback) {var xhr = new XMLHttpRequest();//在这个演示中绕过 CORS - 顽皮,德雷克斯xhr.open('GET', '//cors-anywhere.herokuapp.com/' + url);xhr.responseType = "blob";xhr.onload = 函数(){回调(网址,xhr.response);};xhr.onerror = 函数(){alert('发生网络错误!');};xhr.send();}函数 headerCallback(url, headerString) {printHeaderInfo(url, headerString);}函数 remoteCallback(url, blob) {打印图像(斑点);getBLOBFileHeader(url, blob, headerCallback);}功能打印图像(斑点){//将此图像添加到文档正文以证明 GET 成功var fr = new FileReader();fr.onloadend = 函数(){$("hr").after($("").attr("src", fr.result)).after($("

").text("Blob MIME 类型:" + blob.type));};fr.readAsDataURL(blob);}//添加更多来自 http://en.wikipedia.org/wiki/List_of_file_signatures函数 mimeType(headerString) {开关(标头字符串){案例89504e47":type = "图像/png";休息;案例47494638":类型=图像/gif";休息;案例ffd8ffe0":案例ffd8ffe1":案例ffd8ffe2":type = "图像/jpeg";休息;默认:类型=未知";休息;}返回类型;}函数printHeaderInfo(url,headerString){$("hr").after($("

").text("真实 MIME 类型:" + mimeType(headerString))).after($("

").text("文件头:0x" + headerString)).after($("

").text(url));}/* 演示驱动程序代码 */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"];//检查 FileReader 支持如果(window.FileReader && window.Blob){//从 urls 数组加载所有远程图像for (var i = 0; i < imageURLsArray.length; i++) {getRemoteFileHeader(imageURLsArray[i], remoteCallback);}/* 处理本地文件 */$("input").on('change', function(event) {var file = event.target.files[0];如果(文件大小> = 2 * 1024 * 1024){alert("文件大小不得超过 2MB");返回;}remoteCallback(escape(file.name), file);});} 别的 {//不支持文件和 Blob$("hr").after( $("<div>").text("您的浏览器似乎不支持 FileReader") );}/* 德雷克斯,2015 */

img {最大高度:200px}div {高度:26px;字体:Arial;字体大小:12pt}形式 {高度:40px;}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><表格><输入类型=文件"/><div>选择一个图像以查看其文件签名.</div></表单><hr/>

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?

解决方案

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
}

Step 1:

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>


Step 2:

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.


Demo

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

这篇关于上传前如何使用javascript检查文件MIME类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆