30 de maio de 2009

Sintaxe para ativar envio de email no HTML

É bastante comum que páginas na internet tenham um email que, quando clicado, abre o Outlook (ou outro programa de email) para que o usuário do site possa enviar uma mensagem. Quem já mexeu com HTML sabe que esse efeito é obtido com uma âncora, isto é, a tag <a> e que a sintaxe básica é a seguinte:
<a href="mailto:contato@server.com">Contato</a>

O resultado HTML é um link como Contato.

O que menos gente conhece é que a sintaxe do mailto permite mais do que simplesmente informar o destinatário do email. É possível incluir uma lista de destinatários, lista de endereços que receberão cópia ou cópia oculta da mensagem, o assunto e o corpo da mensagem. Veja a sintaxe :
mailto:lista-destin?outros-params
Onde
mailto: é texto obrigatório
lista-destin é a lista de endereços de email separados por vírgula - ao menos um tem que estar informado, como no exemplo acima.
?outros-paramsSe quiser incluir outros parâmetros, inclua a interrogação (?) e um ou mais dos seguintes parâmetros,separados pelo caracter & (E comercial):
subject=texto para o assunto do email
cc=endereços de email a receberem cópia, separados por vírgula
bcc=endereços de email a receberem cópia oculta, separados por vírgula
body=texto para o corpo do email; para inserir quebra de linha, use a combinação %0A (porcentagem zero a).

Segue um modelo usando esses recursos:
<a href="mailto:amigo1@server.com,vizinho@server.com?subject=Contato a partir do site&cc=socio1@server.com,socio2@server.com&bcc=eumesmo@server.com&body=Contato a partir do site.%0ASite indicado por um amigo.">Contato</a>

O resultado é: Contato . Clique no link para ver como isso aparece no seu programa de email.

Essa abordagem tem um inconveniente: ela exige que o usuário do site tenha um programa de email (como o outlook) para funcionar. Para evitar isso, você terá que criar um formulário em sua página e tratar o envio do email no servidor, através de script ou dentro do seu programa.

Se o site é criado e hospedado remotamente - como esse blog - uma sugestão que encontrei num forum é o site Nomailto.com, que oferece gratuitamente um serviço que cria o formulário, permitindo o envio de até 300 emails por mês. No entanto, como não usei o serviço, não possa dar uma opinião sobre ele ...

Uma última recomendação : incluir endereços de email diretamente numa página facilita a vida dos robots que vasculham e montam uma base de dados para depois enviar spams aos emails encontrados. Se quiser ao menos dificultar um pouco o trabalho deles, use algum serviço para codificar os endereços de email que for incluir em suas páginas. Um exemplo desse tipo de serviço é o E-mail Obfuscator Tool, que oferece codificação simplesmente modificando a representação dos caracteres ou ainda através da inserção de um código javascript, mais complexo.

Obs: os endereços de email neste post são apenas exemplos, eles não existem!

29 de maio de 2009

Recuperação de arquivos apagados no Windows

Atire a primeira pedra quem nunca apagou um arquivo importante que não tinha backup! Mesmo um técnico previdente ou o analista mais experiente vez ou outra comete esse erro.

Para essas horas de pânico, arrumei uma ferramenta muita boa chamada Handy Recovery que é capaz de recuperar arquivos que tenham sido removidos da lixeira do Windows. Por ser pequeno, a instalação dele não chegou a comprometer os dados do arquivo que eu queria recuperar. Na verdade, a instalação em si não é necessária pois eu simplesmente copiei o programa de um outro computador.

Segundo a documentação, o Handy Recovery é capaz de restaurar arquivos de qualquer sistema de arquivos do Windows - FAT16, FAT32 ou NTFS. A versão 4 da aplicação tem instalação até mesmo para Windows Vista, mas esta é uma versão comercial, isto é, paga.

A versão gratuita é ainda a 1.0, que possui uma interface de usuário mais simples que a versão paga e disponibiliza menos recursos, mas faz o serviço a que se propõe. Essa interface lembra bastante o Windows Explorer, mostrando num painel à esquerda a lista de pastas e no outro, à direita, a lista de arquivos. Há um filtro que pode ser ativado para que a ferramenta mostre apenas os arquivos deletados; com o filtro desligado, todos os arquivos - incluindo os deletados - são mostrados na lista. Os arquivos e pastas que estão deletados aparecem com um ícone diferente (um xis vermelho) para facilitar a localização.

Para restaurar um arquivo, basta localizá-lo no painel à direita e selecioná-lo. É possível selecionar mais de um arquivo por vez usando o control ou o shift, tal qual o Windows Explorer. Quando há um ou mais arquivos selecionados, o botão Restore é habilitado na barra de ferramentas, permitindo a restauração deles. Ao clicar o botão, o programa exibe uma tela para que você possa selecionar a pasta para onde o(s) arquivo(s) deve(m) ser restaurado(s).

É importante selecionar uma unidade de disco diferente daquela onde o arquivo original está. Como o arquivo está apagado para o Windows, os bytes que ele ocupa no disco são registrados como "livres". Assim, restaurar no mesmo disco pode fazer com que o Windows escreva inadivertidamente sobre esses espaços "livres", danificando o arquivo que se quer restaurar.

A versão paga tem alguns recursos extras interessantes, como a possibilidade de se localizar o arquivo pelo seu nome, extensão ou qualquer máscara usando "*" e "?". Há ainda possibilidade de preview do arquivo a ser restaurado e opção de criar uma imagem completa de um disco e, mais tarde, restaurar arquivos a partir dessa imagem, se for necessário.

28 de maio de 2009

Chamando funções de uma biblioteca externa em Delphi

