Profruit banner

Умен звънец в openHAB

16 декември 2023

Телефонът е винаги с мен и сега, където и да се намирам получавам известие от openHAB, че някой е позвънил и чака пред вратата. А ако ме няма вкъщи става ясно часа и датата на последното позвъняване.

Tasmota Multipress

30 септември 2023

Идеята е Sonoff Touch T1 US 2 в салона, да управлява освен своето осветление и това в кухнята, а още вентилатора.

DIY 12V 1A WiFi Router UPS

22 април 2023

При поредно прекъсване на захранването вкъщи, батерията на нoтбука и двата UPS автоматично превключват на аварийно захранване и едновременно полита съобщение за конфуза

Zigbee2MQTT клониране

21 януари 2023

... как да клонираме съществуваща настройка на Zigbee2MQTT без да се налага последвало интервю на zigbee-устройствата.

LD2410 - бюджетен датчик присъствие в openHAB

11 февруари 2023

Цената на HLK-LD2410 зададе име на поредната тема в моя блог. С негова помощ се постига "народен" датчик присъствие в домашната автоматизация. . ...

PHP Upload Form

Качването на файлове на сървър е често практикувано действие от интернет-потребители и причина да погледнем нещата отблизо.

За окачествяване на теоретическото обяснение съм подготвил демонстративен пример. За съжаление Blogger не позволява изпълнение на PHP - скриптове и остава да се доверите на вашата интуиция, и възприятие при прочит. Тестова площадка на примера е ОС-ма Ubuntu desktop 10.04 с инсталиран LAMP. Става ясно, че главен мотор на качването ще PHP.

Multipart-форми

Качването на файлове на сървър става с помощта на multipart-форма. Тя има поле за качване, бутон - избери файл и бутон - качи файл.

Класически вид на тази форма е следния.
Недостатък е, че всеки браузър има собствена интерпретация на изгледа. Google Chrome браузърът показва огледално, например. Сходен вид демонстрира Safari браузър.

Това е кодът на формата.
<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="file" name="uploadfile">
 <input type="submit" value="upload">
</form>
Multipart-формите ползват предимно метод POST. Изхождайки от посочения горе пример и кода, формата има две полета (input):
  • Поле за избор на файла <input type="File" атрибути>.
  • Бутон за качване на файла <input type="submit" value="upload">.

Обработка на Multipart-форми

Преди да пристъпим към писане на PHP-скрипт за обработка на данните от формата е необходимо да редактираме конфигурационен файл php.ini, за да разрешим качване на файлове на сървъра.

В Debian/Ubuntu/LinuxMint система това става с команда в терминала:
sudo gedit /etc/php5/apache2/php.ini
Конфигурационен файл php.ini в PHP съдържа три параметъра, отнасящи се към качване файлове на сървър:
  • file_uploads=On - разрешава качване на файлове на сървър по протокол HTTP;
  • upoad_tmp_dir=/tmp - задава временен каталог за съхранение на качения файл;
  • upload_max_filesize=2M - максимален разрешен обем на качения файл.
За да влязат в сила измененията в php.ini е нужен рестарт на сървър Apache. Постигаме с команда:
sudo /etc/init.d/apache2 reload

Как PHP обработва Multipart-форми

Получавайки файла, PHP го съхранява в временен каталог upload_tmp_dir с произволно генерирано име. После създава четири променливи в суперглобалния масив $_FILES. Този масив съдържа информация за качения файл.

Нека импровизираме. В root-директория на Apache създадем каталог с име upload.php. В този каталог поместим multipart-формa, а след това качим файл.

Примерен код:
<pre>
<?php
 print_r($_FILES);
?>
</pre>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>PHP Upload File</title>
 </head>
 <body>
  <form action="" method="POST" enctype="multipart/form-data">
   <input type="file" name="uploadfile">
   <input type="submit" value="upfiles">
  </form>
 </body>
</html>
Тестови файл е снимка с име picture.png. Резултатът връща функция print_r($_FILES):
Array
(
    [upload] => Array
        (
            [name] => picture.png
            [type] => image/png
            [tmp_name] => /tmp/phprGRvvg
            [error] => 0
            [size] => 4724
        )

)
Обърнете внимание, че в случая се предполага име на value="upfiles" (надписът върху бутон качи). Разбира се името може да бъде произволно. На практика в суперглобален масив $_FILES се създава масив uploadfile с ключове:
  • $_FILES['uploadfile']['name'] - името на файла, до качването му на сървъра;
  • $_FILES['uploadfile']['type'] - MIME-тип на получения файл, например: image/jpeg, text/html;
  • $_FILES['uploadfile']['tmp_name'] - временен каталог в php.ini и временно име на файла;
  • $_FILES['uploadfile']['error'] - код за грешки по време на трансфер;
  • $_FILES['uploadfile']['size'] - размер в байтове на приетия файл.
При финализиране работата на скрипта, временния файл бива премахнат. Това означава, че каченият файл трябва да се копира от временния каталог в указано място. Тест, имаме следното действие. При натиснат бутон Submit стартира качване на файла на сървъра в временния каталог под временно име, например /tmp/phprGRvvg. Веднага след това PHP-сценарият трябва да копира файла с име $_FILES['uploadfile']['tmp_name'] в указан каталог. Каталогът трябва да притежава права за запис - 0777.

Практикум

Различни web-приложения позволяват на потребителя да качва файлове. Аватари при форумите, снимки при фотогалериите, видео и изображения при социалните мрежи и т.н.

Да предположим, че ни е нужно да организираме подобен каталог за съхраняване на файлове качени по протокол HTTP. Такъв в случая е каталог files, който се намира в родителски каталог upload. Upload на свой ред се намира в корневия каталог на web-сървър Apache - /var/www (в каталог DocumentRoot).

Това е нашия сайт погледнат отвътре с единствено твърдо условие - само за снимки.
Apache root directory
  • index.php - индекс на сайта, тук е формата;
  • upload.php - PHP обработчик на формата;
  • files - каталог за съхранение на качените снимки;
  • images - стилизираща графика на сайта;
  • js - каталог за Javascript-файлове;
  • stylesheet - каталог за CSS-файлове.
За каталог files в Linux-система трябва да отчитаме правата за достъп. Функция mkdir() няма да сработи, защото нямаме права за писане в каталог DocumentRoot (традиционно /var/www/ или ~/httpd/html). Зарегистрирaйте се в системата като root, създайте каталог files и изменете правата с следните команди:
cd /var/www/upload
mkdir files
chown www-data:www-data files
chmod 1770 /var/www/upload/files
По този начин, даваме възможност на Apache да качва снимки в указания каталог и това показва команда, за примерно качена снимка - picture.jpeg.
ls -l /var/www/upload/files
Върнатия резултат е:
-rw-r--r-- 1 www-data www-data 14845 2011-09-29 18:33 picture.jpeg
В Ubuntu система Apache се представя като www-data.

The Code

Цел на задачата е приемане на снимка от потребителя и последвал показ, импровизиран image-store.

Формата ще предава снимката за обработка на upload.php, а той на свой ред ще я пренасочва към каталог files. Подобна подредба на нещата предполага възможно най-малка смес на HTML-код с PHP-код.

Отлично и веднага лъсват две дупки в сигурността. Тези "дупки" предоставят на злоумишленик, да извършва нежелани действия, да преглежда файловете в каталог files и до изпълнение на произволен код през формата. Например, вместо снимка качи PHP файл, а после го изпълни. Този файл може да носи разрушителни действия.

Каталог files не е цел на темата. Можем да го манипулираме с файл .htaccess. Например, забрана листинга на файлове при пряко обръщение към него и/или да пренасочваме към индекс-файла на сайта и т.н. Идеи за защита много.

На главен ред е файл upload.php. Съдържанието му е авторско виждане по въпроса и то е следното:
<?php
if ($_POST['hidden']=='upfiles'){
session_start();
 
 //черен списък на разширенията на файловете
 $blacklist = array('php', 'phtml', 'php3', 'php4', 'html', 'htm', 'js', 'css');
 
 // разрешен тип на файловете
 $allowed_filetypes = array('jpg','gif','bmp','png');
 
  // макс размер на файла в байтове (в случая 1MB)
 $max_filesize = 1048576;
 
  // каталог на качените файлове
 $upload_path = './files/';
 
 // присвояваме името на файла и разширението му
 $filename = $_FILES['upfile']['name']; 
 
 //отсяваме черните файлове
 foreach ($blacklist as $item) {
  if(preg_match("/$item\$/i", $filename)) {
  $_SESSION['error'] = $item.' файл не е позволен!';
  header('Location: index.php');
  exit();
   }
  }
  
 // в $ext вкарваме разширението на качения файл.
 $ext = substr($filename, strpos($filename,'.')+1); 
 
 // сравнение полученото разширение с списъка на разрешените. Предупреждение при несъответствие
 if(!in_array($ext,$allowed_filetypes)) {
  $_SESSION['error'] = 'Този файл не е картинка!';
  header('Location: index.php');
  exit();
 }
 
 // проверка размера на файла. При надхвърляне лимита вадим предупреждение
 if(filesize($_FILES['upfile']['tmp_name']) > $max_filesize) {
  $_SESSION['error'] = 'Файлът надхвърля лимита!';
  header('Location: index.php');
  exit();
 }
 
 // проверка  достъп на запис. Предупреждение за  права  0777
 if(!is_writable($upload_path)){
  $_SESSION['error'] = 'Невъзможно е да качите файл. Задайте права - 0777.';
  header('Location: index.php');
  exit();
 }
 
 // същинско качване на файла
 if(move_uploaded_file($_FILES['upfile']['tmp_name'], $upload_path . $filename)){
  $_SESSION['res'] = array ('Вашият файл е успешно качен', '<a href="'.$upload_path . $filename . '">'. $_SERVER['SERVER_NAME'] . $upload_path . $filename . '</a>'); 
  header('Location: index.php');
  exit();
 }
}
else{
 header('Location: index.php');
 }
Море от условия, това е положението, иначе дупка и лошият чичко хака. Той е в своята дупка, нагъва сандвичи пред монитора и тъпче код.

Ред 2. Проверяваме натиснат ли е бутон upload. Ако не, пращаме на ред 61 и пренасочваме към индекса. По този начин режем директен достъп - dirname(__FILE__).'/upload/files/'. За целта добавяме скрито поле <input type="hidden" name="hidden" value="upfiles">.

Ако е натиснат бутона, то сверяваме суперглобален масив $_POST за наличие на ключ hidden с значение upfiles. При успех стартираме сесия в ред 3.

Сесията е нужна, за да може да маневрираме успешно при съответните пренасочвания от upload.php към index.php.

Ред 5. Създаваме масив от черен списък на разширенията на файловете. Това са смъртоносните файлове, за нашата импровизация.

Ред 8. Създаваме масив от бял списък на разширенията на файловете. Файлове с подобни разширения ще минават цедката и успешно ще бъдат качени.

Ред 12. Задаваме наше ограничение (равно или по-малко от това в php.ini) за размер на снимката.

Ред 14. Указваме на PHP къде да качва снимките.

Ред 17. Присвояваме името на файла и разширението му (помним суперглобален масив $_FILES).

Ред 21. Прекарваме заявката през обход на масив. В цикъла добавяме регулярен израз, който да търси разширение от черния списък. Шаблонът на регулярния израз (/$item\$/i) вкарваме като параметър на функция preg_match(). Вторият параметър е качвания файл. При съвпадения създаваме в суперглобален масив $_SESSION['error']. Тази променлива ще извежда съобщение, че такова разширение не позволено. Пренасочване към индекса и прекратяване по-нататъшно изпълнение на скрипта.

