Follow me @Tmeister

Air 2.0 Conexión IMAP mediante SecureSocket

ESCRITO POR Tmeister EN February - 26 - 2010


Una de las nuevas características de Adobe Air 2.0 es la conexión a servidores mediante sockets encriptados (SSL).

Uno de los servicios mas comunes que utiliza una conexión encriptado son los proveedores de correo usando IMAP.

Para este ejemplo vamos a conectarnos al servidor IMAP de Gmail y obtener el numero de mensajes nuevos y mensajes en total que existen en nuestra cuenta.

Para fines prácticos de este ejemplo necesitamos:

Lo primero es crear una interfaz sencilla en donde se pueda insertar el nombre de usuario, la contraseña, campos de output y un boton para iniciar el proceso.

	<s:Label x="10" y="17" text="Gmail username:"/>
	<s:Label x="10" y="45" text="Gmail password:"/>
	<s:TextInput y="10" id="username_txt" change="_username = username_txt.text" right="10" left="120"/>
	<s:TextInput y="40" id="password_txt" change="_password = password_txt.text" displayAsPassword="true" right="10" left="120"/>
	<s:Button y="70" label="Tell me!!" click="start()" right="10"/>
	<s:Label x="10" y="100" width="280" id="result_txt"/>
	<s:TextArea id="out_txt" editable="false" right="10" bottom="10" top="130" left="10"/>

El resultado de esto se ve de esta forma:

Despues de tener nuestra interfaz vamos a declarar todas la variables y constantes que vamos a necesitar:

			/******************************************************************
			 *	Datos de la cuenta (Gmail IMAP)
			 * ***************************************************************/

			private static const INCOMING_SERVER:String = "imap.gmail.com";
			private static const PORT:int = 993;
			private var _username:String = "";
			private var _password:String = "";

			/******************************************************************
			 * Socket && Stuff
			 * ***************************************************************/ 

			private var _server:Socket;
			private var _buffer:ByteArray;
			private var _action:String;
			private var _totalMessages:String;
			private var _newMessages:String;

			/******************************************************************
			 * Respuestas del servidor (para filtar el contenido)
			 * http://tools.ietf.org/html/draft-gahrns-imap-namespace-00
			 * ****************************************************************/

			private static const CONNECT:String = "*";
			private static const LOGIN:String = "A002";
			private static const STATUS:String = "A006";
			private static const CRLF:String = "\r\n";
			private var _regSearch:RegExp = RegExp("\\"+_action+"\\r\\n");

Aqui hay un par de cosas que debo resaltar:

			private var _buffer:ByteArray;

En esta variable almacenaremos las respuestas que nos envié el servidor.

			private var _regSearch:RegExp = RegExp("\\"+_action+"\\r\\n");

Con esta expresion regular verificaremos si el mensaje que recibimos del servidor es util para nosotros.

			private static const CONNECT:String = "*";
			private static const LOGIN:String = "A002";
			private static const STATUS:String = "A006";

Estas constantes son los identificadores de los mensajes que se mandan desde y hacia el servidor para saber mas de estos identificadores recomiendo esta lectura http://tools.ietf.org/html/draft-gahrns-imap-namespace-00

Ahora nos queda inicializar nuestro socket y crear los eventos que usaremos para comunicarnos con el servidor

				_server = new SecureSocket();
				_server.addEventListener(Event.CONNECT, onConnect);
				_server.addEventListener(Event.CLOSE, onClose);
				_server.addEventListener(ProgressEvent.SOCKET_DATA, onData);
				_server.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

El único cambio con respecto a trabajar con Sockets sin encriptar es el constructor de la clase en lugar de usar

				_server = new Socket();

Ahora debemos utilizar

				_server = new SecureSocket();

Así de simple; Bien, hemos seteado los listeners para CONNECT, SOCKET_DATA, CLOSE y IO_ERROR vamos a concentranos solamente en SOCKET_DATA que es el evento que se dispara al momento de recibir un mensaje del servidor.

			private function onData(e:ProgressEvent):void
			{
				out_txt.text += "Data " + e+"\n";
				var socket:Socket = e.target as Socket;
				var bufferString:String;
				socket.readBytes(_buffer, _buffer.length, socket.bytesAvailable);
				bufferString = _buffer.toString();
				out_txt.text += "SERVER: " + _buffer.toString()+"\n";
				if (  bufferString.search( _regSearch ) )
				{
					out_txt.text += "Es una respuesta valida parsea\n";
					parseResponse();
				}
				cursorManager.removeBusyCursor()
			}

En esta función recibimos el evento y tomamos el contenido del mensaje mediante el método readBytes y lo almacenamos en la variable _buffer

				socket.readBytes(_buffer, _buffer.length, socket.bytesAvailable);

Verificamos que el mensaje corresponda a nuestra petición, en este caso solo usamos 3 peticiones CONNECT, LOGIN, STATUS y si el mensaje es correcto parseamos el contenido.

