Способы проверки MIME-типа документов в PHP

Раздел: Работа с файлами -> Работа с файлами в PHP

Способы определения MIME-типа файла в PHP

Определение типа файла (MIME-типа) требуется при загрузке пользовательских файлов, проверке содержимого или в задачах автоматизации. Основная цель - убедиться, что файл соответствует ожидаемому формату, а не только расширению, которое можно подделать. В PHP есть несколько подходов, от простых до более надёжных, каждый со своими ограничениями.

Какой способ является самым надёжным для определения MIME-типа?

Класс finfo (Fileinfo), доступный через модуль fileinfo, использует встроенную базу magic-чисел (magic database) для анализа содержимого файла. Это наиболее достоверный метод, не зависящий от расширения. Он работает как с файлами, так и с данными в памяти.


<?php
// Создаём объект finfo с константой FILEINFO_MIME_TYPE
$finfo = new finfo(FILEINFO_MIME_TYPE);
$filename = 'example.pdf';
$mimeType = $finfo->file($filename);
echo $mimeType; // application/pdf
?>
  

Php file date (дата файла в php)

В результате возвращается строка MIME-типа, например image/jpeg, text/plain. Константа FILEINFO_MIME вернёт полную строку, включая кодировку (text/plain; charset=us-ascii).

Возможные проблемы и их решение:

  • Модуль fileinfo может быть отключён. Проверьте наличие extension=fileinfo в php.ini или установите его через `pecl install fileinfo`.
  • База magic-чисел может устареть. На Linux обновляется вместе с libmagic. На Windows файл magic.mgc поставляется с модулем.
  • Если файл отсутствует или недоступен для чтения, будет выброшено предупреждение. Проверяйте существование файла функцией file_exists().

Как определить MIME-тип с помощью устаревшей функции mime_content_type?

Функция mime_content_type() - предшественник finfo, но всё ещё широко используется. Она также полагается на magic-базу, но не предоставляет доступа к детальным опциям. Рекомендуется для быстрых решений, если нет возможности использовать finfo.


<?php
$mime = mime_content_type('image.png');
echo $mime; // image/png
?>
  

Php get file type (определение типа файла в php)

Проблемы:

  • Функция объявлена устаревшей в PHP 8.1. В будущих версиях может быть удалена.
  • На некоторых хостингах может быть отключена вместе с модулем fileinfo.
  • Некорректно определяет MIME-тип для некоторых файлов без магии (например, пустых).

Как определить тип файла по его расширению с помощью pathinfo?

Самый быстрый, но и самый ненадёжный способ - проверить только расширение. Используется для предварительной фильтрации или когда содержимое не требуется проверять (например, в описаниях).


<?php
$ext = pathinfo('document.pdf', PATHINFO_EXTENSION);
$mimeTypes = [
    'pdf' => 'application/pdf',
    'jpg' => 'image/jpeg',
    'png' => 'image/png',
];
$mime = $mimeTypes[$ext] ?? 'application/octet-stream';
echo $mime; // application/pdf
?>
  

обработка файлов php (обработка файлов в php)

Проблемы:

  • Злоумышленник может переименовать вредоносный файл в photo.jpg, а содержимое останется исполняемым.
  • Не поддерживает файлы без расширения или с неизвестным расширением.

Как определить тип изображения с помощью exif_imagetype?

Функция exif_imagetype() быстро считывает первые байты файла и возвращает константу типа изображения (IMAGETYPE_JPEG, IMAGETYPE_PNG и т.д.). Подходит только для графических форматов.


<?php
$type = exif_imagetype('photo.jpg');
if ($type !== false) {
    echo $type; // 2 (IMAGETYPE_JPEG)
}
?>
  

Files show php file (показ файлов в php)

Проблемы:

  • Работает только с изображениями (JPEG, PNG, GIF, BMP, TIFF, ICO, WBMP, WebP, AVIF).
  • Требует модуль exif (обычно включён).
  • Не возвращает MIME-строку напрямую; можно воспользоваться функцией image_type_to_mime_type() для преобразования.

Как получить MIME-тип и размеры изображения с помощью getimagesize?

getimagesize() возвращает массив с шириной, высотой, строкой MIME-типа и другими данными. Хотя функция предназначена для изображений, она также считывает достаточно данных для определения MIME.


<?php
$info = @getimagesize('photo.jpg');
if ($info !== false) {
    echo $info['mime']; // image/jpeg
}
?>
  

Php загрузка файла (загрузка файла в php)

Проблемы:

  • Работает только с поддерживаемыми форматами изображений (JPEG, PNG, GIF, BMP, TIFF, WebP и др.).
  • Для не-изображений или повреждённых файлов возвращает false.
  • Может потреблять больше ресурсов, чем exif_imagetype, так как считывает заголовок подробнее.

Как определить тип файла по его сигнатуре (magic bytes) вручную?

Самостоятельная проверка первых байтов файла позволяет обойтись без модуля fileinfo. Этот метод полезен, когда модуль недоступен, или для поддержки нестандартных форматов.


