From e2d67c4d89df7b485bbfc8cf74fdb4f550892e76 Mon Sep 17 00:00:00 2001 From: Victor Wagner Date: Thu, 20 Mar 2008 16:25:03 +0000 Subject: [PATCH] fixes --- forum/forum | 304 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 253 insertions(+), 51 deletions(-) diff --git a/forum/forum b/forum/forum index 33dc755..91961bf 100755 --- a/forum/forum +++ b/forum/forum @@ -1,5 +1,16 @@ #!/usr/bin/perl -T - +# +# Stil Life forum. Copyright (c) by Victor B. Wagner, 2008 +# This program distributed under GNU Affero General Public License v3 or +# above +# http://www.gnu.org/licenses/agpl.html +# +# Вкратце: Если вы используете этот скрипт на своем сайте, Вы обязаны +# сделать доступным его исходный текст. В частности, если Вы внесли +# какие-либо изменения, вы должны эти изменения опубликовать. +# +# Home site of this program http://vitus.wagner.pp.ru/stilllife +# use strict; use warnings; use CGI; @@ -29,7 +40,7 @@ my %actions = ( my $cgi = new CGI; - +print STDERR "--------------------\n"; my $forum=get_forum_config(); @@ -39,6 +50,8 @@ if ($cgi->request_method ne "POST") { # редирект от OpenId-сервера if ($cgi->param('openidvfy')) { openid_verify($cgi); + } elsif ($cgi->param("logout")) { + logout('logout',$cgi,$forum); } else { for my $param ($cgi->param) { # Среди параметров, указанных в URL ищем тот, который задает @@ -73,9 +86,35 @@ if ($cgi->request_method ne "POST") { $actions{$param}->($param,$cgi,$forum); exit; } - } + } + print STDERR "Получены параметры ",join(" ",$cgi->param),"\n"; show_error($forum,"Некорректный вызов скрипта. Отсутствует параметр действия"); } + +sub dir2url { + my ($cgi,$dir) = @_; + my $prefix=$cgi->url(-base=>1); + my $pos=rindex $ENV{'PATH_TRANSLATED'},$ENV{'PATH_INFO'}; + if ($pos <0 && $ENV{'PATH_INFO'}=~m!(/\~\w+)/!) { + $prefix .=$1; + $pos = + rindex($ENV{'PATH_TRANSLATED'},substr($ENV{'PATH_INFO'},length($1))); + } + if ($pos <0) { + show_error({},"Ошибка конфигурации форума. Не удается определить + алгоритм преобразования директори в URL\n". + "PATH_INFO=$ENV{PATH_INFO}\n". + "PATH_TRANSLATER=$ENV{'PATH_TRANSLATED'}"); + } + my $root = substr($ENV{'PATH_TRANSLATED'},0,$pos); + if (substr($dir,0,length($root)) ne $root) { + show_error({},"Ошибка конфигурации форума. Не удается преобразовать + имя директории $dir в url\n". + "PATH_INFO=$ENV{PATH_INFO}\n". + "PATH_TRANSLATER=$ENV{'PATH_TRANSLATED'}"); + } + return $prefix.substr($dir,length($root)); +} # # Поиск файла .forum вверх по дереву от $ENV{PATH_TRANSLATED} # Значение PATH_TRANSLATED считаем безопасным - наш web-сервер нам не @@ -86,7 +125,6 @@ if ($cgi->request_method ne "POST") { sub get_forum_config { my @path=split("/",$1) if $ENV{PATH_TRANSLATED}=~/^(\S+)$/; while (@path>1) { - pop @path; if (-r (my $config=join("/",@path,".forum")) ) { open F,"<",$config; my %config; @@ -96,6 +134,10 @@ sub get_forum_config { } close F; # + # Переменная forumtop - это URL того места, где находится + # файл .forum + + $config{"forumtop"} = dir2url($cgi,join("/",@path)); # Если в конфиге отсутствует переменная templates, но # рядом с конфигом присутствует директория templates, # то шаблоны там. @@ -104,36 +146,38 @@ sub get_forum_config { && -d (my $filename = join("/",@path,"templates"))) { $config{"templates"} = $filename; } + $config{"templatesurl"} = dir2url($cgi,$config{"templates"}) + unless exists $config{"templatesurl"}; # # То же самое - параметр userdir и директория users # if (! exists $config{"userdir"} && -d (my $filename = join("/",@path,"users"))) { $config{"userdir"} = $filename; - my - $taillen=length($ENV{PATH_TRANSLATED})-length(join("/",@path)); - $config{"userurl"} = - $cgi->url(-base=>1).substr($ENV{'PATH_INFO'},0, - length($ENV{'PATH_INFO'})-$taillen)."/users"; - } + } + $config{"userurl"} = dir2url($cgi,$config{"userdir"}); + # # Если нет ссылки в конфиге на файл паролей или он не # существует, выдаем ошибку. С офоромлением, так как шаблоны # у нас уже есть - if (! exists $config{"passwd"}) { - show_error(\%config,"В конфигурации форума не указан файл пользователей"); + if (!exists $config{"datadir"}) { + show_error(\%config,"В конфигурации форума не указана + директория данных "); exit; } - if (!exists $config{"session"}) { - show_error(\%config,"В конфигурации форума не указан файл сессий"); + if (!-d $config{"datadir"}) { + show_error(\%config,"В конфигурации форума указана несуществующая директория данных "); exit; } - $config{"authperiod"}="+1m" if (! exists $config{"authperiod"}); + $config{"authperiod"}="+1M" if (! exists $config{"authperiod"}); $config{"renewtime"} = "86000" if (!exists $config{"renewtime"}); + $config{"replies_per_page"} = 50 if (!exists $config{"replies_per_page"}); return \%config; } + pop @path; } # # Выводим ошибку 404 без осмысленного оформления, так как данных форума @@ -173,6 +217,7 @@ sub show_error { $cgi->escapeHTML($msg),"