El primer mensaje que recibimos del servidor es el de conexión

SERVER: * OK Gimap ready for requests from 189.216.40.184 14if7528374pzk.62

Una vez que estemos conectados mandamos nuestras credenciales para que el servidor nos autentique y cambiamos el mensaje a LOGIN indicando que estamos esperando el mensaje de respuesta de la solicitud de logueo.

				if ( _action == CONNECT )
				{
					out_txt.text += "Es es tag de conexion manda login\n";
					_server.writeUTFBytes(LOGIN + " LOGIN " + _username + " " + _password + CRLF);
					_action = LOGIN;
					_server.flush();
					return;
				}

Se envia el mensaje y de inmediato recibimos la respuesta del servidor, en caso de login correcto:

A002 OK tmeister@gmail.com authenticated (Success)

Si es incorrecto:

A002 NO [ALERT] Invalid credentials (Failure)

Con esto es muy fácil saber el resultado de la operación, usemos indexOf para saber si la palabra “OK” existe

				if ( _action == LOGIN )
				{
					out_txt.text += "Es la respuesta del login\n";
					if( bufferString.indexOf("OK") != -1 )
					{
						out_txt.text += "El login es correcto, estamos autenticados - Pedir correos sin leer\n";
						_action = STATUS;
						_server.writeUTFBytes(STATUS + " STATUS inbox (MESSAGES UNSEEN)" + CRLF);
						_server.flush();
					}else
					{
						out_txt.text += "Ooops!! Las credenciales no son validas.\n";
						Alert.show("Ooops!! Las credenciales no son validas.", "Error");
						_server.close();
					}
					return;
				}

Excelente estamos autenticados, Ahora que? solo nos falta pedir el estatus de la cuenta pidiendo los mensajes totales y los mensajes nuevos de la carpeta Inbox

						_server.writeUTFBytes(STATUS + " STATUS inbox (MESSAGES UNSEEN)" + CRLF);

El ultimo paso es recibir el mensaje del servidor con la información que pedimos y parsearla, al ser texto solo tenemos que usar indexOf y substring para acceder a lo que nos interesa

STATUS “inbox” (MESSAGES 61298 UNSEEN 5)

				if( _action == STATUS )
				{
					out_txt.text += "Es la respuesta del Status " + bufferString+"\n";
					var startSub:int = bufferString.indexOf("(");
					var endSub:int = bufferString.indexOf(")");
					var message:String = bufferString.substring(startSub+1, endSub);
					var slides:Array = message.split(" ");
					var count:int = 0;
					for each( var slide:String in slides )
					{
						switch( slide )
						{
							case "MESSAGES":
								_totalMessages = slides[count + 1];
								break;
							case "UNSEEN":
								_newMessages = slides[count + 1];
								break;
						}
						count++;
					}
					result_txt.text = _newMessages + " mensajes nuevos, de " + _totalMessages + " en total";
					out_txt.text += _totalMessages;
					out_txt.text += _newMessages;
					_action = null;
					_server.close();
				}
			}

Sin duda, lo mas complejo al momento de trabajar con Sockets es saber utilizar e implementar el protocolo de comunicación pero esa ya es otra historia.

