30 de dezembro de 2011

Preparando a execução de um programa em desktop remoto

A adoção maciça da internet e os benefícios associados a ela têm levado as empresas a optar cada vez com mais frequência por programas que possam ser executados nessa infraestrutura. O principal benefício aqui está relacionado aos custos já que os desktops não precisam ser tão potentes - basta que tenham acesso à internet para executarem esses programas remotos - e a manutenção dos equipamentos passa ser centralizada.

Por outro lado, nem todos os programas em uso pelas empresas operam nativamente na internet. Seja por que são programas legados ou por que o fabricamente ainda não os preparou para o ambiente web ou simplesmente porque há muitos programas e não houve tempo hábil para uma conversão em massa. Por isso, programas como o RDS (Remote Desktop Services) da Microsoft e as soluções da Citrix para virtualização de desktops têm sido bastante adotados.

Essas soluções permitem que um usuário execute remotamente os programas que necessita, através de um desktop virtual acessível via rede local ou internet. Com isso, o hardware mais robusto fica centralizado num servidor e pode ser compartilhado por diversos usuários simultâneos.

Nesse ambiente, diversos usuários podem estar executando sua aplicação no mesmo computador remoto, compartilhando recursos como a pasta de trabalho, memória mapeada, disco, etc. Desse modo, as diferentes execuções simultâneas do programa podem conflitar entre si. Decisões baseadas no nome do computador, seu endereço IP ou outra informação de hardware também podem causar problemas já que todos os usuários remotos lerão os mesmos valores. A ABC71, por exemplo, usa o nome do computador onde nosso ERP está em execução como parte do controle de licenças.

Em Windows, normalmente as soluções de desktop virtual se baseiam nos serviços de Remote Desktop (antes chamado de Terminal Services), razão pela qual podemos usar a Remote Desktop Services API para preparar nossas aplicações para que rodem nesse ambiente. Essa API permite resgatar e modificar informações sobre um usuário específico, além de executar tarefas relativas à administração do serviço.

Para começar, temos que determinar se o programa está executando num desktop convencional ou num virtual. Isso pode ser conseguido com uma chamada à função GetSystemMetrics. Saber que estou numa sessão remota me permite, por exemplo, ajustar uma pasta de trabalho coerente para cada execução distinta do meu programa.
function TForm1.IsRemoteSession : boolean;
var res : integer;
begin
res := GetSystemMetrics (SM_REMOTESESSION);
Result := (res <> 0);
end;
A função GetSystemMetrics é um curinga da API do Windows; com ela é possível recuperar uma porção de parâmetros do ambiente operacional. Chamada com SM_REMOTESESSION, ela retorna um valor diferente de zero se o programa estiver num desktop virtual.

Muitas informações valiosas sobre a sessão remota em andamento podem ser obtidas através da função WTSQuerySessionInformation. No exemplo abaixo, eu a utilizo para recuperar o nome do computador que está acessando o desktop remoto:
procedure TForm1.GetSessionInfo;
var lSessionId: DWORD;
lBuffer : PChar;
lBytesReturned : DWORD;
lStationName : String;
begin
lSessionId := 0;

{ Descobre a identificação da sessão do usuário com base na identificação do programa no Windows }
if not ProcessIdToSessionId (GetCurrentProcessId (), DWORD(@lSessionId)) then
raise Exception.Create ('Não foi possível obter Remote SessoinId');

lBuffer := Nil;
lBytesReturned := 0;
lStationName := '';

{ Obtem nome da máquina Client }
if (WTSQuerySessionInformation (WTS_CURRENT_SERVER_HANDLE,
lSessionId,
WTSClientName,
lBuffer,
lBytesReturned))
then
lStationName := String(lBuffer)
else
raise Exception.Create ('Não foi possível obter o nome da estação');

{ Libera a memória alocada automaticamente }
WTSFreeMemory (lBuffer);

{ ... }
end;
Vamos por partes. Para recuperar as informações de uma sessão remota, primeiro temos que identificar essa sessão. A função ProcessIdToSessionId da API do Windows nos fornece isso, mapeando o Process ID de nosso programa para a correspondente identificação da sessão remota onde ele está executando.

O passo seguinte é chamar a função WTSQuerySessionInformation para levantar as informações desejadas. Essa função recebe 5 parâmetros. O primeiro é um handle para o servidor da sessão remota. No exemplo, passei a constante WTS_CURRENT_SERVER_HANDLE para indicar que quero informações sobre o servidor atual. Poderia ter usado WTSOpenServer para abrir outro servidor remoto.

O segundo parâmetro é a identificação da sessão que obtivemos no primeiro passo. O parâmetro seguinte é o que determina qual informação será recuperada. Os valores permitidos são os listados no enumerado WTS_INFO_CLASS. No exemplo, usei WTSClientName para obter o nome do computador do usuário que diparou o acesso remoto; a lista de informações recuperáveis inclui o endereço IP desse mesmo computador, a pasta de trabalho remota (no servidor), o nome do usuário e informações sobre o uso da sessão e sobre a Client, dentre outras.

Os dois últimos parâmetros são, respectivamente, um ponteiro para a área que receberá a informação solicitada e a quantidade de bytes que essa informação está ocupando. Como ambos são calculados pela função WTSQuerySessionInformation, não é preciso alocar previamente a memória para eles. No entanto, é nossa responsabilidade liberar a memória alocada usando a função WTSFreeMemory, como mostra o passo final do código do exemplo.

Se tivesse usado GetComputerName, eu obteria o nome do computador remoto, isto é, o servidor onde o programa está efetivamente sendo executado. A questão é que o mesmo nome seria retornado para qualquer usuário que acesse um desktop remoto nesse servidor ...

A lista de funções da Remote Desktop Services API inclui ainda formas de administrar o RDS tais como iniciar e encerrar sessões, inventariar as sessões ativas, modificar configurações de uma sessão, etc.

21 de dezembro de 2011

Design Patterns com Delphi: Template - Parte II

No meu último post, introduzi o conceito do Design Pattern comportamental Template. Em resumo, esse padrão estabelece uma sequência de passos para a execução de uma determinada tarefa, permitindo que sejam criadas heranças que implementem suas próprias versões de um ou mais dos passos, de acordo com a necessidade.

Neste post, eu sugiro uma implementação em Delphi do exemplo envolvendo criação de infraestruturas que usei para apresentar o Template no outro post. Para facilitar o entendimento, reproduzo novamente abaixo o diagrama UML que modela a infraestrutura da transações de negócio usada como exemplo:
Diagrama UML para o padrão Template
Como dá para intuir a partir da simplicidade do diagrama, a codificação também não é complexa. A primeira providência é criar a classe que define o formato geral do algorítimo, os passos que o comporão, em qual ordem esses passos devem ser chamados para completar a tarefa e quais deles poderão ser particularizados em heranças do algorítimo. Esta classe - chamada de Abstract Class - servirá, portanto, de base para as demais implementações do mesmo algorítimo. No nosso exemplo, é a classe TWTransacao que tem essa responsabilidade:
TWTransacao=class
protected
_Erros: TWErros; { Lista de erros da transação }

