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

sexta-feira, 16 de janeiro de 2009

Algoritmo para determinar o número de votantes em uma enquete da internet

Você já teve curiosidade em saber quantas pessoas votaram em uma enquete, daquelas que só mostram o percentual de cada opção sem revelar o total? É meio estranho, mas eu já tive essa curiosidade várias vezes, para ter idéia da expressividade de uma pesquisa.
Às vezes, saber que 33,33% das pessoas pensam algo pode não ser suficiente. Talvez apenas 3 pessoas tenham respondido a enquete e só uma escolheu essa opção (1/3=0,3333)

Pensando matematicamente, cheguei a uma fórmula simples de como determinar esse total, e demonstro abaixo.

Sabendo o resultado parcial de uma enquete, e então interferindo no resultado (dando o seu voto) e lendo o novo resultado parcial, é possível determinar o total de votantes, medindo a variação percentual em relação a 1 voto (o seu). Calma, é simples.

Em uma enquete com as opções A, B, C... Vamos analisar a opção A

Seja:
Pa - Porcentagem da opção A antes do seu voto.
Pd - Porcentagem da opção A depois do seu voto na mesma
T - Total de votantes
X - Votantes da opção A

Sabemos que:
Pa = ( X / T )

Quando você vota, o número de votos na opção A fica X + 1, e o percentual fica Pd:
Pd = ( X + 1 ) / ( T + 1 )

Isto é um sistema de 2 equações e 2 incógnitas ( X e T ).
Rearranjando:

X - TxPa = 0
X - TxPd = Pd - 1

Eliminando X da equação chegamos facilmente a:

T = ( 1 - Pd ) / ( Pd - Pa )

Pa e Pd são valores conhecidos que você obtém na leitura dos resultados parciais.

Em anexo coloquei uma planilha do excel que faz a conta automaticamente.
DOWNLOAD: http://www.box.net/shared/zmponninux

Por exemplo, entrei no site do Floripa TEM e fiz o teste com as enquetes que tinham lá, porque eu estava curioso em saber o nível de visitação do site e qual os principais interesses dessas pessoas. Veja como ficou:

Em qual atividade você vai relaxar no Floripa Tem?
Pilates (antes):
30,14%
Pilates (depois):
31,08%
Votantes:
72




Em qual roteiro cultural você vai embarcar para conhecer melhor o que Floripa Tem?
Centro Histórico (antes): 22,22%
Centro Histórico (depois): 30%
Votantes:
9




Em qual embaixada você vai curtir mais no Floripa Tem 2009?
Beira-Mar (antes):
26,32%
Beira-Mar (depois):
30%
Votantes:
18




Mexa-se: qual trilha você vai encarar?
Costa da Lagoa (antes) 28,57%
Costa da Lagoa (depois) 30,56%
Votantes:
34




Qual esporte radical você vai aprender primeiro no Floripa Tem 2009?
Kitesurf (antes)
32,26%
Kitesurf (depois)
34,38%
Votantes:
30

Conclusão: Procuram o Floripa TEM pessoas com tendências sedentárias e sem interesse por cultura.

Cya.

quarta-feira, 14 de janeiro de 2009

Como descobrir o ip de um dispositivo de rede

Este artigo descreve um método para descobrir o ip que um dispositivo de rede (modem, roteador, switch, access-point, impressora ou até mesmo um computador) estiver usando naquele momento, usando um sniffer de rede.

Ir aos manuais muitas vezes resolve quando o dispositivo está no endereço ip padrão (10.1.1.1, 192.168.0.1, etc), mas pode ser trabalhoso achar esse manual quando você não tem as coisas tão organizadas assim (quase ninguém têm os manuais ao alcance).

Explicando o método:
Quando um nó é conectado na rede, o seu endereço ip é fixado por uma das 3 possibilidades: DHCP, Estático, Auto-atribuido. Nos 3 casos o procedimento é o mesmo, antes de tomar um certo ip pra si, o dispositivo envia frames ARP em broadcast, perguntando à toda rede se alguém já está usando aquele ip. Caso ninguém responda então ele assume o tal ip.

Requerimentos:
Baixe um sniffer de rede. No caso eu irei me basear no software livre Wireshark
WIRESHARK DOWNLOAD: http://www.wireshark.org/download.html

Procedimento:
Tenha a máquina que vai fazer o monitoramento no mesmo domínio de broadcast que o dispositivo incógnito, ou seja, não podem estar separados por roteadores, no máximo hubs/switches/access-points.
No wireshark monitore a interface de rede. Por experiência própria, quando a interface for wireless, eu só consegui ver os pacotes desabilitando o Promiscuous Mode nas opções de captura dela.

Pra facilitar a filtragem do tráfego você pode usar uma expressão conveniente no campo "Filter".
ex1: Mostrando apenas pacotes ARP.
Filter: arp

ex2: Caso a rede seja tão inundada que o filtro anterior não garanta que você veja com calma os pacotes, tente este
Filter: arp and eth.addr eq 00-00-00-00-00-00
(no lugar de 00-00-00-00-00-00 substitua pelo mac address do dispositivo incógnito).

DESLIGUE o modem/router/switch/impressora/o-que-quer-que-seja e religue-o denovo. Fique de olho no monitoramento até aparecerem os pacotes Gratuitous ARP request. Na coluna Info estará o endereço ip que você quer.

Figura 1

Veja na Figura 1 que eu fiz o filtro (destacado em laranja) para filtrar pacotes ARP do mac address que eu estou interessado. A filtragem mostra (destacado em vermelho) que os frames ARP vieram desse mac address. Caso eu usasse apenas o filtro "arp" eu deveria verificar se Source é realmente o meu dispositivo. Destacado em azul está o endereço ip que ele está tentando pegar. Como não houve nenhum impedimento, após a 4ª tentativa ele assumirá o ip 10.4.25.71, que é o ip que eu estou procurando.

Simplificando:
1- Ligar o sniffer e capturar pacotes
2- Filtrar (opcional)
3- Religar o dispositivo
4- Verificar o ip entre os pacotes capturados

Pode parecer demorado ou até complicado, mas não se deixe enganar. Por mais que o post seja extensivo, eu fiz isso apenas para não deixar dúvidas a quem não está acostumado ao drill, e é extremamente rápido, bem mais do que ficar adivinhando ou pesquisando.

Vantagens:
Existem vários outros métodos, até mais fáceis muitas vezes, mas a maioria que conheço funciona somente sob certas circunstâncias (o dispositivo é um gateway da rede - método traceroute, o dispositivo está na mesma subrede que o seu computador - método ipscan).
A vantagem deste é que supera as dificuldades dos dois métodos citados. As limitações são que o dispositivo possa ser resetado e esteja no mesmo domínio de broadcast.