大象根據研究與實際項目經驗,向大家介紹一個關于文件類型驗證的解決辦法。不清楚的朋友可以了解下,知道的不喜勿噴。
對于文件上傳,相信大家都不會陌生,我們都知道,文件在上傳到服務器的過程中,都是以流的形式傳輸的,在后臺處理文件上傳的代碼中,獲得這個流,然后讀取數據流將之保存到上傳文件的臨時目錄中,如果有使用到MongoDB,再將這個文件存儲到文件系統中。
大部分的文件上傳都是通過HTML的上傳組件完成的,而業務需求往往是有類型要求的,比如只允許上傳jpg、gif、png類型的圖片,或者是只允許上傳Office文檔等等,雖然可以用JavaScript對上傳文件做一些類型驗證之類的控制,但還是不能完全做到過慮。這時,就需要在后臺,用代碼來進一步完成這個驗證工作。
到底通過什么方式可以做到正確驗證呢?答案就是通過文件的頭部信息,通過大量測試,大象發現每種類型的文件,他們最開始的一段信息都是一樣的,比如Office97~03,它的頭四位16進制信息就是d0 cf 11 e0,而Office2007則是50 4b 03 04,PDF為25 50 44 46,大家可以多用這樣的文件分別測試一下,看看前四位16進制信息是不是都是一樣的。當然這其中也有個別情況,比如jpg類型的圖片,它的前四位16進制信息就有兩種一個是ff d8 ff e0,另一個是ff d8 ff e1,區別是最后一位。知道了這些,我們就有一個方向了。 可能有同學有疑問了,為什么只取前四位,不是六位或八位呢?這是因為,大象根據反復測試發現,從第五位開始到第八位,同一種類型的文件,在這幾位里面很有一些存在區別,像圖片以及pdf,這種現象很多,為了避免同一類型的文件,因為這一些小的不同,要定義N多檢測頭信息,這樣做似乎沒有必要,因此大象才建議取前四位作為類型檢測的依據。 不過說了這么多,還是沒講怎么做,這顯然不是大象的風格,大象一般都從實際出發,用代碼來說話。
package com.bolo.util;
public class FileValidateUtil {
public static boolean validateType(byte[] b, String customTypes) {
if (b != null) {
int size = b.length;
String hex = null;
StringBuilder contentType = new StringBuilder();
for (int i = 0; i < size; i++) {
hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = "0" + hex;
}
contentType.append(hex);
if (i > 2)
break;
}
if (customTypes.indexOf(contentType.toString()) > -1) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
} 上面這段代碼就是用來對文件類型作驗證的方法,第一個參數是文件的字節數組,第二個就是定義的可通過類型。代碼很簡單,主要是注意中間的一處,將字節數組的前四位轉換成16進制字符串,并且轉換的時候,要先和0xFF做一次與運算。這是因為,整個文件流的字節數組中,有很多是負數,進行了與運算后,可以將前面的符號位都去掉,這樣轉換成的16進制字符串最多保留兩位,如果是正數又小于10,那么轉換后只有一位,需要在前面補0,這樣做的目的是方便比較,取完前四位這個循環就可以終止了。
下面我們準備些文件來測試一下這段代碼有沒有問題。
package com.bolo.util;
import java.io.IOException;
import junit.framework.Assert;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.springframework.util.ResourceUtils;
import com.bolo.util.FileValidateUtil;
public class FileValidateUtilTest {
/**
* 文件頭部信息,十六進制信息,取前4位
* 50 4b 03 04 office 2007+
* d0 cf 11 e0 office 97~03
* 25 50 44 46 pdf
* ff d8 ff e0 jpg,部分png與jpg頭文件前4位一樣
* ff d8 ff e1 jpg,一種不同的jpg頭文件
* 89 50 4e 47 png
*/
private static final String FILE_TYPE = "504b0304 d0cf11e0 25504446 ffd8ffe0 ffd8ffe1 89504e47";
@Test
public void jpgTest(){
validateType("file/1.jpg");
}
@Test
public void docTest(){
validateType("file/2.doc");
}
@Test
public void docxTest(){
validateType("file/3.docx");
}
@Test
public void pdfTest(){
validateType("file/4.pdf");
}
@Test
public void exeTest(){
validateType("file/5.png");
}
private void validateType(String path){
try {
Assert.assertTrue(FileValidateUtil.validateType(FileUtils
.readFileToByteArray(ResourceUtils.getFile("classpath:" + path)), FILE_TYPE));
} catch (IOException e) {
e.printStackTrace();
}
}
} 從測試代碼可以看到文件類型,只有第五個不是正確文件,是我將一個exe文件通過改后綴為png。這個測試類,用到了commons-io的FileUtils,需要在pom中加入這個依賴,而ResourceUtils在org.springframework.util包中,它屬于spring-core.jar,大象這個測試類是放在之前ssm3工程test里面,要想正常運行測試,要在test/resources目錄下建個file文件夾存放測試文件,最后編譯之后file及其測試文件都會在test-classes下面,所以文件查找是以classpath:開頭。
OK,運行測試,結果就是前四個成功,最后一個失敗,這達到了我們的預期,只允許FILE_TYPE里面定義的文件類型通過測試。大家可以自己動手試驗一下。
本文為菠蘿大象原創,如要轉載請注明出處。http://bolo.blogjava.net/
posted on 2012-05-01 11:37
菠蘿大象 閱讀(6352)
評論(0) 編輯 收藏 所屬分類:
Java