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

Emails without additional components - The Resurrection - Part 1
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...
Summary
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 same topic adding some issues on which I was consulted and also some new ideas that occurred to me.
Description
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 same topic adding some issues on which I was consulted and also some new ideas that occurred to me.

First of all, I wish to thank to "Edelmiro" who phoned me from Spain two days after the publishing in order to help me with a little problem in the coding of the examples.

Objectives

I will propose to enhance the classes WSendMail and Wsocket in order to get some fixes on them and add new functionality.

We will go through the following points:

  1. Fix in Wsocket class in order to get a synchronous working.
  2. New properties and inheritance in WsendMail class.
  3. To control the commands sent to the Mail server.
  4. To send eMail with authenticated SMTP.
    1. Plain authentication
    2. 64 bits base authentication
  5. Generation of mail ID.
    1. GUID (Globally Unique Identifier) Format
    2. UUID (Universal Unique Identifier) Format
  6. MIME implementation.
  7. Attaching binary files.
  8. Implementation and use of the class.

Fix in WSocket class

In the first place, we must do a small fix to the Wsocket class, because the last article had a minor problem. It was that the Wsocket class worked in an asynchronous way, so, when sending an info pack to the server via the API function "send" it returned immediately, resulting in an asynchronous queuing of the information sending. Thus, when trying to send a new packet, there was no guarantee that the former one had been completely sent, so, the method "Sent" of the WSendMail class ended up believing that all required info was sent, which was not always correct.

In order to make the API "send" function work in synchronous mode, we should modify a socket option to have it working as we wish. We achieve this using another API function: "setsockopt".

optionValue = SO_SYNCHRONOUS_NONALERT

IF( setsockopt(             ;
        INVALID_SOCKET,     ;
        SOL_SOCKET,         ;
        SO_OPENTYPE,        ;
        @optionValue,       ;
        4                   ; && sizeof(int)
     ) == SOCKET_ERROR )

    RETURN .F.

ENDIF

This change should be added to the "CrearSock" method, thus making it work in a way that the synchronous mode will not produce non-controlled error messages.

Another critic modification that should be done is in the methods "Enviar" and "Recibir" [Send and Receive] where we need to call the "ioctlsocket" API function. As you may remember, we used this function to know whether the socket had pending info to receive or whether it was able to send, but the function leaves the socket in "nonblocking" state, meaning asynchronous; this makes it unfit for our purposes.

Once modified, the methods should look like this:

PROCEDURE CrearSock &CreateSock
   LOCAL wSpaceData
   LOCAL optionValue

   wSpaceData = SPACE( 400 ) && Space for struct WSAData

   *|-- Init WS2_32.DLL to use Sockets.-
   IF ( WSAStartup( THIS.MAKEWORD(1, 1), @wSpaceData) != 0 )
      RETURN .F. && The OS can't init socket
   ENDIF

   optionValue = SO_SYNCHRONOUS_NONALERT

   IF( setsockopt(          ;
           INVALID_SOCKET,    ;
           SOL_SOCKET,       ;
           SO_OPENTYPE,         ;
           @optionValue,      ;
           4                ; && sizeof(int)
           ) == SOCKET_ERROR )
      RETURN .F.
   ENDIF


   *|-- Get handle to OS' Socket
   THIS.m_HandleSocket = socket( This.m_SockFamily, This.m_SockType, 0 )
   IF THIS.m_HandleSocket == INVALID_SOCKET
      RETURN .F. && The OS can't get handle of socket
   ENDIF

ENDPROC

PROCEDURE Enviar( msg ) &Send
   LOCAL slen, nlenMsg, nindex, smsgAux, bWait, nCountWait
   LOCAL lnControl

   bWait      = .T.
   nCountWait = 0
   nlenMsg    = LEN( msg )
   nindex     = 0
   slen       = 0

   IF( This.CheckBeforeSend )
      nRead = 0x1
      DO WHILE( bWait )
         slen  = ioctlsocket( THIS.m_HandleSocket, FIONBIO, @nRead )
         IF ( slen != 0 ) && SOCKET_ERROR

            IF( nCountWait > 5 )
               RETURN .F.
            ELSE
               *|-- The server is busy.-
               nCountWait = nCountWait + 1
               Sleep( 50 )
            ENDIF
         ELSE
            bWait = .F.
         ENDIF
      ENDDO
   ENDIF

   smsgAux       = msg
   nlenMsgGlobal = nlenMsg
   nlenMsg2      = nlenMsg
   Vuelta        = 0
   PaqEnv        = 512

   IF( This.PaqueteEnvio <= 0 )
      PaqEnv = nlenMsgGlobal
   ELSE
      PaqEnv = This.PaqueteEnvio
   ENDIF

   DO WHILE( nlenMsgGlobal > 0 )

      smsgAux  = SUBSTR( msg, ( Vuelta * PaqEnv ) + 1, PaqEnv )
      nlenMsg  = LEN( smsgAux )
      nlenMsg2 = nlenMsg
      DO WHILE( nlenMsg > 0 )

         slen = send( THIS.m_HandleSocket, smsgAux, nlenMsg, This.SockSendCallMode )
         IF ( slen < 1 )
            RETURN .F. && Server Send failed.-
         ENDIF

         nlenMsg = nlenMsg - slen
         nindex  = nindex + slen
         smsgAux = SUBSTR( msg, nindex, LEN( msg ) - nindex )
      ENDDO

      nlenMsgGlobal = nlenMsgGlobal - nlenMsg2
      Vuelta        = Vuelta + 1
   ENDDO

   RETURN .T.
ENDPROC

PROCEDURE Recibir() &Receive
   LOCAL buffer, retval, reply, nRead, slen, bWait, nCountWait

   bWait      = .T.
   nCountWait = 0

   IF( This.CheckBeforeRec )
      nRead      = 0x1
      DO WHILE( bWait )
         slen  = ioctlsocket( THIS.m_HandleSocket, FIONREAD, @nRead )

         IF ( slen == SOCKET_ERROR OR nRead <= 0 )
            IF( nCountWait > 5 )
               RETURN ""
            ELSE
               *|-- The server is busy.-
               nCountWait = nCountWait + 1
               Sleep( 50 )
            ENDIF
         ELSE
            bWait = .F.
         ENDIF
      ENDDO
   ENDIF

   reply  = ""
   buffer = ""
   retval = 0

   DO WHILE( retval = 0 )
      buffer = SPACE( 4096 )
      retval = recv( THIS.m_HandleSocket, @buffer, LEN( buffer ), 0 )
      IF( retval == SOCKET_ERROR )
         LOCAL nErr
         nErr = WSAGetLastError()
      ENDIF
      IF retval <> 0 AND retval <> SOCKET_ERROR
         reply = reply + LEFT( buffer, retval )
      ENDIF
   ENDDO

   RETURN reply
ENDPROC

As you can see, in the methods "Enviar" (Send) and "Recibir" (Receive), before calling the API function "ioctlsocket", the state of the CheckBeforeSend and CheckBeforeRec properties is checked. They are false by default, so that the state of the socket remains unmodified. You will also notice that comments are both in Spanish and English, according to the requests I received, since many people don't know enough Spanish. I tried my best in this issue, since my English is not too good.

The method "Enviar" (Send) has a change that will be apparent for those who read the former article, which consists in how the info packets are generated. Originally, the whole info was sent to the "send" and what couldn't be sent was processed in a later cycle, but there is a property now called "PaqueteEnvio" that defines the maximum size for the packet that will be sent to the server, and if it is bigger than the proper size, the string is truncated and is sent in a later round. The property "PaqueteEnvio" is set to -1 by default, so that it will try to send the info all at once, but if any server requires to set a maximum per packet, you have to modify this property.

New properties and inheritance in the WSendMail class

At this point, we will see the new properties that our mailing class will expose and how to use them. Then, we will change the inheritance for the class and discuss a bit why to do this.

New properties:

  1. m_UsrSMTP
  2. m_PassSMTP
  3. m_TipoAut
  4. m_UltMsg

  1. m_TipoMailPartID
  2. m_MailPartID
  3. m_SeparadorDir
  4. m_MIME_Version
  5. m_SocketConn (Protected)
  6. m_ObjUtils (Protected)
  7. m_HostName
  8. m_HostPort

m_UsrSMTP: This is the user name that will be used to authenticate with the SMTP server.

m_PassSMTP: This is the password that will be used to authenticate with the SMTP server.

m_TipoAut: This property accepts one of two values.

  • If it is set to zero, a plain authentication will be used, meaning there will be no encryption and the data will be sent to the server just as they are.
  • If it is set to 1, an encryption in 64 base will be applied to the data before it is sent to the server

    m_UltMsg: Messages received from the SMTP server are stored here.

    m_TipoMailPartID: This property accepts one of two values.

  • If it is set to zero, the GUID algorithm will be used to establish the boundary of the mail.
  • If it is set to 1, the UUID algorithm will be used to establish the boundary.

    m_MailPartID: Holds the ID generated for the mail boundary.

    m_SeparadorDir: Holds the character to separate the addresses set in the "m_Destinatarios" (recipients). By default, ";"

    m_MIME_Version: Holds the MIME version for the mail to be sent. By default, "1.0"

    m_SocketConn: Holds the connection socket object, the WSocket class.

    m_ObjUtils: Holds the utilities object, the FoxUtils class.

    m_HostName: Holds the name of the SMTP host we will connect to.

    m_HostPort: Port of the SMTP host we will connect to, by default: 25.

    Those who read my first article may be surprised to find the m_SocketConn, m_HostName and m_HostPort properties, formerly unneeded. The reason for this is simple, the "WSendMail" class does not inherit from "WSocket" (which provides the connection) anymore, instead, it uses it as a "Socket" object, just as "WinSock".

    This simple change, as you will soon see, has a reason, and will give use certain benefits.

    To control the commands sent to the eMail server

    One of the greats weaknesses of the former version of the "WSendMail" class is that it did not validate the messages returned by the SMTP server, meaning each time we send a command to the SMTP server, this responds with a number and a descriptive message.

    Let's see an example with telnet.

    220 postino3.prima.com.ar ESMTP
    HELO 10.10.10.1
    250 postino3.prima.com.ar
    MAIL FROM:<ianni_roberto@hotmail.com>
    250 ok
    RCPT TO:<ianni_roberto@hotmail.com>
    250 ok
    DATA
    354 go ahead
    X-Sender: ianni_roberto@hotmail.com
    X-Mailer: M$ SendMail Build 1.0.0019
    Date: Sat, 24 Jan 2004 13:45:00 0300
    From: "My Name and Surname"
    Subject: Mail Test with Telnet
    To: "IANNI, ROBERTO" <ianni_roberto@hotmail.com>
    
    Prueba de texto en el cuerpo del mail (Email body)
    
    .
    250 ok 1074969345 qp 85748
    
    QUIT
    221 postino3.prima.com.ar
    

    So, looking at this old example we realize that when we connect to the SMTP we get a message "220 postino3...", this means that it IS an SMTP and it is ready to receive a message.

    Then we can see that each command produces a response, being "HELO"; "MAIL FROM", "RCPT TO", "DATA", "QUIT", all of them trigger responses.

    We will create a method called VerificarRespuesta [Verify Reply] in the WSendMail class to verify the commands. This method will have the responsibility of telling whether or not the response is correct.

    #DEFINE CHK_220_REPLY   "220"
    #DEFINE CHK_221_REPLY   "221"
    #DEFINE CHK_235_REPLY   "235"
    #DEFINE CHK_250_REPLY   "250"
    #DEFINE CHK_334_REPLY   "334"
    #DEFINE CHK_354_REPLY   "354"
    
    PROTECTED PROCEDURE Helo
       ...
       ...
       THIS.m_SocketConn.Enviar( "HELO " + ALLTRIM( LocalHost ) + ENTER )
       IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_250_REPLY ) )
          RETURN .F.
       ENDIF
       ...
       ...
    ENDPROC
    
    PROCEDURE VerificarRespuesta( lsString, lsToVerify ) &Verify Reply
       This.m_UltMsg = lsString
       RETURN( SUBSTR( lsString, 1, LEN(lsToVerify) ) == lsToVerify )
    ENDPROC
    

    You can see that this is quite simple, but we need no more to achieve our goal.

    Sending email with authenticated SMTP

    This is one of the most popular issues, according to the mails I received after publishing the first article. So, let us look into it.

    Some SMTP servers support authentication. For some of them, authentication is optional and for others, it is mandatory. To deal with this, SMTP provides the EHLO and AUTH commands. An SMTP server that supports authentication will respond to the EHLO command with the code "250". What is the EHLO command for? It accomplishes the same function that HELO; to hail (contact) the server, but in addition, (since it is an extended command) it also inform in advance that it will use authentication.

    Provided SMTP properly responded to EHLO, we are able to choose what kind of authentication to use. We will only explain the two more commonly used methods here, but there are others, depending on the SMTP server and its version.

    For our examples, the AUTH command is used as follows:

  • Plain authentication: "AUTH PLAIN"
  • 64base authentication: "AUTH LOGIN"

    After sending the proper AUTH and type, SMTP responds with the code "334" thus informing that the server supports that kind of authentication. Afterwards, we just need to send user and password without any other command; provided the server accepts the user, it returns "334" and if the password is right, the answer is the code "235"; at this point, we are already authenticated into the server. Let us see some examples.

    Plain Authentication:

    220 postino3.prima.com.ar ESMTP
    EHLO 10.10.10.1
    250 postino3.prima.com.ar
    AUTH PLAIN
    334
    MyUser
    334
    MyPassword
    235
    
    QUIT
    221 postino3.prima.com.ar
    

    64base Autentificación:

    220 postino3.prima.com.ar ESMTP
    EHLO 10.10.10.1
    250 postino3.prima.com.ar
    AUTH LOGIN
    334
    TWlVc3Vhcmlv
    334
    TWlDbGF2ZQ==
    235
    
    QUIT
    221 postino3.prima.com.ar
    

    As you can see, plain authentication is pretty straight-forward; we just send the user name and password. However, with 64base authentication it is not so clear to the naked eye. In 64base, the user name and password are encrypted prior to be sent to the server validation. So, the actual string sent to the server was "TWlVc3Vhcmlv" while we really typed "MyUser" as we could see if the string got decrypted. And just the same happens with the password.

    Since we need to implement this, we will code the encryption and decryption 64base functions for VFP. For this goal, let us first analyze how the Encode64 would be written in C++ because this is a more natural language for this job, and then create a similar routine in VFP.

    char *m_base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    bool InternalEncode64( const unsigned char *in, unsigned inLen, unsigned char *out, unsigned outmax)
    {
       unsigned olen = (inLen + 2) / 3 * 4;
       if (outmax < olen)
          return false;
    
       while (inLen >= 3)
       {
          *out++ = m_base64Table[in[0] >> 2];
          *out++ = m_base64Table[((in[0] << 4) & 0x30) | (in[1] >> 4)];
          *out++ = m_base64Table[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
          *out++ = m_base64Table[in[2] & 0x3f];
          in += 3;
          inLen -= 3;
       }
       if (inLen > 0)
       {
          *out++ = m_base64Table[in[0] >> 2];
          unsigned char oval = (in[0] << 4) & 0x30;
    
          if (inLen > 1)
             oval |= in[1] >> 4;
    
          *out++ = m_base64Table[oval];
          *out++ = (inLen < 2) ? '=' : m_base64Table[(in[1] << 2) & 0x3c];
          *out++ = '=';
       }
    
       if (olen < outmax)
          *out = '\0';
       return true;
    }
    

    Does it look entangled? Really it is not. 64base encryption relies on taking a certain character from the string, get its ASCII equivalent and modify that number so that it lies within the range 0 to 63.

    Looking at the "m_base64Table" variable, we find a string that depicts how the characters will be converted, as is shown in the following table:

    The 64base 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 + /

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

    • Operator ">>": binary "Shift Right", equivalent to dividing the number by 2.

      For instance, decimal 50 is binary 110010, if we move the binary representation one position to the right, it becomes "11001" (last zero is gone) and that is the binary for decimal 25, so, it is the same as dividing 50 by 2. You may ask why we should not just code the division. Well, the reason is that to divide and multiply are quite more expensive operations for the processor than the binary shifting. Think about a very big number, divided by two, and again the result by two and so on for fifty times. Expensive, isn't it? This is not so with the binary shifting.

    • Operator "<<": binary "Shift Left", equivalent to multiply the number by 2.

      The former example should suffice, just change the direction from right to left.

    • Operator "&": binary "And" on data.

      For example, the numbers 3 and 6 are:
      011
      110
      010 && 010 && would be the result for binary "And"

    • Operator "|": Inclusive binary "Or".

      For example, the numbers 5 and 6 are:
      101
      110
      111 && would be the result for Inclusive binary "Or"

    Knowing this, let us go back to the Encode64 algorithm. We find that the characters in the string are grouping by three and to the first group we apply:
    m_base64Table[in[0] >> 2]
    

    Then, the first character's ASCII value for the group is divided twice by two. If it was 256 (the biggest ASCII character), this operation would result into 64. Then, the position for that number is located in the array of Encode.

    Now for the two remaining characters of the group of three, the procedure is as follows:

    m_base64Table[((in[0] << 4) & 0x30) | (in[1] >> 4)];
    m_base64Table[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
    m_base64Table[in[2] & 0x3f];
    

    combining thus the first of them with the second, then obtaining the character in the array, then combining the second with the third and obtaining the position in the array, and finally, the third character operates a binary "And" with the mask 0x3f in order to obtain the character within the array. This cycle keeps repeating while there are at least three characters in the array available to process.

    Then, the remaining characters are processed (if any) keeping in mind that if a remainder exists the last character must be "=".

    Once Encode64 is understood, we can easily write it in VFP.

    PROCEDURE Encode64( lsIn, lsOut )
       LOCAL inlen, lsOut, lnInRel, oval
    
       inlen   = LEN( lsIn )
       olen    = (inlen + 2) / 3 * 4
       outmax  = inlen * 2
       lsOut   = ""
       lnInRel = 1
    
       IF( outmax < olen )
          RETURN .F.
       ENDIF
    
       DO WHILE( inlen >= 3 )
          lsOut = lsOut + This.m_base64Table[ (BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 2 )) + 1 ]
    
          lsOut = lsOut + This.m_base64Table[ (BITOR( BITAND( (BITLSHIFT( ASC( ;
           SUBSTR( lsIn, lnInRel, 1 )), 4 )), 0x30 ), ;
           BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 4 ) )) + 1]
    
          lsOut = lsOut + This.m_base64Table[ (BITOR( BITAND( (BITLSHIFT( ;
           ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 2 )), 0x3C ), BITRSHIFT( ;
           ASC(SUBSTR( lsIn, lnInRel + 2, 1 )), 6 ) )) + 1]
    
          lsOut = lsOut + This.m_base64Table[ (BITAND( ASC(SUBSTR( ;
           lsIn, lnInRel + 2, 1 )), 0x3F )) + 1 ]
    
          lnInRel = lnInRel + 3
          inlen   = inlen - 3
    
       ENDDO
    
       IF( inlen > 0 )
          lsOut = lsOut + This.m_base64Table[ (BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 2 )) + 1 ]
          oval = BITAND( BITLSHIFT( ASC(SUBSTR( lsIn, lnInRel, 1 )), 4 ), 0x30 )
    
          IF( inlen > 1 )
             oval = BITOR( BITRSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 4 ), oval )
          ENDIF
    
          lsOut = lsOut + This.m_base64Table[ oval + 1 ]
          lsOut = lsOut + IIF( (inlen < 2), '=', This.m_base64Table[ (BITAND( ;
           BITLSHIFT( ASC(SUBSTR( lsIn, lnInRel + 1, 1 )), 2 ), 0x3C )) + 1 ] )
          lsOut = lsOut + '='
       ENDIF
    
       RETURN .T.
    ENDPROC
    

    As we can see in the VFP code, the logic is the same but the way to perform the calculations varies. VFP lacks the operators ">>", "<<", "|", "&", but equivalent functions are available: BITRSHIFT, BITLSHIFT, BITOR, BITAND.

    Thus, replacing the operators with the functions, the work gets done. Of course, being functions, they work much slower than an operator, so the code is not efficient for very long strings, but since our goal is to encrypt just one user and one password, it is good enough.

    Now, with the encryption ready, we may create a new method in the WSendMail class that performs the authentication in the SMTP server.

    PROTECTED PROCEDURE Login
       LOCAL lsPass, lsUsr
    
       IF( EMPTY( This.m_UsrSMTP ) AND EMPTY( This.m_PassSMTP ) )
          RETURN .T.
       ENDIF
    
       DO CASE
          CASE This.m_TipoAut == 0
             THIS.m_SocketConn.Enviar( "AUTH PLAIN" + ENTER )
             IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) )
                RETURN .F.
             ENDIF
    
             lsUsr  = This.m_UsrSMTP
             lsPass = This.m_PassSMTP
          CASE This.m_TipoAut == 1
             THIS.m_SocketConn.Enviar( "AUTH LOGIN" + ENTER )
             IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) )
                RETURN .F.
             ENDIF
    
             IF( This.m_ObjUtils.Encode64( This.m_UsrSMTP, @lsUsr ) )
                IF( !This.m_ObjUtils.Encode64( This.m_PassSMTP, @lsPass ) )
                   RETURN .F.
                ENDIF
             ELSE
                RETURN .F.
             ENDIF
       ENDCASE
    
       *|-- Envía el usuario.-
       *|-- Sends the user name.-
       THIS.m_SocketConn.Enviar( lsUsr + ENTER )
       IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_334_REPLY ) )
          RETURN .F.
       ENDIF
    
       *|-- Envia el password.-
       *|-- Sends the password.-
       THIS.m_SocketConn.Enviar( lsPass + ENTER )
       IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_235_REPLY ) )
          RETURN .F.
       ENDIF
    
       RETURN .T.
    ENDPROC
    

    As you can see, the implementation is quite simple; we check first that a user and password are actually defined in the class, and then the type of authentication to be used. If it is plain, we send the data to the server just as it is. If it is 64base instead, we process the data with the "Encode64" before they get sent.

    Generation of the ID for the mail

    In order to generate the identification for the boundaries of the mail, we will implement two alternatives, the first one to generate GUIDs and the other to generate UUIDs. You will surely ask why to invest time to generate an ID for the boundary of the mail, so let us see the image.

    Figure 1: Parts of the Body

    The ID that defines the boundary is useful to separate the mail's blocks; if we use a separador defined by the user in the subject or the body of the message, is quite possible that the program that receives the mail gets confused with the boundaries thus misunderstanding something that is actually a text and taking it as a boundary.

    We will use in order to generate the GUID the API called CoCreateGuid and StringFromGUID2, whose definition is as follows:

    HRESULT CoCreateGuid(
      GUID  *pguid  //Pointer to the GUID on return
    );
    

    pguid: Pointer where the new GUID will be returned

    int StringFromGUID2(
      REFGUID rguid,  //GUID to be converted
      LPOLESTR lpsz,  //Pointer to resulting string
      int cchMax      //Size of array at lpsz
    );
    

    rguid: The GUID we want to convert to string.

    lpsz: The pointer to the string where the converted GUID will be stored.

    cchMax: Maximum size for the variable lpsz.

    DECLARE INTEGER CoCreateGuid IN "ole32" ;
       STRING @pGuid
    

    DECLARE INTEGER StringFromGUID2 IN "ole32" ;
       STRING rGuid, STRING @lpsz, INTEGER cchMax
    
    PROCEDURE CrearGuid
       LOCAL lsGuid, lsGGuid
    
       lsGuid = SPACE(16)
       lsGGuid = REPLICATE( CHR(0), 76 )
    
       *|-- Creates the GUID and converts it to a string.-
       CoCreateGuid( @lsGuid )
       StringFromGUID2( lsGuid, @lsGGuid, 76 )
    
       lsGGuid = STRTRAN( lsGGuid, CHR(0) )
       lsGGuid = SUBSTR( lsGGuid, 2, 36 )
    
       RETURN lsGGuid
    ENDPROC
    

    APIs in general are easy to use and as we see in the CrearGuid method, what we do is to define a variable 16 spaces long to be able to call the CoCreateGuid API function and then we convert it with the StringFromGUID2 function.

    Then, we locate the end of the string, given by CHR(0) and remove the first and last character from the result, since a GUID looks something like this: {F57E0FC1-17CC-49ED-A3A5-CCF10E08BDA1}, and we are only interested in the unique numbers: F57E0FC1-17CC-49ED-A3A5-CCF10E08BDA1

    The GUID separators are the "-" characters, and since we need a "_" character for separador, we'll create a new function to perform the replacement.

    PROCEDURE ObtenerGuid
       LOCAL lsGuid
       lsGuid = This.CrearGuid()
    
       RETURN STRTRAN( lsGuid, "-", "_" )
    ENDPROC
    

    The ObtenerGuid (GetGuid) method, is in charge of creating the GUID and returning it ready for use in the eMail.

    Now, to generate a UUID we will just use the API UUIDCreate function, which has the following definition:

    RPC_STATUS RPC_ENTRY UuidCreate(
      UUID *Uuid
    );
    

    Uuid: String type pointer, where the new UUID will be allocated

    PROCEDURE CrearUuid( lsUIDPart1, lsUIDPart2, lsUIDPart3, lsUIDPart4 )
       LOCAL lsUUID
       lsUUID = SPACE( 16 )
    
       *|-- Generates UUID.-
       UUIDCreate( @lsUUID )
    
       *|-- Convert values from binary to hex.-
       lsUIDPart1 = This.BinTOHex( SUBSTR( lsUUID,  1, 4 ) )
    

       lsUIDPart2 = This.BinTOHex( SUBSTR( lsUUID,  5, 4 ) )
       lsUIDPart3 = This.BinTOHex( SUBSTR( lsUUID,  8, 4 ) )
       lsUIDPart4 = This.BinTOHex( SUBSTR( lsUUID, 13, 4 ) )
    
    ENDPROC
    

    The UUIDCreate API function returns a string with the four parts of the ID but with such a conformation that we can not see them normally as if they were numbers. Hence, you need to take four bits of the string and convert them to a number that VFP is able to understand. The BinTOHex method does that. Finally, the combination of the four parts of the UUID conform the ID.

    PROCEDURE ObtenerUuid
       LOCAL lsUIDPart1, lsUIDPart2, lsUIDPart3, lsUIDPart4
       This.CrearUuid( @lsUIDPart1, @lsUIDPart2, @lsUIDPart3, @lsUIDPart4 )
    
       RETURN lsUIDPart1 + "_" + lsUIDPart2 + "_" + lsUIDPart3 + "_" + lsUIDPart4
    ENDPROC
    

    I considered it was not worth-while to implement the ID generator functions in VFP or C++ since APIs are available to do the job, but if someone is interested in a deeper insight in the ID creation theory, they may take a look at the following url http://www.opengroup.org/dce/info/draft-leach-uuids-guids-01.txt.

    To be continued...

    In the following and last issue of this article, we will see the rest of the functionality required to finish the implementation of the class, including:

    • Implementing MIME
    • Attaching binary files
    • Implementation of the WSendMail class
    • Using the class WSendMail

    By the way, let me tell you that I started to develop a system for sending and receiving mails, based upon these classes, via SMTP and POP3, called "FoxLook".

    This client will run in Win32 and ASP.NET (C#) modes; both for Intranet and Internet, using the FOX classes as the core of both projects. The project will be OpenSource as well as the classes, and I intend that everyone who is interested to participate, feel free to do so. At the beginning, I will concentrate all the code via mail at my address and afterwards, the idea is to upload it to some web site where it can be shared.

    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, 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, 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.
    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.