Mostrei numa série de posts como mapear as funções de uma DLL para poder chamá-las em um programa Delphi usando a sintaxe:
function GetName (pName: PChar): Integer;cdecl;
external 'BIBLIO.DLL' name '_GetName';

Essa abordagem, no entanto, tem um inconveniente: a DLL é carregada imediatamente, junto com o programa. Se a DLL não puder ser carregada - ela não foi encontrada ou uma de suas dependências não foi encontrada, por exemplo - então o programa em si também não é carregado e o Windows exibirá uma mensagem de erro.

Vou mostrar aqui um conjunto de funções da API do Windows que permite carregar a biblioteca dinamicamente. Nesse cenário, se a biblioteca não puder ser carregada por qualquer motivo, o programa em Delphi continuará em execução e poderá tratar o erro de uma forma mais elegante, mostrando mensagem de erro apropriada. Se for o caso, o programa poderá também desabilitar seus recursos que dependam da biblioteca problemática.

Para começar, será preciso definir um tipo de dado no Pascal que aceite receber um ponteiro para a função da biblioteca. Usando a função declarada no começo do post como exemplo, o tipo a ser definido será:
type TPtrGetNome = function(pNome: Pchar): Integer;cdecl;

Veja que o tipo declarado é exatamente igual à assinatura da função, sem o nome. Se quiser chamar outras funções, será necessário criar um tipo para cada assinatura diferente.

A função da API do Windows necessária para obter o endereço (ponteiro) da função que queremos chamar aceita como parâmetro um handle para a biblioteca. Por isso, teremos que obter esse handle antes, através da chamada de outras duas APIs do Windows, LoadLibrary para a carga e FreeLibrary para descarregar a biblioteca da memória, devolvendo ao Windows os recursos utilizados.
var lHandle: THandle;
begin
lHandle := LoadLibrary ('biblio.dll');

if lHandle <> 0 then begin
{ chamar a função aqui }
end
else
ShowMessage (GetLastErrorMsg);

FreeLibrary (lHandle);
end;

A API LoadLibrary retorna zero se não foi possível carregar a DLL; a API GetLastError recupera o código do erro ocorrido; qualquer outro número indica um handle válido mapeado para a DLL carregada. A função GetLastErrorMsg é minha e apenas formata o código de erro retornado por GetLastError.

Agora podemos obter o ponteiro para a função GetName na biblioteca carregada e, então, executá-la como se ela estivesse codificada em nosso próprio programa. Para obter o ponteiro da função, devemos chamar a API GetProcAddress informando o handle da biblioteca e o nome da função desejada. O nome da função segue as regras que eu descrevi no post sobre name mangling; se não tiver certeza do nome correto, use as dicas sobre como descobrir os nomes das funções em uma DLL.

var lHandle: THandle;
DoGetName: TPtrGetNome;
begin
lHandle := LoadLibrary ('biblio.dll');

if lHandle <> 0 then begin
DoGetName := GetProcAddress (lHandle, '_GetName');
if @DoGetName <> nil then
DoGetName (_Name);
end
else
ShowMessage (GetLastErrorMsg);

FreeLibrary (lHandle);
end;

Caso a função solicitada não exista na biblioteca, o ponteiro nil é retornado e deve ser tratado. Não mostrei nesse trecho mas a variável _Name é do tipo PChar e precisa ser alocada antes de se efetuar a chamada - aqui, fiz isso em outro momento.

26 de maio de 2009

Chamando funções e classes C++ no Delphi - parte 3

Recebi alguns feedbacks a respeito dos posts anteriores em que mostrei como chamar funções C/C++ dentro do Delphi. Então, resolvi retomar o assunto para explicar algumas dúvidas que surgiram, principalmente referente ao uso de ponteiros.
Como variáveis do tipo ponteiro são muito comuns no C/C++, ficou faltando mostrar como lidar com uma função que tivesse parâmetros desse tipo. Veja abaixo a declaração em C de uma função existente numa biblioteca:
int GetName (unsigned char * pName);

O Delphi também lida com ponteiros, sendo o tipo PChar correspondente ao unsigned char *. Supondo que a DLL seja BIBLIO.DLL e a função esteja exportada como _GetName, o mapeamento da função em Delphi ficaria:
function GetName (pName: PChar): Integer;cdecl;
external 'BIBLIO.DLL' name '_GetName';

Normalmente, esse tipo de chamada supõe que o ponteiro passado como parâmetro já esteja alocado antes de ser usado. Então, para utilizar a função, devemos antes fazer a alocação e, depois de usar o retorno, devolver a memória alocada:
var lName: PChar;
begin
{ Aloca espaço para até 50 caracteres. A documentação da função deve indicar qual o tamanho necessário.}
GetMem (lName, 50);

if (GetName (lName) = 0) then begin
{ Usa aqui o nome contido em lNome }
end;

{ Devolve a memória alocada }
FreeMem (lName);
end;

Outra dúvida recorrente foi quanto ao uso do cdecl: o que é isso exatamente ? Esse recurso das linguagens de programação chama-se formalmente Convenção de chamada. Isto afeta as chamadas de funções e procedures de duas maneiras: como é feita a passagem de parâmetros para a função e de quem é a responsabilidade de fazer a limpeza da "pilha de chamadas" (stack) quando terminar a chamada à função. O quadro abaixo mostra as convenções mais comuns:
ConvençãoLimpezaParâmetros
cdeclRotina chamadoraPassados da direita para a esquerda usando o stack. Comum em bibliotecas desenvolvidas em C/C++
pascalA própria rotinaPassados da esquerda para a direita usando o stack. Usada pela linguagem Pascal.
stdcallA própria rotinaPassados da direita para a esquerda usando endereços múltiplos de 4 bytes no stack. Usada principalmente em funções da API Win32.
fastcallA própria rotinaOs 2 primeiros parâmetros do tipo DWORD (ou menores) são passados em registradores. Os demais são passados da direita para a esquerda usando o stack.
registerA própria rotinaPassados em registradores, quando possível. Os que não forem possíveis de passar em registradores são passados da esquerda para a direita usando o stack.
safecallA própria rotinaPassados da direita para a esquerda usando o stack. Usada normalmente em métodos de interface COM, tratando exceções que sejam levantadas pela interface.


