Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git

Adds category

Author Dave Jarvis <email>
Date 2026-02-08 20:00:14 GMT-0800
Commit 25ac3e21c87728a96293418368bc6035d54151d6
Parent d1020aa
new/File.php
$this->getTimeElapsed(),
$this->isDir,
- fn(string $type) => $this->isMediaType($type)
+ fn(string $type) => $this->isType($type),
+ fn(string $cat) => $this->isCategory($cat)
);
}
- private function isMediaType(string $type): bool {
- $data = '';
+ /**
+ * Checks if the file matches a specific Media Type string.
+ */
+ public function isType(string $type): bool {
+ $data = $this->getSniffBuffer();
+ return str_contains(MediaTypeSniffer::isMediaType($data, $this->name), $type);
+ }
- // Only attempt to read if it's not a directory and the file exists locally
- if (!$this->isDir && file_exists($this->name)) {
- $handle = @fopen($this->name, 'rb');
- if ($handle) {
- // Read the first 12 bytes as expected by the Sniffer
- $read = fread($handle, 12);
- if ($read !== false) {
- $data = $read;
- }
- fclose($handle);
- }
+ /**
+ * Checks if the file belongs to a specific category (image, video, etc).
+ */
+ public function isCategory(string $category): bool {
+ $data = $this->getSniffBuffer();
+ return MediaTypeSniffer::isCategory($data, $this->name) === $category;
+ }
+
+ /**
+ * Reads the first 12 bytes required for signature sniffing.
+ */
+ private function getSniffBuffer(): string {
+ if ($this->isDir || !file_exists($this->name)) {
+ return '';
}
- return str_contains(MediaTypeSniffer::isMediaType($data, $this->name), $type);
+ $handle = @fopen($this->name, 'rb');
+ if (!$handle) {
+ return '';
+ }
+
+ $read = fread($handle, 12);
+ fclose($handle);
+
+ return ($read !== false) ? $read : '';
}
new/MediaTypeSniffer.php
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;
}
}
Delta 184 lines added, 116 lines removed, 68-line increase