procedure BeginTrans;
procedure CommitTrans;
procedure RollbackTrans;

function ConsisteDados : boolean;virtual;
function GravaDados : integer;virtual;

public
function ExecutarTransacao : integer;
end;

{ ... }

function TWTransacao.ConsisteDados : boolean;
begin
Result := true;
end;

function TWTransacao.GravaDados : integer;
begin
Result := 0;
end;

function TWTransacao.ExecutarTransacao : integer;
begin
Result := -99;

Try
_Erros.Clear;

{ Inicia uma transação com o banco de dados }
BeginTrans;

{ Se estiver tudo OK com os dados, gravá-los no banco }
if (ConsisteDados) then
Result := GravaDados;

{ Efetiva a transação }
CommitTrans;
Except
{ Se houve algum erro, aborta a transação com o banco de dados}
RollbackTrans;
Result := -100;
end;
end;

Observe que há apenas uma função pública : ExecutarTransacao. As partes do programa que desejarem executar uma transação de negócio precisam ter acesso apenas a ela, não importando quais ou quantos são os passos necessários para executá-la.

Em contraste, tanto os passos do algorítimo que poderão ser estendidos pelas heranças quanto os métodos internos auxiliares foram colocados na área protegida, escondendo os detalhes para códigos externos. Aqui, as funções ConsisteDados e GravaDados poderiam ter sido declaradas como abstratas, o que forçaria as heranças a fornecer-lhes uma implementação. No entanto, não é mandatório que elas tenham essa característica, de modo que o exemplo ilustra como a classe base do Template pode providenciar uma versão padrão dos passos do algorítimo, mesmo que sejam versões bem simples. Elas poderiam, por exemplo, registrar um log da execução.

As funções relativas a transações com o banco de dados (BeginTrans, CommitTrans e RollbackTrans) apenas repassam suas respectivas chamadas diretamente ao banco; por isso, suas implementações foram omitidas no quadro acima.

Já temos a classe base, podemos então codificar as heranças previstas para transações de produtos, pedidos de venda e notas fiscais:
TWTrnProduto = class(TWTransacao)
private
_prod : TWProduto;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

TWTrnPedidoVenda = class(TWTransacao)
private
_Pedido : TWPedidoVenda;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

TWTrnNotaFiscal = class(TWTransacao)
private
_Nota : TWNotaFiscal;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

{ ... }

function TWTrnProduto.ConsisteDados : boolean;
begin
Result := inherited ConsisteDados;

if (TWProduto.ExisteProd (_Prod) ) then
begin
Result := false;
_Erros.Add ('Produto já existe');
end;
end;

function TWTrnProduto.GravaDados : integer;
begin
inherited GravaDados;

_Produto.Insert();
end;

function TWTrnPedidoVenda.ConsisteDados : boolean;
begin
Result := inherited ConsisteDados;

if (TWPedidoVenda.ExistePedido (_Pedido) ) then
begin
Result := false;
_Erros.Add ('Já existe pedido com esse número.');
end;

if (not _Pedido.VerifSaldoProdutos) then
Result := false;

if (not _Pedido.VerifDadosComerciais) then
Result := false;

{ Se detectou algum erro, reporta para a transação }
_Erros.Add(_Pedido.Erros);
end;

function TWTrnPedidoVenda.GravaDados : integer;
begin
inherited GravaDados;

{ Insere o pedido, atualiza saldo dos produtos e faz outros ajustes }
_Pedido.Insert ();
end;
Vemos no quadro anterior que as transações reais são heranças da classe de transação básica e que elas fornecem suas próprias versões de passos do algorítimo base. Com isso, elas garantem que suas próprias regras de negócio sejam respeitadas, enquanto são mantidas a ordem de chamada dos passos e outras regras estipuladas pela classe TWTransacao. A transação de nota fiscal foi omitida no quadro mas a ideia dela é bastante similar à de pedidos de venda.

Para completar o exemplo, falta a classe Client, isto é, aquela parte do código que utilizará o nosso template.
TWTela = class
protected
_Trn : TWTransacao;

public
{ ... }
procedure ExecutarTransacao(); end;

{ ... }

procedure TWTela.ExecutarTransacao();
begin
_Trn := ObtemTransacao ();
_Trn.ExecutarTransacao;
end;
Como mostra o quadro, o código da classe TWTela acaba ficando extremamente simples. Aqui, o maior problema talvez seja decidir o melhor meio de obter uma instância da transação correta. Dependendo das circunstâncias, podemos optar pelo método Factory ou organizar as telas usando o padrão Mediator - o que permitiria cada tela instanciar diretamente a transação adequada.

13 de dezembro de 2011

Design Patterns com Delphi: Template - Parte I

Criar uma infraestrutura de software é padronizar a forma como as coisas acontececerão dentro de um programa, estabelecendo regras e ditando as mensagens que cada parte do código está preparada para receber. Por exemplo, uma infraestrutura para realizar transações de negócio dentro do sistema pode ser descrita simplificadamente em poucos passos : abertura de transação com o banco de dados; consistência das informações contidas no(s) objeto(s) de negócio envolvido(s); persistência das informações nas respectivas tabelas; encerramento da transação. Todo objeto com capacidade de realizar uma transação terá necessariamente que implementar esses passos.

Se cada Caso de Uso implementar uma versão independente dos passos, mudanças nessa sequência teriam que ser replicadas em todas as implementações. O alvo do Design Pattern comportamental Template é justamente situações como esta, onde um algorítimo é definido de modo centralizado, moldando a forma de trabalho a ser seguida por todas as classes de objeto que desejarem implementá-lo. Um ou mais passos podem ser sobrepostos pelas heranças, permitindo que elas implementem comportamentos diferenciados quando for necessário.

Essa idéia é similar à do Pattern Strategy. A diferença mais importante entre ambos é que o Template permite que heranças alterem partes de um algorítimo enquanto no Strategy temos que optar por uma das versões de um algorítimo bem conhecido e executar todo o trabalho com esse algorítimo.

O quadro a seguir traz um diagrama UML mostrando as relações esperadas entre as classes participantes da solução para o Template. O cenário abordado é o da transação de negócio citado no primeiro parágrafo deste post:
Diagrama UML para o padrão Template
Neste exemplo, nem todos os métodos são virtuais. São declarados como virtuais apenas aqueles que poderão (ou deverão) ter uma implementação distinta nas classes herdadas. Como podemos ver, o algorítimo é decomposto em métodos mais granulares que podem ser sobrepostos quando ncessários. Neste contexto, tais métodos são chamados de operações primitivas.

