Html 上传前如何使用javascript检查文件MIME类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18299806/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to check file MIME type with javascript before upload?
提问by Question Overflow
I have read thisand thisquestions 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.
我已经阅读了这个和这个问题,这似乎表明可以在客户端使用 javascript 检查文件 MIME 类型。现在,我明白真正的验证仍然必须在服务器端完成。我想执行客户端检查以避免不必要的服务器资源浪费。
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:
为了测试这是否可以在客户端完成,我将JPEG
测试文件的扩展名更改为.png
并选择要上传的文件。在发送文件之前,我使用 javascript 控制台查询文件对象:
document.getElementsByTagName('input')[0].files[0];
This is what I get on Chrome 28.0:
这是我在 Chrome 28.0 上得到的:
File {webkitRelativePath: "", lastModifiedDate: Tue Oct 16 2012 10:00:00 GMT+0000 (UTC), name: "test.png", type: "image/png", size: 500055…}
文件 {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 Sniffingshould be implemented.
它显示的类型image/png
似乎表明检查是基于文件扩展名而不是 MIME 类型完成的。我尝试了 Firefox 22.0,它给了我相同的结果。但是根据W3C 规范,应该实现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 检查 MIME 类型是否正确?或者我错过了什么?
回答by Drakes
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.
在将文件FileReader
上传到服务器之前,您可以使用 JavaScript 轻松确定文件 MIME 类型。我同意我们应该更喜欢服务器端检查而不是客户端检查,但客户端检查仍然是可能的。我将向您展示如何操作并在底部提供一个工作演示。
Check that your browser supports both File
and Blob
. All major ones should.
检查您的浏览器是否同时支持File
和Blob
。所有主要的都应该。
if (window.FileReader && window.Blob) {
// All the File APIs are supported.
} else {
// File and Blob are not supported
}
Step 1:
第1步:
You can retrieve the File
information from an <input>
element like this (ref):
您可以File
从这样的<input>
元素中检索信息( 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):
这是上述(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:
第2步:
We can now inspect the files and tease out headers and MIME types.
我们现在可以检查文件并梳理出标题和 MIME 类型。
✘ Quick method
✘ 快速方法
You can na?vely ask Blobfor the MIME type of whatever file it represents using this pattern:
您可以天真地向Blob询问它使用以下模式表示的任何文件的 MIME 类型:
var blob = files[i]; // See step 1 above
console.log(blob.type);
For images, MIME types come back like the following:
对于图像,MIME 类型返回如下:
image/jpeg
image/png
...
图像/jpeg
图像/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 类型是从文件扩展名中检测到的,可能会被欺骗或欺骗。可以将 a 重命名.jpg
为 a.png
并且 MIME 类型将报告为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, JPEGhas 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.
为了获得客户端文件的真实 MIME 类型,我们可以更进一步,检查给定文件的前几个字节,以与所谓的幻数进行比较。请注意,这并不完全简单,因为例如,JPEG有一些“神奇数字”。这是因为格式自 1991 年以来一直在发展。您可能只检查前两个字节,但我更喜欢检查至少 4 个字节以减少误报。
Example file signatures of JPEG (first 4 bytes):
JPEG 的示例文件签名(前 4 个字节):
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);
You can then determine the real MIME type like so (more file signatures hereand here):
然后您可以像这样确定真正的 MIME 类型(更多文件签名在这里和这里):
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.
根据预期的 MIME 类型,根据需要接受或拒绝文件上传。
Demo
演示
Here is a working demo for local files andremote 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 ordata file, and the file signature and/or MIME type will be displayed.
这是本地文件和远程文件的工作演示(为了这个演示,我不得不绕过 CORS)。打开代码片段,运行它,您应该会看到显示了三个不同类型的远程图像。您可以在顶部选择本地图像或数据文件,然后将显示文件签名和/或 MIME 类型。
Notice that even if an image is renamed, its true MIME type can be determined. See below.
请注意,即使图像重命名,也可以确定其真正的 MIME 类型。见下文。
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/>
回答by Vitim.us
As stated in other answers, you can check the mime type by checking the signatureof the file in the first bytes of the file.
如其他答案所述,您可以通过检查文件首字节中的文件签名来检查 MIME 类型。
But what other answers are doing is loading the entire file in memoryin 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.
但是其他答案正在做的是将整个文件加载到内存中以检查签名,这是非常浪费的,如果您不小心选择了一个大文件,很容易冻结您的浏览器。
/**
* 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>
回答by Vinay
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.
对于不希望自己实现此功能的任何人,Sindresorhus 创建了一个可在浏览器中运行的实用程序,并为您可能需要的大多数文档提供标头到 MIME 的映射。
https://github.com/sindresorhus/file-type
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):
您可以结合 Vitim.us 的建议,即只读取前 X 个字节以避免使用此实用程序将所有内容加载到内存中(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(bytes);
// ext is the desired extension and mime is the mimetype
};
reader.readAsArrayBuffer(blob);
回答by Roberto14
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.
如果您只想检查上传的文件是否为图像,您可以尝试将其加载到<img>
标签中以检查是否有任何错误回调。
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;
}
回答by Kailas
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');
}
回答by Eric Coulthard
Here is a Typescript implementation that supports webp. This is based on the JavaScript answer by Vitim.us.
这是一个支持 webp 的 Typescript 实现。这是基于 Vitim.us 的 JavaScript 回答。
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">
回答by lmiguelmh
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 typeand the magic number):
正如 Drake 所说,这可以通过 FileReader 来完成。然而,我在这里展示的是一个功能版本。考虑到使用 JavaScript 执行此操作的大问题是重置输入文件。好吧,这仅限于 JPG(对于其他格式,您必须更改mime 类型和幻数):
<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.
考虑到这是在最新版本的 Firefox 和 Chrome 以及 IExplore 10 上测试过的。
For a complete list of mime types see Wikipedia.
有关 mime 类型的完整列表,请参阅 Wikipedia。
回答by pathfinder
Here is an extension of Roberto14's answer that does the following:
这是 Roberto14 答案的扩展,它执行以下操作:
THIS WILL ONLY ALLOW IMAGES
这将只允许图像
Checks if FileReader is available and falls back to extension checking if it is not available.
检查 FileReader 是否可用,如果不可用则回退到扩展检查。
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>
回答by Lex
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.
正如您所注意到的,浏览器源自type
文件扩展名。Mac 预览似乎也运行了扩展程序。我假设它是因为它更快地读取包含在指针中的文件名,而不是在磁盘上查找和读取文件。
I made a copy of a jpg renamed with png.
我复制了一个用 png 重命名的 jpg。
I was able to consistently get the following from both images in chrome (should work in modern browsers).
我能够始终如一地从 chrome 中的两个图像中获得以下内容(应该在现代浏览器中工作)。
???àJFIF?t;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90
???àJFIF?t;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90
Which you could hack out a String.indexOf('jpeg') check for image type.
你可以破解一个 String.indexOf('jpeg') 检查图像类型。
Here is a fiddle to explore http://jsfiddle.net/bamboo/jkZ2v/1/
这是一个探索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] )) );
console.log( /^(.*)$/m.exec(window.atob( image.src.split(',')[1] )) );
- Splits the base64 encoded img data, leaving on the image
- Base64 decodes the image
- Matches only the first line of the image data
- 拆分base64编码的img数据,留在图像上
- Base64 解码图像
- 只匹配图像数据的第一行
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
小提琴代码使用了在 IE9 中不起作用的 base64 解码,我确实找到了一个使用 VB 脚本的好例子,它可以在 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
加载图像的代码来自 Joel Vardy,他在上传之前正在做一些很酷的图像画布调整客户端大小,这可能会引起人们的兴趣https://joelvardy.com/writing/javascript-image-upload