Permitir que os usuários utilizem códigos HTML nas suas colaborações não apenas deixa o nosso site mais bonito, mas também demonstra respeito pelos usuários, ao permitir que se expressem utilizando seus conhecimentos.
Porém, deve haver limite. Algumas marcações mal escritas podem estragar o layout da página ou até mesmo conter programas perigosos para os visitantes. Como filtrar o que pode e o que não pode ser utilizado?
Vamos criar uma função para esta tarefa. Tudo o que não for permitido ou o que estiver com algum erro de sintaxe será convertido de modo a ser apresentado na tela como parte do conteúdo.
O PHP já possui uma função específica para converter tudo aquilo que não queremos que o navegador interprete como código HTML. Trata-se da função htmlspecialchars(). Experimente alguma coisa como:
print htmlspecialchars ('<html><body><h1>Eu não sou um documento html</h1></body></html>');
O resultado desta operação será de que tudo o que foi enviado para a função será impresso no navegador literalmente como texto. Isto é útil. Mas como ela não distingue marcações benignas de marcações malignas, vamos criar uma função capaz desta distinção.
Nossa função terá um conjunto de marcações e parâmetros de marcações que podem ser aceitos por padrão. Mas um segundo argumento indica se desejamos ou não filtrar o conteúdo. Embora pareça patético enviar um conteúdo para uma função e, ao mesmo tempo, indicar que este não deva ser filtrado, isto nos será útil posteriormente.
Se o segundo parâmetro da função for 1, a filtragem deverá ser feita normalmente; se for 2, todo o HTML é permitido – o conteúdo não será filtrado; mas, se 0, então todo o início de marcação HTML (“<“) será convertido para a entidade “<”.
Não utilizaremos htmlspecialchars(), pois o conteúdo poderá conter entidades HTML que não desejaremos escapar.
Vamos À função:
<?php function filter_html ($string, $filter_html_mode=1) { // filter html // A lista de tags permitidas por padrão static $valid_tags = array( 'b'=>true, // Texto em negrito 'br'=>true, // Quebra de linha forçada 'caption'=>true, // Legenda de tabela 'col'=>true, // Coluna de tabela 'colgroup'=>true, // Grupo de colunas de tabela 'div'=>true, // Contentor genérico 'em'=>true, // ênfase 'hr'=>true, // Barra horizontal 'i'=>true, // Texto em itálico 'mark'=>true, // Destaque 's'=>true, // Texto em negrito 'small'=>true, // Texto pequeno 'span'=>true, // Contentor genérico 'strong'=>true, // Texto grande 'sub'=>true, // Texto Subescrito 'sup'=>true, // Texto superescrito 'table'=>true, // Tabela 'tbody'=>true, // Corpo de tabela 'td'=>true, // Célula de tabela 'tfoot'=>true, // Rodapé de tabela 'th'=>true, // Cabeçalho de tabela 'thead'=>true, // Cabeçalho de tabela 'tr'=>true, // Linha de tabela 'u'=>true, // Texto sublinhado 'wbr'=>true // Local propício para quebra de linha ); // As tags que não trabalham em pares. static $single_tags=array( 'br'=>true, 'hr'=>true, 'wbr'=>true ); // A lista de argumentos permitidos por padrão static $valid_arguments = array ( '/'=>true, // Argumento padrão para marcações singulares 'abbr'=>true, // Cabeçalho - legenda 'alt'=>true, // Descrição 'axis'=>true, // Lista de cabeçalhos relacionados 'cellpadding'=>true, // Preenchimento da célula 'cellspacing'=>true, // Espaçamento entre células 'cite'=>true, // Endereço da citação 'colspan'=>true, // Número de colunas ocupadas pela célula 'frame'=>true, // Parte do quadro a ser renderizado 'header'=>true, // Cabeçalho 'height'=>true, // Altura 'href'=>true, // Endereço 'lang'=>true, // Idioma 'name'=>true, // Nome 'rowspan'=>true, // Número de linhas ocupadas pela célula 'rules'=>true, 'title'=>true, // Título - legenda 'scope'=>true, // Campo coberto pelas células do cabeçalho 'span'=>true, 'src'=>true, // Endereço do recurso 'style'=>true, // Regras de folha de estilo 'summary'=>true, // Descrição de estrutura para saída em fala 'width'=>true // Largura ); // Se nenhum HTML for permitido if ($filter_html_mode == 0) return str_replace ('<', '<', $string); // Se todo o HTML for permitido elseif ($filter_html_mode == 2) return $string; // O resultado $result = ''; // O ponteiro que utilizaremos para percorrer a string $pointer = 0; // Vamos iniciar um loop infinito para percorrer a string while (true) { // infinity loop // Vamos procurar o início de uma tag ("<") $tag_start = strpos ($string, '<', $pointer); // Se não for encontrado, a tarefa terminou e sairemos do loop if ($tag_start === false) { // tag not found $result .= substr ($string, $pointer); return $result; } // tag not found // Vamos procurar o fim da tag (">") $tag_start ++; $tag_end = strpos ($string, '>', $tag_start); /// Se não for encontrado, a tarefa terminou e sairemos do loop if ($tag_end === false) { // tag end not found $result .= str_replace ('<', '<', substr ($string, $pointer)); return $result; } // tag end not found // Vamos recortar o conteúdo da tag e fazer uma análize $tag = substr ($string, $tag_start, $tag_end - $tag_start); $tag_end ++; // Vamos recortar cada elemento da tag preg_match_all ('/([a-zA-Z]+)([=]["][^"]*["])?\ ?\/?/', $tag, $preg); $tag_array = $preg[1]; // O primeiro elemento corresponde à própria tag $tag_original = array_shift ($tag_array); // A tag poderá estar em maiúsculas, // vamos converter para minúsculas a fim de poder comparar com a lista de tags válidas $tag_type = strtolower ($tag_original); // Vamos comparar o primeiro elemento com a lista de tags válidas $accept = false; if (isset ($valid_tags[$tag_type])) { // accept tag $accept = true; // Vamos comparar os demais elementos com a lista de argumentos válidos foreach ($tag_array as $argument) { // each argument $argument = strtolower ($argument); if (!isset ($valid_arguments[$argument])) { // invalid argument $accept = false; break; } // invalid argument } // each argument } // accept tag // Se toda a tag foi aceita if ($accept) { // allow tag // Se a tag trabalha sozinha if (isset ($single_tags[$tag_type])) { // allow single tag $result .= substr ($string, $pointer, $tag_end - $pointer); } // allow single tag // Outro caso, iremos procurar uma tag de fechamento correspondente else { // allow double tag $close_tag = '</' . $tag_original . '>'; $close_tag_start = strpos ($string, $close_tag, $tag_end); // Se a tag de fechamento não foi encontrada if ($close_tag_start === false) { // deny no closed tag $result .= str_replace ('<', '<', substr ($string, $pointer, $tag_end - $pointer)); } // deny no closed tag // Se encontramos a tag de fechamento else { // close tag found $result .= substr ($string, $pointer, $tag_end - $pointer); // IMPORTANTE: O conteúdo encontrado entre as tags // será analizado em busca de outras tags $result .= filter_html (substr ($string, $tag_end, $close_tag_start - $tag_end)); $result .= $close_tag; $tag_end = $close_tag_start + strlen ($close_tag); } // close tag found } // allow double tag } // allow tag // Caso a tag ou algum argumento não foi permitido else { // deny - escape html special chars $result .= str_replace ('<', '<', substr ($string, $pointer, $tag_end - $pointer)); } // deny - escape html special chars // Avançaremos o ponteiro para continuar a análize $pointer = $tag_end; } // infinity loop } // filter html ?>
Da forma como está configurada, esta função é bastante liberal, permitindo marcações para estilizar o texto e construir tabelas. Argumentos como style (para folhas de estilo) e para estruturar as tabelas são permitidas. Por outro lado, marcações ativas ou argumentos que poderiam acionar scripts não são permitidos.
Marcações que estiverem “cruzadas” como em
<i><u>texto</i></u>
, ou que não tenham a tag de fechamento encontradas serão escapadas para o conteúdo. Isto ajudará o usuário e evitará mal intencionados que porventura queiram estragar o layout da página com algo como
<table style="display:none">
– isto poderia fazer o navegador não exibir o restante do documento, esperando o fechamento da tabela.
Deixe seu comentário