No exemplo, o método ExecutarTransacao é responsável por chamar cada operação primitiva no tempo correto, moldando efetivamente o algorítimo que deve ser executado por cada classe da hierarquia. Este método é chamado de Template (modelo).

Formalmente, as classes participantes de uma solução para o pattern Template recebem os seguintes nomes:
É denominada Abstract Class a classe base que define as operações primitivas que estarão disponíveis para o algorítimo. Ela também define o esqueleto do algorítimo ao implementar o método Template, chamando cada operação primitiva na ordem correta. No diagrama acima, este é o papel da classe TWTransacao. É importante ressaltar que, apesar do nome, essa classe não precisa ser obrigatoriamente abstrata; se for adequado, a Abstract Class pode prover versões básicas de todas as operações primitivas.

As classes que provêm versões particulares de uma ou mais das operações primitivas são chamadas Concrete Class. Isso permite-lhes realizar ações específicas dentro dos passos básicos que formam o algorítimo determinado pela classe base. No exemplo, as classes TWTrnProduto, TWTrnNotaFiscal e TWTrnPedidoVenda são desse tipo. Elas fornecem suas próprias versões dos métodos que consistem e gravam os dados relativos, respectivamente, às transações para inserir um novo produto, criar uma nota fiscal e criar um pedido de venda.

O Client é qualquer parte do sistema que esteja apta a disparar o algorítimo definido na classe base. A classe TWTela do diagrama de exemplo detem esse papel.
No próximo post, eu apresento uma sugestão de como implementar o exemplo proposto usando Delphi.

Mais Informações
Posts sobre Design Patterns

8 de dezembro de 2011

HTML5 criará novos desafios para os profissionais de Segurança em TI

Agora parece que a Adobe se rendeu em definitivo ao avanço do HTML5, que nasceu com a missão de flexibilizar o desenvolvimento para aplicações Web, eliminando a necessidade de instalação de plugins de terceiros - como o próprio Flash da Adobe. Em post recente no blog do Flex, a empresa recomenda que se opte pelo HTML5 pois essa tecnologia prevalecerá no longo prazo.

Eles também anunciaram que não desenvolverão o plugin do Flash para versões futuras dos navegadores de dispositivos móveis. É claro que não pretendem deixar de criar ferramentas e estão usando o know-how que têm e a base fiel de clientes conquistados para se manterem relevantes. Ao que parece, a ideia é criar novas ferramentas mantendo a essência da filosofia de desenvolvimento das ferramentas atuais - só que produzindo HTML5 e Javascript ao invés do código próprietário do Flahs. Veja aqui o comunicado publicado no site da empresa, revelando os planos para o futuro do Flash.

Com isso, a euforia com o HTML5 já começa a assentar, dando lugar ao pragmatismo. Os especialistas começam a fazer avaliações mais realistas sobre a nova tecnologia e levantam questões até aqui deixadas em segundo plano. A expectativa de adoção dela vem suscitando, por exemplo, questões relativas à segurança, conforme mostra o artigo abaixo, cujo original (em inglês) foi publicado pela InfoWorld:

A adoção do HTML5 abrirá espaço para a criação de uma vasta gama de novas aplicações Web, mas poderá também introduzir novos desafios para os profissionais de segurança nas empresas, de acordo com a firma de segurança Sophos.

Em suas previsões sobre segurança para 2012, a Sophos identifica como principais riscos as novas tecnologias para Web e redes - incluindo HTML5. Enquanto essas tecnologias introduzem novos recursos impressionantes para desenvolvimento de aplicações Web ricas, elas também introduzem novas formas de ataque, explicou a companhia.

O HTML4 supriu conteúdo na Web por muitos anos, mas é uma linguagem de programação muito básica, de modo que os desenvolvedores tiveram que complementá-lo com add-ons como o JavaScript, Flash e Google Gears. Estes add-ons têm frequentemente muitas vulnerabilidades, tornando o sistema inseguro como um todo.

HTML5 remove a necessidade da maioria dos add-ons, porque é uma linguagem mais sofisticada e vem com um banco de dados completo, permitindo que os usuários armazenem gigas de informação. Então, por exemplo, você pode criar uma animação completa, realidade virtual em 3D ou guardar aplicações completas dentro do navegador.

De acordo com James Lyne, tecnólogo sênior na Sophos, isso chega muito perto da visão in-client originalmente associada à computação na nuvem. No entanto, armazenar dados dentro do navegador torna o navegador em si um alvo para os cyber criminosos.

"Tradicionalmente, o navegador é um meio para que cyber criminosos obtenham acesso ao seu PC. Agora, eles tentarão atacar o próprio navegador para roubar seus dados", diz Lyne.

A execução em sandbox do HTML5 também aumenta os riscos de "clickjacking" (induzir um usuário Web a revelar informações confidentiais ou tomar o controle do computador desse usuário quando ele clica num link aparentemente inócuo) já que as páginas Web não são mais capazes de identificar de onde os comandos estão vindo.

"Todo aquele código escrito pelos desenvolvedores para prevenir que aplicações Web sejam capturadas por alguém não autorizado ou sofram clickjacking agora não funcionará", diz Lyne. "Implementaram um recurso para aumentar a segurança mas inadivertidamente desativaram um outro mais importante."

Além disso, HTML levanta novas questões sobre cookies, que poderão tornar redundantes as recomendações para remoção deles após um determinado período.

"O HTML5 poderia até ter novos super-cookies," diz Lyne. "Se um site não for codificado de forma apropriada, os caras maus poderão compilar uma enorme base de dados com as URLs por onde você navegou e rastrear as informações que você eventualmente tenha fornecido nelas. Eles poderão capturar uma grande quantidade de dados."

Apesar desses problemas pontenciais, Lyne diz que adotar o HTML5 trará muitos benefícios para a segurança. Haverá redução nos riscos relacionados aos add-ons, bem como mais segurançã ao facilitar validação de dados no lado client das aplicações. Também haverão bibliotecas capazes de lidar com questões como SQL injection.

"Ao longo do tempo, o HTML5 vai resolver muitos dos problemas que temos, mas, como qualquer nova tencologia, há uma tendência a uma regressão num primeiro momemto," diz ele. "De maneira geral, deveríamos acelerar na direção do HTML5 porque Flash tem sido traumático e as novas aplicações Web estão muito legais - só temos que nos certificar de não estarmos adotando um pesadelo."

4 de novembro de 2011

Trabalhando com emails em uma caixa postal usando IMAP4 em Delphi

Mostrei num outro post aqui no blog como ter acesso aos emails numa caixa postal através do protocolo POP3 no Delphi. O POP3 é um protocolo amplamente adotado para permitir o acesso remoto aos emails de uma caixa postal mas ele é bastante limitado. Seus recursos estão restritos basicamente à manipulação das mensagens existentes na caixa postal, permitindo lê-las, apagá-las ou arquivá-las.

