From 18b864b6e7383f81bc2c9c216437bc5f9edb53d2 Mon Sep 17 00:00:00 2001 From: peter279k Date: Sat, 20 Oct 2018 16:00:11 +0800 Subject: [PATCH] Test enhancement --- .travis.yml | 4 - composer.json | 8 +- phpunit.xml.dist | 2 +- src/SoftCreatR/MimeDetector/MimeDetector.php | 294 +++++++++--------- .../MimeDetector/MimeDetectorException.php | 2 +- .../MimeDetector/MimeDetectorTest.php | 106 +++---- .../MimeDetector/MimeDetectorTestUtil.php | 42 +-- 7 files changed, 227 insertions(+), 231 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01972d3..6bd2fa2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,6 @@ after_script: - php php-semver-checker-git.phar suggest -vvv after_success: - bash <(curl -s https://codecov.io/bash) -branches: - only: - - master - - dev cache: directories: - $HOME/.composer/cache diff --git a/composer.json b/composer.json index d9da809..0e7921f 100644 --- a/composer.json +++ b/composer.json @@ -31,13 +31,13 @@ "phpunit/phpunit": "~7.0" }, "autoload": { - "psr-0": { - "SoftCreatR\\MimeDetector\\": "src" + "psr-4": { + "SoftCreatR\\MimeDetector\\": "src/SoftCreatR/MimeDetector" } }, "autoload-dev": { - "psr-0": { - "SoftCreatR\\Tests\\MimeDetector\\": "tests" + "psr-4": { + "SoftCreatR\\Tests\\MimeDetector\\": "tests/SoftCreatR/MimeDetector" } }, "scripts": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bd110cb..2ff501a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,7 +12,7 @@ tests - + src diff --git a/src/SoftCreatR/MimeDetector/MimeDetector.php b/src/SoftCreatR/MimeDetector/MimeDetector.php index e68d52e..3fa4dde 100644 --- a/src/SoftCreatR/MimeDetector/MimeDetector.php +++ b/src/SoftCreatR/MimeDetector/MimeDetector.php @@ -18,35 +18,35 @@ class MimeDetector * @var MimeDetector */ private static $instance; - + /** * Cached first X bytes of the given file * * @var array */ private $byteCache = []; - + /** * Number of cached bytes * * @var integer */ private $byteCacheLen = 0; - + /** * Path to the given file * * @var string */ private $file = ''; - + /** * Hash of the given file * * @var string */ private $fileHash = ''; - + /** * Font Awesome Icon classes * @@ -72,7 +72,7 @@ class MimeDetector 'image' => 'fa-file-image-o', 'video' => 'fa-file-video-o' ]; - + /** * Singletons do not support a public constructor. Override init() if * you need to initialize components on creation. @@ -81,14 +81,14 @@ final protected function __construct() { $this->init(); } - + /** * Called within __construct(), override if necessary. */ protected function init() { } - + /** * Object cloning is disallowed. * @@ -97,7 +97,7 @@ protected function init() final protected function __clone() { } - + /** * Object serializing is disallowed. * @@ -108,7 +108,7 @@ final public function __sleep() { throw new MimeDetectorException('Serializing of Singletons is not allowed'); } - + /** * Returns an unique instance of the MimeDetector class. * @@ -119,10 +119,10 @@ public static function getInstance(): MimeDetector if (empty(self::$instance)) { self::$instance = new MimeDetector(); } - + return self::$instance; } - + /** * Setter for the file to be checked. * @@ -135,19 +135,19 @@ public function setFile(string $filePath): void if (!file_exists($filePath)) { throw new MimeDetectorException("File '" . $filePath . "' does not exist."); } - + $fileHash = hash_file('crc32b', $filePath); - + if ($this->fileHash !== $fileHash) { $this->byteCache = []; $this->byteCacheLen = 0; $this->file = $filePath; $this->fileHash = $fileHash; - + $this->createByteCache(); } } - + /** * Tries to determine the correct mime type of the given file by using "magic numbers". * @@ -158,7 +158,7 @@ public function getFileType(): array if (empty($this->byteCache)) { return []; } - + // Perform check if ($this->checkForBytes([0xFF, 0xD8, 0xFF])) { return [ @@ -166,35 +166,35 @@ public function getFileType(): array 'mime' => 'image/jpeg' ]; } - + if ($this->checkForBytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) { return [ 'ext' => 'png', 'mime' => 'image/png' ]; } - + if ($this->checkForBytes([0x47, 0x49, 0x46])) { return [ 'ext' => 'gif', 'mime' => 'image/gif' ]; } - + if ($this->checkForBytes([0x57, 0x45, 0x42, 0x50], 8)) { return [ 'ext' => 'webp', 'mime' => 'image/webp' ]; } - + if ($this->checkForBytes([0x46, 0x4C, 0x49, 0x46])) { return [ 'ext' => 'flif', 'mime' => 'image/flif' ]; } - + // Needs to be before `tif` check if (( $this->checkForBytes([0x49, 0x49, 0x2A, 0x0]) || @@ -206,7 +206,7 @@ public function getFileType(): array 'mime' => 'image/x-canon-cr2' ]; } - + if ($this->checkForBytes([0x49, 0x49, 0x2A, 0x0]) || $this->checkForBytes([0x4D, 0x4D, 0x0, 0x2A]) ) { @@ -215,28 +215,28 @@ public function getFileType(): array 'mime' => 'image/tiff' ]; } - + if ($this->checkForBytes([0x42, 0x4D])) { return [ 'ext' => 'bmp', 'mime' => 'image/bmp' ]; } - + if ($this->checkForBytes([0x49, 0x49, 0xBC])) { return [ 'ext' => 'jxr', 'mime' => 'image/vnd.ms-photo' ]; } - + if ($this->checkForBytes([0x38, 0x42, 0x50, 0x53])) { return [ 'ext' => 'psd', 'mime' => 'image/vnd.adobe.photoshop' ]; } - + // Zip-based file formats // Need to be before the `zip` check if ($this->checkForBytes([0x50, 0x4B, 0x3, 0x4])) { @@ -252,7 +252,7 @@ public function getFileType(): array 'mime' => 'application/epub+zip' ]; } - + // Assumes signed `.xpi` from addons.mozilla.org if ($this->checkString('META-INF/mozilla.rsa', 30)) { return [ @@ -260,28 +260,28 @@ public function getFileType(): array 'mime' => 'application/x-xpinstall' ]; } - + if ($this->checkString('mimetypeapplication/vnd.oasis.opendocument.text', 30)) { return [ 'ext' => 'odt', 'mime' => 'application/vnd.oasis.opendocument.text' ]; } - + if ($this->checkString('mimetypeapplication/vnd.oasis.opendocument.spreadsheet', 30)) { return [ 'ext' => 'ods', 'mime' => 'application/vnd.oasis.opendocument.spreadsheet' ]; } - + if ($this->checkString('mimetypeapplication/vnd.oasis.opendocument.presentation', 30)) { return [ 'ext' => 'odp', 'mime' => 'application/vnd.oasis.opendocument.presentation' ]; } - + // The docx, xlsx and pptx file types extend the Office Open XML file format: // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats $zipHeaderIndex = 0; // The first zip header was already found at index 0 @@ -289,14 +289,14 @@ public function getFileType(): array $type = null; $oxmlCTypes = $this->toBytes('[Content_Types].xml'); $oxmlRels = $this->toBytes('_rels/.rels'); - + do { $offset = $zipHeaderIndex + 30; - + if (!$oxmlFound) { $oxmlFound = $this->checkForBytes($oxmlCTypes, $offset) || $this->checkForBytes($oxmlRels, $offset); } - + if (!$type) { if ($this->checkString('word/', $offset)) { $type = [ @@ -315,34 +315,34 @@ public function getFileType(): array ]; } } - + if ($oxmlFound && $type) { return $type; } - + $zipHeaderIndex = $this->searchForBytes([0x50, 0x4B, 0x03, 0x04], $offset); } while ($zipHeaderIndex !== -1); - + // No more zip parts available in the buffer, but maybe we are almost certain about the type? if ($type) { return $type; } } - + if ($this->checkForBytes([0x50, 0x4B, 0x3, 0x4])) { return [ 'ext' => 'zip', 'mime' => 'application/zip' ]; } - + if ($this->checkForBytes([0x75, 0x73, 0x74, 0x61, 0x72], 257)) { return [ 'ext' => 'tar', 'mime' => 'application/x-tar' ]; } - + if ($this->checkForBytes([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) && ( $this->byteCache[6] === 0x0 || @@ -354,35 +354,35 @@ public function getFileType(): array 'mime' => 'application/x-rar-compressed' ]; } - + if ($this->checkForBytes([0x1F, 0x8B, 0x8])) { return [ 'ext' => 'gz', 'mime' => 'application/gzip' ]; } - + if ($this->checkForBytes([0x42, 0x5A, 0x68])) { return [ 'ext' => 'bz2', 'mime' => 'application/x-bzip2' ]; } - + if ($this->checkForBytes([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) { return [ 'ext' => '7z', 'mime' => 'application/x-7z-compressed' ]; } - + if ($this->checkForBytes([0x78, 0x01])) { return [ 'ext' => 'dmg', 'mime' => 'application/x-apple-diskimage' ]; } - + if ($this->checkForBytes([0x33, 0x67, 0x70, 0x35]) || // 3gp5 ( $this->checkForBytes([0x0, 0x0, 0x0]) && @@ -403,18 +403,18 @@ public function getFileType(): array 'mime' => 'video/mp4' ]; } - + if ($this->checkForBytes([0x4D, 0x54, 0x68, 0x64])) { return [ 'ext' => 'mid', 'mime' => 'audio/midi' ]; } - + // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska if ($this->checkForBytes([0x1A, 0x45, 0xDF, 0xA3])) { $idPos = $this->searchForBytes([0x42, 0x82]); - + if ($idPos !== -1) { if ($this->checkString('matroska', $idPos + 3)) { return [ @@ -422,7 +422,7 @@ public function getFileType(): array 'mime' => 'video/x-matroska' ]; } - + if ($this->checkString('webm', $idPos + 3)) { return [ 'ext' => 'webm', @@ -431,7 +431,7 @@ public function getFileType(): array } } } - + if ($this->checkForBytes([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) || $this->checkForBytes([0x66, 0x72, 0x65, 0x65], 4) || $this->checkForBytes([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], 4) || @@ -443,7 +443,7 @@ public function getFileType(): array 'mime' => 'video/quicktime' ]; } - + // RIFF file format which might be AVI, WAV, QCP, etc if ($this->checkForBytes([0x52, 0x49, 0x46, 0x46])) { if ($this->checkForBytes([0x41, 0x56, 0x49], 8)) { @@ -452,14 +452,14 @@ public function getFileType(): array 'mime' => 'video/vnd.avi' ]; } - + if ($this->checkForBytes([0x57, 0x41, 0x56, 0x45], 8)) { return [ 'ext' => 'wav', 'mime' => 'audio/vnd.wave' ]; } - + // QLCM, QCP file if ($this->checkForBytes([0x51, 0x4C, 0x43, 0x4D], 8)) { return [ @@ -468,28 +468,28 @@ public function getFileType(): array ]; } } - + if ($this->checkForBytes([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) { return [ 'ext' => 'wmv', 'mime' => 'video/x-ms-wmv' ]; } - + if ($this->checkForBytes([0x0, 0x0, 0x1, 0xBA]) || $this->checkForBytes([0x0, 0x0, 0x1, 0xB3])) { return [ 'ext' => 'mpg', 'mime' => 'video/mpeg' ]; } - + if ($this->checkForBytes([0x66, 0x74, 0x79, 0x70, 0x33, 0x67], 4)) { return [ 'ext' => '3gp', 'mime' => 'video/3gpp' ]; } - + // Check for MPEG header at different starting offsets for ($offset = 0; ($offset < 2 && $offset < ($this->byteCacheLen - 16)); $offset++) { if ($this->checkForBytes([0x49, 0x44, 0x33], $offset) || // ID3 header @@ -500,7 +500,7 @@ public function getFileType(): array 'mime' => 'audio/mpeg' ]; } - + // MPEG 1 or 2 Layer 2 header if ($this->checkForBytes([0xFF, 0xE4], $offset, [0xFF, 0xE4])) { return [ @@ -508,7 +508,7 @@ public function getFileType(): array 'mime' => 'audio/mpeg' ]; } - + // MPEG 2 layer 0 using ADTS if ($this->checkForBytes([0xFF, 0xF8], $offset, [0xFF, 0xFC])) { return [ @@ -516,7 +516,7 @@ public function getFileType(): array 'mime' => 'audio/mpeg' ]; } - + // MPEG 4 layer 0 using ADTS if ($this->checkForBytes([0xFF, 0xF0], $offset, [0xFF, 0xFC])) { return [ @@ -525,7 +525,7 @@ public function getFileType(): array ]; } } - + if ($this->checkForBytes([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], 4) || $this->checkForBytes([0x4D, 0x34, 0x41, 0x20]) ) { @@ -534,7 +534,7 @@ public function getFileType(): array 'mime' => 'audio/mp4' // RFC 4337 ]; } - + // Needs to be before `ogg` check if ($this->checkForBytes([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], 28)) { return [ @@ -542,11 +542,11 @@ public function getFileType(): array 'mime' => 'audio/opus' ]; } - + // If 'OggS' in first bytes, then OGG container if ($this->checkForBytes([0x4F, 0x67, 0x67, 0x53])) { // This is a OGG container - + // If ' theora' in header. if ($this->checkForBytes([0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61], 28)) { return [ @@ -554,7 +554,7 @@ public function getFileType(): array 'mime' => 'video/ogg' ]; } - + // If '\x01video' in header. if ($this->checkForBytes([0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00], 28)) { return [ @@ -562,7 +562,7 @@ public function getFileType(): array 'mime' => 'video/ogg' ]; } - + // If ' FLAC' in header https://xiph.org/flac/faq.html if ($this->checkForBytes([0x7F, 0x46, 0x4C, 0x41, 0x43], 28)) { return [ @@ -570,7 +570,7 @@ public function getFileType(): array 'mime' => 'audio/ogg' ]; } - + // 'Speex ' in header https://en.wikipedia.org/wiki/Speex if ($this->checkForBytes([0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20], 28)) { return [ @@ -578,7 +578,7 @@ public function getFileType(): array 'mime' => 'audio/ogg' ]; } - + // If '\x01vorbis' in header if ($this->checkForBytes([0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73], 28)) { return [ @@ -586,7 +586,7 @@ public function getFileType(): array 'mime' => 'audio/ogg' ]; } - + // Default OGG container https://www.iana.org/assignments/media-types/application/ogg // @codeCoverageIgnoreStart return [ @@ -595,14 +595,14 @@ public function getFileType(): array ]; // @codeCoverageIgnoreEnd } - + if ($this->checkForBytes([0x66, 0x4C, 0x61, 0x43])) { return [ 'ext' => 'flac', 'mime' => 'audio/x-flac' ]; } - + // 'MAC ' if ($this->checkForBytes([0x4D, 0x41, 0x43, 0x20])) { return [ @@ -610,7 +610,7 @@ public function getFileType(): array 'mime' => 'audio/ape' ]; } - + // 'wvpk' if ($this->checkForBytes([0x77, 0x76, 0x70, 0x6B])) { return [ @@ -618,28 +618,28 @@ public function getFileType(): array 'mime' => 'audio/wavpack' ]; } - + if ($this->checkForBytes([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) { return [ 'ext' => 'amr', 'mime' => 'audio/amr' ]; } - + if ($this->checkForBytes([0x25, 0x50, 0x44, 0x46])) { return [ 'ext' => 'pdf', 'mime' => 'application/pdf' ]; } - + if ($this->checkForBytes([0x4D, 0x5A])) { return [ 'ext' => 'exe', 'mime' => 'application/x-msdownload' ]; } - + if (( $this->byteCache[0] === 0x43 || $this->byteCache[0] === 0x46 ) && @@ -650,21 +650,21 @@ public function getFileType(): array 'mime' => 'application/x-shockwave-flash' ]; } - + if ($this->checkForBytes([0x7B, 0x5C, 0x72, 0x74, 0x66])) { return [ 'ext' => 'rtf', 'mime' => 'application/rtf' ]; } - + if ($this->checkForBytes([0x00, 0x61, 0x73, 0x6D])) { return [ 'ext' => 'wasm', 'mime' => 'application/wasm' ]; } - + if ($this->checkForBytes([0x77, 0x4F, 0x46]) && ( $this->checkForBytes([0x00, 0x01, 0x00, 0x00], 4) || @@ -677,7 +677,7 @@ public function getFileType(): array 'mime' => 'font/woff' ]; } - + if ($this->byteCache[3] === 0x32) { return [ 'ext' => 'woff2', @@ -685,7 +685,7 @@ public function getFileType(): array ]; } } - + if ($this->checkForBytes([0x4C, 0x50], 34) && ( $this->checkForBytes([0x00, 0x00, 0x01], 8) || @@ -698,77 +698,77 @@ public function getFileType(): array 'mime' => 'application/vnd.ms-fontobject' ]; } - + if ($this->checkForBytes([0x00, 0x01, 0x00, 0x00, 0x00])) { return [ 'ext' => 'ttf', 'mime' => 'font/ttf' ]; } - + if ($this->checkForBytes([0x4F, 0x54, 0x54, 0x4F, 0x00])) { return [ 'ext' => 'otf', 'mime' => 'font/otf' ]; } - + if ($this->checkForBytes([0x00, 0x00, 0x01, 0x00])) { return [ 'ext' => 'ico', 'mime' => 'image/x-icon' ]; } - + if ($this->checkForBytes([0x00, 0x00, 0x02, 0x00])) { return [ 'ext' => 'cur', 'mime' => 'image/x-icon' ]; } - + if ($this->checkForBytes([0x46, 0x4C, 0x56, 0x01])) { return [ 'ext' => 'flv', 'mime' => 'video/x-flv' ]; } - + if ($this->checkForBytes([0x25, 0x21])) { return [ 'ext' => 'ps', 'mime' => 'application/postscript' ]; } - + if ($this->checkForBytes([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) { return [ 'ext' => 'xz', 'mime' => 'application/x-xz' ]; } - + if ($this->checkForBytes([0x53, 0x51, 0x4C, 0x69])) { return [ 'ext' => 'sqlite', 'mime' => 'application/x-sqlite3' ]; } - + if ($this->checkForBytes([0x4E, 0x45, 0x53, 0x1A])) { return [ 'ext' => 'nes', 'mime' => 'application/x-nintendo-nes-rom' ]; } - + if ($this->checkForBytes([0x43, 0x72, 0x32, 0x34])) { return [ 'ext' => 'crx', 'mime' => 'application/x-google-chrome-extension' ]; } - + if ($this->checkForBytes([0x4D, 0x53, 0x43, 0x46]) || $this->checkForBytes([0x49, 0x53, 0x63, 0x28]) ) { @@ -777,7 +777,7 @@ public function getFileType(): array 'mime' => 'application/vnd.ms-cab-compressed' ]; } - + // Needs to be before `ar` check if ($this->checkForBytes([ 0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, @@ -789,42 +789,42 @@ public function getFileType(): array 'mime' => 'application/x-deb' ]; } - + if ($this->checkForBytes([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) { return [ 'ext' => 'ar', 'mime' => 'application/x-unix-archive' ]; } - + if ($this->checkForBytes([0xED, 0xAB, 0xEE, 0xDB])) { return [ 'ext' => 'rpm', 'mime' => 'application/x-rpm' ]; } - + if ($this->checkForBytes([0x1F, 0xA0]) || $this->checkForBytes([0x1F, 0x9D])) { return [ 'ext' => 'Z', 'mime' => 'application/x-compress' ]; } - + if ($this->checkForBytes([0x4C, 0x5A, 0x49, 0x50])) { return [ 'ext' => 'lz', 'mime' => 'application/x-lzip' ]; } - + if ($this->checkForBytes([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) { return [ 'ext' => 'msi', 'mime' => 'application/x-msi' ]; } - + if ($this->checkForBytes([ 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02 @@ -834,7 +834,7 @@ public function getFileType(): array 'mime' => 'application/mxf' ]; } - + if ($this->checkForBytes([0x47], 4) && ( $this->checkForBytes([0x47], 192) || @@ -846,21 +846,21 @@ public function getFileType(): array 'mime' => 'video/mp2t' ]; } - + if ($this->checkForBytes([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) { return [ 'ext' => 'blend', 'mime' => 'application/x-blender' ]; } - + if ($this->checkForBytes([0x42, 0x50, 0x47, 0xFB])) { return [ 'ext' => 'bpg', 'mime' => 'image/bpg' ]; } - + if ($this->checkForBytes([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) { // JPEG-2000 family if ($this->checkForBytes([0x6A, 0x70, 0x32, 0x20], 20)) { @@ -869,21 +869,21 @@ public function getFileType(): array 'mime' => 'image/jp2' ]; } - + if ($this->checkForBytes([0x6A, 0x70, 0x78, 0x20], 20)) { return [ 'ext' => 'jpx', 'mime' => 'image/jpx' ]; } - + if ($this->checkForBytes([0x6A, 0x70, 0x6D, 0x20], 20)) { return [ 'ext' => 'jpm', 'mime' => 'image/jpm' ]; } - + if ($this->checkForBytes([0x6D, 0x6A, 0x70, 0x32], 20)) { return [ 'ext' => 'mj2', @@ -891,21 +891,21 @@ public function getFileType(): array ]; } } - + if ($this->checkForBytes([0x46, 0x4F, 0x52, 0x4D, 0x00])) { return [ 'ext' => 'aif', 'mime' => 'audio/aiff' ]; } - + if ($this->checkForBytes([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], 60)) { return [ 'ext' => 'mobi', 'mime' => 'application/x-mobipocket-ebook' ]; } - + // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format) if ($this->checkForBytes([0x66, 0x74, 0x79, 0x70], 4)) { if ($this->checkForBytes([0x6D, 0x69, 0x66, 0x31], 8)) { @@ -914,14 +914,14 @@ public function getFileType(): array 'mime' => 'image/heif' ]; } - + if ($this->checkForBytes([0x6D, 0x73, 0x66, 0x31], 8)) { return [ 'ext' => 'heic', 'mime' => 'image/heif-sequence' ]; } - + if ($this->checkForBytes([0x68, 0x65, 0x69, 0x63], 8) || $this->checkForBytes([0x68, 0x65, 0x69, 0x78], 8) ) { @@ -930,7 +930,7 @@ public function getFileType(): array 'mime' => 'image/heic' ]; } - + // @codeCoverageIgnoreStart if ($this->checkForBytes([0x68, 0x65, 0x76, 0x63], 8) || $this->checkForBytes([0x68, 0x65, 0x76, 0x78], 8) @@ -942,28 +942,28 @@ public function getFileType(): array } // @codeCoverageIgnoreEnd } - + if ($this->checkForBytes([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) { return [ 'ext' => 'ktx', 'mime' => 'image/ktx' ]; } - + if ($this->checkForBytes([0x44, 0x49, 0x43, 0x4D], 128)) { return [ 'ext' => 'dcm', 'mime' => 'application/dicom' ]; } - + if ($this->checkForBytes([0x1B, 0x4C, 0x75, 0x61])) { return [ 'ext' => 'luac', 'mime' => 'application/x-lua-bytecode' ]; } - + // this class is intended to detect binary files, only. But there's nothing wrong in // trying to detect text files aswell. if ($this->checkString(' 'image/svg+xml' ]; } - + if ($this->searchForBytes($this->toBytes('searchForBytes($this->toBytes('searchForBytes($this->toBytes(' 'text/html' ]; } - + if ($this->searchForBytes($this->toBytes(' 'rdf', 'mime' => 'application/rdf+xml' ]; } - + if ($this->searchForBytes($this->toBytes(' 'rss', 'mime' => 'application/rss+xml' ]; } - + return [ 'ext' => 'xml', 'mime' => 'application/xml' ]; } - + if ($this->checkString('checkString('checkString(' 'text/html' ]; } - + return []; } - + /** * Returns the detected file extension. * @@ -1028,14 +1028,14 @@ public function getFileType(): array public function getFileExtension(): string { $fileType = $this->getFileType(); - + if (!empty($fileType['ext'])) { return $fileType['ext']; } - + return ''; } - + /** * Returns the detected file mime type. * @@ -1044,14 +1044,14 @@ public function getFileExtension(): string public function getMimeType(): string { $fileType = $this->getFileType(); - + if (!empty($fileType['mime'])) { return $fileType['mime']; } - + return ''; } - + /** * Returns the appropriate Font Awesome icon class for the given file or a given mime type. * @@ -1062,11 +1062,11 @@ public function getMimeType(): string public function getFontAwesomeIcon(string $fileMimeType = '', bool $fixedWidth = false): string { $iconClass = 'fa-file-o'; - + if (empty($fileMimeType)) { $fileMimeType = $this->getMimeType(); } - + if (!empty($fileMimeType)) { foreach ($this->fontAwesomeIcons as $iconMimeType => $iconName) { if (strpos($fileMimeType, $iconMimeType) !== false) { @@ -1074,10 +1074,10 @@ public function getFontAwesomeIcon(string $fileMimeType = '', bool $fixedWidth = } } } - + return 'fa ' . $iconClass . ($fixedWidth ? ' fa-fw' : ''); } - + /** * Returns the byte sequence of a given string. * @@ -1088,7 +1088,7 @@ public function toBytes(string $str): array { return array_values(unpack('C*', $str)); } - + /** * Checks the byte sequence of a given string. * @@ -1100,7 +1100,7 @@ protected function checkString(string $str, int $offset = 0): bool { return $this->checkForBytes($this->toBytes($str), $offset); } - + /** * Returns the offset to the next position of the given byte sequence. * Returns -1 if the sequence was not found. @@ -1113,16 +1113,16 @@ protected function checkString(string $str, int $offset = 0): bool protected function searchForBytes(array $bytes, int $offset = 0, array $mask = []): int { $limit = $this->byteCacheLen - count($bytes); - + for ($i = $offset; $i < $limit; $i++) { if ($this->checkForBytes($bytes, $i, $mask)) { return $i; } } - + return -1; } - + /** * Returns true, if a given byte sequence is found at the given offset within the given file. * @@ -1136,10 +1136,10 @@ protected function checkForBytes(array $bytes, int $offset = 0, array $mask = [] if (empty($bytes) || empty($this->byteCache)) { return false; } - + // make sure we have nummeric indices $bytes = array_values($bytes); - + foreach ($bytes as $i => $byte) { if (!empty($mask)) { if (!isset($this->byteCache[$offset + $i]) || @@ -1152,10 +1152,10 @@ protected function checkForBytes(array $bytes, int $offset = 0, array $mask = [] return false; } } - + return true; } - + /** * Caches the first 4096 bytes of the given file, so we don't have to read the whole file on every iteration. * @@ -1167,19 +1167,19 @@ protected function createByteCache(): void if (!empty($this->byteCache)) { return; } - + if (empty($this->file)) { throw new MimeDetectorException('No file provided.'); } - + $handle = fopen($this->file, 'rb'); $data = fread($handle, 4096); fclose($handle); - + foreach (str_split($data) as $i => $char) { $this->byteCache[$i] = ord($char); } - + $this->byteCacheLen = count($this->byteCache); } } diff --git a/src/SoftCreatR/MimeDetector/MimeDetectorException.php b/src/SoftCreatR/MimeDetector/MimeDetectorException.php index daed4a0..3e0d731 100644 --- a/src/SoftCreatR/MimeDetector/MimeDetectorException.php +++ b/src/SoftCreatR/MimeDetector/MimeDetectorException.php @@ -7,7 +7,7 @@ /** * Default exception for MimeDetector related errors. - * + * * @copyright SoftCreatR Media * @license LGPL-3.0 * @package SoftCreatR\MimeDetector diff --git a/tests/SoftCreatR/MimeDetector/MimeDetectorTest.php b/tests/SoftCreatR/MimeDetector/MimeDetectorTest.php index fd3d8fc..5d3a10d 100644 --- a/tests/SoftCreatR/MimeDetector/MimeDetectorTest.php +++ b/tests/SoftCreatR/MimeDetector/MimeDetectorTest.php @@ -20,20 +20,20 @@ class MimeDetectorTest extends TestCaseImplementation public function getInstance(): MimeDetector { $mimeDetector = MimeDetector::getInstance(); - + return $mimeDetector; } - + /** * @return void */ public function testGetInstance(): void { $mimeDetector = $this->getInstance(); - + self::assertInstanceOf(MimeDetector::class, $mimeDetector); } - + /** * Test, if `setFile` throws an exception, if the provided file does not exist. * @@ -43,11 +43,11 @@ public function testGetInstance(): void public function testSetFileThrowsException(): void { $this->expectException(MimeDetectorException::class); - + $mimeDetector = MimeDetector::getInstance(); $mimeDetector->setFile('nonexistant.file'); } - + /** * @dataProvider provideTestFiles * @param array $testFiles @@ -57,17 +57,17 @@ public function testSetFileThrowsException(): void public function testSetFile($testFiles): void { $mimeDetector = $this->getInstance(); - + foreach ($testFiles as $testFile) { $mimeDetector->setFile($testFile['file']); - + self::assertAttributeNotEmpty('byteCache', $mimeDetector); self::assertAttributeGreaterThanOrEqual(1, 'byteCacheLen', $mimeDetector); self::assertAttributeSame($testFile['file'], 'file', $mimeDetector); self::assertAttributeSame($testFile['hash'], 'fileHash', $mimeDetector); } } - + /** * Test, if `getFileType` returns an empty array, if the bytecache is empty (i.e. empty file provided). * @@ -79,16 +79,16 @@ public function testGetFileTypeReturnEmptyArrayWithoutByteCache(): void { $mimeDetector = $this->getInstance(); $mimeDetector->setFile(__FILE__); - + MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'byteCache', []); MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'file', ''); MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'fileHash', ''); - + $fileData = $mimeDetector->getFileType(); - + self::assertEmpty($fileData); } - + /** * Test, if `getFileType` returns an empty array, if the file type is unknown. * @@ -100,10 +100,10 @@ public function testGetFileTypeReturnEmptyArrayWithUnknownFileType(): void $mimeDetector = $this->getInstance(); $mimeDetector->setFile(__FILE__); $fileData = $mimeDetector->getFileType(); - + self::assertEmpty($fileData); } - + /** * @dataProvider provideTestFiles * @param array $testFiles @@ -113,15 +113,15 @@ public function testGetFileTypeReturnEmptyArrayWithUnknownFileType(): void public function testGetFileType(array $testFiles): void { $mimeDetector = $this->getInstance(); - + foreach ($testFiles as $testFile) { $mimeDetector->setFile($testFile['file']); $fileData = $mimeDetector->getFileType(); - + self::assertSame($testFile['ext'], $fileData['ext']); } } - + /** * Test, if `getFileExtension` returns an empty string, if the file type of the provided file cannot be determined. * @@ -134,10 +134,10 @@ public function testGetFileExtensionEmpty(): void $mimeDetector = $this->getInstance(); $mimeDetector->setFile(__FILE__); $detectedExtension = $mimeDetector->getFileExtension(); - + self::assertEmpty($detectedExtension); } - + /** * @dataProvider provideTestFiles * @param array $testFiles @@ -147,15 +147,15 @@ public function testGetFileExtensionEmpty(): void public function testGetFileExtension(array $testFiles): void { $mimeDetector = $this->getInstance(); - + foreach ($testFiles as $testFile) { $mimeDetector->setFile($testFile['file']); $detectedExtension = $mimeDetector->getFileExtension(); - + self::assertSame($testFile['ext'], $detectedExtension); } } - + /** * Test, if `getMimeType` returns an empty string, if the file type of the provided file cannot be determined. * @@ -168,10 +168,10 @@ public function testGetMimeTypeEmpty(): void $mimeDetector = $this->getInstance(); $mimeDetector->setFile(__FILE__); $detectedMimeType = $mimeDetector->getMimeType(); - + self::assertEmpty($detectedMimeType); } - + /** * @dataProvider provideTestFiles * @param array $testFiles @@ -181,16 +181,16 @@ public function testGetMimeTypeEmpty(): void public function testGetMimeType(array $testFiles): void { $mimeDetector = $this->getInstance(); - + foreach ($testFiles as $testFile) { $mimeDetector->setFile($testFile['file']); $detectedMimeType = $mimeDetector->getMimeType(); - + // we don't know the mime type of our test file, so we'll just check, if any mimetype has been detected self::assertNotEmpty($detectedMimeType); } } - + /** * @dataProvider provideFontAwesomeIcons * @param array $fontAwesomeIcons @@ -200,17 +200,17 @@ public function testGetMimeType(array $testFiles): void public function testGetFontAwesomeIcon(array $fontAwesomeIcons): void { $mimeDetector = $this->getInstance(); - + foreach ($fontAwesomeIcons as $mimeType => $params) { self::assertSame('fa ' . $params[0], $mimeDetector->getFontAwesomeIcon($mimeType, $params[1])); } - + $mimeDetector->setFile(__FILE__); - + self::assertSame('fa fa-file-o', $mimeDetector->getFontAwesomeIcon()); self::assertSame('fa fa-file-o fa-fw', $mimeDetector->getFontAwesomeIcon('', true)); } - + /** * @return void */ @@ -218,10 +218,10 @@ public function testToBytes(): void { $mimeDetector = $this->getInstance(); $result = $mimeDetector->toBytes('php'); - + self::assertEquals([112, 104, 112], $result); } - + /** * @return void * @throws ReflectionException @@ -233,10 +233,10 @@ public function testCheckString(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'checkString'); $result = $method->invoke($mimeDetector, 'php', 2); - + self::assertTrue($result); } - + /** * Test, if `searchForBytes` returns -1, if a byte array is provided, that isn't in the cached byte array. * @@ -250,10 +250,10 @@ public function testSearchForBytesNegative(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'searchForBytes'); $result = $method->invoke($mimeDetector, [0x66, 0x6F, 0x6F]); // foo - + self::assertEquals(-1, $result); } - + /** * @return void * @throws MimeDetectorException @@ -265,10 +265,10 @@ public function testSearchForBytes(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'searchForBytes'); $result = $method->invoke($mimeDetector, [0x70, 0x68, 0x70]); // php - + self::assertEquals(2, $result); } - + /** * Test, if `checkForBytes` returns false, if an empty byte array is provided. * @@ -282,10 +282,10 @@ public function testCheckForBytesFalse(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'checkForBytes'); $result = $method->invoke($mimeDetector, []); - + self::assertFalse($result); } - + /** * @return void * @throws MimeDetectorException @@ -297,10 +297,10 @@ public function testCheckForBytes(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'checkForBytes'); $result = $method->invoke($mimeDetector, [0x70, 0x68, 0x70], 2); // php - + self::assertTrue($result); } - + /** * Test, if `createByteCache` returns early. * @@ -314,10 +314,10 @@ public function testCreateByteCacheNull(): void $mimeDetector->setFile(__FILE__); $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'createByteCache'); $result = $method->invoke($mimeDetector); - + self::assertNull($result); } - + /** * Test, if `createByteCache` throws a MimeDetectorException. * @@ -328,18 +328,18 @@ public function testCreateByteCacheNull(): void public function testCreateByteCacheException(): void { $this->expectException(MimeDetectorException::class); - + $mimeDetector = $this->getInstance(); $mimeDetector->setFile(__FILE__); - + MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'byteCache', []); MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'file', ''); MimeDetectorTestUtil::setPrivateProperty($mimeDetector, 'fileHash', ''); - + $method = MimeDetectorTestUtil::getProtectedMethod($mimeDetector, 'createByteCache'); $method->invoke($mimeDetector); } - + /** * Returns an array of all existing test files and their corresponding CRC32b hashes. * @@ -348,7 +348,7 @@ public function testCreateByteCacheException(): void public function provideTestFiles(): array { $files = []; - + foreach (new DirectoryIterator(__DIR__ . '/fixtures') as $file) { if ($file->isFile() && $file->getBasename() !== '.git') { $files[$file->getBasename()] = [ @@ -358,10 +358,10 @@ public function provideTestFiles(): array ]; } } - + return [[$files]]; } - + /** * Returns an array of all existing test files and their corresponding CRC32b hashes. * diff --git a/tests/SoftCreatR/MimeDetector/MimeDetectorTestUtil.php b/tests/SoftCreatR/MimeDetector/MimeDetectorTestUtil.php index f8f919b..744bcd7 100644 --- a/tests/SoftCreatR/MimeDetector/MimeDetectorTestUtil.php +++ b/tests/SoftCreatR/MimeDetector/MimeDetectorTestUtil.php @@ -24,22 +24,22 @@ class MimeDetectorTestUtil public static function getPrivateMethod(MimeDetector $obj, string $methodName): ReflectionMethod { $class = new ReflectionClass($obj); - + if (!$class->hasMethod($methodName)) { throw new ReflectionException('Method ' . $methodName . ' is not defined.'); } - + $method = $class->getMethod($methodName); - + if (!$method->isPrivate()) { throw new ReflectionException('Method ' . $methodName . ' is not private.'); } - + $method->setAccessible(true); - + return $method; } - + /** * Returns a protected method for testing purposes. * @@ -51,22 +51,22 @@ public static function getPrivateMethod(MimeDetector $obj, string $methodName): public static function getProtectedMethod(MimeDetector $obj, string $methodName): ReflectionMethod { $class = new ReflectionClass($obj); - + if (!$class->hasMethod($methodName)) { throw new ReflectionException('Method ' . $methodName . ' is not defined.'); } - + $method = $class->getMethod($methodName); - + if (!$method->isProtected()) { throw new ReflectionException('Method ' . $methodName . ' is not protected.'); } - + $method->setAccessible(true); - + return $method; } - + /** * Updates a protected property for testing purposes. * @@ -79,22 +79,22 @@ public static function getProtectedMethod(MimeDetector $obj, string $methodName) public static function setProtectedProperty(MimeDetector $obj, string $propertyName, $value = null): void { $class = new ReflectionClass($obj); - + if (!$class->hasProperty($propertyName)) { throw new ReflectionException('Property ' . $propertyName . ' is not defined.'); } - + $property = $class->getProperty($propertyName); - + if (!$property->isProtected()) { throw new ReflectionException('Property ' . $propertyName . ' is not protected.'); } - + $property->setAccessible(true); $property->setValue($obj, $value); $property->setAccessible(false); } - + /** * Updates a protected property for testing purposes. * @@ -107,17 +107,17 @@ public static function setProtectedProperty(MimeDetector $obj, string $propertyN public static function setPrivateProperty(MimeDetector $obj, string $propertyName, $value = null): void { $class = new ReflectionClass($obj); - + if (!$class->hasProperty($propertyName)) { throw new ReflectionException('Property ' . $propertyName . ' is not defined.'); } - + $property = $class->getProperty($propertyName); - + if (!$property->isPrivate()) { throw new ReflectionException('Property ' . $propertyName . ' is not private.'); } - + $property->setAccessible(true); $property->setValue($obj, $value); $property->setAccessible(false);