From d67e1c567df9df4e4f9bb408884cc26e0cc66823 Mon Sep 17 00:00:00 2001 From: yulin Date: Sat, 31 Dec 2022 21:31:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(FileTypeUtil):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=A0=B9=E6=8D=AEfile=20magic=20number=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.重构多个Magic Number 2.根据单独类型匹配,修复某些文件跳位置对比 3.获取到文件Mime类型和后缀 4.获取文件流从28byte提升到64byte 5.添加精确匹配如docx、xlsx、pptx、doc、xls、ppt,精确匹配为8192byte(大小10k文件左右) 6.添加file magic number枚举 Closes https://github.com/dromara/hutool/issues/2821 --- .../cn/hutool/core/io/FileMagicNumber.java | 1238 +++++++++++++++++ .../java/cn/hutool/core/io/FileTypeUtil.java | 203 +-- .../main/java/cn/hutool/core/io/IoUtil.java | 28 +- .../cn/hutool/core/io/FileTypeUtilTest.java | 2 +- 4 files changed, 1363 insertions(+), 108 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java b/hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java new file mode 100644 index 000000000..ab8051391 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java @@ -0,0 +1,1238 @@ +package cn.hutool.core.io; + +import cn.hutool.core.util.ArrayUtil; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +public enum FileMagicNumber { + UNKNOWN(null, null) { + @Override + public boolean match(byte[] bytes) { + return false; + } + }, + //image start--------------------------------------------- + JPEG("image/jpeg", "jpg") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0xff) + && Objects.equals(bytes[1], (byte) 0xd8) + && Objects.equals(bytes[2], (byte) 0xff); + } + }, + JXR("image/vnd.ms-photo", "jxr") { + @Override + public boolean match(byte[] bytes) { + //file magic number https://www.iana.org/assignments/media-types/image/jxr + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0x49) + && Objects.equals(bytes[1], (byte) 0x49) + && Objects.equals(bytes[2], (byte) 0xbc); + } + }, + APNG("image/apng", "apng") { + @Override + public boolean match(byte[] bytes) { + boolean b = bytes.length > 8 + && Objects.equals(bytes[0], (byte) 0x89) + && Objects.equals(bytes[1], (byte) 0x50) + && Objects.equals(bytes[2], (byte) 0x4e) + && Objects.equals(bytes[3], (byte) 0x47) + && Objects.equals(bytes[4], (byte) 0x0d) + && Objects.equals(bytes[5], (byte) 0x0a) + && Objects.equals(bytes[6], (byte) 0x1a) + && Objects.equals(bytes[7], (byte) 0x0a); + + if (b) { + int i = 8; + while (i < bytes.length) { + try { + int dataLength = new BigInteger(1, Arrays.copyOfRange(bytes, i, i + 4)).intValue(); + i += 4; + byte[] bytes1 = Arrays.copyOfRange(bytes, i, i + 4); + String chunkType = new String(bytes1); + i += 4; + if (Objects.equals(chunkType, "IDAT") || Objects.equals(chunkType, "IEND")) { + return false; + } else if (Objects.equals(chunkType, "acTL")) { + return true; + } + i += dataLength + 4; + }catch (Exception e){ + return false; + } + } + } + return false; + } + }, + PNG("image/png", "png") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x89) + && Objects.equals(bytes[1], (byte) 0x50) + && Objects.equals(bytes[2], (byte) 0x4e) + && Objects.equals(bytes[3], (byte) 0x47); + } + }, + GIF("image/gif", "gif") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0x47) + && Objects.equals(bytes[1], (byte) 0x49) + && Objects.equals(bytes[2], (byte) 0x46); + } + }, + BMP("image/bmp", "bmp") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 1 + && Objects.equals(bytes[0], (byte) 0x42) + && Objects.equals(bytes[1], (byte) 0x4d); + } + }, + TIFF("image/tiff", "tiff") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 4) { + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0x49) + && Objects.equals(bytes[1], (byte) 0x49) + && Objects.equals(bytes[2], (byte) 0x2a) + && Objects.equals(bytes[3], (byte) 0x00); + boolean flag2 = (Objects.equals(bytes[0], (byte) 0x4d) + && Objects.equals(bytes[1], (byte) 0x4d) + && Objects.equals(bytes[2], (byte) 0x00) + && Objects.equals(bytes[3], (byte) 0x2a)); + return flag1 || flag2; + + } + }, + + DWG("image/vnd.dwg", "dwg") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 10 + && Objects.equals(bytes[0], (byte) 0x41) + && Objects.equals(bytes[1], (byte) 0x43) + && Objects.equals(bytes[2], (byte) 0x31) + && Objects.equals(bytes[3], (byte) 0x30); + } + }, + + WEBP("image/webp", "webp") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 11 + && Objects.equals(bytes[8], (byte) 0x57) + && Objects.equals(bytes[9], (byte) 0x45) + && Objects.equals(bytes[10], (byte) 0x42) + && Objects.equals(bytes[11], (byte) 0x50); + } + }, + PSD("image/vnd.adobe.photoshop", "psd") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x38) + && Objects.equals(bytes[1], (byte) 0x42) + && Objects.equals(bytes[2], (byte) 0x50) + && Objects.equals(bytes[3], (byte) 0x53); + } + }, + ICO("image/x-icon", "ico") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x00) + && Objects.equals(bytes[1], (byte) 0x00) + && Objects.equals(bytes[2], (byte) 0x01) + && Objects.equals(bytes[3], (byte) 0x00); + } + }, + XCF("image/x-xcf", "xcf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 9 + && Objects.equals(bytes[0], (byte) 0x67) + && Objects.equals(bytes[1], (byte) 0x69) + && Objects.equals(bytes[2], (byte) 0x6d) + && Objects.equals(bytes[3], (byte) 0x70) + && Objects.equals(bytes[4], (byte) 0x20) + && Objects.equals(bytes[5], (byte) 0x78) + && Objects.equals(bytes[6], (byte) 0x63) + && Objects.equals(bytes[7], (byte) 0x66) + && Objects.equals(bytes[8], (byte) 0x20) + && Objects.equals(bytes[9], (byte) 0x76); + } + }, + //image end----------------------------------------------- + + //audio start--------------------------------------------- + + WAV("audio/x-wav", "wav") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 11 + && Objects.equals(bytes[0], (byte) 0x52) + && Objects.equals(bytes[1], (byte) 0x49) + && Objects.equals(bytes[2], (byte) 0x46) + && Objects.equals(bytes[3], (byte) 0x46) + && Objects.equals(bytes[8], (byte) 0x57) + && Objects.equals(bytes[9], (byte) 0x41) + && Objects.equals(bytes[10], (byte) 0x56) + && Objects.equals(bytes[11], (byte) 0x45); + } + }, + MIDI("audio/midi", "midi") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x4d) + && Objects.equals(bytes[1], (byte) 0x54) + && Objects.equals(bytes[2], (byte) 0x68) + && Objects.equals(bytes[3], (byte) 0x64); + } + }, + MP3("audio/mpeg", "mp3") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 2) { + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0x49) && Objects.equals(bytes[1], (byte) 0x44) && Objects.equals(bytes[2], (byte) 0x33); + boolean flag2 = Objects.equals(bytes[0], (byte) 0xFF) && Objects.equals(bytes[1], (byte) 0xFB); + boolean flag3 = Objects.equals(bytes[0], (byte) 0xFF) && Objects.equals(bytes[1], (byte) 0xF3); + boolean flag4 = Objects.equals(bytes[0], (byte) 0xFF) && Objects.equals(bytes[1], (byte) 0xF2); + return flag1 || flag2 || flag3 || flag4; + } + }, + OGG("audio/ogg", "ogg") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x4f) + && Objects.equals(bytes[1], (byte) 0x67) + && Objects.equals(bytes[2], (byte) 0x67) + && Objects.equals(bytes[3], (byte) 0x53); + } + }, + FLAC("audio/x-flac", "flac") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x66) + && Objects.equals(bytes[1], (byte) 0x4c) + && Objects.equals(bytes[2], (byte) 0x61) + && Objects.equals(bytes[3], (byte) 0x43); + } + }, + M4A("audio/mp4", "m4a") { + @Override + public boolean match(byte[] bytes) { + return (bytes.length > 10 + && Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x4d) + && Objects.equals(bytes[9], (byte) 0x34) + && Objects.equals(bytes[10], (byte) 0x41)) + || (Objects.equals(bytes[0], (byte) 0x4d) + && Objects.equals(bytes[1], (byte) 0x34) + && Objects.equals(bytes[2], (byte) 0x41) + && Objects.equals(bytes[3], (byte) 0x20)); + } + }, + AAC("audio/aac", "aac") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length <1) { + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0xFF) && Objects.equals(bytes[1], (byte) 0xF1); + boolean flag2 = Objects.equals(bytes[0], (byte) 0xFF) && Objects.equals(bytes[1], (byte) 0xF9); + return flag1 || flag2; + } + }, + AMR("audio/amr", "amr") { + @Override + public boolean match(byte[] bytes) { + //single-channel + if (bytes.length < 11){ + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0x23) + && Objects.equals(bytes[1], (byte) 0x21) + && Objects.equals(bytes[2], (byte) 0x41) + && Objects.equals(bytes[3], (byte) 0x4d) + && Objects.equals(bytes[4], (byte) 0x52) + && Objects.equals(bytes[5], (byte) 0x0A); + //multi-channel: + boolean flag2 = Objects.equals(bytes[0], (byte) 0x23) + && Objects.equals(bytes[1], (byte) 0x21) + && Objects.equals(bytes[2], (byte) 0x41) + && Objects.equals(bytes[3], (byte) 0x4d) + && Objects.equals(bytes[4], (byte) 0x52) + && Objects.equals(bytes[5], (byte) 0x5F) + && Objects.equals(bytes[6], (byte) 0x4d) + && Objects.equals(bytes[7], (byte) 0x43) + && Objects.equals(bytes[8], (byte) 0x31) + && Objects.equals(bytes[9], (byte) 0x2e) + && Objects.equals(bytes[10], (byte) 0x30) + && Objects.equals(bytes[11], (byte) 0x0a); + return flag1 || flag2; + } + }, + AC3("audio/ac3", "ac3") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0x0b) + && Objects.equals(bytes[1], (byte) 0x77); + } + }, + AIFF("audio/x-aiff", "aiff") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 11 + && Objects.equals(bytes[0], (byte) 0x46) + && Objects.equals(bytes[1], (byte) 0x4f) + && Objects.equals(bytes[2], (byte) 0x52) + && Objects.equals(bytes[3], (byte) 0x4d) + && Objects.equals(bytes[8], (byte) 0x41) + && Objects.equals(bytes[9], (byte) 0x49) + && Objects.equals(bytes[10], (byte) 0x46) + && Objects.equals(bytes[11], (byte) 0x46); + } + }, + //audio end----------------------------------------------- + + //font start--------------------------------------------- + // The existing registration "application/font-woff" is deprecated in favor of "font/woff". + WOFF("font/woff", "woff") { + @Override + public boolean match(byte[] bytes) { + boolean flag1 = Objects.equals(bytes[0], (byte) 0x77) + && Objects.equals(bytes[1], (byte) 0x4f) + && Objects.equals(bytes[2], (byte) 0x46) + && Objects.equals(bytes[3], (byte) 0x46); + boolean flag2 = Objects.equals(bytes[4], (byte) 0x00) + && Objects.equals(bytes[5], (byte) 0x01) + && Objects.equals(bytes[6], (byte) 0x00) + && Objects.equals(bytes[7], (byte) 0x00); + boolean flag3 = Objects.equals(bytes[4], (byte) 0x4f) + && Objects.equals(bytes[5], (byte) 0x54) + && Objects.equals(bytes[6], (byte) 0x54) + && Objects.equals(bytes[7], (byte) 0x4f); + boolean flag4 = Objects.equals(bytes[4], (byte) 0x74) + && Objects.equals(bytes[5], (byte) 0x72) + && Objects.equals(bytes[6], (byte) 0x75) + && Objects.equals(bytes[7], (byte) 0x65); + return bytes.length > 7 + && (flag1 && (flag2 || flag3 || flag4)); + } + }, + WOFF2("font/woff2", "woff2") { + @Override + public boolean match(byte[] bytes) { + boolean flag1 = Objects.equals(bytes[0], (byte) 0x77) + && Objects.equals(bytes[1], (byte) 0x4f) + && Objects.equals(bytes[2], (byte) 0x46) + && Objects.equals(bytes[3], (byte) 0x32); + boolean flag2 = Objects.equals(bytes[4], (byte) 0x00) + && Objects.equals(bytes[5], (byte) 0x01) + && Objects.equals(bytes[6], (byte) 0x00) + && Objects.equals(bytes[7], (byte) 0x00); + boolean flag3 = Objects.equals(bytes[4], (byte) 0x4f) + && Objects.equals(bytes[5], (byte) 0x54) + && Objects.equals(bytes[6], (byte) 0x54) + && Objects.equals(bytes[7], (byte) 0x4f); + boolean flag4 = Objects.equals(bytes[4], (byte) 0x74) + && Objects.equals(bytes[5], (byte) 0x72) + && Objects.equals(bytes[6], (byte) 0x75) + && Objects.equals(bytes[7], (byte) 0x65); + return bytes.length > 7 + && (flag1 && (flag2 || flag3 || flag4)); + } + }, + TTF("font/ttf", "ttf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0x00) + && Objects.equals(bytes[1], (byte) 0x01) + && Objects.equals(bytes[2], (byte) 0x00) + && Objects.equals(bytes[3], (byte) 0x00) + && Objects.equals(bytes[4], (byte) 0x00); + } + }, + OTF("font/otf", "otf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0x4f) + && Objects.equals(bytes[1], (byte) 0x54) + && Objects.equals(bytes[2], (byte) 0x54) + && Objects.equals(bytes[3], (byte) 0x4f) + && Objects.equals(bytes[4], (byte) 0x00); + } + }, + + //font end----------------------------------------------- + + //archive start----------------------------------------- + EPUB("application/epub+zip", "epub") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 58 + && Objects.equals(bytes[0], (byte) 0x50) && Objects.equals(bytes[1], (byte) 0x4b) + && Objects.equals(bytes[2], (byte) 0x03) && Objects.equals(bytes[3], (byte) 0x04) + && Objects.equals(bytes[30], (byte) 0x6d) && Objects.equals(bytes[31], (byte) 0x69) + && Objects.equals(bytes[32], (byte) 0x6d) && Objects.equals(bytes[33], (byte) 0x65) + && Objects.equals(bytes[34], (byte) 0x74) && Objects.equals(bytes[35], (byte) 0x79) + && Objects.equals(bytes[36], (byte) 0x70) && Objects.equals(bytes[37], (byte) 0x65) + && Objects.equals(bytes[38], (byte) 0x61) && Objects.equals(bytes[39], (byte) 0x70) + && Objects.equals(bytes[40], (byte) 0x70) && Objects.equals(bytes[41], (byte) 0x6c) + && Objects.equals(bytes[42], (byte) 0x69) && Objects.equals(bytes[43], (byte) 0x63) + && Objects.equals(bytes[44], (byte) 0x61) && Objects.equals(bytes[45], (byte) 0x74) + && Objects.equals(bytes[46], (byte) 0x69) && Objects.equals(bytes[47], (byte) 0x6f) + && Objects.equals(bytes[48], (byte) 0x6e) && Objects.equals(bytes[49], (byte) 0x2f) + && Objects.equals(bytes[50], (byte) 0x65) && Objects.equals(bytes[51], (byte) 0x70) + && Objects.equals(bytes[52], (byte) 0x75) && Objects.equals(bytes[53], (byte) 0x62) + && Objects.equals(bytes[54], (byte) 0x2b) && Objects.equals(bytes[55], (byte) 0x7a) + && Objects.equals(bytes[56], (byte) 0x69) && Objects.equals(bytes[57], (byte) 0x70); + } + }, + ZIP("application/zip", "zip") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length <4) { + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0x50) && Objects.equals(bytes[1], (byte) 0x4b); + boolean flag2 = Objects.equals(bytes[2], (byte) 0x03) || Objects.equals(bytes[2], (byte) 0x05) || Objects.equals(bytes[2], (byte) 0x07); + boolean flag3 = Objects.equals(bytes[3], (byte) 0x04) || Objects.equals(bytes[3], (byte) 0x06) || Objects.equals(bytes[3], (byte) 0x08); + return flag1 && flag2 && flag3; + } + }, + TAR("application/x-tar", "tar") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 261 + && Objects.equals(bytes[257], (byte) 0x75) + && Objects.equals(bytes[258], (byte) 0x73) + && Objects.equals(bytes[259], (byte) 0x74) + && Objects.equals(bytes[260], (byte) 0x61) + && Objects.equals(bytes[261], (byte) 0x72); + } + }, + RAR("application/x-rar-compressed", "rar") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 6 + && Objects.equals(bytes[0], (byte) 0x52) + && Objects.equals(bytes[1], (byte) 0x61) + && Objects.equals(bytes[2], (byte) 0x72) + && Objects.equals(bytes[3], (byte) 0x21) + && Objects.equals(bytes[4], (byte) 0x1a) + && Objects.equals(bytes[5], (byte) 0x07) + && (Objects.equals(bytes[6], (byte) 0x00) || Objects.equals(bytes[6], (byte) 0x01)); + } + }, + GZ("application/gzip", "gz") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0x1f) + && Objects.equals(bytes[1], (byte) 0x8b) + && Objects.equals(bytes[2], (byte) 0x08); + } + }, + BZ2("application/x-bzip2", "bz2") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && Objects.equals(bytes[0], (byte) 0x42) + && Objects.equals(bytes[1], (byte) 0x5a) + && Objects.equals(bytes[2], (byte) 0x68); + } + }, + SevenZ("application/x-7z-compressed", "7z") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 6 + && Objects.equals(bytes[0], (byte) 0x37) + && Objects.equals(bytes[1], (byte) 0x7a) + && Objects.equals(bytes[2], (byte) 0xbc) + && Objects.equals(bytes[3], (byte) 0xaf) + && Objects.equals(bytes[4], (byte) 0x27) + && Objects.equals(bytes[5], (byte) 0x1c) + && Objects.equals(bytes[6], (byte) 0x00); + } + }, + PDF("application/pdf", "pdf") { + @Override + public boolean match(byte[] bytes) { + //去除bom头并且跳过三个字节 + if (bytes.length > 3 && Objects.equals(bytes[0], (byte) 0xEF) + && Objects.equals(bytes[1], (byte) 0xBB) && Objects.equals(bytes[2], (byte) 0xBF)) { + bytes = Arrays.copyOfRange(bytes, 3, bytes.length); + } + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x25) + && Objects.equals(bytes[1], (byte) 0x50) + && Objects.equals(bytes[2], (byte) 0x44) + && Objects.equals(bytes[3], (byte) 0x46); + } + }, + EXE("application/x-msdownload", "exe") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 1 + && Objects.equals(bytes[0], (byte) 0x4d) + && Objects.equals(bytes[1], (byte) 0x5a); + } + }, + SWF("application/x-shockwave-flash", "swf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 2 + && (Objects.equals(bytes[0],0x43) || Objects.equals(bytes[0], (byte) 0x46)) + && Objects.equals(bytes[1], (byte) 0x57) + && Objects.equals(bytes[2], (byte) 0x53); + } + }, + RTF("application/rtf", "rtf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0x7b) + && Objects.equals(bytes[1], (byte) 0x5c) + && Objects.equals(bytes[2], (byte) 0x72) + && Objects.equals(bytes[3], (byte) 0x74) + && Objects.equals(bytes[4], (byte) 0x66); + } + }, + NES("application/x-nintendo-nes-rom", "nes") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x4e) + && Objects.equals(bytes[1], (byte) 0x45) + && Objects.equals(bytes[2], (byte) 0x53) + && Objects.equals(bytes[3], (byte) 0x1a); + } + }, + CRX("application/x-google-chrome-extension", "crx") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x43) + && Objects.equals(bytes[1], (byte) 0x72) + && Objects.equals(bytes[2], (byte) 0x32) + && Objects.equals(bytes[3], (byte) 0x34); + } + }, + CAB("application/vnd.ms-cab-compressed", "cab") { + @Override + public boolean match(byte[] bytes) { + if ( bytes.length < 4){ + return false; + } + boolean flag1 = Objects.equals(bytes[0], (byte) 0x4d) && Objects.equals(bytes[1], (byte) 0x53) + && Objects.equals(bytes[2], (byte) 0x43) && Objects.equals(bytes[3], (byte) 0x46); + boolean flag2 = Objects.equals(bytes[0], (byte) 0x49) && Objects.equals(bytes[1], (byte) 0x53) + && Objects.equals(bytes[2], (byte) 0x63) && Objects.equals(bytes[3], (byte) 0x28); + return flag1||flag2; + } + }, + PS("application/postscript", "ps") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 1 + && Objects.equals(bytes[0], (byte) 0x25) + && Objects.equals(bytes[1], (byte) 0x21); + } + }, + XZ("application/x-xz", "xz") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 5 + && Objects.equals(bytes[0], (byte) 0xFD) + && Objects.equals(bytes[1], (byte) 0x37) + && Objects.equals(bytes[2], (byte) 0x7A) + && Objects.equals(bytes[3], (byte) 0x58) + && Objects.equals(bytes[4], (byte) 0x5A) + && Objects.equals(bytes[5], (byte) 0x00); + } + }, + SQLITE("application/x-sqlite3", "sqlite") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 15 + && Objects.equals(bytes[0], (byte) 0x53) && Objects.equals(bytes[1], (byte) 0x51) + && Objects.equals(bytes[2], (byte) 0x4c) && Objects.equals(bytes[3], (byte) 0x69) + && Objects.equals(bytes[4], (byte) 0x74) && Objects.equals(bytes[5], (byte) 0x65) + && Objects.equals(bytes[6], (byte) 0x20) && Objects.equals(bytes[7], (byte) 0x66) + && Objects.equals(bytes[8], (byte) 0x6f) && Objects.equals(bytes[9], (byte) 0x72) + && Objects.equals(bytes[10], (byte) 0x6d) && Objects.equals(bytes[11], (byte) 0x61) + && Objects.equals(bytes[12], (byte) 0x74) && Objects.equals(bytes[13], (byte) 0x20) + && Objects.equals(bytes[14], (byte) 0x33) && Objects.equals(bytes[15], (byte) 0x00); + } + }, + DEB("application/x-deb", "deb") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 20 + && Objects.equals(bytes[0], (byte) 0x21) && Objects.equals(bytes[1], (byte) 0x3c) + && Objects.equals(bytes[2], (byte) 0x61) && Objects.equals(bytes[3], (byte) 0x72) + && Objects.equals(bytes[4], (byte) 0x63) && Objects.equals(bytes[5], (byte) 0x68) + && Objects.equals(bytes[6], (byte) 0x3e) && Objects.equals(bytes[7], (byte) 0x0a) + && Objects.equals(bytes[8], (byte) 0x64) && Objects.equals(bytes[9], (byte) 0x65) + && Objects.equals(bytes[10], (byte) 0x62) && Objects.equals(bytes[11], (byte) 0x69) + && Objects.equals(bytes[12], (byte) 0x61) && Objects.equals(bytes[13], (byte) 0x6e) + && Objects.equals(bytes[14], (byte) 0x2d) && Objects.equals(bytes[15], (byte) 0x62) + && Objects.equals(bytes[16], (byte) 0x69) && Objects.equals(bytes[17], (byte) 0x6e) + && Objects.equals(bytes[18], (byte) 0x61) && Objects.equals(bytes[19], (byte) 0x72) + && Objects.equals(bytes[20], (byte) 0x79); + } + }, + AR("application/x-unix-archive", "ar") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 6 + && Objects.equals(bytes[0], (byte) 0x21) + && Objects.equals(bytes[1], (byte) 0x3c) + && Objects.equals(bytes[2], (byte) 0x61) + && Objects.equals(bytes[3], (byte) 0x72) + && Objects.equals(bytes[4], (byte) 0x63) + && Objects.equals(bytes[5], (byte) 0x68) + && Objects.equals(bytes[6], (byte) 0x3e); + } + }, + LZOP("application/x-lzop", "lzo") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 7 + && Objects.equals(bytes[0], (byte) 0x89) + && Objects.equals(bytes[1], (byte) 0x4c) + && Objects.equals(bytes[2], (byte) 0x5a) + && Objects.equals(bytes[3], (byte) 0x4f) + && Objects.equals(bytes[4], (byte) 0x00) + && Objects.equals(bytes[5], (byte) 0x0d) + && Objects.equals(bytes[6], (byte) 0x0a) + && Objects.equals(bytes[7], (byte) 0x1a); + } + }, + LZ("application/x-lzip", "lz") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x4c) + && Objects.equals(bytes[1], (byte) 0x5a) + && Objects.equals(bytes[2], (byte) 0x49) + && Objects.equals(bytes[3], (byte) 0x50); + } + }, + ELF("application/x-executable", "elf") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 52 + && Objects.equals(bytes[0], (byte) 0x7f) + && Objects.equals(bytes[1], (byte) 0x45) + && Objects.equals(bytes[2], (byte) 0x4c) + && Objects.equals(bytes[3], (byte) 0x46); + } + }, + LZ4("application/x-lz4", "lz4") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0x04) + && Objects.equals(bytes[1], (byte) 0x22) + && Objects.equals(bytes[2], (byte) 0x4d) + && Objects.equals(bytes[3], (byte) 0x18); + } + }, + //https://github.com/madler/brotli/blob/master/br-format-v3.txt,brotli 没有固定的file magic number,所以此处只是参考 + BR("application/x-brotli", "br") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0xce) + && Objects.equals(bytes[1], (byte) 0xb2) + && Objects.equals(bytes[2], (byte) 0xcf) + && Objects.equals(bytes[3], (byte) 0x81); + } + }, + DCM("application/x-dicom", "dcm") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 128 + && Objects.equals(bytes[128], (byte) 0x44) + && Objects.equals(bytes[129], (byte) 0x49) + && Objects.equals(bytes[130], (byte) 0x43) + && Objects.equals(bytes[131], (byte) 0x4d); + } + }, + RPM("application/x-rpm", "rpm") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0xed) + && Objects.equals(bytes[1], (byte) 0xab) + && Objects.equals(bytes[2], (byte) 0xee) + && Objects.equals(bytes[3], (byte) 0xdb); + } + }, + ZSTD("application/x-zstd", "zst") { + @Override + public boolean match(byte[] bytes) { + int length = bytes.length; + if (length < 5) { + return false; + } + byte[] buf1 = new byte[]{(byte)0x22,(byte)0x23,(byte)0x24,(byte)0x25,(byte)0x26,(byte)0x27,(byte) 0x28}; + boolean flag1 = ArrayUtil.contains(buf1, bytes[0]) + && Objects.equals(bytes[1], (byte) 0xb5) + && Objects.equals(bytes[2], (byte) 0x2f) + && Objects.equals(bytes[3], (byte) 0xfd); + if (flag1) { + return true; + } + if ((bytes[0] & 0xF0) == 0x50) { + return bytes[1] == 0x2A && bytes[2] == 0x4D && bytes[3] == 0x18; + } + return false; + } + }, + //archive end------------------------------------------------------------ + //video start------------------------------------------------------------ + MP4("video/mp4", "mp4") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 13) { + return false; + } + boolean flag1 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x4d) + && Objects.equals(bytes[9], (byte) 0x53) + && Objects.equals(bytes[10], (byte) 0x4e) + && Objects.equals(bytes[11], (byte) 0x56); + boolean flag2 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x69) + && Objects.equals(bytes[9], (byte) 0x73) + && Objects.equals(bytes[10], (byte) 0x6f) + && Objects.equals(bytes[11], (byte) 0x6d); + return flag1||flag2; + } + }, + AVI("video/x-msvideo", "avi") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 11 + && Objects.equals(bytes[0], (byte) 0x52) + && Objects.equals(bytes[1], (byte) 0x49) + && Objects.equals(bytes[2], (byte) 0x46) + && Objects.equals(bytes[3], (byte) 0x46) + && Objects.equals(bytes[8], (byte) 0x41) + && Objects.equals(bytes[9], (byte) 0x56) + && Objects.equals(bytes[10], (byte) 0x49) + && Objects.equals(bytes[11], (byte) 0x20); + } + }, + WMV("video/x-ms-wmv", "wmv") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 9 + && Objects.equals(bytes[0], (byte) 0x30) + && Objects.equals(bytes[1], (byte) 0x26) + && Objects.equals(bytes[2], (byte) 0xb2) + && Objects.equals(bytes[3], (byte) 0x75) + && Objects.equals(bytes[4], (byte) 0x8e) + && Objects.equals(bytes[5], (byte) 0x66) + && Objects.equals(bytes[6], (byte) 0xcf) + && Objects.equals(bytes[7], (byte) 0x11) + && Objects.equals(bytes[8], (byte) 0xa6) + && Objects.equals(bytes[9], (byte) 0xd9); + } + }, + M4V("video/x-m4v", "m4v") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 12) { + return false; + } + boolean flag1 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x4d) + && Objects.equals(bytes[9], (byte) 0x34) + && Objects.equals(bytes[10], (byte) 0x56) + && Objects.equals(bytes[11], (byte) 0x20); + boolean flag2 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x6d) + && Objects.equals(bytes[9], (byte) 0x70) + && Objects.equals(bytes[10], (byte) 0x34) + && Objects.equals(bytes[11], (byte) 0x32); + return flag1||flag2; + } + }, + FLV("video/x-flv", "flv") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x46) + && Objects.equals(bytes[1], (byte) 0x4c) + && Objects.equals(bytes[2], (byte) 0x56) + && Objects.equals(bytes[3], (byte) 0x01); + } + }, + MKV("video/x-matroska", "mkv") { + @Override + public boolean match(byte[] bytes) { + //0x42 0x82 0x88 0x6d 0x61 0x74 0x72 0x6f 0x73 0x6b 0x61 + boolean flag1 = bytes.length > 11 + && Objects.equals(bytes[0], (byte) 0x1a) + && Objects.equals(bytes[1], (byte) 0x45) + && Objects.equals(bytes[2], (byte) 0xdf) + && Objects.equals(bytes[3], (byte) 0xa3); + + if (flag1) { + //此处需要判断是否是'\x42\x82\x88matroska',算法类似kmp判断 + byte[] bytes1 = {(byte) 0x42, (byte) 0x82, (byte) 0x88, (byte) 0x6d, (byte) 0x61, (byte) 0x74, (byte) 0x72, (byte) 0x6f, (byte) 0x73, (byte) 0x6b, (byte) 0x61}; + int index = FileMagicNumber.indexOf(bytes, bytes1); + return index > 0; + } + return false; + } + }, + + WEBM("video/webm", "webm") { + @Override + public boolean match(byte[] bytes) { + boolean flag1 = bytes.length > 8 + && Objects.equals(bytes[0], (byte) 0x1a) + && Objects.equals(bytes[1], (byte) 0x45) + && Objects.equals(bytes[2], (byte) 0xdf) + && Objects.equals(bytes[3], (byte) 0xa3); + if (flag1) { + //此处需要判断是否是'\x42\x82\x88webm',算法类似kmp判断 + byte[] bytes1 = {(byte) 0x42, (byte) 0x82, (byte) 0x88, (byte) 0x77, (byte) 0x65, (byte) 0x62, (byte) 0x6d}; + int index = FileMagicNumber.indexOf(bytes, bytes1); + return index > 0; + } + return false; + } + }, + //此文件签名非常复杂,只判断常见的几种 + MOV("video/quicktime", "mov") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 12) { + return false; + } + boolean flag1 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x71) + && Objects.equals(bytes[9], (byte) 0x74) + && Objects.equals(bytes[10], (byte) 0x20) + && Objects.equals(bytes[11], (byte) 0x20); + boolean flag2 = Objects.equals(bytes[4], (byte) 0x6D) + && Objects.equals(bytes[5], (byte) 0x6F) + && Objects.equals(bytes[6], (byte) 0x6F) + && Objects.equals(bytes[7], (byte) 0x76); + boolean flag3 = Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x72) + && Objects.equals(bytes[6], (byte) 0x65) + && Objects.equals(bytes[7], (byte) 0x65); + boolean flag4 = Objects.equals(bytes[4], (byte) 0x6D) + && Objects.equals(bytes[5], (byte) 0x64) + && Objects.equals(bytes[6], (byte) 0x61) + && Objects.equals(bytes[7], (byte) 0x74); + boolean flag5 = Objects.equals(bytes[4], (byte) 0x77) + && Objects.equals(bytes[5], (byte) 0x69) + && Objects.equals(bytes[6], (byte) 0x64) + && Objects.equals(bytes[7], (byte) 0x65); + boolean flag6 = Objects.equals(bytes[4], (byte) 0x70) + && Objects.equals(bytes[5], (byte) 0x6E) + && Objects.equals(bytes[6], (byte) 0x6F) + && Objects.equals(bytes[7], (byte) 0x74); + boolean flag7 = Objects.equals(bytes[4], (byte) 0x73) + && Objects.equals(bytes[5], (byte) 0x6B) + && Objects.equals(bytes[6], (byte) 0x69) + && Objects.equals(bytes[7], (byte) 0x70); + return flag1 || flag2 || flag3 || flag4 || flag5 || flag6 || flag7; + } + }, + MPEG("video/mpeg", "mpg") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 3 + && Objects.equals(bytes[0], (byte) 0x00) + && Objects.equals(bytes[1], (byte) 0x00) + && Objects.equals(bytes[2], (byte) 0x01) + && (bytes[3] >= (byte) 0xb0 && bytes[3] <= (byte) 0xbf); + } + }, + RMVB("video/vnd.rn-realvideo", "rmvb") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 4 + && Objects.equals(bytes[0], (byte) 0x2E) + && Objects.equals(bytes[1], (byte) 0x52) + && Objects.equals(bytes[2], (byte) 0x4D) + && Objects.equals(bytes[3], (byte) 0x46); + } + }, + M3GP("video/3gpp", "3gp") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 10 + && Objects.equals(bytes[4], (byte) 0x66) + && Objects.equals(bytes[5], (byte) 0x74) + && Objects.equals(bytes[6], (byte) 0x79) + && Objects.equals(bytes[7], (byte) 0x70) + && Objects.equals(bytes[8], (byte) 0x33) + && Objects.equals(bytes[9], (byte) 0x67) + && Objects.equals(bytes[10], (byte) 0x70); + } + }, + //video end --------------------------------------------------------------- + //document start ---------------------------------------------------------- + DOC("application/msword", "doc") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xd0,(byte) 0xcf,(byte) 0x11,(byte) 0xe0,(byte) 0xa1,(byte) 0xb1,(byte) 0x1a,(byte) 0xe1}; + boolean flag1 = bytes.length > 515 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 8), byte1); + if (flag1) { + byte[] byte2 = new byte[]{(byte) 0xec,(byte) 0xa5,(byte) 0xc1,(byte) 0x00}; + boolean flag2 = Arrays.equals(Arrays.copyOfRange(bytes, 512, 516), byte2); + byte[] byte3 = new byte[]{(byte) 0x00,(byte) 0x0a,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x4d,(byte) 0x53,(byte) 0x57,(byte) 0x6f,(byte) 0x72,(byte) 0x64 + ,(byte) 0x44,(byte) 0x6f,(byte) 0x63,(byte) 0x00,(byte) 0x10,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x57,(byte) 0x6f,(byte) 0x72, (byte) 0x64, + (byte) 0x2e,(byte) 0x44,(byte) 0x6f,(byte) 0x63,(byte) 0x75, (byte) 0x6d,(byte) 0x65,(byte) 0x6e,(byte) 0x74,(byte) 0x2e,(byte) 0x38,(byte) 0x00, + (byte) 0xf4,(byte) 0x39,(byte) 0xb2,(byte) 0x71}; + byte[] range = Arrays.copyOfRange(bytes, 2075, 2142); + boolean flag3 = bytes.length > 2142 && FileMagicNumber.indexOf(range, byte3) > 0; + return flag2 || flag3; + } + return false; + } + }, + + XLS("application/vnd.ms-excel", "xls") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xd0,(byte) 0xcf,(byte) 0x11,(byte) 0xe0,(byte) 0xa1,(byte) 0xb1,(byte) 0x1a,(byte) 0xe1}; + boolean flag1 = bytes.length > 520 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 8), byte1); + if (flag1) { + byte[] byte2 = new byte[]{(byte) 0xfd,(byte) 0xff,(byte) 0xff,(byte) 0xff}; + boolean flag2 = Arrays.equals(Arrays.copyOfRange(bytes, 512, 516), byte2) && (bytes[518] == 0x00 || bytes[518] == 0x02); + byte[] byte3 = new byte[]{(byte) 0x09,(byte) 0x08,(byte) 0x10,(byte) 0x00,(byte) 0x00,(byte) 0x06,(byte) 0x05,(byte) 0x00}; + boolean flag3 = Arrays.equals(Arrays.copyOfRange(bytes, 512, 520), byte3); + byte[] byte4 = new byte[]{(byte) 0xe2,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x5c,(byte) 0x00,(byte) 0x70,(byte) 0x00,(byte) 0x04,(byte) 0x00,(byte) 0x00,(byte) 0x43,(byte) 0x61,(byte) 0x6c,(byte) 0x63}; + boolean flag4 = bytes.length > 2095 && Arrays.equals(Arrays.copyOfRange(bytes, 1568, 2095), byte4); + return flag2 || flag3 || flag4; + } + return false; + } + + }, + PPT("application/vnd.ms-powerpoint", "ppt") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xd0,(byte) 0xcf,(byte) 0x11,(byte) 0xe0,(byte) 0xa1,(byte) 0xb1,(byte) 0x1a,(byte) 0xe1}; + boolean flag1 = bytes.length > 524 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 8), byte1); + if (flag1) { + byte[] byte2 = new byte[]{(byte) 0xa0,(byte) 0x46,(byte) 0x1d,(byte) 0xf0}; + byte[] byteRange = Arrays.copyOfRange(bytes, 512, 516); + boolean flag2 = Arrays.equals(byteRange, byte2); + byte[] byte3 = new byte[]{(byte) 0x00,(byte) 0x6e,(byte) 0x1e,(byte) 0xf0}; + boolean flag3 = Arrays.equals(byteRange, byte3); + byte[] byte4 = new byte[]{(byte) 0x0f,(byte) 0x00,(byte) 0xe8,(byte) 0x03}; + boolean flag4 = Arrays.equals(byteRange, byte4); + byte[] byte5 = new byte[]{(byte) 0xfd,(byte) 0xff,(byte) 0xff,(byte) 0xff}; + boolean flag5 = Arrays.equals(byteRange, byte5) && bytes[522] == 0x00 && bytes[523] == 0x00; + byte[] byte6 = new byte[]{(byte) 0x00,(byte) 0xb9,(byte) 0x29,(byte) 0xe8,(byte) 0x11,(byte) 0x00,(byte) 0x00,(byte) 0x00, + (byte) 0x4d,(byte) 0x53,(byte) 0x20,(byte) 0x50,(byte) 0x6f,(byte) 0x77,(byte) 0x65,(byte) 0x72,(byte) 0x50,(byte) + 0x6f,(byte) 0x69,(byte) 0x6e,(byte) 0x74,(byte) 0x20,(byte) 0x39,(byte) 0x37}; + boolean flag6 = bytes.length > 2096 && Arrays.equals(Arrays.copyOfRange(bytes, 2072, 2096), byte6); + return flag2 || flag3 || flag4 || flag5 || flag6; + } + return false; + } + } + , + DOCX("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx") { + @Override + public boolean match(byte[] bytes) { + return Objects.equals(FileMagicNumber.matchDocument(bytes), DOCX); + } + }, + PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx") { + @Override + public boolean match(byte[] bytes) { + return Objects.equals(FileMagicNumber.matchDocument(bytes), PPTX); + } + }, + XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx") { + @Override + public boolean match(byte[] bytes) { + return Objects.equals(FileMagicNumber.matchDocument(bytes), XLSX); + } + }, + + //document end ------------------------------------------------------------ + //other start ------------------------------------------------------------- + WASM("application/wasm", "wasm") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 7 + && Objects.equals(bytes[0], (byte) 0x00) + && Objects.equals(bytes[1], (byte) 0x61) + && Objects.equals(bytes[2], (byte) 0x73) + && Objects.equals(bytes[3], (byte) 0x6D) + && Objects.equals(bytes[4], (byte) 0x01) + && Objects.equals(bytes[5], (byte) 0x00) + && Objects.equals(bytes[6], (byte) 0x00) + && Objects.equals(bytes[7], (byte) 0x00); + } + }, + // https://source.android.com/devices/tech/dalvik/dex-format#dex-file-magic + DEX("application/vnd.android.dex", "dex") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 36 + && Objects.equals(bytes[0], (byte) 0x64) + && Objects.equals(bytes[1], (byte) 0x65) + && Objects.equals(bytes[2], (byte) 0x78) + && Objects.equals(bytes[3], (byte) 0x0A) + && Objects.equals(bytes[36],(byte) 0x70); + } + }, + DEY("application/vnd.android.dey", "dey") { + @Override + public boolean match(byte[] bytes) { + return bytes.length > 100 + && Objects.equals(bytes[0], (byte) 0x64) + && Objects.equals(bytes[1], (byte) 0x65) + && Objects.equals(bytes[2], (byte) 0x79) + && Objects.equals(bytes[3], (byte) 0x0A)&& + DEX.match(Arrays.copyOfRange(bytes, 40, 100)); + } + }, + EML("message/rfc822", "eml") { + @Override + public boolean match(byte[] bytes) { + if (bytes.length < 8) { + return false; + } + byte[] byte1 = new byte[]{(byte) 0x46, (byte) 0x72, (byte) 0x6F, (byte) 0x6D, (byte) 0x20, (byte) 0x20, (byte) 0x20}; + byte[] byte2 = new byte[]{(byte) 0x46, (byte) 0x72, (byte) 0x6F, (byte) 0x6D, (byte) 0x20, (byte) 0x3F, (byte) 0x3F, (byte) 0x3F}; + byte[] byte3 = new byte[]{(byte) 0x46, (byte) 0x72, (byte) 0x6F, (byte) 0x6D, (byte) 0x3A, (byte) 0x20}; + byte[] byte4 = new byte[]{(byte) 0x52, (byte) 0x65, (byte) 0x74, (byte) 0x75, (byte) 0x72, (byte) 0x6E, (byte) 0x2D, (byte) 0x50, (byte) 0x61, (byte) 0x74, (byte) 0x68, (byte) 0x3A, (byte) 0x20}; + return Arrays.equals(Arrays.copyOfRange(bytes, 0, 7), byte1) + || Arrays.equals(Arrays.copyOfRange(bytes, 0, 8), byte2) + || Arrays.equals(Arrays.copyOfRange(bytes, 0, 6), byte3) + || bytes.length>13 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 13), byte4); + } + }, + MDB("application/vnd.ms-access", "mdb") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x6E, (byte) 0x64, + (byte) 0x61, (byte) 0x72, (byte) 0x64, (byte) 0x20, (byte) 0x4A, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x44, (byte) 0x42}; + return bytes.length > 18 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 18), byte1); + } + }, + //CHM 49 54 53 46 + CHM("application/vnd.ms-htmlhelp", "chm") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0x49, (byte) 0x54, (byte) 0x53, (byte) 0x46}; + return bytes.length > 4 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 4), byte1); + } + }, + //class CA FE BA BE + CLASS("application/java-vm", "class") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}; + return bytes.length > 4 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 4), byte1); + } + }, + //torrent 64 38 3A 61 6E 6E 6F 75 6E 63 65 + TORRENT("application/x-bittorrent", "torrent") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0x64, (byte) 0x38, (byte) 0x3A, (byte) 0x61, (byte) 0x6E, (byte) 0x6E, (byte) 0x6F, (byte) 0x75, (byte) 0x6E, (byte) 0x63, (byte) 0x65}; + return bytes.length > 11 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 11), byte1); + } + }, + WPD("application/vnd.wordperfect", "wpd") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xFF, (byte) 0x57, (byte) 0x50, (byte) 0x43}; + return bytes.length > 4 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 4), byte1); + } + }, + DBX("", "dbx") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0xCF, (byte) 0xAD, (byte) 0x12, (byte) 0xFE}; + return bytes.length > 4 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 4), byte1); + } + }, + PST("application/vnd.ms-outlook-pst", "pst") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0x21, (byte) 0x42, (byte) 0x44, (byte) 0x4E}; + return bytes.length > 4 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 4), byte1); + } + }, + RAM("audio/x-pn-realaudio", "ram") { + @Override + public boolean match(byte[] bytes) { + byte[] byte1 = new byte[]{(byte) 0x2E, (byte) 0x72, (byte) 0x61, (byte) 0xFD, (byte) 0x00}; + return bytes.length > 5 && Arrays.equals(Arrays.copyOfRange(bytes, 0, 5), byte1); + } + } + //other end --------------------------------------------------------------- + ; + private final String mimeType; + private final String extension; + + FileMagicNumber(String mimeType, String extension) { + this.mimeType = mimeType; + this.extension = extension; + } + + public static FileMagicNumber getMagicNumber(byte[] bytes) { + FileMagicNumber number = Arrays.stream(values()) + .filter(fileMagicNumber -> fileMagicNumber.match(bytes)) + .findFirst() + .orElse(UNKNOWN); + if (number.equals(FileMagicNumber.ZIP)){ + FileMagicNumber fn = FileMagicNumber.matchDocument(bytes); + return fn == UNKNOWN ? ZIP : fn; + } + return number; + } + + public String getMimeType() { + return mimeType; + } + + public String getExtension() { + return extension; + } + + private static int indexOf(byte[] array, byte[] target) { + if (array == null || target == null || array.length < target.length) { + return -1; + } + if (target.length == 0) { + return 0; + } else { + label1: + for(int i = 0; i < array.length - target.length + 1; ++i) { + for(int j = 0; j < target.length; ++j) { + if (array[i + j] != target[j]) { + continue label1; + } + } + return i; + } + return -1; + } + } + + //处理 Open XML 类型的文件 + private static boolean compareBytes(byte[] buf, byte[] slice, int startOffset) { + int sl = slice.length; + if (startOffset + sl > buf.length) { + return false; + } + byte[] sub = Arrays.copyOfRange(buf, startOffset, startOffset + sl); + return Arrays.equals(sub, slice); + } + + private static FileMagicNumber matchOpenXmlMime(byte[] bytes,int offset) { + byte[] word = new byte[]{'w','o','r','d','/'}; + byte[] ppt = new byte[]{ 'p','p','t','/'}; + byte[] xl = new byte[]{'x','l','/'}; + if (FileMagicNumber.compareBytes(bytes, word, offset)) { + return FileMagicNumber.DOCX; + } + if (FileMagicNumber.compareBytes(bytes, ppt, offset)) { + return FileMagicNumber.PPTX; + } + if (FileMagicNumber.compareBytes(bytes, xl, offset)) { + return FileMagicNumber.XLSX; + } + return FileMagicNumber.UNKNOWN; + } + + private static FileMagicNumber matchDocument(byte[] bytes) { + FileMagicNumber fileMagicNumber = FileMagicNumber.matchOpenXmlMime(bytes, (byte) 0x1e); + if (false== fileMagicNumber.equals(UNKNOWN)){ + return fileMagicNumber; + } + byte[] bytes1 = new byte[]{0x5B,0x43,0x6F,0x6E,0x74,0x65,0x6E,0x74,0x5F,0x54,0x79,0x70,0x65,0x73,0x5D,0x2E,0x78,0x6D,0x6C}; + byte[] bytes2 = new byte[]{0x5F,0x72,0x65,0x6C,0x73,0x2F,0x2E,0x72,0x65,0x6C,0x73}; + byte[] bytes3 = new byte[]{0x64,0x6F,0x63,0x50,0x72,0x6F,0x70,0x73}; + boolean flag1 = FileMagicNumber.compareBytes(bytes, bytes1, (byte) 0x1e); + boolean flag2 = FileMagicNumber.compareBytes(bytes, bytes2, (byte) 0x1e); + boolean flag3 = FileMagicNumber.compareBytes(bytes, bytes3, (byte) 0x1e); + if (false==(flag1 || flag2 || flag3)){ + return UNKNOWN; + } + int index = 0; + for (int i = 0; i < 4; i++) { + index = searchSignature(bytes, index+4,6000); + if (index == -1) { + continue; + } + FileMagicNumber fn = FileMagicNumber.matchOpenXmlMime(bytes, index +30); + if (false==fn.equals(UNKNOWN)) { + return fn; + } + } + return UNKNOWN; + } + + private static int searchSignature(byte[] bytes,int start,int rangeNum) { + byte[] signature = new byte[]{0x50,0x4B,0x03,0x04}; + int length = bytes.length; + int end = start + rangeNum; + if (end> length){ + end = length; + } + int index = FileMagicNumber.indexOf(Arrays.copyOfRange(bytes, start, end), signature); + return (index == -1) + ? -1 + : (start + index); + } + + public abstract boolean match(byte[] bytes); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java index e9fec0772..f7eaf8774 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileTypeUtil.java @@ -1,10 +1,9 @@ package cn.hutool.core.io; +import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; +import java.io.*; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentSkipListMap; @@ -20,71 +19,7 @@ import java.util.concurrent.ConcurrentSkipListMap; */ public class FileTypeUtil { - private static final Map FILE_TYPE_MAP; - - static { - FILE_TYPE_MAP = new ConcurrentSkipListMap<>((s1, s2) -> { - int len1 = s1.length(); - int len2 = s2.length(); - if (len1 == len2) { - return s1.compareTo(s2); - } else { - return len2 - len1; - } - }); - - FILE_TYPE_MAP.put("ffd8ff", "jpg"); // JPEG (jpg) - FILE_TYPE_MAP.put("89504e47", "png"); // PNG (png) - FILE_TYPE_MAP.put("4749463837", "gif"); // GIF (gif) - FILE_TYPE_MAP.put("4749463839", "gif"); // GIF (gif) - FILE_TYPE_MAP.put("49492a00227105008037", "tif"); // TIFF (tif) - // https://github.com/sindresorhus/file-type/blob/main/core.js#L90 - FILE_TYPE_MAP.put("424d", "bmp"); // 位图(bmp) - FILE_TYPE_MAP.put("41433130313500000000", "dwg"); // CAD (dwg) - FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); // Rich Text Format (rtf) - FILE_TYPE_MAP.put("38425053000100000000", "psd"); // Photoshop (psd) - FILE_TYPE_MAP.put("46726f6d3a203d3f6762", "eml"); // Email [Outlook Express 6] (eml) - FILE_TYPE_MAP.put("5374616E64617264204A", "mdb"); // MS Access (mdb) - FILE_TYPE_MAP.put("252150532D41646F6265", "ps"); - FILE_TYPE_MAP.put("255044462d312e", "pdf"); // Adobe Acrobat (pdf) - FILE_TYPE_MAP.put("2e524d46000000120001", "rmvb"); // rmvb/rm相同 - FILE_TYPE_MAP.put("464c5601050000000900", "flv"); // flv与f4v相同 - FILE_TYPE_MAP.put("0000001C66747970", "mp4"); - FILE_TYPE_MAP.put("00000020667479706", "mp4"); - FILE_TYPE_MAP.put("00000018667479706D70", "mp4"); - FILE_TYPE_MAP.put("49443303000000002176", "mp3"); - FILE_TYPE_MAP.put("000001ba210001000180", "mpg"); // - FILE_TYPE_MAP.put("3026b2758e66cf11a6d9", "wmv"); // wmv与asf相同 - FILE_TYPE_MAP.put("52494646e27807005741", "wav"); // Wave (wav) - FILE_TYPE_MAP.put("52494646d07d60074156", "avi"); - FILE_TYPE_MAP.put("4d546864000000060001", "mid"); // MIDI (mid) - FILE_TYPE_MAP.put("526172211a0700cf9073", "rar"); // WinRAR - FILE_TYPE_MAP.put("235468697320636f6e66", "ini"); - FILE_TYPE_MAP.put("504B03040a0000000000", "jar"); - FILE_TYPE_MAP.put("504B0304140008000800", "jar"); - // MS Excel 注意:word、msi 和 excel的文件头一样 - FILE_TYPE_MAP.put("d0cf11e0a1b11ae10", "xls"); - FILE_TYPE_MAP.put("504B0304", "zip"); - FILE_TYPE_MAP.put("4d5a9000030000000400", "exe"); // 可执行文件 - FILE_TYPE_MAP.put("3c25402070616765206c", "jsp"); // jsp文件 - FILE_TYPE_MAP.put("4d616e69666573742d56", "mf"); // MF文件 - FILE_TYPE_MAP.put("7061636b616765207765", "java"); // java文件 - FILE_TYPE_MAP.put("406563686f206f66660d", "bat"); // bat文件 - FILE_TYPE_MAP.put("1f8b0800000000000000", "gz"); // gz文件 - FILE_TYPE_MAP.put("cafebabe0000002e0041", "class"); // class文件 - FILE_TYPE_MAP.put("49545346030000006000", "chm"); // chm文件 - FILE_TYPE_MAP.put("04000000010000001300", "mxp"); // mxp文件 - FILE_TYPE_MAP.put("6431303a637265617465", "torrent"); - FILE_TYPE_MAP.put("6D6F6F76", "mov"); // Quicktime (mov) - FILE_TYPE_MAP.put("FF575043", "wpd"); // WordPerfect (wpd) - FILE_TYPE_MAP.put("CFAD12FEC5FD746F", "dbx"); // Outlook Express (dbx) - FILE_TYPE_MAP.put("2142444E", "pst"); // Outlook (pst) - FILE_TYPE_MAP.put("AC9EBD8F", "qdf"); // Quicken (qdf) - FILE_TYPE_MAP.put("E3828596", "pwl"); // Windows Password (pwl) - FILE_TYPE_MAP.put("2E7261FD", "ram"); // Real Audio (ram) - // https://stackoverflow.com/questions/45321665/magic-number-for-google-image-format - FILE_TYPE_MAP.put("52494646", "webp"); - } + private static final Map FILE_TYPE_MAP = new ConcurrentSkipListMap<>(); /** * 增加文件类型映射
@@ -120,26 +55,51 @@ public class FileTypeUtil { return fileTypeEntry.getValue(); } } - return null; + byte[] bytes = (HexUtil.decodeHex(fileStreamHexHead)); + return FileMagicNumber.getMagicNumber(bytes).getExtension(); + } + + /** + * 根据文件流的头部信息获得文件类型 + * + * @param in 文件流 + * @param fileHeadSize 自定义读取文件头部的大小 + * @return 文件类型,未找到为{@code null} + */ + public static String getType(InputStream in,int fileHeadSize) throws IORuntimeException { + return getType((IoUtil.readHex(in, fileHeadSize,false))); } /** * 根据文件流的头部信息获得文件类型
- * 注意此方法会读取头部28个bytes,造成此流接下来读取时缺少部分bytes
+ * 注意此方法会读取头部一些bytes,造成此流接下来读取时缺少部分bytes
* 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 - * * @param in {@link InputStream} + * @param isExact 是否精确匹配,如果为false,使用前64个bytes匹配,如果为true,使用前8192bytes匹配 * @return 类型,文件的扩展名,未找到为{@code null} - * @throws IORuntimeException 读取流引起的异常 + * @throws IORuntimeException 读取流引起的异常 */ - public static String getType(InputStream in) throws IORuntimeException { - return getType(IoUtil.readHex28Upper(in)); + public static String getType(InputStream in,boolean isExact) throws IORuntimeException { + return isExact + ?getType(IoUtil.readHex8192Upper(in)) + :getType(IoUtil.readHex64Upper(in)); } + /** + * 根据文件流的头部信息获得文件类型
+ * 注意此方法会读取头部64个bytes,造成此流接下来读取时缺少部分bytes
+ * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * @param in {@link InputStream} + * @return 类型,文件的扩展名,未找到为{@code null} + * @throws IORuntimeException 读取流引起的异常 + */ + public static String getType(InputStream in) throws IORuntimeException { + return getType(in,false); + } /** * 根据文件流的头部信息获得文件类型 - * 注意此方法会读取头部28个bytes,造成此流接下来读取时缺少部分bytes
+ * 注意此方法会读取头部64个bytes,造成此流接下来读取时缺少部分bytes
* 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 * *
@@ -151,24 +111,33 @@ public class FileTypeUtil {
 	 * @param in       {@link InputStream}
 	 * @param filename 文件名
 	 * @return 类型,文件的扩展名,未找到为{@code null}
-	 * @throws IORuntimeException 读取流引起的异常
+	 * @throws IORuntimeException  读取流引起的异常
 	 */
-	public static String getType(InputStream in, String filename) {
-		String typeName = getType(in);
+	public static String getType(InputStream in, String filename) throws IORuntimeException  {
+		return getType(in,filename,false);
+	}
 
+	/**
+	 * 根据文件流的头部信息获得文件类型
+	 * 注意此方法会读取头部一些bytes,造成此流接下来读取时缺少部分bytes
+ * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * + *
+	 *     1、无法识别类型默认按照扩展名识别
+	 *     2、xls、doc、msi头信息无法区分,按照扩展名区分
+	 *     3、zip可能为docx、xlsx、pptx、jar、war、ofd头信息无法区分,按照扩展名区分
+	 * 
+ * @param in {@link InputStream} + * @param filename 文件名 + * @param isExact 是否精确匹配,如果为false,使用前64个bytes匹配,如果为true,使用前8192bytes匹配 + * @return 类型,文件的扩展名,未找到为{@code null} + * @throws IORuntimeException 读取流引起的异常 + */ + public static String getType(InputStream in, String filename,boolean isExact) throws IORuntimeException { + String typeName = getType(in,isExact); if (null == typeName) { // 未成功识别类型,扩展名辅助识别 typeName = FileUtil.extName(filename); - } else if ("xls".equals(typeName)) { - // xls、doc、msi的头一样,使用扩展名辅助判断 - final String extName = FileUtil.extName(filename); - if ("doc".equalsIgnoreCase(extName)) { - typeName = "doc"; - } else if ("msi".equalsIgnoreCase(extName)) { - typeName = "msi"; - } else if ("ppt".equalsIgnoreCase(extName)) { - typeName = "ppt"; - } } else if ("zip".equals(typeName)) { // zip可能为docx、xlsx、pptx、jar、war、ofd等格式,扩展名辅助判断 final String extName = FileUtil.extName(filename); @@ -213,21 +182,51 @@ public class FileTypeUtil { *
 	 *     1、无法识别类型默认按照扩展名识别
 	 *     2、xls、doc、msi头信息无法区分,按照扩展名区分
-	 *     3、zip可能为docx、xlsx、pptx、jar、war头信息无法区分,按照扩展名区分
+	 *     3、zip可能为jar、war头信息无法区分,按照扩展名区分
+	 * 
+ * + * @param file 文件 {@link File} + * @param isExact 是否精确匹配,如果为false,使用前64个bytes匹配,如果为true,使用前8192bytes匹配 + * @return 类型,文件的扩展名,未找到为{@code null} + * @throws IORuntimeException 读取文件引起的异常 + */ + public static String getType(File file,boolean isExact) throws IORuntimeException { + FileInputStream in = null; + try { + in = IoUtil.toStream(file); + return getType(in, file.getName(),isExact); + } finally { + IoUtil.close(in); + } + } + + /** + * 根据文件流的头部信息获得文件类型 + * + *
+	 *     1、无法识别类型默认按照扩展名识别
+	 *     2、xls、doc、msi头信息无法区分,按照扩展名区分
+	 *     3、zip可能为jar、war头信息无法区分,按照扩展名区分
 	 * 
* * @param file 文件 {@link File} * @return 类型,文件的扩展名,未找到为{@code null} - * @throws IORuntimeException 读取文件引起的异常 + * @throws IORuntimeException 读取文件引起的异常 */ - public static String getType(File file) throws IORuntimeException { - FileInputStream in = null; - try { - in = IoUtil.toStream(file); - return getType(in, file.getName()); - } finally { - IoUtil.close(in); - } + public static String getType(File file) throws IORuntimeException { + return getType(file,false); + } + + /** + * 通过路径获得文件类型 + * + * @param path 路径,绝对路径或相对ClassPath的路径 + * @param isExact 是否精确匹配,如果为false,使用前64个bytes匹配,如果为true,使用前8192bytes匹配 + * @return 类型 + * @throws IORuntimeException 读取文件引起的异常 + */ + public static String getTypeByPath(String path,boolean isExact) throws IORuntimeException { + return getType(FileUtil.file(path),isExact); } /** @@ -235,9 +234,11 @@ public class FileTypeUtil { * * @param path 路径,绝对路径或相对ClassPath的路径 * @return 类型 - * @throws IORuntimeException 读取文件引起的异常 + * @throws IORuntimeException 读取文件引起的异常 */ - public static String getTypeByPath(String path) throws IORuntimeException { - return getType(FileUtil.file(path)); + public static String getTypeByPath(String path) throws IORuntimeException { + return getTypeByPath(path,false); } + + } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index b259bbdde..71579ea00 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -530,25 +530,41 @@ public class IoUtil extends NioUtil { } /** - * 从流中读取前28个byte并转换为16进制,字母部分使用大写 + * 从流中读取前64个byte并转换为16进制,字母部分使用大写 * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 */ - public static String readHex28Upper(InputStream in) throws IORuntimeException { - return readHex(in, 28, false); + public static String readHex64Upper(InputStream in) throws IORuntimeException { + return readHex(in, 64, false); } /** - * 从流中读取前28个byte并转换为16进制,字母部分使用小写 + * 从流中读取前8192个byte并转换为16进制,字母部分使用大写 * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 */ - public static String readHex28Lower(InputStream in) throws IORuntimeException { - return readHex(in, 28, true); + public static String readHex8192Upper(InputStream in) throws IORuntimeException { + try { + int i = in.available(); + return readHex(in, Math.min(8192, in.available()), false); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 从流中读取前64个byte并转换为16进制,字母部分使用小写 + * + * @param in {@link InputStream} + * @return 16进制字符串 + * @throws IORuntimeException IO异常 + */ + public static String readHex64Lower(InputStream in) throws IORuntimeException { + return readHex(in, 64, true); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java index 05703204d..d33ea0350 100755 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java @@ -48,7 +48,7 @@ public class FileTypeUtilTest { @Ignore public void ofdTest() { File file = FileUtil.file("e:/test.ofd"); - String hex = IoUtil.readHex28Upper(FileUtil.getInputStream(file)); + String hex = IoUtil.readHex64Upper(FileUtil.getInputStream(file)); Console.log(hex); String type = FileTypeUtil.getType(file); Console.log(type);