quinta-feira, 29 de janeiro de 2009

Forma segura de fazer AJAX/PHP/MYSQL

Desafio

Transportar dados de uma página web com uma chamada XHR (XmlHTTPRequest) em método POST para um servidor que vai receber a solicitação e rodar um código PHP, depois inserir em um banco MySQL.

Browser -> XmlHTTPRequest (POST) -> PHP -> MySQL

Depois o processo inverso:

Ler do banco MySQL, processar no PHP uma resposta em XML e finalmente receber o XML na página web e processar com javascript essa resposta.

MySQL -> PHP (XML) -> XmlHTTPRequest (JS) -> Browser

Requisitos:

  1. Suportar múltiplos idiomas (Portanto requer UTF-8 de fim a fim)
  2. Garantir a integridade dos dados (não ter problemas de acentuação, caracteres japoneses, chineses, arábicos, etc.)
  3. Não causar erros de processamento. (XML não pode conter caracteres que impeçam o parse por exemplo)

Solução

Acho que cheguei a uma solução. O esquema é o seguinte:

*** SENTIDO PÁGINA WEB -> BANCO DE DADOS


AJAX

Na página WEB você pega o conteúdo que quer armazenar no banco de dados, vamos dizer.. algo parecido com um scrap do orkut, que tem livre input do usuário. Não há restrições de códigos ou caracteres de determinados idiomas. O usuário pode colocar o que ele quiser, o detalhe é que códigos não serão processados, mas apenas mostrados em uma requisição futura exatamente como foram inseridos.

Então você passa esse conteúdo pela função nativa javascript encodeURIComponent para que ela possa ser transmitida sem problemas.
Essa string, segura para ser transmitida, é então enviada em um XmlHTTPRequest via método POST para não limitar o número de caracteres. Se você quiser limitar, trate isso em uma função antes. (fonte: http://www.jmarshall.com/easy/cgi/portugue...html#getvspost)

PHP

O PHP ao receber a variável $_POST, já tem o conteúdo magicamente como se tivesse passado por um decodeURIComponent, ou seja, no mesmo estado em que o usuário digitou na caixa de texto, inclusive com vários espaços seguidos, quebras de linha, caracteres japoneses.

MYSQL

Você deve inserir com uma query normal (INSERT, UPDATE, REPLACE), só que a variável recebida deve passar pela função php mysql_real_escape_string para garantir a segurança (veja: http://www.php.net/mysql_real_escape_string).

Pronto! Ficará armazenado no banco de dados a string na exata forma em que o usuário digitou no campo de texto, sem qualquer transformação. Issp é útil para fazer buscas depois, sem perder nenhum caracter.

*** SENTIDO BANCO DE DADOS -> PÁGINA WEB

MYSQL

Faça um SELECT normal. Sem mistérios.

PHP

Como definido no problema, a resposta deve vir em XML. Para isso o conteúdo será enviado na forma:
"<![CDATA[".rawurlencode($resposta)."]]>"
<![CDATA[ para o interpretador XML não acusar erros de parse com quaisquer caracteres da resposta
rawurlencode para a resposta poder conter outros <![CDATA[ dentro dela que não devem ser interpretados, e sim mostrados na tela.
E ainda deve ser enviado o header utf8:
header('Content-type: text/html; charset=utf-8');
É certo que a resposta virá em UTF-8, de acordo com a RFC, mas não quero dar margem à erros bizarros.

AJAX

A função que tratará a resposta deve considerar os seguintes tratamentos do XML recebido:
var meuXML = new XML(meuObjetoXHR.responseText);
var minhaString = decodeURIComponent(meuXML['resposta'])
minhaString = htmlspecialchars(minhaString)
minhaString = conformNLBR(minhaString)

new XML criará um objeto XML com a resposta recebida.
decodeURIComponent irá transformar os %22 em caracteres reconhecíveis (se tiver dúvidas consulte a documentação)
htmlspecialchars é uma função criada por Kevin van Zonneveld, que peguei na internet e fiz algumas modificações para que funcionasse melhor.
conformNLBR é opcional, um simples replace de newlines por <br> e espaços duplos por " & nbsp;" de forma que seja mostrada a resposta em um elemento como um div ou span de forma idêntica ao input de usuário. Se ele quebrou a linha apertando enter, o texto aparecerá com a quebra de linha no lugar condizente. Se ele deu vários espaços para alinhar alguma coisa, isto permanecerá. Aqui só é interessante harmonizar os font-family da caixa de input e do container do texto depois.

-------------

Pensa que acabou?? Ainda tem o content-type da página web, o codepage do arquivo .php e as configurações do banco de dados.

A página web que aparecerá para o usuário deve estar na codificação UTF-8. Você pode escolher um método para fazer isso como meta-tags ou o comando header do PHP (eu uso o segundo).

Para editar os arquivos .php precisa usar um editor compatível com UTF-8 (uso o PSPad) e salvar nesse formato para garantir que uma string dentro do arquivo seja mostrada corretamente.

Agora o mais complicado, talvez seja o banco de dados.
O collation das tabelas deve ser utf8_unicode_ci.
Conjunto de caracteres MySQL: UTF-8 Unicode (utf8)
Collation de conexão do MySQL: utf8_unicode_ci
Tem uma parte que eu não entendo muito e aceito a ajuda de quem souber, mas caso você, na console do mysql dê um show variables; aparecerão essas linhas:
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8 |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |


Comigo funcionaram esses valores, desde que toda vez que eu abra uma conexão no php eu execute os seguintes comandos:
mysql_query("SET CHARACTER SET utf8");
mysql_query("SET NAMES utf8");


É isso aí. Foi bem extenso, mas acho que cheguei à solução. Em seguida vou postar os arquivos de exemplo que usei pra chegar no resultado.
Obrigado.

Aqui está o arquivo.
Para testar configure a sua conexão ao servidor mysql no arquivo includes/mysqlStart.php
Crie o banco de dados biblioteca com o collation correto.
dentro desse banco de dados crie a tabela teste com as colunas:

id -> INT -> auto_increment
text -> text

Abra no navegador o arquivo teste.php

Arquivo(s) anexado(s)

bagunca.zip ( 4.44KB )

Esqueci de dizer. Os exemplo foram testados em Firefox 3, e eu não sei se roda em IE.
Felizmente posso me dar à liberdade de desenvolver pra browsers que seguem padrões e ignorar totalmente o IE (que se exploda)

=)

Correção: new XML() criará um E4X e não um objeto XML no padrão DOM.
E4X é bem mais simples de manipular XML do que o object XMLDocument

http://en.wikipedia.org/wiki/E4X

Nenhum comentário: