在处理上传文件时,经常用到MultipartFile文件。MultipartFile是spring类型,代表html中from-data方式上传的文件,包含二进制数据+文件名称。
MultipartFile和File可以互相转换
MultipartFile -> File:使用transferTo方法
MultipartFile multipartFile = ...
File file = new File(****);
multipartFile.transferTo(file);
File -> MultipartFile:使用commons-fileupload
File file = new File(***);
FileItem fileItem = new DiskFileItem("copyfile.txt", Files.probeContentType(file.toPath()), false, file.getName(), (int)file.length(), file.getParentFile());
try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()) {
IOUtils.copy(inputStream,os);
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
。。。。。
} catch (IOException e) {
e.printStackTrace();
}
下面再说一下,在处理文件上传文件时,出于安全性考虑经常会对文件类型做检查,常用的检查方式有:
- 通过文件名后缀的方式检测
// 截取文件后缀 进行比较即可
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
- 通过文件魔数检测
public enum FileType {
/**
* JPEG (jpg)
*/
JPEG("JPG", "FFD8FF"),
/**
* PNG
*/
PNG("PNG", "89504E47"),
/**
* GIF
*/
GIF("GIF", "47494638"),
/**
* TIFF (tif)
*/
TIFF("TIF", "49492A00"),
/**
* Windows bitmap (bmp)
*/
BMP("BMP", "424D"),
/**
* 16色位图(bmp)
*/
BMP_16("BMP", "424D228C010000000000"),
/**
* 24色位图(bmp)
*/
BMP_24("BMP", "424D8240090000000000"),
/**
* 32色位图(bmp)
*/
BMP_256("BMP", "424D8E1B030000000000"),
/**
* CAD (dwg)
*/
DWG("DWG", "41433130"),
/**
* Adobe photoshop (psd)
*/
PSD("PSD", "38425053"),
/**
* Rich Text Format (rtf)
*/
RTF("RTF", "7B5C727466"),
/**
* XML
*/
XML("XML", "3C3F786D6C"),
/**
* HTML (html)
*/
HTML("HTML", "68746D6C3E"),
/**
* Email [thorough only] (eml)
*/
EML("EML", "44656C69766572792D646174653A"),
/**
* Outlook Express (dbx)
*/
DBX("DBX", "CFAD12FEC5FD746F "),
/**
* Outlook (pst)
*/
PST("PST", "2142444E"),
/**
* doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db
*/
OLE2("OLE2", "0xD0CF11E0A1B11AE1"),
/**
* Microsoft Word/Excel 注意:word 和 excel的文件头一样
*/
XLS("XLS", "D0CF11E0"),
/**
* Microsoft Word/Excel 注意:word 和 excel的文件头一样
*/
DOC("DOC", "D0CF11E0"),
/**
* Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样
*/
DOCX("DOCX", "504B0304"),
/**
* Microsoft Word/Excel 2007以上版本文件 注意:word 和 excel的文件头一样 504B030414000600080000002100
*/
XLSX("XLSX", "504B0304"),
/**
* Microsoft Access (mdb)
*/
MDB("MDB", "5374616E64617264204A"),
/**
* Word Perfect (wpd)
*/
WPB("WPB", "FF575043"),
/**
* Postscript
*/
EPS("EPS", "252150532D41646F6265"),
/**
* Postscript
*/
PS("PS", "252150532D41646F6265"),
/**
* Adobe Acrobat (pdf)
*/
PDF("PDF", "255044462D312E"),
/**
* Quicken (qdf)
*/
QDF("qdf", "AC9EBD8F"),
/**
* QuickBooks Backup (qdb)
*/
QDB("qbb", "458600000600"),
/**
* Windows Password (pwl)
*/
PWL("PWL", "E3828596"),
/**
* ZIP Archive
*/
ZIP("ZIP", "504B0304"),
/**
* ARAR Archive
*/
RAR("ARAR", "52617221"),
/**
* WAVE (wav)
*/
WAV("WAV", "57415645"),
/**
* AVI
*/
AVI("AVI", "41564920"),
/**
* Real Audio (ram)
*/
RAM("RAM", "2E7261FD"),
/**
* Real Media (rm) rmvb/rm相同
*/
RM("RM", "2E524D46"),
/**
* Real Media (rm) rmvb/rm相同
*/
RMVB("RMVB", "2E524D46000000120001"),
/**
* MPEG (mpg)
*/
MPG("MPG", "000001BA"),
/**
* Quicktime (mov)
*/
MOV("MOV", "6D6F6F76"),
/**
* Windows Media (asf)
*/
ASF("ASF", "3026B2758E66CF11"),
/**
* ARJ Archive
*/
ARJ("ARJ", "60EA"),
/**
* MIDI (mid)
*/
MID("MID", "4D546864"),
/**
* MP4
*/
MP4("MP4", "00000020667479706D70"),
/**
* MP3
*/
MP3("MP3", "49443303000000002176"),
/**
* FLV
*/
FLV("FLV", "464C5601050000000900"),
/**
* 1F8B0800000000000000
*/
GZ("GZ", "1F8B08"),
/**
* CSS
*/
CSS("CSS", "48544D4C207B0D0A0942"),
/**
* JS
*/
JS("JS", "696B2E71623D696B2E71"),
/**
* Visio
*/
VSD("VSD", "d0cf11e0a1b11ae10000"),
/**
* WPS文字wps、表格et、演示dps都是一样的
*/
WPS("WPS", "d0cf11e0a1b11ae10000"),
/**
* torrent
*/
TORRENT("TORRENT", "6431303A637265617465"),
/**
* JSP Archive
*/
JSP("JSP", "3C2540207061676520"),
/**
* JAVA Archive
*/
JAVA("JAVA", "7061636B61676520"),
/**
* CLASS Archive
*/
CLASS("CLASS", "CAFEBABE0000002E00"),
/**
* JAR Archive
*/
JAR("JAR", "504B03040A000000"),
/**
* MF Archive
*/
MF("MF", "4D616E69666573742D56"),
/**
* EXE Archive
*/
EXE("EXE", "4D5A9000030000000400"),
/**
* ELF Executable
*/
ELF("ELF", "7F454C4601010100"),
/**
* Lotus 123 v1
*/
WK1("WK1", "2000604060"),
/**
* Lotus 123 v3
*/
WK3("WK3", "00001A0000100400"),
/**
* Lotus 123 v5
*/
WK4("WK4", "00001A0002100400"),
/**
* Lotus WordPro v9
*/
LWP("LWP", "576F726450726F"),
/**
* Sage(sly.or.srt.or.slt;sly;srt;slt)
*/
SLY("SLY", "53520100"),
NOT_EXITS_ENUM("", "");
private String fileTypeName;
private String magicNum;
FileType(String fileTypeName, String magicNum) {
this.fileTypeName = fileTypeName;
this.magicNum = magicNum;
}
private String getFileTypeName() {
return fileTypeName;
}
private String getMagicNum() {
return magicNum;
}
private static FileType getFileTypeByFileTypeName(String fileTypeName) {
if (StringUtils.isNotBlank(fileTypeName)) {
for (FileType type : values()) {
if (StringUtils.equalsIgnoreCase(type.getFileTypeName(), fileTypeName)) {
return type;
}
}
}
return NOT_EXITS_ENUM;
}
@Nullable
private static String bytesToHexStr(byte[] bytes) {
if (bytes == null || bytes.length <= 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (byte value : bytes) {
int v = value & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
builder.append(0);
}
builder.append(hv);
}
return builder.toString();
}
public static boolean isEqualsFileType(byte[] bytes, Set<String> fileTypeList) {
if (CollectionUtils.isEmpty(fileTypeList)) {
return false;
}
String magicNum = bytesToHexStr(bytes);
if (!StringUtils.isBlank(magicNum)) {
magicNum = magicNum.toUpperCase();
for (String f : fileTypeList) {
FileType fileType = getFileTypeByFileTypeName(f);
if (fileType == NOT_EXITS_ENUM) {
continue;
}
if (magicNum.startsWith(fileType.getMagicNum().toUpperCase())) {
return true;
}
}
}
return false;
}
}
注意:魔数有一个问题就是,有些文件的魔数前缀相同。并且,黑客也会修改文件头,所以这样也不会做到绝对安全。不过这样已经能减少很多安全问题了。
- mime-type校验
什么是MIME TYPE?
首先我们要知道浏览器是如何处理内容的,在浏览器中显示的内容有HTML、XML、GIF、FLASH…,浏览器就是通过MIME TYPE区分它们,决定用什么形式来显示的,也就是该资源的媒体类型。在HTTP中,MIME类型被定义在Content-Type header中。
常见的MIME类型
超文本标记语言文本 :.html,.html text/html
普通文本: .txt text/plain
RTF文本: .rtf application/rtf
GIF图形: .gif image/gif
JPEG图形: .ipeg,.jpg image/jpeg
au声音文件: .au audio/basic
MIDI音乐文件: mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件: .ra, .ram audio/x-pn-realaudio
MPEG文件: .mpg,.mpeg video/mpeg
AVI文件: .avi video/x-msvideo
GZIP文件: .gz application/x-gzip
TAR文件: .tar application/x-tar
常用jar包
使用jmimemagic包,进行检测
MultipartFile uploadFile = ********;
// 创建一个临时文件
File tempFile = File.createTempFile(********);
FileUtils.copyInputStreamToFile(uploadFile.getInputStream(), tempFile);
MagicMatch magicMatch = Magic.getMagicMatch(tempFile, true, false);
String mimeType = magicMatch.getMimeType();
使用tika包,进行检测
// 获取mime type
AutoDetectParser parser = new AutoDetectParser();
parser.setParsers(new HashMap<MediaType, Parser>());
Metadata metadata = new Metadata();
metadata.add(TikaMetadataKeys.RESOURCE_NAME_KEY, file.getName());
try (InputStream stream = new FileInputStream(file)) {
parser.parse(stream, new DefaultHandler(), metadata, new ParseContext());
} catch(Exception e) {
e.printStackTrace();
}
return metadata.get(HttpHeaders.CONTENT_TYPE);