Se precisar de recursos mais avançados - tais como gerenciar pastas, localizar uma mensagem pesquisando o texto dela, compartilhar caixas postais com um grupo de trabalho, entre outras - temos que apelar para o IMAP4, um protocolo mais completo.

O primeiro passo para trabalhar com o IMAP4, assim como acontece com os outros protocolos baseados no TCP/IP, é estabelecer uma conexão com o servidor. Aqui também isso exige que alguns parâmetros sejam informados, tais como o endereço do servidor, a porta onde ele estará aguardando requisições e um usuário com permissão de acesso ao serviço, bem como sua respectiva senha:
var iMap TIdIMAP4;
begin
iMap := TIdIMAP4.Create (Nil);

{ Atribui os dados fornecidos pelo usuário e tenta se conectar }
iMap.Host := _ServerIMAP; { Endereço do servidor }
iMap.Port := _PortaIMAP; { Porta que o servidor está ouvindo }
iMap.Username := _UserIMAP;
iMap.Password := _SenhaIMAP;

iMap.Connect(100000);

{ Realiza aqui algumas tarefas com o IMAP conectado}

{ Descarta a conexão e outros recursos qdo não forem mais necessários }
iMap.Disconnect();

iMap.Free ();
end;
Uma grande diferença em relação ao funcionamento do POP3 é que o IMAP4 pode gerenciar várias caixas postais numa única conexão. Do ponto de vista do usuário, cada caixa postal é normalmente encarada como uma pasta. Como os comandos do IMAP4 são direcionados a uma caixa postal, isso nos obriga a selecionar uma antes de submeter os comandos.

Ao menos a caixa chamada INBOX (caixa de entrada) deve existir mas você pode levantar uma lista completa com todos os nomes das caixas que existem na conta usando a função ListMailBoxes. Um usuário pode, então, ser apresentado a essa lista para que escolha a caixa mais apropriada para as necessidades dele. Considerando o iMap criado e conectado no quadro anterior, fazer o levantamento é bem simples:
var lMaixBoxes : TStringList;
lCaixaSel : String;
begin
{ ... }
lMaixBoxes := TStringList.Create();

iMap.ListMailBoxes(lMaixBoxes);

{ Carrega a lista na tela para o usuário escolher uma delas. }
lCaixaSel := SelecionaCaixaPostal (lMaixBoxes);

lMailBoxes.Free();

if (not iMap.SelectMailBox (lCaixaSel) ) then
raise Exception.Create('Erro na seleção da caixa postal');
{ ... }
end;
Uma vez que a caixa foi escolhida, precisamos repassá-la ao iMap para que possamos operar com a caixa específica. É o que faz a chamada à função SelectMailBox no código acima. Essa chamada faz com que o TidIMAP4 alimente sua propriedade MailBox com um resumo do estado atual da caixa selecionada, disponibilizando informações como a quantidade total de mensagens e quantas dessas permanecem não lidas, entre outras informações.

Com uma caixa selecionada, podemos realizar operações como pesquisar os emails contidos nela. Nesse quesito, o IMAP4 é muito mais flexível que o POP3 já que nos fornece uma opção para filtrar as mensagens desejadas usando a estrutura TIdIMAP4SearchRec. O principal recurso dessa estrutura é o campo SearchKey. É esse campo que determina quais critérios devem ser respeitados na consulta e, portanto, quais dos outros campos da estrutura serão considerados quando a consulta for submetida pela função SearchMailBox. O exemplo abaixo usa o iMap conectado para procurar todas as mensagens que ainda não foram lidas na caixa postal atual e varre a lista encontrada, recuperando cada uma delas.
var rec : array of TIdIMAP4SearchRec;
TheMsg : TIdMessage;
contMsg, i, idMsg : Integer;
begin
{ Procura por emails do tipo skUnseen - ou seja, somente os "não lidos" }
SetLength(rec,1);
rec[0].SearchKey := skUnseen;
if not iMap.SearchMailBox (rec) then
raise Exception.Create('Erro na pesquisa da caixa postal');

{ Quantas mensagens foram encontradas ? }
contMsg := iMap.MailBox.SearchResult.Length;

for i := 0 to contMsg - 1 do begin
TheMsg := TIdMessage.Create(Nil);

{ Obtem a identificação da mensagem ... }
idMsg := iMap.MailBox.SearchResult[i];

{ ... e a usa para recuperar o email completo }
iMap.Retrieve (idMsg, TheMsg);

{ Analisa anexos e outros procedimentos }
end;
end;
Veja que a a função de pesquisa aceita um array de filtros como parâmetro. Isso significa que podemos construir uma combinação de filtros para que uma lista mais refinada seja retornada. Outras possibilidades de filtros para pesquisar a caixa incluem o texto contido nas mensagens ou seus cabeçalhos, a data de recepção delas ou até mesmo seu tamanho.

Um outro ponto importante a destacar é que o resultado da pesquisa é armazenado na propriedade SearchResult do MailBox. O resultado fica acessível como uma lista de identificadores das mensagens que atenderam o critério estipulado para a busca. Esses identificadores são números inteiros que podem ser usados em diversas funções da caixa postal, como a que recupera o conteúdo da mensagem ou que permite excluí-la.

Uma vez que uma mensagem foi recuperada com sucesso, a instância da classe TIdMessage nos dá acesso a todas as informações pertinentes a ela, incluindo os anexos, caso ela possua algum. O trecho que trata os anexos foi removido pra não poluir muito o quadro. Como a recuperação de anexos funciona exatamente igual ao do POP3, o processo descrito no post Recuperando emails de uma caixa postal com Delphi pode ser usado para realizar esse trabalho.

Um outro recurso do IMAP4 é que você pode copiar mensagens da caixa atualmente selecionada pra uma outra caixa usando a função CopyMsgs. Ela aceita 2 parâmetros : um array com os identificadores das mensagens a serem copiadas e o nome da caixa postal que receberá as mensagens. Caso precise mover as mensagens, use essa função combinada com a DeleteMsgs, que exclui uma lista de mensagens da caixa atual.

O IMAP4 ainda disponibiliza funções para gerenciar as caixas postais. É possível criar uma caixa nova, renomer uma caixa e excluir caixas existentes. Os nomes das funções com esses objetivos são bastante intuitivos : CreateMailBox, DeleteMailBox e RenameMailBox.

21 de outubro de 2011

Trabalhando com a tag Canvas do HTML5 - parte IV

