-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathfile-upload.xml
More file actions
553 lines (526 loc) · 29.5 KB
/
file-upload.xml
File metadata and controls
553 lines (526 loc) · 29.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 3944dc63330edde959cfd3e7d113c999cbec10ff 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>
<simpara>
Значение кода ошибки определяется константой семейства
<constant>UPLOAD_ERR_<replaceable>*</replaceable></constant>.
</simpara>
</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><Directory></literal> или, возможно, внутри блока
<literal><VirtualHost></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
-->