Загрузка файлов на PHP-сайте: что проверить в первую очередь

Проблемы с загрузкой файлов на PHP-сайте обычно проявляются просто: файл не загружается, форма зависает, появляется ошибка 413, картинка не открывается после загрузки или сервер принимает файл, который принимать не должен. Начинать лучше не с переписывания формы, а с проверки лимитов, прав и обработки файла.

Проверить форму

Для загрузки файла форма должна использовать POST и enctype multipart/form-data. Если enctype забыли, файл на сервер не придёт, даже если поле в браузере выглядит нормально.

<form method="post" enctype="multipart/form-data">
    <input type="file" name="image">
    <button type="submit">Загрузить</button>
</form>

В Yii2 при использовании ActiveForm это обычно задаётся в options:

<?= ActiveForm::begin([
    'options' => ['enctype' => 'multipart/form-data'],
]) ?>

Проверить лимиты PHP

Если маленький файл загружается, а большой нет, первым делом смотрят лимиты PHP. Важны upload_max_filesize и post_max_size. post_max_size должен быть больше, чем максимальный размер загружаемого файла.

php -i | grep upload_max_filesize
php -i | grep post_max_size
php -i | grep max_file_uploads

Пример настроек:

upload_max_filesize = 20M
post_max_size = 24M
max_file_uploads = 20

После изменения php.ini нужно перезапустить PHP-FPM или веб-сервер, иначе настройки не применятся.

Проверить лимиты nginx

Если nginx возвращает 413 Request Entity Too Large, PHP может даже не получить запрос. В таком случае проверяют client_max_body_size.

server {
    client_max_body_size 24m;
}

После изменения конфигурации:

nginx -t
sudo systemctl reload nginx

Права на директорию uploads

Сайт должен иметь право записывать файлы в директорию загрузок. Если директория принадлежит другому пользователю, загрузка может падать с permission denied.

ls -la web/uploads
mkdir -p web/uploads
chown -R www-data:www-data web/uploads
chmod -R ug+rw web/uploads

Пользователь www-data здесь только пример. На конкретном сервере это может быть nginx, apache или пользователь хостинга.

Проверка файла в Yii2

На backend нужно проверять не только факт загрузки, но и размер, расширение, ошибки и тип файла. В Yii2 для этого обычно используют UploadedFile и правила модели.

public function rules()
{
    return [
        [
            ['image'],
            'file',
            'skipOnEmpty' => false,
            'extensions' => ['jpg', 'jpeg', 'png', 'webp'],
            'maxSize' => 5 * 1024 * 1024,
        ],
    ];
}

Само сохранение может выглядеть так:

$file = UploadedFile::getInstance($model, 'image');

if ($file !== null && $model->validate()) {
    $name = Yii::$app->security->generateRandomString(24) . '.' . $file->extension;
    $path = Yii::getAlias('@webroot/uploads/') . $name;

    if (!$file->saveAs($path)) {
        Yii::error('Не удалось сохранить файл', 'upload');
    }
}

Имя файла лучше генерировать заново. Пользовательское имя может содержать пробелы, кириллицу, спецсимволы или совпасть с уже существующим файлом.

Не выполнять загруженные PHP-файлы

Директория uploads не должна выполнять PHP. Даже если форма принимает только картинки, эту защиту лучше настроить на уровне веб-сервера.

location /uploads/ {
    location ~ \.php$ {
        deny all;
    }
}

Дополнительно можно хранить оригиналы вне webroot, а через публичную директорию отдавать только обработанные изображения или файлы после проверки.

MIME-тип и расширение

Расширение файла само по себе не гарантирует безопасность. Файл может называться image.jpg, но внутри быть чем угодно. Для изображений полезно проверять MIME-тип и возможность прочитать картинку.

$info = getimagesize($file->tempName);

if ($info === false) {
    throw new BadRequestHttpException('Файл не является изображением');
}

Для документов логика будет другой, но общий принцип тот же: не доверять только имени файла.

Логи загрузки

Если загрузка нестабильна, стоит добавить временное логирование. В лог можно писать размер, расширение, ошибку PHP и итоговый путь. Полное содержимое файла в лог писать не нужно.

Yii::info([
    'name' => $file ? $file->name : null,
    'size' => $file ? $file->size : null,
    'error' => $file ? $file->error : null,
], 'upload');

Короткий чек-лист

  1. У формы есть enctype multipart/form-data.
  2. Поле file имеет правильное имя.
  3. upload_max_filesize и post_max_size достаточны.
  4. В nginx настроен client_max_body_size.
  5. Директория uploads существует и доступна на запись.
  6. Файл проверяется правилами модели.
  7. Имя файла генерируется сервером.
  8. В uploads запрещено выполнение PHP.
  9. Ошибки загрузки пишутся в лог.

Загрузка файлов — место, где удобство и безопасность легко конфликтуют. Поэтому лучше сразу настроить понятные ограничения, чем потом разбираться, почему сервер принял лишний файл или не может сохранить нормальное изображение.

Комментарии (0)

Пока нет комментариев. Будьте первым!