Até agora , nos posts que escrevi sobre o uso do canvas do HTML5, quando precisei me referir a uma cor nos scripts de exemplo, eu sempre usei a representação tradicional para cores estabelecida pelo CSS (Cascading Style Sheet). Isto é, as cores sempre foram especificadas como uma mistura da intensidade de vermelho, verde e azul (o código RGB) que resultasse na cor desejada. Neste post, mostro que o Canvas permite aplicar um grau de transparência às cores e também trabalhar com gradientes de cores.

As regras para cores no canvas são as mesmas definidas para o CSS. Portanto, podemos usar aqui também a função rgba(), que introduz um quarto parâmetro para controlar o nível de transparência de cores. Os 3 primeiros parâmetros são os conhecidos níveis de intensidade do vermelho, verde e azul (representados por números de 0 a 255). O último valor – chamado de alpha – corresponde ao grau de transparência a ser aplicado aos desenhos subsequentes. Ele pode assumir valores entre 0 (completamente transparente) e 1 (desenha a imagem opaca, sobrepondo totalmente o fundo, sendo esse o efeito padrão obtido até aqui).
var myCanvas = document.getElementById("cnvCores2");
var ctx = myCanvas.getContext("2d");
var incrY = myCanvas.height / 10;
var grausFin = 360;
var radFin = (Math.PI/180)* grausFin;
var meioCompr = myCanvas.width / 2;

ctx.font = "11px Arial";

/* Primeiro, preenche com cor do fundo opaca */
ctx.fillStyle = 'rgb(20,230,20)';
ctx.fillRect(0, 0, meioCompr, myCanvas.height / 2);
ctx.fillRect(meioCompr, myCanvas.height / 2, meioCompr, myCanvas.height / 2);

for (var i = 0; i < 10;i++) { /* agora, pinta por cima usando branco mas variando a transparência */
ctx.fillStyle = 'rgba(255,255,255,' + (1.0 - (i / 10.0)) + ')';
ctx.fillRect(0, i*incrY, meioCompr, incrY);

ctx.fillStyle = "navy";
ctx.fillText (Math.ceil (10.0 - i) / 10.0, 8, (i+1)*incrY - 4);
}

/* preenche no centro um círculo marrom com alto grau de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgba(128,0,0,0.4)';
ctx.arc(meioCompr, myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();

/* Como seria sem o parâmetro de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgb(128,0,0)';
ctx.arc(meioCompr, 3 * myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do código é dedicada a escrever o grau de transparência de cada uma das faixas horizontais. Como o desenho de texto também é afetado pelo valor de fillStyle - incluindo a transparência configurada nele -, eu atribuo a ela uma outra cor sólida para que o texto esteja plenamente visível, sem qualquer interferência do fundo previamente desenhado.

Há uma propriedade do contexto de pintura chamada globalAlpha que permite mudar o grau de transparência dos desenhos, influenciando preenchimentos e contornos num único comando. Essa característica tem sua utilidade, mas é menos flexível do que controlar separadamente pelo tipo de desenho.

Uma outra forma de trabalhar com cores prevista no canvas do HTML5 é o preenchimento de áreas com um gradiente, isto é, a pintura é iniciada com uma cor e vai gradualmente se aproximando de uma segunda cor, que é finalmente usada para concluir a pintura. Estão previstos dois tipos de gradiente : um linear e outro radial. Para usar um deles, basta criar o tipo desejado, alimentar as cores que comporão o gradiente e então associá-lo à propriedade fillStyle do contexto do Canvas, exatamente como fizemos com cores e transparências.

O tipo linear é instanciado através da função createLinearGradient enquanto para o gradiente radial usamos createRadialGradient. Ambos os métodos criam objetos que respeitam a especificação da interface CanvasGradient, atualmente composta por apenas uma função cujo objetivo é adicionar pontos de parada para as cores no gradiente. Ou seja, essa função define quantas cores básicas haverão no gradiente resultante e quanto espaço cada uma ocupará nele. As cores intermediárias são calculadas automaticamente de modo a preencher toda a área definida pela função de criação.
/* Gradiente linear simples com 2 cores: uma vai do início do retângulo até o meio e a outra do meio até o fim */
var lineargradient = ctx.createLinearGradient(0,0,120,120);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.5, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'rgb(120,120,220)');

/* Preenche o retângulo real com o padrão gradiente criado acima */
ctx.fillStyle = lineargradient
ctx.fillRect (0,0,180,180);

/* Outro gradiente, agora com mais cores ... */
lineargradient = ctx.createLinearGradient(0,200,180,280);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.2, 'red');
lineargradient.addColorStop (0.8, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'green');

ctx.fillStyle = lineargradient
ctx.fillRect (0,200,180, 280);
Seu navegador (ou leitor de RSS) não suporta canvas ...
A função createLinearGradient cria uma área retangular - definida por pontos que indicam o canto superior esquerdo e o inferior direito. O gradiente é criado nesse retângulo virtual e ele então é usado para preencher o retângulo real no canvas. Como o retângulo real pode ter tamanho diferente daquele usado para criar o gradiente, o canvas faz alguns ajustes para que caiba. Se o retângulo real for maior, a última cor de parada é usada para preencher o resto da área.

Veja que para adicionarmos uma cor de parada temos que informar um valor entre 0 e 1. Esse valor é chamado de offset e indica a que distância do início do retângulo a nova cor deve começar a aparecer. Uma cor prevalece até que a próxima cor de parada seja encontrada. Então, o tanto de espaço ocupado por cada uma é definido pelo offset da parada seguinte.

Para o gradiente radial, a principal diferença é que, ao invés do efeito ser aplicado num retângulo, ele é aplicado numa área delimitada por dois círculos Os círculos são definidos na criação do gradiente, sendo que os 3 primeiros parâmetros correspondem ao ponto central e raio do círculo inicial e os 3 parâmetros seguintes correspondem ao ponto central e raio do círculo final. A área fora dos círculos é mantida com a cor original da área preenchida.
/* Círculo com gradiente */ var radgrad = ctx.createRadialGradient (105, 115, 20, 112, 130, 50);
radgrad.addColorStop(0, '#FF5F98');
radgrad.addColorStop(0.75, '#FF0188');
radgrad.addColorStop(1, 'rgba(255,1,136,0)');
/* Ao preencher retângulo, somente a área com o gradiente circular é considerada !*/
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Outro círculo com gradiente */
radgrad = ctx.createRadialGradient(95,35,15,102,40,40);
radgrad.addColorStop(0, '#00C9FF');
radgrad.addColorStop(0.8, '#00B5E2');
radgrad.addColorStop(1, 'rgba(0,201,255,0)');
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Aqui , a área circular resultante é maior que o retângulo; então, o retângulo todo é preenchido */
radgrad = ctx.createRadialGradient (200, 350, 65, 200, 350, 140);
radgrad.addColorStop (0, 'navy');
radgrad.addColorStop (0.5, 'rgb(50,50,180)');
radgrad.addColorStop (1, 'rgb(120,120,220)');
ctx.fillStyle = radgrad
ctx.fillRect (50,200,150,150);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do exemplo acima foi retirada do tutorial de canvas da Fundação Mozilla. Veja que a criação do gradiente aceita a função rgba(), significando que podemos aplicar transparência neste momento também.

