Меню
Бесплатно
Главная  /  Программы  /  Загрузка файлов на сервер с помощью PHP. Основные уязвимости и способы их избежать

Загрузка файлов на сервер с помощью PHP. Основные уязвимости и способы их избежать

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

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

Первая и самая очевидная - это имена файлов . Их обязательно нужно проверять на спецсимволы, так как пользователь может подделать HTTP-запрос, в результате чего загружаемый файл будет иметь имя, например, ../index.php. и при попытке его сохранения затрет корневой индекс. Кроме того, название может содержать русские буквы в кодировке windows-1251 или koi-8, которые некорректно сохранятся в файловой системе. Вывод: нужно сохранять файл не под тем именем, под которым его загрузил пользователь, а под случайным, например MD5-хешем от имени файла, времени загрузки и IP пользователя. Имя же этого файла где-нибудь в базе, а потом отдавать файл скритом, который предварительно будет выдавать заголовок Content-disposition: attachment; filename="имя_файла".

Вторая проблема - нельзя доверять расширению или MIME-типу , их при желании можно подделать. Поэтому если тип файла важен, его нужно проверять на соответствие формату уже на сервере (для картинок хорошо подойдет функция getimagesize из модуля GD, для других типов - чтение заголовков) и отвергать те файлы, у которых формат не соответствует.

И наконец, третья, самая главная проблема - . Тут есть несколько решений. Первое - если предполагается загрузка только определенных видов файлов (например, автар может быть только картинкой в PNG, JPG, GIF) и отвергать все, что не подходит. Но иногда требуется разрешить загружать файлы любых типов. Тогда возникает второй вариант: проверять расширение загруженного файла и, если оно небезопасно, переименовывать его (например, замена расширения с.php на.phps приведет к тому, что скрипт не будет выполняться, а будет показан его код с подсветкой синтаксиса). Главный недостаток этого решения - может оказаться, что на каком-то сервере настроено исполнение скриптов с необычном расширением, например, .php3, и отфильтровать это не получится. И наконец, третий вариант - это отключить обработку скриптов, например через.htaccess:

RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml
AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp

Однако следует учесть, что файл.htaccess влияет только на Apache (да и то его использование нужно включить в настройках), а на других серверах он будет проигнорирован. (Особенно важно это для скриптов, которые выкладываются в публичный доступ: рано или поздно найдется пользователь с каким-нибудь IIS, который не примет должных мер, поэтому лучше сочетать этот способ с предыдущим.)

И последнее: после прочтения этого текста может возникнуть желание хранить пользовательские файлы вообще в базе данных. Не стоит этого делать: хотя это и кажется простым решением, но следует учитывать, что современные поисковики индексируют не только обычные HTML-страницы, но и файлы других типов. И в момент прохождения поискового робота нагрузка на SQL-сервер будет резко возрастать из-за необходимости отдать сразу большой объем данных, что будет приводить к проблемам в работе сайта.

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

Давайте напомню код, который мы вчера рассматривали:

$uploadfile = "images/".$_FILES["somename"]["name"];
move_uploaded_file($_FILES["somename"]["tmp_name"], $uploadfile);
?>

Фактически, на данный момент может быть загружено абсолютно всё, что угодно: любые исполняемые файлы, скрипты, HTML-страницы и другие весьма опасные вещи. Поэтому обязательно надо проверять загружаемые файлы очень тщательно. И сейчас мы с Вами займёмся их тщательной проверкой.

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

  1. Тип - только jpg (jpeg ).
  2. Размер - менее 100 КБ .
" в соответствии с этими требованиями:

$blacklist = array(".php", ".phtml", ".php3", ".php4", ".html", ".htm");
foreach ($blacklist as $item)
if(preg_match("/$item\$/i", $_FILES["somename"]["name"])) exit;
$type = $_FILES["somename"]["type"];
$size = $_FILES["somename"]["size"];
if (($type != "image/jpg") && ($type != "image/jpeg")) exit;
if ($size > 102400) exit;
$uploadfile = "images/".$_FILES["somename"]["name"];
move_uploaded_file($_FILES["somename"]["tmp_name"], $uploadfile);
?>

Теперь давайте подробно поясню, что здесь происходит. Первым делом мы проверяем на расширение загружаемого файла . Если оно представляет собой PHP-скрипт , то мы такой файл просто не пропускаем. Дальше мы получаем MIME-type и размер. Проверяем их на удовлетворение нашим условиям. Если всё хорошо, то мы загружаем файл.

Вы, наверное, можете спросить: "А зачем надо проверять и расширение, и MIME-type? ". Тут очень важно понимать, что это далеко не одно и то же. Если злоумышленник попытается отправить PHP-файл через браузер , то и одной проверки MIME-type хватит, чтобы его попытка провалилась. А вот если он напишет какой-нибудь скрипт, который будет формировать запрос и отсылать вредосный файл, то этого не хватит. Почему? А потому, что MIME-type задаётся клиентом, а не сервером! И фактически, злоумышленник может поставить любой MIME-type (и картинки тоже), но при этом отсылать PHP-скрипт . И вот именно такую хитрую попытку мы и ломаем, проверяя на расширение файла.

Я сразу скажу, что данный код далеко не 100% защита (100% просто не существует), однако, взломать такой код будет очень и очень тяжело, поэтому можете смело утверждать, что Вы обеспечили высокую безопасность при загрузке файлов на сервер через PHP .

1 53

GreyCat

1 ответ:

модель процессора, описанная в руководстве по процессору Intel/AMD, является довольно несовершенной моделью для real двигатель исполнения современного ядра. В частности, понятие регистров процессора не соответствует действительности, нет такого понятия, как регистр EAX или RAX.

одной из основных задач декодера инструкций является преобразование устаревших инструкций x86 / x64 в micro-ops , инструкции RISC-подобного процессора. Небольшие инструкции, которые легки для того чтобы исполнить одновременно и мочь принять преимущество множественных подблоков исполнения. Позволяет одновременно выполнять до 6 инструкций.

чтобы сделать эту работу, понятие регистров процессора также виртуализируется. Декодер команд выделяет регистр из большого банка регистров. Когда инструкция пенсии , значение этого динамически выделенного регистра записывается обратно в любой регистр, который в настоящее время содержит значение ну, скажем, РАКС.

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

такая же проблема с тем, как вы как его на работу. Большая проблема, она требует, чтобы два значения регистра были объединены когда инструкция будет удалена. Создание зависимости данных, которая будет засорять ядро. Заставляя верхний 32-бит равняться 0, эта зависимость мгновенно исчезает, больше не нужно сливаться. Скорость выполнения деформации 9.

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

§1. Общие принципы

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

Для валидации картинки по URL мы будем использовать функцию getimagesizefromstring() , т. к. cURL скачает её в переменную для дальнейших манипуляций.

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

§2. Правила безопасности

Безопасность загрузки изображений сводится к недопущению попадания на сервер чужеродного кода и его выполнения. На практике загрузка картинок наиболее уязвимое место в PHP-приложениях: попадание shell-скриптов , запись вредоносного кода в бинарные файлы , подмена EXIF-данных . Для того, чтобы избежать большинства методов взлома нужно придерживаться следующих правил:

а не доверять данным из $_FILES;
б не проверять MIME-тип картинки из функции getimagesize();
в загружаемому файлу генерировать новое имя и расширение;
г запретить выполнение PHP-скриптов в папке с картинками;
д не вставлять пользовательские данные через require и include;
е для $_FILES использовать is_uploaded_file() и move_uploaded_file().

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

§3. Конфигурация php.ini

PHP позволяет внести определённые конфигурационные значения в процесс загрузки любых файлов. Для этого необходимо в файле php.ini найти блоки «Resource Limits », «Data Handling » и «File Uploads », а затем отредактировать, по необходимости, следующие значения:

; Максимальное время выполнения скрипта в секундах max_execution_time = 60 ; Максимальное потребление памяти одним скриптом memory_limit = 64M ; Максимально допустимый размер данных отправляемых методом POST post_max_size = 5M ; Разрешение на загрузку файлов file_uploads = On ; Папка для хранения файлов во время загрузки upload_tmp_dir = home/user/temp ; Максимальный размер загружаемого файла upload_max_filesize = 5M ; Максимально разрешённое количество одновременно загружаемых файлов max_file_uploads = 10

Исходя из указанных значений, пользователь не сможет за один раз загрузить больше десяти файлов, причём каждый файл не должен превышать 5 Мбайт. Параметры из блока «Resource Limits » больше нужны для загрузки удалённого файла, т. к. с помощью cURL мы будем скачивать содержимое в переменную и проверять её по нужным нам критериям, а для этого необходимо дополнительное время и память.

Конфигурационный файл php.ini всегда необходимо настраивать согласно бизнес-логики разрабатываемого веб-приложения. Например, мы планируем загружать не более десяти файлов до 5 Мбайт, а это значит нам понадобиться ~50 Мбайт памяти. Кроме того, нам нужно знать максимальное время загрузки одного файла с локальной машины и по ссылке, дабы установить достаточное время выполнения скрипта в max_execution_time и не пугать пользователей ошибками.

