enctype).$_FILES та його структура.move_uploaded_file.image/jpeg).Додавання аватарки до профілю, прикріплення резюме (PDF) або завантаження фотографій товару — це стандартний функціонал майже будь-якого сучасного веб-застосунку. Робота з файлами кардинально відрізняється від роботи з простим текстом із форми. Файли мають вагу (іноді гігабайти), вони передаються частинами і, що найважливіше, вони є головним вектором хакерських атак (через завантаження шкідливих PHP-скриптів під виглядом картинок). У цій лекції ми навчимось правильно конфігурувати форму і безпечно обробляти вхідні файли в PHP.
Звичайна форма <form method="POST"> вміє відправляти лише текст. Спробувавши відправити через неї файл, ви отримаєте в PHP просто його назву (рядок “my-photo.jpg”), а не самі байти зображення.
Щоб “навчити” браузер розрізати файл на частини та відправляти його на сервер у бінарному вигляді, форму обов’язково потрібно наділити атрибутом enctype="multipart/form-data".
<!-- Атрибут enctype критично необхідний! Без нього $_FILES буде порожнім -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
<label>Оберіть своє фото (Аватар):</label>
<!-- type="file" створює кнопку "Огляд..." в браузері -->
<!-- Атрибут accept (необов'язковий) підказує браузеру показувати лише картинки -->
<input type="file" name="avatar" accept="image/png, image/jpeg" />
<button type="submit">Завантажити</button>
</form>
$_FILESКоли файл прилітає на сервер, PHP зберігає його у свою приховану тимчасову системну папку (Temp dir). А для розробника він люб’язно створює суперглобальний двовимірний масив $_FILES.
Якщо наш input мав name="avatar", то масив $_FILES['avatar'] буде містити 5 стандартних ключів-характеристик цього завантаження:
<?php
// Для дебагінгу подивимось, що всередині:
// var_dump($_FILES['avatar']);
/* Приблизний вивід:
array(5) {
["name"] => string(10) "my-cat.jpg" // Оригінальна назва файлу на ПК користувача
["type"] => string(10) "image/jpeg" // MIME-тип (визначається браузером)
["tmp_name"] => string(24) "C:\xampp\tmp\php6A3E.tmp" // Де він ЗАРАЗ тимчасово лежить на сервері
["error"] => int(0) // Код помилки (0 - означає успіх)
["size"] => int(102400) // Розмір у Байтах (102400 = 100 Кб)
}
*/
?>
error0 (UPLOAD_ERR_OK): Файл завантажено успішно.1 (UPLOAD_ERR_INI_SIZE): Розмір перевищує ліміт, вказаний у php.ini.4 (UPLOAD_ERR_NO_FILE): Користувач натиснув “Submit”, але забув вибрати файл у вікні.move_uploaded_fileФайл, який лежить у системній тимчасовій папці (tmp_name), буде безповоротно видалений самим PHP автоматично одразу після того, як скрипт upload.php завершить роботу (тобто через долі секунди).
Наше завдання — встигнути перемістити його з tmp_name до нашої постійної папки проєкту (наприклад, /uploads/avatars/). Для цього використовується спеціалізована захищена функція move_uploaded_file(). Заборонено використовувати звичайну функцію копіювання copy(), оскільки вона не перевіряє, чи дійсно цей файл був завантажений через HTTP, чи це системний файл сервера.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['avatar'];
// 1. Перевіряємо, чи взагалі надійшов файл і чи не було помилок під час транспортування
if ($file['error'] === UPLOAD_ERR_OK) {
// 2. Звідки беремо? (Тимчасовий системний шлях)
$tmpPath = $file['tmp_name'];
// 3. Куди кладемо? (Наприклад, в папку uploads поруч зі скриптом)
// ВАЖЛИВО: папка "uploads" має фізично існувати на диску до запуску!
$destination = 'uploads/' . $file['name'];
// 4. Переміщуємо
if (move_uploaded_file($tmpPath, $destination)) {
echo "Файл успішно збережений як: " . $destination;
} else {
echo "Помилка запису на диск (можливо, немає прав у папки uploads).";
}
} else {
echo "Файл не було завантажено (Помилка " . $file['error'] . ")";
}
}
?>
Код вище є вкрай небезпечним. Якщо хакер перейменує вірус на virus.php і завантажить його як аватарку, а потім відкриє адресу yoursite.com/uploads/virus.php — сервер люб’язно виконає його код. Ваша система буде повністю зламана.
Щоб цього уникнути, потрібен жорсткий 3-етапний контроль.
Перевіряти $file['size']. Захищає від переповнення дисків (DDoS-атаки на сховище).
$maxSize = 2 * 1024 * 1024; // 2 Мегабайти
if ($file['size'] > $maxSize) {
die("Файл завеликий! Максимум 2 МБ.");
}
Браузерному атрибуту accept="image/png" вірити не можна (його можна видалити в F12). $_FILES['type'] також формується браузером клієнта.
Найнадійніший алгоритм — забрати оригінальне розширення з кінця імені файла (pathinfo()) та перевірити його вручну через так званий “White list” (Білий список дозволених).
// Білий список безпечних розширень:
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];
// Дістаємо "jpg" з тексту "my_photo-2023.min.jpg"
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExtensions)) {
die("Дозволено завантажувати ТІЛЬКИ картинки (jpg, png, gif)!");
}
Ніколи не зберігайте файл під його оригінальним $file['name']. По-перше, зловмисник може вбудувати туди шкідливі символи. По-друге, якщо два користувачі завантажать аватарку з ідентичним ім’ям photo.jpg (що масово зустрічається на телефонах) — другий файл автоматично перезапише й знищить перший без будь-яких попереджень!
Генеруємо унікальний, гарантовано рандомний хеш імені:
// Створюємо нове ім'я на основі унікального ідентифікатора часу
$newName = uniqid('avatar_', true) . '.' . $ext;
// Вийде щось накшталт: avatar_65f01...b2.jpg
$destination = 'uploads/' . $newName;
move_uploaded_file($tmpPath, $destination);
enctype="multipart/form-data" і підтримується лише через метод HTTP POST.$_FILES.move_uploaded_file().uniqid()) є головною причиною злому більшості веб-додатків у світі.$_POST['avatar_file'] завжди буде повертати просто рядок тексту, а масив $_FILES буде порожнім, якщо форма має вигляд: <form method="POST"><input type="file" name="avatar_file"></form>. Якого фундаментального атрибута тут не вистачає?error в масиві $_FILES позначається успішне, стовідсоткове скачування файлу на тимчасову територію сервера (Temp dir)?move_uploaded_file()? Згадайте, якою буде його доля по завершенні роботи upload.php, якщо програміст його так і НЕ перемістить?['name']..php і .exe). Чому?