Mais Informações
Trabalhando com a tag Canvas do HTML5 - parte I, parte II e parte III, Tutorial de Canvas na Fundação Mozilla

6 de outubro de 2011

Design Patterns com Delphi: Strategy - Parte II

No último post, eu apresentei o conceito e a aplicabilidade do Design Pattern comportamental Strategy. Aqui, vou mostrar como implementar em Delphi o exemplo prático utilizado naquele post, relacionado a pagamento de títulos. Para facilitar o entendimento, reproduzo abaixo o diagrama UML com a sugestão de modelagem proposta:
Diagrama UML para o padrão Strategy
A implementação das relações expostas no diagrama não é especialmente complexa. Ela envolve apenas conceitos comuns da programação orientada a objetos, como herança e polimorfismo.

O primeiro passo é declarar uma classe abstrata (ou interface, se preferir), definindo nela as linhas mestras que deverão ser respeitadas por todos as implementações reais do algoritmo em questão. Isto é, essa classe introduz funções que determinam a estratégia para execução da tarefa, impondo-a a todas as classes que queiram atuar como um algoritmo alternativo para a tarefa - daí o nome do padrão ser Strategy:
TWTitulo = class;

TWPagamento=class
public
function efetuaPgto (ATitulo: TWTitulo) : integer;virtual;abstract;
end;
Nesse exemplo, há uma única função abstrata que obrigatoriamente terá que existir em cada nova implementação do algoritmo. Essa função aceita como parâmetro uma instância da classe Context da solução, papel encarnado aqui pelo título a pagar (TWTitulo). Com isso, qualquer algoritmo que adote a estratégia definida tem acesso às informações relevantes do título, bem como a suas operações. Permitir essa comunicação é imprescindível para que a tarefa (o pagamento do título) possa ser concretizada.

Agora que temos a interface estabelecida, podemos adicionar os diferentes algoritmos previstos no diagrama, criando cada um deles como uma herança simples que forneça código para a função abstrata da interface:
TWContaBancaria = class;

TWPgtoBoleto=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

TWPgtoDebitoAuto=class(TWPagamento)
{ ... }
_Conta : TWContaBancaria;
public
constructor Create (AConta: TWContaBancaria);
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

TWPgtoInternet=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

implementation

{ ... }
constructor TWPgtoDebitoAuto.Create (AConta: TWContaBancaria);
begin
_Conta := AConta;
{ ... }
end;

function TWPgtoDebitoAuto.efetuaPgto (ATitulo: TWTitulo) : integer;
begin
_Conta.Conecta;
_Conta.DebitaValor(ATitulo.ObtemValor);
ATitulo.RegistraPagto;
{ ... }
end;
A implementação da TWPgtoDebitoAuto no quadro acima mostra que podem ser necessárias outras informações para que a tarefa seja executada num determinado algorítmo. A função que executa a tarefa, no entanto, foi fixada na classe base (a interface) e não deve ser alterada já que isso implicaria que todas as outras heranças teriam que respeitar os novos parâmetros, o que nem sempre é desejável. Então, uma solução é passar as informações extras no construtor da própria classe.

Esse detalhe tem que ser levado em conta pela Factory responsável pela criação de instâncias da classe de pagamento. De acordo com o conceito de Factory, devemos criar uma função que centraliza a instanciação de uma família de classes:
TWTipoPagto = (tpBoleto, tpDebitoAuto, tpInternet);

function CriaNovoPagto (ATipo: TWTipoPagto; AContaDebito : TWContaBancaria) : TWPagamento;
begin
Result := Nil;

case (ATipo) of
tpBoleto:
Result := TWPgtoBoleto.Create;
tpDebitoAuto:
Result := TWPgtoDebitoAuto.Create (AContaDebito);
tpInternet:
Result := TWPgtoInternet.Create;
end;
end;
O último aspecto que falta abordar é com relação às características da classe de contexto TWTitulo. Além de propriedades inerentes a títulos - como seu valor, data de vencimento, código de barras, identificação do beneficiário, etc. - essa classe terá que manter uma instância da classe base de estratégia (o pagamento) para poder trocar mensagens com ela.
TWTitulo=class
protected
_Id : String;
_DataVencto : TDateTime;
_Valor : Double;
_Pagto : TWPagamento;
_FoiPago : Boolean;
procedure RegistraPagto;
{ ... }
public
function ObtemValor : Double;
function Pagar (AMeioPgto: TWTipoPagto): integer;
{ ... }
end;

{ ... }

function TWTitulo.Pagar (AMeioPgto: TWTipoPagto): integer;
begin
if (_Pagto = Nil) then
_Pagto := CriaNovoPagto (AMeioPgto, _Sessao.ObtemContaBancaria);

_Pagto.efetuaPgto (Self);
end;
Com isso, temos a classe de contexto armazenando internamente uma instância do pagamento (a estratégia) e controlando seu ciclo de vida. A instância fica disponível para que o contexto possa utilizá-la sempre que for necessário executar a tarefa embutida nela.

Note que a solução fica aberta para receber facilmente novos algoritmos, preservando o fraco acoplamento entre as classes já previstas e implementadas. Assim, poucos pontos têm que ser alterados para se introduzir uma nova forma de realizar a tarefa: basta codificar a nova classe e prever a criação de suas instâncias na função factory.

29 de setembro de 2011

Design Patterns com Delphi: Strategy - Parte I

Certas tarefas em um sistema computacional podem ser implementadas de diferentes maneiras sem que isso afete o resultado final esperado. Isto é, partindo de um determinado contexto, podemos selecionar um algoritmo entre vários possíveis e atingir o mesmo objetivo com qualquer um deles. Por exemplo, dada uma lista que precisa ser ordenada, há diversos algoritmos de ordenação que podem realizar a tarefa, como o método da bolha, quick sort ou busca binária. Independentemente do algoritmo escolhido, ao final do processo teremos a lista classificada. É claro que há diferenças de performance de um método pra outro mas isso é parte do processo de escolha daquele que melhor se adapta a uma situação. Cada situação encontrada pode levar a uma escolha distinta, sem que uma seja necessariamente preferida em relação às outras.

O programa pode, então, ter que intercambiar dinamicamente o algoritmo, seja por opção explícita do usuário ou por causa de outras questões circunstanciais encontradas durante a execução. Em projetos orientados a objetos, podemos desenhar uma solução para esse tipo de cenário usando o Design Pattern comportamental Strategy.