§4. Загрузка картинок из формы

Сейчас мы не будем рассматривать загрузку нескольких файлов на сервер, а разберём лишь саму механику загрузки на примере одного файла. Итак, для загрузки картинки с компьютера пользователя необходимо с помощью HTML-формы отправить файл PHP-скрипту методом POST и указать способ кодирования данных enctype="multipart/form-data" (в данном случае данные не кодируются и это значение применяется только для отправки бинарных файлов). С формой ниже мы будем работать дальше:

Для поля выбора файла мы используем имя name="upload" в нашей HTML-форме, хотя оно может быть любым. После отправки файла PHP-скрипту file-handler.php его можно перехватить с помощью суперглобальной переменной $_FILES["upload"] с таким же именем, которая в массиве содержит информацию о файле:

Array ( => picture.jpg // оригинальное имя файла => image/jpeg // MIME-тип файла => home\user\temp\phpD07E.tmp // бинарный файл => 0 // код ошибки => 17170 // размер файла в байтах )

Не всем данным из $_FILES можно доверять: MIME-тип и размер файла можно подделать, т. к. они формируются из HTTP-ответа, а расширению в имени файла не стоит доверять в силу того, что за ним может скрываться совершенно другой файл. Тем не менее, дальше нам нужно проверить корректно ли загрузился наш файл и загрузился ли он вообще. Для этого необходимо проверить ошибки в $_FILES["upload"]["error"] и удостовериться, что файл загружен методом POST с помощью функции is_uploaded_file() . Если что-то идёт не по плану, значит выводим ошибку на экран.

// Перезапишем переменные для удобства $filePath = $_FILES ["upload" ]["tmp_name" ]; $errorCode = $_FILES ["upload" ]["error" ]; // Проверим на ошибки if ($errorCode !== UPLOAD_ERR_OK || ! is_uploaded_file ($filePath )) { // Массив с названиями ошибок $errorMessages = [ UPLOAD_ERR_INI_SIZE => "Размер файла превысил значение upload_max_filesize в конфигурации PHP." , UPLOAD_ERR_FORM_SIZE => "Размер загружаемого файла превысил значение MAX_FILE_SIZE в HTML-форме." , UPLOAD_ERR_PARTIAL => "Загружаемый файл был получен только частично." , UPLOAD_ERR_NO_FILE => "Файл не был загружен." , UPLOAD_ERR_NO_TMP_DIR => "Отсутствует временная папка." , UPLOAD_ERR_CANT_WRITE => "Не удалось записать файл на диск." , UPLOAD_ERR_EXTENSION => "PHP-расширение остановило загрузку файла." , ]; // Зададим неизвестную ошибку $unknownMessage = "При загрузке файла произошла неизвестная ошибка." ; // Если в массиве нет кода ошибки, скажем, что ошибка неизвестна $outputMessage = isset ($errorMessages [$errorCode ]) ? $errorMessages [$errorCode ] : $unknownMessage ; // Выведем название ошибки die ($outputMessage ); }

Для того, чтобы злоумышленник не загрузил вредоносный код встроенный в изображение, нельзя доверять функции getimagesize() , которая также возвращает MIME-тип. Функция ожидает, что первый аргумент является ссылкой на корректный файл изображения . Определить настоящий MIME-тип картинки можно через расширение FileInfo . Код ниже проверит наличие ключевого слова image в типе нашего загружаемого файла и если его не окажется, выдаст ошибку:

// Создадим ресурс FileInfo $fi = finfo_open (FILEINFO_MIME_TYPE); // Получим MIME-тип $mime = (string) finfo_file ($fi , $filePath ); );

На данном этапе мы уже можем загружать абсолютно любые картинки на наш сервер, прошедшие проверку на MIME-тип, но для загрузки изображений по определённым характеристикам нам необходимо валидировать их с помощью функции getimagesize() , которой скормим сам бинарный файл $_FILES["upload"]["tmp_name"] . В результате мы получим массив максимум из 7 элементов :

Array ( => 1280 // ширина => 768 // высота => 2 // тип => width="1280" height="768" // аттрибуты для HTML => 8 // глубина цвета => 3 // цветовая модель => image/jpeg // MIME-тип )

Для дальнейшей валидации изображения и работы над ним нам необходиом знать только 3 значения: ширину , высоту и размер файла (для вычисления размера применим функцию filesize() для бинарного файла из временной папки).

// Результат функции запишем в переменную $image = getimagesize ($filePath ); $limitBytes = 1024 * 1024 * 5 ; $limitWidth = 1280 ; $limitHeight = 768 ; // Проверим нужные параметры if (filesize ($filePath ) > $limitBytes ) die ("Размер изображения не должен превышать 5 Мбайт." ); if ($image > $limitHeight ) die (); if ($image > $limitWidth ) die ();

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

// Сгенерируем новое имя файла на основе MD5-хеша $name = md5_file ($filePath ); // Сократим.jpeg до.jpg // Переместим картинку с новым именем и расширением в папку /pics if (! move_uploaded_file ($filePath , __DIR__ . "/pics/" . $name . $format )) { die ("При записи изображения на диск произошла ошибка." ); }

На этом загрузка изображения завершена. Для более удобной загрузки файлов можете использовать класс UploadedFile из пакета Symfony HttpFoundation , который является обёрткой для $_FILES и также сохраняет файл через move_uploaded_file() .

§5. Загрузка изображения по ссылке

Для загрузки изображения по ссылке нам понадобиться библиотека cURL , которая работает с удалёнными ресурсами. С помощью неё мы скачаем контент в переменную. С одной стороны может показаться, что для этих целей подойдёт file_get_contents() , но на самом деле мы не сможем контролировать объём скачиваемых данных и нормально обрабатывать все возникшие ошибки. Для того, чтобы cURL корректно скачал данные нам нужно: разрешить следовать перенаправлениям , включить проверку сертификата , указать максимальное время работы cURL (формируется за счёт объёма скачиваемых данных и средней скорости работы с ресурсом). Как правильно скачать файл в переменную показано ниже с необходимыми параметрами :

// Каким-то образом получим ссылку $url = "https://site.ru/picture.jpg" ; // Проверим HTTP в адресе ссылки if (! preg_match ("/^https?:/i" , $url ) && filter_var ($url , FILTER_VALIDATE_URL)) { die ("Укажите корректную ссылку на удалённый файл." ); } // Запустим cURL с нашей ссылкой $ch = curl_init ($url ); // Укажем настройки для cURL curl_setopt_array ($ch , [ // Укажем максимальное время работы cURL CURLOPT_TIMEOUT => 60 , // Разрешим следовать перенаправлениям CURLOPT_FOLLOWLOCATION => 1 , // Разрешим результат писать в переменную CURLOPT_RETURNTRANSFER => 1 , // Включим индикатор загрузки данных CURLOPT_NOPROGRESS => 0 , // Укажем размер буфера 1 Кбайт CURLOPT_BUFFERSIZE => 1024 , // Напишем функцию для подсчёта скачанных данных // Подробнее: http://stackoverflow.com/a/17642638 CURLOPT_PROGRESSFUNCTION => function ($ch , $dwnldSize , $dwnld , $upldSize , $upld ) { // Когда будет скачано больше 5 Мбайт, cURL прервёт работу if ($dwnld > 1024 * 1024 * 5 ) { return - 1 ; } }, // Включим проверку сертификата (по умолчанию) CURLOPT_SSL_VERIFYPEER => 1 , // Проверим имя сертификата и его совпадение с указанным хостом (по умолчанию) CURLOPT_SSL_VERIFYHOST => 2 , // Укажем сертификат проверки // Скачать: https://curl.haxx.se/docs/caextract.html CURLOPT_CAINFO => __DIR__ . "/cacert.pem" , ]); $raw = curl_exec ($ch ); // Скачаем данные в переменную $info = curl_getinfo ($ch ); // Получим информацию об операции $error = curl_errno ($ch ); // Запишем код последней ошибки // Завершим сеанс cURL curl_close ($ch );

Если всё прошло успешно и cURL уложился в 60 секунд, тогда содержимое по ссылке будет скачано в переменную $raw . Кроме того, функция curl_getinfo() вернёт информацию о проделанном запросе, откуда мы можем получить дополнительную информацию для анализа работы с удалёнными ресурсами:

Array ( => image/jpeg // MIME-тип из Content-Type => 200 // последний HTTP-код => 0 // количество перенаправлений => 0.656 // общее время работы cURL => 0.188 // время на соединение с хостом => 4504 // реальный размер полученных данных => 4504 // размер данных из Content-Length /* ... */ )

Дальше нам нужно проверить нет ли ошибок в curl_errno() и удостовериться, что ресурс отдаёт HTTP-код равный 200, иначе мы скажем, что по такому-то URL ничего не найдено. После всех проверок переменную $raw передаём в getimagesizefromstring() и работаем уже по отработанной схеме как в случае с загрузкой картинок из формы.

Обратите внимание, что мы валидируем размер файла в момент получения данных, т. к. мы не можем на 100% доверять curl_getinfo(), поскольку значения content_type, http_code, download_content_length формируются на основе полученных HTTP-заголовков. Скачивать файл полностью, а потом проверять количество байт потребует много времени и памяти. Поэтому мы контролировали размер получаемых данных с помощью опции CURLOPT_PROGRESSFUNCTION: как только cURL получит больше данных, чем наш лимит, он прекратит работу и выдаст ошибку CURLE_ABORTED_BY_CALLBACK.

// Проверим ошибки cURL и доступность файла if ($error === CURLE_OPERATION_TIMEDOUT) die ("Превышен лимит ожидания." ); if ($error === CURLE_ABORTED_BY_CALLBACK) die ("Размер не должен превышать 5 Мбайт." ); if ($info ["http_code" ] !== 200 ) die ("Файл не доступен." ); // Создадим ресурс FileInfo $fi = finfo_open (FILEINFO_MIME_TYPE); // Получим MIME-тип используя содержимое $raw $mime = (string) finfo_buffer ($fi , $raw ); // Закроем ресурс FileInfo finfo_close ($fi ); // Проверим ключевое слово image (image/jpeg, image/png и т. д.) if (strpos ($mime , "image" ) === false ) die ("Можно загружать только изображения." ); // Возьмём данные изображения из его содержимого $image = getimagesizefromstring($raw ); // Зададим ограничения для картинок $limitWidth = 1280 ; $limitHeight = 768 ; // Проверим нужные параметры if ($image > $limitHeight ) die ("Высота изображения не должна превышать 768 точек." ); if ($image > $limitWidth ) die ("Ширина изображения не должна превышать 1280 точек." ); // Сгенерируем новое имя из MD5-хеша изображения $name = md5 ($raw ); // Сгенерируем расширение файла на основе типа картинки $extension = image_type_to_extension ($image ); // Сократим.jpeg до.jpg $format = str_replace ("jpeg" , "jpg" , $extension ); // Сохраним картинку с новым именем и расширением в папку /pics if (! file_put_contents (__DIR__ . "/pics/" . $name . $format , $raw )) { die ("При сохранении изображения на диск произошла ошибка." ); }

Для сохранения изображения на диск можно воспользоваться file_put_contents() , которая запишет контент в файл. Новое имя файла мы создадим через функцию md5() , а расширение сделаем из image_type_to_extension() . Теперь мы можем загружать любые картинки по ссылке.

§6. Настройка выбора нескольких файлов

В этом параграфе разберём способы загрузки нескольких изображений за один раз с локальной машины пользователя и по удалённым ссылкам. Для отправки ссылок мы задействуем $_POST и передадим ей все данные с помощью тега textarea . Для загрузки файлов из формы мы продолжим дальше работать с $_FILES . Наша новая HTML-форма будет немного отличаться от старой.

В конец имени поля выбора файла name="upload" добавились фигурные скобки и аттрибут multiple , который разрешает браузеру выбрать несколько файлов. Все файлы снова загрузятся во временную папку, если не будет никаких ошибок в php.ini . Перехватить их можно в $_FILES , но на этот раз суперглобальная переменная будет иметь неудобную структуру для обработки данных в массиве. Решается эта задача небольшими манипуляциями с массивом:

// Изменим структуру $_FILES foreach ($_FILES ["upload" ] as $key => $value ) { foreach ($value as $k => $v ) { $_FILES ["upload" ][$k ][$key ] = $v ; } // Удалим старые ключи unset ($_FILES ["upload" ][$key ]); } // Загружаем все картинки по порядку foreach ($_FILES ["upload" ] as $k => $v ) { // Загружаем по одному файлу $_FILES ["upload" ][$k ]["tmp_name" ]; $_FILES ["upload" ][$k ]["error" ]; }

Для загрузки нескольких картинок по URL передадим наши ссылки через textarea с именем name="upload" , где их можно указать через пробел или с новой строки. Функция preg_split разберёт все данные из $_POST["upload"] и сформирует массив, по которому нужно пройтись циклом и каждый валидный URL отправить в обработчик.

$data = preg_split ("/\s+/" , $_POST ["upload" ], - 1 , PREG_SPLIT_NO_EMPTY); foreach ($data as $url ) { // Валидируем и загружаем картинку по URL }