Загрузка файла в PHP: проверяем ошибку и выбираем своё имя

3 мин чтения

Принимаем текстовый файл в PHP: проверяем ошибку загрузки, размер и сохраняем файл под серверным именем.

Файл проходит проверку до сохранения
  1. ПриёмПроверяем код ошибки и размер файла
  2. Проверка содержимогоОпределяем допустимый тип на сервере
  3. ХранениеНазначаем своё имя вне публичного каталога

Браузер присылает не только файл, но и его имя, MIME-тип и результат загрузки. Имени и типу нельзя доверять как проверке безопасности. Начнём с ошибки в $_FILES, затем ограничим размер и сохраним принятый файл под собственным именем вне публичной папки. Пример рассчитан на обычный текстовый документ.

Обработчик загрузки

<?php
$file = $_FILES['note'] ?? null;
if (!is_array($file) || !isset($file['error'])
    || !is_int($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Файл не принят');
}
if (!isset($file['tmp_name'], $file['size'])
    || !is_string($file['tmp_name']) || !is_int($file['size'])
    || $file['size'] < 1 || $file['size'] > 100000) {
    throw new RuntimeException('Неверный размер');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
if ($mime !== 'text/plain') {
    throw new RuntimeException('Ожидался текстовый файл');
}
$target = '/srv/private-notes/' . bin2hex(random_bytes(16)) . '.txt';
if (!move_uploaded_file($file['tmp_name'], $target)) {
    throw new RuntimeException('Не удалось сохранить файл');
}

Форма должна использовать метод POST, enctype multipart/form-data и поле с именем note. Лимит размера подберите под файлы, которые принимает ваше приложение. Случайное серверное имя исключает использование клиентского пути как назначения. Функция move_uploaded_file дополнительно проверяет, что источник относится к загрузке PHP. Она не анализирует смысл содержимого и может перезаписать существующее назначение, поэтому правила именования и хранения остаются ответственностью приложения.

Лимиты PHP и веб-сервера

Предел в обработчике работает только после того, как запрос прошёл ограничения веб-сервера и PHP. Значения post_max_size и upload_max_filesize согласуют заранее; при превышении общего размера запроса массивы формы могут оказаться пустыми. Ошибку отсутствия файла нужно отличать от повреждённой структуры поля. Для нескольких файлов требуется другой разбор массивов, поэтому этот пример намеренно принимает только один элемент.

Определение MIME через Fileinfo полезно, но не доказывает, что файл безопасен. Если содержимое затем показывается в HTML, его нужно выводить как текст с соответствующим экранированием. Права на каталог должны позволять приложению запись, а выдачу сохранённых файлов следует проводить через отдельную проверку доступа. Добавьте авторизацию, защиту формы от подделки запроса и правила удаления старых записей до использования такого обработчика в настоящем сервисе.

Проверка результата

Попробуйте отправить короткий текст, пустой и двоичный файлы, а также файл больше лимита. Проверьте note в виде массива и недоступный каталог. Только разрешённый случай должен сохранить файл под серверным именем; остальные — вернуть понятный отказ. Файл не должен открываться по прямому URL, а ошибки — раскрывать внутренние пути.

Источники и дальнейшее чтение