PHP и MySQL. Часть 3. Работа с сервером и файловой системой

PHP и MySQL. Часть 3. Работа с сервером и файловой системой

Предположим, что необходимо предоставить вашему пользователю (или клиенту) возможность вносить изменения в какую-то отдельную часть содержимого web-сайта. Скажем, вносить новые записи в раздел новостей компании. Одно из возможных решений — предоставление пользователю возможности загружать на сервер простые текстовые файлы. Далее эти файлы включаются в содержимое сайта при помощи шаблона, написанного на РНР. А если нам нужен более удобный FTP-интерфейс?

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

<html><head> <title>Загрузка нового файла</title></head> <bod
y><h1>Загрузка файла новостей</h1> <form enctype=”mutlipart/ form-data” action= “u
p-load.php” method = post> <input type=”hidden” name=”MAX_FILE_SIZE” value=”1000”> Загрузит
ь файл: <input name=”userfile” type=”file”><input type=”Submit” value=”Послать файл”><
;/form></body></html>
Следует обратить внимание, что для загрузки указан метод POST. Применим также метод PUT — его поддержка реализована в обозревателях Netscape и Amaya. Однако поддержка метода GET в вышеперечисленных обозревателях отсутствует.
Особенность этой формы состоит в следующем. Сервер получает уведомление, что файл передается вместе с данными из формы, за что отвечает дескриптор enctype="mutlipart/form-data". Форма должна содержать скрытое поле, которое содержит значение максимально возможного размера закачиваемого файла. Указанному полю <input type= "hidden" name="MAX_FILE_SI-ZE" value="1000"> необходимо присвоить имя MAX_FILE_ SIZE. И еще одно скрытое поле применяется для указания файла в качестве типа передаваемых данных.
Перейдем к написанию РНР — процедуры приема файла. При загрузке файл помещается во временный каталог, специально определенный для этой цели. Если файл не переместить или не переименовать, прежде чем сценарий закончит работу, он будет уничтожен. Поскольку форма содержит поле с именем userfile, сценарию передаются четыре переменных, обратиться к которым возможно через массив $HTTP_POST_FILES:

$HTTP_POST_FILES['userfile']['tmp_name'] $HTTP_POST_FILES['userfile']['name'] $HTTP_POST_FILES
['userfile']['size'] $HTTP_POST_FILES['userfile']['type']
Их назначение интуитивно понятно из самих названий.
В нашем примере загружаемый файл должен содержать статьи новостей, поэтому следует удалить все дескрипторы, которые в нем могут быть, и сохранить их в некотором каталоге. Как это сделать, показано на следующем примере:

<html><head> <title>Загрузка...</title> </head><body> <
h1>Загрузка файла...</h1> <? // $userfile — указывает, куда будет помещен файл на web-се
рвере // $userfile_name — оригинальное имя файла // $userfile_size — размер в байтах этого файла // 
$userfile_type — mime-тип файла if ($userfile==”none”) {echo “Ошибка: нечего загружать”; exit;} if (
$userfile_size==0) {echo “Ошибка: нулевой размер файла”; exit;} if ($userfile_type != “text/plain”) 
{echo “Проблема: файл не является текстовым”; exit;} $upfile = “/home/autoware/uploads/”.$userfile_n
ame; if ( !copy($userfile, $upfile)) {echo “Ошибка: не могу поместить файл в эту папку”; exit;} echo
 “Файл закачан успешно.<br><br>”; $fp = fopen($upfile, “r”); $contents = fread ($fp, fil
esize ($upfile)); fclose ($fp); $contents = strip_tags($contents); $fp = fopen($upfile, “w”); fwrite
($fp, $contents); fclose($fp); echo “Просмотр содержимого файла:<br><hr>”; echo $content
s; echo “<br><hr>”; ?></body></html>
Как можно заметить, основная часть этого сценария — многочисленные проверки ошибок. Загрузка файлов на сервер может быть чревата серьезными нарушениями безопасности, которые, по возможности, надо предотвращать. Кроме того, иногда стоит проверять содержимое файла на предмет допустимости его открытой публикации на сервере.
Рассмотрим проверки более детально. Первая, и самая главная, проверяет, есть ли нам вообще что закачивать, то есть проверяет значение $userfile. Далее идет проверка присутствия содержимого в файле, размер его не должен быть нулевым. Затем — проверка на соответствие типа содержимого этого файла требуемому типу, который содержится в переменной $userfile_type.
После этого файл открывается, очищается от дескрипторов HTML и PHP с помощью функции strip_tags() и записывается на прежнее место. И в заключение содержимое файла выводится на экран, чтобы пользователь мог убедиться в успешном завершении загрузки.
С целью проверки успешного завершения загрузки файла и гарантии, что мы имеем дело не с локальным файлом наподобие etc/passwd, рекомендуется применять функцию is_uploaded_file(). Эта функция появилась в РНР с версии 4.0.3 и выше. Небрежно написанный сценарий загрузки может позволить злонамеренному пользователю создать временный файл с определенным именем и заставить сценарий обрабатывать его как загруженный. Тогда у пользователя появится возможность обратиться к любому файлу, доступному для считывания web-сервером.
Следует также принимать во внимание следующие замечания.
В приведенном примере не было проверки прав доступа пользователей. Нельзя разрешать загрузку файлов на сервер кому угодно. При работе в системах семейства Microsoft не забывайте указывать обратные косые в строках путей. (Или это под Linux надо не забыть обратные косые черты? Это еще для кого и где обратные косые=)). Если вдруг возникли затруднения с запуском примера, проверьте файл PHP.INI, в котором проверьте значение директивы upload_tmp_dir. Если планируется закачивать файлы довольно большого объема, потребуется настройка директивы memory_limit, который определяет максимально допустимые размеры загружаемых файлов.
Идем далее. После загрузки нескольких файлов пользователям может понадобиться возможность просмотра и манипулирования файлами. Для этой цели в РНР предусмотрен еще один набор функций: для работы с файлами и каталогами. Следующий сценарий показывает, что это совсем не сложно выполнить:

<html><head> <title>Обзор каталога</title> </head><body> &
lt;h1>Обзор</h1> <? // обычная версия работы $current_dir = “/home/autoware/uploads/”; $
dir = opendir($current_dir); echo “Загрузочный каталог: $current_dir<br>”; echo “Список файлов
:<br><hr><br>”; while ($file = readdir($dir)) { echo “$file<br>”; } echo “&l
t;hr><br>”; closedir($dir); // объектно-ориентированная версия $current_dir = “/home/autowa
re/uploads/”; $dir = dir($current_dir); echo “Upload directory is “.$dir->path.”<br>”; echo
 “Directory Listing:<br><hr><br>”; while ($file = $dir->read()) { echo “$file&l
t;br>”; } echo “<hr><br>”; $dir->close(); ?> </body> </html>
Поясним немного приведенный код. Функция оpendir() открывает каталог для чтения, как обыкновенный файл. Она действует подобно функции fopen(), однако с аргументом, содержащим имя каталога, а не файла. Функция оpendir() возвращает дескриптор каталога подобно все той же функции fopen(). После того, как каталог открыт для чтения, из него можно читать имена файлов функцией readdir(). К сожалению, файлы при этом не сортируются каким-либо образом, поэтому, если есть необходимость в сортировке, имена файлов надо считывать сначала в массив и сортировать перед выводом пользователю. По завершении чтения из каталога его следует закрыть функцией closedir(). Опять же, эта функция аналогична файловой функции fclose(). Еще одна полезная функция — rewinddir(). Она возвращает указатель считываемого файла на начало каталога.
Как показано в примере (во второй его логической части), альтернативой перечисленным функциям может быть объектный подход, когда используется класс dir, определенный в РНР. Его свойства handle и path, а также методы read(), close() и rewind() аналогичны внеклассовым переменным и функциям.
Сходным образом можно получать информацию как о файлах, так и о каталогах. Так, функции dirname($path), basename($path) возвращают составляющие пути, содержащие соответственно имена каталога и файла. В листинг каталога можно также включить строку с указанием дискового объема, определенного под загружаемые файлы. Типичным примером использования и применения всего этого служит портал TUT.BY. Каждому зарегистрировавшемуся в системе и получившему свой почтовый ящик также выделяется 5 Мб дискового пространства. Причем в окне просмотра содержимого сайта вы можете найти именно такую информацию: список файлов и каталогов, их размер, дата создания, а также размер свободного места из выделенной вам квоты.
Изменим наш сценарий просмотра каталогов, добавив в него чтение файлов:
echo "<a href=\"filedetails.php?file=".$file."\"> ".$file."</a> <br> ";
Теперь составим сценарий filedetails.php, который считывает дополнительную информацию о файле. Этот сценарий страдает лишь одним, но существенным недостатком: некоторые функции, в том числе fileowner() и filegroup(), в MS Windows отсутствуют или могут выполняться ненадежно.