E como é que eu sei qual das convenções deve ser usada para mapear uma determinada função ? Bem, se a documentação da função não for explícita quanto a isso, será preciso dar uns chutes, fazer algumas experiências... O normal é que funções em C/C++ usem o tipo cdecl e que funções desenvolvidas com o Delphi/Pascal usem o tipo pascal ou register. E bibliotecas que sigam o padrão da Microsoft normalmente usam o tipo stdcall.

Ainda sobrou um assunto: como carregar DLLs dinamicamente e como declarar os ponteiros para funções corretamente para usar nesse cenário. Falo disso num próximo post.

25 de maio de 2009

Obtendo o MAC Address para gerar um Hardware Key em Delphi

Há certas situações em que é interessante ter um número único para identificar uma estação de trabalho. Por exemplo, a instalação de um software pode ter a política de licença de uso baseado nessa identificação da estação. O SAP Business One usa um esquema assim para gerar um código e associá-lo ao servidor de licenças da aplicação. Para conseguir o arquivo com as licenças adquiridas, é preciso informar no site da SAP esse código gerado. Caso se informe um código errado, as licenças simplesmente não são liberadas e o software não funciona.

Vou mostrar aqui um método para gerar um código Hardware Key baseado no MAC Address, isto é, na informação da placa de rede instalada na máquina. Essa é uma boa abordagem pois é teoricamente garantido que não haverão duas placas de rede com o mesmo MAC Address, mesmo que sejam de fabricantes diferentes. A ideia é capturar o endereço - que é uma informação binária - e gerar uma representação textual (string) dele. Para obter o endereço, vou usar uma função da API do Windows chamada GetAdaptersInfo. Ela aceita 2 parâmetros: um ponteiro para a estrutura IP_ADAPTER_INFO e o outro o tamanho da estrutura alocada:
function GetAdaptersInfo(pAdapterInfo: PIP_ADAPTER_INFO; var pOutBufLen: ULONG): DWORD;stdcall;

Parece que essa função não está mapeada no Delphi mas localizei no site do projeto JEDI fonte com o mapeamento das funções da biblioteca Iphlpapi.dll do Windows - incluindo a GetAdaptersInfo e as estruturas necessárias. Também tenho cópia desses fontes, caso não encontrem lá.

Esta é uma daquelas funções malucas do Windows que é preciso chamar 2 vezes: a primeira para obter a quantidade de memória a ser alocada e a segunda para efetivamente recuperar os dados desejados.
dwBufLen := 0;
AdapterInfo := Nil;
GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwBufLen > 0) then begin
GetMem (AdapterInfo, dwBufLen);
dwStatus := GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
...

O endereço que precisamos é um array de bytes, declarado como segue:
type TWMacId = array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;

Listo abaixo o código para a função que recupera o MAC Address. Neste exemplo, estou recuperando apenas a primeira placa de rede encontrada; se houverem outras, o algorítmo simplesmente as ignora:
function GetMACaddress : TWMacId;
var i : integer;
AdapterInfo : Pointer;
pAdapterInfo : PIP_ADAPTER_INFO;
dwBufLen, dwStatus : LongWord;
begin
dwBufLen := 0;
AdapterInfo := Nil;{ Inicialmente, limpa o retorno }
for i := 0 to MAX_ADAPTER_ADDRESS_LENGTH - 1 do
Result [i] := 0;
GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwBufLen > 0) then begin
GetMem (AdapterInfo, dwBufLen);
dwStatus := GetAdaptersInfo(PIP_ADAPTER_INFO (AdapterInfo), dwBufLen);
if (dwStatus = ERROR_SUCCESS) then begin
pAdapterInfo := PIP_ADAPTER_INFO (AdapterInfo);

{ Copia o endereço p/ nossa variável de retorno }
for i := 0 to MAX_ADAPTER_ADDRESS_LENGTH - 1 do
Result[i] := pAdapterInfo.Address[i];
FreeMem (AdapterInfo);
end;
end;
end;

Agora, falta formatar esse resultado como um texto. Vou transformar os bytes retornados usando a representação hexadecimal de cada um deles, numa ordem aleatória. Isto é, peguei aleatoriamente os bytes de número 4, 2, 1, 5 e 3. Segue a função completa:
function GetHardwareKey : String;
var id : TWMacId;
begin
id := getMacAddress;
Result := Format ( '%.2X', [id [4]]) +
Format ( '%.2X', [id [2]]) +
Format ( '%.2X', [id [1]]) +
Format ( '%.2X', [id [5]]) +
Format ( '%.2X', [id [3]]);
end;

Se achar que usar somente o MAC Address não é suficiente para gerar um código seguro para sua aplicação, use essa informação como base e mescle-a com outros dados do computador. Por exemplo, inclua dados da CPU, do sistema operacional, etc.

Mais Informações
Endereço MAC, JEDI Project.

22 de maio de 2009

Chrome 2.0 e a guerra dos Browsers

