| | private const BUFFER = 12; |
| | private const ANY = -1; |
| | - private const EOS = -2; |
| | + |
| | + // Categories |
| | + private const CAT_IMAGE = 'image'; |
| | + private const CAT_VIDEO = 'video'; |
| | + private const CAT_AUDIO = 'audio'; |
| | + private const CAT_TEXT = 'text'; |
| | + private const CAT_ARCHIVE = 'archive'; |
| | + private const CAT_APP = 'application'; |
| | + private const CAT_BINARY = 'binary'; |
| | |
| | private const FORMATS = [ |
| | // Images |
| | - [[0x3C, 0x73, 0x76, 0x67, 0x20], 'image/svg+xml'], |
| | - [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], 'image/png'], |
| | - [[0xFF, 0xD8, 0xFF, 0xE0], 'image/jpeg'], |
| | - [[0xFF, 0xD8, 0xFF, 0xEE], 'image/jpeg'], |
| | - [[0xFF, 0xD8, 0xFF, 0xE1, self::ANY, self::ANY, 0x45, 0x78, 0x69, 0x66, 0x00], 'image/jpeg'], |
| | - [[0x47, 0x49, 0x46, 0x38], 'image/gif'], |
| | - [[0x42, 0x4D], 'image/bmp'], |
| | - [[0x49, 0x49, 0x2A, 0x00], 'image/tiff'], |
| | - [[0x4D, 0x4D, 0x00, 0x2A], 'image/tiff'], |
| | - [[0x52, 0x49, 0x46, 0x46, self::ANY, self::ANY, self::ANY, self::ANY, 0x57, 0x45, 0x42, 0x50], 'image/webp'], |
| | - [[0x38, 0x42, 0x50, 0x53, 0x00, 0x01], 'image/vnd.adobe.photoshop'], |
| | - [[0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], 'video/x-mng'], |
| | - [[0x23, 0x64, 0x65, 0x66], 'image/x-xbitmap'], |
| | - [[0x21, 0x20, 0x58, 0x50, 0x4D, 0x32], 'image/x-xpixmap'], |
| | + [self::CAT_IMAGE, [0x3C, 0x73, 0x76, 0x67, 0x20], 'image/svg+xml'], |
| | + [self::CAT_IMAGE, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], 'image/png'], |
| | + [self::CAT_IMAGE, [0xFF, 0xD8, 0xFF, 0xE0], 'image/jpeg'], |
| | + [self::CAT_IMAGE, [0xFF, 0xD8, 0xFF, 0xEE], 'image/jpeg'], |
| | + [self::CAT_IMAGE, [0xFF, 0xD8, 0xFF, 0xE1, self::ANY, self::ANY, 0x45, 0x78, 0x69, 0x66, 0x00], 'image/jpeg'], |
| | + [self::CAT_IMAGE, [0x47, 0x49, 0x46, 0x38], 'image/gif'], |
| | + [self::CAT_IMAGE, [0x42, 0x4D], 'image/bmp'], |
| | + [self::CAT_IMAGE, [0x49, 0x49, 0x2A, 0x00], 'image/tiff'], |
| | + [self::CAT_IMAGE, [0x4D, 0x4D, 0x00, 0x2A], 'image/tiff'], |
| | + [self::CAT_IMAGE, [0x52, 0x49, 0x46, 0x46, self::ANY, self::ANY, self::ANY, self::ANY, 0x57, 0x45, 0x42, 0x50], 'image/webp'], |
| | + [self::CAT_IMAGE, [0x38, 0x42, 0x50, 0x53, 0x00, 0x01], 'image/vnd.adobe.photoshop'], |
| | + [self::CAT_IMAGE, [0x23, 0x64, 0x65, 0x66], 'image/x-xbitmap'], |
| | + [self::CAT_IMAGE, [0x21, 0x20, 0x58, 0x50, 0x4D, 0x32], 'image/x-xpixmap'], |
| | + |
| | + // Video |
| | + [self::CAT_VIDEO, [0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], 'video/x-mng'], |
| | |
| | // Documents/Text |
| | - [[0x3C, 0x21], 'text/html'], |
| | - [[0x3C, 0x68, 0x74, 0x6D, 0x6C], 'text/html'], |
| | - [[0x3C, 0x68, 0x65, 0x61, 0x64], 'text/html'], |
| | - [[0x3C, 0x62, 0x6F, 0x64, 0x79], 'text/html'], |
| | - [[0x3C, 0x48, 0x54, 0x4D, 0x4C], 'text/html'], |
| | - [[0x3C, 0x48, 0x45, 0x41, 0x44], 'text/html'], |
| | - [[0x3C, 0x42, 0x4F, 0x44, 0x59], 'text/html'], |
| | - [[0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20], 'text/xml'], |
| | - [[0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78], 'text/xml'], |
| | - [[0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00], 'text/xml'], |
| | - [[0x25, 0x50, 0x44, 0x46, 0x2D], 'application/pdf'], |
| | - [[0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D], 'application/postscript'], |
| | - [[0x25, 0x21, 0x50, 0x53], 'application/postscript'], |
| | + [self::CAT_TEXT, [0x3C, 0x21], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x68, 0x74, 0x6D, 0x6C], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x68, 0x65, 0x61, 0x64], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x62, 0x6F, 0x64, 0x79], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x48, 0x54, 0x4D, 0x4C], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x48, 0x45, 0x41, 0x44], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x42, 0x4F, 0x44, 0x59], 'text/html'], |
| | + [self::CAT_TEXT, [0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20], 'text/xml'], |
| | + [self::CAT_TEXT, [0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78], 'text/xml'], |
| | + [self::CAT_TEXT, [0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00], 'text/xml'], |
| | + [self::CAT_TEXT, [0x25, 0x50, 0x44, 0x46, 0x2D], 'application/pdf'], |
| | + [self::CAT_TEXT, [0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D], 'application/postscript'], |
| | + [self::CAT_TEXT, [0x25, 0x21, 0x50, 0x53], 'application/postscript'], |
| | |
| | - // Audio/Video |
| | - [[0xFF, 0xFB, self::ANY], 'audio/mpeg'], |
| | - [[0x49, 0x44, 0x33], 'audio/mpeg'], |
| | - [[0x2E, 0x73, 0x6E, 0x64], 'audio/basic'], |
| | - [[0x64, 0x6E, 0x73, 0x2E], 'audio/basic'], |
| | - [[0x52, 0x49, 0x46, 0x46, self::ANY, self::ANY, self::ANY, self::ANY, 0x57, 0x41, 0x56, 0x45], 'audio/wav'], |
| | + // Audio |
| | + [self::CAT_AUDIO, [0xFF, 0xFB, self::ANY], 'audio/mpeg'], |
| | + [self::CAT_AUDIO, [0x49, 0x44, 0x33], 'audio/mpeg'], |
| | + [self::CAT_AUDIO, [0x2E, 0x73, 0x6E, 0x64], 'audio/basic'], |
| | + [self::CAT_AUDIO, [0x64, 0x6E, 0x73, 0x2E], 'audio/basic'], |
| | + [self::CAT_AUDIO, [0x52, 0x49, 0x46, 0x46, self::ANY, self::ANY, self::ANY, self::ANY, 0x57, 0x41, 0x56, 0x45], 'audio/wav'], |
| | |
| | - // Archives/Binaries |
| | - [[0x50, 0x4B, 0x03, 0x04], 'application/zip'], |
| | - [[0x50, 0x4B, 0x05, 0x06], 'application/zip'], |
| | - [[0x50, 0x4B, 0x07, 0x08], 'application/zip'], |
| | - [[0x1F, 0x8B, 0x08], 'application/gzip'], |
| | - [[0x42, 0x5A, 0x68], 'application/x-bzip2'], |
| | - [[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00], 'application/x-xz'], |
| | - [[0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar'], |
| | - [[0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed'], |
| | + // Archives |
| | + [self::CAT_ARCHIVE, [0x50, 0x4B, 0x03, 0x04], 'application/zip'], |
| | + [self::CAT_ARCHIVE, [0x50, 0x4B, 0x05, 0x06], 'application/zip'], |
| | + [self::CAT_ARCHIVE, [0x50, 0x4B, 0x07, 0x08], 'application/zip'], |
| | + [self::CAT_ARCHIVE, [0x1F, 0x8B, 0x08], 'application/gzip'], |
| | + [self::CAT_ARCHIVE, [0x42, 0x5A, 0x68], 'application/x-bzip2'], |
| | + [self::CAT_ARCHIVE, [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00], 'application/x-xz'], |
| | + [self::CAT_ARCHIVE, [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07], 'application/vnd.rar'], |
| | + [self::CAT_ARCHIVE, [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C], 'application/x-7z-compressed'], |
| | |
| | - // Executables/System |
| | - [[0x41, 0x43, self::ANY, self::ANY, self::ANY, self::ANY, 0x00, 0x00, 0x00, 0x00, 0x00], 'application/acad'], |
| | - [[0xCA, 0xFE, 0xBA, 0xBE], 'application/java-vm'], |
| | - [[0xAC, 0xED], 'application/x-java-serialized-object'], |
| | - [[0x4D, 0x5A], 'application/x-msdownload'], |
| | - [[0x7F, 0x45, 0x4C, 0x46], 'application/x-elf'], |
| | - [[0xCE, 0xFA, 0xED, 0xFE], 'application/x-mach-binary'], |
| | - [[0xCF, 0xFA, 0xED, 0xFE], 'application/x-mach-binary'], |
| | - [[0xFE, 0xED, 0xFA, 0xCE], 'application/x-mach-binary'], |
| | - [[0xFE, 0xED, 0xFA, 0xCF], 'application/x-mach-binary'], |
| | + // Applications/System |
| | + [self::CAT_APP, [0x41, 0x43, self::ANY, self::ANY, self::ANY, self::ANY, 0x00, 0x00, 0x00, 0x00, 0x00], 'application/acad'], |
| | + [self::CAT_APP, [0xCA, 0xFE, 0xBA, 0xBE], 'application/java-vm'], |
| | + [self::CAT_APP, [0xAC, 0xED], 'application/x-java-serialized-object'], |
| | + [self::CAT_APP, [0x4D, 0x5A], 'application/x-msdownload'], |
| | + [self::CAT_APP, [0x7F, 0x45, 0x4C, 0x46], 'application/x-elf'], |
| | + [self::CAT_APP, [0xCE, 0xFA, 0xED, 0xFE], 'application/x-mach-binary'], |
| | + [self::CAT_APP, [0xCF, 0xFA, 0xED, 0xFE], 'application/x-mach-binary'], |
| | + [self::CAT_APP, [0xFE, 0xED, 0xFA, 0xCE], 'application/x-mach-binary'], |
| | + [self::CAT_APP, [0xFE, 0xED, 0xFA, 0xCF], 'application/x-mach-binary'], |
| | ]; |
| | |
| | private const EXTENSION_MAP = [ |
| | - 'txt' => 'text/plain', 'html' => 'text/html', 'htm' => 'text/html', |
| | - 'css' => 'text/css', 'js' => 'application/javascript', |
| | - 'json' => 'application/json', 'xml' => 'application/xml', |
| | - 'pdf' => 'application/pdf', 'zip' => 'application/zip', |
| | - 'jar' => 'application/java-archive', 'war' => 'application/java-archive', |
| | - 'ear' => 'application/java-archive', 'class' => 'application/java-vm', |
| | - 'gz' => 'application/gzip', 'bz2' => 'application/x-bzip2', |
| | - 'xz' => 'application/x-xz', 'tar' => 'application/x-tar', |
| | - 'rar' => 'application/vnd.rar', '7z' => 'application/x-7z-compressed', |
| | - 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', |
| | - 'gif' => 'image/gif', 'svg' => 'image/svg+xml', 'webp' => 'image/webp', |
| | - 'bmp' => 'image/bmp', 'tiff' => 'image/tiff', 'tif' => 'image/tiff', |
| | - 'ico' => 'image/x-icon', 'mp4' => 'video/mp4', 'avi' => 'video/x-msvideo', |
| | - 'mov' => 'video/quicktime', 'wmv' => 'video/x-ms-wmv', |
| | - 'flv' => 'video/x-flv', 'webm' => 'video/webm', 'mp3' => 'audio/mpeg', |
| | - 'wav' => 'audio/wav', 'ogg' => 'audio/ogg', 'flac' => 'audio/flac', |
| | - 'aac' => 'audio/aac', 'php' => 'application/x-php', |
| | - 'py' => 'text/x-python', 'java' => 'text/x-java', 'c' => 'text/x-c', |
| | - 'cpp' => 'text/x-c++', 'h' => 'text/x-c', 'hpp' => 'text/x-c++', |
| | - 'cs' => 'text/x-csharp', 'go' => 'text/x-go', 'rs' => 'text/x-rust', |
| | - 'rb' => 'text/x-ruby', 'pl' => 'text/x-perl', 'sh' => 'application/x-sh', |
| | - 'bat' => 'application/x-bat', 'ps1' => 'application/x-powershell', |
| | - 'md' => 'text/markdown', 'yaml' => 'text/yaml', 'yml' => 'text/yaml', |
| | - 'toml' => 'application/toml', 'ini' => 'text/plain', 'cfg' => 'text/plain', |
| | - 'conf' => 'text/plain', |
| | + 'txt' => [self::CAT_TEXT, 'text/plain'], |
| | + 'html' => [self::CAT_TEXT, 'text/html'], |
| | + 'htm' => [self::CAT_TEXT, 'text/html'], |
| | + 'css' => [self::CAT_TEXT, 'text/css'], |
| | + 'js' => [self::CAT_TEXT, 'application/javascript'], |
| | + 'json' => [self::CAT_TEXT, 'application/json'], |
| | + 'xml' => [self::CAT_TEXT, 'application/xml'], |
| | + 'pdf' => [self::CAT_TEXT, 'application/pdf'], |
| | + 'zip' => [self::CAT_ARCHIVE, 'application/zip'], |
| | + 'jar' => [self::CAT_ARCHIVE, 'application/java-archive'], |
| | + 'war' => [self::CAT_ARCHIVE, 'application/java-archive'], |
| | + 'ear' => [self::CAT_ARCHIVE, 'application/java-archive'], |
| | + 'class' => [self::CAT_APP, 'application/java-vm'], |
| | + 'gz' => [self::CAT_ARCHIVE, 'application/gzip'], |
| | + 'bz2' => [self::CAT_ARCHIVE, 'application/x-bzip2'], |
| | + 'xz' => [self::CAT_ARCHIVE, 'application/x-xz'], |
| | + 'tar' => [self::CAT_ARCHIVE, 'application/x-tar'], |
| | + 'rar' => [self::CAT_ARCHIVE, 'application/vnd.rar'], |
| | + '7z' => [self::CAT_ARCHIVE, 'application/x-7z-compressed'], |
| | + 'jpg' => [self::CAT_IMAGE, 'image/jpeg'], |
| | + 'jpeg' => [self::CAT_IMAGE, 'image/jpeg'], |
| | + 'png' => [self::CAT_IMAGE, 'image/png'], |
| | + 'gif' => [self::CAT_IMAGE, 'image/gif'], |
| | + 'svg' => [self::CAT_IMAGE, 'image/svg+xml'], |
| | + 'webp' => [self::CAT_IMAGE, 'image/webp'], |
| | + 'bmp' => [self::CAT_IMAGE, 'image/bmp'], |
| | + 'tiff' => [self::CAT_IMAGE, 'image/tiff'], |
| | + 'tif' => [self::CAT_IMAGE, 'image/tiff'], |
| | + 'ico' => [self::CAT_IMAGE, 'image/x-icon'], |
| | + 'mp4' => [self::CAT_VIDEO, 'video/mp4'], |
| | + 'avi' => [self::CAT_VIDEO, 'video/x-msvideo'], |
| | + 'mov' => [self::CAT_VIDEO, 'video/quicktime'], |
| | + 'wmv' => [self::CAT_VIDEO, 'video/x-ms-wmv'], |
| | + 'flv' => [self::CAT_VIDEO, 'video/x-flv'], |
| | + 'webm' => [self::CAT_VIDEO, 'video/webm'], |
| | + 'mp3' => [self::CAT_AUDIO, 'audio/mpeg'], |
| | + 'wav' => [self::CAT_AUDIO, 'audio/wav'], |
| | + 'ogg' => [self::CAT_AUDIO, 'audio/ogg'], |
| | + 'flac' => [self::CAT_AUDIO, 'audio/flac'], |
| | + 'aac' => [self::CAT_AUDIO, 'audio/aac'], |
| | + 'php' => [self::CAT_TEXT, 'application/x-php'], |
| | + 'py' => [self::CAT_TEXT, 'text/x-python'], |
| | + 'java' => [self::CAT_TEXT, 'text/x-java'], |
| | + 'c' => [self::CAT_TEXT, 'text/x-c'], |
| | + 'cpp' => [self::CAT_TEXT, 'text/x-c++'], |
| | + 'h' => [self::CAT_TEXT, 'text/x-c'], |
| | + 'hpp' => [self::CAT_TEXT, 'text/x-c++'], |
| | + 'cs' => [self::CAT_TEXT, 'text/x-csharp'], |
| | + 'go' => [self::CAT_TEXT, 'text/x-go'], |
| | + 'rs' => [self::CAT_TEXT, 'text/x-rust'], |
| | + 'rb' => [self::CAT_TEXT, 'text/x-ruby'], |
| | + 'pl' => [self::CAT_TEXT, 'text/x-perl'], |
| | + 'sh' => [self::CAT_APP, 'application/x-sh'], |
| | + 'bat' => [self::CAT_APP, 'application/x-bat'], |
| | + 'ps1' => [self::CAT_APP, 'application/x-powershell'], |
| | + 'md' => [self::CAT_TEXT, 'text/markdown'], |
| | + 'yaml' => [self::CAT_TEXT, 'text/yaml'], |
| | + 'yml' => [self::CAT_TEXT, 'text/yaml'], |
| | + 'toml' => [self::CAT_TEXT, 'application/toml'], |
| | + 'ini' => [self::CAT_TEXT, 'text/plain'], |
| | + 'cfg' => [self::CAT_TEXT, 'text/plain'], |
| | + 'conf' => [self::CAT_TEXT, 'text/plain'], |
| | ]; |
| | - |
| | - private static function sniff( $data ): string { |
| | - $mediaType = 'application/octet-stream'; |
| | |
| | + /** |
| | + * Returns [Category, Mime] or null if not found. |
| | + */ |
| | + private static function sniff( $data ): ?array { |
| | if( !empty( $data ) ) { |
| | $dataLength = strlen( $data ); |
| | $maxScan = min( $dataLength, self::BUFFER ); |
| | $sourceBytes = []; |
| | |
| | for( $i = 0; $i < $maxScan; $i++ ) { |
| | $sourceBytes[$i] = ord( $data[$i] ) & 0xFF; |
| | } |
| | |
| | - foreach( self::FORMATS as [$pattern, $type] ) { |
| | + foreach( self::FORMATS as [$category, $pattern, $type] ) { |
| | $patternLength = count( $pattern ); |
| | - |
| | - if( $patternLength > $dataLength ) { |
| | - continue; |
| | - } |
| | + if( $patternLength > $dataLength ) continue; |
| | |
| | $matches = true; |
| | - |
| | for( $i = 0; $i < $patternLength; $i++ ) { |
| | - $patternByte = $pattern[$i]; |
| | - $sourceByte = $sourceBytes[$i]; |
| | - |
| | - if( $patternByte !== self::ANY && $patternByte !== $sourceByte ) { |
| | + if( $pattern[$i] !== self::ANY && $pattern[$i] !== $sourceBytes[$i] ) { |
| | $matches = false; |
| | break; |
| | } |
| | } |
| | |
| | if( $matches ) { |
| | - $mediaType = $type; |
| | - break; |
| | + return [$category, $type]; |
| | } |
| | } |
| | } |
| | - |
| | - return $mediaType; |
| | + return null; |
| | } |
| | |
| | - private static function getMediaTypeByExtension( $filePath ): string { |
| | + private static function getInfoByExtension( $filePath ): array { |
| | $extension = strtolower( pathinfo( $filePath, PATHINFO_EXTENSION ) ); |
| | - return self::EXTENSION_MAP[$extension] ?? 'application/octet-stream'; |
| | + return self::EXTENSION_MAP[$extension] ?? [self::CAT_BINARY, 'application/octet-stream']; |
| | } |
| | |
| | public static function isMediaType( $data, $filePath = '' ): string { |
| | - $sniffed = self::sniff( $data ); |
| | + $info = self::sniff( $data ); |
| | |
| | - return ($sniffed === 'application/octet-stream' && !empty( $filePath )) |
| | - ? self::getMediaTypeByExtension( $filePath ) |
| | - : $sniffed; |
| | + if ( $info === null && !empty( $filePath ) ) { |
| | + $info = self::getInfoByExtension( $filePath ); |
| | + } |
| | + |
| | + return $info ? $info[1] : 'application/octet-stream'; |
| | + } |
| | + |
| | + public static function isCategory( $data, $filePath = '' ): string { |
| | + $info = self::sniff( $data ); |
| | + |
| | + if ( $info === null && !empty( $filePath ) ) { |
| | + $info = self::getInfoByExtension( $filePath ); |
| | + } |
| | + |
| | + return $info ? $info[0] : self::CAT_BINARY; |
| | } |
| | } |