上传前如何使用 javascript 检查文件 MIME 类型?
我已阅读 this 和 this 似乎表明可以在客户端使用 javascript 检查文件 MIME 类型的问题.现在,我知道真正的验证仍然必须在服务器端完成.我想执行客户端检查以避免不必要的服务器资源浪费.
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.
为了测试这是否可以在客户端完成,我将 JPEG
测试文件的扩展名更改为 .png
并选择要上传的文件.在发送文件之前,我使用 javascript 控制台查询文件对象:
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];
这是我在 Chrome 28.0 上得到的:
This is what I get on Chrome 28.0:
文件 {webkitRelativePath: "", lastModifiedDate: 2012 年 10 月 16 日星期二10:00:00 GMT+0000 (UTC),名称:test.png",类型:image/png",大小:500055…}
File {webkitRelativePath: "", lastModifiedDate: Tue Oct 16 2012 10:00:00 GMT+0000 (UTC), name: "test.png", type: "image/png", size: 500055…}
它显示类型为 image/png
这似乎表明检查是基于文件扩展名而不是 MIME 类型完成的.我尝试了 Firefox 22.0,它给了我相同的结果.但是根据 W3C 规范,MIME 嗅探应该被实现.
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.
我是否正确地说目前无法使用 javascript 检查 MIME 类型?还是我错过了什么?
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?
推荐答案
在将文件上传到服务器之前,您可以使用 JavaScript 的 FileReader
轻松确定文件 MIME 类型.我同意我们应该更喜欢服务器端检查而不是客户端检查,但客户端检查仍然是可能的.我将向您展示如何操作并在底部提供一个工作演示.
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.
检查您的浏览器是否同时支持 File
和 Blob
.所有主要的都应该.
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
}
第 1 步:
您可以像这样从 <input>
元素检索 File
信息(ref):
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>
<小时>
第 2 步:
我们现在可以检查文件并梳理出标题和 MIME 类型.
Step 2:
We can now inspect the files and tease out headers and MIME types.
✘快速方法
您可以天真地向 Blob 询问任何文件的 MIME 类型它表示使用这种模式:
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);
对于图像,MIME 类型返回如下:
For images, MIME types come back like the following:
图像/JPEG
图片/png
...
image/jpeg
image/png
...
警告: MIME 类型是从文件扩展名中检测到的,可能会被欺骗或欺骗.可以将 .jpg
重命名为 .png
,MIME 类型将报告为 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
.
✓正确的标头检查方法
要获得客户端文件的真正 MIME 类型,我们可以更进一步,检查给定文件的前几个字节,以与所谓的 幻数.请注意,这并不完全简单,例如,JPEG 有一些神奇数字".这是因为格式自 1991 年以来一直在发展.您可能只检查前两个字节就可以了,但我更喜欢检查至少 4 个字节以减少误报.
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.
JPEG 的示例文件签名(前 4 个字节):
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)
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);
然后您可以像这样确定真正的 MIME 类型(更多文件签名这里和这里):
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;
}
根据预期的 MIME 类型接受或拒绝文件上传.
Accept or reject file uploads as you like based on the MIME types expected.
这是一个本地文件和远程文件的工作演示(我不得不绕过 CORS 只是为了这个演示).打开代码片段,运行它,您应该会看到显示的三个不同类型的远程图像.在顶部您可以选择本地图像或数据文件,将显示文件签名和/或MIME类型.
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.
请注意,即使图像被重命名,也可以确定其真正的 MIME 类型.见下文.
Notice that even if an image is renamed, its true MIME type can be determined. See below.
截图
// 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/>
相关文章