Fazia tempo que não se via uma guerra boa assim ! Desde que a Microsoft se deu conta do potencial da internet e incluiu o Internet Explorer gratuitamente no Windows para recuperar o terreno dominado pelo Netscape que os navegadores de internet (os browsers) não estavam tanto na berlinda.

A Microsoft tinha se acomodado depois de vencer a guerra contra a Netscape e ficou bastante tempo sem atualizar o Internet Explorer. Voltou a se mexer apenas quando foi lançado o Firefox - desde então, lançou duas atualizações do IE, incorporando inovações introduzidas pelo novo concorrente (abas para navegação, por exemplo) e produzindo suas próprias inovações (navegação InPrivate - aquela que permite o usuário navegar sem deixar rastros no computador pois não registra o histórico de navegação nem os cookies).

Agora, junta-se à guerra o Google Chrome. A versão 1 já alardeava ter um motor de scripts mais rápidos que os outros Browsers mas foi oficialmente lançada a versão 2.0 e os engenheiros alegam que conseguiram uma melhoria de 30% na carga e execução de páginas com scripts. Veja no blog do Chrome e a notícia publicada no IDG Now !. Além disso, melhoraram também a página exibida quando se cria uma nova aba vazia. Essa página já mostrava imagens dos sites mais visitados e agora permite que você remova dessa lista aqueles sites que você visitou mas tem vergonha que as outras pessoas saibam.

Outras melhorias já existiam nos concorrentes do Chrome, como o modo tela cheia (full screen) e a possibilidade de armazenar informações que você digitou em formulários na internet, evitando que se tenha que digitá-las todas as vezes que entrar no mesmo site.

Outros fabricantes também anunciaram novas versões, praticamente todos correndo para tornar a carga de páginas mais rápida. Como a Web 2.0 está cada vez mais difundida e depende fortemente do uso de scripts, ter um motor mais estável e rápido para esse recurso acaba sendo imprescindível. Dentre o sites que usam massivamente a Web 2.0 e são bastante populares, cito o Google Docs e GMail.

O Firefox 3.5 (ainda em beta) é um dos que anunciaram melhorias, tanto na carga de páginas com conteúdo multimídia quanto no engine de carga e execução de scripts. Além de incorporar o recurso de navegação InPrivate, essa versão também está preparada para o novo padrão do HTML, a versão 5. Veja aqui notícia sobre o FireFox 3.5 na Info Online.

Por fim, a Apple lançará o Safari 4 cujo foco inovativo é também a melhoria dos engines de renderização das páginas e de execução de scripts.

=> Mais informações
Google Chrome Blog,Google lança versão 2.0 do Chrome, Nova geração de Navegadores

21 de maio de 2009

Erro no backup do SQL Server

A maior parte dos Clientes da ABC71 usa o banco de dados MS SQL Server e, de vez em quando, algum deles precisa mandar a base para analisarmos.

Outro dia, recebi a base de um Cliente num pen-drive e tentei restaurá-la em nosso servidor de testes. A tentativa me rendeu uma mensagem escabrosa:
Msg 3241, level 16
The media family on Device ‘device-name’ is incorrectly formed. SQL Server cannot process this media family

Revirei a internet para tentar achar uma forma de contornar o problema mas simplesmente não foi possível; parece que o arquivo de backup se corrompeu em algum momento do processo e não poderia ser utilizado.

Procurei também pela causa provável deste problema e listo abaixo as que achei mas plausíveis:
1) Havia usuários conectados ao Banco de Dados - talvez até alguma transação em andamento - no instante em que o backup estava sendo feito. O resultado é um backup inconsistente ou incompleto.
2) O backup gerou um arquivo muito grande e a tentativa de copiá-lo ou compactá-lo resultou num arquivo corrompido. Isto é particularmente válido se há setores ruins no disco rígido ou se você instalou o SQL Server num Virtual PC - o Virtual PC tem problemas em lidar com arquivos grandes e transferí-los para a máquina real pode corrompê-los.

Para eliminar a primeira possibilidade, pedi ao Cliente que executasse o comando de restauração com a opção de apenas verificar a integridade do backup. A sintaxe é a seguinte:
RESTORE VERIFYONLY
FROM DISK = 'C:\TEMP\DB.bak'

No meu caso, o próprio Cliente não conseguia visualisar o conteúdo nem restaurar o backup - o SQL dele reportava a mesma mensagem de erro. Então, não havia muito o que fazer a não ser pedir que fizesse outro backup para enviar.

Este é o ponto aqui: geralmente, faz-se o backup na ilusão de que qualquer problema com a base é só restaurá-lo. No entanto, negligencia-se um aspecto básico, que é certificar que o próprio backup feito está íntegro e poderá ser restaurado sem problemas. Use o comando RESTORE VERIFYONLY para fazer tal checagem.

Se foi verificado pelo RESTORE VERIFYONLY na estação original que o backup está íntegro mas o erro na restauração persiste, então é certo que o arquivo foi corrompido no processo de transferência para a máquina onde se quer restaurá-lo.

Caso a cópia tenha sido feita através de um Shared Folder do Virtual PC, copie novamente o arquivo, usando desta vez a rede normal no Virtual PC.

Caso a cópia tenha sido feita num pen-drive ou gravada num DVD, certifique-se que a mídia não está corrompida ou que a gravação foi feita com sucesso. Considere ainda a possibilidade de seu disco rígido estar corrompido.

19 de maio de 2009

Descobrindo as funções exportadas numa DLL

Imagine a seguinte situação: você tem uma DLL com várias funções exportadas, quer chamar essas funções a partir de um programa seu em Delphi ou C++ mas não consegue descobrir com que nome essas funções foram exportadas e, portanto, não sabe como mapear as funções no seu código.

