Filtrando o HTML

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 “&lt;”.

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 ('<', '&lt;', $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 ('<', '&lt;', 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 ('<', '&lt;', 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 ('<', '&lt;', 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.

Sobre o Autor

Avatar de angelobeck
Redes Sociais:

Deixe seu comentário

X