Java 使用魔數判斷檔案型別

lyu6發表於2024-11-29

引言
在日常的開發工作中,一般是使用文案字尾去判斷檔案型別,這種不是很嚴謹,那麼這樣可透過修改檔名的方式去修改檔案型別,如果將一個危險檔案修成成png或者txt上傳到檔案伺服器,後果難以估量。那麼可以使用檔案頭魔數的方式去判斷檔案型別,這是一種比較安全的方式。

魔數介紹(Magic Number)
檔案頭魔數是指檔案格式中用來標識檔案型別的一段特定位元組序列。它通常位於檔案的開頭部分,能夠幫助作業系統或應用程式識別檔案的格式和型別。
特點

  • 唯一性:每種檔案格式通常都有一個獨特的魔數。
  • 固定位置:魔數通常位於檔案的開頭,便於快速檢查。
  • 二進位制格式:魔數通常以二進位制形式儲存。

檔案型別魔數列舉

package com.demo.utils;

/**
 * 檔案型別魔數列舉
 * 使用場景:用於判斷檔案型別,獲取檔案型別
 *
 * @author lyu
 * @description:
 * @create 2024-11-29 15:43
 */
public enum MagicNumnerFileTypeEnum {
    /**
     * JPG
     */
    JPG("ffd8ff", "jpg"),
    /**
     * PNG
     */
    PNG("89504e47", "png"),
    /**
     * GIF
     */
    GIF_87("4749463837", "gif"),
    /**
     * GIF
     */
    GIF_89("4749463839", "gif"),
    /**
     * TIF
     */
    TIF("49492a00227105008037", "tif"),
    /**
     * Windows bitmap
     */
    BMP_16("424d228c010000000000", "bmp"),
    /**
     * Windows bitmap
     */
    BMP_24("424d8240090000000000", "bmp"),
    /**
     * Windows bitmap
     */
    BMP_256("424d8e1b030000000000", "bmp"),
    /**
     * CAD
     */
    DWG("41433130313500000000", "dwg"),
    /**
     * Rich Text Format
     */
    RTF("7b5c727466315c616e73", "rtf"),
    /**
     * Adobe photoshop
     */
    PSD("38425053000100000000", "psd"),
    /**
     * eml
     */
    EML("46726f6d3a203d3f6762", "eml"),
    /**
     * Microsoft Access
     */
    MDB("5374616E64617264204A", "mdb"),
    /**
     * Postscript
     */
    PS("252150532D41646F6265", "ps"),
    /**
     * Adobe Acrobat
     */
    PDF("255044462d312e", "pdf"),
    /**
     * rmvb
     */
    RMVB("2e524d46000000120001", "rmvb"),
    /**
     * flv
     */
    FLV("464c5601050000000900", "flv"),
    /**
     * MP4
     */
    MP4("00000020667479706", "mp4"),
    /**
     * MP4
     */
    MP4_ALT("00000018667479706d70", "mp4"),
    /**
     * MP3
     */
    MP3("49443303000000002176", "mp3"),
    /**
     * mpg
     */
    MPG("000001ba210001000180", "mpg"),
    /**
     * wmv
     */
    WMV("3026b2758e66cf11a6d9", "wmv"),
    /**
     * wav
     */
    WAV("52494646e27807005741", "wav"),
    /**
     * AVI
     */
    AVI("52494646d07d60074156", "avi"),
    /**
     * MID
     */
    MID("4d546864000000060001", "mid"),
    /**
     * ARAR Archive
     */
    RAR("526172211a0700cf9073", "rar"),
    /**
     * INI
     */
    INI("235468697320636f6e66", "ini"),
    /**
     * JAR
     */
    JAR("504B03040a0000000000", "jar"),
    /**
     * JAR
     */
    JAR_ALT("504B0304140008000800", "jar"),
    /**
     * Microsoft Word/Excel
     */
    XLS("d0cf11e0a1b11ae10", "xls"),
    /**
     * ZIP Archive
     */
    ZIP("504B0304", "zip"),
    /**
     * windows exe
     */
    EXE("4d5a9000030000000400", "exe"),
    /**
     * JSP
     */
    JSP("3c25402070616765206c", "jsp"),
    /**
     * MF
     */
    MF("4d616e69666573742d56", "mf"),
    /**
     * java
     */
    JAVA("7061636b616765207765", "java"),
    /**
     * windows script
     */
    BAT("406563686f206f66660d", "bat"),
    /**
     * GZ
     */
    GZ("1f8b0800000000000000", "gz"),
    /**
     * java bytecode
     */
    CLASS("cafebabe0000002e0041", "class"),
    /**
     * CHM
     */
    CHM("49545346030000006000", "chm"),
    /**
     * mxp
     */
    MXP("04000000010000001300", "mxp"),
    /**
     * torrent
     */
    TORRENT("6431303a637265617465", "torrent"),
    /**
     * mov
     */
    MOV("6D6F6F76", "mov"),
    /**
     * Word Perfect
     */
    WPD("FF575043", "wpd"),
    /**
     * Outlook Express
     */
    DBX("CFAD12FEC5FD746F", "dbx"),
    /**
     * Outlook
     */
    PST("2142444E", "pst"),
    /**
     *
     */
    QDF("AC9EBD8F", "qdf"),
    /**
     * windows password
     */
    PWL("E3828596", "pwl"),
    /**
     * Real Audio
     */
    RAM("2E7261FD", "ram");