Já me vi diversas vezes nessa situação, mesmo porque os módulos do ERP da ABC71 são DLLs e a cada operação em um módulo há uma função na DLL. A principal dificuldade é que em bibliotecas escritas em C++ os nomes das funções podem ter sofrido name mangling - toquei nesse assunto em um post sobre como chamar funções C++ no Delphi.

Pesquisando na Internet, localizei uma ferramenta muito boa para saber os nomes exatos das funções e até descobrir se a função desejada foi mesmo exportada. A ferramenta chama-se Dependency Walker. Reproduzo abaixo um pedaço da tela da aplicação que mostra a lista de funções de uma biblioteca que carreguei:
funções exportadasVeja que a primeira coluna indica se a função foi exportada como C, C++ ou através de um índice. Esse índice é o "ordinal" que aparece na segunda coluna. A última coluna traz o nome da função na forma como está exportado, isto é, trata-se do nome que procuramos para passar à função GetProcAddress (API do Windows) ou mapear no Delphi através da declaração external, como no exemplo abaixo.
function index_vlr (vlr: double; indice: smallint;
dt_base: TDateTime; precisao: byte) : double;cdecl;
external 'TPAUX.DLL' name '@index_vlr$qds16System@TDateTimes';

É importante observar que a ferramenta apenas lista os nomes das funções que estão exportadas na biblioteca e que ela não tem como levantar quantos são os parâmetros esperados nem seus tipos de dado - embora seja possível dar um bom chute quando o nome da função agrega name mangling. Portanto, é preciso conhecer bem a biblioteca ou ter uma documentação precisa para obter a informação sobre os parâmetros e tipo de retorno.

A função principal do Dependency Walker é, na verdade, indicar de quais DLLs a DLL indicada depende, isto é, se ela usa funções de outras DLLs - daí o nome de "navegador de dependências". Então, a ferramenta percorre a DLL, verificando quais dependências estáticas estão registradas. Depois, percorre cada uma das dependências encontradas fazendo a mesma coisa, até percorrer todas as DLLs envolvidas. Ao final, tem-se uma lista completa das bibliotecas necessárias para a DLL inicial funcionar corretamente. Isto é bastante útil para determinar quais bibliotecas devem ser incluídas num pacote de instalação, por exemplo.

Para resolver essa questão completamente, no entanto, pode ser necessário usar a opção "Profile". Nessa opção, você carrega um executável (.EXE) e o Dependency Walker tentará monitorar todas as DLL´s usadas pela aplicação, aí incluídas as cargas dinâmicas de DLL (através da função LoadLibrary).

Mais Informações
Dependency Walker, Chamando funções C++ no Delphi (parte 1 e parte 2).

18 de maio de 2009

Incluindo a assinatura digital no XML

No último post, mostrei como criar a assinatura digital de um documento XML. Para poder enviá-lo à Receita Federal é preciso ainda incluir essa assinatura digital no corpo do XML e é isso que vou fazer neste post.

A assinatura tem que ser incluida como um nó filho do elemento NFe, no mesmo nível do nó infNFe, conforme mostrado no excerto de XML de Nota Fiscal abaixo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
<Signature>
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe3508059978"
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>4aXU7m8rl14ALy6X...=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>Igq7eI/Fy3PyjjSW...=</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIGxDCCBaygAwIBAgIIN6q4...=</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</NFe>
</enviNFe>

Considere a variável xmlNFe do tipo XmlElement criada de acordo com o descrito no post sobre criação de nós no XML. Considere também uma instância da classe SignedXml chamada signedXml e preparada da forma descrita no post sobre assinatura de um Xml. Vou usá-las no exemplo a seguir.

Primeiro, criarei um nó para conter o elemento Signature. Depois, vou usar as propriedades SignedInfo e KeyInfo do signedXml para obter nós XML contendo respectivamente dados sobre a informação que foi assinada e sobre a chave usada nessa assinatura:
XmlElement xmlSignature = doc.CreateElement("Signature", "http://www.w3.org/2000/09/xmldsig#");
XmlElement xmlSignedInfo = signedXml.SignedInfo.GetXml();
XmlElement xmlKeyInfo = signedXml.KeyInfo.GetXml();

Note que o namespace passado para a criação do nó Signature é diferente daquele usado para criar as informações da Receita Federal que usei no post sobre a criação de nós. O namespace "http://www.w3.org/2000/09/xmldsig#" indica que vamos usar a sintaxe de assinatura descrita no link do W3C, conforme especificado na documentação da Receita Federal.

Além dos dados que já obtivemos, é necessário ainda acrescentar a assinatura em si como nó filho do elemento Signature. De acordo com a documentação, a assinatura - que é uma sequência de bytes - deve ser representada como um texto utilizando a codificação Base 64. A criação do nó SignatureValue, sua conversão para Base 64 e a adição do nó ao elemento Signature estão no trecho de código abaixo:
XmlElement xmlSignatureValue = doc.CreateElement("SignatureValue", xmlSignature.NamespaceURI);
string signBase64 = Convert.ToBase64String(signedXml.Signature.SignatureValue);
XmlText text = doc.CreateTextNode(signBase64);
xmlSignatureValue.AppendChild(text);
xmlSignature.AppendChild(xmlSignatureValue);

O trecho signedXml.Signature.SignatureValue é onde se recupera o valor da assinatura computada.

Agora, só falta importar os dados contidos nos elementos xmlSignedInfo e xmlKeyInfo obtidos no primeiro passo, acrescentado-os ao elemento NFe:
xmlSignature.AppendChild(doc.ImportNode(xmlSignedInfo, true));
xmlSignature.AppendChild (doc.ImportNode(xmlKeyInfo, true));
xmlNfe.AppendChild(xmlSignature);

