Files
php-doc-ru/features/file-upload.xml
2024-10-16 05:30:01 +03:00

624 lines
32 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 04d9aded7bbd447523cf038ddf88e6d7f7e43c53 Maintainer: shein Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="features.file-upload" xmlns="http://docbook.org/ns/docbook">
<title>Загрузка файлов на сервер</title>
<sect1 xml:id="features.file-upload.post-method">
<title>Загрузка файлов методом POST</title>
<simpara>
Через этот механизм загружают как текстовые, так и бинарные файлы.
Через PHP-функции аутентификации и работы с файлами программист получает
полный контроль над тем, кому можно загружать файлы на сервер, и что делать с файлом после загрузки.
</simpara>
<simpara>
PHP умеет принимать загруженные файлы из браузеров,
которые совместимы со стандартом RFC-1867.
</simpara>
<note>
<title>Смежные замечания по конфигурации</title>
<para>
Ознакомьтесь также с описанием директив конфигурационного
файла &php.ini;:
<link linkend="ini.file-uploads">file_uploads</link>,
<link linkend="ini.upload-max-filesize">upload_max_filesize</link>,
<link linkend="ini.upload-tmp-dir">upload_tmp_dir</link>,
<link linkend="ini.post-max-size">post_max_size</link>
и <link linkend="ini.max-input-time">max_input_time</link>.
</para>
</note>
<para>
PHP также поддерживает загрузку файлов методом PUT,
через который загружают файлы на сервер клиенты <productname>Netscape Composer</productname>
и <productname>Amaya</productname> консорциума W3C. Подробнее
об этом методе рассказывает раздел
«<link linkend="features.file-upload.put-method">Поддержка метода PUT</link>».
</para>
<para>
<example>
<title>Форма для загрузки файлов</title>
<para>
Страница загрузки файлов на сервер реализуется
через форму, которая выглядит примерно так:
</para>
<programlisting role="html">
<![CDATA[
<!-- Тип кодирования данных, enctype, требуется указывать только так, как показывает пример -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
<!-- Поле MAX_FILE_SIZE требуется указывать перед полем загрузки файла -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Название элемента input определяет название элемента в суперглобальном массиве $_FILES -->
Отправить файл: <input name="userfile" type="file" />
<input type="submit" value="Отправить файл" />
</form>
]]>
</programlisting>
<para>
В приведённом примере значение <literal>__URL__</literal> нужно заменить ссылкой
на PHP-файл.
</para>
<para>
Скрытое поле <literal>MAX_FILE_SIZE</literal> (значение
требуется указывать в байтах) должно идти перед полем
выбора файла. Значение поля указывает максимальный
размер файла, который принимает PHP.
Рекомендуется добавлять этот элемент в форму, поскольку
он не заставляет пользователя ждать окончания передачи большого
файла, а только потом узнавать, что файл оказался
слишком большим и передача не состоялась.
Имейте в виду: обойти это ограничение на стороне браузера
легко, поэтому не рассчитывайте, что эта функция
заблокирует файлы большего размера. Это только удобная функция
для пользователей клиентской части приложения.
Однако серверные PHP-настройки, которые касаются максимального размера,
обойти невозможно.
</para>
</example>
</para>
<note>
<para>
Проверьте, что форма загрузки содержит атрибут
<literal>enctype="multipart/form-data"</literal>, иначе
загрузка файлов на сервер не будет работать.
</para>
</note>
<para>
Суперглобальный массив <varname>$_FILES</varname> содержит полную информацию о файлах,
которые загрузили на сервер. Содержимое массива после отправки приведённой формы выводит пример на этой странице.
Обратите внимание, здесь элемент с выбором файла называется <emphasis>userfile</emphasis>,
как в приведённом примере. Полю выбора файла разрешается присваивать произвольное имя.
<variablelist>
<varlistentry>
<term><varname>$_FILES['userfile']['name']</varname></term>
<listitem>
<para>
Исходное название файла на компьютере клиента.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>$_FILES['userfile']['type']</varname></term>
<listitem>
<para>
MIME-тип файла, если браузер отправил такую информацию.
Пример MIME-типа: «<literal>image/gif</literal>».
MIME-тип не проверяется на стороне PHP, поэтому значение не принимают
без проверки.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>$_FILES['userfile']['size']</varname></term>
<listitem>
<para>
Размер принятого файла в байтах.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>$_FILES['userfile']['tmp_name']</varname></term>
<listitem>
<para>
Временное имя файла, под которым PHP хранил файл, который загрузили на сервер.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>$_FILES['userfile']['error']</varname></term>
<listitem>
<para>
<link linkend="features.file-upload.errors">Код ошибки</link>,
которая возникает при загрузке файла.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>$_FILES['userfile']['full_path']</varname></term>
<listitem>
<para>
Полный путь, который отправил браузер. Это значение не всегда содержит реальную структуру
каталогов и ему нельзя доверять. Поле доступно с PHP 8.1.0.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
По умолчанию PHP сохраняет принятые файлы на сервере в стандартной
вре́менной папке до тех пор, пока через директиву <link linkend="ini.upload-tmp-dir">upload_tmp_dir</link>
конфигурационного файла &php.ini; не зададут другой каталог.
На сервере директорию по умолчанию можно изменить через переменную <envar>TMPDIR</envar> того окружения,
в котором работает PHP. Установка переменной
функцией <function>putenv</function> внутри PHP-скрипта работать
не будет. Через эту переменную окружения также проверяют,
что другие операции тоже работают с принятыми файлами.
<example>
<title>Проверка файлов, которые загрузили на сервер</title>
<para>
Дополнительную информацию дают описания функций
<function>is_uploaded_file</function>
и <function>move_uploaded_file</function>. Следующий пример
принимает и обрабатывает файл, который загрузили на сервер через форму.
</para>
<programlisting role="php">
<![CDATA[
<?php
$uploaddir = '/var/www/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
echo '<pre>';
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "Файл не содержит ошибок и успешно загрузился на сервер.\n";
} else {
echo "Возможная атака на сервер через загрузку файла!\n";
}
echo 'Дополнительная отладочная информация:';
print_r($_FILES);
print "</pre>";
?>
]]>
</programlisting>
</example>
</para>
<simpara>
PHP-скрипт, который принимает файл, должен реализовывать логику
определения того, что требуется сделать с файлом, который загрузили на сервер.
Можно, например, проверить переменную <varname>$_FILES['userfile']['size']</varname>,
чтобы отсечь чрезмерно большие или слишком мелкие файлы. Можно также
использовать переменную <varname>$_FILES['userfile']['type']</varname>,
чтобы выбросить файлы, которые не соответствуют заданным критериям типа файла.
Но выполняйте такую проверку только как первую в серии проверок, потому что это значение
контролируется клиентом и не проверяется на стороне PHP.
Кроме того, можно использовать переменную <varname>$_FILES['userfile']['error']</varname>
и планировать логику поведения кода с учётом <link linkend="features.file-upload.errors">кодов ошибок</link>.
При любой логике требуется либо удалить файл из временного каталога,
либо переместить файл в другую директорию.
</simpara>
<simpara>
Если при отправке формы файл не выбрали, PHP установит
для переменной <varname>$_FILES['userfile']['size']</varname> значение 0, а переменной
<varname>$_FILES['userfile']['tmp_name']</varname> — none.
</simpara>
<simpara>
PHP удалит файл из временного каталога в конце запроса, если файл не переместили
или не переименовали.
</simpara>
<example>
<title>Загрузка массива файлов</title>
<para>
PHP поддерживает <link linkend="faq.html.arrays">передачу массива из HTML-формы</link>
даже с файлами.
</para>
<programlisting role="html">
<![CDATA[
<form action="" method="post" enctype="multipart/form-data">
<p>Изображения:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Отправить" />
</p>
</form>
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
foreach ($_FILES["pictures"]["error"] as $key => $error) {
if ($error == UPLOAD_ERR_OK) {
$tmp_name = $_FILES["pictures"]["tmp_name"][$key];
// Функция basename() помогает защититься от атак на файловую систему;
// иногда требуется дополнительная проверка или очистка имени файла
$name = basename($_FILES["pictures"]["name"][$key]);
move_uploaded_file($tmp_name, "data/$name");
}
}
?>
]]>
</programlisting>
</example>
<para>
Полосу прогресса загрузки файлов можно реализовать через
«<link linkend="session.upload-progress">Отслеживание прогресса загрузки файлов через сессии</link>».
</para>
</sect1>
<sect1 xml:id="features.file-upload.errors">
<title>Объяснение сообщений об ошибках</title>
<simpara>
PHP наряду с другими атрибутами принятого файла возвращает код ошибки.
Код ошибки хранится в массиве, который PHP создаёт
при загрузке файла, и доступен при обращении по ключу
<literal>error</literal>. Другими словами, код ошибки
находят в переменной <varname>$_FILES['userfile']['error']</varname>.
</simpara>
<para>
<variablelist>
<varlistentry>
<term><constant>UPLOAD_ERR_OK</constant></term>
<listitem>
<para>
Значение: 0; ошибки не возникали, файл успешно загрузился на сервер.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_INI_SIZE</constant></term>
<listitem>
<para>
Значение: 1; размер принятого файла превысил максимально допустимый
размер, который задали директивой <link linkend="ini.upload-max-filesize">upload_max_filesize</link>
конфигурационного файла &php.ini;.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_FORM_SIZE</constant></term>
<listitem>
<para>
Значение: 2; размер загружаемого файла превысил значение <emphasis>MAX_FILE_SIZE</emphasis>,
которое указали в HTML-форме.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_PARTIAL</constant></term>
<listitem>
<para>
Значение: 3; PHP получил загружаемый файл частично.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_NO_FILE</constant></term>
<listitem>
<para>
Значение: 4; файл не загрузился.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_NO_TMP_DIR</constant></term>
<listitem>
<para>
Значение: 6; отсутствует временная папка.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_CANT_WRITE</constant></term>
<listitem>
<para>
Значение: 7; не удалось записать файл на диск.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>UPLOAD_ERR_EXTENSION</constant></term>
<listitem>
<para>
Значение: 8; модуль PHP остановил загрузку файла. PHP
не даёт способа определить, какой модуль остановил
загрузку файла; в этом помогает просмотр списка загруженных
модулей, который возвращает функция <function>phpinfo</function>.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</sect1>
<sect1 xml:id="features.file-upload.common-pitfalls">
<title>Распространённые подводные камни</title>
<simpara>
Элементу <literal>MAX_FILE_SIZE</literal> нельзя указывать размер файла,
который превышает предел, который установили в директиве
<link linkend="ini.upload-max-filesize">upload_max_filesize</link> файла &php.ini;.
Ограничение по умолчанию составляет 2 мегабайта.
</simpara>
<simpara>
Если установили ограничения памяти, может потребоваться
увеличение значения опции <link linkend="ini.memory-limit">memory_limit</link>.
Убедитесь, что значение директивы <link linkend="ini.memory-limit">memory_limit</link>
достаточно.
</simpara>
<simpara>
Время, которое потребуется для работы скрипта, может превысить
значение опции <link linkend="ini.max-execution-time">max_execution_time</link>,
если для директивы установили маленькое значение.
Убедитесь, что значение директивы <literal>max_execution_time</literal> достаточно.
</simpara>
<note>
<simpara>
Директива <link linkend="ini.max-execution-time">max_execution_time</link>
влияет только на время выполнения самого скрипта.
Время, которое заняли действия за пределами скрипта, — системные вызовы
функций <function>system</function> или <function>sleep</function>, запросы
к базе данных, время, которое заняла загрузка файла на сервер, и т. д. —
не учитывается при определении максимального времени работы скрипта.
</simpara>
</note>
<warning>
<simpara>
Директива <link linkend="ini.max-input-time">max_input_time</link> устанавливает
максимальное время в секундах, в течение которого скрипту разрешается получать входные данные;
время загрузки файла тоже включается. Загрузка больших файлов, набора файлов
или обработка запросов пользователей с медленными соединениями может превысить значение по умолчанию
в <literal>60</literal> секунд.
</simpara>
</warning>
<simpara>
Сервер не загрузит большие файлы, если
для директивы <link linkend="ini.post-max-size">post_max_size</link>
установили слишком маленькое значение.
Проверьте, что значение директивы <literal>post_max_size</literal> достаточно.
</simpara>
<simpara>
Опция
<link linkend="ini.max-file-uploads">max_file_uploads</link>
контролирует максимальное количество файлов, которые загружают на сервер в течение
одного запроса. Суперглобальный массив <varname>$_FILES</varname>
прекратит обработку файлов, как только достигнет ограничения,
если загружается больше файлов, чем в ограничении.
Например, если значение директивы
<link linkend="ini.max-file-uploads">max_file_uploads</link> равняется
<literal>10</literal>, массив <varname>$_FILES</varname> не будет
содержать больше 10 элементов.
</simpara>
<simpara>
Без проверки того, с каким файлом ведётся работа, пользователи смогут
получить доступ к конфиденциальной информации других каталогов.
</simpara>
<simpara>
Из-за многообразия стилей, в которых файловые системы ведут список каталогов,
PHP не гарантирует правильную обработку файлов с экзотическими именами,
например файлов с пробелами в именах.
</simpara>
<simpara>
Разработчикам нельзя смешивать обычные <literal>input</literal>-поля и поля загрузки файлов
в одной и той же переменной формы (например, нельзя указывать в имени <literal>input</literal>-элемента
значение <literal>foo[]</literal>).
</simpara>
</sect1>
<sect1 xml:id="features.file-upload.multiple">
<title>Загрузка нескольких файлов</title>
<simpara>
Набор файлов загружают через разные значения
атрибута <literal>name</literal> в <literal>input</literal>-элементах формы.
</simpara>
<simpara>
Возможно также загружать набор файлов одновременно
и автоматически получать файлы в виде массива.
Для этого в HTML-форме пользуются тем же синтаксисом отправки массива,
что и для отправки списка значений через HTML-элементы SELECT или поля ввода с типом checkbox:
</simpara>
<para>
<example>
<title>Загрузка нескольких файлов</title>
<programlisting role="html">
<![CDATA[
<form action="file-upload.php" method="post" enctype="multipart/form-data">
Файлы:<br />
<input name="userfile[]" type="file" /><br />
<input name="userfile[]" type="file" /><br />
<input type="submit" value="Отправить" />
</form>
]]>
</programlisting>
</example>
</para>
<simpara>
При отправке приведённой формы PHP инициализирует массивы
<varname>$_FILES['userfile']</varname>,
<varname>$_FILES['userfile']['name']</varname>
и <varname>$_FILES['userfile']['size']</varname>.
</simpara>
<simpara>
Предположим, что отправили файлы
<filename>/home/test/review.html</filename>
и <filename>/home/test/xwp.out</filename>. Тогда переменная
<varname>$_FILES['userfile']['name'][0]</varname> будет содержать значение
<filename>review.html</filename>, а переменная
<varname>$_FILES['userfile']['name'][1]</varname>
<filename>xwp.out</filename>. Аналогично, переменная
<varname>$_FILES['userfile']['size'][0]</varname> будет содержать размер
файла <filename>review.html</filename> и так далее.
</simpara>
<simpara>
PHP также инициализирует переменные <varname>$_FILES['userfile']['name'][0]</varname>,
<varname>$_FILES['userfile']['tmp_name'][0]</varname>,
<varname>$_FILES['userfile']['size'][0]</varname>
и <varname>$_FILES['userfile']['type'][0]</varname>.
</simpara>
<warning>
<simpara>
Параметр конфигурации
<link linkend="ini.max-file-uploads">max_file_uploads</link>
ограничивает количество файлов, которое разрешается загружать
за один запрос. Потребуется проверить, что форма
не пытается загрузить за один запрос больше файлов, чем ограничение.
</simpara>
</warning>
<para>
<example>
<title>Загрузка каталога</title>
<simpara>
В HTML-полях выбора файла для загрузки указывают атрибут <literal>webkitdirectory</literal>,
чтобы загрузить весь каталог.
Бо́льшая часть браузеров поддерживает эту функцию.
</simpara>
<simpara>
Информация, которую хранит элемент <literal>full_path</literal>, помогает сохранить
относительные пути или воссоздать такой же каталог на сервере.
</simpara>
<programlisting role="html">
<![CDATA[
<form action="file-upload.php" method="post" enctype="multipart/form-data">
Загрузка каталога:<br />
<input name="userfile[]" type="file" webkitdirectory multiple />
<input type="submit" value="Загрузить файлы" />
</form>
]]>
</programlisting>
</example>
<warning>
<simpara>
Атрибут <literal>webkitdirectory</literal> нестандартен и не соответствует спецификациям.
Не рекомендуется включать атрибут в элементы форм на рабочих сайтах: атрибут работает не у каждого пользователя.
Способ обработки атрибута несовместим между браузерами, и поведение могут изменить в будущих выпусках.
</simpara>
<simpara>
PHP анализирует только информацию об относительном пути, которую отправили браузер или пользовательский агент,
и передаёт информацию в суперглобальный массив <varname>$_FILES</varname>.
Нет гарантии, что значения в массиве <literal>full_path</literal> содержат реальную структуру каталогов,
и PHP-приложение не должно доверять этой информации.
</simpara>
</warning>
</para>
</sect1>
<sect1 xml:id="features.file-upload.put-method">
<title>Поддержка метода PUT</title>
<para>
PHP поддерживает HTTP-метод PUT, через который отдельные клиенты
отправляют файлы на сервер для хранения.
PUT-запросы проще, чем загрузка файлов POST-запросами, а выглядят PUT-запросы примерно так:
<informalexample>
<programlisting role="HTTP">
<![CDATA[
PUT /path/filename.html HTTP/1.1
]]>
</programlisting>
</informalexample>
</para>
<para>
Обычно такой вызов означает, что удалённый клиент хотел бы сохранить
файл <filename>/path/filename.html</filename>
в дереве каталогов веб-сервера.
Не сомневаемся, что настраивать веб-сервер Apache или PHP так, чтобы они разрешали
каждому автоматически перезаписывать файлы веб-сервера, — плохая идея.
Поэтому, чтобы обработать такой запрос, потребуется сначала сообщить веб-серверу, чтобы
запрос обрабатывал конкретный PHP-скрипт. На веб-сервере Apache
это делается через директиву <emphasis>Script</emphasis>.
Обычно директиву записывают в произвольном месте конфигурационного файла веб-сервера Apache
внутри блока <literal>&lt;Directory&gt;</literal> или, возможно, внутри блока
<literal>&lt;VirtualHost&gt;</literal>. Строка наподобие этой
укажет веб-серверу конкретный PHP-файл для обработки запроса:
<informalexample>
<programlisting>
<![CDATA[
Script PUT /put.php
]]>
</programlisting>
</informalexample>
</para>
<simpara>
Строка говорит веб-серверу Apache перенаправлять
каждый PUT-запрос к URI-идентификаторам, которые соответствуют контексту,
в котором записали строку, в файл <filename>put.php</filename>.
Предполагается, что файлы с расширением <filename class="extension">.php</filename>
обрабатываются как PHP-скрипты, и что сам PHP активен.
Ресурсом назначения для PUT-запросов к этому скрипту должен
быть сам скрипт, а не имя, которое требуется дать загружаемому файлу.
</simpara>
<simpara>
Затем внутри файла put.php разработчик мог бы написать код наподобие следующего примера.
Пример скопирует содержимое загруженного файла в файл <filename>myputfile.ext</filename> на сервере.
Возможно, потребуются проверки и (или) аутентификация пользователя перед копированием файла.
</simpara>
<para>
<example>
<title>Сохранение файлов, которые отправили HTTP-методом PUT</title>
<programlisting role="php">
<![CDATA[
<?php
/* PUT-данные приходят в стандартный поток входных данных stdin */
$putdata = fopen("php://input", "r");
/* Открываем файл для записи */
$fp = fopen("myputfile.ext", "w");
/* Читаем 1 KB данных за один раз
и записываем в файл */
while ($data = fread($putdata, 1024)) {
fwrite($fp, $data);
}
/* Закрываем потоки */
fclose($fp);
fclose($putdata);
?>
]]>
</programlisting>
</example>
</para>
</sect1>
<sect1 xml:id="features.file-upload.errors.seealso">
&reftitle.seealso;
<para>
<simplelist>
<member><link linkend="security.filesystem">Безопасность файловой системы</link></member>
</simplelist>
</para>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->