Полноценная обработка загрузки файлов включает несколько этапов безопасности и валидации. Рассмотрим профессиональный подход.
<form method="post" enctype="multipart/form-data" action="upload.php">
<input type="file" name="userfile" accept=".jpg,.jpeg,.png,.pdf,.docx" multiple>
<input type="submit" value="Загрузить">
</form>
Ключевые атрибуты:
enctype="multipart/form-data"
- обязателен для загрузки файловaccept
- ограничение типов файлов (не заменяет серверную проверку)multiple
- разрешает выбор нескольких файлов<?php
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
const UPLOAD_DIR = __DIR__ . '/uploads/';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Метод не разрешен');
}
if (!isset($_FILES['userfile']) {
http_response_code(400);
exit('Файл не был отправлен');
}
// Создаем директорию, если не существует
if (!file_exists(UPLOAD_DIR) && !mkdir(UPLOAD_DIR, 0755, true)) {
http_response_code(500);
exit('Не удалось создать директорию для загрузки');
}
$files = is_array($_FILES['userfile']['name'])
? reorganizeFiles($_FILES['userfile'])
: [$_FILES['userfile']];
foreach ($files as $file) {
processUpload($file);
}
echo 'Файлы успешно загружены';
function processUpload(array $file): void {
// Проверка ошибок загрузки
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException(handleUploadError($file['error']));
}
// Проверка типа файла
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, ALLOWED_TYPES)) {
throw new RuntimeException("Недопустимый тип файла: {$mime}");
}
// Проверка размера
if ($file['size'] > MAX_SIZE) {
throw new RuntimeException("Превышен максимальный размер файла");
}
// Генерация безопасного имени
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$basename = bin2hex(random_bytes(8));
$filename = sprintf('%s.%s', $basename, $extension);
$destination = UPLOAD_DIR . $filename;
// Перемещение файла
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new RuntimeException("Не удалось сохранить файл");
}
// Установка прав
chmod($destination, 0644);
}
function reorganizeFiles(array $files): array {
$result = [];
foreach ($files as $key => $values) {
foreach ($values as $index => $value) {
$result[$index][$key] = $value;
}
}
return $result;
}
function handleUploadError(int $error): string {
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return 'Превышен максимальный размер файла';
case UPLOAD_ERR_PARTIAL:
return 'Файл загружен частично';
case UPLOAD_ERR_NO_FILE:
return 'Файл не был загружен';
case UPLOAD_ERR_NO_TMP_DIR:
return 'Отсутствует временная директория';
case UPLOAD_ERR_CANT_WRITE:
return 'Не удалось записать файл на диск';
case UPLOAD_ERR_EXTENSION:
return 'Расширение PHP остановило загрузку';
default:
return 'Неизвестная ошибка загрузки';
}
}
finfo
, а не доверяем данным клиентаfile_uploads = On
upload_max_filesize = 10M
post_max_size = 12M
max_file_uploads = 20
upload_tmp_dir = /tmp
$logEntry = sprintf(
"[%s] %s: %s (%s)\n",
date('Y-m-d H:i:s'),
$filename,
$mime,
$file['size']
);
file_put_contents(UPLOAD_DIR . 'upload.log', $logEntry, FILE_APPEND);
if (str_starts_with($mime, 'image/')) {
$image = new Imagick($destination);
$image->resizeImage(800, 600, Imagick::FILTER_LANCZOS, 1, true);
$image->writeImage($destination);
}
$clamav = new ClamAV();
if (!$clamav->scan($destination)) {
unlink($destination);
throw new RuntimeException("Файл содержит вредоносный код");
}
enctype="multipart/form-data"
в формеmove_uploaded_file()
вместо copy()
/rename()