Depois de acrescentar o xmlNfe ao documento XML final, deve-se transformar o XmlDocument doc em texto para ser enviado à Receita Federal.

15 de maio de 2009

Assinando um documento XML

Agora que tenho assentada uma terminologia XML e métodos para criar um XML (incluindo o cabeçalho) posso mostrar como assinar digitalmente um documento XML e incluir nele essa assinatura.

Antes, uma consideração importante: a Receita Federal só aceita a Nota Fiscal Eletrônica assinada com um Certificado Digital autorizado, isto é, não vale criar um certificado de teste (com o Certificate Creation Tool, por exemplo) e tentar enviar a nota usando ele. Alguns emissores de certificados autorizados: Correios, Serasa e Certisign.

A Receita Federal usou um subconjunto do padrão do W3C para estipular as regras de assinatura digital da NFe. Devem ser assinados todos os elementos infNFe de um lote XML de Notas Fiscais segundo essas regras. Considerando uma variável do tipo XmlDocument de nome doc, já montada com a estrutura correta da NFe, segue um exemplo em C# de como chegar a esse nó:
XmlNodeList ListInfNFe = doc.GetElementsByTagName("infNFe");

Há uma classe no namespace System.Security.Cryptography.Xml do framework .NET chamada SignedXml, que implementa o padrão W3C para assinatura de documentos e verificação de documentos assinados. O trecho de código abaixo exemplifica a configuração básica desta classe:
foreach (XmlElement infNFe in ListInfNFe)
{
string id = infNFe.Attributes.GetNamedItem("Id").Value;
signedXml = new SignedXml(infNFe);
signedXml.SigningKey = ObtemCertificado().PrivateKey;

O id obtido é um código calculado segundo regras estipuladas pela Receita Federal e serve para identificar cada Nota. Esse id é inserido como um atributo no elemento infNFe. Ele será usado mais adiante, quando for preciso referenciar o elemento infNFe na assinatura. A função ObtemCertificado é minha: ela usa os conceitos descritos no post sobre Certificate Store do Windows para obter um certificado válido. A propriedade SigningKey é a chave que será usada para calcular a assinatura; por isso, é atribuido a ela a chave privada do certificado obtido.

Parte da assinatura é dedicada a descrever o nó a partir do qual a assinatura foi feita e quais transformações esse nó e seus filhos sofreram antes da assinatura ser efetivamente calculada. De acordo com a documentação da Receita Federal, as transformações a serem aplicadas são duas:
1) a que indica que a assinatura é "envelopada", ou seja, o trecho assinado é incluído na assinatura. Aqui cabe um esclarecimento: o elemento infNFe não é o elemento assinado; ele na verdade é usado para calcular um valor chamado de DigestValue, este sim inserido no trecho assinado, de forma que qualquer alteração no conteúdo de infNFe ou de seus filhos resultará num DigestValue diferente e, por fim, a assinatura também não será válida.
2) a que indica que o XML deve ser colocado na forma canônica antes do processamento.

A classe Reference cuida dessa parte do processo, incluindo a identificação do nó infNFe e as transformações exigidas:
// Transformações p/ DigestValue da Nota
Reference reference = new Reference("#" + id);
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigC14NTransform());
signedXml.AddReference(reference);

Antes de computar a assinatura, falta configurar a parte que trata das informações a respeito do certificado digital utilizado. É com base nesses dados que a Receita Federal consegue validar a assinatura e atestar que nenhuma informação da Nota foi modificada depois que o emissor da Nota Fiscal a assinou. Devemos incluir uma cláusula com os dados do certificado:
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(ObtemCertificado()));
signedXml.KeyInfo = keyInfo;

Agora, só falta computar a assinatura:
signedXml.ComputeSignature();

Neste ponto, a variável signedXml contém todos os dados para criar o elemento Signature que deve ser incluído na NFe antes do envio à Receita Federal. Mostro no próximo post como fazer isso.

14 de maio de 2009

Trabalhando com XML - parte 4

Para finalizar essa série sobre a criação de XML através da implementação DOM do C#, falo aqui da exportação desse DOM para o formato texto.

A chave para a exportação é uma classe chamada XmlTextWriter que fica no namespace System.Xml. Ela possui um construtor que aceita dois parâmetros : um stream onde a gravação deve ser feita e o Encoding responsável pela gravação. Stream é uma classe abstrata que apenas define uma interface para auxiliar a gravação e a leitura de bytes; por isso, devemos usar uma das heranças que faça efetivamente o trabalho.

A declaração a seguir é um exemplo que usa a herança chamada MemoryStream, que faz a leitura e gravação em memória:
Encoding enc = Encoding.UTF8;
MemoryStream ms = new MemoryStream();
XmlTextWriter writer = new XmlTextWriter (ms, enc);

No contexto que utilizei esse código na ABC71, usar o MemoryStream era a opção mais conveniente. No entanto, há um construtor do XmlTextWriter que aceita um nome de arquivo como parâmetro. Nesse caso, o texto resultante será enviado diretamente para o arquivo.

Quando criei o cabeçalho para o XML no post anterior, informei o Encoding UTF-8, conforme especifica a documentação da Receita Federal. Porque, então, tenho que informá-lo novamente na gravação ? A questão é que a classe XmlTextWriter usará o Encoding para "transformar" os textos contidos no DOM e, se não o informarmos, um encoding padrão será utilizado para tal "transformação". Isto pode gerar caracteres incorretos, além de fazer com que o cabeçalho exportado contenha o nome do Encoding padrão ao invés daquele que informamos na montagem do XML, tornando-o inválido para a Receita Federal.