<html><head> <title>Описания файлов</title> </head><body> 
<? $current_dir = “/home/ autoware/uploads/”; $file = basename($file); // удаляем информацию о ка
талоге в целях безопасности echo “<h1>Details of file: “.$file.”</h1>”; $file = $current
_dir.$file; echo “<h2>File data</h2>”; echo “File last accessed: “.date(“j F Y H:i”, fil
eatime($file)).”<br>”; echo “File last modified: “.date(“j F Y H:i”, filemtime($file)).”<br
>”; $user = posix_getpwuid(fileowner($file)); echo “File owner: “.$user[“name”].”<br>”; $gr
oup = posix_getgrgid(filegroup($file)); echo “File group: “.$group[“name”].”<br>”; echo “File 
permissions: “.decoct(fileperms($file)).”<br>”; echo “File type: “.filetype($file).”<br>
”; echo “File size: “.filesize($file).” bytes<br>”; echo “<h2>File tests</h2>”; ec
ho “is_dir: “.(is_dir($file)? “true” : “false”).”<br>”; echo “is_executable: “.(is_executable(
$file)? “true” : “false”).”<br>”; echo “is_file: “.(is_file($file)? “true” : “false”).”<br&
gt;”; echo “is_link: “.(is_link($file)? “true” : “false”).”<br>”; echo “is_readable: “.(is_rea
dable($file)? “true” : “false”).”<br>”; echo “is_writable: “.(is_writable($file)? “true” : “fa
lse”).”<br>”; ?> </body></html>
Разберем приведенный код подробно.
Как уже говорилось, функция basename() возвращает имя файла без указания каталога, в котором он расположен. Чтобы получить имя каталога без имени файла, воспользуйтесь функцией dirname(). Функции fileatime() и filemtime() возвращают метки времени соответственно последнего обращения и последней модификации файла. Чтобы представить данные в более удобоваримом виде, их переформатировали, воспользовавшись функцией date().
Функции fileowner() и filegroup() возвращают идентификаторы пользователя (uid) и группы (gid), которым предоставлен доступ к файлу. Для большей читаемости идентификаторы можно преобразовать в имена функциями posix_getpwuid() и posix_getgrgid(). Эти функции возвращают массив данных, включая и имя.
Функция fileperms() возвращает разрешения доступа к файлу. У нас они переформатированы при помощи функции decoct() в шестнадцатеричное представление, более привычное для пользователей UNIX-подобных систем.
Функция filetype() возвращает некоторые сведения о типе файла.
Функция filesize() отображает размер файла в байтах.
Следующий набор функций — is_dir(), is_executable(), is_file(), is_link(), is_readable(), is_writable(). Каждая из них проверяет определенный атрибут файла, возвращая в результате true или false.
Вместо большого числа функций можно использовать одну — stat(), которая возвращает массив информации, содержащий значения перечисленного множества функций. Аналогично ей функция lstat() применяется для символических ссылок.
Вызывать эти функции слишком часто не рекомендуется, так как они слишком затратные в смысле времени выполнения, и возвращаемые ими результаты кэшируются. Для обновления возвращаемых значений после модификации файлов необходимо вызвать функцию clearstatcache(), которая уничтожит предыдущие результаты. Если бы сценарий был действительно предназначен для работы, его выполнение следовало бы начать именно с вызова этой функции. В этом случае получение неустаревших результатов гарантировано.
РНР также может использовать переменные среды окружения, то есть переменные операционной системы. Для этого определены две функции: getenv() и putenv(). Первая возвращает значение переменной среды, а вторая устанавливает их. Для получения списка всех переменных используется функция phpinfo().
Набор функций РНР не ограничивается работой с файлами, это очевидно. Существуют также сетевые функции, благодаря которым сценарии могут взаимодействовать с Internet. Их работа обеспечивается через сетевые протоколы, такие, как HTTP, POP, FTP и др.
Основной способ отправки почтовых сообщений в РНР — использование простой функции mail(). Эта функция проста в применении и использует протокол SMTP — простой протокол пересылки почты. Для получения почты используются протоколы POP и IMAP. Последний протокол используется для получения и управления почтовыми сообщениями, сохраненными на сервере, и является более сложным, чем РОР. Основное применение РОР заключается в простой загрузке сообщений на клиентскую машину и удалении их с сервера. РНР содержит библиотеку IMAP, которую можно использовать также для установки соединений РОР и NNTP (Network News Transfer Protocol).
Одно из лучших применений РНР заключается в возможности использовать, изменять или встраивать службы и информацию в собственные страницы.
Средствами РНР можно включать в свои страницы курсы валют, сведения о погоде и прочую информацию. Можно проверять информацию о хостах, IP-адресах, почтовых доменах, можно создать и поддерживать зеркало сайта на отдельном FTP-сервере. Чаще всего, как правило, используется процедура проверки введенного пользователем URL или адреса почтового ящика. Рассмотрим следующий пример. На форме пользователь вводит адрес сайта и адрес своего почтового ящика. Нам необходимо проверить, правильно ли он ввел эти адреса, то есть не допустил ли ошибки при вводе, и не пытается ли он зарегистрировать фиктивные адреса:

