Форма создания поста.

Привет всем. Я пишу сайт, и столкнулся с задачей создать форму для отправки комментария или поста.

<div id="textDiv" style="width:100%;height:200px;position:relative;">
    <textarea id="textBox" name="content" class="textarea"></textarea>
    <div id="handleBottom" style="width:100%; height:5px; background-color:#C0C0C0; position:absolute; top:195px; cursor:s-resize;">
    </div>
</div>

Пользователь набирает текст в textarea -> submit и инфа попадает в базу. Сейчас нет никаких проверок контена или обработки bb-кодов и прочего. Т.е. никто не мешает пользователю вписать javascript-код и к примеру украсть кукисы, оно по идее должно выполниться.. В связи с этим назрел вопрос, как лучше/удобнее реализовать форматирование текста.. (поддержка тегов подразумивается) + как лучше всего фильтровать содержимое поста ? Желательно примеры из личной практики, если таковые имеются. В общем все мысли, готов выслушать.. =)

PS: При создании поста в Input Format есть режим Full HTML, т.е. туда при желании можно впихать рабочий js ?

Простого

Простого однозначного решения, для гибкого разграничения, пожалуй нет.
Но на CPAN ЕМНИП есть модули парсящие javascript, и выковыривать его не очень сложно, так что можно попробовать.
javascript вставляется несколькими методами (<script inline, <script include, просто добавка обработчиков в теги), так что если не удалять совсем все теги, остается только их полностью парсить..
Ну или средний по сложности вариант, создать собственное подмножество тегов типа тех-же bb-кодов, и тоже их парсить и жестко ограничивать функциональность.

fu7ur3

fu7ur3 написал
Привет всем.
PS: При создании поста в Input Format есть режим Full HTML, т.е. туда при желании можно впихать рабочий js ?

Тут следует обратить внимание, что запрос не обязательно придет из вашими руками написанных форм.
Злоумышленник может сам создать строку запроса и послать ее используя, например, wget.

[ниже - IMHO]

Сам я давненько подобным не занимался,
но если бы занимался, то использовал бы либо конструкцию
 
$content =~ s/<script.*?>|<\/script>//sg;
 
Либо обрабатывал бы все теги, удаляя все, что не соответствует списку разрешенных.
Так, по моему, делает ЖЖ.
Т.е. где-то так:

my @tags = qw/div p a span br/; # разрешенные теги
my $content; # cюда - текст поста
 
my $cpos = 0;
sub findTag(\$;$);
 
my $tag_pattern = '^' . join('$|^',@tags) . '$';
 
while (my @tag = findTag($content,$cpos)) {
  my $tag = substr($content,$tag[0]+1,$tag[1]-3);
 
  $tag =~ s/\s.*//;
  if (substr($tag,0,1) eq '/') {
   substr($tag,0,1,'');
  }
   
  if ($tag =~ /$tag_pattern/si) {
    $cpos = $tag[0] + $tag[1]-1;
    next;
  }
  substr($content,$tag[0],$tag[1]-1,'');
}
print $content;
  
sub findTag(\$;$) {
  my ($text, $pos) = @_;
  while (1) {
   my $end = index($$text,'>', $pos);
   return () if $end == -1;
   my $begin = rindex($$text,'<', $end);
   if ($begin < $pos) {
     substr($$text,$end,1,'>');
     next;
   };
   my $check = index($$text,'>', $begin);
   $end = $check if $check < $end;
   return ($begin,$end-$begin+2);
  }
}

В первом случае — нет начального тега - значит нет скрипта.
Второй случай позволяет более тонко контролировать ввод, но имеет ряд недостатков, в том числе и громоздкость.
К тому же этот код не проходил релаьных тестов :))

Оба случая оставляют как есть содержимое парных тегов.

И, конечно же, соглашусь с предидущим оратором: наверняка на CPAN есть нечто подобное.

Я уже исправил

Я уже исправил свой пост - как всегда забыл что хатеэмельные скобки тут не показываются ;) вобщем проблема в том что можно javascript и без явного тега <script> вставлять.

Да, я еще