Então, informando novamente o Encoding para forçar o XmlTextWriter a gravar o cabeçalho com o valor que precisamos. Simultaneamente, nenhuma transformação é aplicada no conteúdo, preservando a integridade do XML. A classe Encoding usada no exemplo fica no namespace System.Text.

O próximo passo é configurar o XmlTextWriter para que o resultado atenda nossas necessidades. Veja o exemplo:
writer.Formatting = Formatting.Indented;
writer.Indentation = 2;
writer.Namespaces = true;

A primeira linha indica que a gravação deve ser feita de forma estruturada, identando os elementos do XML. A segunda estabelece quantos espaços em branco devem ser usados para identar os elementos. Se não quiser usar identação para que o tamanho do texto exportado seja menor, use Formatting.None na propriedade Formatting: todo o XML será gerado sem quebra de linhas e sem espaços em branco extras. Por fim, indico que há namepaces na estrutura e que eles devem constar no texto exportado. Na verdade, sem essa configuração o .NET levantará uma exceção (Exception) e não executará a exportação pois, se há namespaces informados no DOM, eles devem mesmo constar no texto resultante.

Para finalizar a exportação, falta ainda chamar o comando específico. Estou considerando que a variável doc é do tipo XmlDocument e contém a estrutura XML já montada, nos moldes descritos nos outros posts sobre XML (veja parte 2 e parte 3).
doc.WriteTo(writer);
writer.Flush();
writer.Close();

O resultado disso tudo é que o MemoryStream de nome ms agora contém todo o XML em formato texto, pronto para ser enviado, por exemplo, no corpo de um email ou exibido em tela.

13 de maio de 2009

Trabalhando com XML - parte 3

Quando fiz o post sobre criar um XML usando C#, ficou faltando mostrar a parte de como criar o cabeçalho do XML dentro da estrutura do DOM. Vou mostrar isso neste post e, para tanto, volto a reproduzir o mesmo XML dos outros dois posts sobre o assunto e vou usá-lo como exemplo :
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>

Há uma classe no namespace System.Xml do framework do .Net responsável pela criação do cabeçalho do XML. O nome dessa classe é XmlDeclaration. Considerando a variável doc do tipo XmlDocument criada no post anterior sobre XML, a inclusão do cabeçalho deve ser feita como no exemplo abaixo :
XmlDeclaration decl;
decl = doc.CreateXmlDeclaration("1.0", System.Text.Encoding.UTF8.HeaderName, "");
doc.InsertBefore(decl, doc.DocumentElement);

Os parâmetros para a função CreateXmlDeclaration são a versão do XML (normalmente, o valor "1.0"), em seguida o tipo de codificação a ser utilizado (no exemplo, obtive o nome do encoding UTF-8 definido pelo próprio framework) e, por fim, o valor para o atributo standalone. Como no nosso XML exemplo o standalone não aparece, passei um texto vazio, instruindo a criação do cabeçalho sem essa informação.

A última linha do código faz com que a declaração de cabeçalho seja acrescentada na estrutura DOM do XML imediamente antes do nó raiz. O nó raiz é aqui representado pela propriedade DocumentElement da variável doc (que é um XmlElement).

Nos próximos posts, mostro como exportar essa estrutura DOM para o formato texto de forma que possa ser usado para gravar um arquivo com o conteúdo XML; devo falar também sobre a navegação pelos nós de um XML dentro do DOM e, finalmente, como assinar o XML para enviá-lo à Receita Federal.

11 de maio de 2009

Trabalhando com XML - parte 2

Como eu havia informado no post anterior sobre XML, vou mostrar aqui como criar um documento XML usando C#. O XML a ser criado será um fragmento do layout exigido pela Receita Federal para o envio de Notas Fiscais Eletrônicas; para facilitar, reproduzo novamente aqui o documento a ser criado :
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>
Para iniciar, o pacote onde estão as classes para manipulação de XML através de DOM é o System.Xml. Portanto, é preciso informar isso no início do fonte.

using System.Xml;

O documento XML em si é representado por uma instância da classe XmlDocument. Também vamos usar bastante o namespace definido pela Receita Federal para suas estruturas, então, vou declarar uma constante para facilitar. Inclua as linhas abaixo na sua classe :
static string NFeNamespace = "http://www.portalfiscal.inf.br/nfe";
private XmlDocument doc = new XmlDocument();

O primeiro passo na criação é criar o nó raiz. No nosso exemplo, é como segue :
XmlElement raiz, no, noNFe, noInfNFe;
XmlAttribute att;
raiz = doc.CreateElement("enviNFe", NFeNamespace);
// Atributos do nó de enviNFe
att = doc.CreateAttribute("versao");
att.Value = "1.10";
raiz.Attributes.Append(att);

Note que criei manualmente apenas o atributo versao enquanto o xmlns (namespace) é colocado automaticamente pelo parâmetro passado ao CreateElement. Todos os "nós filhos" compartilharão essa informação de namespace.

Vamos criar agora o elemento idLote, que possui um valor associado :
XmlText noText;
no = doc.CreateElement("idLote", NFeNamespace);
noText = doc.CreateTextNode("71");
no.AppendChild(noText);
// "Nó" é filho de raiz :
raiz.AppendChild(no);

Observe que continua imprescindível passar o namespace na criação do nó idLote; se não for informado, subentende-se que um novo namespace (vazio) deve ser usado.

As mesmas regras vistas até agora valem para criar o nó NFe e o nó infNFe e seus filhos:
// infNFE
noInfNFe = doc.CreateElement("infNFe", NFeNamespace);
att = doc.CreateAttribute("Id");
att.Value = "NFe3508059978";
noInfNFe.Attributes.Append(att);

