Что может быть общего у фотографий, находящихся в онлайн-фотоальбоме, прикреплённых к электронному письму или у файлов, представленных в онлайн-приложении для пакетной обработки? Всех их необходимо загрузить на сервер через интернет из веб-браузера. Действительно, загрузка файлов является важной особенностью многих сайтов и веб-приложений, которые мы используем ежедневно. Из этой статьи вы узнаете, как добавить к себе на сайт поддержку загрузки файлов используя для этого PHP.
Требования перед загрузкой файлов
Обработка загружаемых файлов - стандартный процесс, но существует несколько мелочей, на которые необходимо обратить внимание перед началом работы. Первое, в чем нужно удостовериться, что PHP настроен и позволяет загружать файлы. Для этого в php.ini стоит проверить директиву file_uploads
, и, если она выключена, то включить.
file_uploads = On
Загружаемые файлы сначала сохраняются во временном каталоге (но не волнуйтесь ... ваш PHP-скрипт впоследствии может переместить файлы в более подходящее место). Исходное местоположение является временным каталогом для системы по умолчанию. Вы можете указать другой каталог, используя директиву upload_tmp_dir
в php.ini. Независимо от этого, будет не лишним проверить, что процесс PHP имеет правильные права на запись в зависимости от используемого каталога.
upload_tmp_dir = "/tmp"
tboronczyk@zarkov:~$ ls -l / | grep tmp
drwxrwxrwt 13 root root 40960 2011-08-31 00:50 tmp
После того, как вы уверены, что конфигурация позволяет серверу принимать загруженные файлы, вы можете сосредоточить свое внимание на деталях и добавить HTML форму. Крайне важно, чтобы ваш элемент <form>
использовал метод POST и имел атрибут enctype
, установленный для multipart/form-data
.
<form action="upload.php" method="post" enctype="multipart/form-data">
Этапы загрузки файла на сервер
Скорей всего, вы уже можете представить, как загружаются файлы на сервер, основываясь на собственном опыте и требованиях о которых я упомянул выше.
- Посетитель просматривает HTML-страницу с формой, специально написанной для поддержки загрузки файлов;
- Далее он предоставляет файл, который он хочет загрузить и нажимает кнопку отправить;
- Браузер кодирует файл и отправляет его как часть запроса POST;
- PHP получает форму отправки, декодирует файл и сохраняет его во временном месте на сервере;
- Написанный PHP-скрипт, ответственный за обработку сообщения формы, проверяет файл и обрабатывает его каким-то образом, часто перемещая его из своего временного местоположения в постоянное, где будет храниться файл.
Для добавления поддержки загрузки файлов вам необходимо создать HTML форму, которая будет представлена пользователю, и скрипт PHP, который позаботится о загруженном файле на сервере.
Добавляем HTML форму для загрузки файла
HTML-формы предоставляют собой интерфейс, через который пользователь инициирует загрузку файла. Нужно помнить, что элемент <form>
должен иметь свой атрибут метода, установленный для публикации, и его атрибут enctype
, заданный для multipart/form-data
. Элемент <input>
файла предоставляет поле, используемое для указания файла, который будет загружен. Как и любой другой элемент формы, важно указать атрибут имени, чтобы вы могли ссылаться на него в PHP скрипте, обрабатывающем форму.
Вот как выглядит типичная разметка формы загрузки файлов:
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="myFile">
<br>
<input type="submit" value="Upload">
</form>
Стоит отметить, что разные браузеры будут визуализировать поле загрузки файла по-разному. IE, Firefox и Opera отображают его как текстовое поле с кнопкой рядом с ней надписью «Обзор» или «Выбрать». Safari отображает ее так же, как кнопку с надписью: «Выбрать файл». По большому счету это не проблема с тех пор, как пользователи привыкли к тому, как поле отображается в своем браузере и умеют его использовать. Иногда, однако, вы столкнетесь с клиентом или дизайнером, который непреклонно представляет его определенным образом. Количество CSS и JavaScript, которые могут применяться к файловому полю, крайне ограничено из-за соображений о безопасности, наложенных браузерами. Типизация файла может быть затруднена. Если внешний вид очень важен для вас, я рекомендую вам прочитать одну из статей «Питер-Пол Кох» типа ввода = «файл».
Переходим на сервере и работаем с PHP
Информация о загрузке файла предоставляется с помощью многомерного массива $_FILES
. Этот массив обладает своей структурой, назначенными именами для полей файла в форме HTML, точно так же, как и при работе с $_GET
и $_POST
. Затем массив каждого файла содержит следующие элементы:
- $_FILES["myFile"]["name"] - хранит исходное имя файла;
- $_FILES["myFile"]["type"] - сохраняет mime-типа файла;
- $_FILES["myFile"]["size"] - сохраняет размер файла (в байтах);
- $_FILES["myFile"]["tmp_name"] - хранит имя временного файла;
- $_FILES["myFile"]["error"] - хранит код ошибки, полученный в результате передачи.
При помощи функции move_uploaded_file()
мы можем перенести файл из своего временного каталога в постоянное место. Так же хорошей практикой является использовать именно её вместо copy()
и rename()
для этой цели, поскольку она выполняет дополнительные проверки, чтобы гарантировать, что файл был действительно загружен запросом HTTP методом POST.
Если вы собираетесь сохранить файл с исходным именем, предоставленным пользователем, рекомендуется убедиться, что это безопасно. Имя файла не должно содержать символов, которые могут повлиять на путь назначения, например, косая черта (слэш). Имя файла должно быть уникальным, чтоб избежать затирания существующих с тем же именем (если это не предусматривается вашим приложением). Гарантировать это можно заменяя любые символы символом подчеркивания, который не является буквой, а затем добавляя увеличивающийся номер, если файл с таким именем уже существует.
Вот так как выглядит получение и обработка загрузки файла при помощи PHP:
define("UPLOAD_DIR", "/srv/www/uploads/");
if (!empty($_FILES["myFile"])) {
$myFile = $_FILES["myFile"];
if ($myFile["error"] !== UPLOAD_ERR_OK) {
echo "<p>Произошла ошибка.</p>";
exit;
}
// обеспечиваем безопасное имени файла
$name = preg_replace("/[^A-Z0-9._-]/i", "_", $myFile["name"]);
// не перезаписываем существующий файл
$i = 0;
$parts = pathinfo($name);
while (file_exists(UPLOAD_DIR . $name)) {
$i++;
$name = $parts["filename"] . "-" . $i . "." . $parts["extension"];
}
// сохраняем файл из временного каталога
$success = move_uploaded_file($myFile["tmp_name"],
UPLOAD_DIR . $name);
if (!$success) {
echo "<p>Не удалось сохранить файл.</p>";
exit;
}
// устанавливаем правильные права для нового файла
chmod(UPLOAD_DIR . $name, 0644);
}
Сначала мы удостоверяемся, что PHP загрузка файла на сервер прошла без ошибок. Затем определяем безопасное имя файла, как я только что описал выше, а затем перемещаем файл в его конечный каталог с помощью move_uploaded_file()
. И наконец делаем вызов chmod()
, чтобы убедиться, что в новом файле установлены необходимые права доступа.
Вопросы безопасности
Вы бы позволили совершенно незнакомым людям хранить файлы на наших персональных компьютерах? Не думаю так, и все же это именно то, что вы делаете, когда разрешаете загрузку файлов в вашем приложении. Вы можете намереваться загрузить изображение пользователя для страницы профиля, но что, если он попытается загрузить специально созданный, наполненный вирусами исполняемый файл? Я хотел бы поделиться несколькими шагами, которые вы можете предпринять, чтобы минимизировать риски безопасности, связанные с возможностью загрузки файлов.
Один из них заключается в том, чтобы проверить тип загружаемого файла, каким он должен быть. Опираться на значение $_FILES["myFile"]["type"]
или на расширение имени файла не является безопасным, поскольку оба могут легко подделываться. Скорее, используйте функцию exif_imagetype()
, чтобы проверить содержимое файла и определить, действительно ли это GIF, JPEG или один из нескольких других поддерживаемых форматов изображений. Если exif_imagetype()
недоступен (функция требует, чтобы расширение Exif было включено), вы можете использовать getimagesize()
. Массив, возвращаемый ей, будет содержать тип изображения, если он распознан.
// проверяем файл GIF, JPEG или PNG
$fileType = exif_imagetype($_FILES["myFile"]["tmp_name"]);
$allowed = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
if (!in_array($fileType, $allowed)) {
// тип файла не разрешен
...
Для файлов без изображения вы можете использовать exec()
для вызова утилиты файлов unix. он определяет тип файла, ища известные двоичные подписи в ожидаемых местах.
// проверяем файл в формате PDF
$mime = "application/pdf; charset=binary";
exec("file -bi " . $_FILES["myFile"]["tmp_name"], $out);
if ($out[0] != $mime) {
// файл не PDF
...
Еще один шаг, который вы можете предпринять, - наложить жесткие ограничения на общий размер запроса POST и количество файлов, которые можно загрузить. Для этого укажите соответствующее значение для директив upload_max_size
, post_max_size
и max_file_uploads
в php.ini. Директива upload_max_size указывает максимальный размер загрузки файла. В дополнение к размеру загрузки вы можете ограничить размер всего запроса POST директивой post_max_size
. max_file_uploads
- это новая директива (добавлена в версии 5.2.12), которая ограничивает количество загрузок файлов. Эти три директивы помогают защитить ваш сайт от атак, которые пытаются нарушить его доступность, вызывая интенсивный сетевой трафик или загрузку системы.
post_max_size = 8M
upload_max_size = 2M
max_file_uploads = 20
Третий шаг, который вы можете предпринять для минимизации риска, - это сканирование загруженных файлов с помощью антивирусного сканера. Это жизненно важно для защиты от распространённых вирусов и вредоносных программ, особенно если ваш сайт осуществляет файлообмен между разными людьми, как пример - вложения в веб-почтовый клиент или (юридический) сайт для обмена файлами. Существует расширение PHP, которое обеспечивает доступ к ClamAV, но, конечно, вы можете вызвать утилиту командной строки ClamAV так же, как я продемонстрировал для файла.
exec("clamscan --stdout " . $_FILES["myFile"]["tmp_name"], $out, $return);
if ($return) {
// файл заражен
...
Подводим итоги и делаем выводы
Сегодня вы узнали, как происходит настройка и осуществляется процесс PHP загрузки файлов на сервер с вашего сайта или веб-приложения. Чтобы загрузка была успешной, форма HTML должна быть отправлена через запрос POST с множественным форматированием данных, а PHP должен разрешать передачу, как указано, с помощью директивы file_uploads
. После переноса файла, сценарий, ответственный за обработку загрузки, использует информацию, найденную в массиве $_FILES
, чтобы переместить файл из временного каталога в нужное место. Я также поделился некоторыми дополнительными мерами предосторожности, которые вы можете предпринять, чтобы защитить себя и своих пользователей от некоторых рисков, связанных с возможностью загрузки файлов. Чтобы гарантировать свою безопасность - проверяйте тип файла, наложите жесткие ограничения на загрузку трафика и применяйте сканирование на наличие вирусов.
Для тех, кто может быть заинтересован, дополнительный код для этой статьи доступен на GitHub. Вы можете просматривать, загружать или клонировать репозиторий и играть с кодом, чтобы лучше понять, как работает процесс загрузки файлов.