За окачествяване на теоретическото обяснение съм подготвил демонстративен пример. За съжаление Blogger не позволява изпълнение на PHP - скриптове и остава да се доверите на вашата интуиция, и възприятие при прочит. Тестова площадка на примера е ОС-ма Ubuntu desktop 10.04 с инсталиран LAMP. Става ясно, че главен мотор на качването ще PHP.
Multipart-форми
Класически вид на тази форма е следния.
Това е кодът на формата.
<form action="upload.php" method="POST" enctype="multipart/form-data"> <input type="file" name="uploadfile"> <input type="submit" value="upload"> </form>
- Поле за избор на файла <input type="File" атрибути>.
- Бутон за качване на файла <input type="submit" value="upload">.
Обработка на Multipart-форми
В Debian/Ubuntu/LinuxMint система това става с команда в терминала:
- file_uploads=On - разрешава качване на файлове на сървър по протокол HTTP;
- upoad_tmp_dir=/tmp - задава временен каталог за съхранение на качения файл;
- upload_max_filesize=2M - максимален разрешен обем на качения файл.
Как PHP обработва Multipart-форми
Нека импровизираме. В 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>
Array ( [upload] => Array ( [name] => picture.png [type] => image/png [tmp_name] => /tmp/phprGRvvg [error] => 0 [size] => 4724 ) )
- $_FILES['uploadfile']['name'] - името на файла, до качването му на сървъра;
- $_FILES['uploadfile']['type'] - MIME-тип на получения файл, например: image/jpeg, text/html;
- $_FILES['uploadfile']['tmp_name'] - временен каталог в php.ini и временно име на файла;
- $_FILES['uploadfile']['error'] - код за грешки по време на трансфер;
- $_FILES['uploadfile']['size'] - размер в байтове на приетия файл.
Практикум
Да предположим, че ни е нужно да организираме подобен каталог за съхраняване на файлове качени по протокол HTTP. Такъв в случая е каталог files, който се намира в родителски каталог upload. Upload на свой ред се намира в корневия каталог на web-сървър Apache - /var/www (в каталог DocumentRoot).
Това е нашия сайт погледнат отвътре с единствено твърдо условие - само за снимки.
- index.php - индекс на сайта, тук е формата;
- upload.php - PHP обработчик на формата;
- files - каталог за съхранение на качените снимки;
- images - стилизираща графика на сайта;
- js - каталог за Javascript-файлове;
- stylesheet - каталог за CSS-файлове.
mkdir files
chown www-data:www-data files
chmod 1770 /var/www/upload/files
The Code
Формата ще предава снимката за обработка на 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. Масива е резултата и съобщения, които рапортуваме на потребителя. Пренасочване към индекса и изход от скрипта.
<?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.
Резултат
до нови срещи ^.^
0 Response to "PHP Upload Form"
Публикуване на коментар