O objetivo do Strategy é permitir que o programa utilize de forma transparente qualquer algoritmo capaz de realizar uma determinada tarefa, construindo para isso uma estrutura de classes com baixo acoplamento. Com isso, fica fácil adicionar novos algoritmos, enquanto os códigos que os utilizarão continuam independentes da escolha feita.

Num sistema real, essa solução pode ser aplicada, por exemplo, no pagamento de títulos. Considere que um título pode ser pago levando o respectivo boleto ao banco, cadastrando a conta como débito automático ou registrando o pagamento no Internet Banking. Qualquer que seja a forma selecionada, o resultado é o título pago. O diagrama UML abaixo mostra as classes e suas relações para o Strategy aplicado a este cenário:
Diagrama UML para o padrão Strategy
A nomenclatura formal para as classes que participam da solução com o pattern Strategy é a seguinte:
A classe Strategy é uma interface onde é definido o comportamento comum a ser respeitado por todos os algoritmos. Em outras palavras, devemos incluir nesta classe funções virtuais (e provavelmente abstratas) que fixarão a forma com que a tarefa estará acessível às outras partes do programa. A classe TWPagamento do diagrama acima exerce esse papel, definindo que pagamentos deverão ser feitos através da função efetuaPgto.

São chamadas de Concrete Strategy as classes que atendem a definição introduzida no Strategy e que, portanto, implementam um algoritmo que efetivamente realize a tarefa proposta. No diagrama, esse papel cabe a três classes distintas : TWPgtoBoleto, TWPgtoDebitoAuto e TWPgtoInternete. Cada uma delas executará a tarefa a sua maneira, providenciando uma versão própria da função efetuaPgto.

O Context é a classe que usará o comportamento definido pela Strategy, invocando as funções disponibilizadas por ela. Portanto, o Context deverá armazenar internamente uma referência ao Strategy. Em geral, ele também terá que expor formas de interagir com o Strategy, publicando propriedade e funções. No nosso exemplo, o Context é a classe TWTitulo; a interação com o Strategy é conseguida passando-se para a tarefa uma instância do próprio título a ser pago.

Um Client é qualquer parte do sistema que solicite uma operação à classe Context. Esse tipo de classe foi omitido do diagrama.
No próximo post, mostro uma sugestão de como implementar na prática essa solução usando o Delphi.

Mais Informações
Posts sobre Design Patterns

23 de setembro de 2011

Trabalhando com a tag Canvas do HTML5 - parte III

Dando continuidade aos posts sobre uso da tag Canvas (veja parte I e parte II), falo aqui sobre o uso de textos no canvas e a manipulação de imagens.

As propriedades básicas disponíveis para formatarmos a exibição de textos no Canvas são o tipo de fonte e a cor para o desenho. O tipo do fonte pode ser modificado através da propriedade font. Como a interpretação do valor contido nessa propriedade é idêntica ao de folhas de estilo CSS, podemos configurar com ela a família do fonte (Arial, Tahoma, Courier, etc.), o tamanho das letras e estilos especiais (negrito, itálico, etc.).

Quando trabalhamos com figuras geométricas no outro post, vimos que há duas formas de comandar um desenho : traçando apenas a borda ou preenchendo também o interior da figura, sendo que as cores para um ou outro são configuradas em propriedades separadas. O mesmo vale para o desenho de textos.

Assim, a propriedade fillStyle conterá a cor para preencher o texto quando este for desenhado usando a função fillText. Por outro lado, a propriedade strokeStyle define a cor das bordas do texto vazado a ser desenhado usando a função strokeText. Ambas as propriedades aceitam valores que estejam em conformidade com as regras de definição de cores estabelecidas pelo padrão CSS. Veja um exemplo :
var myCanvas = document.getElementById("cnvText2");
var ctx = myCanvas.getContext("2d");

ctx.fillText ("Fonte e cores padrões", 5, 20);

ctx.font = "bold 16px Verdana";
ctx.fillStyle = "blue";
ctx.fillText ("Verdana 16px negrito", 5, 40);

ctx.font = "20px Courier";
ctx.strokeStyle = "#0FAF0F";
ctx.strokeText ("Courier 20px stroke", 5, 65);

