不同类型文件的魔术数字

前言

在Web项目中,用户上传的文件可能是用户恶意篡改的病毒文件,所以为了避免这种情况,需要对文件的后缀名进行过滤.

通过对后缀名的判断只能够简单的验证文件,如果用户恶意更改了文件的后缀名,这种判断就不起作用了.所以还需要对文件的字节进行验证,每一个文件都有自己的魔术数字.也就是在一个文件的开头部分的字节码,用winhex-19.8(下载下来之后有个木马文件,删除掉就可以,不影响使用)查看一个文件的字节码,就能够知道该类型文件的魔术数字.通过判断魔术数字,就能够起到验证文件的作用.

以下程序通过HashMap存储了后缀名和字节码,实现了对上传文件的验证.

FileType为验证的主方法,在FileUtil中只是控制文件上传.大家可以拿去做个参考.

对于图片类型,通过判断图片的宽和高是否有0来实现对文件的验证.

FileType.java

package com.mbyte.easy.util;import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;/*** @Description : 读取文件的后缀名** @author : 申劭明* @date : 2019/9/2 19:42
*/
public class FileType
{/*** key是文件尾缀,value是文件的魔术数字*/public final static Map FILE_TYPE_MAP = new HashMap<>();private FileType(){}static{//初始化文件类型信息getAllFileType();}/*** @Description : 用静态方法初始化FILE_TYPE_MAP** @author : 申劭明* @date : 2019/9/2 19:41*/private static void getAllFileType(){//图片文件FILE_TYPE_MAP.put("jpg", "FFD8FF");FILE_TYPE_MAP.put("png", "89504E47");FILE_TYPE_MAP.put("gif", "47494638");FILE_TYPE_MAP.put("tif", "49492A00");FILE_TYPE_MAP.put("bmp", "424D");FILE_TYPE_MAP.put("dwg", "41433130");FILE_TYPE_MAP.put("html", "68746D6C3E");FILE_TYPE_MAP.put("rtf", "7B5C727466");FILE_TYPE_MAP.put("xml", "3C3F786D6C");FILE_TYPE_MAP.put("zip", "504B0304");FILE_TYPE_MAP.put("rar", "52617221");//Photoshop (psd)FILE_TYPE_MAP.put("psd", "38425053");//Email [thorough only] (eml)FILE_TYPE_MAP.put("eml", "44656C69766572792D646174653A");//Outlook Express (dbx)FILE_TYPE_MAP.put("dbx", "CFAD12FEC5FD746F");//Outlook (pst)FILE_TYPE_MAP.put("pst", "2142444E");//MS WordFILE_TYPE_MAP.put("xls", "D0CF11E0");//MS Excel 注意:word 和 excel的文件头一样FILE_TYPE_MAP.put("doc", "D0CF11E0");//MS Access (mdb)FILE_TYPE_MAP.put("mdb", "5374616E64617264204A");//WordPerfect (wpd)FILE_TYPE_MAP.put("wpd", "FF575043");FILE_TYPE_MAP.put("eps", "252150532D41646F6265");FILE_TYPE_MAP.put("ps", "252150532D41646F6265");//Adobe Acrobat (pdf)FILE_TYPE_MAP.put("pdf", "255044462D312E");//Quicken (qdf)FILE_TYPE_MAP.put("qdf", "AC9EBD8F");//Windows Password (pwl)FILE_TYPE_MAP.put("pwl", "E3828596");//音频文件FILE_TYPE_MAP.put("mp3", "4944330300");FILE_TYPE_MAP.put("wav", "57415645");//视频文件FILE_TYPE_MAP.put("avi", "41564920");FILE_TYPE_MAP.put("mp4", "0000002066747970");FILE_TYPE_MAP.put("mkv", "1A45DFA3A3428681");//Real Audio (ram)FILE_TYPE_MAP.put("ram", "2E7261FD");//Real Media (rm)FILE_TYPE_MAP.put("rm", "2E524D46");FILE_TYPE_MAP.put("mpg", "000001BA");FILE_TYPE_MAP.put("mov", "6D6F6F76");//Windows Media (asf)FILE_TYPE_MAP.put("asf", "3026B2758E66CF11");//MIDI (mid)FILE_TYPE_MAP.put("mid", "4D546864");}/*** 该filePath指向的文件不存在时返回值为null* @param filePath 待获取尾缀的文件路径* @return*/public static String getFileSuffix(String filePath) {return getFileSuffix(new File(filePath));}/*** @Description : 获取filePath对应文件的尾缀** @param file File对象* @return : 文件的尾缀,如jpg等.* return值为空指针的情况:* 1.该图片文件的宽或高为0;* 2.该文件的后缀名不包含在FILE_TYPE_MAP中* 3.程序发生异常* @author : 申劭明* @date : 2019/9/2 20:15* @thorws : IOException*/public static String getFileSuffix(File file){if (file.exists()){String result = null;try {result = getImageFileType(file);return result == null? getFileByFile(file) : result;} catch (IOException e) {e.printStackTrace();return null;}}else{return null;}}/*** @Description : 获取图片的尾缀** @param f 待获取尾缀的File对象* @return : 如果该文件是真实的图片,返回该图片的尾缀;否则返回空指针* @author : 申劭明* @date : 2019/9/2 19:38*/public final static String getImageFileType(File f) throws IOException {if (isImage(f)){ImageInputStream iis = ImageIO.createImageInputStream(f);Iterator iter = ImageIO.getImageReaders(iis);if (!iter.hasNext()){return null;}ImageReader reader = iter.next();iis.close();return reader.getFormatName();}return null;}/*** @Description : 通过字节码判断文件类型.** @param file 待判断的File对象* @return : 如果该文件字节码存在于map集合中,返回该文件的尾缀名;否则返回空指针* @author : 申劭明* @date : 2019/9/2 19:35*/public final static String getFileByFile(File file) throws IOException {String fileType = null;//读取字节文件中的前50个字节byte[] b = new byte[50];InputStream is = new FileInputStream(file);is.read(b);fileType = getFileTypeByStream(b);is.close();return fileType;}/*** @Description : 通过文件的字节码判断文件类型** @param b 文件的字节码数组* @return : 如果该文件字节码能够在FILE_TYPE_MAP中找到,返回该key值(文件类型名);否则返回空指针* @author : 申劭明* @date : 2019/9/2 19:26*/public final static String getFileTypeByStream(byte[] b){String filetypeHex = String.valueOf(getFileHexString(b));Iterator> entryiterator = FILE_TYPE_MAP.entrySet().iterator();while (entryiterator.hasNext()) {Entry entry =  entryiterator.next();String fileTypeHexValue = entry.getValue();//如果是以该字节码为开头,则属于该类型文件if (filetypeHex.toUpperCase().startsWith(fileTypeHexValue)) {return entry.getKey();}}return null;}/*** @Description : 通过读取图片的宽和高来判断是否为一个图片** @param file 待检测的文件* @return : true 是 | false 否,发生异常也会导致返回值为null* @author : 申劭明* @date : 2019/9/2 19:25*/public static final boolean isImage(File file){boolean flag = false;try{BufferedImage bufferedImage = ImageIO.read(file);int width = bufferedImage.getWidth();int height = bufferedImage.getHeight();if(width==0 || height==0){flag = false;}else {flag = true;}} catch (Exception e) {flag = false;}return flag;}/*** @Description : 功能说明** @param b* @return : java.lang.String* @author : 申劭明* @date : 2019/9/2 19:25*/public final static String getFileHexString(byte[] b){StringBuilder stringBuilder = new StringBuilder();if (b == null || b.length <= 0){return null;}for (int i = 0; i < b.length; i++){int v = b[i] & 0xFF;String hv = Integer.toHexString(v);if (hv.length() < 2){stringBuilder.append(0);}stringBuilder.append(hv);}return stringBuilder.toString();}
}

 

FileUtil.java

package com.mbyte.easy.util;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.Arrays;/*** @ClassName: FileUtil* @Author : 申劭明* @Description: 文件上传工具类* @Version 2.0**/
@Slf4j
public class FileUtil {/*** 文件上传路径前缀(在application中设置)*/public static String uploadSuffixPath;/*** 本地磁盘目录(在application中设置)*/public static String uploadLocalPath;/*** 上传图片支持尾缀*/public static final String[] IMAGE_FILES = {"jpg","jpeg","png","ico","gif"};/*** 上传音频支持尾缀*/public static final String[] AUDIO_FILES = {"mp3","wav"};/*** 上传视频支持尾缀*/public static final String[] VIDEO_FILES = {"avi","mkv","mp4"};/*** @Title: uploadFile* @Author : 申劭明* @Description: 单文件上传到本地磁盘* @param: multipartFile* @return: 如果文件上传成功,返回文件的网络访问路径;上传失败返回空指针*/public static String uploadFile(MultipartFile multipartFile){if(multipartFile == null){return null;}//获取文件相对路径String fileName = getUploadFileName(multipartFile.getOriginalFilename());String dateDir = DateUtil.format(null,DateUtil.PATTERN_yyyyMMdd);File destFileDir = new File(uploadLocalPath + File.separator + dateDir);if(!destFileDir.exists()){destFileDir.mkdirs();}try {File destFile = new File(destFileDir.getAbsoluteFile()+File.separator+fileName);multipartFile.transferTo(destFile);log.info("文件【"+multipartFile.getOriginalFilename()+"】上传成功");return uploadSuffixPath + "/" + dateDir+"/"+fileName;} catch (IOException e) {log.error("文件上传异常:"+multipartFile.getOriginalFilename(),e);return null;}}/*** @Description : 上传图片文件,支持的文件格式包含在 IMAGE_FILES 数组中** @param multipartFile* @return : 文件在数据库中的存储路径* @author : 申劭明* @date : 2019/9/2 20:49*/public static String uploadImage(MultipartFile multipartFile){return inStringArray(IMAGE_FILES,getFileSuffix(multipartFile))? uploadFile(multipartFile) : null;}/*** @Description : 上传音频文件,支持的文件格式包含在 AUDIO_FILES 数组中** @return : 文件在数据库中的存储路径* @author : 申劭明* @date : 2019/9/2 20:51*/public static String uploadAudio(MultipartFile multipartFile){return inStringArray(AUDIO_FILES,getFileSuffix(multipartFile))? uploadFile(multipartFile) : null;}/*** @Description : 上传视频文件,支持的文件格式包含在 VIDEO_FILES 数组中** @param multipartFile* @return : 文件在数据库中的存储路径* @author : 申劭明* @date : 2019/9/2 20:57*/public static String uploadVideo(MultipartFile multipartFile){return inStringArray(VIDEO_FILES,getFileSuffix(multipartFile))? uploadFile(multipartFile) : null;}/*** @Description : 获取文件的后缀** @param multipartFile* @return : 文件后缀* @author : 申劭明* @date : 2019/9/2 20:55*/private static String getFileSuffix(MultipartFile multipartFile){if(multipartFile == null){return null;}File file = new File(uploadLocalPath + "\\test.txt");try {//将mutipartFile以输入流的形式暂时存储在file文件中,用于方法的输入参数FileUtils.copyInputStreamToFile(multipartFile.getInputStream(),file);//获得文件的尾缀String suffix = FileType.getFileSuffix(file);return suffix;} catch (IOException e) {e.printStackTrace();return null;}}/*** @Title: getUploadFilePath* @Description: 获取上传后的文件相对路径  --数据库存储该路径* @Author: 申劭明* @param: fileName* @return: 数据中存储的路径名称,eg:D:\home\lxt\Desktop\0729\test\20190821,D为项目所在的文件夹路径* @throws:*/public static String getUploadFileName(String fileName){return new StringBuilder().append(DateUtil.format(null, DateUtil.PATTERN_yyyyMMddHHmmssSSS)).append("_").append(Utility.getRandomStrByNum(6)).append(".").append(FilenameUtils.getExtension(fileName)).toString();}/*** @Title: isFileBySuffix* @Author: 申劭明* @Description: 通过后缀名判断是否是某种文件* @param: fileName 文件名称* @param: suffix 后缀名* @return: 如果满足条件返回true,否则返回false* @throws:*/public static boolean isFileBySuffix(String fileName,String suffix){if(StringUtils.isNoneBlank(fileName) && StringUtils.isNoneBlank(suffix)){return fileName.endsWith(suffix.toLowerCase()) || fileName.endsWith(suffix.toUpperCase());}return false;}/*** @Description : 上传音频文件的同时获取该音频的时间,并返回该时间字段** 该方法需要引入 it.sauronsoftware.jave.Encoder;* 和 it.sauronsoftware.jave.MultimediaInfo;* @param videoMultipart 音频文件* @return : videoMultipart 音频文件的时间* @author : 申劭明* @date : 2019/8/28 16:11*/
//    public static String getVideoLength(MultipartFile videoMultipart){
//        String filePath = FileUtil.uploadFile(videoMultipart);
//        return getVideoLength(filePath);
//    }/*** @Description : 通过filePath获取该文件的时间长短* 该方法需要引入 it.sauronsoftware.jave.Encoder;* 和 it.sauronsoftware.jave.MultimediaInfo;** @param filePath 音频文件的路径* @return : 该音频文件播放的时间,eg:3分24秒* @author : 申劭明* @date : 2019/8/28 16:10*/
//    public static String getVideoLength(String filePath){
//
//        File source = new File(uploadLocalPath+filePath.replace(uploadSuffixPath,""));
//        Encoder encoder = new Encoder();
//        long ls = 0;
//        MultimediaInfo m;
//        String time = "";
//        try {
//            m = encoder.getInfo(source);
//            ls = m.getDuration();
//            long l = ls/1000;
//            time = ls/60000+"分"+(ls%60000)/1000+"秒";
//            System.out.println();
//        } catch (Exception e) {
//            System.out.println("获取音频时长有误:" + e.getMessage());
//        }
//        return time;
//    }/*** @Description : 判断array数组中是否包含target对象** @param array 字符串数组* @param target 目标字符串* @return : 包含 -> true,否则 -> false* @author : 申劭明* @date : 2019/9/2 20:38*/private static boolean inStringArray(String[] array,String target){return Arrays.asList(array).contains(target);}}

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部