1
0
mirror of https://github.com/php/doc-ru.git synced 2026-04-24 15:58:39 +02:00
Files
archived-doc-ru/features/file-upload.xml
T

630 lines
33 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: fc174e8d6162091550edde46159917ee7e5a2e73 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>
Также ознакомьтесь с описанием директив <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> конфигурационного
файла &php.ini;
</para>
</note>
<para>
Также следует заметить, что PHP поддерживает загрузку файлов методом PUT,
который используется в клиентах <productname>Netscape Composer</productname>
и W3C <productname>Amaya</productname>. Для получения
более детальной документации обратитесь к разделу
<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-тип файла, в случае, если браузер предоставил такую
информацию. В качестве примера можно привести <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>
Временное имя, с которым принятый файл был сохранён на сервере.
</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>
По умолчанию принятые файлы сохраняются на сервере в стандартной
временной папке до тех пор, пока не будет задана другая директория при
помощи директивы <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>
для исключения файлов, которые не удовлетворяют критерию касательно
типа файла, однако, принимайте во внимание, что это поле полностью
контролируется клиентом, используйте его только в качестве первой
из серии проверок. Также вы можете использовать
<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>
По окончанию работы скрипта, если загруженный файл не был
переименован или перемещён, он будет автоматически удалён из временной папки.
</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; Загружаемый файл был получен только частично.
</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>
Поскольку разные системы по-разному работают с файловой структурой,
нет никаких гарантий того, что файлы с экзотическими именами
(например, которые содержат пробельные символы) будут обработаны корректно.
</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-форме, что и для множественных полей 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>
В случае, если такая форма была отправлена, массивы
<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>
Переменные <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-запросами и выглядят примерно так:
<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>.
Как правило, эта директива расположена внутри блока
<literal>&lt;Directory&gt;</literal> или же внутри блока
<literal>&lt;VirtualHost&gt;</literal>. Сама запись выглядит
следующим образом:
<informalexample>
<programlisting>
<![CDATA[
Script PUT /put.php
]]>
</programlisting>
</informalexample>
</para>
<simpara>
Это указывает веб-серверу Apache на необходимость перенаправлять
все PUT-запросы, контекст которых совпадает с контекстом, в
которым вы разместили эту строку, в файл <filename>put.php</filename>.
Предполагается, что файлы с расширением <filename>.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
-->