Avatar ·

Как получить расширение файла в PHP? Надёжные способы и подводные камни

Всем привет! Я начинающий PHP-разработчик, и столкнулся с, казалось бы, простой задачей: мне нужно получить расширение файла из его имени. У меня есть переменная, например, `$filename = "document.pdf"` или `$path = "/var/www/uploads/image.jpg"`. Мне нужно извлечь "pdf" или "jpg". Я попробовал самый простой способ — explode с точкой: ```php $filename = "document.pdf"; $parts = explode('.', $filename); $ext = end($parts); echo $ext; // pdf ``` Но сразу возникли проблемы. Во-первых, что если в имени файла несколько точек, например, `my.file.name.txt`? Мой код выдаст "txt", что вроде бы верно, но вдруг это не то, что нужно? Во-вторых, что если это полный путь, а не просто имя? Например, `/home/user/file.tar.gz` — explode разобьёт всё по слешам и точкам, и результат может быть неожиданным. В-третьих, что делать с файлами без расширения, как `Makefile` или `.htaccess`? Мой код вернёт "htaccess", но это же не расширение в классическом понимании. Я слышал, что есть встроенная функция `pathinfo()`, но не уверен, как правильно её использовать для этой задачи. Также меня волнует безопасность: если я получаю расширение из пользовательского ввода (например, загруженного файла), не может ли злоумышленник подсунуть что-то вроде `shell.php.jpg` или `evil.php%00.jpg`? Как правильно обрабатывать такие случаи? Подскажите, пожалуйста, самый надёжный и безопасный способ получить расширение файла в PHP. Заранее спасибо! <<>>

Использование pathinfo() — стандартный и надёжный способ

В PHP есть встроенная функция pathinfo(), которая специально создана для разбора путей к файлам. Она возвращает ассоциативный массив с ключами dirname, basename, filename и extension. Это гораздо безопаснее и удобнее, чем ручной разбор через explode().

<?php
// Простейший случай
$filename = "document.pdf";
$info = pathinfo($filename);
echo $info['extension']; // pdf

// Файл с несколькими точками
$filename2 = "my.file.name.txt";
$info2 = pathinfo($filename2);
echo $info2['extension']; // txt (последняя часть после последней точки)

// Полный путь
$path = "/var/www/uploads/image.jpg";
$info3 = pathinfo($path);
echo $info3['extension']; // jpg
echo $info3['filename']; // image
echo $info3['basename']; // image.jpg
?>

Особые случаи

Функция pathinfo() корректно обрабатывает почти все граничные случаи:

  • Файлы без расширения (например, Makefile или README): pathinfo() вернёт пустую строку для extension.
  • Скрытые файлы (например, .htaccess): pathinfo() вернёт htaccess как расширение. Это спорный момент, но для большинства задач такое поведение приемлемо.
  • Файлы с точкой в конце (например, file.): расширение будет пустой строкой.
<?php
// Без расширения
echo pathinfo('Makefile', PATHINFO_EXTENSION); // (пусто)

// Скрытый файл
echo pathinfo('.htaccess', PATHINFO_EXTENSION); // htaccess

// Точка в конце
echo pathinfo('file.', PATHINFO_EXTENSION); // (пусто)
?>

Безопасность при работе с загруженными файлами

Если вы получаете расширение из имени загруженного файла (например, из $_FILES['file']['name']), никогда не доверяйте этому значению для определения типа файла. Злоумышленник может переименовать файл virus.exe в photo.jpg.exe или использовать null-byte инъекции (shell.php%00.jpg).

Правильный подход — определять MIME-тип файла через его содержимое, а не через расширение:

<?php
// Никогда так не делайте для проверки безопасности!
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if ($ext !== 'jpg') {
    die('Только JPG разрешены!'); // Это можно обойти
}

// Правильно: используйте finfo для определения реального типа
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);

if ($mime !== 'image/jpeg') {
    die('Это не JPG!');
}
?>

Дополнительные советы

  • Используйте константу PATHINFO_EXTENSION для получения только расширения, без создания всего массива: pathinfo($file, PATHINFO_EXTENSION).
  • Для получения расширения в нижнем регистре всегда используйте strtolower(): strtolower(pathinfo($file, PATHINFO_EXTENSION)).
  • Если вам нужно проверить, является ли файл изображением, лучше использовать getimagesize() или exif_imagetype().

В итоге: pathinfo() — это правильный и безопасный способ для синтаксического разбора имени файла. Но помните, что расширение — это всего лишь строка в имени, а не гарантия содержимого файла.

<<>>

Альтернативный подход: регулярные выражения

Хотя pathinfo() — предпочтительный метод, в некоторых специфических ситуациях может потребоваться более гибкий подход. Например, если вам нужно извлечь не последнее расширение, а все расширения (для .tar.gz), или если вы работаете с нестандартными именами файлов.

<?php
// Извлечение всех расширений (для .tar.gz)
function getAllExtensions($filename) {
    preg_match('/\.([^.]+)$/', $filename, $match);
    return $match[1] ?? '';
}

function getAllExtensionsMulti($filename) {
    preg_match_all('/\.([^.]+)/', $filename, $matches);
    return $matches[1] ?? [];
}

echo getAllExtensions('archive.tar.gz'); // gz
print_r(getAllExtensionsMulti('archive.tar.gz')); 
// Array ( [0] => tar [1] => gz )
?>

Регулярное выражение /\.([^.]+)$/ ищет точку, за которой следует один или более символов, не являющихся точкой, до конца строки. Это эквивалентно тому, что делает pathinfo(), но даёт больше контроля.

Когда это может пригодиться:

  • Если вам нужно работать с архивами, имеющими двойное расширение (например, .tar.gz, .tar.bz2).
  • Если вы пишете свою собственную реализацию для учебных целей.
  • Если по какой-то причине pathinfo() недоступна (хотя это крайне маловероятно).

Однако помните: для 99% реальных задач pathinfo() более чем достаточно. Регулярные выражения добавляют сложность и потенциальные ошибки без веской причины.