Записи в рубрике 'PHP Статьи'
Очередной раз заглянув в почту и обнаружив очередную пачку писем от серверов решил положить этому конец. Упрощало задачу то что, большую часть писем генерируют мои собственные программы и скрипты, которые установлены на различных серверах.
Большая часть этих сообщений не являются критичными. Например, <Невозможно соединится с сервером>, <Обработано файлов столько-то>, <Затрачено времени столько-то> и т.п. Я рассмотрел два основных варианта этой проблемы:
Первый вариант, в лоб - использовать перенаправление вывода или собственные лог-файлы. Этот вариант не удобен для мониторинга, так как придётся просматривать больше количество файлов отчётов.
Второй вариант - не изобретать велосипед и использовать syslog.
syslog - стандарт отправки сообщений о происходящих в системе событиях (логов), использующийся в компьютерных сетях, работающих по протоколу IP.
Протокол syslog прост: отправитель посылает короткое текстовое сообщение, размером меньше 1024 байт получателю сообщения. Получатель при этом носит имя <syslogd>, <syslog daemon>, либо же, <syslog server>. Сообщения могут отправляться как по UDP, так и по TCP. Как правило, такое сообщение отсылается в открытом виде.
Syslog используется для удобства администрирования и обеспечения информационной безопасности. Он реализован под множество платформ и используется в множестве устройств. Поэтому, использование syslog позволяет обеспечить сбор информации с разных мест и хранение её в едином репозитории.
Как правило syslog и syslogd работают внутри одного сервера (то есть все соединения локальны) и никакой внешней сетевой деятельности не производится.
Теперь, как все это выглядит на практике.
Perl:
use Sys::Syslog;
openlog(”имя вашей программы”, “ndelay,pid”, “local0″);
syslog(LOG_WARNING, “Программа запущена”);
…
# тут программа
…
syslog(LOG_WARNING, “Программа завершила выполнение”);
closelog();
PHP:
openlog(”имя вашей программы”, LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING,”Программа запущена”);
…
// тут программа
…
syslog(LOG_WARNING, “Программа завершила выполнение”);
closelog();
C/C++:
#include <syslog.h>
openlog(”имя вашей программы”, 0, LOG_USER);
syslog(LOG_NOTICE, “Can not open file “%s” for writing.”,filename);
closelog();
Далее можете поискать ваши сообщения в журналах syslog. Тут все зависит от настроек syslogd. Обычно это /var/log/messages.
В документации PHP написано, что в windows тоже работает, но я не пробовал.
Подробнее о флагах и значениях можно прочитать в руководствах:
C
PHP
Perl
Автор: mike
Tags:
log,
php,
syslog
Март 24, 2008
95% бесплатных php-скриптов (и не только php) - полный «отстой». Оно и понятно: хороший программист бесплатно ничего писать не будет, а если и будет, то только в свободное время в качестве развлечения, и уж, конечно не всякие банальности, вроде гостевых книг. Или, как говорил Рома Воронежский: «Вот проблема с этими творческими людьми: они всегда желают быть композиторами, художниками и писателями. В результате производством труб большого диаметра занимаются бездарности».
Именно так это и происходит.
Сегодня опять ковырялся в каталогах бесплатных скриптов, главным образом из любопытства, но еще и в тайной надежде найти что-нибудь забавное. В прошлый раз из «забавных» скриптов я нашел, например, «скрипт вывода текстового файла в php». Думал - парсер. Оказалось - да: почти что парсер. Привожу скрипт целиком: «?php include (”text.txt”); ?». Или вот вижу скрипт, написано «This script will reverse the text you give it. reversed: .ti evig uoy txet eht esrever lliw tpircs siht It isn’t very useful, it’s just funny. Try it out :)», то есть скрипт переворачивает строку задом-наперед. Самые худшие ожидания оправдались: они делали это циклом. Наверное, не знали, что в php есть специальная, уже готовая функция для этого:
Нашел скрипт поиска по сайту: он обшаривает директории, которые вы указали, открывает все html-файлы и тупо сравнивает: У меня тоже такой же скрипт был давным-давно написан, но потом, когда я понял, что народ поиском все-таки пользуется (сюрприз!), решил сделать его по-человечески. С индексом и прочими благами цивилизации: Сделал. В результате 1.5 мегабайта заметок превращаются в 900-килобайтный индекс за 17 секунд (индексацию надо проводить раз в несколько дней, или даже реже - в зависимости от скорости обновления сайта), после чего поиск по индексу происходит меньше одной секунды.
В общем, решил я все-таки поделиться этим скриптом. Краткая информация: скрипт на php, для работы никаких mysql не надо, предполагается, что html-ные (или txt-овые, как у меня) файлы где-то лежат, а не хранятся в mysql. В общем, поисковая машина для небольшого (ну, или «среднего») сайта. Как, например, spectator.ru.
Итак, начали:
Самое первое - скрипт индексации. Для чего он нужен?.. Вот у меня 278 заметок. Если мы будем открывать каждый файл и искать совпадения, то нам надо будет открыть 278 файлов. А это ой как долго: Более того, нам надо будет 278 раз провести хитрые манипуляции с этими файлами (про манипуляции - ниже). Если же у нас есть индекс, то во-первых, поиск происходит в одном файле (индексе), во-вторых, все эти «хитрые манипуляции» уже выполнены.
Алгоритм индексирующего скрипта такой:
Открываем очередной файл
Убираем из него «мусор» ( зачем убирается мусор - понятно, чем мусора меньше, тем ищется быстрее: ):
переводы строк
html-тэги
знаки препинания
слова, короче трех букв (а зачем они там?)
Делаем заглавные буквы строчными.
Убираем повторяющиеся слова. (Действительно, зачем нам вся это тавтология?)
Записываем все в индекс.
Если еще есть файлы, переходим к пункту 1.
Реализуется это все на php - легко!
<?php
// Spectator’s Indexing Script
// (C) Spectator.ru
// Для работы требуется PHP 4 или выше.
// Если вы будете использовать этот скрипт, ссылка на Spectator.ru
// крайне желательна. Спасибо.
// ставим скрипт “на счетчик” (чтобы знать, как долго он выполнялся
$ttt=microtime();
$ttt=((double)strstr($ttt, ‘ ‘)+(double)substr($ttt,0,strpos($ttt,’ ‘)));
$indexdir=”text”; #индексируемая директория
$indexfile=”indexfile.txt”; #файл, в котором будет лежать индекс
// если вы хотите индексировать файлы в нескольких директориях, надо
// внести несколько махоньких добавлений…
// делаем так, чтобы не было таймаута из-за того, что скрипт будет долго
// выполняться (на всякий случай) и из-за того, что пользователь нажмет
// кнопку “стоп” в браузере
$abort = ignore_user_abort(1);
set_time_limit(600);
// Функция, удалающая слова, короче 3х букв. Пригодится дальше.
function sw (&$item1, $key) { if (strlen($item1)<3) $item1=”"; }
// по очереди открываем все файлы в директории и проверяем, можно ли их
// индексировать у меня можно индексировать только файлы, которые имеют
// вид “число.txt” то есть && (is_numeric(str_replace (”.txt”,”", $file)))
// это вам наверняка не понадобится.
$handle=opendir(’./’.$indexdir);
while (false!==($file = readdir($handle))):
if ($file!=”.” && $file!=”..” && (is_numeric(str_replace (”.txt”,”", $file)))):
// открываем очередной файл
$fd = fopen ($indexdir.”/”.$file, “r”);
$contents = fread ($fd, filesize ($indexdir.”/”.$file));
Fclose ($fd);
// убираем переводы строк
$contents=str_replace (”n”,” “, $contents);
$contents=str_replace (”r”,”", $contents);
// убираем хтмл-тэги
$contents=str_replace (’<br>’, ‘ ‘, $contents);
$contents=str_replace (’<p>’, ‘ ‘, $contents);
$contents=strip_tags ($contents);
// убираем знаки препинания и цифры
// все эти строки работают быстрей, чем один eregi_replace!
$contents=str_replace (’ -’, ‘ ‘, $contents);
$contents=str_replace (’.', ‘ ‘, $contents);
$contents=str_replace (’,', ‘ ‘, $contents);
$contents=str_replace (’!', ‘ ‘, $contents);
$contents=str_replace (’?', ‘ ‘, $contents);
$contents=str_replace (’:', ‘ ‘, $contents);
$contents=str_replace (’;', ‘ ‘, $contents);
$contents=str_replace (’)', ‘ ‘, $contents);
$contents=str_replace (’(', ‘ ‘, $contents);
$contents=str_replace (’”‘, ‘ ‘, $contents);
// убираем заглавные буквы
$contents=strtolower ($contents);
// разбиваем на слова, убираем слова, короче 3х букв
$contents=explode (” “, $contents);
// вот и функция пригодилась…
array_walk ($contents, ’sw’);
// убираем повторяющиеся слова
$contents=array_unique ($contents);
// соединяем слова
$contents=implode (” “, $contents);
// формируем соответствующую строку в индексе.
$fullfile.=$file.”| “.$contents.” n”;
// индекс-файл будет иметь вид:
// имя_файла|индекс_для_данного_файла n
// имя_файла|индекс_для_данного_файла n
// имя_файла|индекс_для_данного_файла n
echo ($file.” проиндексирован<br>”);
// переходим к следующему файлу
endif;
endwhile;
closedir($handle);
// убираем двойные пробелы
while (stristr($fullfile, ” “)) $fullfile=str_replace (” “,” “,$fullfile);
// индекс готов, сохраняем его
$fp = fopen($indexfile, “w+”);
fwrite($fp, $fullfile);
fclose($fp);
// считаем, как долго работал скрипт
$ddd=microtime();
$ddd=((double)strstr($ddd, ‘ ‘)+(double)substr($ddd,0,strpos($ddd,’ ‘)));
echo (”<br>Время индексации: “.(number_format(($ddd-$ttt),3)).
” секунд<br>”);
echo (”Размер индекса: “.(number_format((round ((filesize($indexfile))/1024)) ,
0, “.”,”.”))).” Kb”;
?>
Итак, у нас есть индекс. Дальше - просто. Так ведь?.. Надо просто произвести поиск в нем. Берем функцию eregi, например:
Хотя я делал совсем по-другому:
Лирическое отступление: часто, когда надо проверить, если в строке какая-нибудь комбинация символов, пишут что-то вроде этого:
if (eregi(’this must be found’,$string))
echo ‘found!!’;
else
echo ‘нифига не found!’;
Способ хороший, но тормозной - из-за eregi. (Функция это работает с регулярными выражениями, поэтому и тормозит). По той же причине рекомендуется использовать там, где это можно, str_replace вместо ereg_replace. Быстрее раз в 10: Поэтому крутые программеры ;), когда им надо проверить, найдено ли что-то в строке, используют функцию strstr. На самом деле, она для этого не предназначена, (верней, «предназначена не для этого»), ибо она «Find first occurrence of a string», то есть «ищет первое местонахождение строки» и выводит строку, начиная с этого самого местоположения. Запутал, верно? (смайлик).
Ок, вот пример с php.net:
$email = ’sterling@designmultimedia.com’;
$domain = strstr ($email, ‘@’);
print $domain;
// выводит: @designmultimedia.com
Теперь понятно? Функция ищет, где в строке встречается подстрока «@» и выводит все после нее (включительно). Что самое главное - если ничего на найдено, то функция возвращает false. Именно поэтому ее можно использовать вот так:
if (stristr($string, ‘this must be found’))
echo ‘found!!’;
else
echo ‘нифига не found!’;
У меня в скрипте для поиска в индексе используется stristr. Кроме того, поиск понимает простейший синтаксис: «+» (слово должно быть найдено, aka AND), «-» (слово не должно быть найдено, aka NOT) и «*» (звездочка). Но, анализируя то, что искали у меня на сайте, могу сказать только одно: Где-то в дискуссии про поисковые машины и их AI (искусственный интеллект), я нашел такую фразу, что «проще выучить последнего дебила пользоваться языком запросов, чем научить поисковую машину угадывать, что же именно этому дебилу надо/». Действительно, запрограммировать поисковую систему так, чтобы она сразу же выдавала то, что надо по идиотским запросам - сложно. Но, похоже, обучить ИХ составлять запросы правильно еще сложней:
В моем поиске всеми этими значками никто не пользуется, как бы я ни распинался. Хотя, когда мне надо найти у себя что-то конкретное, я нахожу с первого запроса (да, конечно, я ведь примерно знаю, что искать, и сам писал скрипт поиска, но все-таки:)
Скрипт простой, но работает надежно. Если правильно составить запрос - то находит все с первого раза. В принципе, можно еще сделать сортировку по релевантности, сделать так, чтобы из найденного файла показывался кусок с текстом, где искомое слово было бы выделено, и прочее: Но это вы делайте сами:
<?php
// Spectator’s Site Search Script
// (C) Spectator.ru
// Для работы требуется PHP 4 или выше.
// Если вы будете использовать этот скрипт, ссылка на Spectator.ru
// крайне желательна. Спасибо.
// файл с индексом
$indexfile=”indexfile.txt”;
// обрабатываем запрос
$total=0;
$qu2=str_replace (”+”,”&”,$words);
// убираем заглавные буквы
$qu2=strtolower ($qu2);
// обрубаем в конце лишние пробелы
$qu2=chop($qu2);
// убираем двойные пробелы
while (stristr($qu2,” “)) $qu2=str_replace (” “,” “,$qu2);
echo (’Запрос: ‘.$qu2);
echo (’<p><p><p>’);
// разбиваем запрос на слова
$words = explode (’ ‘, $qu2);
// удаляем в запросе все лишнее (знаки препинания, и прочее)
$qu2=eregi_replace (’[.?,!()#”:;|]’, ”, $qu2);
// проверяем длинну запроса
if (strlen($qu2)>2):
// открываем индекс
$index=file ($indexfile);
$num= (count($index)-1);
// для каждой сточки из индекса (одна строчка=один файл) выполняем:
for ($i=1; $i<$num+2; $i++):
$contents=$index[$i-1];
$wordcount =0;
$mustfound=1;
// выполняем для каждого их запрошенных слов:
$mustntfound=1;
for ($q=0; $q<count($words); $q++):
// обработка знаков *, + и -
// знак *
if (stristr($words[$q], “*”)) {$search=str_replace (”*”,”",$words[$q]); }
else { $search=” “.$words[$q].” “;}
// если в слове есть звездочка, то убираем звездочку и добавляем в начало
// и конец слова по пробелу если нет проблелов, то слово будет искаться не
// целиком, а “вообще”, то есть на запрос “чай” будет выводиться и слово
// “случайный”.
// bug: скрипт не учитывает, где в слове стоит звездочка и считает, что в
// любом случае она стоит в конце (!!)
// знак & (или +)
if (stristr($search, “&”)) {$search=str_replace (”&”,”",$search); $mustfound++; }
// если стоит знак +, то количество слов, которые _должны_ быть найдены,
// увеличиваются на 1
// знак -
if (stristr($search, “-”)) {
$search=str_replace (”-”,”",$search);
$mustntfound=0;
}
// если стоит знак +, то если слово найдено, весь результат умножается на 0
// (смотри дальше).
// если слово найдено, считаем его и умножаем на $mustntfound, то есть на 1,
// если найдено “правильное” слово и на 0, если найдено слово, помеченное
// знаком -
if (stristr($contents, $search)) {
$wordcount++;
$wordcount=$wordcount*$mustntfound;
}
endfor;
// проверяем, все ли слова, помеченные знаком + найдены,
// либо (если таких слов нет), найдено ли вообще хоть одно слово
if ($wordcount >= $mustfound):
// находим имя файла, в котором это найдено
$file=explode (”|”,$contents);
$file=$file[0];
// выводим имя файла, в котором это найдено с ссылкой
// (этот кусочек вам надо будет переделать под собственные нужды).
$file=str_replace (”.txt”, “”, $file);
$file=str_replace (”_”, “.”, $file);
echo (”<a href=”.$file.”>”.$file.”</a><br>”);
// считаем, колько всего файлов надено
$total++;
endif;
// переходим к следующемуу файлу
endfor;
// выводим результаты
if ($total!=0) echo (’<br><br>Всего найдено страниц: ‘.$total);
else echo (”<b>Ничего не найдено!</b><p>Возможно, вы”.
.” просто не правильно составили запрос. Как это сделать правильно”.
.” - смотрите <a href=search>вот здесь</a>.”);
else:
echo (”<br>Слишком короткий запрос!”);
endif;
?>
<!– Форма для поиска: –>
<form method=get action=search.php>
<input type=text size=19 name=words value=”" maxlength=150>
<input type=submit class=frm value=Go>
</form>
PS. PHP.net - очень хороший сайт. 99% того, что мне надо, я нахожу там. Все просто: если вам не понятно, как работает какая-то функция, достаточно просто ввести ее в поле поиска на том сайте и вам выдадут описание функции, но кроме того, на php.net к описанию каждой функции пользователи могут оставлять комментарии, поэтому вам выдадут и гору толковых комментариев. То, что надо.
© http://spectator.ru/
Добавить комментарий >>>
Tags:
php,
search,
поиск
Март 23, 2008
В каждой области существуют свои мифы, каждая сфера деятельности овеяна некоторой тайной, в следствие которой появляются мифы. Я попытался описать 5 наиболее распространенных на мой взгляд мифов о Web программировании.
Миф №1. C++/Pascal рулят миром.
Многие думают что, только такие гиганты программирования как Страуструп могут создать идеальный язык. Нет, на самом деле действительно на C можно написать все, что только можно реализовать в виде логической цепочки действий, но(!): обращу ваше внимание на слово “написать”. Т.е. написать то можно, но вот сколько это потребует усилий, какова будет цена выбора в пользу “идеального” языка. Возьмем, например PHP. Все знают (ну теперь точно все), что интерпретатор PHP написать на C. Что из этого следует? А то, что для того что бы писать скрипты для web на C так же легко и быстро, нужно будет написать аналог PHP. Все дело в задаче и “себестоимости” ее выполнения.
Миф №2. Web программист никогда не напишет “ничего серьезного”.
Под “серьезным” обычно понимается масштабный проект, который отнимает кучу времени нервов, но приносит таки “достойное” вознаграждение. ОК, вот аналогия из жизни. Капуста на рынке стоит N, в магазине эта же капуста стоит 1,25N, в супермаркете premium класса эта же капуста стоит 5N, а на базаре на рублевке эта же капуста стоит 1000N. И что, кто тут рубит капусту (пишет что-то серьезное)? Так вот, имхо, вопрос не в том, как вырастить капусту (написать программу), а как ее продать, а это уже вопрос из области маркетинга и программирование (хоть на PHP, хоть на C/Pascal) тут вообще не причем.
Миф №3. Лучше начинать учиться со “сложных” языков.
Отчасти, да. Вот только, как всегда завеса тайны, внесла коррективы трактовку этой фразы. Действительно что бы понять суть азы программирования (типы данных, ссылки, основы ООП) лучше начинать изучать тот язык где эти самые азы реализованы лучше всего, но(!): Обычно фраза “Лучше начинать учиться со “сложных” языков” употребляется в том контексте, что допустим человек выучивший PHP не сможет перейти допустим на C. Повторюсь PHP написан на C и унаследовал очень много от прорадителя. Значит PHP похож на C, ровно столько же сколько C похож на PHP. Почему кто-то не сможет перейти от простого к сложному, а от сложного к простому этот же самый человек сможет. Т.е. не изучая PHP вы способны изучить C, а вот освоив PHP вы сразу же, по мановению волшебной палочки, теряете свойство “могу изучить C”. Бред!
Миф №4. Пиши с нуля.
Этот миф часто возникает сам по себе в головах начинах кодеров. “Ах, сколько кода, как долго с ним разбираться”: “лучше я напишу свой аналог, в котором будет присутствовать только “то, что мне нужно”". Почему это миф? Во-первых, если сложно понять, чужой код, то это еще не значит что он плохой. Во-вторых, составляем список “того что нужно”, сравниваем с тем, что есть и забиваем на “пиши с нуля”. Лучше писать надстройки для, например, управлениями обмена ссылками между 10 (100,1000 нужное подчеркнуть) форумов, модули ко всяким CMS наконец. Другое дело, если кодить что-то уникальное по своей природе, но в 99,99% случаях фраза “пиши с нуля” употребляется из-за нежелания разбираться в чужом коде и учиться в целом. Психологический вопрос, имхо. Конечно, можно кодить с нуля для “закрепления пройденного материала”, но опять же это укладывается в те самые 0,001%.
Миф №5. Все уже написано.
Вот только не надо опускать руки! На самом деле написано настолько мало, что аж [censored]! Другое дело, если в процессе обучения (практики) кодинга, возникает ситуация когда не находится задачи к которой стоит “приложить руки”. И вот тут в игру вступают “срули”. У них все написано, все сделано, все места заняты: пора пойти и застрелится. Что тут можно сказать. Люди, это не программирование придумает задачи для жизни, а жизнь дает пишу (кусочек хлебушка и чашку и икорочкой) программисту. Не там ищите идеи. Действительно, накодить что-нить очень тяжело, а вот например накодить парсер, новостного сайта сложнее, еще сложнее усовершенствовать его и сделать настраиваемые уведомленения по e-mail, icq и sms. Еще сложнее написать систему отслеживания копий текста с сайта (мало ли кто-то “чисто случайно” забыл поставить ссылку на вас). Да много чего можно сделать.
Вместо заключения скажу, что многие мифы это на самом деле простая подмена понятий, или какие-то психологические барьеры. Никто не может решить за вас, что лучше, что правда, и что делать.
Автор: Kotov
Блог PHP разработчика
Tags:
php
Март 22, 2008
Для начала немного теории. Всё, что написано ниже, справедливо для сервера Apache (их в интернете подавляющее большинство). Когда вы набираете в строке несуществующий адрес или переходите по “битой ссылке”, на страничке высвечивается жирными буквами сообщение “Not found” - “документ не найден”, хотя вкупе с устрашающим видом надписи может быть переведено неопытным пользователем как “пошёл вон” :). Кстати, пользователи IE возможно и не видели эту страничку ни разу, поскольку он – IE – формирует в этом случае своё сообщение с “дружественным” содержанием, типа “обновите страничку, позвоните другу, который вам эту ссылочку дал…”, но это делу не помогает. Иногда IE может и показать оригинальное сообщение сервера, но только в том случае, если оно больше определённого размера (по умолчанию 512 байт). В общем, итог всегда один – страницы нет и посетитель недоволен. А нам, администраторам сайтов, надо заботиться, чтобы эта потеря была менее болезненной.
Давайте разберёмся, что происходит на сервере при запросе правильных/ошибочных URL.
При GET-запросе URL (тот, что в адресной строке браузера) передаётся серверу, а на выходе клиенту выдаётся набор заголовков с последующим полем данных. Заголовки никогда не выводятся пользователю напрямую и содержат много служебной информации, которая может быть обработан CGI-скриптами (а это нам скоро и понадобится). Среди этих заголовков всегда есть так называемые коды ответов. Если не вдаваться в подробности, то они передаются примерно так:
GET /index.htm
HTTP/1.1 404 Not Found
Первая строка говорит о том, что пользователем (точнее, браузером пользователя) был передан запрос на страницу index.htm, а вторая строка сообщает ему, что такой документ не найден, после чего следует блок данных - HTML-страница с сообщением об ошибке. Если URL правильный, всё происходит так, как и должно быть – передаётся код ответа 200 и запрошенный файл. Код ответа 200 передаётся всегда при удачном запросе, но мы его никогда не видим. Как уже говорилось, при неудачном запросе сервер сгенерирует код ответа в диапазоне 400…499 и мы увидим стандартное сообщение об ошибке, которое, огорчает пользователя и портит репутацию сайта. Вообще говоря, у хорошего веб-мастера таких ошибок на сайте быть не должно, но не его вина, что например пользователь вдруг сказал “принеси то, не знаю что”.
Перейдём от рассуждений к делу. У сервера Apache имеется стандартная директива обработки ошибок ErrorDocument, которая сопоставляет коду ошибки адрес документа, который будет показан пользователю. Обычно перенаправление устанавливают на документ, содержащий логотип сайта и краткую информацию “что делать”. Увидев такую страницу вместо стандартного сообщения на fatal.ru (два года назад, когда начал заниматься веб-программированием), я долго был под впечатлением! Откуда они знают, что у меня такой страницы нет?
Формат директивы такой:
ErrorDocument 400 400.html
В случае ошибки 400 пользователю выдаётся файл 400.html – всё очень просто и удобно. Сразу же отметим, что можно использовать четыре варианта передачи сообщения об ошибке:
ErrorDocument 500 http://foo.example.com/cgi-bin/tester
ErrorDocument 404 /cgi-bin/bad_urls.pl
ErrorDocument 401 /subscription_info.html
ErrorDocument 403 “Доступ запрещён
В последнем случае вместо файла пользователь увидит на экране сообщение, следующее за одиночной кавычкой (закрывать кавычку не следует!).
Прописать директиву можно в двух местах – конфигурационном файле Apache httpd.conf или в файле управления доступом к директориям .htaccess. В первом случае вы должны иметь доступ к httpd.conf, а для этого вы должны являться администратором сервера (и, скорее всего, вам эта статья не понадобилась бы
или пользователем хостинга - во втором случае. Причём на хостинге должно быть включено редактирование .htaccess самим пользователем (это позволяют все платные и почти все бесплатные службы хостинга). Кроме того, на платных серверах часто установлена панель управления сайтом cPanel. Так вот там можно самому создавать и редактировать страницы ошибок на основе SSI, даже если вы не специалист.
Короче, создаём в корне своего сайта файлик .htaccess и записываем в него строчку “ErrorDocument 400 400.html”, при условии, что файл 400.html уже существует.
Дальше начинается самое интересное, а именно – как будет выглядеть эта страничка. Создать страничку можно как минимум тремя способами.
Самый простой вариант – набросать в HTML-редакторе или “Блокноте” обычную страничку без графического оформления и сохранить её под нужным именем.
Вариант посложнее – можно добавить в этот HTML-файл директивы SSI, которые, к примеру, будут показывать URL, адрес, с которого пришёл пользователь, время, подключить внешние файлы и т.д.
Но согласитесь, первые два варианта – это примитив, не достойный настоящего веб-мастера. Опытные программисты пишут страницы ошибок на PHP. В этом есть одно достоинство – мы получаем доступ к переменным окружения, что даёт возможность лучше анализировать ошибочную ситуацию, выводить больше информации пользователю, вести лог ошибок в файле или базе данных, а не тупо выводить Not found и посылать бедного юзера на главную страницу.
Как вы уже догадались, мы будем рассматривать именно третий вариант. Начнём с простого – как PHP-скрипт получит код ошибки? Естественно, через параметр. Например, вот так:
error.php?400
В самом скрипте код ошибки после знака вопроса будет доступен в переменной $argv[0]. Теперь проверим скрипт (он должен вывести код ошибки, указанный после “?”):
<?php
echo $argv[0];
?>
Можно было бы передать параметр в классическом виде error.php?id=400, но так проще и безопаснее - этот скрипт принимает только одну переменную, зачем лишние лазейки? Сразу же обезопасим скрипт от ввода неверных данных: приведём код к целому типу и сделаем присвоение кода 404 по умолчанию, если скрипт был вызван без параметров. Приучайте себя к написанию безопасного кода! Теперь даже если ввести error.php?404abc или даже error.php?abc – значение переменной $id (она введена для читабельности) всё равно будет равно 404.
<?php
// проверяем переменную
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
echo $id;
?>
Следующее, что мы сделаем – сопоставим коду русскоязычное описание. Это можно сделать при помощи операторов switch…case, но опытный программист сделает это более аккуратно и красиво – через ассоциативный массив, в котором ключ – это код ошибки, а значение – это её описание. Сделать такой массив очень просто, да и дополнять его легче, чем списки switch…case. Рекомендую начинающим программистам всегда использовать ассоциативный массив вместо switch…case, если в списке больше трёх позиций – выигрыш в скорости, размере кода и понятности. Список кодов ошибок можно найти на официальном сайте W3C (организация по веб-стандартам) http://www.w3.org/Protocols/HTTP/HTRESP.html или в любой книге по веб-программированию. Я сделал так:
<?php
// проверяем переменную
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
// ассоциативный массив кодов и описаний
$a[401] = “Требуется авторизация”;
$a[403] = “Пользователь не прошел аутентификацию, доступ запрещен”;
$a[404] = “Документ не найден”;
$a[500] = “Внутренняя ошибка сервера”;
$a[400] = “Неправильный запрос”;
// выводим код и описание
echo “$id $a[$id]”;
?>
В результате выполнения этого кода пользователь увидит сообщение “404 Документ не найден” (по-крайней мере, так вывелось у меня, вы можете смоделировать другую ошибку). В принципе, тот же результат можно было получить при использовании первого способа, но разве я сказал, что мы на этом остановимся?
Кстати, пока не забыл. Ошибки с кодами 500…599 встречаются обычно, когда ошибку совершает сценарий, расположенный на вашем сервере. Некоторые интерпретаторы сценариев, например PHP, никогда не допускают такой ошибки. Если в вашем PHP-скрипте есть ошибка, интерпретатор сам выведет ошибочное сообщение с указанием её происхождения, а не просто “Ошибка”. Со стороны сервера это будет выглядеть как нормальный ответ клиенту, поэтому ошибка 500 при использовании PHP у вас практически никогда не возникнет. В отличие от него, Perl генерирует ошибку 500 и записывает сообщение о ней в лог сервера. Иди потом, разбери его – сложность отладки Perl-скриптов очень высокая. Это была одна из причин, по которой мне в своё время посоветовали перейти с Perl на PHP, что я и сделал, чего и вам желаю.
Вернёмся к написанию скрипта. Основная часть – обработка кода ошибки – уже готова, займёмся “довесками”. Для начала выведем поясняющее сообщение с ошибочным URL:
echo “Запрошенный Вами URL: <b>http://$SERVER_NAME$REQUEST_URI</b><br /> “;
Здесь включены две глобальные переменные $SERVER_NAME и $REQUEST_URI. Первая содержит имя сервера, вторая – URI (не путать с URL), который был запрошен. Выведем ещё на всякий случай IP-адрес, название браузера и текущее время на сервере.
$time = date(”d.m.Y H:i:s”);
Ваш IP: <b>$REMOTE_ADDR</b><br />
Ваш браузер: <b>$HTTP_USER_AGENT</b><br />
Текущее время сервера: <b>$time</b><br />
Согласитесь, это уже намного интересней. Если пользователь пришёл на ошибку по ссылке с другой страницы, покажем ему и этот URL этой страницы (переменная $HTTP_REFERER). Дополнительно можно показать реальный IP-адрес клиента, если он работает через прокси (переменная $HTTP_X_FORWARDER_FOR).
if ($HTTP_REFERER) $body .= “Вы пришли со страницы: <b>$HTTP_REFERER</b><br />\n”;
if ($HTTP_X_FORWARDER_FOR) $body .= “Ваш IP через прокси: <b>$HTTP_X_FORWARDER_FOR</b><br />\n”;
Ну и в завершение в самом низу “подпись” сервера (не на всех хостингах она работает корректно, потому что это чисто “серверная” переменная):
$_SERVER[’SERVER_SIGNATURE’]
Думаю, этой информации будет достаточно, для того чтобы пользователь не почувствовал, что пришёл на “последнюю страницу интернета”. Но есть ещё одна деталь, которую не упустит внимательный программист. Вспомните, откуда вы чаще всего попадаете на “ошибочные страницы”? Ну конечно из поисковых систем, в которых информация быстро устаревает. В подавляющем большинстве случаев мелкие проекты, типа FoxWeb переезжают с сервера на сервер, а потом на новом сервере сайт модернизируется, а старый остаётся на старом месте. Ну, в общем вы меня поняли :). У меня было так: первый сайт был открыт на kiiut.fatal.ru, потом всё его содержимое было скопировано на foxweb.net.ru. В поисковых системах хранятся ссылки на оба сайта. Через какое-то время старое содержимое на foxweb было удалено, а ссылки на него остались в поисковиках. Если заменить адрес сервера в ошибочной ссылке, то она вполне будет работать. Поясню на примере.
Предположим, человек нашёл в поисковой системе что-то вроде http://foxweb.net.ru/catalog/sbornik_fox1.html. У нашего сервера такой ссылки нет, но она должна была остаться на старом сервере со старым содержимым. Тогда предложим пользователю перейти по адресу http://kiiut.fatal.ru/catalog/sbornik_fox1.html. Опишем эту особенность в программе:
Возможно интересующую Вас информацию можно найти по старому адресу:<br />
<a href=”http://kiiut.fatal.ru$REQUEST_URI” mce_href=”http://kiiut.fatal.ru$REQUEST_URI” target=”_blank”><b>http://kiiut.fatal.ru$REQUEST_URI</b></a><br />
Даже если у вас не было такой ситуации, как у меня, возможно вы переписывали скрипты и адреса поменялись. Например, http://someserver.ru/catalog/ заменим на http://someserver.ru/?catalog или http://someserver.ru/cgi-bin/catalog.cgi. Надеюсь, общий смысл вам понятен, главное знать, что чем заменять.
Есть ещё одна особенность применения ошибочных страниц. Если несуществующая страница вызвана через вложенные директории, а заменяющая страница (наш скрипт) лежит в корне, то картинки на ней не будут отображены, а ссылки не будут работать. Почему? Это произойдёт в том случае, если вы используете на своём сайте относительные пути. Приведу пример:
http://someserver.ru/dir1/dir2/dir3/ - ошибочный адрес.
http://someserver.ru/error.php?404 – заменяющая страница будет вызвана по ОШИБОЧНОМУ ПУТИ, так как будто она там и хранится! Тогда ссылки на ней типа “page1.html” будут реально приводить вас по адресу http://someserver.ru/dir1/dir2/dir3/page1.html что будет вызывать ещё большее количество ошибок. Излечиться от этого можно, используя абсолютные пути ссылок и картинок (с именем сервера) или ставить перед относительным путём знак “/”, что на большинстве серверов указывает на корневую директорию сайта. Заметьте, что знак “./” будет указывать на текущую директорию.
В заключении приведу полный текст скрипта. Он может использоваться как сам по себе (отдельная страничка), так и в качестве модуля основного движка (вызывается движком и вставляется в серединку шаблона, как это сделано на моём сайте). Для серьёзных профессиональных проектов может оказаться полезным записывать информацию об ошибках в базу данных.
Фрагмент файла http.conf или .htaccess для правильной обработки ошибок:
ErrorDocument 400 /error.php?400
ErrorDocument 401 /error.php?401
ErrorDocument 403 /error.php?403
ErrorDocument 404 /error.php?404
ErrorDocument 500 /error.php?500
Текст скрипта error.php:
<?php
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
// ассоциативный массив кодов и описаний
$a[401] = “Требуется авторизация”;
$a[403] = “Пользователь не прошел аутентификацию, доступ запрещен”;
$a[404] = “Документ не найден”;
$a[500] = “Внутренняя ошибка сервера”;
$a[400] = “Неправильный запрос”;
// определяем дату и время в стандартном формате
$time = date(”d.m.Y H:i:s”);
// эта переменная содержит тело сообщения
$body =<<<END
Запрошенный Вами URL: <b>http://$SERVER_NAME$REQUEST_URI</b><br />
Возможно интересующую Вас информацию можно найти по старому адресу:<br />
<a href=”http://kiiut.fatal.ru$REQUEST_URI” mce_href=”http://kiiut.fatal.ru$REQUEST_URI” target=”_blank”><b>http://kiiut.fatal.ru$REQUEST_URI</b></a><br />
<br />
Ваш IP: <b>$REMOTE_ADDR</b><br />
Ваш браузер: <b>$HTTP_USER_AGENT</b><br />
Текущее время сервера: <b>$time</b><br />
END;
if ($HTTP_REFERER) $body .= “Вы пришли со страницы: <b>$HTTP_REFERER</b><br />\n”;
if ($HTTP_X_FORWARDER_FOR) $body .= “Ваш IP через прокси: <b>$HTTP_X_FORWARDER_FOR</b><br />\n”;
?>
<h1><i><?=$id?></i> <?=$a[$id]?></h1>
<p><?=$body?></p>
<?=$GLOBALS[’SERVER_SIGNATURE’]?>
Желаю всем удачного программирования и 200-го кода!
Автор: fox++
http://foxweb.net.ru/
Tags:
404,
Apache,
error,
php
Март 21, 2008
Сегодня расскажу вам о том, как написать гостевую книгу на PHP и MySQL. Ничего сложного в этом нет, да и возможности данной гостевой не самые большие: постраничный вывод записей, проверка вводимых данных, возможность удалять записи.
Допустим, что у вас уже есть PHP, MySQL и веб-сервер. Вы всё установили и настроили.
Начнём с создания таблицы, в которой будут храниться данные нашей гостевой книги. Будем спрашивать у пользователя имя и комментарий. При желании пользователь сможет сообщить адреса электронной почты и домашней странички. Для администрирования книги нам понадобится ещё одно поле, уникальное для каждой записи, - идентификатор. Ну и дата, конечно. В итоге получается такая таблица:
CREATE TABLE gb (
id int(10) unsigned NOT NULL auto_increment,
datetime datetime DEFAULT ‘0000-00-00 00:00:00′ NOT NULL,
name varchar(100) NOT NULL,
email varchar(100),
www varchar(100),
message text NOT NULL,
PRIMARY KEY (id)
);
Таблица у нас есть. Теперь можно приступать к программированию.
Для создадим файл с настройками гостевой книги:
<?php
// общие константы
define(’PATH’, ‘/gb/’); // путь к гостевой книге
define(’RECSPERPAGE’, 10); // количество записей на одной странице
define(’ADMIN_EMAIL’, ‘artem@sapegin.ru’); // email администратора
define(’ERROR_LOG_FILE’, ‘logs/error.log’); // файл лога ошибок
// Параметры БД
define(’DBHOST’, ‘localhost’); // имя хоста
define(’DBUSER’, ‘root’); // имя пользователя
define(’DBPASSWD’, ”); // пароль
define(’DBNAME’, ‘test’); // имя базы данных
?>
Теперь подумаем, какие вспомогательные функции нам понадобятся. На нужно будет взаимодействовать с СУБД, проверят и обрабатывать вводимые пользователем данные. Так же для функций администрирования на понадобится отличать администратора от простых пользователей.
Начнём с работы с СУБД.
<?php
/** recource db_connect ( string host, string user, string passwd, string dbname )
* Подключение к СУБД и открытие базы данных
*/
function db_connect($host, $user, $passwd, $dbname)
{
$link = mysql_pconnect($host, $user, $passwd) or die(’Could not connect to database’);
mysql_select_db($dbname) or die(’Could not select database’);
return $link;
}
/** Выполняет запрос к БД
*
* @param текст запроса
* @return resource id
*/
function db_query($query)
{
$result = mysql_query($query)
or die(’Bad database query’);
return $result;
}
/** Выполняет запрос к БД (placeholder)
*
* @param текст запроса
* @param*
* @return resource id
*/
function db_query_ex($query)
{
$values = func_get_args();
array_shift($values);
$i = 0;
return db_query(preg_replace(’%\?%e’, ‘”\’”.addslashes($values[$i++]).”\’”‘,
$query));
}
?>
Обработка строк (проверка и фильтрация вводимых пользователем данных).
<?php
/**
* Проверяет является ли строка адресом e-mail
*/
function strings_isemail($string)
{
return preg_match(’%[-\.\w]+@[-\w]+(?:\.[-\w]+)+%’, $string);
}
/**
* Добавление ссылок на http и e-mail
*/
function strings_addlinks($string)
{
return preg_replace(
‘%((?:http|ftp)://[-\w]+(?:\.[-\w]+)+\b[-\w:@&?=+,!/~*$\.\’\%]*)(?<![\.,?!)])%i’,
‘<a href=”\\1″>\\1<a>’,
$string
);
}
/**
* Чистка строки
*/
function strings_clear($string)
{
$string = trim($string);
$string = stripslashes($string);
return htmlspecialchars($string, ENT_QUOTES);
}
/**
* Обрезание строки
*/
function strings_stripstring($text, $wrap, $length)
{
$text = preg_replace(’%(\S{’.$wrap.’})%’, ‘\\1 ‘, $text);
return substr($text, 0, $length);
}
?>
Написание аутентификации администратора я оставляю вам в качестве домашнего задания. Есть достаточно много способов и их обсуждение - тема отдельной статьи. Я приведу лишь функцию-заглушку:
<?php
/**
* Проверка: администратор или обычный пользователь
*/
function auth_is_admin()
{
return @$_GET[’admin’];
}
?>
Далее идёт достаточно большой модуль, в котором содержится почти весь HTML-код гостевой книги, - шаблон. В нём нет ничего сложного и его написание можно вполне под силу верстальщику сайта, если у вас таковой имеется.
<?php
/**
* заголовок страницы
*/
function template_header($page)
{
?><html>
<head>
<title>page <?=$page?> < fjGuestbook Demo</title>
<style>
body{
padding: 15px;
margin: 0;
color: #333;
background-color: #eee;
border-left: 30px solid #adba8e;
font: 500 .9em verdana, arial, helvetica;
}
a:link{color: #250;}
a:visited{color: #639;}
a:active,a:hover{
color: #c00;
text-decoration: underline;
}
h1 { font-size: 150%; }
h2 { font-size: 110%; }
.c{margin-bottom: 10px;}
.cn{
background-color: #d2d6bc;
padding: 2px 4px;
margin-bottom: 4px;
}
</style>
</head>
<body>
<h1>fjGuestbook Demo</h1><?php
}
/**
* окончание страницы
*/
function template_footer()
{
?>
<p>fjGuestbook 1.2. Copyright © 2002—2004
<a href=”http://sapegin.ru”>Artem Sapegin</a></p>
</body></html>
<?php
}
/**
* форма добавления новой записи
*/
function template_form($name, $email, $www, $message, $error)
{
// вывод сообщения об ошибке
function error($error)
{
if($error) echo ‘<br><font color=#880000>’.$error.
‘</font>’;
}
echo ‘<h2>Добавить новое сообщение</h2>
<p><table cellspacing=”2″ cellpadding=”2″ border=”0″>
<form action=’.PATH.’?add=1 method=post><tr>
<td>Имя<font color=#880000>*</font>:</td>
<td><input type=text name=”name” size=30
maxlength=100 value=”‘.$name.’”>’;
@error($error[’name’]);
echo ‘</td>
</tr><tr>
<td>Email:</td>
<td><input type=text name=”email” size=30
maxlength=100 value=”‘.$email.’”>’;
@error($error[’email’]);
echo ‘</td>
</tr><tr>
<td>URL:</td>
<td><input type=text name=”www” size=30
maxlength=100 value=”‘.$www.’”>’;
echo ‘</td>
</tr><tr>
<td>Сообщение<font color=#880000>*</font>:</td>
<td><textarea cols=40 rows=5
name=”message”>’.$message.’</textarea>’;
@error($error[’message’]);
echo ‘</td>
</tr><tr>
<td> </td>
<td><small><font color=#880000>*</font>
— Обязательные поля</small></td>
</tr><tr>
<td> </td>
<td><input name=”sb” type=submit
value=”Добавить сообщение”></td>
</form></tr>
</table>’;
}
/**
* печать одной записи гостевой книги
*/
function template_show_body($id, $name, $email, $www, $message, $datetime)
{
$out = ‘<div class=c><div class=cn><b>’.$name.’</b>’;
// если есть email или homepage - печатаем их
if($email || $www)
{
$out .= ‘( ‘;
if($email)
$out .= ‘ <a href=mailto:’.$email.’>email</a>’;
if($email && $www)
$out .= ‘ | ‘;
if($www)
$out .= ‘ <a href=’.$www.’>www</a>’;
$out .= ‘ )’;
}
$out .= ‘ пишет ‘.$datetime.’:</div>’.$message.’</div>’;
// если гостевую книгу просматривает администратор - печатаем кнопку
// удаления записи
if(auth_is_admin())
{
$out .= ‘<div class=c>[ <a href=’.PATH.’?admin=1&del=’.$id.
‘>удалить</a> ]</div>’;
}
return $out;
}
?>
И вот, мы наконец-то дошли до главного. До модуля гостевой книги. Постараюсь написать побольше комментариев, чтобы вам было понятно.
<?php
/**
* Создание таблицы, если её ещё нет
*/
function gb_install()
{
db_query(
‘CREATE TABLE IF NOT EXISTS gb (
id int(10) unsigned NOT NULL auto_increment,
datetime datetime NOT NULL default \’0000-00-00 00:00:00\’,
name varchar(100) NOT NULL default \’\',
email varchar(100) default NULL,
www varchar(100) default NULL,
message text NOT NULL,
PRIMARY KEY (id),
INDEX (datetime)
) TYPE=MyISAM;’
);
}
/**
* Добавление записи в гостевую книгу
*/
function gb_add($name, $email, $www, $message, &$error)
{
// проверяем правильность заполнения полей
$error = ”;
if(empty($name))
$error[’name’] = ‘Это обязательное поле’;
if(empty($message))
$error[’message’] = ‘Это обязательное поле’;
if(!empty($email) && !strings_isemail($email))
$error[’email’] = ‘Это не email’;
// если не было ошибок - добавляем
if(!$error)
{
// чистим данные
$name = strings_clear($name);
$message = strings_clear($message);
$name = strings_stripstring($name, 15, 100);
$email = strings_stripstring($email, 100, 100);
$www = strings_stripstring($www, 100, 100);
$message = strings_stripstring($message, 100, 2000);
$message = nl2br($message);
// если пользователь поленился написать http:// перед адресом - сделаем
// это за него
if(!empty($www) && ‘http://’ != substr($www, 0, 7))
$www = ‘http://’.$www;
// запрос на добавление записи в базу данных
db_query_ex(’INSERT INTO gb (name, email, www, message, datetime)
VALUES(?, ?, ?, ?, NOW())’, $name, $email, $www, $message);
// перекидываем браузер на первую страницу
// это нужно, чтобы, если пользователь нажмет кнопку Refresh,
// запись не добавилась еще раз
header(’Location: ‘.PATH.”?page=1″);
}
}
// удаление записи из гостевой книги
function gb_delete($id)
{
// запрос на удаление записи из базы данных
// WHERE id = ‘.$id указывает на запись, которую следует удалить
db_query_ex(’DELETE FROM gb WHERE id = ?’, $id);
header(’Location: ‘.PATH.”?page=1″); // ???
}
// вывод страницы с записями
function gb_show($page)
{
// положение первой записи страницы
$begin = ($page - 1) * 10;
// выборка записей из базы данных
// SELECT * FROM gb - все поля из бд gb
// ORDER BY datetime DESC - сортировка по дате, новые сверху
// LIMIT ‘.$begin.’,’.RECSPERPAGE - ограничение:
// RECSPERPAGE (см. defines.php) записей начиная с $begin
$result = db_query(’SELECT * FROM gb ORDER BY datetime DESC LIMIT ‘.
$begin.’, ‘.RECSPERPAGE);
$out = ”;
// цикл по всем выбранным записям
while($row = mysql_fetch_array($result))
$out .= template_show_body($row[’id’], $row[’name’], $row[’email’],
$row[’www’], $row[’message’], $row[’datetime’]);
// уничтожаем результат
mysql_free_result($result);
echo $out;
}
// вывод списка страниц
function gb_showpages($current)
{
// узнаем число записей в гостевой книге
$result = db_query(’SELECT * FROM gb’);
$rows = mysql_num_rows($result);
if($rows)
{
$pages = ceil($rows / RECSPERPAGE);
// печатаем ссылки на страницы (номер текущей страницы не является ссылкой)
echo ‘<div class=c>’;
for($i = 1; $i <= $pages; $i++)
{
if($i != $current)
echo ‘ | <a href=’.PATH.’?page=’.$i.’>’.$i.’</a>’;
else
echo ‘ | ‘.$i;
}
echo ‘ |’;
// если это не полследняя страница печатаем ссылку “Дальше”
if($current < $pages)
echo ‘ >a href=’.PATH.’?page=’.($current + 1).
‘>Дальше >></a>’;
echo ‘</div>’;
}
}
?>
И последнее - объединяем всё вместе.
<?php
/**
* fjGuestbook 1.2
*
* Ядро гостевой книги
*
* Copyright 2002-2004 Artem Sapegin
* http://sapegin.ru
*/
// подключаем модули
require_once ‘my/defines.php’;
require_once ‘my/template.php’;
require_once ‘engine/lib/strings.php’;
require_once ‘engine/lib/auth.php’;
require_once ‘engine/lib/bd.php’;
require_once ‘engine/gb.php’;
// подключаемся к БД
db_connect(DBHOST, DBUSER, DBPASSWD, DBNAME);
// создаём таблицу, если её нет
gb_install();
// получаем данные формы, если форма была отправлена
if (!empty($_POST[’sb’]))
{
$name = @$_POST[’name’];
$email = @$_POST[’email’];
$www = @$_POST[’www’];
$message = @$_POST[’message’];
$formerr = ”;
}
else
{
$name = $email = $www = $message = $formerr = ”;
}
// если в GET-запросе не указан номер страницы, выводим первую
if(is_numeric(@$_GET[’page’]))
$page = $_GET[’page’];
else
$page = 1;
// если нужно добавить запись, добавляем
if(@$_GET[’add’])
gb_add($name, $email, $www, $message, $formerr);
// если нужно удалить запись, удаляем
if(isset($_GET[’del’]) && auth_is_admin())
gb_delete(intval($_GET[’del’]));
// печатаем гостевую книгу
template_header($page);
gb_showpages($page);
gb_show($page);
gb_showpages($page);
template_form($name, $email, $www, $message, $formerr);
template_footer();
?>
Как видите, ничего сложного не было. Но если вопросы всё же возникнут - спрашивайте - постараюсь помочь.
Вы так же можете скачать гостевую книгу.
Автор: Artem Sapegin
http://sapegin.ru
Источник: http://www.codenet.ru
Tags:
MySQL,
php
Март 18, 2008
Виват, дорогие читатели ! Сегодня, в этот ничем не знаменательный день, а может быть совершенно наоборот, я написал “это”, а сейчас вам предстоит всё “это” прочитать, а самое главное понять. Сегодня мы с вами посвятим время такой теме, как “Системы голосований на РНР”.
Да, на сегоднешний день этой довольно интересной теме посвященны целые горы статей, но как-то маловато статей рассказывают всё чётко и во всех мелочах. Именно про эти мелочи и чётко мы сейчас и поговорим. Наверное больше половины из вас участвовали в разных интерактивных голосования, форумных pool’ах, и в прочей дребедени. Но как это всё построено ? На каком алгоритме это всё “пашет” ?
А алгоритмов существует великое множество. Сейчас я перечислю самые популярные среди разработчиков:
XML- вопрос и ответы, храняться в одном XML файле, с которого через парсер и “достают” все данные для обеспечения работы голосований.
Хранение данных в файле - этот тип хранения более популярен чем его XML-ий товарищ. Принцип такой: вопрос помещаеться в первую строку файла, после построчно выводятся варианты ответа, а через некий разделитель количество голосов. Однако в отличие от предыдущего данный метод требует больше времени для написания, и немного ограничивает возможности.
Хранение данных в БД (способ №1) - этот способ являеться самым рациональным, ведь он не требует особых ментальных усилий, при этом обеспечивая высокую продуктивнось, но для него, во-первых, требуеться наличие БД, во-вторых, этот метод занимает много физической памяти базы данных. Мы рассмотрим его в самом начале. Принцип таков: вопрос и прочие статические данных располагаються в некой абстрактной таблице “а”, при этом ответы в таблице “б”. На каждый ответ приходится ряд “р”, таблице “а”, который имеет ссылку на номер голосования находящегося в таблице “а”. То есть между таблицей “а” и “б” устанавливаеться прямая связь. Почему я сказал, что этот способ требует немалого пространства в БД ? Так потому, что под каждый ответ выделяеться отдельный столбец, а это крайне не рационально в случае при работе с БД.
Хранение данных в БД (способ №2) - этот способ самый сложный среди всех вышеперечисленных, однако он не занимает много место, при этом он совмещает ответы, вопрос, и количество ответов в отной таблице, и в этой таблице ряд отводиться под всё голосование в целом, хотя эта компактность компенсируеться повышеной сложностью обновления данных, и засчёт каждого нового голоса. Принцип в том, что все ответы помещаються в одну строку, при чём в особом порядке, так, что позиции каждого ответа соответствует позиция значения количества голосов, людей которые проголосовали именно за этот вариант. Так же поле вариантов ответов, и соответственно количества голосов будут кодироваться в формате base64, для уменьшения размера конечной строки.
Всё, это были все варианты выполнения алгоритма процесса голосования, которые мы сегодня рассмотрим.
Лично мой выбор второй вариант хранения в БД, так как я автор данного способа, и он мне более по душе, но тем кто только начинает осваивать технологии Веб-программирования, я советую почитать про первый вариант при работе с БД. Что ж, теперь можно перейти к практической части, и она начнёться именно с первого варианта при работе с БД. Но я забыл сказать про довольно важную на мой взгляд деталь. Я забыл сказать про защиту от повторного голосования, это мы будем делать во всех голосования при помощи БД, поскольку именно этот способ обеспечивает полную целостность данных, ибо для доступа к БД третьим лицам им нужно будет иметь данные для доступа или взломать БД, что крайне проблематично, поэтому хакерам будет довольно сложно нанести скрипту урон в этой части. А файлы всегда можно удалить, даже если на них будет блокировка “666″, то всё равно они защищены не в полном объёме.
Поэтому я выбрал БД. Посему сейчас вам нужно создать табличку для храния данных о пользователях которые уже проголосовали.
Структура таблички такова:
ТАБЛИЦА `alredy_vote`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
vote_id- BIGINT- NOT NULL- UNIQUED
ip- TEXT- NOT NULL
Название оставляю вам, но скажу что я буду использовать “alredy_voted”. Что ж, а теперь на практику 
Первый вариант при работе с БД.
Что ж, по моему мнению для тех кто называет себя программистами на “достаточном” уровне хватило бы одного только описания в начале, но во всяком случае я опишу данный способ со всеми частицами. Итак, с начала мы создадим две таблички в вашей базе данных, называйте их как хотите, но в статье я буду использовать названия “pools_answs” (для вариантов ответа) и “pools” (для самих голосований). Их структура сдедующая:
ТАБЛИЦА `pools`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
question- TEXT- NOT NULL
status- ENUM(’on’,'off’)- DEFAULT ‘on’- NOT NULL
ТАБЛИЦА `pools_answs`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
vote_id- BIGINT- NOT NULL- UNIQUED
value- TEXT- NOT NULL
Вот, это структура таблиц для работы нашей системы, я привожу лишь их сруктуру, поскольку если вы даже языка SQL не знаете, то вам делать здесь явно нечего. Итак, первой нашей с вами функцией будет функция для добавления голосований.
Так же хочу упомянуть, что пользователь сам может устанавливать количество вариантов ответа. Конечно есть соблазн использовать DOM модель и любимый нами всеми JS (ведь так ?), но я воздержусь, и воспользуюсь простым и банальным параметром QUERY_STRING и старым, родимым $_GET интерфейсом. Что ж, давайте попытаемся воплотить всё это безобразие на “холсте” 
<?
$count=isset($_GET[’count’])? $_GET[’count’]:5;
if(!isset($_POST[’add’])){
print”<form action=” method=’post’ name=’addPool’>”;
print”<table width=’300′ height=’50′ align=’center’>”;
print”<tr><td colspan=’2′ style=’text-align:center;’><input
size=’40′ type=’text’ name=’question’
value=’Введите вопрос голосования’ onFocus=’this.select();’></td>
</tr>”;
print”<tr><td style=’text-align:center;’ colspan=’2′><button
onClick=\”top.location=’”.$_SERVER[’PHP_SELF’].”?count=”.($count+1).”‘\”>
Добавить вариант</button></td></tr>”;
for($i=0;$i<$count;$i++){
print”<tr><td>Вариант ответа №”.$i.”:</td><td><input
type=’text’ name=’answs[]’></td></tr>”;
}
print”<tr><Td colspan=’2′ style=’text-align:center;’><input
type=’submit’ name=’add’ value=’Добавить’></td></tr>”;
print”</table>”;
print”</form>”;
}else{
$question=$_POST[’question’];
$answs=$_POST[’answs’];
if(trim($question)==”){
die(”Вы не ввели вопрос !”);
}
$count=0;
for($i=(count($answs)-1);$i>=0;$i–){
if(trim($answs[$i])==”){
$count++;
}
if($count==count($answs) || (count($answs)-$count)<2){
die(’Должно быть как минимум 2 варианта ответа!’);
}
}
$conn_id=@mysql_connect(”localhost”,”root”,”") or die(”Ошибка соединения с
сервером БД !”);
@mysql_select_db(”shockstudio”);
$check=@mysql_query(”SELECT id FROM `pools` WHERE question=’”.$question.”‘”,
$conn_id) or die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)!=0){
die(”Голосование с таким вопросом уже существует !”);
}
unset($check);
$q=@mysql_query(”INSERT into `pools` VALUES(”,’”.$question.”‘,’on’)”,$conn_id)
or die(”Ошибка запроса к БД !”);
unset($q);
$q=@mysql_query(”SELECT id FROM `pools` WHERE question=’”.$question.”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
$row=@mysql_fetch_array($q);
$id=$row[’id’];
unset($q,$row);
for($i=(count($answs)-1);$i>=0;$i–){
if(trim($answs[$i])!=”){
$q=@mysql_query(”INSERT into `pools_answs` VALUES(”,’”.$id.”‘,’”.$answs[$i].”‘,
”)”,$conn_id) or die(”Ошибка запроса к БД !”);
}
}
}
?>
Что ж, это и есть функциональная база для добавления голосования в базу данных. Давайте остановимся на этом и рассмотрим всё детальней.
С самого начала мы получаем количество вариантов ответа, которые нужно вывести для редактирования. Мы объявляем переменную `count`, которая в соответствии от существования переменной `count` в QUERY_STRING будет принимать значение или переменной QUERY_STRING (если такая сущестует) и значение по умолчанию, а именно 5. После этого мы работаем над пользовательским интерфейсом, и при этом создаём интересную кнопочку, которая и служит для добавления варианта ответа. При её нажатии значение переменной `count` помещаеться в строку запроса в броузере, и при этом она увеличиваеться на единицу. Вот как всё просто, прям до ужаса. Далее всё просто, и вот мы перешли к обработке полученных из формы данных.
В начале обработки идёт процесс валидации данных. Мы проверям заполнены ли поля формы, а после этого количество вариантов ответа. Для проверки заполненности полей “ответов”, мы объявляем переменную $count, которая будет каждый раз увеличиваться, если $i-й элемент массива будет не заполнен.
После, если количество незаполненных полей ($count), будет равно общей сумме вариантов ответа, или их разница будет менее 2-х, то будет возбужденно исключение, которое сообщит пользователю о том, что нужно ввести минимум 2 варианта ответов. На этом проверка данных завершаеться, и мы переходим к валидации данных в БД. Сначала нам нужно проверить, не существует ли уже голосования с таким вопросом, и если существует то возбуждаем ошибку. Если всё же нет, то добавляем данные в таблицу.
Следующим шагом будет получение индификатора (id) текущей записи, для добавления вариантов ответа в таблицу. После того как мы получили ID, мы снова делаем перебор, только теперь уже с иной целью. Сейчас мы будем добавлять каждый i-й элемент массива, при условии что он не пустой, в таблицу для ответов, при этом мы так же добавляем и индификатор голосования к которому будет привязан данных ответ. Ну и в случае успеха, выводим сообщение о том, что всё прошло успешно. Как видите, без учёта некоторых моментов, всё довольно легко. Но это только первый кусочек той большой мозаики общей функциональностию. Сейчас мы переходим к следующей части, которая и будет неким тестом предыдущей, а именно воплотим сам механизм голосования через интерфейс сайта. В контексте данного варианта исполнения, это займёт почти несколько строк, так как нам всего-то навсего нужно обновить поле в одной таблице, и занести данные в другую.
Что ж, вот мой вариант исполнения данной задачи:
<?
if(!isset($_POST[’vote’])){
print”<form action=” method=’post’ name=’vote’>”;
print”<table width=’400′ height=’50′ align=’center’>”;
$conn_id=@mysql_connect(”localhost”,”root”,”") or
die(”Ошибка соединения с сервером БД !”);
@mysql_select_db(”db”);
$q=@mysql_query(”SELECT * FROM `pools` WHERE status=’on’”,$conn_id) or
die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)==0){
echo”Голосования не найдены !”;
}else{
$id=mt_rand(1,@mysql_num_rows($q));
unset($q);
$q=@mysql_query(”SELECT * FROM `pools` WHERE id=’”.$id.”‘”,$conn_id) or
die(”Ошибка запроса к БД !”);
$row=@mysql_fetch_array($q);
print”<tr><Td colspan=’2′>Q: “.$row[’question’].”</td>
</tr>”;
unset($q);
$vote_check=@mysql_query(”SELECT id FROM `alredy_vote` WHERE ip=’”.
$_SERVER[’REMOTE_ADDR’].”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
$q=@mysql_query(”SELECT id,value FROM `pools_answs` WHERE vote_id=’”.
$id.”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)==0){
die(”Вопросы не найдены !”);
}else{
while($row=@mysql_fetch_array($q)){
$row2=@mysql_fetch_array($q2);
if(@mysql_num_rows($vote_check)!=0){
$q2=@mysql_query(”SELECT count FROM `pools_answs` WHERE id=’”.$row[’id’].”‘”,
$conn_id) or die(”Ошибка запроса к БД !”);
print”<tr><td>”.$row[’value’].”</td><td>”.
$row2[’count’].”</td></tr>”;
}else{
print”<tr><td>”.$row[’value’].”</td><td><input
type=’radio’ name=’answer’ value=’”.$row[’id’].”‘></td></tr>”;
print”<input type=’hidden’ name=’id’ value=’”.$id.”‘>”;
print”<tr><td colspan=’2′><input type=’submit’ name=’vote’
value=’Проголосовать’></td></tr>”;
}
}
}
}
print”</table>”;
print”</form>”;
@mysql_close($conn_id);
}else{
$id=$_POST[’id’];
$answer=$_POST[’answer’];
$conn_id=@mysql_connect(”localhost”,”root”,”")
or die(”Ошибка во время запроса к серверу !”);
@mysql_select_db(”db”);
$q=@mysql_query(”SELECT id FROM `aredy_vote` WHERE ip=’”.
$_SERVER[’REMOTE_ADDR’].”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
if(@mysql_num_rows($q)!=0){
print”Вы уже участвовали в данном голосовании !”;
}else{
$q=@mysql_query(”INSERT into `alredy_vote` VALUES(”,’”.$id.”‘,’”.
$_SERVER[’REMOTE_ADDR’].”‘)”,$conn_id) or die(”Ошибка запроса к БД !”);
unset($q);
$q=@mysql_query(”UPDATE `pools_answs` SER count=count+1 WHERE id=’”.$id.
“‘ AND vote_id=’”.$_POST[’answer’].”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
print”Ваш голос учтён. Спасибо за участие !!”;
}
@mysql_close($conn_id);
}
?>
Я конечно перегнул на счёт нескольких строк, хотя оно в действительсности так и есть, ведь для обновления данных в БД нужно всего-то навсего 2 запроса к БД. Теперь давайте посмотрим на сомнительные места более пристально. Ну, сначала мы получаем все записи из БД, после делаем рандомизацию
(случайную выборку), ну и после получаем данные по “выпавшему” элементу. Но главным в пользовательском интерфейсе являеться именно то, чтобы обеспечить недоступность для пользователя повторной попытки проголосовать. Для этого мы делаем запрос к таблице `aredy_vote`, где мы проверяем наличие записи у которой ip-адрес равен текущему значению $_SERVER[’REMOTE_ADDR’], и если такая действительно найдена, то мы вместо радио-боксов выводим количество голосов, по каждому элементу, и так же убираем кнопочку для отправки данных формы. После вывода пользовательского интерфейса нам необходимо обработать данные поступившие из него. Но что именно нам оттуда понадобиться ?
Нам нужно будет всего-то две переменные, а именно индификатор текущего голосования, и, безусловно, вариант ответа. После этого мы производим запрос к БД, с целью проверки принимал ли данный пользователь участие в голосовании, с той же целью безопастности.
После этой проверки, мы добавляем данные о пользователе в таблицу “проголосовавших”, ну и только после этого мы обновляем данные по голосованию.
И после всех этих манипуляций мы закрываем соединение с БД и благодарим пользователя за участие в нашем голосовании. Вот мы и проделали уже около 40% всей работы над данным проектом, нам ещё осталось три механизма, а какие поговорим дальше. Сейчас я предлагаю вам перейти к следующей ступени разработки проекта, а именно к редактированию уже существующего голосования. Это уже не будет новым для вас. Однако скажу, что при редактировании голосования, мы будем обнулять его результаты.
То есть не обнулять, а удалять все элементы “ответов”, с целью пересоздания, это делаеться с целью повышения практичности и урезания кода. Так как проверка того, существует ли тот или иной ответ, введённый из формы, довольно долгое и скучное занятие, так что я решил поступить именно так.
<?
$count=isset($_GET[’count’])? $_GET[’count’] : 0;
if(!isset($_GET[’eid’]) || !is_numeric($_GET[’eid’]){
die(”Ошибка при проверке QUERY_STRING!”);
}
$_GET[’eid’]=addslashes($_GET[’eid’]);
if(!isset($_POST[’edit’])){
$conn_id=@mysql_connect(”localhost”,”root”,”")
or die(”Ошибка соединения с сервером БД !”);
@mysql_select_db(”shockstudio”);
$q=@mysql_query(”SELECT * FROM `pools` WHERE id=’”.$_GET[’eid’].”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
if(@mysql_num_rows($q)==0){
die(”Данное голосование не существует!”);
}else{
$row=@mysql_fetch_array($q);
print”<form action=” method=’post’ name=’addPool’>”;
print”<table width=’300′ height=’50′ align=’center’>”;
print”<tr><td colspan=’2′ style=’text-align:center;’><input
size=’40′ type=’text’ name=’question’ value=’”.$row[’question’].”” onFocus=’this.select();’></td></tr>”;
unset($q);
print”<input type=’hidden’ name=’id’ value=’”.$_GET[’eid’].”‘>”;
$q=@mysql_query(”SELECT value FROM `pools_answs` WHERE vote_id=’”.$_GET[’eid’].
“‘”,$conn_id);
$i=0;
while($row=@mysql_fetch_array($q)){
$i++;
print”<tr><td>Вопрос №”.$i.”:</td><td><input
type=’text’ value=’”.$row[’value’].”‘ name=’answs[]’></td></tr>”;
}
if($count!=0){
for($j=1;$j<$count;$j++){
print”<tr><td>Вопрос №”.($j+$i).”:</td><td><input
type=’text’ name=’answs[]’></td></tr>”;
}
}
}
print”<tr><td colspan=’2′><button
onClick=\”top.location.href=’?eid=”.$_GET[’eid’].”&count=”.($count+1).”‘;\”>
Добавить вариант ответа</button></td></tr>”;
print”<tr><Td colspan=’2′ style=’text-align:center;’><input
type=’submit’ name=’edit’ value=’Изменить’></td><tr>”;
print”</table>”;
print”</form>”;
}else{
$question=$_POST[’question’];
$id=$_POST[’id’];
$answs=$_POST[’answs’];
if(trim($question)==”){
die(”Вы не ввели вопрос !”);
}
$count=0;
for($j=(count($answs)-1);$j>=0;$j–){
if(trim($answs[$j])==”){
$count++;
}
if($count==count($answs) || (count($answs)-$count)<2){
die(’Должно быть как минимум 2 варианта ответа’);
}
}
$conn_id=@mysql_connect(”localhost”,”root”,”")
or die(”Ошибка соединения с сервером !”);
@mysql_select_db(”shockstudio”);
$q=mysql_query(”UPDATE `pools` SET question=’”.$question.”‘ WHERE id=’”.$id.
“‘”,$conn_id) or die(”Ошибка запроса к БД !”);
unset($q);
$q=@mysql_query(”DELETE FROM `pools_answs` WHERE vote_id=’”.$id.”‘”)
or die(”Ошибка запроса к БД !”);
for($i=(count($answs)-1);$i>=0;$i–){
$q=@mysql_query(”INSERT into `pools_answs` VALUES(”,’”.$id.”‘,’”.$answs[$i].
“‘,”)”) or die(”Ошибка запроса к БД !”);
}
print”<hr/><a href=’”.$_SERVER[’PHP_SELF’].”?eid=”.$id.
“‘>Назад</a><hr/>”;
}
?>
Что ж, в этом приложении совместились куски всего того, что мы делали в начале статьи. Пользовательский интерфейс данного приложения очень напоминает интерфейс для добавления голосования, однако здесь есть некоторые отличия. Сначала мы получаем данные по переданному индификатору.
Так как для редактирования элемента в скрипт нужно передать его ID в базе данных, по которому будут обрабатываться данные. Но в первую очередь стоит проверить существует ли данное голосование вовсе, так как мало ли что пользователь может вписать в QUERY_STRING, и кстати именно по этой причине мы производим её проверку и экранируем все данные в ней, для предотвращения SQL-инъекций. После всех проверок, мы получаем данные по голосования с индификатором $_GET[’eid’]; Теперь мы подставляем их в текстовые поля формы. Но всё бы и ничего, но ведь в случая с ответами, нам нужно не только подставить значения для редактирования, но и иметь возможность добавлять новые элементы. Потому, мы с начала выводим все элементы, имеющиеся в таблице с ответами, а вне цикла while() мы создаём ещё один цикл, который будет выводить текстовые поля в соостветствии со значением переменной $count. Замечу, что в отличии от скрипта добавления, данная переменная будет иметь значение по умолчанию, равное 0, а не пяти. Всё, с пользовательским интерфейсом разобрались, теперь плавно переходим к процесу обработки данных формы. С начала всё банально, проверяем количество заполненных полей “ответов”, и конечно не оставил ли пользователь пустым поле для текстового значения вопроса. Если всё нормально, то соединяемся с БД.
Первый наш запрос будет по обновлению значения вопроса голосования, это ничего нового для вас не откроет.
После этого, как я говорил раньше, мы удаляем все текущие элементы из таблицы ответов, и снова создаём аналогичный первому цикл, только в этот раз, мы будет добавлять данные в таблицу. Вот видите, я же говорил, что аналогия между скриптом для добавления голосований прослеживаеться ?
Ну, и в случае успеха, мы ставим ссылочку обратно, то есть на некую “главную” страницу, и передаём тот же индификатор голосования, для проверки всё ли прошло успешно и были ли данные занесены в таблицу. Ну, и теперь нам осталось лишь несколько шагов которые и приведут нас к желаемому результату, а именно механизмы блокировки голосования и его восстановления. Что я имею ввиду под “блокировкой” ?
Ну, предположим голосование в активе уже более месяца, и вам хочеться его скрыть, не удаляя, а вдруг опять захочеться его опубликовать ?
Или скажет, ваши администраторы могут сами создавать голосования, но прежде они должны пройти рецензию у вас, и пока вы не разблокируете созданные помошниками голосования они не будут в работоспособном виде. Вот для этого и нужна блокировка и публикация. Далее я не буду рассказывать про механизмы рецензий и прочего, а только о предмете данной статьи, о голосованиях. Сейчас я покажу как воплотить в жизнь данные механизмы.
Это будет производиться как нельзя просто, нам просто нужно будет изменить значение индификатора status, в таблице `pools`. Ведь вы заметили, что при рандомизации и выборке для вывода в пользовательский интерфейс сайта, мы проверяли значение поля status на равенство значению ‘on’.
Принципом же блокировки и публикации будет банальное изменение значения status, посему, если вы считаете что это легко, то я советую вам не читать дальше, поскольку действительно, многие из вас нового здесь ничего не отыщут. Вот механизм блокировки, хочу учесть, что скрипт работает под контекстом того, что ему был передан параметр $_GET[’eid’], который являеться индификатором текущего голосования.
<?
if(!isset($_GET[’eid’]) || !is_numeric($_GET[’eid’])){
die(”Ошибка проверки QUERY_STRING !”);
}
$_GET[’eid’]=addslashes($_GET[’eid’]);
$conn_id=@mysql_connect(”locahost”,”root”,”")
or die(”Ошибка соединения с сервером !”);
@mysql_select_db(’db”);
$q=@mysql_query(”SELECT * FROM `pools` WHERE id=’”.$_GET[’eid’].”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
if(@mysql_num_rows($q)==0){
die(”Данное голосование не существует !”);
}else{
$q=@mysql_query(”UPDATE `pools` SET status=’off’ WHERE id=’”.$_GET[’eid’].”"”,
$conn_id) or die(”Ошибка во время запроса к серверу !”);
}
@mysql_close($conn_id);
?>
И для публикации.
<?
if(!isset($_GET[’eid’]) || !is_numeric($_GET[’eid’])){
die(”Ошибка проверки QUERY_STRING !”);
}
$conn_id=@mysql_connect(”locahost”,”root”,”")
or die(”Ошибка соединения с сервером !”);
@mysql_select_db(’db”);
$q=@mysql_query(”SELECT * FROM `pools` WHERE id=’”.$_GET[’eid’].”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
if(@mysql_num_rows($q)==0){
die(”Данное голосование не существует !”);
}else{
$q=@mysql_query(”UPDATE `pools` SET status=’on’ WHERE id=’”.$_GET[’eid’].”"”,
$conn_id) or die(”Ошибка во время запроса к серверу !”);
}
@mysql_close($conn_id);
?>
Всё, на этом я заканчиваю рассмотрение данного механизма голосований, как и этот выпуск данной статьи. Со всеми остальными технологиями мы познакомимся в следующих выпусках, так как для одной статьи это слишком большой объём информации. На этом я с вами прощаюсь.
Если будут какие бы то нибыло вопросы, пишите мне на Ik1990@list.ru
С уважением Карпенко Кирилл, лидер команды разработки и программирования ShockStudio.
Источник http://www.codenet.ru
Tags:
php,
голосование
Март 17, 2008
Данная статья не претендует на роль всеобъемлющего руководства на тему “как сделать так, чтоб меня никто не поломал”. Так не бывает. Единственная цель этой статьи - показать некоторые используемые мной приемы для защиты веб-приложений типа WWW-чатов, гостевых книг, веб-форумов и других приложений подобного рода. Итак, давайте рассмотрим некоторые приемы программирования на примере некоей гостевой книги, написанной на PHP.
Первой заповедью веб-программиста, желающего написать более-менее защищенное веб-приложение, должно стать “Никогда не верь данным, присылаемым тебе пользователем”. Пользователи - это по определению такие злобные хакеры, которые только и ищут момента, как бы напихать в формы ввода всякую дрянь типа PHP, JavaScript, SSI, вызовов своих жутко хакерских скриптов и тому подобных ужасных вещей. Поэтому первое, что необходимо сделать - это жесточайшим образом отфильтровать все данные, присланные пользователем.
Допустим, у нас в гостевой книге существует 3 формы ввода: имя пользователя, его e-mail и само по себе тело сообщения. Прежде всего, ограничим количество данных, передаваемых из форм ввода чем-нибудь вроде:
<input type=text name=username maxlength=20>
На роль настоящей защиты, конечно, это претендовать не может - единственное назначение этого элемента - ограничить пользователя от случайного ввода имени длиннее 20-ти символов. А для того, чтобы у пользователя не возникло искушения скачать документ с формами ввода и подправить параметр maxlength, установим где-нибудь в самом начале скрипта, обрабатывающего данные, проверку переменной окружения web-сервера HTTP-REFERER:
<?
$referer=getenv(”HTTP_REFERER”);
if (!ereg(”^http://www.myserver.com”)) {
echo “hacker? he-he…\n”;
exit;
}
?>
Теперь, если данные переданы не из форм документа, находящегося на сервере www.myserver.com, хацкеру будет выдано деморализующее сообщение. На самом деле, и это тоже не может служить 100%-ой гарантией того, что данные ДЕЙСТВИТЕЛЬНО переданы из нашего документа. В конце концов, переменная HTTP_REFERER формируется браузером, и никто не может помешать хакеру подправить код браузера, или просто зайти телнетом на 80-ый порт и сформировать свой запрос. Так что подобная защита годится только от Ну Совсем Необразованных хакеров. Впрочем, по моим наблюдениям, около 80% процентов злоумышленников на этом этапе останавливаются и дальше не лезут - то ли IQ не позволяет, то ли просто лень. Лично я попросту вынес этот фрагмент кода в отдельный файл, и вызываю его отовсюду, откуда это возможно. Времени на обращение к переменной уходит немного - а береженого Бог бережет.
Следующим этапом станет пресловутая жесткая фильтрация переданных данных. Прежде всего, не будем доверять переменной maxlength в формах ввода и ручками порежем строку:
$username=substr($username,0,20);
Не дадим пользователю использовать пустое поле имени - просто так, чтобы не давать писать анонимные сообщения:
if (empty($username)) {
echo “invalid username”;
exit;
}
Запретим пользователю использовать в своем имени любые символы, кроме букв русского и латинского алфавита, знака “_” (подчерк), пробела и цифр:
if (preg_match(”/[^(\w)|(\x7F-\xFF)|(\s)]/”,$username)) {
echo “invalid username”;
exit;
}
Я предпочитаю везде, где нужно что-нибудь более сложное, чем проверить наличие паттерна в строке или поменять один паттерн на другой, использовать Перл-совместимые регулярные выражения (Perl-compatible Regular Expressions). То же самое можно делать и используя стандартные PHP-шные ereg() и eregi(). Я не буду приводить здесь эти примеры - это достаточно подробно описано в мануале.
Для поля ввода адреса e-mail добавим в список разрешенных символов знаки “@” и “.”, иначе пользователь не сможет корректно ввести адрес. Зато уберем русские буквы и пробел:
if (preg_match(”/[^(\w)|(\@)|(\.)]/”,$usermail)) {
echo “invalid mail”;
exit;
}
Поле ввода текста мы не будем подвергать таким жестким репрессиям - перебирать все знаки препинания, которые можно использовать, попросту лень, поэтому ограничимся использованием функций nl2br() и htmlspecialchars() - это не даст врагу понатыкать в текст сообщения html-тегов. Некоторые разработчики, наверное, скажут: “а мы все-таки очень хотим, чтобы пользователи _могли_ вставлять теги”. Если сильно неймется - можно сделать некие тегозаменители, типа “текст, окруженный звездочками, будет высвечен bold’ом.”. Но никогда не следует разрешать пользователям использование тегов, подразумевающих подключение внешних ресурсов - от тривиального <img> до супернавороченного <bgsound>.
Как-то раз меня попросили потестировать html-чат. Первым же замеченным мной багом было именно разрешение вставки картинок. Учитывая еще пару особенностей строения чата, через несколько минут у меня был файл, в котором аккуратно были перечислены IP-адреса, имена и пароли всех присутствовавших в этот момент на чате пользователей. Как? Да очень просто - чату был послан тег <img src=http://myserver.com/myscript.pl>, в результате чего браузеры всех пользователей, присутствовавших в тот момент на чате, вызвали скрипт myscript.pl с хоста myserver.com. (там не было людей, сидевших под lynx’ом
). А скрипт, перед тем как выдать location на картинку, свалил мне в лог-файл половину переменных окружения - в частности QUERY_STRING, REMOTE_ADDR и других. Для каждого пользователя. С вышеупомянутым результатом.
Посему мое мнение - да, разрешить вставку html-тегов в чатах, форумах и гостевых книгах - это красиво, но игра не стоит свеч - вряд ли пользователи пойдут к Вам на книгу или в чат, зная, что их IP может стать известным первому встречному хакеру. Да и не только IP - возможности javascript’a я перечислять не буду
Для примитивной гостевой книги перечисленных средств хватит, чтобы сделать ее более-менее сложной для взлома. Однако для удобства, книги обычно содержат некоторые возможности для модерирования - как минимум, возможность удаления сообщений. Разрешенную, естественно, узкому (или не очень) кругу лиц. Посмотрим, что можно сделать здесь.
Допустим, вся система модерирования книги также состоит из двух частей - страницы со списком сообщений, где можно отмечать подлежащие удалению сообщения, и непосредственно скрипта, удаляющего сообщения. Назовем их соответственно admin1.php и admin2.php.
Простейший и надежнейший способ аутентикации пользователя - размещение скриптов в директории, защищенной файлом .htaccess. Для преодоления такой защиты нужно уже не приложение ломать, а web-сервер. Что несколько сложнее и уж, во всяком случае, не укладывается в рамки темы этой статьи. Однако не всегда этот способ пригоден к употреблению - иногда бывает надо проводить авторизацию средствами самого приложения.
Первый, самый простой способ - авторизация средствами HTTP - через код 401. При виде такого кода возврата, любой нормальный браузер высветит окошко авторизации и попросит ввести логин и пароль. А в дальнейшем браузер при получении кода 401 будет пытаться подсунуть web-серверу текущие для данного realm’а логин и пароль, и только в случае неудачи потребует повторной авторизации. Пример кода для вывода требования на такую авторизацию есть во всех хрестоматиях и мануалах:
if (!isset($PHP_AUTH_USER)) {
Header(”WWW-Authenticate: Basic realm=\”My Realm\”");
Header(”HTTP/1.0 401 Unauthorized”);
exit;
}
Разместим этот кусочек кода в начале скрипта admin1.php. После его выполнения, у нас будут две установленные переменные $PHP_AUTH_USER и PHP_AUTH_PW, в которых соответственно будут лежать имя и пароль, введенные пользователем. Их можно, к примеру, проверить по SQL-базе:
*** Внимание!!!***
В приведенном ниже фрагменте кода сознательно допущена серьезная ошибка в безопасности. Попытайтесь найти ее самостоятельно.
$sql_statement=”select password from peoples where name=’$PHP_AUTH_USER’”;
$result = mysql($dbname, $sql_statement);
$rpassword = mysql_result($result,0,’password’);
$sql_statement = “select password(’$PHP_AUTH_PW’)”;
$result = mysql($dbname, $sql_statement);
$password = mysql_result($result,0);
if ($password != $rpassword) {
Header(”HTTP/1.0 401 Auth Required”);
Header(”WWW-authenticate: basic realm=\”My Realm\”");
exit;
}
Упомянутая ошибка, между прочим, очень распространена среди начинающих и невнимательных программистов. Когда-то я сам поймался на эту удочку - по счастью, особого вреда это не принесло, не считая оставленных хакером в новостной ленте нескольких нецензурных фраз.
Итак, раскрываю секрет: допустим, хакер вводит заведомо несуществующее имя пользователя и пустой пароль. При этом в результате выборки из базы переменная $rpassword принимает пустое значение. А алгоритм шифрования паролей при помощи функции СУБД MySQL Password(), так же, впрочем, как и стандартный алгоритм Unix, при попытке шифрования пустого пароля возвращает пустое значение. В итоге - $password == $rpassword, условие выполняется и взломщик получает доступ к защищенной части приложения. Лечится это либо запрещением пустых паролей, либо, на мой взгляд, более правильный путь - вставкой следующего фрагмента кода:
if (mysql_numrows($result) != 1) {
Header(”HTTP/1.0 401 Auth Required”);
Header(”WWW-authenticate: basic realm=\”My Realm\”");
exit;
}
То есть - проверкой наличия одного и только одного пользователя в базе. Ни больше, ни меньше.
Точно такую же проверку на авторизацию стоит встроить и в скрипт admin2.php. По идее, если пользователь хороший человек - то он приходит к admin2.php через admin1.php, а значит, уже является авторизованным и никаких повторных вопросов ему не будет - браузер втихомолку передаст пароль. Если же нет - ну, тогда и поругаться не грех. Скажем, вывести ту же фразу “hacker? he-he…”.
К сожалению, не всегда удается воспользоваться алгоритмом авторизации через код 401 и приходится выполнять ее только средствами приложения. В общем случае модель такой авторизации будет следующей:
Пользователь один раз авторизуется при помощи веб-формы и скрипта, который проверяет правильность имени и пароля.
Остальные скрипты защищенной части приложения каким-нибудь образом проверяют факт авторизованности пользователя.
Такая модель называется сессионной - после прохождения авторизации открывается так называемая “сессия”, в течение которой пользователь имеет доступ к защищенной части системы. Сессия закрылась - доступ закрывается. На этом принципе, в частности, строится большинство www-чатов: пользователь может получить доступ к чату только после того, как пройдет процедуру входа. Основная сложность данной схемы заключается в том, что все скрипты защищенной части приложения каким-то образом должны знать о том, что пользователь, посылающий данные, успешно авторизовался.
Рассмотрим несколько вариантов, как это можно сделать:
После авторизации все скрипты защищенной части вызываются с неким флажком вида adminmode=1. (Не надо смеяться - я сам такое видел).
Ясно, что любой, кому известен флажок adminmode, может сам сформировать URL и зайти в режиме администрирования. Кроме того - нет возможности отличить одного пользователя от другого.
Скрипт авторизации может каким-нибудь образом передать имя пользователя другим скриптам. Распространено во многих www-чатах - для того, чтобы отличить, где чье сообщение идет, рядом с формой типа text для ввода сообщения, пристраивается форма типа hidden, где указывается имя пользователя. Тоже ненадежно, потому что хакер может скачать документ с формой к себе на диск и поменять значение формы hidden. Некоторую пользу здесь может принести вышеупомянутая проверка HTTP_REFERER - но, как я уже говорил, никаких гарантий она не дает.
Определение пользователя по IP-адресу. В этом случае, после прохождения авторизации, где-нибудь в локальной базе данных (sql, dbm, да хоть в txt-файле) сохраняется текущий IP пользователя, а все скрипты защищенной части смотрят в переменную REMOTE_ADDR и проверяют, есть ли такой адрес в базе. Если есть - значит, авторизация была, если нет - “hacker? he-he…”
Это более надежный способ - не пройти авторизацию и получить доступ удастся лишь в том случае, если с того же IP сидит другой пользователь, успешно авторизовавшийся. Однако, учитывая распространенность прокси-серверов и IP-Masquerad’инга - это вполне реально.
Единственным, известным мне простым и достаточно надежным способом верификации личности пользователя является авторизация при помощи random uid. Рассмотрим ее более подробно.
После авторизации пользователя скрипт, проведший авторизацию, генерирует достаточно длинное случайное число:
mt_srand((double)microtime()*1000000);
$uid=mt_rand(1,1000000);
Это число он:
заносит в локальный список авторизовавшихся пользователей;
Выдает пользователю.
Пользователь при каждом запросе, помимо другой информации (сообщение в чате, или список сообщений в гостевой книге), отправляет серверу свой uid. При этом в документе с формами ввода будет присутствовать, наряду с другими формами, тег вида:
<input type=hidden name=uid value=1234567890>
Форма uid невидима для пользователя, но она передается скрипту защищенной части приложения. Тот сличает переданный ему uid с uid’ом, хранящимся в локальной базе и либо выполняет свою функцию, либо… “hacker? he-he…”.
Единственное, что необходимо сделать при такой организации - периодически чистить локальный список uid’ов и/или сделать для пользователя кнопку “выход”, при нажатии на которую локальный uid пользователя сотрется из базы на сервере - сессия закрыта.
Некоторые программисты используют в качестве uid не “одноразовое” динамически генерирующееся число, а пароль пользователя. Это допустимо, но это является “дурным тоном”, поскольку пароль пользователя обычно не меняется от сессии к сессии, а значит - хакер сможет сам открывать сессии. Та же самая модель может быть использована везде, где требуется идентификация пользователя - в чатах, веб-конференциях, электронных магазинах.
В заключение стоит упомянуть и о такой полезной вещи, как ведение логов. Если в каждую из описанных процедур встроить возможность занесения события в лог-файл с указанием IP-адреса потенциального злоумышленника - то в случае реальной атаки вычислить хакера будет гораздо проще, поскольку хакеры обычно пробуют последовательно усложняющиеся атаки. Для определения IP-адреса желательно использовать не только стандартную переменную REMOTE_ADDR, но и менее известную HTTP_X_FORWARDED_FOR, которая позволяет определить IP пользователя, находящегося за прокси-сервером. Естественно - если прокси это позволяет.
При ведении лог-файлов, необходимо помнить, что доступ к ним должен быть только у Вас. Лучше всего, если они будут расположены за пределами дерева каталогов, доступного через WWW. Если нет такой возможности - создайте отдельный каталог для лог-файлов и закройте туда доступ при помощи .htaccess (Deny from all).
Я буду очень признателен, если кто-нибудь из программистов поделится своими не описанными здесь методами обеспечения безопасности при разработке приложений для Web.
Автор: Илья Басалаев
Тсточник http://www.codenet.ru
Tags:
php,
SQL,
безопасность
Март 16, 2008
Предисловие
Ошибки - это бич любой программы. Чем больше проект, тем труднее исправлять и находить ошибки. Но наиболее важным в процессе работы с программой является квалификация программиста и его желание написать правильный и аккуратный код, содержащий минимальное количество ошибок.
В этой статье я постараюсь собрать техники и приемы, позволяющие минимизировать количество ошибок в программе, написанной на PHP. Но некоторые из представленных методов могут пригодится если вы пишите на любом языке программирования.
Знание - половина успеха
Узнаем, о чем сообщает PHP
В любом языке существует множество потенциально опасных ситуаций, которые чреваты неявными ошибками. При разборе транслятором исходного кода программы он может сообщать разработчику об этих ситуациях. Для этого надо лишь включить соответствующую опцию, которая очень часто по умолчанию выключена по некоторым соображениям.
В PHP контроль вывода сообщений транслятора определяется функцией error_reporting и значением директивы error_reporting в php.ini. Рекомендуемое её значение E_ALL - т.е. выводить сообщения о всех потенциально опасных ситуациях. К ним в PHP относятся, например, использование неинициализированной переменной, обращение к несуществующему элементу массива и т.д.
Для включения максимально подробного вывода сообщений транслятора поставьте в начале программы вызов функции error_reporting:
// Для PHP4
error_reporting(E_ALL);
или поставьте значение error_reporting = E_ALL в php.ini.
С более подробном описании возможных уровней reporting можно знакомится в PHP документации - Error Handling and Logging Functions.
Для PHP5 введен уровень E_STRICT, который включает вывод сообщений о использовании в коде устаревших методов программирования (например, используется var для описания внутренних переменных класса). Он не входит в E_ALL, поэтому для PHP5 рекомендуемый уровень сообщений E_ALL | E_STRICT (т.е. E_ALL и E_STRICT). Соответственно, для задания вывода всех сообщений от транслятора надо вызвать error_reporting с таким параметром:
// Для PHP5
error_reporting(E_ALL | E_STRICT);
Если ни о чем не сообщает
Если Вы установили вывод ошибок и ошибки по не выводятся, то возможно вывод ошибок в script output отключен. Проверьте значение опции ini файла display_errors (она включает вывод ошибок непосрественно в script output) и, если она выключена, включите её.
// проверяет значение опции display_errors
if (ini_get(’display_errors’) != 1) {
// включает вывод ошибок вместе с результатом работы скрипта
ini_set(’display_errors’, 1);
}
Если вдруг сообщит
Крайне редко удается протестировать программу полностью до выпуска и в то-же время лучше не показывать пользователю сообщения об ошибках ибо его реакция на них непредсказуема. Лучше перенаправлять ошибки транслятора, которые произошли непосредственно во время работы программы, в log файл ошибок. Включить это перенаправление можно опцией log_errors в файле php.ini.
Полезно также поставить свой обработчик ошибок, если Вы хотите не только заносить ошибки в Log файл но и добавить некоторую дополнительную логику их обработки. Например, отправить письмо при сообщении транслятора или вывести некоторое специальное сообщение для пользователя. Подробнее об этом написано в статье Ловля ошибок в PHP, которую написал Антон Довгаль.
Сравниваем константу с переменной, а не наоборот
Сколько раз Вам приходилось выяснять, что ошибка в программе связанна с использованием оператора “=” вместо “==”? Что бы приходилось реже, используйте сравнения вида
if (10 == $i) {
// что-то делаем
}
В случае использования “=” вместо “==” транслятор выдаст ошибку “Parse error: parse error in … on line …”. Таким образом ошибка обнаруживается значительно быстрее.
Не используем значение дважды
Конечно, это преувеличение. Но если в программе возникает необходимость использовать значение несколько раз, можно порекомендовать объявить константу и использовать её вместо значения.
Для PHP4 существует единственный способ объявить константу - использовать функцию define.
Например:
define (’BEFORE_RENDER’, ‘beforeRender’);
Констант в классах объявлять нельзя.
Расширение PHP 5 для определения констант сходно с тем, которое было осуществлено при расширении от C до C++ - используется ключевое слово const. Но константы таким образом можно создавать только внутри классов.
Например:
class ControlEvents {
const BEFORE_RENDER = ‘beforeRender’;
}
print ControlEvents::BEFORE_RENDER;
Но для обращения к такой константе необходимо знать имя класса.
Константы могут быть также добавлены непосредственно в класс. Но PHP не поддерживает такой метод. Поэтому придется объявить их как обычные переменные:
class Control {
var $BEFORE_RENDER = ‘beforeRender’;
function render() {
$eventFunction = $this->BEFORE_RENDER;
$this->$eventFunction();
}
}
Проверка параметров функции
В PHP параметром в функцию можно передать любую переменную. Но вот алгоритму функции может быть вовсе не все равно, что за переменную ему передали. Поэтому в начале функции полезно проверять её входные параметры на необходимый тип и диапазон значений.
Для проверки типа используются следующие функции:
gettype(Mixed $var) - возвращает тип переменной.
Наиболее часто используемые типы: “boolean”, “integer”, “double”, “string”, “array”, “object”, “resource”, “NULL”.
Функции проверки на тип: is_bool(Mixed $var), is_integer(Mixed $var), is_double(Mixed $var), is_string(Mixed $var), is_array(Mixed $var), is_object(Mixed $var), is_resource(Mixed $var) - возвращают true или false.
Для определения класса объекта используются функции:
get_class(Object $obj) - возвращает имя класса, экземпляром которого является obj.
is_a(Object $obj, String $class) - проверяет, является ли obj экземпляром сласса class или класса, унаследованного от class.
Для PHP4 не существует автоматического способа проверки параметров функции. Все необходимые проверки необходимо делать самостоятельно.
Код функции, осуществляющей проверку аргументов, может быть примерно такой:
/**
* Функция showControl принимает один параметр $control,
* этот параметр должен являться классом и являться
* экземпляром класса HTMLControl либо классом,
* унаследованным от HTMLControl.
*/
function showControl(&$control) {
is_a($control, ‘HTMLControl’) or $control == null or exit(’Type missmatch.’);
…
}
Достоинство этого метода состоит в том, что можно управлять сообщениями об ошибках и использовать собственный обработчик ошибок. Например, Вы можете использовать следующие функции для проверки параметров:
function checkParameter(&$var, $class) {
if (!is_a($var, $class) && $var != null)
SFExit(’Type missmatch.’);
}
function SFExit(&$message) {
print $message . ‘<br>’;
$backtrace = debug_backtrace();
for($i = 0; $i < count($backtrace); $i++) {
print $i . ‘: ‘ . $backtrace[$i][’file’] . ‘(’ .
$backtrace[$i][’line’] . ‘)<br>’;
}
exit();
}
Примечание: Функция debug_backtrace введена только в PHP 4.3.0.
Пример их применения:
function showControl(&$control) {
checkParameter($control, ‘HTMLControl’);
…
}
Для PHP5 некоторые проверки типов параметров можно задать непосредственно в описании функции. Предыдущий пример на PHP5 будет выглядеть следующим образом:
function showControl(HTMLControl $control) {
…
}
Asserts
Во время создания и отладки программы можно использовать встроенный механизм добавления проверок в код программы. Он называется asserts (или assert-проверки). Идея его состоит в том, что в код программы добавляются специальные проверочные конструкции, которые можно отключить для production сайта, но в любой момент включить при разработке.
Следующие фрагменты кода примерно аналогичны:
/* Использование Asserts */
assert_options (ASSERT_ACTIVE, 1);
function showControl(&$control) {
assert(’is_a($var, \’HTMLControl\’) || $var == null’);
…
}
/* Использование if конструкций */
define(’ASSERT_ACTIVE’, 1);
function showControl(&$control) {
if (ASSERT_ACTIVE && !(is_a($var, ‘HTMLControl’) || $var == null’))
trigger_error(’Assertion failed’, E_USER_ERROR);
…
}
С помощью таких проверок также можно проверять параметры функций, возвращаемые функциями значения и т.д. Нужно лишь учесть, что assert-проверки не должны быть включены в реально действующем сайте - если программа нормально работает и проходит все проверки, то их можно отключить.
Проверять значения параметров скрипта $_REQUEST, $_GET, $_POST, $_COOKIES
PHP скрипт можно рассматривать как большую функцию, которая вызывается с неопределенным списком string параметров. Если предполагается, что некоторые параметры будут использоваться в некоторых вычислениях, или отправляться в базу данных, то их обязательно надо преобразовывать к требуемому типу и использовать только после явного приведения!
Все массивы REQUEST являются является обычными массивами, поэтому значения в них могут быть переопределены непосредственно.
Например:
if (isset($_GET[’id’]))
$_GET[’id’] = (int)$_GET[’id’];
else
$_GET[’id’] = null;
Разделяй и властвуй
Известный со времен древнего Рима принцип “Разделяй и властвуй” вполне может пригодится при разработке программ на любом языке программирования. В том числе и на PHP. Для реализации этого принципа разделяйте программу на логические блоки. Для этого можно воспользоваться следующими методами:
Использование функций.
Выносите структурные части алгоритма в функции. Проверяйте каждую часть отдельно и затем работу всего алгоритма в целом.
Использование классов.
Организуйте программный код в виде объектов, взаимодействующих друг с другом. Выделяйте сущности и оформляйте их в виде объектов. Внимательно рассматривайте, как они взаимодействуют друг с другом. Используйте, где это разумно, лучшие шаблоны проектирования (design patterns).
Разделяйте логику и HTML.
Для этого существует множество способов: темплейтные библиотеки, XML, XML/XSL. Подберите для себя наилучший и используйте.
Разделяйте логику самого приложения при помощи enterprise design patterns.
Используйте разделение приложения на уровни (layering) и другие технологии, позволяющие структурно разделить проект на крупные блоки.
Заключение
Возможно, кому-то материал статьи покажется сбором прописных истин. Но я думаю, что большинству он все-таки пригодится, а для начинающих программистов последний раздел “Разделяй и властвуй” может оказаться особенно полезным, поскольку задает направление изучения программирования.
Если у Вас есть комментарии или собственные приемы работы, которые не упомянуты в этой статье, я буду рад услышать и обсудить их с Вами.
Также хочу выразить признательность участникам клуба phpclub.ru за помощь в написании статьи.
Источник http://www.codenet.ru
Tags:
error,
php,
PHP5,
xml
Март 15, 2008
Пароль на страницу. Часть 1. Скорее теоретическая.
я решил описать способы закрыть паролем часть сайта. Тема, на самом деле, большая, поэтому на первый раз ограничусь авторизацией php+mysql.
Самый первый вопрос, который обычно встаёт — как закрыть директорию со скрпитами администрирования паролем. При этом не нужно никаких изысков — один или несколько администраторов имеют одни и те же права, а персоналии меняются редко. Проще всего в данной ситуации использовать стандартную серверную авторизацию — положить файлы .htaccess и .htpasswd и прописать в них нужные параметры. Про это уже написано много, поэтому я ничего особо нового не скажу, лучше посмотрите сюда.
Добавлю две вещи. Первое — это куда класть файл .htpasswd. Экспериментальным путем я выяснил, что если, например, путь к документу с сообщением об ошибке (ErrorDocument) пишется относительно системной переменной DocumentRoot. Но путь к файлу с паролями (UserFile) пишется относительно ServerRoot. Насколько я понял, выше ServerRoot положить .htpasswd нельзя — “../” не воспринимается. Всё это сделано для того, чтобы можно было поместить файл с паролями, например, одним уровнем выше корневой директории сайта, чтобы из сети доступа к файлу не было вообще.
Второе — это то, что скрипт может узнать, кто его открывает и пароль: переменные $PHP_AUTH_USER и $PHP_AUTH_PW.
Главный недостаток этого способа — сервер не может блокировать подбор пароля (это после нескольких неудачных попыток входа пользователю предлагается подождать часок-другой, а в течение этого времени обращения с его IP-адреса игнорируются). Это написано в официальной документации по Апачу.
Ещё один недостаток — необходимость переписывать файлы с паролями при удалении пользователя или введении нового. Но если это происходит нечасто, этого способа вполне достаточно, к тому же не придётся забивать голову написанием механизма авторизации.
Автоматизация авторизации
Это нужно не только для упрощения работы с большим количеством пользователей и их большой “текучкой”. Если нужно держать дополнительную информацию о пользователях, либо необходимо гибкое разграничение прав, лучше перенести авторизацию в базу.
Каждая страница закрытой территории подключает файл с вот таким кодом:
$result = mysql_query(”
SELECT * FROM person WHERE
login=’”. preg_replace(”/[^\\w_-]/”,”",$PHP_AUTH_USER). “‘
AND pass=’”. md5($PHP_AUTH_PW). “‘”);
if (@mysql_num_rows($result)!=1) {
header(”WWW-Authenticate: Basic realm=\”User area\”");
header(”HTTP/1.0 401 Unauthorized”);
print(”Чтобы войти в пользовательскую часть сайта, надо ввести имя и пароль.”);
exit();
};
$user_row = mysql_fetch_array($result);
В первой строке из логина удаляются все символы кроме букв, цифр, тире и символа подчеркивания. Затем проверяется количество полученных строк, и только если это одна строка, дается доступ. В остальных случаях пользователь увидит в броузере окно, предлагающее ввести логин и пароль. Если же пользователь вошел успешно, в массиве $user_row мы имеем всю информацию о нем.
Конечно же, пример, который я привёл, имеет ряд существенных недостатков. Не переписывайте его один-в-один, чтобы потом не пасть жертвой попыток подбора пароля, потому что
1. защиты от подбора здесь нет
2. если таблица пользователей большая, при подборе пароля злоумышленник, скорее всего, “завалит” базу
И последний на сегодня способ — хранение зашифрованных данных в куках.
Есть скрипт для входа, остальные подключают код, позволяющий только продолжить действия в закрытой области — если куки истекут, или он выйдет оттуда, придётся возвращаться на страницу для входа.
Входной скрипт проверяет логин и пароль и выдает две куки. В первой — логин, чтобы сразу опознать пользователя (в базе поле логина, естественно, уникальное или даже ключевое). Во второй куке — хэш от времени входа и пароля (для полноты конспирации я добавляю к этим строкам букву “Ы” — тогда хэш подобрать почти невозможно :).
Все остальные программы подключают код, который делает следующее. Делает запрос в базу — выбирает строку с полученным логином. Из этой строки берет поле “log_time” и пароль и делает из них, как и описано выше, хэш. Сравнивает его с тем, что получил, и если они совпадают, выдает новую куку хэша, опять же, от пароля, времени и буквы “Ы” и делает запрос в базу данных “UPDATE user SET log_time=’…’ WHERE login=’$cookie_login’”.
if (isset($HTTP_COOKIE_VARS[$cookie_login]) && isset($HTTP_COOKIE_VARS[$cookie_code])) {
$login = $HTTP_COOKIE_VARS[$cookie_login];
$code = $HTTP_COOKIE_VARS[$cookie_code];
$result = mysql_query(”SELECT date_format(log_date,’%Y%m%d%H%i%s’) as log_date1,pass,uid
FROM user WHERE email=’$login’ AND log_date>’DATE_SUB(NOW(),INTERVAL 15 MINUTE)’”);
if (!mysql_error() && @mysql_num_rows($result)==1) {
$log_time0 = time();
$log_time1 = date(”YmdHis”, $log_time0);
$log_time2 = date(”Y-m-d H:i:s”, $log_time0);
$current_user = mysql_fetch_array($result);
if (md5($current_user[”pass”].$current_user[”log_date1″].$md5letter) == $code) {
mysql_query(”UPDATE user SET log_date=’$log_time2′ WHERE uid=”.$current_user[”uid”]);
setcookie($cookie_code, md5($current_user[”pass”].$log_time1.$md5letter),
time()+900, $site_path);
$auth = true;
}
else
unset($current_user);
};
};
Опять же, здесь нет никакой защиты от подбора и атаки на сервер (кстати, здесь можно вместо буквы “Ы” писать IP-адрес пользователя — чтобы, например, соседу по офису нельзя было взять файл с кукой и зайти со своего компьютера).
Пароль на страницу. Часть 2. Блокировка подбора
Когда я выложил этот выпуск в прошлый раз, меня запинали на месте, мол такой блокировкой можно и сервер “пустить под откос”.
Но сначала о блокировке подбора. Банальности, но всё-таки. Пароль длинной десять символов из букв латиницы и цифр - это очень много вариантов. Если подбирать пароль по 1 000 000 вариантов в секунду, понадобится несколько тысяч лет. Но поскольку такую абракадабру запомнить сложно, мы чаще делаем пароль из осмысленных слов. Несколько лет назад оказалось, что большинство паролей можно подобрать при помощи словаря из 10 000 слов. В своё время в сети появился червь (вирус такой), который лазил по юниксовым серверам, используя их дырки в защите, и подбирал пароли привелигированых пользователей при помощи… системного орфографического словаря Юникса. Ничего таскать не надо было!
Каждый пользователь, пока он не ввёл правильный логин и пароль, считается злобным хакером. С чем же мы имеем дело, когда пользователь вводит что-либо неправильно?
забывчивость (на это на приличных сайтах есть формочка “забыл пароль”, чтобы отправить на введёный в системных настройках email этот самый пароль)
баловство (”ибо нефиг”)
подбор пароля по словарю (вероятность удачного подбора велика, поэтому закрывать надо, тем более, если сайт коммерческого характера)
DoS-атака (чтобы не перегрузить сервер, надо минимизировать действия, которые будет выполнять скрипт в таком случае)
Я долго думал, как можно вызвать перегрузку на сервере, если механизм защиты стоит на файлах. Оказалось, несложно (сколько это будет стоить - другой вопрос). Итак, допустим, сервер не выдержит, если скрипт будет пытаться 1000 раз в секунду открывать файлы на запись и писать в них данные. Поскольку после 5 неудачных попыток войти в систему пользователь будет сразу получать отказ в доступе (без какой-либо записи данных в файл), надо найти 200 уникальных IP, с которых по пять раз и обратиться. Это возможно. Вешаем в баннерокрутилке html-баннер с пятью тегами:
<img src=”http://user:password@www.host.ru/secret/absent.gif” mce_src=”http://user:password@www.host.ru/secret/absent.gif” width=1 height=1>
Пользователь моментально делает пять обращений сервер пять раз пишет в файл (кстати, в некоторых броузерах, возможно, выскочит окно для ввода логина и пароля). Можно сделать html-страницу с пятью такими картинками, а саму страницу вставить через iframe на посещаемый сайт (через iframe - чтобы по полю referer не нашли. Вряд ли служба поддержки халявного хостинга будет заниматься такими вещами как копание в лог-файлах в поисках рефереров). Те примеры, которые я привёл, разумеется, натянуты, но сам факт того, что можно воспользоваться таким недостатком системы, доказан. Кстати, нечто подобное уже было.
Но всё-таки приведу этот способ - зря писал, что ли? Его, кстати, можно без особого страха применять для ограниченного количества адресов (например, для локальной сети фирмы), положив в директорию файл .htaccess такого содержания:
order deny,allow
deny from all
allow from xxx.xxx.xxx
А вот код программы:
$errors = 0;
$fn = “ignore/”. preg_replace(”[^\d\.]”, “”, $REMOTE_ADDR. “.”. $HTTP_FORWARDED_FOR);
if (is_file($fn)) {
if (filectime($fn) < time()-3600)
unlink($fn);
else
$errors = fread(fopen($fn, “r”), 2);
};
if ($errors>5) {
print (”Доступ закрыт. Зайдите через час.”);
exit();
};
// здесь происходит установка связи с сервером БД. чтобы не трогать зря, если пользователя сразу же “отлупили”.
$result = mysql_query(”SELECT * FROM user WHERE
login=’”. preg_replace(”/[^\w_\-]/”, “”, $PHP_AUTH_USER). “‘ AND
pass=’”. md5($PHP_AUTH_PW). “‘”);
if (@mysql_num_rows($result)!=1) {
header(”WWW-Authenticate: Basic realm=\”secret area\”");
header(”HTTP/1.0 401 Unauthorized”);
print (”Authorization required”);
fwrite(fopen($fn, “w”), ++$errors);
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);
Впрочем, грех работать с файлами, если есть база. Шутка. Для непрошедших авторизаций создаём таблицу:
CREATE TABLE unauth (username VARCHAR(64) NOT NULL, pass VARCHAR(64) NOT NULL, ip VARCHAR(255), logintime TIMESTAMP)
И вместо обращения к файлам работаем с базой.
$errors = @mysql_result(mysql_query(”SELECT count(username) as falses FROM unauth WHERE
logintime>DATE_SUB(NOW(),INTERVAL 1 HOUR) AND ip=’$REMOTE_ADDR’”),0);
if (mysql_error())
die(mysql_error());
if ($errors>5) {
print (”Доступ закрыт. Зайдите через час.”);
exit();
};
$result = mysql_query(”SELECT * FROM user WHERE
login=’”. preg_replace(”/[^\w_\-]/”, “”, $PHP_AUTH_USER). “‘ AND
pass=’”. md5($PHP_AUTH_PW). “‘”);
if (@mysql_num_rows($result)!=1) {
header(”WWW-Authenticate: Basic realm=\”secret area\”");
header(”HTTP/1.0 401 Unauthorized”);
print (”Authorization required”);
mysql_query(”INSERT INTO unauth (username, pass, ip) VALUES
(’$PHP_AUTH_USER’, ‘$PHP_AUTH_PW’, ‘$REMOTE_ADDR $HTTP_X_FORWARDED_FOR’)”);
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);
Хранить ли старые записи для статистики или нет - дело хозяйское. Если что, их можно удалять, выполняя перед авторизацией запрос:
DELETE FROM unauth WHERE logintime<DATE_SUB(NOW(),INTERVAL 1 HOUR)
Такой механизм при больших нагрузках будет работать быстрее и надёжнее, чем файлы - в базе часто используемые данные буферизуются и обрабатываются непосредственно в оперативной памяти.
Пароль на страницу. Часть 3. Пароль от базы
Была у меня в своё время проблема: надо закрыть администрационную часть сайта, но при этом я не могу положить файл .htpasswd выше корневой директории сайта. Врождённая подозрительность не позволяла положить файл с паролем и отдельную директорию и заблокировать доступ к ней по http. Решил попробовать сделать защиту как в phpMyAdmin: у пользователя спрашиваются логин и пароль, с которыми скрипт соединяется с базой. В своём анализаторе логов я сделал именно так. Удобство метода в том, что файл можно складывать куда угодно — никаких кук, никаких директив сервера для директории. Заодно, если поменяется пароль в базе данных, не надо ничего исправлять в скрипте.
Распишу метод на примере MySQL. Пишем функцию, например, mysql_die:
function mysql_die() {
header(”HTTP/1.0 401 Unauthorized”);
header(”WWW-authenticate: basic realm=\”Statistics\”");
print (”Access denied. User name and password required.”);
exit();
}
В начале программы указываются хост сервера БД и, если надо, имя базы:
$db_host = “localhost”;
$db_name = “somedatabase”;
А для соединения с базой берутся переменные сервера: $PHP_AUTH_USER и $PHP_AUTH_PW.
$db_connect = @mysql_connect($db_host, $PHP_AUH_USER, $PHP_AUTH_PW)
or mysql_die();
И всё. Теперь о недостатках. Разумеется, с такой защитой можно пробовать подбирать пароль (в принципе, можно приделать блокировку, но тогда потерятеся вся красота метода). Пароль, как и в случае защитой средствами сервера, пересылается в открытом виде. Но для простых задач такое вполне сгодится.
На днях в форуме разгорелась дискуссия, что лучше где применять — защиту через 401-й код, свои куки или сессии. Я не стал ввязываться, чтобы не растерять красноречия, но в ближайших выпусках опишу в общих чертах последние два метода (а пока рекомендую читать статью про сессии).
Пароль на страницу. Часть 4. Печенюшки
Способ этот применим там, где, во-первых, пользователей много, и их контингент постоянно меняется. Во-вторых, где нужно сделать удобный вход - чтобы можно было зайти в систему, введя логин и пароль в форме на странице.
Рисуем форму и делаем файл, который получает логин и пароль (защиту от подборки я уде описывал, допишите её сюда сами).
// обработка строки с логином
$login = str_repalce(”‘”, “”, $login);
$login_result = mysql_query(”SELECT id FROM user WHERE
login=’$login’ AND pass=’”. md5($pass). “‘”);
if (!mysql_error() && @mysql_num_rows($login_result)==1) {
/* выдача кук. Имена кук и путь лучше во избежание путаницы определять
в едином подключаемом файле. */
setcookie($COOKIE_LOGIN_NAME, $login, time()+3600, $COOKIE_PATH);
setcookie($COOKIE_PASSW_NAME, $pass, time()+3600, $COOKIE_PATH);
/* Сразу же после входа пользователя перенаправляют на закрытый паролем адрес. */
header(”Location: /somepath/”);
exit;
}
elseif (!mysql_error()) {
/* вывод сообщения об ошибке и формы для повторного ввода */
print (”Неправильный логин или пароль.”);
}
else
print (mysql_error());
Все закрытые страницы вызывают файл, в котором проверяется правильность пароля, полученного из куки:
$login = str_repalce(”‘”, “”, $HTTP_COOKIE_VARS[$COOKIE_LOGIN_NAME]);
$login_result = mysql_query(”SELECT id FROM user WHERE
login=’$login’ AND pass=’”. md5($HTTP_COOKIE_VARS[$COOKIE_PASSW_NAME]). “‘”);
if (!mysql_error() && @mysql_num_rows($login_result)!=1) {
/* Если такой строки в таблице нет, пользователь перенаправляется на страицу входа. */
header(”Location: /login.php”);
exit;
}
else
print (mysql_error());
Имена кук будут использоваться в нескольких местах, поэтому лучше заранее поместить их в одном месте (например, объявив константы), чтобы потом не исправлять по нескольку раз.
Как видите, пароль будет бегать по каналу и лежать в файле с куками в открытом незакодированном виде. Это очень небезопасно. В отсутствие хозяина можно подойти к компьютеру, заглянуть в файл, где броузер держит куки, и записать пароль на бумажку (а если в локальной сети всё общее, то и подходить не надо, и стащить пароль можно прямо при хозяине).
Чтобы этого не произошло, пароль нужно кодировать. Как приемлемый вариант, хэш md5. Тут уже нельзя увидеть пароль и зайти в систему, записав его на бумажку или copy-paste-нув. Кстати, именно так можно залазить под паролем и без ведома друга в web-интерфейсы, строящие авторизацию на сессиях. Поэтому последнее, что можно сделать в этом направлении - это менять куку при каждой загрузке страницы.
Сам когда-то делал такую схему: в таблице пользователей есть колонка с датой последнего обращения. Эту дату последнего обращения и пароль, закодированные через md5, пользователь получает при каждом обращении. Система берёт куку с логином, вытаскивает из базы эту строку, генерирует хэш от полей last_log и passwd и сравнивает его с полученным. Если они совпадают, значит посетителя можно впускать. Для пущей безопасности можно добавить проверку на истечение куки - кука должна истечь после получаса неактивности, и, соответсвенно, в базе дата последнего лога должна быть менее чем полчаса назад.
$login = str_repalce(”‘”, “”, $HTTP_COOKIE_VARS[$COOKIE_LOGIN_NAME]);
$login_result = mysql_query(”SELECT * FROM user WHERE
login=’$login’ AND last_log>DATE_SUB(NOW(), INTERVAL 30 MINUTE)”);
if (!mysql_error() && @mysql_num_rows($login_result)==1) {
/* Получаем строку таблицы и формируем хэш от нужных полей. */
$current_user = mysql_fetch_array($login_result);
$hash_to_check = md5($current_user[”passwd”]. ” Ы - чтоб никто не догадался “.
$current_user[log_time]);
if ($hash_to_check == $HTTP_COOKIE_VARS[$COOKIE_HASH_NAME]) {
$current_time = time();
/* Обновление поля последнего входа и выдача новой куки. */
mysql_query(”UPDATE user SET last_log=’”. date(”Y-m-d H:i:s”, $current_time). “‘
WHERE login=’$login’”);
setcookie($COOKIE_HASH_NAME, md5(date(”Y-m-d H:i:s”, $current_time).
” Ы - чтоб никто не догадался “.
$current_user[”passwd”]), $current_time + 1800, $COOKIE_PATH);
}
else {
/* В случае несовпадения хэша пользователь перенаправляется на страицу входа в систему. */
header (”Location: /login.php”);
exit;
};
}
elseif (!mysql_error() && @mysql_num_rows($log_result)!=1) {
header(”Location: /login.php”);
exit;
}
else
print (mysql_error());
Разумеется, ” Ы - чтоб никто не догадался ” лучше тоже выделить в отдельную переменную, а лучше использовать вместо этой строки ip-адрес посетителя (или, для обрывающегося диалапа, первые два/три числа ip-адреса).
Кстати, насчёт IP-адреса. Его лучше проверять, но не весь адрес, а только первые два (для ip, начинающихся на число меньше 127) или три (соответственно, больше 127) числа адреса. Это спасёт пользователей плохого и обрывающегося диалапа от необходимости заново авторизовыватсья после обрыва связи, и в то же время, не даст зайти взломщику, укравшему куку. Конечно же, он не сможет перезвонить и зайти через другого провайдера - адрес пула не тот, но это не наши проблемы (”в такую погоду свои дома сидят”). Как не наша проблема и воровство паролей внутри фирмы. Мы защитили от любопытных товарищей и неграмотных взломщиков, а против троянов и снифферов, которые можно поставить жертве, ничего сделать не можем.
На этом “навороты” закончились. Надёжнее защиту уже не сделать. Никто не будет лазить в файл кук за хэшем и подбирать его. Проще будет поместить между пользователем и веб-интерфейсом сниффер и при помощи него найти пароль. Можно поместить трояна, который будет запоминать всё, что пользователь ввёл на клавиатуре, но это уже не наши проблемы. Чтобы защититься от прослушивания канала, надо использовать соединения типа SSL или шифрование данных.
Пароль на страницу. Часть 5. Сессии
Зачем я писал заметку про куки? “Не понимаю, зачем писать про куки, когда в php есть сессии?!” Затем, чтобы у читателей не образовывалась перед глазами плоская картина. Не везде ещё стоит php 4-й версии, а в третьей они не поддерживаются. Более того, не везде сессии так необходимы — за редким исключением алгоритм авторизации проверяет правильность логина/пароля и правильность данных сессии, а затем либо отфутболивает клиента на страницу входа, либо берёт массив (или объект) с данными о пользователе.
Случаев, когда работа сессиями необходима, не так уж и часты. Например, в своей игре “Монополист” я сразу стал использовать сессии, потому что пользователь может играть в нескольких играх и одна и та же страница в одном и том же сеансе работы может содержать разные данные. Там лучше данные для одной из игр, в которых пользователь участвует, хранить в сессии и сделать страницу для перехода между играми.
В общем, я не утверждаю, что сессиями пользоваться не нужно. Нужно, только всему своё место. К вопросу применимости трёх способов авторизации — через 401-й заголовок (”realm”), куки или сессии — я вернусь позже. Сейчас поговорю о сессиях.
Сессии в php — это на самом деле не метод авторизации (само понятие неправильное, но в форумах спрашивают именно “как авторизовывать пользователя через сессии?”). Встроенный в php механизм пользовательских сессий лишь идентифицирует этих пользователей, авторизовывать — опять же, работа вашего скрипта.
Много про механизм сессий рассказывать не буду — уже рассказано. В самом простом виде (вернее в самом dafault-ном) механизм этот работает так: система держит на сервере файл сессии, в котором содержатся её переменные. Пользователь при запуске сессии получает уникальный идентификатор (обычно через куку), и при обращении к другим страницам отправляет её. При запуске механизма сессий в вашем скрипте обработчик php проверяет, существует ли файл соответствующий пришедшему идентификатору сессии — если существует, то скрипт сможет прочесть данные из файла, если нет — будет запущена новая сессия и создан файл. Разумеется, имя данной переменной опеределено в установках php.
Теперь о том, какими функциями мы пользуемся.
session_start(). Запускает сам механизм сессий. От пользователя должна быть переменная и соответствующий ей файл. Если нет файла, он создаётся, и сессия запускается с нуля. Если нет ни файла, ни переменной, то генерируется переменная (например, посылается заголовок с кукой) и создаётся файл.
session_register(имя1, имя2, имя3…). Указание, какие переменные запомнить в файле по окончании работы скрипта. После того как пользователь перейдёт к другой странице, можно запустить механизм сессий, и после вызова данной функции переменные будут доступны.
session_destroy(). Удаляет файл данных сессии (при использовании кук надо удалять их вручную, выставив пустую куку: “setcookie(session_name())”).
session_end(). Если после авторизации данные о пользователе менять не надо, лучше сразу “выключить за собой свет” — закрыть файл и освободить доступ к нему.
session_set_cookie_params(жизнь, путь, домен). Установка параметров куки с идентификатором сессии (по умолчанию кука выставляется на корень сервера и на 0 секунд — до закрытия браузера).
Пока всё. Подробно про сессии будут отдельные выпуски. Пока опишу механизм авторизации и идентификации пользователя при помощи сессий.
Итак, имеем три файла — вход (login), проверка (auth) и выход (logout).
// вырезка всех нежелательных символов
$login = preg_replace(”/[^\w_\.\-]/”, “”, $HTTP_POST_VARS[”login”]);
$pass = trim($HTTP_POST_VARS[”pass”]);
// проверка переменных
if (strlen($login)==0 || strlen($pass)==0)
$error = “Введите логин и пароль”;
else {
// проверка логина и пароля
$user_result = mysql_query(”SELECT * FROM user WHERE
login=’$login’ AND pass=’”. md5($pass). “‘”);
/* если возникла ошибка в базе (например, пользователь всунул в сессию дли-и-инную
переменную, которую база переваривать не захотела) или получилась не одна строка,
отфутболиваем пользователя */
if (mysql_error())
die(mysql_error());
elseif (@mysql_num_rows($user_result) != 1)
$error = “Неверное имя пользователя или пароль.”;
// если всё нормально, выбираем данные, запускаем сессию
else {
$user = mysql_fetch_assoc($user_result);
session_set_cookie_params(1800, “/”);
session_start();
// запоминаем данные о пользователе
session_register(”user”);
// и дальше отправляем его куда-нибудь
if (isset($HTTP_POST_VARS[”return”]))
header(”Location: {$HTTP_POST_VARS[’return’]}”);
else
header(”Location: /”);
exit();
};
};
/* здесь пользователь уже не прошёл авторизацию, но может отправить куку из
закрытой сессии. очистим её. */
if (isset($HTTP_COOKIE_VARS[session_name()]))
setcookie(session_name());
// дальше рисуем форму, это неинтересно.
Данный скрипт является и обработчиком и формой для ввода данных. При получении логина и пароля он обрабатывает их и, если они правильные, прекращает работу, отправив пользователя на нужную страницу. Если данные неправильные или вообще отсутствуют, рисует форму.
/* убиваем переменную user, чтобы нельзя было, нарисовав форму, отправить данные
в post-запросе. */
unset($user);
// флаг “ошибка сессии” — если он включён, работа прекратится.
$session_error = false;
// если не существует куки с идентификатором сессии, поднять флаг
if (!isset($HTTP_COOKIE_VARS[session_name()]))
$session_error = true;
// если существует, запускаем механизм сессий и регистрируем переменную $user.
else {
session_start();
session_register(”user”);
/* если случайно в массиве нет логина и пароля, работа тоже прекращается (”ничего
не знаем, мы вам их давали”) */
if (!isset($user[”login”]) || !isset($user[”pass”]))
$session_error = true;
};
/* если пользователю до сих пор удалось геройски избежать ошибок, делается проверка
через базу так же, как и на входе. */
if (!$session_error) {
$check_result = mysql_query(”SELECT uid FROM user WHERE
login=’{$user[login]}’ AND pass=’{$user[pass]}’”);
if (mysql_error() || @mysql_num_rows($user_result) != 1)
$session_error = true;
};
// если была какая-то ошибка, то
if ($session_error) {
// уничтожаем данные сессии
session_destroy();
// уничтожаем куку, если она была
if (!isset($HTTP_COOKIE_VARS[session_name()]))
setcookie(session_name(),”",”/”);
/* отправляем пользователя на вход, с возможностью вернуться на запрошенный адрес */
header(”Location: /login.php?return=$REQUEST_URI”);
// прекращаем работу
exit();
};
mysql_free_result($check_result);
Пользователь проверен и в массиве $user — все данные о нём, можно, например, поприветствовать его по имени-отчеству:
<?
include(”auth.inc”);
?><html>
<head><title><? print (”Здравствуйте, {$user[fname]} {$user[sname]}!”); ?></title></head>
<body>
[skip]
И выход:
if(isset($HTTP_COOKIE_VARS[session_name()])) {
// запуск механизма сессий
session_start();
// удаление файла
session_destroy();
// удаление куки
setcookie(session_name());
};
// выход со страницы
header(”Location: /login.php”);
Пара замечаний: закрываемая паролем часть в данном примере - весь сервер (например, service.firm.ru), для закрытия директории нужно исправить пути. Вместо PHPSESSID используется session_name(), чтобы можно было свободно менять имя идентификатора. Кстати, на одном физическом сервере можно делать разные имена идентификаторов сессий - достаточно в нужную часть положить файл .htaccess со строкой php_value session.name “ABRACADABRA”
Оставить комментарий
Источник http://phpclub.ru
Tags:
AUTH,
htpasswd,
php,
авторизация,
куки,
логин,
подбор
Март 14, 2008
Многие обладатели домашних страничек рано или поздно начинают интересоваться устройством крупных новостных порталов и контент-проектов - не вручную же они ве эти страницы делают! А как тогда? Данная статья поможет начинающему веб-мастеру начать создание проектов с применением PHP-MySQL.
Один из самых часто задаваемых вопросов начинающих веб-мастеров: как начать работать с базами данных MySQL, используюя скрипты на PHP? Это неудивительно, потому что эра статических HTML-сайтов давно прошла (на “Народе” это любимое народом дело процветает
и теперь более-менее грамотные начинающие веб-мастера осознали удобство и комфорт создания и поддержания контент проектов на основе PHP-MySQL. Скептики и консерваторы будут утверждать, что работать с файлами проще, что легче наверстать страничку вручную, чем тратить время на отладку и написание скриптов. Не слушайте их - это абсурд! Как говорится “Лучше день потерять потом за 5 минут долететь!”. Ну допустим, на подготовку и создание динамической версии сайта на основе PHP-MySQL уходит раза в два-три больше времени, а то и больше, но зато поддержка проекта в будущем покажется вам приятным и простым занятием (конечно, настолько, насколько серьёзно вы к этому подойдёте). А почему загибаются казалось бы хорошие статические сайты? Причина всему - рутинные операци по обновлению сайта. Если на PHP-MySQL сайте есть возможность добавлять статьи через форму (копировать, вставить, отправить), то для добавления статьи на статический сайт нужно намного больше более сложных операций:
Сверстать новую страничку на основе существующего шаблона.
Проверить форматирование, ссылки, картинки, всё ли на месте.
Добавить ссылку на новую страницу где-то ещё (а то и на несколько!), например в раздел “Статьи”.
Проверить, как всё это работает целиком.
Соединиться с сервером FTP.
Закачать все обновлённые страницы на сервер.
Проверить, всё ли работает в онлайне.
Вот почему многие бросают любимое занятие. Ну есть конечно некоторые усердные личности, которые годами поддерживают такие сайты. А есть и умельцы, которые за пару месяцев разработают БД и PHP-движок и будут жить методом “копировать, вставить, отправить”!
Надеюсь, я привёл достаточные аргументы в пользу динамического сайта. И вот Вася Пупкин решил опробовать себя в этом нелёгком деле, но тут возникает несколько резонных вопросов:
Есть ли у меня PHP?
Есть ли у меня MySQL?
Есть ли у меня вообще сайт? (шутка
Итак, для создания динамического сайта нам понадобится хостинг с поддержкой PHP и MySQL. Ну здесь я вам не советчик - если вы создаёте серьёзный проект, то лучше немного заплатить, но получить всё и сразу - тогда вам сюда http://hcenter.info. Из бесплатных хостингов PHP и MySQL доступны кажется на Агаве, но я могу ошибаться - тогда поищите в Яндексе “бесплатный хостинг с поддержкой PHP MySQL” или что-то в этом роде.
Пропускаем момент регистрации на хостинге. Теперь вам должны выслать по email письмо с логинами и паролями. Внимательно прочитайте инструкции, посмотрите FAQ на сайте хостинга, если что-то непонятно.
Во-первых, вам необходимо создать новую базу данных на MySQL-сервере. Это очень легко делается при помощи инструментов типа phpMyAdmin или любого графического клиента MySQL - SQLyog, MySQL-Front. На большинстве хостингов это делается при помощи панели управления хостингом - тут я вам не помощник, смотрите сами. Выполните функцию создания новой базы, назвав её например “test”.
При помощи одной из указанных выше программ выполните представленный ниже SQL-дамп:
#
# Table structure for table `links`
#
# Creation: Aug 12, 2003 at 05:11 PM
# Last update: Aug 12, 2003 at 05:28 PM
#
CREATE TABLE `links` (
`id` int(10) unsigned NOT NULL auto_increment,
`url` varchar(100) NOT NULL default ”,
`description` varchar(100) NOT NULL default ”,
PRIMARY KEY (`id`)
) TYPE=MyISAM AUTO_INCREMENT=6 ;
#
# Dumping data for table `links`
#
INSERT INTO `links` VALUES (1,
‘http://www.programmingsite.co.uk’,
‘programming directory’);
INSERT INTO `links` VALUES (2,
‘http://www.beginnersphp.co.uk’,
‘PHP tutorials and code’);
INSERT INTO `links` VALUES (3,
‘http://asp.programmershelp.co.uk’,
‘ASP site with code and tutorials’);
INSERT INTO `links` VALUES (4,
‘http://javascript.programmershelp.co.uk’,
‘javascript site’);
INSERT INTO `links` VALUES (5,
‘http://software.programmingsite.co.uk’,
’software directory’);
Теперь вы можете написать и выполнить свои PHP-скрипты для операций с только что созданной базой данных. Следующий PHP-скрипт подключается с серверу MySQL, производит выборку данных и выводит содержимое таблицы “links” в виде обычной HTML-таблицы.
<?php
//соединение с базой данных при помощи функции mysql_connect()
//в аргументах функции укажите имя сервера, логин и пароль.
$db = mysql_connect(”сервер”,”логин”,”пароль”);
//функция mysql_select_db() выбирает текущую
//базу данных с именем “test”
mysql_select_db(”test” ,$db);
//функция mysql_query() выполняет запрос на выборку данных
//результирующий набор данных хранится в переменной $sql
$sql = mysql_query(”SELECT * FROM links” ,$db);
//после получения данных начнём формирование HTML-таблицы
echo (”<table border =’1′>”);
//выводим строку заголовков
echo (”<tr><td>Адрес</td><td>Описание</td></tr>”);
//функция mysql_fetch_row() извлекает одну строку из результата
//и сохраняет её в массиве $tablerows
while ($tablerows = mysql_fetch_row($sql))
{
//теперь в цикле для каждой полученной строки сделаем вывод
//$tablerows[1] соответствует полю “url”
//$tablerows[2] соответствует полю “description”
echo(”<tr><td><a href=’$tablerows[1]’>$tablerows[1]</a></td><td>$tablerows[2]</td></tr> “);
}
echo “</table>”;
//закрытие соединение (рекомендуется)
mysql_close($db);
?>
Сохраните скрипт в файл под именем, например test.php и закачайте на FTP-сервер вашего сайта. Скрипт будет доступен например так: http://vasya.server.ru/test.php (ну или смотря куда вы его закачали). Если всё сделано правильно, скрипт отработает и выведет на экран содержимое БД в виде обычной таблицы.
Буду искренне рад, если эта статья стала для вас отправной точкой для создания динамического сайта!
источник http://www.codenet.ru
Перевод: fox++
на основе статьи и скриптов: http://tutorials.programmingsite.co.uk.
Tags:
MySQL,
php
Март 13, 2008
Старые записи