<html><head> <title>Результаты проверки адреса сайта</title> </head
><body> <h1>Результаты</h1> <? //Проверяем адрес сайта $url = parse_url($url
); $host = $url[host]; if(!($ip = gethostbyname($host))) { echo “Адрес не имеет IP-адреса!”; exit; }
 echo “Адрес имеет следующий IP: $ip <br>”; //Проверяем почтовый ящик $email = explode(“@”, $e
mail); $emailhost = $email[1]; if (!getmxrr($emailhost, $mxhostsarr)) { echo “Неправильно указан поч
товый адрес”; exit; } echo “Почта доставлена через: “; foreach ($mxhostsarr as $mx) echo “$mx “; //Е
сли выполнение добралось до этой точки… echo “<br>Все адреса правильны.<br>”; //В реальн
ом приложении тут можно обработать полученные данные… ?> </body></html>
Функция parse_url() возвращает ассоциативный массив различных частей имени URL. Доступные блоки информации включают в себя протокол (scheme), пользователя (user), пароль (pass), хост (host), порт (port), путь (path), запрос (query), фрагмент (fragment). Обычно во всех этих элементах нет необходимости, но для примера можно показать, как они формируют URL. Пусть у нас есть следующий URL:
http://username:userpass@somehost.com/script.php?var=value#anchor

Имеем следующие значения:
Schema: http://, user — username, pass — userpass, host — somehost.com, port — 80, path — script.php, query — var=value, fragment — anchor.
Нам требуется только информация о хосте, поэтому массив обрабатывается следующим образом:
$url = parse_url($url);
$host = $url[host];

После этого можно получить IP-адрес хоста, если он содержится в DNS-сервере, с использованием функции gethostbyname():
$ip = gethostbyname($host);
Или можно воспользоваться аналогичной функцией gethostbyaddr(). Она возвращает символическое имя хоста. Если в результате применения двух этих функций последовательно результат оказался отличным от имени, с которого начинался поиск, то это значит, что сайт использует службу виртуальных хостов.
Далее по коду проверяем адрес e-mail. Сначала выделим из адреса название хоста: $email = explode(@, $email); $emailhost = $email[1];
Имея имя хоста, несложно проверить, можно ли отправить на него почту. Это делается с помощью функции getmxrr():
getmxrr($emailhost, $mshostsarr).

Эта функция возвращает набор записей МХ для этого адреса в массиве, заданном переменной $mshostsarr. МХ — запись содержится в DNS, и ее поиск происходит так же, как поиск имени хоста. Машина, указанная в записи МХ, необязательно является той, куда придет почта. Однако эта машина точно знает, куда надо почту перенаправить. Таких записей может быть несколько, поэтому функция и возвращает массив данных. Если ни одной записи не найдено, значит, почте просто некуда идти.
Если все проверки прошли успешно, данные можно обрабатывать дальше — занести в базу данных, вывести пользователю и т.д.

Продолжение следует.

Денис "Denver" Мигачев, dtm@tut.by


Компьютерная газета. Статья была опубликована в номере 24 за 2003 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета