Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Reading email without additional components - Part III
Roberto C. Ianni, April 1, 2005
The intention of this article is to finish with the use of POP3 (Post Office Protocol 3), implementing everything we have seen so far.
Summary
The intention of this article is to finish with the use of POP3 (Post Office Protocol 3), implementing everything we have seen so far.
Description

The intention of this article is to finish with the use of POP3 (Post Office Protocol 3), implementing everything we have seen so far.

Objecdtives

At this point, we still have to talk about something very important: the processing of the files received in a message. This has an additional level of complexity when the files are binary files, since they will come encoded with Encode64. Once we finish with the theory, we can see how to implement the classes so that they can be used by the test application.

The issues we will develop will be:

  • Receiving attached files.
    • Text files and binary files.
    • Decode base 64.
  • Implementing the WPop3Mail class.
  • Test application.

Receiving attached files

Receiving files is nothing else than parsing the text that we have received, with certain rules. The first is that you have to detect the "boundary" that will tell us the separator for every part of the e-mail.

Figure 1: Body part

Once we have done this, we can scan through all the attachments that the e-mail has, until we find the one that ends with the boundary name plus two hyphens, "--", indicating that there are no further attachments in the message. Every attachment is located between two boundaries, as shown in the figure.

Figure 2: Attachment

The message has properties that indicate how the attachment is encoded, what is the original filename, and whether it really is an attachment or not, for what is within the limit may be the message body.

Text files and binary files

The content of the boundary allows us to obtain the information from the attached file, but, as we saw, have been encoded because it was a binary file, or it may have been sent as a plain text file. The second case is easier, because, if the file was sent as 8-bit ASCII, the content within the boundaries is copied directly to the file without any additional processing. The problem appears when the file was encoded, for in this case, the text is not the real file content, but rather, it was processed. In this case, you have to do a DeCode64 (in the SMTP, when the binary file is sent, it has to do an Endoce64).

The text file doesn't make much sense when we analyze it, therefore, we will concentrate on the encoding. For this purpose, we need to see how a Decode of the received file is done; this is basically the inverse process of what we did when we sent the encoded file (we saw this in the section on sending e-mail with SMTP).

We will analyze the function first in C++, since it is a little more natural, and then, we will see some implementation details that some of you may already have seen in the SMTP Encode implementation.

bool InternalDecode64(const unsigned char *in, unsigned inLen, unsigned char *out, unsigned &outmax)
{
   char *pdest;
   int lnBloque = (int)( inLen / 4 ), lnInRel;
   int lnCantRelleno = 0;
   int lnPos1, lnPos2, lnPos3, lnPos4;

   // Counts how many time the padding is used.-
   for( lnInRel = inLen-1; (unsigned int)lnInRel <= inLen; lnInRel-- )
   {
      if( in[lnInRel] == '=' )
         lnCantRelleno++;
      else
         break;
   }

   for( lnInRel = 0; lnInRel < lnBloque; lnInRel ++)
   {
      pdest  = strchr( m_base64Table, in[lnInRel * 4] );
      lnPos1 = (int)(pdest - m_base64Table);

      pdest  = strchr( m_base64Table, in[lnInRel * 4 + 1] );
      lnPos2 = (int)(pdest - m_base64Table);

      pdest  = strchr( m_base64Table, in[lnInRel * 4 + 2] );
      lnPos3 = (int)(pdest - m_base64Table);

      pdest  = strchr( m_base64Table, in[lnInRel * 4 + 3] );
      lnPos4 = (int)(pdest - m_base64Table);

      out[lnInRel * 3    ] = (char)( ( lnPos1 << 2 )         + ( ( lnPos2 & 48 ) >> 4 ) );
      out[lnInRel * 3 + 1] = (char)( ( ( lnPos2 & 15 ) << 4)   + ( ( lnPos3 & 60)  >> 2 ) );
      out[lnInRel * 3 + 2] = (char)( ( ( lnPos3 & 3  ) << 6)   + lnPos4 );
   }

   // Eliminates the padding.-
   outmax = (lnBloque * 3) - lnCantRelleno;
   out[ outmax ] = '\0';

   return true;
}

