SpringBoot 文件上传 通过Content-Type和文件头判断文件类型 关于MIME MIME 的全称是Multipurpose Internet Mail Extensions,即多用途互联网邮件扩展,尽管读起来有些拗口,但大多数人可能都知道, 这是HTTP协议中用来定义文档性质及格式的标准。IETF RFC 6838 ,对HTTP传输内容类型进行了全面定义。 而IANA (互联网号码分配机构)是负责管理所有标准MIME类型的官方机构。可以在这里 )找到所有的标准MIME
服务器通过MIME告知响应内容类型,而浏览器则通过MIME类型来确定如何处理文档; 因此为传输内容(文档、图片等)设置正确的MIME非常重要 。
通常Server会在HTTP响应中设置Content-Type ,如下面的响应:
HTTP/1.1 200 OK Server: Golfe2 Content-Length: 233 Content-Type: application/html Date: Sun, 28 Dec 2018 02:55:19 GMT 12345
这表示服务端将返回html格式的文档,而同样客户端也可以在HTTP请求中设置Content-Type 以告知服务器当前所发送内容的格式。 如下面的请求体:
POST / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: keep-alive Content-Type: application/json Content-Length: 465 1234567
这表示客户端会发送application/json格式的数据到服务端,同时应该注意到Accept 请求头,这个选项用于告知服务器应该返回什么样的数据格式(由客户端接收并完成解析)。
MIME的格式
这是一个两级的分类,比较容易理解,第一级分类通常包含:
类型
描述
text
普通文本
image
某种图像
audio
某种音频文件
video
某种视频文件
application
应用数据
multi-part
复合内容
而二级类型则非常多,以下是一些常用的MIME:
MIME
描述
audio/wav
wave音频流媒体文件
audio/webm
webm 音频文件格式
audio/ogg
ogg多媒体文件格式的音频文件
audio/mpeg
mpeg多媒体文件格式的音频文件
image/gif
gif图片
image/jpeg
jpeg图片
image/png
png图片
image/svg+xml
svg矢量图片
application/json
json格式
application/xml
xml格式
application/xhtml+xml
扩展html格式
application/x-www-form-urlencoded
表单url内容编码
application/octet-stream
二进制格式
application/pdf
pdf文档
application/atom+xml
atom订阅feed流
multipart/form-data
多文档格式
text/plain
普通文本
text/html
html文档
text/css
css文件
text/javascript
javascript文件
text/markdown
markdown文档
video/mpeg
mpeg多媒体视频文件
video/quicktime
mov多媒体视频文件
MIME Type 与 Content-Type 的关系 首先看看tomcat服务器中默认的web.xml中的描述:
<!-- ===================== Default MIME Type Mappings =================== --> <!-- When serving static resources, Tomcat will automatically generate --> <!-- a "Content-Type" header based on the resource's filename extension, --> <!-- based on these mappings. Additional mappings can be added here (to --> <!-- apply to all web applications), or in your own application's web.xml --> <!-- deployment descriptor. --> 123456
再看看apache服务器中mime.types的描述:
This file controls what Internet media types are sent to the client for given file extension(s). Sending the correct media type to the client is important so they know how to handle the content of the file. Extra types can either be added here or by using an AddType directive in your config files. For more information about Internet media types, please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type registry is at http://www.iana.org/assignments/media-types/. 123456
当web服务器收到静态的资源文件请求时,依据请求文件的后缀名在服务器的MIME配置文件中找到对应的MIME Type,再根据MIME Type设置HTTP Response的Content-Type,然后浏览器根据Content-Type的值处理文件。
也就是说, 文件扩展名=>MIME Type=>Content-Type
通过文件头识别文件 不同的文件类型有不同的文件头, 而文件头部的几个字节被称为Magic Number, 通常用十六进制表示, 可用来判断文件类型.
比如png文件的文件头Magic Number是0x89504E开始的, java class文件Magic Number为Oxcafebabe
我们可以通过判断文件的文件头信息来判断文件的类型, 而且即使改变文件扩展名文件头信息也是不改变的 .
通过java代码判断文件类型:
public class FileType { public static int CHECK_BYTES_NUMBER = 3 ; public final static Map<String, String> FILE_TYPE_MAP = new HashMap<String, String>(); private FileType () {} static { getAllFileType(); } private static void getAllFileType () { FILE_TYPE_MAP.put("ffd8ffe000104a464946" , "jpg" ); FILE_TYPE_MAP.put("89504e470d0a1a0a0000" , "png" ); FILE_TYPE_MAP.put("47494638396126026f01" , "gif" ); FILE_TYPE_MAP.put("49492a00227105008037" , "tif" ); FILE_TYPE_MAP.put("424d228c010000000000" , "bmp" ); FILE_TYPE_MAP.put("424d8240090000000000" , "bmp" ); FILE_TYPE_MAP.put("424d8e1b030000000000" , "bmp" ); FILE_TYPE_MAP.put("41433130313500000000" , "dwg" ); FILE_TYPE_MAP.put("3c21444f435459504520" , "html" ); FILE_TYPE_MAP.put("3c21646f637479706520" , "htm" ); FILE_TYPE_MAP.put("48544d4c207b0d0a0942" , "css" ); FILE_TYPE_MAP.put("696b2e71623d696b2e71" , "js" ); FILE_TYPE_MAP.put("7b5c727466315c616e73" , "rtf" ); FILE_TYPE_MAP.put("38425053000100000000" , "psd" ); FILE_TYPE_MAP.put("46726f6d3a203d3f6762" , "eml" ); FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000" , "doc" ); FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000" , "vsd" ); FILE_TYPE_MAP.put("5374616E64617264204A" , "mdb" ); FILE_TYPE_MAP.put("252150532D41646F6265" , "ps" ); FILE_TYPE_MAP.put("255044462d312e350d0a" , "pdf" ); FILE_TYPE_MAP.put("2e524d46000000120001" , "rmvb" ); FILE_TYPE_MAP.put("464c5601050000000900" , "flv" ); FILE_TYPE_MAP.put("00000020667479706d70" , "mp4" ); FILE_TYPE_MAP.put("49443303000000002176" , "mp3" ); FILE_TYPE_MAP.put("000001ba210001000180" , "mpg" ); FILE_TYPE_MAP.put("3026b2758e66cf11a6d9" , "wmv" ); FILE_TYPE_MAP.put("52494646e27807005741" , "wav" ); FILE_TYPE_MAP.put("52494646d07d60074156" , "avi" ); FILE_TYPE_MAP.put("4d546864000000060001" , "mid" ); FILE_TYPE_MAP.put("504b0304140000000800" , "zip" ); FILE_TYPE_MAP.put("526172211a0700cf9073" , "rar" ); FILE_TYPE_MAP.put("235468697320636f6e66" , "ini" ); FILE_TYPE_MAP.put("504b03040a0000000000" , "jar" ); FILE_TYPE_MAP.put("4d5a9000030000000400" , "exe" ); FILE_TYPE_MAP.put("3c25402070616765206c" , "jsp" ); FILE_TYPE_MAP.put("4d616e69666573742d56" , "mf" ); FILE_TYPE_MAP.put("3c3f786d6c2076657273" , "xml" ); FILE_TYPE_MAP.put("494e5345525420494e54" , "sql" ); FILE_TYPE_MAP.put("7061636b616765207765" , "java" ); FILE_TYPE_MAP.put("406563686f206f66660d" , "bat" ); FILE_TYPE_MAP.put("1f8b0800000000000000" , "gz" ); FILE_TYPE_MAP.put("6c6f67346a2e726f6f74" , "properties" ); FILE_TYPE_MAP.put("cafebabe0000002e0041" , "class" ); FILE_TYPE_MAP.put("49545346030000006000" , "chm" ); FILE_TYPE_MAP.put("04000000010000001300" , "mxp" ); FILE_TYPE_MAP.put("504b0304140006000800" , "docx" ); FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000" , "wps" ); FILE_TYPE_MAP.put("6431303a637265617465" , "torrent" ); FILE_TYPE_MAP.put("6D6F6F76" , "mov" ); FILE_TYPE_MAP.put("FF575043" , "wpd" ); FILE_TYPE_MAP.put("CFAD12FEC5FD746F" , "dbx" ); FILE_TYPE_MAP.put("2142444E" , "pst" ); FILE_TYPE_MAP.put("AC9EBD8F" , "qdf" ); FILE_TYPE_MAP.put("E3828596" , "pwl" ); FILE_TYPE_MAP.put("2E7261FD" , "ram" ); } public static String getFileType (String filePaht) { String res = null ; try { FileInputStream is = new FileInputStream(filePaht); getFileType(is); } catch (FileNotFoundException e) { e.printStackTrace(); } return res; } public static String getFileType (InputStream in) { String res = null ; try { byte [] b = new byte [CHECK_BYTES_NUMBER]; in.read(b, 0 , b.length); String fileCode = bytesToHexString(b); Iterator<String> keyIter = FILE_TYPE_MAP.keySet().iterator(); while (keyIter.hasNext()){ String key = keyIter.next(); if (key.toLowerCase().startsWith(fileCode.toLowerCase()) || fileCode.toLowerCase().startsWith(key.toLowerCase())){ res = FILE_TYPE_MAP.get(key); break ; } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return res; } public static String bytesToHexString (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(); } public static int getCheckBytesNumber () { return CHECK_BYTES_NUMBER; } public static void setCheckBytesNumber (int checkBytesNumber) { CHECK_BYTES_NUMBER = checkBytesNumber; } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
常见文件头表示如下:
255044PDF 526563 EML D0CF11 PPT 4D5AEE COM E93B03 COM 4D5A90 EXE 424D3E BMP 49492A TIF 384250 PSD C5D0D3 EPS 0A0501 PCS 89504E PNG 060500 RAW 000002 TGA 60EA27 ARJ 526172 RAR 504B03 ZIP 495363 CAB 1F9D8C Z 524946 WAV 435753 SWF 3026B2 WMV 3026B2 WMA 2E524D RM 00000F MOV 000077 MOV 000001 MPA FFFB50 MP3 234558 m3u 3C2144 HTM FFFE3C XSL 3C3F78 XML 3C3F78 MSC 4C0000 LNK 495453 CHM 805343 scm D0CF11 XLS 31BE00 WRI 00FFFF MDF 4D4544 MDS 5B436C CCD 00FFFF IMG FFFFFF SUB 17A150 PCB 2A5052 ECO 526563 PPC 000100 DDB 42494C LDB 2A7665 SCH 2A2420 LIB 434841 FNT 7B5C72 RTF 7B5072 GTD 234445 PRG 000007 PJT 202020 BAS 000002 TAG 4D5A90 dll 4D5A90 OCX 4D5A50 DPL 3F5F03 HLP 4D5A90 OLB 4D5A90 IMM 4D5A90 IME 3F5F03 LHP C22020 NLS 5B5769 CPX 4D5A16 DRV 5B4144 PBK 24536F PLL 4E4553 NES 87F53E GBC 00FFFF SMD 584245 XBE 005001 XMV 000100 TTF 484802 PDG 000100 TST 414331 dwg D0CF11 max
另外还有一些重要的文件,没有固定的文件头 ,如下:
TXT 没固定文件头定义 TMP 没固定文件头定义 INI 没固定文件头定义 BIN 没固定文件头定义 DBF 没固定文件头定义 C 没没固定文件头定义 CPP 没固定文件头定义 H 没固定文件头定义 BAT 没固定文件头定义
还有一些不同的文件有相同的文件头,最典型的就是下面:
4D5A90 EXE 4D5A90 dll 4D5A90 OCX 4D5A90 OLB 4D5A90 IMM 4D5A90 IME
文件上传 当我们需要实现上传文件的时候, 为了安全起见, 我们需要判断上传文件的格式, 防止将病毒木马等有害的文件上传到服务器上.
判断文件类型的三种方式
通过文件后缀名
这个方法只要修改后缀名就可以了
通过Content-Type判断
但是Content-Type取决于文件类型, 文件类型取决于文件扩展名, 所以改变了文件扩展名就改变了Content-Type
通过文件头判断文件, 即使文件扩展名改变了文件头也不会改变
文件上传的思路: 先判断Content-Type, Content-Type符合条件的再判断文件头信息
@ResponseBody @GetMapping("validate") public Map<String, String> validate (@Validated({AllFiled.class}) UserInfo userInfo, BindingResult result) { Map<String, String> map = new HashMap<String, String>(); if (result.hasErrors()) { List<ObjectError> list = result.getAllErrors(); for (ObjectError error : list) { FieldError fieldError = (FieldError)error; String defaultMessage = fieldError.getDefaultMessage(); String field = fieldError.getField(); map.put(field, defaultMessage); } } return map; } @PostMapping(value = "file") @ResponseBody public String file (@RequestParam("username") String name, MultipartFile file) throws IOException { String fileName = file.getOriginalFilename(); String partName = file.getName(); boolean empty = file.isEmpty(); String contentType = file.getContentType(); Long size = file.getSize(); byte [] bytes = file.getBytes(); InputStream in = file.getInputStream(); String type = FileType.getFileType(in); StringBuilder builder = new StringBuilder(); File root = new File("D:/temp" ); if (!root.isDirectory()) { root.mkdirs(); } try { file.transferTo(new File(root, name)); return String.format("Upload to %s" , fileName); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "Upload Failed" ; }