ctx.font = "36px Arial";
ctx.strokeStyle = "maroon";
ctx.strokeText ("Arial 36px stroke", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

Usando fontes pequenas, o efeito de desenhar somente a borda acaba diluído, dando a impressão que o texto está todo preenchido. Portanto, ele funciona melhor com fontes maiores.

Conforme vemos no quadro anterior, as funções de desenho de texto aceitam 3 parâmetros : o próprio texto que será escrito e as coordenadas da posição no canvas onde ele deve ser renderizado. Normalmente, a posição indica o canto superior esquerdo de um retângulo virtual que conterá o texto. O significado dessa posição, no entanto, pode variar de acordo com o valor das propriedades textAlign e textBaseline. A primeira altera a interpretação do posicionamento horizontal do texto enquanto a segunda controla a coordenada vertical. Não é muito comum ter que alterar esses valores pois, no geral, os valores originais dão conta do recado.

Poderíamos ainda ter passado como 4o parâmetro o comprimento máximo que o texto deve ocupar - caso o tamanho real exceda esse valor, o tamanho do fonte é alterado automaticamente para que o texto caiba.

Um outro aspecto para incrementar a renderização de textos é a criação do efeito de sombra. Essa característica é controlada pelas propriedades shadowColor - que corresponde à cor da sombra - e o par shadowOffsetX e shadowOffsetY - respectivamente, o deslocamento horizontal e vertical da sombra a ser projetada em relação ao texto. É possível também esfumaçar a sombra, deixando o efeito mais sutil. Consegue-se isso usando a propriedade shadowBlur, cujo valor indica o quão borrada deve ser a sombra projetada. Seguem alguns exemplos:
ctx.font = "18pt Arial";
ctx.fillStyle = "#0F0FA0";
ctx.shadowColor = "gray";
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillText ("Texto com sombra comum", 5, 40);

ctx.fillStyle = "#7F0FA0";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.fillText ("Texto com sombra borrada", 5, 70);

ctx.fillStyle = "green";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 8;
ctx.fillText ("Sombra muito borrada", 5, 100);
Seu navegador (ou leitor de RSS) não suporta canvas ...

De acordo com a padronização da W3C, para desenharmos uma imagem no canvas devemos usar a função drawImage. Com ela, podemos transferir para o canvas uma imagem inteira ou uma parte retangular dela. Também podemos modificar a proporção da imagem renderizada no canvas, indicando se ela é cópia exata da imagem original ou se será ajustada para um novo comprimento e altura. Neste link publicado pela W3C há uma explicação e um gráfico que ilustram o funcionamento dos parâmetros envolvidos na cópia da imagem original para o Canvas.

Em sua versão mais simples, o drawImage aceita como parâmetros a imagem em si e a coordenada do canto superior esquerdo no canvas a partir de onde a imagem será renderizada. Neste contexto, uma imagem pode ser tanto uma tag IMG quanto um outro canvas ou até mesmo a tag VIDEO, introduzida no HTML5. No caso da IMG, podemos recuperar uma referência à imagem usando a função getElementById do documento HTML (se a imagem já estiver embutida na página HTML) ou criar uma nova referência do zero, instanciando o objeto Image:
var img = new Image();
img.onload = function () {
ctx.drawImage (img, 10, 10);
ctx.drawImage (img, 110, 10, 70, 110);
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

ctx.font = "10pt Tahoma";
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Novo tamanho", 110, 130);
ctx.fillText ("Cópia parcial",210, 130);
Seu navegador (ou leitor de RSS) não suporta canvas ...
É importante ressaltar que a carga da imagem é disparada de modo assíncrono, logo após a URL para a imagem ter sido informada. Isto significa que o script continua a executar enquanto o navegador localiza e carrega a imagem. Por isso, no script acima a imagem é desenhada no canvas dentro do evento onload da tag da imagem. Ou seja, o desenho só é feito quando efetivamente a imagem está disponível.

As propriedades para sombreamento de texto discutidas neste post também são aplicáveis ao desenho de imagens. Assim, podemos incrementar o resultado adicionando esse efeito conforme a necessidade:
var img = new Image();
img.onload = function () {
/* Remove o recurso de sombreamento para a 1a imagem */
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.drawImage (img, 10, 10);

/* Aplica o recurso de sombreamento para as demais imagens */
ctx.shadowColor = "#2D90E0";
ctx.shadowOffsetX = 7;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 8;

/* Copia a imagem completa para um local no canvas */
ctx.drawImage (img, 110, 10);
/* Copia parte da imagem para um local no canvas */
ctx.drawImage (img, 10, 15, 45, 60, 220, 20, 45, 60);
}
img.src = '40anos.gif';

/* Para os textos, remove os efeitos de sombreamento */
ctx.font = "10pt Tahoma";
ctx.shadowColor = "black";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
ctx.fillText ("Normal", 30, 130);
ctx.fillText ("Com Sombra", 110, 130);
ctx.fillText ("Cópia parcial", 210, 123);
ctx.fillText ("com sombra", 210, 137);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Note pelo exemplo anterior que mesmo na cópia de apenas parte de uma imagem o efeito de sombreamento se aplica.

Em outra oportunidade mostrarei mais recursos do canvas associado a cores e imagens (como rotação e criação de gradientes) e como misturar isso tudo.

15 de setembro de 2011

Funcionamento do Certificate Store do Windows

Tenho recebido com frequência dúvidas a respeito do acesso a certificados digitais importados no Certificate Store do Windows. Pela forma com que as dúvidas têm sido colocadas, percebi que faltou nos posts sobre esse assunto dar uma ideia global do que é o Certificate Store e como ele funciona.

Como disse no post sobre acesso ao Certificate Store do Windows com C#, o Certificate Store é um repositório centralizado onde são armazenados os certificados digitais disponíveis em um computador. É possível acessar esse repositório visualmente, acessando o console de gerenciamento pela opção Executar do menu Iniciar do Windows. Na caixa de edição que se abre, digite o comando abaixo:
mmc \windows\system32\certmgr.msc
Normalmente, a tela que aparece é similar à que está reproduzida abaixo:
Certificados Digitais
Com essa aplicação, é possível gerenciar os certificados instalados no computador, fazendo a importando de novos certificados e eliminando aqueles que não são mais desejados. Observe que o painel à esquerda mostra uma organização hierárquica, com uma raiz e diversas pastas embaixo dela. O nó raiz nesse exemplo apresenta o nome Certificates - Current User. Isso significa que todas as pastas sob ele e os certificados listados no painel à direita estão disponíveis apenas para o usuário que atualmente está logado no Windows.

As pastas servem para agrupar os certificados por semelhança na finalidade de uso. Na imagem, a pasta Personal está selecionada, permitindo-nos visualizar à direita os certificados catalogados para uso pessoal do usuário Windows atual. Outro exemplo de pasta é a que contém certificados identificando as Autoridades Certificadoras (CA), que são as entidades responsáveis pela emissão dos certificados.

Do ponto de vista do programador que vai acessar o Store usando a classe X509Store (ou o CAPICOM), essas duas informações - nó raiz e pasta - são imprescindíveis. O construtor da X509Store aceita como parâmetros o Store Name e o Store Location, sendo que o name corresponde à "pasta" onde está o certificado e location é o nó raiz ao qual a pasta está atrelada. A pasta personal é mapeada com o nome "My" para essa função.

Agora, imagine que você construiu um serviço que acessa o Certificate Store. No Windows, cada serviço é executado por um usuário específico que é configurado na guia Log On das propriedades do serviço. Com isso, ele poderá entrar em execução mesmo que ninguém esteja logado no Windows pois o login será feito automaticamente para o usuário que foi associado ao serviço. Portanto, se você importou um certificado para seu usuário mas está executando o serviço com um usuário diferente, esse outro usuário não enxergará o seu certificado.

Isso vale também para aplicações ISAPI - sites construídos em ASP ou ASP.NET se encaixam nessa categoria - uma vez que elas rodam como parte do serviço do IIS (Internet Information Services). O IIS é atrelado normalmente a um usuário padrão do Windows chamado Local System, significando que as credencias desse usuário é que serão utilizadas para acessar os recursos do sistema, tais como o registry e a hierarquia de certificados.

Para resolver esse impasse, podemos importar o certificado num Store Location acessível por todos os usuários. Como a aplicação disponível carrega apenas originalmente o Location "Current User", teremos que adicionar na mão um plugin para o Location com a característica que precisamos. Esse Location é o "Local Computer".

Para isso, execute o MMC.exe sem parâmetros no Iniciar -> Executar do Windows. No menu File da aplicação, clique na opção Add/Remove snap-in e então o botão "Add" para ativar a tela de adição dos plugins. Na lista de plugins que aparece, selecione certificates e novamente clique em "Add". A imagem abaixo mostra a tela de adição do plugin de gerenciamento de certificados com a opção "Computer Account" (o computer local) selecionada.
Add snap-in

Após a adição, o console mostrará uma hierarquia similar à da imagem no início deste post, com a exceção de que o nó raiz será "Local Computer". Agora podemos importar certificados na pasta "Personal" e torná-los disponíveis para os programas, independentemente das credenciais do usuário que está executando esse programa ou serviço.

Certificados importados na pasta "Personal" do "Local Computer" podem, então, ser acessados abrindo-se o Certificate Store com os parâmetros mostrados abaixo:
X509Store lStore = new X509Store (StoreName.My, StoreLocation.LocalComputer);
/* ... */