Ред 30. Наричам го "оглозгване" на файла. С поредица от функции извличаме разширението на снимката и я закачаме на променлива $ext, която ще послужи за последвала проверка.

Ред 33. Сравняваме разширенията с белия списък. Тук леко преигравам с този двойна проверка, защото може да се доизкусори шаблона. Идеята ми е друга. Ако потребителя по погрешка качи .txt, .mp3, .doc файл, да му напомним, че това не е снимка. Тези файлове са безобидни. Функция in_array() ще оглежда файла. Ако ли не, пренасочване с извеждане на грешка.

Ред 47. Профилактично проверяваме правата за запис на каталог files.

Ред 54. Ако такова чудо ($_FILES['upfile']['tmp_name']) е оцеляло, правим сливане (конкатенация) и го местим на очакваното място. Създаваме масив ['res'] в $_SESSION. Масива е резултата и съобщения, които рапортуваме на потребителя. Пренасочване към индекса и изход от скрипта.
Логичен завършек на index.php придоби този вид:
<?php
 session_start();
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="stylesheet" href="./stylesheet/style.css" type="text/css" media="screen">
  
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
  <script type="text/javascript" src="./js/form.js"></script>
  
  <title>PHP Upload File</title>
 </head>
 <body>
  
 <div id="form">
  <form action="upload.php" method="POST" enctype="multipart/form-data">
  <fieldset><legend>PHP upload form</legend>
   <p><input type="file" name="upfile" class="inputfile"></p>
   <p><input type="submit" value="upload file" class="button gradient"></p>
   <p><input type="hidden" name="hidden" value="upfiles"></p>
   <p class="error"><span><?=$_SESSION['error']?></span></p>
   <p><span><?=$_SESSION['res'][0] ?></span></p>
   <p><span><?=$_SESSION['res'][1] ?></span></p>
  </fieldset>
  </form>
</div>

 <?php
  session_unset();
  session_destroy();
 ?>
 
 <script type="text/javascript">
  $(document).ready(function(){
   $('input:file').jInputFile();
  });
 </script>
 
 </body>
</html>
Новото тук е, че разчупих визията на формата. Все пак съм внук на да Винчи. И така по реда си:

Ред 2. Стартираме сесия, за да имаме достъп до суперглобален масив $_SESSION и реализираме замисъла в upload.php (помним пренасочванията).

Ред 23. Тук се извеждат стресиращите съобщения на потребителя.

Ред 24. Този ред и последвалия го 25 извежда благоуханните съобщения, че всичко е гуд и потребителя може да съзерцава снимката си (аман от нарциси).

Ред 36. Трием сесията, респективно съобщенията. Един вид хигиена на визията.

Още ново се забелязва в ред 8. Стилизираме формата. Много е трудно input type="file" name="uploadfile" да се преобрази чрез CSS. Този инпут е костелив орех, за разлика от подoбните нему. Постигнатото е по-скоро показ, че все пак може, отколкото практична реализация от това. За целта вкарваме мощта на javascript-интерпретатора, който да подменя визията. За коректно изпълнение на документа се подсигуряваме с jQuery в ред 35.

Резултат

Поредицата снимки надолу показва поведение на формата. Първоначален вид, следва избор на файл, успешно качване и споделяне адреса му, опит за качване на грешен файл и накрая изглед на формата при изключен javascript.
PHP Upload form

PHP Upload form

PHP Upload form

PHP Upload form

PHP Upload form

PHP Upload form

PHP Upload form

PHP Upload form






до нови срещи   ^.^
30.09.2011 profruit 


|

0 Response to "PHP Upload Form"

Публикуване на коментар

Този блог е реинкарнация на първите ми опити за споделяне в нета. На времето започнах с къси разкази на преживяното. После се обезсмисли и превърнах блога си в системно радио. Пиша единствено неща, които карат душата ми да живее: openHAB, Ubuntu, Споделено и т.н. Това е моето системно радио, разбирате ли? Моята вълна и вие сте на нея сега.

Архив на блога