For those of you who don't know C++, I will briefly explain some of the operators used in the code:

  • ">>" operator: Does a binary "Shift Right", the equivalent of dividing a number by 2.

  • "<<" operator : Does a binary "Shift Left", the equivalent of multiplying by 2.

  • "&" operator: Does a binary "And" on the data.

    For example, the numbers 3 and 6 would be as follows:
    011
    110
    010 && result of the binary "And"

  • The variable m_base64Table contains a table of characters that will be used to decode the original string.

    The Base64 alphabet

    Value 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
    Code A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f

    Value 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
    Code g h I j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /

    Now we can return to the algorithm. The first thing it does is obtain the amount of padding that was generated by Encode (the "=" characters at the end of the string), so that the string can later be traversed from the beginning to the end. In each pass, a relationship is sought between every byte and the string where the 64 bytes are stored. These positions are then combined, forming three new bytes, which are part of the original string that was then encoded.

    After seeing this, we can have an idea how to implement the class in VFP. One possible implementation might be like this:

    PROCEDURE Decode64( lsIn, lsOut )
       LOCAL inLen, inLen2, lnInRel, lnBloque
       LOCAL lnByte1, lnByte2, lnByte3
       LOCAL lnPos1, lnPos2, lnPos3, lnPos4
       LOCAL lnCantRelleno
    
       inLen    = LEN( lsIn )
       lnBloque = ROUND( inLen / 4, 0 )
       inLen2   = lnBloque * 3
       lnInRel  = 1
       lsOut    = ""
       lnCantRelleno = OCCURS( "=", lsIn )
    
       FOR lnInRel = 0 TO lnBloque
          *|-- Search at the relevant positions.-
          lnPos1 = AT( SUBSTR( lsIn, lnInRel * 4 + 1, 1 ), This.m_base64List ) - 1
          lnPos2 = AT( SUBSTR( lsIn, lnInRel * 4 + 2, 1 ), This.m_base64List ) - 1
          lnPos3 = AT( SUBSTR( lsIn, lnInRel * 4 + 3, 1 ), This.m_base64List ) - 1
          lnPos4 = AT( SUBSTR( lsIn, lnInRel * 4 + 4, 1 ), This.m_base64List ) - 1
          lnPos1 = IIF( lnPos1 < 0, 0, lnPos1 )
          lnPos2 = IIF( lnPos2 < 0, 0, lnPos2 )
          lnPos3 = IIF( lnPos3 < 0, 0, lnPos3 )
          lnPos4 = IIF( lnPos4 < 0, 0, lnPos4 )
    
          lnByte1 = BITLSHIFT( lnPos1, 2 )             + BITRSHIFT( BITAND( lnPos2, 0x30 ), 4 )
          lnByte2 = BITLSHIFT( BITAND( lnPos2, 0xF ), 4 ) + BITRSHIFT( BITAND( lnPos3, 0x3C ), 2 )
          lnByte3 = BITLSHIFT( BITAND( lnPos3, 0x3 ), 6 )   + lnPos4
    
          lsOut = lsOut + CHR( lnByte1 ) + CHR( lnByte2 ) + CHR( lnByte3 )
       NEXT
    
       *|-- Eliminate the padding.-
       lsOut = SUBSTR( lsOut, 1, inLen2 - lnCantRelleno )
    
       inLen2 = 1
    ENDPROC
    

    Now we are able to transform the string of the encoded file into its original form without any problem; we only have to take care of eliminating the "ENTER" in the string, sent by the POP3, since these are not part of the original Encode, rather, they were sent so as not to surpass the maximum line size supported by the SMTP server.

    The function "Decode64" which we developed can be substituted easily by the function "STRCONV( lsEncode, 14 )", available since VFP 8.0, the only problem is that this will make our class "discriminate" against earlier versions of VFP, and I really like VFP 6.0 a lot.

    Implementation of the WPop3Mail class

    With the theory we have seen so far, we can know start to develop our class, which will be in charge of connecting the server and pick up the messages stored on it, allowing us to bring them to our terminal.

    For this purpose I am going to show you a class that will only have the task of connecting and disconnecting from the POP3 server, and that contains a collection of "Mail" objects. Every one of those "Mail" objects will be responsible of handling only a single message located on the server. Our class will look like this:

    DEFINE CLASS WPop3 AS CUSTOM OLEPUBLIC
       ....
    
       NAME = "WPop3"
    
       *|-- User of the connection for the POP3.-
       m_UsrPOP3  = ""
    
       *|-- Password for the POP3 user.-
       m_PassPOP3 = ""
    
       *|-- Array with the list of mails stored on the server.-
       DIMENSION m_MailList[1]
    
       ....
    ENDDEFINE
    

    Thus, we only need to store the user name and his password, and then we can receive the messages. Therefore, our class will be in charge of generating our collection of "Mail" objects so that we can handle them one at a time. Let's see the implementation of a method that does this:

    PROCEDURE RecivirMails
       LOCAL iMail, lsConnMsg
       LOCAL lnStart, lnEnd
    
       *|-- Create the Socket in the clase WSocket.-
       IF ( !THIS.m_SocketConn.CrearSock() )
          This.m_Error = "Can't create the Socket"
          RETURN .F.
       ENDIF
    
       This.SetInfoCon()
    
       *|-- Connect to the mail server.-
       IF ( THIS.m_SocketConn.ConectarServer() )
          lsConnMsg = THIS.m_SocketConn.Recibir()
          IF( This.VerificarRespuesta( lsConnMsg, NO_ERRORS ) )
    
             lnStart = AT( "<", lsConnMsg )
             IF( lnStart > 0 )
                lnEnd   = AT( ">", lsConnMsg )
                IF( lnStart > 0 AND lnEnd > lnStart )
                   This.m_sTimeStamp = SUBSTR( lsConnMsg, lnStart, lnEnd - lnStart + 1 )
                ENDIF
             ELSE
                *|-- Doesn't support APOP RFC1725, MD5 RFC1321
                This.m_sTimeStamp = ""
             ENDIF
    
             IF( This.Login() )
                IF( This.ObtenerCantMail() )
                   *|-- Get the list of e-mails.-
                   IF( This.m_NumMsg > 0 )
                      DIMENSION This.m_MailList[This.m_NumMsg]
    
                      FOR iMail = 1 TO This.m_NumMsg
                         This.m_MailList[iMail] = CREATEOBJECT("WMail")
                         This.m_MailList[iMail].m_MailNum    = iMail
                         This.m_MailList[iMail].m_SocketConn = This.m_SocketConn
                      NEXT
                   ENDIF
    
                ELSE
                   This.m_Error = "Can't obtain the total amount of e-mail "
                   RETURN .F.
                ENDIF
             ELSE
                This.m_Error = "Incorrect user or password"
                RETURN .F.
             ENDIF
          ELSE
             This.m_Error = "Connected to the server, but server doesn't respond adequately"
             RETURN .F.
          ENDIF
       ELSE
          This.m_Error = "Can't connect to the server"
          RETURN .F.
       ENDIF
    
    ENDPROC
    

    As we can see, the first thing to do is to create the socket and establish the connection; in case it supports MD5, it will tell us so when starting the session, as we have seen in the previous article. Then, we send our identification to the server, and obtain the amount of pending messages that have to be downloaded (in the method ObtenerCantMail), passing the STAT command to the server.

    Once we have the amount of messages, the only thing left is to assemble our collection; this is what we do in the FOR, creating the objects "WMAIL" and assigning it to every position in the array.

    Now, the WMAIL class is in charge of obtaining every individual message and processing it as necessary. This class is fairly big, and it isn't worthwhile to talk about something which you can see for yourself without too much complexity, therefore, we will only see some of the things it does for us.

    For example, after reading a message, we will probably want to delete it from the server; for this purpose we implement the method DeleteMail.

    PROCEDURE DeleteMail()
       LOCAL lsMsgReturn
       IF( This.m_SocketConn.Enviar( "DELE " + ALLTRIM( STR( This.m_MailNum ) ) + ENTER ) )
          IF( This.VerificarRespuesta( This.m_SocketConn.Recibir(), NO_ERRORS ) )
             RETURN .T.
          ENDIF
       ELSE
          RETURN .F.
       ENDIF
    ENDPROC
    

    As we can see, the only thing we do in this case is send the "DELE" command to the server, so that the server does the work.

    We can also save the attachments that we receive in our e-mail. For this purpose, we use the class method GrabarAdjunto. This method receives, as a parameter, the number of the attachment we want to save, and the name we want to give to this attachment.

    PROCEDURE GrabarAdjunto( lnNumMail, lsFileName )
       LOCAL lsAdjunto, lnDesde, lnHasta
       LOCAL lsLine
    
       lnDesde   = This.MailAdjuntos[lnNumMail, 2]
       lnHasta   = This.MailAdjuntos[lnNumMail, 3]
       lsAdjunto = SUBSTR( This.MailData, lnDesde, lnHasta - lnDesde )
    
       *|-- Cuts off the surplus.-
       lsLine = MLINE( lsAdjunto, MEMLINES( lsAdjunto ) )
       IF( !EMPTY( lsLine ) )
          lsAdjunto = SUBSTR( lsAdjunto, 1, LEN( lsAdjunto ) - LEN( lsLine ) )
       ENDIF
    
       *|-- Now, take away the CHR(13) and the CHR(10), since these are not part of
       *|-- Encode64, but rather a part of POP3.-
       lsAdjunto = STRTRAN( lsAdjunto, CHR(10), "" )
       lsAdjunto = STRTRAN( lsAdjunto, CHR(13), "" )
    
       RETURN FileDecode64( lsAdjunto, lsFileName )
    ENDPROC
    

    Here we mention the function "FileDecode64", which is stored in the library FoxUtils.fll. We have already seen part of the implementation, so now we only need to publish the function. We can do this more or less as follows:

    FoxInfo myFoxInfo[] =
    {
       {"FileDecode64", (FPFI) FileDecode64, 2, "CC"},
    };
    extern "C" {
    FoxTable _FoxTable =
    {
        (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
    };
    

    We will now see how we can use this class, from a simple VFP application.

    oMail = CREATEOBJECT( "WPop3" )
    
    *|-- Usario y password
    oMail.m_HostName = "ServerPOP3"
    oMail.m_UsrPOP3  = "ianni_roberto"
    oMail.m_PassPOP3 = "123456"
    oMail.m_TipoAut  = 0   && Plain
    
    IF( oMail.RecivirMails() )
       FOR I = 1 TO oMail.m_NumMsg
    
          *|-- Propiedades....
          ?oMail.m_MailList[I].MailSubject
          ?oMail.m_MailList[I].MailDate
          ?oMail.m_MailList[I].MailBody
          ?oMail.m_MailList[I].MailReceiver
    
          *|-- For the attachments, the FoxUtils.fll class is required,
          *|-- available at http://www.foxlook.com.ar/developer/help/FoxUtils.Fll.zip
    
          *|-- Loop through the attachments.-
          FOR J = 1 TO oMail.m_MailList[I].MailCantAdjuntos
             oMail.m_MailList[I].GrabarAdjunto( J, "C:\" + oMail.m_MailList[I].MailAdjuntos[I, 1] )
          NEXT
    
          *|-- Save the entire e-mail to disk.
          STRTOFILE( oMail.m_MailList[I].MailData, "C:\Mail" + ALLTRIM( STR( I ) ) + ".txt" )
       NEXT
    
       *|-- Disconnect the POP3
       oMail.QuitPOP()
       oMail.Desconectarme()
    ELSE
       MESSAGEBOX( "Error: " + ALLTRIM( oMail.m_Error ) )
    ENDIF
    RELEASE oMail
    

    As we see in the example, it is very simple to use the class that receives the messages from the POP3 server and saves them to the local disk. We only have to specify the name of the server, the user and the password in order to receive all the messages in our mailbox, including all the attachments.

    Test application

    As a test application, I include a simple program called "FoxLook Monitor", which stays in the SysTray, checking for messages on the server, and, in case it finds them, showing a nice little window which informs us about the new message (similar to MSN).

    As some of you know, or will notice, the test application is called "FoxLook Monitor", because I called the project for sending and receiving messages "FoxLook". For those interested, the Web site is www.foxlook.com.ar/developer. There, you can obtain the complete classes and the latest versions and improvements done to the classes.

    Roberto C. Ianni, Banco Hipotecario
    Roberto Ianni (Buenos Aires, Argentina) is a professional software developer for the last 4 years. He has programmed in C++, VFP, VC++, C#, and has lots of experience in VFP and VC++. He currently works in the Banco Hipotecario as an Analyst-Developer.
    More articles from this author
    Roberto C. Ianni, December 1, 2004
    Today we are going to talk about data compression. This is something we do frequently, something which is useful for many different tasks.
    Roberto C. Ianni, June 1, 2004
    The "EventLog" or "Event viewer" is something we use on a daily basis, to obtain information about what happened to our computer at a certain moment; for example, when an application generates an error, most of us will intuitively look at the EventLog to see what information it left for us.
    Roberto C. Ianni, May 1, 2004
    The "EventLog" or "Event viewer" is something we use every day, to obtain information about what happened to our computer at a certain moment. For example, when an application generates an error, most of us instinctively go to the EventLog to see what information it left for us.
    Roberto C. Ianni, July 1, 2004
    One of the great limitations I find in VFP is the lack of low-level manipulation I can obtain from it. For many, this might not be an inconvenience, but several times I have encountered a problem which can't be solved with Fox, and it is then that I use a DLL or an FLL.
    Roberto C. Ianni, August 1, 2004
    As an objective for this issue, I want to propose the following points, to complete the first part of the development of an FLL. Advanced development with an FLL; Accessing VFP data; Executing VFP commands from a FLL; MultiThreading; FLL with VFP 9.0; Executing Assembler from VFP.
    Roberto C. Ianni, October 1, 2004
    This article is the continuation of "Sending email through Visual FoxPro without additional components", published last March. Back when I wrote this article, I didn't think it would get such a huge response, but it did, hence, due to the mails coming from everywhere I decided to write again on that...
    Roberto C. Ianni, November 1, 2004
    In this second part we will learn how to: Implement MIME, Attach binary files, the final implementation of the WSendMail class, and the use of the WSendMail class.
    Roberto C. Ianni, May 1, 2005
    Windows Control Panel holds the configuration from several of the operating system's applications, and from some other applications that provide an applet to appear there. Learn how to build this kind of applets to give your application a professional configuration mechanism.
    Roberto C. Ianni, January 1, 2005
    This article is the beginning of receiving email from VFP; this is complemented with the articles you have already seen about sending email from VFP. To be able to do this, we will have to use the POP3 protocol (Post Office Protocol 3).
    Roberto C. Ianni, February 1, 2005
    We will continue some of the points mentioned in the previous article, in order to be able to implement message reception. The points to be developed are: POP3 Authentication (both Flat and MD5 authentication methods) and EMail parsing.
    Roberto C. Ianni, March 1, 2004
    Many of us send email using third-party tools such as Outlook Express or Microsoft Outlook, among others, and most likely have encountered registration problems in the client, licencing problems, OLE errors that only occurr at the client's site but not in our developer environment. Also problems if ...
    Roberto C. Ianni, August 1, 2006
    The present article is one of those that Martín Salías calls "low level fox", the general idea is to develop a service for the operative system whose only goal will be to run a VFP application in charge of handling the business logic.