如何检查上传的文件是图片还是其他文件?

38

在我的网站应用程序中,我有一个图片上传模块。 我想检查上传的文件是图像文件还是其他文件。 我在服务器端使用Java。

该图像在Java中被读取为BufferedImage,然后我使用ImageIO.write()将其写入磁盘。

我该如何检查BufferedImage,以确保它是真正的图像而不是其他类型的文件?

任何建议或链接都将不胜感激。


你最好查看文件扩展名,没有一种100%的方法来确定一个文件是否是图片。 - AndersK
4个回答

112
我假设您是在Servlet上下文中运行此代码。如果只需根据文件扩展名检查内容类型是可行的,则使用ServletContext#getMimeType()获取MIME类型(内容类型)。只需检查它是否以image/开头即可。
String fileName = uploadedFile.getFileName();
String mimeType = getServletContext().getMimeType(fileName);
if (mimeType.startsWith("image/")) {
    // It's an image.
} else {
    // It's not an image.
}

默认的MIME类型在相关servlet容器的web.xml文件中定义。例如,在Tomcat中,它位于/conf/web.xml。您可以通过以下方式在您的Web应用程序的/WEB-INF/web.xml中扩展/覆盖它:
<mime-mapping>
    <extension>svg</extension>
    <mime-type>image/svg+xml</mime-type>
</mime-mapping>

但是这并不能防止用户通过更改文件扩展名来欺骗您。如果您也想覆盖此问题,那么您还可以根据实际文件内容确定mime类型。如果检查only BMP、GIF、JPEG、PNG、TIFF或WBMP类型(但不包括PSD、SVG等)的成本可行,则可以直接将其提供给ImageIO#read()并检查它是否会抛出异常。

try (InputStream input = uploadedFile.getInputStream()) {
    try {
        ImageIO.read(input).toString();
        // It's an image (only BMP, GIF, JPEG, PNG, TIFF and WBMP are recognized).
    } catch (Exception e) {
        // It's not an image.
    }
}

但是,如果您想涵盖更多的图像类型,那么考虑使用第三方库,该库通过检测文件签名来完成所有工作。例如Apache Tika,它不仅可以识别ImageIO格式,还可以识别PSD、BPG、WEBP、ICNS和SVG等格式:

Tika tika = new Tika();
try (InputStream input = uploadedFile.getInputStream()) {
    String mimeType = tika.detect(input);
    if (mimeType.startsWith("image/")) {
        // It's an image.
    } else {
        // It's not an image.
    }
}

如果必要的话,您可以使用组合并权衡彼此。

话虽如此,您不一定需要使用ImageIO#write()将上传的图像保存到磁盘。直接将获取的InputStream以Java IO的方式写入Path或任何OutputStream(例如FileOutputStream)即可(另请参见在servlet应用程序中保存上传文件的推荐方法):

try (InputStream input = uploadedFile.getInputStream()) {
    Files.copy(input, new File(uploadFolder, fileName).toPath());
}

除非您想收集一些图像信息,例如其尺寸和/或想要对其进行操作(裁剪/调整大小/旋转/转换等)。


3
如果文件不是图像,ImageIO.read(input); 不会抛出任何异常,而只会返回 null。 - Salih Erikci
1
Salih是正确的。如果ImageIO.read(input) == null,则它不是图像文件。否则,它就是图像文件。 - Kyung Hwan Min
1
@KyungMin:非图像文件确实会返回null(然后toString()将抛出NPE)。不支持的格式,如CMYK JPEG的图像实际上会抛出IOException - BalusC
我有同样的问题,但我需要验证上传的文件是否为xlsx格式,你能帮帮我吗? - Masoud Mustamandi
@Masoud:只需按照相同的逻辑方式操作,不要尝试将其解析为图像文件,而是作为XLSX文件(例如使用Apache POI或您喜欢的任何XLSX解析器)进行解析。 - BalusC
显示剩余4条评论

8

在我的情况下,我使用了org.apache.commons.imaging.Imaging。以下是一个检查图像是否为jpeg图像的示例代码。如果上传的文件不是图像,则会抛出ImageReadException。

    try {
        //image is InputStream
        byte[] byteArray = IOUtils.toByteArray(image);
        ImageFormat mimeType = Imaging.guessFormat(byteArray);
        if (mimeType == ImageFormats.JPEG) {
            return;
        } else {
            // handle image of different format. Ex: PNG
        }
    } catch (ImageReadException e) {
        //not an image
    }

2
截至2017年6月,该库仍没有稳定版本,只有快照。在生产中使用可能存在风险。 - George
我也可以在https://commons.apache.org/proper/commons-imaging/上阅读:<<在成为Apache Commons组件的初始版本之前,Imaging已经在许多生产项目中得到了应用和使用。>>我决定选择它... - JacquesLeRoux

5

这是内置于JDK中的,只需要支持流的支持就可以了。

byte[] data = ;
InputStream is = new BufferedInputStream(new ByteArrayInputStream(data));
String mimeType = URLConnection.guessContentTypeFromStream(is);
//...close stream

自Java SE 6以来,https://docs.oracle.com/javase/6/docs/api/java/net/URLConnection.html已经存在。

2
尝试使用多部分文件,而不是 BufferedImage
import org.apache.http.entity.ContentType;
...

    public void processImage(MultipartFile file) {

       if(!Arrays.asList(ContentType.IMAGE_JPEG.getMimeType(), ContentType.IMAGE_PNG.getMimeType(), ContentType.IMAGE_GIF.getMimeType()).contains(file.getContentType())) {
            throw new IllegalStateException("File must be an Image");
                 }
      }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接