前言
檔案上傳功能是很多網站都必須的功能,而判斷檔案類型不僅可以過濾檔案的上傳,同時也能防範使用者上傳惡意的可執行檔和指令碼,以及將檔案上傳伺服器當作免費的檔案儲存體伺服器使用。
而對於上傳檔案來說,不能簡單的通過尾碼名來判斷檔案的類型,因為惡意攻擊可以將可執行檔的尾碼名稱改為圖片或者其他格式,誘導使用者執行,因此,判斷上傳檔案的類型需要更安全的方式。
與Java的class檔案類似,很多類型的檔案,起始的幾個位元組內容都是固定的,跟據這幾個位元組的內容,就可以判斷檔案的類型,這幾個位元組也被稱為“魔數”,比如class檔案的魔數就是“CAFEBABE”。
通過魔數判斷檔案類型便是一種更安全的方式,其樣本源碼如下。
源碼
1.建立一個檔案類型的枚舉類
public enum FileType { /** JPEG */ JPEG("FFD8FF"), /** PNG */ PNG("89504E47"), /** GIF */ GIF("47494638"), /** TIFF */ TIFF("49492A00"), /** Windows bitmap */ BMP("424D"), /** CAD */ DWG("41433130"), /** Adobe photoshop */ PSD("38425053"), /** Rich Text Format */ RTF("7B5C727466"), /** XML */ XML("3C3F786D6C"), /** HTML */ HTML("68746D6C3E"), /** Outlook Express */ DBX("CFAD12FEC5FD746F "), /** Outlook */ PST("2142444E"), /** doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db */ OLE2("0xD0CF11E0A1B11AE1"), /** Microsoft Word/Excel */ XLS_DOC("D0CF11E0"), /** Microsoft Access */ MDB("5374616E64617264204A"), /** Word Perfect */ WPB("FF575043"), /** Postscript */ EPS_PS("252150532D41646F6265"), /** Adobe Acrobat */ PDF("255044462D312E"), /** Windows Password */ PWL("E3828596"), /** ZIP Archive */ ZIP("504B0304"), /** ARAR Archive */ RAR("52617221"), /** WAVE */ WAV("57415645"), /** AVI */ AVI("41564920"), /** Real Audio */ RAM("2E7261FD"), /** Real Media */ RM("2E524D46"), /** Quicktime */ MOV("6D6F6F76"), /** Windows Media */ ASF("3026B2758E66CF11"), /** MIDI */ MID("4D546864"); private String value = ""; private FileType(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; }}
2.建立一個檔案工具類,用來判斷上傳檔案的類型
import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class FilUtil { /** 判斷檔案類型 */ public static FileType getType(String filePath) throws IOException { // 擷取檔案頭 String fileHead = getFileHeader(filePath); if (fileHead != null && fileHead.length() > 0) { fileHead = fileHead.toUpperCase(); FileType[] fileTypes = FileType.values(); for (FileType type : fileTypes) { if (fileHead.startsWith(type.getValue())) { return type; } } } return null; } /** 讀取檔案頭 */ private static String getFileHeader(String filePath) throws IOException { byte[] b = new byte[28]; InputStream inputStream = null; try { inputStream = new FileInputStream(filePath); inputStream.read(b, 0, 28); } finally { if (inputStream != null) { inputStream.close(); } } return bytesToHex(b); } /** 將位元組數群組轉換成16進位字串 */ public static String bytesToHex(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(); } }
以上,需要注意的是,可能存在一種類型檔案的魔數與另一種類型檔案魔數的前面部分相等(如‘D0CF11E0’與‘D0CF11E0A1B11AE1’),所以應該盡量將更長的魔數值放在上面。這裡的檔案與魔數枚舉得也不全面,但思路就這樣吧。
參考文獻
[1]陳康賢.大型分布式網站架構設計與實踐[M].北京:電子工業出版社.2014.09