<?php
$fp = fopen('file.bin', 'rb');
$head = fread($fp, 4);
fclose($fp);

$signatures = [
    "\x89\x50\x4E\x47" => 'image/png',
    "\xFF\xD8\xFF"     => 'image/jpeg',
    "\x47\x49\x46\x38" => 'image/gif',
    "%PDF"              => 'application/pdf',
];

$mime = 'application/octet-stream';
foreach ($signatures as $sig => $type) {
    if (strncmp($head, $sig, strlen($sig)) === 0) {
        $mime = $type;
        break;
    }
}
echo $mime;
?>
  

Проблемы:

  • Требуется актуализировать список сигнатур; легко пропустить варинты (например, JPEG с разными байтами).
  • Некоторые сигнатуры пересекаются (GIF87a vs GIF89a).
  • Не подходит для текстовых файлов без BOM.

Выбор метода зависит от контекста. Для критичных задач (загрузка файлов, безопасность) используйте finfo. Для проверки изображений удобны exif_imagetype или getimagesize. Расширение - вспомогательный инструмент. Встроенную сигнатуру применяйте, если недоступны стандартные средства.

Расширенные примеры и нестандартные случаи

1. Определение MIME-типа файла, загруженного через HTTP

Если файл передан через форму, его можно проверить с помощью finfo до сохранения:

Пример

<?php
// $_FILES['file']['tmp_name'] содержит временный путь
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
if ($mime === 'image/jpeg') {
    // обработка
}
?>

Важно: ни в коем случае не доверяйте полю $_FILES['file']['type'], так как оно приходит от клиента и может быть подделано.

2. Использование finfo для определения типа по строке (буферу)

Если данные уже находятся в памяти (например, из cURL), применяется метод finfo->buffer():

Пример

<?php
$data = file_get_contents('https://example.com/photo.jpg');
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->buffer($data);
echo $mime; // image/jpeg
?>

Результат будет точным, так как анализируются первые байты, содержащиеся в строке.

3. Обработка ошибок при недоступности модуля fileinfo

Можно создать резервный механизм на основе exif_imagetype и mime_content_type:

Пример

<?php
function detectMimeType($filename) {
    if (extension_loaded('fileinfo')) {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        return $finfo->file($filename);
    } elseif (function_exists('mime_content_type')) {
        return mime_content_type($filename);
    } elseif (function_exists('exif_imagetype')) {
        $type = exif_imagetype($filename);
        if ($type !== false) {
            return image_type_to_mime_type($type);
        }
    }
    // fallback - чтение сигнатуры
    $signatures = [
        "\xFF\xD8\xFF" => 'image/jpeg',
        "\x89PNG"       => 'image/png',
    ];
    $fp = fopen($filename, 'rb');
    $head = fread($fp, 4);
    fclose($fp);
    foreach ($signatures as $sig => $mime) {
        if (strncmp($head, $sig, strlen($sig)) === 0) {
            return $mime;
        }
    }
    return 'application/octet-stream';
}

echo detectMimeType('unknown.file');
?>
application/octet-stream (если ничего не подошло)

4. Определение типа файла без открытия (только по расширению) с помощью mime_types.json

Для больших списков можно загрузить внешнюю базу соответствия расширений и MIME-типов:

Пример

<?php
$json = file_get_contents('mime_types.json');
$map = json_decode($json, true);
$ext = pathinfo('archive.zip', PATHINFO_EXTENSION);
$mime = $map[$ext] ?? 'application/octet-stream';
echo $mime; // application/zip
?>

Пример содержимого mime_types.json:

{"pdf": "application/pdf", "zip": "application/zip", "png": "image/png"}

Этот способ не защищает от подделки содержимого, но полезен для быстрой генерации заголовка Content-Type при скачивании.

5. Определение MIME-типа с помощью finfo и FILTER_VALIDATE

Можно комбинировать finfo с проверкой на допустимость типа для загрузки:

Пример

<?php
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);

if (!in_array($mime, $allowedTypes, true)) {
    throw new \Exception('Недопустимый тип файла');
}
?>

Этот подход применяется в системах управления контентом и веб-приложениях.

6. Сравнение производительности: finfo vs exif_imagetype для изображений

Бенчмарк для 1000 файлов (размер 50 КБ):

Пример

<?php
$files = glob('/tmp/images/*.jpg');
$start = microtime(true);
foreach ($files as $f) {
    $mime = (new finfo(FILEINFO_MIME_TYPE))->file($f);
}
echo 'finfo: ' . (microtime(true) - $start) . ' сек.';

$start = microtime(true);
foreach ($files as $f) {
    $type = exif_imagetype($f);
}
echo 'exif_imagetype: ' . (microtime(true) - $start) . ' сек.';
?>
finfo: 0.045 сек.
exif_imagetype: 0.032 сек.

В данном тесте exif_imagetype оказался быстрее, но применим только к изображениям. Для универсальной проверки finfo предпочтительнее.

Определение типа файла в PHP - comments

En
Php get file type (php)