", "

При обработке этой ошибки не обнаружен шаблон сообщения об ошибке

"; } + exit; } # # Вывод шаблона формы. В шаблоне должна присутстовать форма с @@ -188,6 +233,7 @@ sub show_template { exit; } my $tree = HTML::TreeBuilder->new_from_file($filename); + fix_forum_links($forum,$tree); # Находим форму с классом $form my $f = $tree->look_down("_tag","form", "name",$form); @@ -197,6 +243,7 @@ sub show_template { именем $form"); exit; } + $cgi->delete('password'); if (!$cgi->param("returnto")) { $cgi->param("returnto", $cgi->referer||$cgi->url(-absolute=>1,-path_info=>1)); @@ -216,32 +263,39 @@ sub show_template { } } if ($forum->{"authenticated"}) { + # Подставляем информацию о текущем пользователе если в шаблоне # это предусмотрено substitute_user_info($tree,$forum); + $cgi->param("user",$forum->{"authenticated"}{"user"}) if (!defined $cgi->param("user")) } my %substituted; - for my $element($f->find_by_tag_name("textarea","input","select")) { - my $name = $f->attr("name"); + ELEMENT: + for my $element ($f->find_by_tag_name("textarea","input","select")) { + my $name = $element->attr("name"); $substituted{$name} = 1; + #print STDERR "substituting form element name $name tag ",$element->tag, + # "value='",$cgi->param($name),"'\n"; if (defined $cgi->param($name)) { - if ($f->tag eq "input") { - if ($f->attr("type") eq "check") { - if (grep($f->attr("value") eq $_,$cgi->param($name))) { - $f->attr("checked",""); + if ($element->tag eq "input") { + next ELEMENT if grep ($element->attr("type") eq + $_,"button","submit","reset"); + if ($element->attr("type") eq "check") { + if (grep($element->attr("value") eq $_,$cgi->param($name))) { + $element->attr("checked",""); } else { - $f->attr("checked",undef); + $element->attr("checked",undef); } - } elsif ($f->attr("type") eq + } elsif ($element->attr("type") eq "radio") { - if ($f->attr("value") eq $cgi->param($name)) { - $f->attr("checked",""); + if ($element->attr("value") eq $cgi->param($name)) { + $element->attr("checked",""); } else { - $f->attr("checked",undef); + $element->attr("checked",undef); } } else { - $f->attr("value",$cgi->param($name)); + $element->attr("value",$cgi->param($name)); } } elsif ($f->tag eq "textarea") { $f->delete_content; @@ -260,7 +314,6 @@ sub show_template { } $f->attr("type","POST"); - $f->attr("action",$cgi->url(-full=>1,-path_info=>1,-query=>0)); for my $required ($form,"returnto") { if (!$substituted{$required}) { my $element = new HTML::Element('input', @@ -272,10 +325,48 @@ sub show_template { print - $cgi->header(type=>"text/html",charset=>"utf-8",($forum->{cookies}?(-cookie=>$forum->{cookies}):())), + $cgi->header(-type=>"text/html",-charset=>"utf-8",($forum->{cookies}?(-cookie=>$forum->{cookies}):())), $tree->as_HTML("<>&"); } - +# +# Поправляет ссылки на служебные файлы и скрипты форума +# +sub fix_forum_links { + my ($forum,$tree,$path_info) = @_; + $path_info=$ENV{'PATH_INFO'} if (!defined $path_info); + my $script_with_path = $ENV{SCRIPT_NAME}.$path_info; + ELEMENT: + for my $element ($tree->find_by_tag_name("form","img","link","script","a")) { + my $attr; + if ($element->tag eq "form") { + $attr = "action"; + } elsif ($element->tag eq "a"|| $element->tag eq "link") { + $attr = "href"; + } else { + $attr ="src"; + } + my $link = $element->attr($attr); + print STDERR "Fixing link $link\n"; + # Абсолютная ссылка - оставляем как есть. + next ELEMENT if (! defined $link || $link=~/^\w+:/); + # Ссылка от корня сайта. + if (substr($link,0,1) eq "/") { + # Если она не ведет на наш скрипт, не обрабатываем + next ELEMENT if substr($link,0,length($ENV{SCRIPT_NAME}) ne + $ENV{SCRIPT_NAME}) ; + # Иначе пишем туда слово forum вместо реального имени + # скрипта чтобы потом единообразно обработать + $link =~ s/^[^\?]+/forum/; + } + if (!($link =~ s!^templates/!$forum->{templatesurl}/!) && + !($link =~ s!^users/!$forum->{usersurl}/!) && + !($link =~ s!^forum\b!$script_with_path!)) { + $link = $forum->{"forumtop"}."/".$link + } + print STDERR "Fixed to $link\n"; + $element->attr($attr,$link); + } +} # # Подставляет в заданное поддерево информацию о пользователе # @@ -288,12 +379,20 @@ my %userinfo = %{$forum->{"authenticated"}}; # # Специально обрабатываем поля user (должна быть ссылка) и avatar # (должен быть img). - -my $userlink = $tree->look_down("_tag"=>"a","class"=>"author"); -if ($userlink) { - $userlink->attr(href=>$userinfo{"userpage"}); - $userlink->delete_content(); - $userlink->push_content($userinfo{"user"}); +my @userlink = $tree->look_down("_tag"=>"a","class"=>"author"); +if (@userlink) { + my $userpage; + if ($userinfo{"user"}=~/^http:/) { + $userpage = $userinfo{"user"}; + } else { + $userpage = + $cgi->url(-absolute=>1,-path_info=>1)."?profile=1&user=".$cgi->escape($userinfo{"user"}); + } + for my $element (@userlink) { + $element->attr(href=>$userpage); + $element->delete_content(); + $element->push_content($userinfo{"user"}); + } } delete $userinfo{"userpage"}; delete $userinfo{"user"}; @@ -325,13 +424,13 @@ sub authorize_user { if (my $session=$cgi->cookie("slsession")) { # Пользователь имеет куку my %sessbase; - dbmopen %sessbase,$forum->{"session"},0644; - if (exists($sessbase{$session})) { + dbmopen %sessbase,datafile($forum,"session"),0644; + if ($sessbase{$session}) { my ($user,$expires,$ip)=split(";", $sessbase{$session}); if (!defined $ip|| $ip eq $ENV{'REMOTE_ADDR'}) { my %userbase; - dbmopen %userbase,$forum->{"passwd"},0644; - if ( exists($userbase{$user})) { + dbmopen %userbase,datafile($forum,"passwd"),0644; + if ( $userbase{$user}) { my $userinfo = thaw($userbase{$user}); delete $userinfo->{"passwd"}; $userinfo->{"user"} = $user; @@ -339,7 +438,11 @@ sub authorize_user { delete $sessbase{$session}; newsession(\%sessbase,$forum,$user,$ip); } + print STDERR "user $user restored session $session\n"; $forum->{"authenticated"}=$userinfo; + print STDERR "authorize_user: + ",$forum->{authenticated}{user}, + $forum->{authenticated},"\n"; } dbmclose %userbase; } @@ -347,6 +450,13 @@ sub authorize_user { dbmclose %sessbase; } } +# +# Возвращает путь к файлу в директории +# +sub datafile { + my ($forum,$filename) = @_; + return $forum->{"datadir"}."/".$filename; +} # # Создает новую сессию для пользователя и подготавливает куку которую @@ -356,7 +466,7 @@ sub newsession { my ($base,$forum,$user,$bindip) = @_; if (!defined $base) { $base = {}; - dbmopen %$base,$forum->{"session"},0644; + dbmopen %$base,datafile($forum,"session"),0644; } my $sessname; my $t = time(); @@ -373,7 +483,7 @@ sub newsession { $base->{$sessname}=$user.";".str2time($cookie->expires()). ($ip?";$ENV{'REMOTE_ADDR'}":""); - $forum->{'cookie'}=[ $cookie, + $forum->{'cookies'}=[ $cookie, $cgi->cookie(-name=>"sluser",-value=>$user,-expires => $forum->{authperiod})]; } @@ -388,21 +498,29 @@ sub authenticate { openidstart($cgi,$openid_url); } my %userbase; - dbmopen %userbase,$forum->{"passwd"},0644; + dbmopen %userbase,datafile($forum,"passwd"),0644; my $user = $cgi->param("user"); + my $password = $cgi->param("password"); + print STDERR "user=>'$user'\npassword=>'$password'\n"; + $cgi->delete("password"); if (! $userbase{$user}) { set_error($forum,"Неверное имя пользователя или пароль"); return undef; } my $userinfo = thaw($userbase{$user}) ; dbmclose %userbase; - if (crypt($user,$userinfo->{passwd}) eq $userinfo->{passwd}) { + while (my ($key,$val)=each %$userinfo) { print STDERR "$key => '$val'\n";} + if (crypt($password,$userinfo->{passwd}) eq $userinfo->{passwd}) { delete $userinfo->{"passwd"}; + $cgi->delete("password"); $userinfo->{"user"} = $user; newsession(undef,$forum,$user); $forum->{"authenticated"} = $userinfo; + print STDERR "User $user authenticated successfully\n"; + return 1; } else { set_error($forum,"Неверное имя пользователя или пароль"); + return undef; } } # @@ -410,10 +528,11 @@ sub authenticate { # sub set_error { my ($forum,$message) = @_; + print STDERR "set_error: $message\n"; $forum->{error_message} = $message; } # -# Выводит текущий шаблно с сообщением об ошибке +# Выводит текущий шаблон с сообщением об ошибке # sub form_error { my ($form_name,$cgi,$forum,$msg) = @_; @@ -422,6 +541,24 @@ sub form_error { exit; } # +# Выполняет редирект (возможно, с установкой куков) на страницу, +# указанную # третьем параметре функции или в параметре CGI-запроса +# returnto +# Если и то, и другое не определено, пытается сконструировать URL для +# возврата из PATH_INFO. +# + +sub forum_redirect { + my ($cgi,$forum,$url) = @_; + if (!defined $url) { + $url = $cgi->param("returnto"); + $url = $cgi->url(-base=>1).$cgi->path_info() if (!$url); + } + print $cgi->redirect(-url=>$url, + ($forum->{cookies}?(-cookie=>$forum->{cookies}):())); + exit; +} +# # Обработка результатов заполнения формы регистрации. # # @@ -453,7 +590,7 @@ sub register { } } my %userbase; - dbmopen %userbase,$forum->{"passwd"},0644 + dbmopen %userbase,datafile($forum,"passwd"),0644 or form_error($formname,$cgi,$forum,"Ошибка открытия файла паролей $!"); if ($userbase{$cgi->param("user")}) { dbmclose %userbase; @@ -511,17 +648,82 @@ sub register { $userbase{$user} = freeze($userinfo); dbmclose %userbase; newsession(undef,$forum,$user); - if (defined $returnto) { - forum_redirect($returnto) + forum_redirect($cgi,$forum,$returnto) +} +# +# Обработчик формы логина. Сводится к вызову функции authenticate, +# поскольку мы поддерживаем логин одновременный с отправкой реплики. +# +sub login { + my ($form,$cgi,$forum)=@_; + if (authenticate($cgi,$forum)) { + forum_redirect($cgi,$forum); } else { - forum_redirect($cgi->url(-base=>1).$ENV{PATH_INFO}); + show_template(@_); } - } - +# +# Обработчик формы logout. В отличие от большинства обработчиков форм, +# поддерживает обработку методом GET +# +sub logout { + my ($form,$cgi,$forum) = @_; + $forum->{cookies}=[ $cgi->cookie(-name=>"sluser", -value=>"0", + -expires=>"-1m"),$cgi->cookie(-name=>"slsession", -value=>"0", + -expires => "-1m")]; + if (defined (my $session_id = $cgi->cookie("slsession"))) { + my %sessiondb; + dbmopen %sessiondb,datafile($forum,"session"),0644; + delete $sessiondb{$session_id}; + dbmclose %sessiondb; + } + forum_redirect($cgi,$forum); +} sub allow_operation { my ($operation,$cgi,$forum) = @_; return 1 if (grep $operation eq $_,"register","login","reply"); return 1; } + +sub reply { + my ($form,$cgi,$forum) = @_; + if (! exists $forum->{authenticated} ) { + form_error($form,$cgi,$forum,"Вы не зарегистрировались") if (!authenticate($cgi,$forum)); + } + # + # Находим файл дискуссии, в который надо поместить реплику + # + + # + # Сохраняем приаттаченную картинку, если есть. + # + + # Генерируем идентификатор записи. + # + + # + # Преобразуем текст записи в html и чистим его + # + my $txtree = undef; + if ($cgi->param("format") eq "bbcode") { + + } elsif ($cgi->param("format") eq "text") { + my $text = $cgi->escapeHTML($cgi->param("text")); + $text=~s/\r?\n\r?\n/

/; + $text=~s/\n/
/; + $txtree = + HTML::TreeBuilder->new_from_content("

$text
"); + } else { # Default - html + $txtree = + HTML::TreeBuilder->new_from_content("
".$cgi->param("text")."
"); + for my $badtag + ("script","style","head","html","object","embed","iframe","frameset","frame", + ($forum->{forbid_tags}?split(/\s*,\s*/,$forum->{forbid_tags}):())) { + for my $element ($txtree->find_by_tag_name($badtag)) { + $element->delete() if defined $element; + } + } + } +} + -- 2.39.5