Да, я еще обратил внимание на то что пост неполный :)
Насколько я понимаю, имеется в виду
1. <script>alert('Hello world')</script>
2. <script type='text/javascript' src='helloworld.js'></script>
3. <body onload='alert("Hello world")'> </body>

Тогда остается только второй вариант из приведенных мною.
Я забыл снабдить его необходимыми комментариями:
он находит все html теги в содержимом и, если тег не определенн как разрешенный - просто удаляет вместе с параметрами тега.
Т.е. после обработки вариант 2 будет содержать просто alert('Hello world'), что будет интерпритировано браузером как обычный текст, варианты 2 и 3 будут пустыми, при условии, что теги script и body запрещены.
Если рассмотреть четвертый вариант - обработчики в разрешенном теге:
<div onload='alert("gigi")'>
То придется дописать в вышеобозначенный код еще и обработку параметров для разрешенных тегов (кста, по моему жж это тоже делает)
btw, исходный код livejournal открыт. И для желающих не изобретать велосипед есть возможность взять оттуда. Но мне больше нравится велосипед.

Всем спасибо,

Всем спасибо, за мысли! =) Выкладываю не суд общественности то что у меня получилось:

sub parse_text
{
    my $text = shift;
 
    $text =~ s/\</</g;
    $text =~ s/\>/>/g;
    $text =~ s/\[b\]/<b>/g;
    $text =~ s/\[\/b\]/<\/b>/g;
    $text =~ s/\[i\]/<i>/g;
    $text =~ s/\[\/i\]/<\/i>/g;
    $text =~ s/\[u\]/<u>/g;
    $text =~ s/\[\/u\]/<\/u>/g;
    $text =~ s/\[link.*=(.*http\:\/\/.*)\](.*)\[\/link\]/\<a href="$1"\>$2\<\/a\>/g;
    $text =~ s/\[quote\]/<p style="margin: 10px;" class="block">/g;
    $text =~ s/\[\/quote\]/ <\/p>/g;
    $text =~ s/\n+/<br>/g;
    $text =~ s/<script.*?>|<\/script>//sg;
 
    return $text;
}

Это весь функционал для добавления комментов, все что мне надо. Ругайте.


#!/usr/bin/perl 
print("goto rulez!!!!!!1<br>");

Вот например

Вот например строка вызовет не совсем правильное выполнение, мне кажется.
[link=http://test.com]lalala[/link][/link]
Может быть стоит в коде
 $text =~ s/\[link.*=(.*http\:\/\/.*)\](.*)\[\/link\]/<a href="$1">$2</a>/g;
заменить '.*' на '.*?' ?

В первых двух строках &lt; и &gt; съелись. Я прав? :)
Тогда $text =~ s/<script.*?>|<\/script>//sg; нужно поместить в самое начало либо просто выбросить.
А, да... еще
   $text =~ s!\[(/{0,1}[biu])\]!<$1>!g;
должна с успехом заменить

    $text =~ s/\[b\]//g;
    $text =~ s/\[\/b\]/<\/b>/g;
    $text =~ s/\[i\]//g;
    $text =~ s/\[\/i\]/<\/i>/g;
    $text =~ s/\[u\]//g;
    $text =~ s/\[\/u\]/<\/u>/g;

Но это на любителя.

В итоге:

sub parse_text
{
    my $text = shift;
    $text =~ s!<script.*?>|</script>!!sg;
    $text =~ s/</&lt;/g;
    $text =~ s/>/&gt;/g;
    $text =~ s!\[(/{0,1}[biu])\]!<$1>!g;
    $text =~ s!\[link=(http\://.*?)\](.*?)\[\/link\]!<a href="$1">$2</a>!g;
    $text =~ s/\[quote\]/<p style="margin: 10px;" class="block">/g;
    $text =~ s!\[/quote\]!<\/p>!g;
    $text =~ s/\n+/<br>/g;
    return $text;
}

Минус всего этого в том что не проверяется парность тегов. А так выглядит достаточно работоспособным.
Хотя, наверняка куча дырок.

Ну и на последок, я бы предпочел принимать ссылку на переменную, модифицировать ее, а возвращать 0 или 1:

sub parse_text (\$)
{
 my $text = shift;
 $$text =~ s/.../.../g;
 ...
 return 1;
}
my $txt = '\[quote\]text\[/quote\]'; #сюда пихаем пост
parse_text($txt);
print $txt;

korshak, спасибо за

korshak, спасибо за ценные советы..

Quote:

В первых двух строках < и > съелись. Я прав? :)

Да, именно так :)

