【图片服务】校验上传图片真实格式、伪后缀校验、文件魔术校验

工具类

import com.google.common.collect.Lists;

import java.io.*;
import java.util.Arrays;
import java.util.List;

public class FileUtil {


    /**
     * 删除
     *
     * @param dir
     */
    public static void deleteAll(File dir) {

        if (dir.isFile()) {
            ApiLogger.info(String.format("delete file , %s  , result:%s", dir, dir.delete()));
            return;

        } else {
            File[] files = dir.listFiles();
            for (File file : files) {

                deleteAll(file);
            }
        }
        ApiLogger.info(String.format("delete dir , %s  , result:%s", dir, dir.delete()));
    }


    /**
     * 将byte数组写入文件
     *
     * @param path
     * @param content
     * @throws IOException
     */
    public static void writeFile(String path, byte[] content) throws IOException {
        try {
            File f = new File(path);
            if (!f.exists()) {
                f.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(path);
            fos.write(content);
            fos.close();
        } catch (IOException e) {
            ApiLogger.error(String.format("when hls upload  , writeFile , the error occured:%s", e.getMessage()), e);
        }
    }


    /**
     * 写入到文件
     *
     * @param file
     */
    public static void writeToFile(File file, String content) throws Exception {
        Writer out = null;
        try {
            out = new FileWriter(file);
            out.write(content);
        } catch (IOException e) {
            ApiLogger.error(String.format("when hls upload  , write to file , the error occured:%s", e.getMessage()), e);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }


    /**
     * 将文本文件中的内容读入到buffer中
     *
     * @param buffer   buffer
     * @param filePath 文件路径
     * @throws IOException 异常
     * @author cn.outofmemory
     * @date 2013-1-7
     */
    public static void readToBuffer(StringBuffer buffer, String filePath) throws IOException {
        InputStream is = new FileInputStream(filePath);
        String line; // 用来保存每行读取的内容
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        line = reader.readLine(); // 读取第一行
        while (line != null) { // 如果 line 为空说明读完了
            buffer.append(line); // 将读到的内容添加到 buffer 中
            buffer.append("\n"); // 添加换行符
            line = reader.readLine(); // 读取下一行
        }
        reader.close();
        is.close();
    }

    /**
     * 读取文本文件内容
     *
     * @param filePath 文件所在路径
     * @return 文本内容
     * @throws IOException 异常
     * @author cn.outofmemory
     * @date 2013-1-7
     */
    public static String readFile(String filePath) throws IOException {
        StringBuffer sb = new StringBuffer();
        FileUtil.readToBuffer(sb, filePath);
        return sb.toString();
    }

    /**
     * 读文件
     */
    public static byte[] readFileFromDiskCache(File file) {
        byte[] buffer = null;

        try {
            buffer = new byte[(int) (file.length())];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            bis.read(buffer);
            bis.close();

        } catch (Exception e) {
            ApiLogger.error("readFileFromDiskCache error, file = " + file.getName(), e);
        } finally {
        }

        return buffer;
    }


    /**
     * 读文件
     */
    public static byte[] readFileFromDiskCache(String filename) {
        byte[] buffer = null;

        try {
            File file = new File(filename);

            if (!file.exists())
                return null;

            buffer = new byte[(int) (file.length())];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            bis.read(buffer);
            bis.close();

        } catch (Exception e) {
            ApiLogger.error("readFileFromDiskCache error, file = " + filename, e);
        } finally {
        }

        return buffer;
    }

    /**
     * byte数组转换成16进制字符串
     *
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 根据文件流判断图片类型
     *
     * @return jpg/png/gif/bmp
     */
    public static String getPicType(byte imageByte[]) {
        // 读取文件的前几个字节来判断图片格式
        byte[] b = new byte[4];
        try {
            System.arraycopy(imageByte, 0, b, 0, 4);
            String type = bytesToHexString(b).toUpperCase();
            if (type.contains("FFD8FF")) {
                return FileTypeEnums.JPG.name();
            } else if (type.contains("89504E47")) {
                return FileTypeEnums.PNG.name();
            } else if (type.contains("47494638")) {
                return FileTypeEnums.GIF.name();
            } else if (type.contains("424D")) {
                return FileTypeEnums.BMP.name();
            } else {
                return FileTypeEnums.UNKNOWN.name();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    public static FileTypeInfo getFileType(byte[] bytes) {
        return getFileType(bytes, 8);
    }

    public static FileTypeInfo getFileType(byte[] bytes, int length) {
        // 读取文件的前几个字节来判断图片格式
        byte[] b = new byte[length];
        try {
            System.arraycopy(bytes, 0, b, 0, length);
            String hex = bytesToHexString(b).toUpperCase();
            for (FileTypeEnums typeEnum : FileTypeEnums.values()) {
                if (hex.contains(typeEnum.getHex())) {
                    return new FileTypeInfo(typeEnum, hex);
                }
            }
        } catch (Exception e) {
            ApiLogger.error("[getFileType] error=", e);
        }
        return new FileTypeInfo(FileTypeEnums.UNKNOWN, "");
    }

    public static boolean isAllowedImage(FileTypeEnums enums) {
        return ALLOW_IMAGE.contains(enums);
    }

    public static boolean isAllowedImage(byte[] bytes) {
        return assertAllowed(bytes, ALLOW_IMAGE);
    }

    public static boolean assertAllowed(byte[] bytes, FileTypeEnums... allowedExtension) {
        return assertAllowed(bytes, Arrays.asList(allowedExtension));
    }

    /**
     * 文件上传校验
     *
     * @param bytes            上传的文件二进制流
     * @param allowedExtension 允许上传的文件后缀集合
     */
    public static boolean assertAllowed(byte[] bytes, List<FileTypeEnums> allowedExtension) {
        // 读取文件的前几个字节来判断图片格式
        byte[] b = new byte[8];
        try {
            System.arraycopy(bytes, 0, b, 0, 8);
            String type = bytesToHexString(b).toUpperCase();
            ApiLogger.info("[assertAllowed] type=" + type);
            for (FileTypeEnums typeEnum : allowedExtension) {
                if (type.contains(typeEnum.getHex())) {
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            ApiLogger.error("[assertAllowed] error=", e);
        }
        return false;
    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
    public static boolean isAllowedExtension(String extension, String[] allowedExtension) {
        for (String str : allowedExtension) {
            if (str.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {

        byte image1[] = readFileFromDiskCache("/Users/marion/data/image/1.jpg");
        byte image2[] = readFileFromDiskCache("/Users/marion/data/image/2.jpeg");
        byte image3[] = readFileFromDiskCache("/Users/marion/data/image/3.png");
        byte image4[] = readFileFromDiskCache("/Users/marion/data/image/4.webp");
        byte image5[] = readFileFromDiskCache("/Users/marion/data/image/5.heif");
        byte image6[] = readFileFromDiskCache("/Users/marion/data/image/6.bmp");
        byte image7[] = readFileFromDiskCache("/Users/marion/data/image/7.webp");

        String type1 = getPicType(image1);
        String type2 = getPicType(image2);
        String type3 = getPicType(image3);
        String type4 = getPicType(image4);
        String type5 = getPicType(image5);
        String type6 = getPicType(image6);
        String type7 = getPicType(image7);

        System.out.println("type1=" + type1); // jpg
        System.out.println("type2=" + type2); // jpg
        System.out.println("type3=" + type3); // png
        System.out.println("type4=" + type4); // 52494646D8410000
        System.out.println("type5=" + type5); // 0000001866747970
        System.out.println("type6=" + type6); // bmp
        System.out.println("type7=" + type7); // bmp
    }

    public static final List<FileTypeEnums> ALLOW_IMAGE = Arrays.asList(FileTypeEnums.JPG,
            FileTypeEnums.PNG,
            FileTypeEnums.GIF,
            FileTypeEnums.BMP,
            FileTypeEnums.WEBP
    );

    public enum FileTypeEnums {
        /**
         * hex: 二进制文件头
         */
        JPG("FFD8FFE0", "jpg"),
        PNG("89504E47", "png"),
        GIF("47494638", "gif"),
        WEBP("52494646", "webp"),
        TIF("49492A00", "tif"),
        BMP("424D", "bmp"),
        PSD("38425053", "psd"),
        XML("3C3F786D6C", "xml"),
        HTML("68746D6C3E", "html"),
        DOC("D0CF11E0", "doc"),
        MDB("5374616E64617264204A", "mdb"),
        PDF("255044462D312E", "pdf"),
        DOCX("504B0304", "docx"),
        RAR("52617221", "rar"),
        AVI("41564920", "avi"),
        HEIF("66747970", "heif"),
        UNKNOWN("", "");

        String hex;
        String suffix;

        FileTypeEnums(String hex, String suffix) {
            this.hex = hex;
            this.suffix = suffix;
        }

        public String getHex() {
            return hex;
        }

        public String getSuffix() {
            return suffix;
        }
    }

    public static class FileTypeInfo {

        private FileTypeEnums type;
        private String fileHex;

        public FileTypeInfo() {
        }

        public FileTypeInfo(FileTypeEnums type, String fileHex) {
            this.type = type;
            this.fileHex = fileHex;
        }

        public FileTypeEnums getType() {
            return type;
        }

        public void setType(FileTypeEnums type) {
            this.type = type;
        }

        public String getFileHex() {
            return fileHex;
        }

        public void setFileHex(String fileHex) {
            this.fileHex = fileHex;
        }

    }

}

参考资料

大聪明教你学Java | 比校验文件后缀名更靠谱的上传文件校验方式 —— 文件魔数校验_不肯过江东丶的博客-CSDN博客_魔数校验

HEIF图片格式详解 - 知乎

常用文件的二进制头信息

Convertio — 文件转换器

16进制转换,16进制转换文本字符串,在线16进制转换 | 在线工具

Java中16进制与字符串之间的相互转换 - 编程世界里晃荡 - 博客园

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用,可以看出MultipartFile的文件格式校验主要包括以下几个步骤: 1. 校验文件是否为空,如果为空,则抛出异常提示上传文件不能为空。 2. 如果只有一个文件,直接进行文件校验。 3. 如果有多个文件,对每个文件都进行文件校验。 根据引用,文件格式校验不仅仅是根据文件后缀来进行判断,还包括对上传文件后缀与MIME Type进行匹配校验,对文件头信息与文件后缀进行匹配校验。 因此,校验MultipartFile的文件格式时,除了根据文件后缀之外,还需要对文件的MIME Type、文件头信息进行匹配校验,以确保文件的完整性和正确性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [[文件上传工具类] MultipartFile 统一校验](https://blog.csdn.net/pingzhuyan/article/details/128801681)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [奇安信代码卫士,文件上传漏洞解决demo](https://download.csdn.net/download/tczaqss/87718438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值