Parsowanie BBCode

Obsługa znaczników BBCode, to częsty problem, na który napotykają programiści tworzący aplikacje internetowe. Wydawać by się mogło, że jest to proste zadanie i owszem. Jednak ostatnio przekonuję się, że wielu twórców aplikacji udowadnia, że nie bardzo je rozumie.

Bezpieczeństwo - BBCode, a XSS

Celem stosowania BBCode jest ograniczenie funkcjonalności użytkownika w taki sposób, by miał on niewielki wpływ na materiały umieszczane w naszym serwisie. Jeśli zakładamy bloga lub małe portfolio, to oczywisty jest fakt, że opublikowane materiały mają służyć rzeszy Internautów. W takim wypadku warto zadbać o to, by każdy z odwiedzających naszą internetową przestrzeń mógł wyrazić swoją opinię.

Problem ten możemy rozwiązać w prosty sposób, dodając możliwość umieszczania komentarzy lub podpinając pod stronę księgę gości. Takie rozwiązanie może być wystarczające na początek, ale wraz z rozwojem naszego serwisu warto zainwestować w rozwój sposobu dodawania opinii, które nierzadko rozwijają się w pokaźne dyskusje.

W takim wypadku dobrze by było mieć możliwość kolorowania tekstu, czy umieszczania obrazków lub hiperłączy. Mamy do wyboru dwa rozwiązania: użycie funkcji: strip_tags i wskazanie dozwolonych znaczników lub użycie funkcji htmlspecialchars i wprowadzenie własnych.

Pierwsza metoda jest najprostsza, ale i najbardziej niebezpieczna. Jeśli dla przykładu pozwolimy użytkownikowi na dodanie obrazka przez użycie znacznika <img>, to możemy łatwo zostać oszukani, gdy ktoś korzystając ze zdarzenia onerror umieści na naszej stronie złośliwy kod JavaScript. Dlatego zasada "Co nie jest zabronione, jest dozwolone" nie zdaje egzaminu w przypadku bezpieczeństwa. Dlatego najlepszym wyjście jest stworzenie własnych znaczników, które będą działały na naszych zasadach.

Aspekty techniczne

Najczęściej popełnianym błędem jest używanie funkcji str_replace zamiast wyrażeń regularnych. O ile takie rozwiązanie sprawdza się w przypadku pewnych znaczników: [b]jakiś tekst[/b] za pomocą kodu
 

str_replace(array("[b]", "[/b]"), array("<b>", "</b>"), $tekst);

lub dla [img]http://www.strona.pl/jakies_zdjecie.jpg[/img]

str_replace(array("[img]", "[/img]"), array('<img src="', '" />'), $tekst);

O tyle bywa zawodne w przypadku gdy nie zamkniemy jakiegoś tagu, lub chcemy użyć bardziej zaawansowanego znacznika.

Dla przykładu: [url=http://www.strona.pl]nazwa strony[/url]

W tym wypadku nie mamy wyboru i musimy użyć wyrażeń regularnych. Mniej zaawansowanym aplikacjom nie powinna zaszkodzić tablica parametrów dla funkcji preg_replace, ale ja zaprezentuję bardziej eleganckie rozwiązanie.

Problem można rozwiązać na dwa sposoby: stosując funkcję preg_replace_callback albo stosując przełącznik "e". To drugie rozwiązanie wydaje mi się bardziej eleganckie, dlatego się do niego odniosę.

Rozwiązanie problemu

function funkcja_przemiany($znacznik, $tekst, $parametr = false)
{
...
}

$tekst = preg_replace("#\[([a-z])(.*?)?](.*?)\[/\\1\]#esi", "funkcja_przemiany('\\1', '\\2', '\\3')", $tekst);

W funkcji przemiany dla parametru $znacznik stosujemy instrukcję switch, która przeniesie nas do odpowiedniej procedury obsługującej parsowany znacznik oraz przekaże odpowiednie parametry. Wynik zwrócony przez funkcję przemiany zostanie zastąpiąny znalezionym wyrażeniem ze wzorca.

Dla przykładu: [url=http://www.strona.pl]nazwa strony[/url] może wyglądać następująco

function przemien_url($tekst, $parametr)
{
return '<a href="'.$parametr.'">'.$tekst.'</a>';
}



Proszę zważać na to co piszecie w komentarzach. Wpisy zawierające idiotyczne, obraźliwe, wulgarne, ubogie merytorycznie lub niezgodne z polskim prawem treści będą usuwane.