Вот еще столкнулся с проблемой. Хочу сделать чтобы просто ссылки без тегов (http://kiev.pm.org) превращались в
kiev.pm.org
$$text =~ s/(http\:\/\/.*)/\<a href="$1"\>$1\<\/a\>/g;

но как быть с тегами [link=http://kiev.pm.org/node/260]Perl Community[/link], оно цепляет его URL тоже, как бытЬ ?
$$text =~ s/\[link=(http\:\/\/.*?)\](.*?)\[\/link\]/\<a href="$1"\>$2\<\/a\>/g;
т.е. надо сдела чтобы эти 2 правила одновременно работали.
Есть одна мысль построчно обрабатывать $text, а если в строке 2 разных ссылки.. В общем хелп..

Вот что сейчас есть.

#!/usr/bin/perl -w
use strict;
use CGI qw(:all);
use CGI qw/:standart/;
use CGI::Carp qw(fatalsToBrowser);
# # # # # # # # # # # # # # # # # # # #
print "Content-Type: text/html; charset=UTF-8;\n\n";
print qq(
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru" dir="ltr">
<head>
	<title>предпросмотр постов.</title>
	<meta http-equiv="content-type" content="text/html; charset=utf-8">
	<meta name="keywords" lang="ru" content="xz">
	<meta name="description" lang="ru" content="">
	<meta name="document-state" content ="dynamic">
	<meta http-equiv="Cache-Control" content="no-cache">
<link rel="stylesheet" type="text/css" href="http://10.10.1.11/css/style.css">
</head>
<body>);	
my $text = param('text');
my $original_text = $text;
$original_text =~ s/\n+//g;
parse_text(\$text);
print $text;
print "<hr>";
print qq(
<center>
<form method="post" action="index.cgi">
<textarea rows="30" cols="90" name="text">
$original_text
</textarea>
<br><br>
<input type="submit" value="Post it!">
</form>
</center>);
sub parse_text(\$)
{
	my $text = shift;
	$$text =~ s/\</</g;
	$$text =~ s/\>/>/g;
	$$text =~ s/\[(\/{0,1}[biu])\]/<$1>/g;
#	$$text =~ s/\[link=(http\:\/\/.*?)\](.*?)\[\/link\]/\<a href="$1"\>$2\<\/a\>/g;
#	$$text =~ s/(http\:\/\/.*)/\<a href="$1"\>$1\<\/a\>/g;
	$$text =~ s/\[quote\]/
<p style="margin: 10px;" class="quote">/g;
	$$text =~ s/\[\/quote\]/ <\/p>/g;
	$$text =~ s/\n+/<br>/g;
#	$$text =~ s/
<script.*?>|<\/script>//sg;
 
	return 1;
}
 
# # # #
exit 0;
------------------

Есть способ, но

Есть способ, но вобще-то это извращение и потеря производительности.
Но имхо, всяко лучше чем писать полный разбор.
Да, кста. в этомслучае уже стоило бы заменить http на http|https|ftp|telnet, это как минимум =)

my $tmpval;
$$text =~ s!(?:\[link=|)(http://[^\]\s]+)(?:\](.*?)\[/link\]|)(?{$tmpval = $2 ? $2 : $1})!<a href="$1">$tmpval</a>!g;

Это мракобесие будет одновременно выгребать и просто ссылки, и ссылки обрамленные тегом link. В первом случае вместо текста описания будет поставлена сама ссылка, во втором - текст описания из link

Если проследить логическое развитие кода, то скоро он все-таки перейдет к полному разбору текста.
По секрету скажу, что это, по моему, единственный способ избежать костылей и граблей,
но использовать его стоит только тогда, когда в этом есть реальная необходимость.
Да и производительность на нам можно получить достаточно приемлемую.
Вообщем, если настанет такой момент, рекомендую пересмотреть мой первый ответ.
Он может показаться интересным =) (но не раньше)