    private final String key;
    private final String value;

    MagicNumnerFileTypeEnum(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public String getKey() {
        return key;
    }

}

檔案型別判斷工具類

package com.demo.utils;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author lyu
 * @version: 1.0
 * @create 2024-11-29 16:24
 */
public class FileTypeUtils {

    private static final String EMPTY = "";

    private static final String DOT = ".";

    private static final int OFFSET = 28;

    private static final int INDEX_NOT_FOUND = -1;

    /**
     * 類Unix路徑分隔符
     */
    public static final char UNIX_SEPARATOR = '/';
    ;
    /**
     * Windows路徑分隔符
     */
    public static final char WINDOWS_SEPARATOR = '\\';

    private FileTypeUtils() {
        throw new UnsupportedOperationException("Disabled use refection to create instance!!");
    }


    /**
     * 獲取檔案頭
     *
     * @param inputStream 輸入流
     * @return 16 進位制的檔案投資訊
     * @throws IOException io異常
     */
    private static String getFileHeader(InputStream inputStream) throws IOException {
        byte[] b = new byte[28];
        inputStream.read(b, 0, 28);
        return bytes2hex(b);
    }

    /**
     * 將位元組陣列轉換成16進位制字串
     *
     * @param src 檔案位元組陣列
     * @return 16進位制字串
     */
    private static String bytes2hex(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length == 0) {
            return null;
        }
        for (byte b : src) {
            int v = b & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString().toUpperCase();
    }

    private static byte[] getFileTypeByte(byte[] bytes) {
        byte[] fileTypeByte = new byte[OFFSET];
        int offset = Math.min(bytes.length, OFFSET);
        System.arraycopy(bytes, 0, fileTypeByte, 0, offset);
        return fileTypeByte;
    }

    public static String getFileType(byte[] bytes, String filename) {
        byte[] fileTypeByte = getFileTypeByte(bytes);
        String fileHeader = bytes2hex(fileTypeByte);
        String typeName = getType(fileHeader);
        if (null == typeName) {
            return extName(filename);
        }
        if ("zip".equalsIgnoreCase(typeName)) {
            // zip可能為docx、xlsx、pptx、jar、war等格式,副檔名輔助判斷
            final String extName = extName(typeName);
            if ("docx".equalsIgnoreCase(extName)) {
                typeName = "docx";
            } else if ("xlsx".equalsIgnoreCase(extName)) {
                typeName = "xlsx";
            } else if ("pptx".equalsIgnoreCase(extName)) {
                typeName = "pptx";
            } else if ("jar".equalsIgnoreCase(extName)) {
                typeName = "jar";
            } else if ("war".equalsIgnoreCase(extName)) {
                typeName = "war";
            }
        }
        return typeName;
    }

    private static String extName(String filename) {
        if (filename == null) {
            return null;
        }
        int index = filename.lastIndexOf(DOT);
        if (index == -1) {
            return EMPTY;
        } else {
            String ext = filename.substring(index + 1);
            // 副檔名中不能包含路徑相關的符號
            return containsAny(ext, UNIX_SEPARATOR, WINDOWS_SEPARATOR) ? EMPTY : ext;
        }
    }


    public static boolean containsAny(CharSequence str, char... testChars) {
        if (!isEmpty(str)) {
            int len = str.length();
            for (int i = 0; i < len; i++) {
                if (contains(testChars, str.charAt(i)) > INDEX_NOT_FOUND) {
                    return true;
                }
            }
        }
        return false;
    }

    private static int contains(char[] array, char value) {
        if (null != array) {
            for (int i = 0; i < array.length; i++) {
                if (value == array[i]) {
                    return i;
                }
            }
        }
        return INDEX_NOT_FOUND;
    }

    private static boolean isEmpty(CharSequence str) {
        return str == null || str.length() == 0;
    }

    private static String getType(String fileHeader) {
        for (MagicNumnerFileTypeEnum fileTypeEnum : MagicNumnerFileTypeEnum.values()) {
            if (fileHeader.startsWith(fileTypeEnum.getValue())) {
                return fileTypeEnum.getKey();
            }
        }
        return null;
    }

    /**
     * 判斷指定輸入流是否是指定檔案格式
     *
     * @param inputStream  檔案流
     * @param fileTypeEnum 檔案格式列舉
     * @return 結果
     * @throws IOException io異常
     */
    public static boolean isFileType(InputStream inputStream, MagicNumnerFileTypeEnum fileTypeEnum) throws IOException {
        if (null == inputStream) {
            return false;
        }
        String fileHeader = getFileHeader(inputStream);
        return fileHeader.startsWith(fileTypeEnum.getValue());
    }

    public static void main(String[] args) throws IOException {
        String str = "E:\\a.docx";
        FileInputStream inputStream = new FileInputStream(str);
        byte[] b = new byte[inputStream.available()];
        inputStream.read(b, 0, inputStream.available());
        System.out.println(getFileType(b, str));
    }

}

Note:最快捷的方式是使用hutool工具包下的FileTypeUtil的getType()方法。

相關文章