Por ultimo les dejo el código completo

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/halo"
					   creationComplete="init(event)"
					    width="300" height="250">
	<fx:Script>
		<![CDATA[
			import flash.events.Event;
			import flash.events.IOErrorEvent;
			import flash.events.MouseEvent;
			import flash.events.ProgressEvent;
			import flash.net.SecureSocket;
			import flash.net.Socket;
			import flash.utils.ByteArray;

			import mx.controls.Alert;
			import mx.events.FlexEvent;

			private static const INCOMING_SERVER:String = "imap.gmail.com";
			private static const PORT:int = 993;
			private var _username:String = "";
			private var _password:String = "";

			private var _server:Socket;
			private var _buffer:ByteArray;
			private var _action:String;
			private var _totalMessages:String;
			private var _newMessages:String;

			private static const CONNECT:String = "*";
			private static const LOGIN:String = "A002";
			private static const STATUS:String = "A006";
			private static const CRLF:String = "\r\n";
			private var _regSearch:RegExp = RegExp("\\"+_action+"\\r\\n");

			protected function init(event:FlexEvent):void
			{

				_server = new SecureSocket();
				_server.addEventListener(Event.CONNECT, onConnect);
				_server.addEventListener(Event.CLOSE, onClose);
				_server.addEventListener(ProgressEvent.SOCKET_DATA, onData);
				_server.addEventListener(IOErrorEvent.IO_ERROR, onIOError);

				_buffer = new ByteArray()
			}
			private function onConnect(e:Event):void
			{
				out_txt.text += "Conexion establecida " + e.toString()+"\n";
			}
			private function onClose(e:Event):void
			{
				out_txt.text += "Conexion cerrada " + e+"\n";
			}
			private function onData(e:ProgressEvent):void
			{
				out_txt.text += "Data " + e+"\n";
				var socket:Socket = e.target as Socket;
				var bufferString:String;
				socket.readBytes(_buffer, _buffer.length, socket.bytesAvailable);
				bufferString = _buffer.toString();
				out_txt.text += "SERVER: " + _buffer.toString()+"\n";
				if (  bufferString.search( _regSearch ) )
				{
					out_txt.text += "Es una respuesta valida parsea\n";
					parseResponse();
				}
				cursorManager.removeBusyCursor()
			}
			private function onIOError(e:IOErrorEvent):void
			{
				out_txt.text += "IOError " + e+"\n";
			}

			private function parseResponse():void
			{
				var bufferString:String = _buffer.toString();
				_buffer.clear();
				if ( _action == CONNECT )
				{
					out_txt.text += "Es es tag de conexion manda login\n";
					_server.writeUTFBytes(LOGIN + " LOGIN " + _username + " " + _password + CRLF);
					_action = LOGIN;
					_server.flush();
					return;
				}
				if ( _action == LOGIN )
				{
					out_txt.text += "Es la respuesta del login\n";
					if( bufferString.indexOf("OK") != -1 )
					{
						out_txt.text += "El login es correcto, estamos autenticados - Pedir correos sin leer\n";
						_action = STATUS;
						_server.writeUTFBytes(STATUS + " STATUS inbox (MESSAGES UNSEEN)" + CRLF);
						_server.flush();
					}else
					{
						out_txt.text += "Ooops!! Las credenciales no son validas.\n";
						Alert.show("Ooops!! Las credenciales no son validas.", "Error");
						_server.close();
					}
					return;
				}
				if( _action == STATUS )
				{
					out_txt.text += "Es la respuesta del Status " + bufferString+"\n";
					var startSub:int = bufferString.indexOf("(");
					var endSub:int = bufferString.indexOf(")");
					var message:String = bufferString.substring(startSub+1, endSub);
					var slides:Array = message.split(" ");
					var count:int = 0;
					for each( var slide:String in slides )
					{
						switch( slide )
						{
							case "MESSAGES":
								_totalMessages = slides[count + 1];
								break;
							case "UNSEEN":
								_newMessages = slides[count + 1];
								break;
						}
						count++;
					}
					result_txt.text = _newMessages + " mensajes nuevos, de " + _totalMessages + " en total";
					out_txt.text += _totalMessages;
					out_txt.text += _newMessages;
					_action = null;
					_server.close();
				}
			}

			private function start():void
			{
				out_txt.text = "iniciando Conexion\n";
				if( _server != null && _server.connected )
				{
					_server.close();
				}
				if( _username.length && _password.length )
				{
					_action = CONNECT;
					_server.connect(INCOMING_SERVER, PORT);
					cursorManager.setBusyCursor();
				}else
				{
					Alert.show("Escribe tus credenciales..", "Error");
				}
			}

		]]>
	</fx:Script>
	<s:Label x="10" y="17" text="Gmail username:"/>
	<s:Label x="10" y="45" text="Gmail password:"/>
	<s:TextInput y="10" id="username_txt" change="_username = username_txt.text" right="10" left="120"/>
	<s:TextInput y="40" id="password_txt" change="_password = password_txt.text" displayAsPassword="true" right="10" left="120"/>
	<s:Button y="70" label="Tell me!!" click="start()" right="10"/>
	<s:Label x="10" y="100" width="280" id="result_txt"/>
	<s:TextArea id="out_txt" editable="false" right="10" bottom="10" top="130" left="10"/>
</s:WindowedApplication>

Descarga

download

Download: Gmail-Imap.air
Version: 0.1
Updated: February 26, 2010
Size: 870.16 KB

Espero les sea de ayuda y nos estamos leyendo.

One Response to “Air 2.0 Conexión IMAP mediante SecureSocket”

  1. esto esta genial!! gracias por compartir ^^

Acerca de mi
Enrique Chavez

Enrique Chávez también es conocido como @Tmeister es un desarrollador, emprendedor, poeta y loco.

Geek autodeclarado y apasionado de todo lo que tiene que ver con tecnología y desarrollo.

Cuenta con una experiencia de más de 7 años en el ramo del desarrollo, creando, sobre todo, aplicaciones web en la empresa Sapotek.

A finales del 2008 crea, junto con 3 colegas, la consultoría llamada AureaCode, enfocándose a brindar servicios de desarrollo web en todas sus variantes.

Si necesitas algún tipo de información o consulta por favor no dude en escribir.

View Enrique Chávez's profile on LinkedIn

Carlos Enrique Chavez Garcia's VisualCV

RSS Feed

Links Patrocinados