att = doc.CreateAttribute("versao");
att.Value = "1.10";
noInfNFe.Attributes.Append(att);

// Filhos do infNFE
no = doc.CreateElement("cUF", NFeNamespace);
noText = doc.CreateTextNode("35");
no.AppendChild(noText);
noInfNFe.AppendChild(no);

no = doc.CreateElement("cNF", NFeNamespace);
noText = doc.CreateTextNode("518005127");
no.AppendChild(noText);
noInfNFe.AppendChild(no);

// *** Inclua os demais campos aqui ....

// Hierarquia de nós

noNFe = doc.CreateElement("NFe", NFeNamespace);
noNFe.AppendChild(noInfNFe);
raiz.AppendChild(noNFe);

Para finalizar, é preciso ainda informar ao XmlDocument quem é seu nó raiz:
doc.AppendChild (raiz);

Para não me estender muito, publico no próximo post como incluir o cabeçalho para o XML e como transformar a estrutura DOM num texto que possa ser salvo em arquivo ou transmitido como parâmetro num WebService.

9 de maio de 2009

Trabalhando com XML

A Extensible Markup Language - ou simplesmente XML - já é uma tecnologia madura, bastante adotada devido à sua flexibilidade em estruturar dados de uma maneira que permite facilmente intercambiar informações numa ampla gama de situações.

Porquê, então, postar um texto sobre isso se já é uma tecnologia tão amplamente dominada e divulgada ? Primeiro, por que é interessante ter onde encontrar informações básicas, mesmo para aqueles que já usam - de vez em quando dá um branco na gente ! Segundo, eu pretendo postar outros textos envolvendo XML e gostaria de usar neles uma terminologia que fosse bem conhecida. Portanto, vou descrever essa terminologia aqui - na forma como a entendo - e me referir a ela nos outros posts.

Em princípio, tenho em mente postar algo a respeito de como criar a assinatura digital de um XML para a Nota Fiscal Eletrônica (NFe) e como anexar essa assinatura ao documento a ser enviado para a Receita Federal. Considere, então o XML simplificado (bastante !) da NFe que reproduzo abaixo :
<?xml version="1.0"encoding="UTF-8" ?>
<enviNFe versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978"versao="1.10">
 <cUF>35</cUF>
 <cNF>518005127</cNF>
 <natOp>Venda a vista</natOp>
 <mod>55</mod>
 <serie>1</serie>
 <dEmi>2008-05-06</dEmi>
 <tpAmb>2</tpAmb>
</infNFe>
 </NFe>
  </enviNFe>

Declaração XML
A primeira linha do XML é um cabeçalho indicando que se trata de um documento XML e que os textos embutidos nele respeitam a codificação UTF-8. Obs: É por essa razão que o XML da NFe que vai para a Receita Federal não pode conter a acentuação típica da língua portuguesa - para isso, a codificação mais apropriada teria que ser a ISO-8859-1.

Elementos ou Tags
Os elementos constituem a fundamentação do XML, moldando a estrutura de dados contida no documento. Eles delimitam as informações do documento e são distinguidos por aparecerem entre os caracteres < e >. No XML reproduzido acima, enviNFe e NFe são exemplos de elementos. Os dados são incluídos no documento entre um elemento de abertura e seu correspondente fechamento, sendo que o elemento de fechamento tem uma barra (/) antes do nome para diferenciá-lo da abertura.Exemplo :
 <dEmi>2008-05-06</dEmi>

Atributos
São propriedades adicionadas à elementos de abertura. Eles são compostos de um nome e de um valor ligados pelo sinal de igual (=). Os valores são delimitados por aspas ("). No XML do exemplo, o elemento infNFe possui 2 atributos : Id (cujo valor é NFe3508059978) e versao (de valor 1.10).

Nós ou Nodes
A representação computacional mais comum para um documento XML é através de DOM - Document Object Model, que permite acessar e manipular os elementos, atributos, valores, etc. através de uma estrutura de árvore. Neste contexto, os elementos também são chamados "nós" e os termos "nó pai" e "nó filho" expressam a relação de hierarquia entre os elementos, isto é, que elemento está contido em qual. Assim, no exemplo acima, os elementos cNF e serie são "nós filhos" de infNFe. Por sua vez, NFe é o "nó pai" de infNFe.

Nó raiz ou Root Node
A hierarquia de um documento XML sempre possui um nó de nível mais alto que, direta ou indiretamente, é "nó pai" de todos os outros. Esse ponto de entrada para navegação no documento XML é chamado "nó raiz" e no exemplo acima é o nó enviNFe

XML Namespace
Com o uso crescente de XML para troca de informações, muitos ramos de negócio, empresas e entidades governamentais construiram conjuntos de tags (elementos) que acabaram por se tornar padrões. São, portanto, bem estabelecidos e há softwares capazes de reconhecê-los e tratá-los. Se for necessário incluir informações de mais de um desses padrões num mesmo documento XML, é possível que haja choque entre as diferentes semânticas (nomes e significados).
Para permitir que uma aplicação reconheça qual conjunto de tags deve ser usado para tratar um determinado ponto do XML foi criado o conceito de namespace. São atributos especiais que são incluídos num elemento do XML de forma que uma aplicação com o conhecimento apropriado saberá que estrutura esperar ao trabalhar com os "nós filhos" desse elemento.
O nome do atributo que indica o namespace é xmlns.
No exemplo da Nota Fiscal Eletrônica, a Receita Federal estruturou as informações sob o namespace http://www.portalfiscal.inf.br/nfe e ele deve constar dos documentos XML a serem enviados.

No próximo post, incluirei exemplo C# de como construir o XML mostrado aqui.