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

Emails without additional components - The Resurrection - Part 2
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.
Summary
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.
Description

This article is the continuation of "Emails 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.

In the first part, we saw some changes for the original WSocket class, new properties and inheritance for the WsendMail class, how to control the commands sent to the eMail server, how to send eMail with authenticated SMTP (in order to do this, we had to review the principles of the Base64 encryption) and how to generate unique GUIDs for our eMail messages.

Now, to continue, we will see how to:

  • Implement MIME
  • Attach binary files
  • Final implementation of the WSendMail class
  • The use of the WSendMail class

So, let's start.

Implementing MIME

At this point, we will just focus on the MIME description for attached files. Although MIME defines many things, we will see the "Content-Type:" property, which attaches the file.

	------=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1
	Content-Type:application/x-msexcel; name=Archivo.xls
	Content-Transfer-Encoding: base64
	Content-Disposition: attachment; filename="Archivo.xls"

The Content-Type in the attached file is a description used by the mail that gets the file in order to know the origin of the file and which is the proper application to open it. Some extensions can be considered fixed by default, while other can not. You have to look up the non fixed extensions in the registry. They are located at "HKEY_CLASSES_ROOT\MIME\Database\Content Type". Once you locate the extension here, you can get the name of the contents.

For this purpose, we will implement three new methods called "ObtenerMimeType" [GetMimeType], "ObtenerHardCodeMime" [GetHardCodeMime] and "ObtenerMimeDinamico" [GetMimeDynamic].

   PROCEDURE ObtenerMimeType( lsFileName )

      LOCAL lsReturn, lsFileExt
      lsReturn  = "text/plain;charset=iso-8859-1"
      lsFileExt = JUSTEXT( lsFileName )

      IF( !This.ObtenerHardCodeMime( lsFileExt, @lsReturn ) )
         This.ObtenerMimeDinamico( lsFileExt, @lsReturn )
      ENDIF

      lsReturn = lsReturn + ";"
      RETURN lsReturn
   ENDPROC

   PROCEDURE ObtenerHardCodeMime( lsFileExt, lsMimeType )

      LOCAL lsReturn

      lsReturn  = .T.
      lsFileExt = LOWER( lsFileExt )

      DO CASE
         CASE lsFileExt = "xls"
            lsMimeType = "application/x-msexcel"
         CASE lsFileExt = "doc"
            lsMimeType = "application/msword"
         CASE lsFileExt = "rtf"
            lsMimeType = "text/richtext"
         CASE lsFileExt = "htm"
            lsMimeType = "text/html"
         CASE lsFileExt = "aiff"
            lsMimeType = "audio/x-aiff"
         CASE lsFileExt = "au"
            lsMimeType = "audio/basic"
         CASE lsFileExt = "wav"
            lsMimeType = "audio/wav"
         CASE lsFileExt = "gif"
            lsMimeType = "image/gif"
         CASE lsFileExt = "jpg"
            lsMimeType = "image/jpeg"
         CASE lsFileExt = "pjpg"
            lsMimeType = "image/pjpeg"
         CASE lsFileExt = "tif"
            lsMimeType = "image/tiff"
         CASE lsFileExt = "png"
            lsMimeType = "image/x-png"
         CASE lsFileExt = "xbm"
            lsMimeType = "image/x-xbitmap"
         CASE lsFileExt = "bmp"
            lsMimeType = "image/bmp"
         CASE lsFileExt = "avi"
            lsMimeType = "video/avi"
         CASE lsFileExt = "mpeg"
            lsMimeType = "video/mpeg"
         CASE lsFileExt = "ps"
            lsMimeType = "application/postscript"
         CASE lsFileExt = "hqx"
            lsMimeType = "application/macbinhex40"
         CASE lsFileExt = "pdf"
            lsMimeType = "application/pdf"
         CASE lsFileExt = "tgz"
            lsMimeType = "application/x-compressed"
         CASE lsFileExt = "zip"
            lsMimeType = "application/x-zip-compressed"
         CASE lsFileExt = "gz"
            lsMimeType = "application/x-gzip-compressed"
         OTHERWISE
            lsReturn = .F.
      ENDCASE

      RETURN lsReturn

   ENDPROC

   PROCEDURE ObtenerMimeDinamico( lsFileExt, lsMimeType )
      LOCAL lnErrCode, lsKeyPath, lnSubBranch
      LOCAL lnKeyEntry, lsNewKey, lnKeySize, lsBuf, lnBufLen, lsRetTime
      LOCAL lnSubKey, lnType, lsData, lnDataLen
      LOCAL lbReturn, lbExit

      lsFileExt   = "." + LOWER( lsFileExt )
      lsKeyPath   = "MIME\Database\Content Type"
      lnSubBranch = 0
      lbReturn    = .T.
      lbExit      = .F.

      lnErrCode = RegOpenKey( HKEY_CLASSES_ROOT, lsKeyPath, @lnSubBranch )

      IF( lnErrCode = 0 )
         lnKeyEntry = 0

         *|-- Go through every branch of the "Content Type" folder.-
         DO WHILE .T.
            lnKeySize = 100
            lnBufLen  = 100
            lsNewKey  = SPACE( lnKeySize )
            lsBuf     = SPACE( lnBufLen )
            lsRetTime = SPACE( 100 )

            lnErrCode = RegEnumKeyEx( lnSubBranch, lnKeyEntry, @lsNewKey, @lnKeySize, 0, ;
             @lsBuf, @lnBufLen, @lsRetTime )

            IF( lnErrCode = 259 OR lnErrCode != 0 )
               lbReturn = .F.
               EXIT   && Sale.-
            ENDIF

            *|-- Remove useless info.-
            lsNewKey = ALLTRIM( lsNewKey )
            lsNewKey = LEFT( lsNewKey, LEN( lsNewKey ) - 1 )

            *|-- Now that I've got the name of the branch of the 
            *|-- "Content Type" folder I open it to get the extension.
            lnErrCode = RegOpenKey( HKEY_CLASSES_ROOT, lsKeyPath + "\" + lsNewKey, @lnSubKey )
            IF( lnErrCode = 0 )
               lnType    = 0
               lnDataLen = 256
               lsData    = SPACE( lnDataLen )

               RegQueryValueEx( lnSubKey, "Extension", 0, @lnType, @lsData, @lnDataLen )

               IF( lnType = REG_SZ )
                  lsData = LEFT( lsData, lnDataLen -1 )

                  IF( lsData == lsFileExt )
                     lsMimeType = lsNewKey
                     lbReturn   = .T.
                     lbExit     = .T.
                  ENDIF
               ENDIF

               *|-- Closes SubBranch.-
               RegCloseKey( lnSubKey )

               *|-- Exits the cycle.-
               IF( lbExit == .T. )
                  EXIT
               ENDIF
            ELSE
               lbReturn = .F.
               EXIT   && Sale.-
            ENDIF

            lnKeyEntry = lnKeyEntry + 1
         ENDDO

         *|-- Closes main Branch.-
         RegCloseKey( lnSubBranch )
      ELSE
         lbReturn = .F.
      ENDIF

      RETURN lbReturn
   ENDPROC

Figure 2: MIME Type

ObtenerMimeType is in charge to get the type, whether fixed or dynamic. Now, if you look at the ObtenerHardCodeMime method, you will realize that its only task is to get the name of the extension as a parameter and look if it is in the CASE, this is so because those extensions are considered to be the most usual.

The ObtenerMimeDinamico method is the most complex of all three, if first open the Branch of the registry with the "RegOpenKey" function and then, using the API function "RegEnumKeyEx" it gets all sub branches of the branch, as you can see in figure 2.

Then, the RegOpenKey API function opens the branch "MIME\Database\Content Type". The "RegEnumKeyEx" API function returns every branch contained in it, for our case, "application/bgl", "application/cdf", "application/fractals", "application/futuresplash", and so on.

When RegEnumKeyEx returns the first Branch, "application/bgl", the ObtenerMimeDinamico method composes a new Branch containing the main branch and the one it just found, resulting for our first case like this "MIME\Database\Content Type\application/bgl".

These branches usually, but not always, have a "Key" called "Extension" that tells us with which extension the branch is associated, that is, the name of the Mime-Type itself.

Figure 3: Branch in the Registry

With the name of the new branch just formed, ObtenerMimeDinamico opens that branch and obtains the "Key" value. If the value matches with the extension of the file, then the search is over because the Content-Type name for the file is the one that we are upon. Otherwise, we keep searching.

In case we do not find any kind of application related to our extension, either fixed or dynamic, we just associate the "text/plain" type, in order to pass it as normal text.

I did not dig in too deeply into the API functions for the registry since I assume that they are well known for almost every one and that they don't represent great complexity.

Attach binary files

One of the most frequent questions, along with authentication, about the first issue of this article, was the missing explanation on attaching binary files. Well, now is the time to do it, and when it gets explained, you won't believe it.

To achieve this, two things will be required, the first of them is the right understanding of how Encode64 works and that you know how to built an FLL. For those who want to read the article that I wrote on this, it can be found at "Developing FLL libraries"

You will surely ask what Encode64, that we used to authenticate with the SMTP, has to do with sending a binary file, and even more: What does an FLL have to do with all this. The answers are simple: in order to send an attached file, the best way is to encrypt it with Base64 so that the SMTP just gets the characters "A-Z, a-z, 0-9 and +/", this way, we guaranty that no CHR(0) will be sent, which for C++ means to stop reading the string, as well as other characters that can cause trouble when the file is received.

Then, in order to send the file, we just need to get it processed with the Encode64 function that we developed in VFP, and send the result to the SMTP. Then, I figure out that we could do this:

   PROCEDURE FileEncode64( sFileName )
      LOCAL lsEncodeFile, lsFile

      *|-- Read the file from the variable lsFile.-
      lsFile = FILETOSTR( sFileName )

      *|-- Pass the file under Encode64.-
      lsEncodeFile = ""
      Encode64( lsFile, @lsEncodeFile )

      *|-- Return the value.-
      RETURN( lsEncodeFile )
   ENDPROC

Easy isn't it? But unfortunately it can not be that easy. This FileEncode64 function will work right, and the result will be proper, and if we send the file there will be no problem because the SMTP server will receive it fine and the POP3 client will take it for a normal mail with its related attachments; then... What could be the problem? If we use the VFP Encode64 function to encrypt, say, a 264KB file (something quite common to be sent by mail) this will need about 270336 rounds, since that is the amount of bits or characters in the file, hence generating cycles in the FOR calling the functions SUBSTRING; BITAND; BITOR, BITRSHIFT, BITLSHIFT for each cycle. This results in the function working too show. This is where the FLL enters the scene.

If we recall the first function we saw to study the base64 algorithm, InternalEncode64, we realize that the problem is already solved, we just need to include it in an FLL and generate it as a public function that communicates with VFP.

   void FAR FileEncode64(ParamBlk FAR *parm)
   {
      char FAR *sFileName = NullTerminate(&parm->p[0].val);
      double nLenAdjunto = parm->p[1].val.ev_real;

      int nReturn = 1;
      FILE *fo;

      if( (fo = fopen( sFileName, "rb")) != NULL )
      {
         unsigned char *buf = NULL, *fBuf = NULL;
         double fSize, bufLen;

         // Calculates size of the file in bits.-
         fseek( fo, 0, SEEK_END );
         fSize = ftell( fo );
         fseek( fo, 0, SEEK_SET );

         // Assigns memory and reads the whole file.-
         fBuf = new unsigned char[ (size_t)fSize ];
         fread( fBuf, sizeof( char ), (size_t)fSize, fo );
         fclose( fo );

         bufLen = (unsigned int)(fSize * 1.5);
         buf    = new unsigned char[(size_t)bufLen];

         // Applies Encode64 to the file.-
         InternalEncode64( fBuf, (unsigned int)(fSize), buf, (unsigned int)bufLen );

         bufLen = strlen( (char*)buf );
         if( nLenAdjunto <= 0 || nLenAdjunto >= bufLen )
            _RetChar( (char*)buf );
         else
         {
            unsigned char *newbuf = NULL;

            // Actual length of the file once processed by Encode64.-
            double nLenBuf  = bufLen, lnNewLenBuf;

            // Add ENTER'S to the real length in order give it the desired format.-
            lnNewLenBuf = nLenBuf + (int)( ( (nLenBuf / nLenAdjunto) * 2 ) + 6 );

            newbuf = new unsigned char[ (size_t)lnNewLenBuf ];

            // Format the string.-
            EncuadraString( buf, nLenBuf, newbuf, lnNewLenBuf, nLenAdjunto );

            _RetChar( (char*)newbuf );
            delete [] newbuf;
         }

         delete [] fBuf;
         delete [] buf;
      }
      else
         _RetChar( "-" );
   }

   FoxInfo myFoxInfo[] =
   {
      {"FileEncode64", (FPFI) FileEncode64, 2, "CN"},
   };

   extern "C" {
   FoxTable _FoxTable =
   {
      (FoxTable *) 0, sizeof(myFoxInfo)/sizeof(FoxInfo), myFoxInfo
   };
   }

This, then, is the FileEncode64 function, which receives as a parameter the name of the file to encrypt. In the first step, it opens the file. If it couldn't open it, it returns the character "-" and exits, calculating the size of the file. For this, it goes to the end of the file and ask for its position via the "ftell" function, getting back to the beginning after this. The size of the file is stored in a variable called "fBuf" with the amount of memory required to read the complete file and then it is passed to memory with the "fread" function. After that, you just call the function InternalEncode64 to encrypt the file, and that's it. You may be asking yourselves what is the difference between this and the VFP function. Well, basically the FLL is a C++ compiled code, instead of interpreted, like is FOX. Besides, the C++ function does not need functions to perform the conversions; instead, it uses the binary operators in a natural way, which is why it is far faster.

After encoding the file, we ask for the second parameter of the function, allocated in the variable "nLenAdjunto", and if the value is less than or equal to zero or greater than the size of the encrypted file, it returns the value and exits, otherwise, it gives a proper format to the value of the encrypted string using the EncuadraString function.

   void EncuadraString( const unsigned char *in, double inLen, 
    unsigned char *out, double outmax, double nLenAdjunto )
   {
      double i = 1, nPos;
      for( nPos = 0; nPos < inLen; nPos ++ )
      {
         *out++ = in[(size_t)nPos];

         if( i == nLenAdjunto )
         {
            *out++ = '\r';
            *out++ = '\n';
            i = 1;
         }
         else
            i++;
      }
      *out++ = '\0';

      return;
   }

As we can see, this function just adds an ENTER to the string each certain number of characters. Why? Because Encode64 returns one only string of text with the complete file, as follows:

QXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaG....

This may result in a too long string, depending upon the size of the file, and according the Standard RFC 822 each line sent to the SMTP can not exceed 1000 bits, meaning 998 plus two for CHR(13) + CHR(10).

Actually, this is not a limitation for many new SMTP servers nowadays and they can normally process strings with more than 1000 bits, but the limitation of the standard must be considered, and there are still servers with that limitation, as defined by the standard. That is why this function must be applied to the string returned by Encode64, so that the string does not exceed 1000 bits per line, as the following one:

   QXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcn
   VlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2

The length for each line is defined by us, provided it is lesser or equal to 1000 bits, if we intend to respect the standard. Now, if we know that the server is free of this limitation, it is convenient to send just one line, since the size of each line we define for the file gets increased by two more bits, and the final size of the file is increased, thus giving a greater load to the POP3 client.

Now, we just need to modify our FileEncode64 VFP method in order to call the FLL.

   PROCEDURE FileEncode64( sFileName, lsEncodeFile, lnLargoCadenaAdjunto )
      lsEncodeFile = FileEncode64( sFileName, lnLargoCadenaAdjunto )
      RETURN ( lsEncodeFile != "-" )
   ENDPROC

As you can see, the FileEncode64 method is quite simple, it just calls the FileEncode64 function in the FLL and if the character "-" is returned (could not open the file) it returns false.

The only thing left is to see how this applies to the attached file; once the file is encrypted with base64 we do just the same as before, but defining one more property to the file that indicates to the client recipient that the file is encrypted as such.

   ------=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1
   Content-Type:application/x-msexcel; name=Archivo.xls
   Content-Transfer-Encoding: base64
   Content-Disposition: attachment; filename="Archivo.xls"

From the code above, we already know the "Content-Type" property, and we find now the "Content-Transfer-Encoding" property. This one indicates to the client in what form the file was transferred, and its values may be: "base64", "X-token" and "quoted-printable" for MIME. We will use "base64" for sending of files.

Then, a typical mail with an encrypted file in base64 would look like this:

   From: "Roberto Ianni" 
   To: ianni_roberto@hotmail.com
   Subject: Test
   Date: Tue, 31 Aug 2004 07:55:54 -0300
   Mime-Version: 1.0
   Content-Type: multipart/mixed; boundary="----=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1"

   This is a multi-part message in MIME format.

   ------=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1
   Content-Type: text/plain; charset=iso-8859-1; format=flowed

   Text in the body of the mail

   ------=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1
   Content-Type:text/plain; name=archivo.txt
   Content-Transfer-Encoding: base64
   Content-Disposition: attachment; filename="archivo.txt"

   QXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcn
   VlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2
   NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZS
   BwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJh
   c2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2by
   BkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29u
   IGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaG
   l2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEg
   Y29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQX
   JjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVl
   YmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA
   0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBw
   cnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2
   U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBk
   ZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIG
   Jhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2
   byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY2
   9uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJj
   aGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYm
   EgY29uIGJhc2U2NA0KQXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0K
   QXJjaGl2byBkZSBwcnVlYmEgY29uIGJhc2U2NA0K

   ------=_NextPart_F57E0FC1_17CC_49ED_A3A5_CCF10E08BDA1--

Implementing and using the class

In this section, I want to remark how to implement all that was seen so far using the existent classes.

The classes to use are the two we already know, WSocket and WSendMail for the Socket connection and for implementation of SMTP, but also a new one will be created, called FoxUtils that will be used, for instance, to get the MimeTypes, to generate Encode, etc. Besides these VFP classes, the creation of an FLL will be necessary too, and it will be named FoxUtils and will be called from the FoxUtils class of FOX.

The WSendMail class has gone through several changes; we will focus just on the major changes, that happen to be SendMail method and the validations of the info packets sent and the server's responses.

  1. Formerly, we just called the method "Enviar" [Send] without verifying the return value, now, instead, we always check the value returned by the class WSocket.
    IF( !THIS.m_SocketConn.Enviar( ENTER + "." + ENTER ) )
       RETURN .F.
    ENDIF
    

  2. After each command sent to the SMTP server, this sends back a return code, which may be or not an error. This was disregarded before, but is validated now as follows:
    *|-- Sends command HELO.-
    IF( !THIS.m_SocketConn.Enviar( "HELO " + ALLTRIM( LocalHost ) + ENTER ) )
       RETURN .F.
    ENDIF
    
    *|-- Verifies the answer to the command HELO.-
    IF( !This.VerificarRespuesta( THIS.m_SocketConn.Recibir(), CHK_250_REPLY ) )
       RETURN .F.
    ENDIF
    

  3. I think that the SendMail method carries most of the changes in order to wrap in some extent all that was seen so far, if we attend the flow chart we could see how it is now and what it does.

Figure 4: Flow Chart

We already saw a lot about the FoxUtils class while developing this article, hence, we are just going to show how the class finally gets finished as if it was abstract and we will explain each method.

DEFINE CLASS FoxUtils AS Custom

   Name = "FoxUtils"

   *|-- Table for encode.-
   DIMENSION m_base64Table[64]
   m_base64List = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

   PROCEDURE Init
      ...
      *|-- Fills encode's array with the value of m_base64List.-
      lnLen = LEN( This.m_base64List )
      DIMENSION This.m_base64Table[ lnLen ]

      FOR i = 1 TO lnLen
         This.m_base64Table[i] = SUBSTR( This.m_base64List, i, 1 )
      NEXT

      IF( !EMPTY( This.m_LibPath ) )
         lsLibFile = ADDBS(This.m_LibPath) + "FoxUtils.fll"
      ELSE
         lsLibFile = "FoxUtils.fll"
      ENDIF

      IF( FILE(lsLibFile) )
         SET LIBRARY TO &lsLibFile
      ENDIF
      ...
   ENDPROC

*|---------------------------------------------------------------------------
*|--------------------------- MIME Functions ---------------------------
*|---------------------------------------------------------------------------
   PROCEDURE ObtenerMimeType( lsFileName )
      ...
   ENDPROC

   PROTECTED PROCEDURE ObtenerMimeDinamico( lsFileExt, lsMimeType )
      ...
   ENDPROC

   PROTECTED PROCEDURE ObtenerHardCodeMime( lsFileExt, lsMimeType )
      ...
   ENDPROC

*|---------------------------------------------------------------------------
*|-------------------------- Base64 Functions --------------------------
*|---------------------------------------------------------------------------
   PROCEDURE Encode64( lsIn, lsOut )
      ...
   ENDPROC

   PROCEDURE FileEncode64( sFileName, lsEncodeFile, lnLargoCadenaAdjunto )
      ...
   ENDPROC

*|---------------------------------------------------------------------------
*|---------------------------------- GUID -----------------------------------
*|---------------------------------------------------------------------------
*|-- Ref.: http://www.opengroup.org/dce/info/draft-leach-uuids-guids-01.txt 

   PROTECTED PROCEDURE CrearGuid
      ...
   ENDPROC

   PROTECTED PROCEDURE CrearUuid( lsUIDPart1, lsUIDPart2, lsUIDPart3, lsUIDPart4 )
      ...
   ENDPROC

   PROCEDURE ObtenerUuid
      ...
   ENDPROC

   PROCEDURE ObtenerGuid
      ...
   ENDPROC

*|---------------------------------------------------------------------------
*|----------------------- Transforming Values -------------------------
*|---------------------------------------------------------------------------
   PROCEDURE BinTOHex( lsVal )
      ...
   ENDPROC

   PROCEDURE HexToChar( lnHexVal )
      ...
   ENDPROC

ENDDEFINE

We see how, when FoxUtils loads, it verifies the existence of the FoxUtils.fll file, and if it finds it, it loads it as a VFP library, besides, in the INIT method it also fills the array "m_base64Table" with the values of the property "m_base64List" so that it may be used later by Encode64. Then, we find four groups of functions "MIME", "Base64", "GUID" and "Transforming Values".

Among MIME functions, only one, ObtenerMimeType, is for public access, since the other two are called from the first one.

Base64 functions have two public methods, one to encrypt a string via VFP and the other to encrypt a complete file, via the FLL.

GUID functions have four methods, but only two are public, ObtenerUuid and ObtenerGuid that call the other two methods that interact with the API functions and generate the GUID.

And finally, only the Transforming Values functions remain.

Using the class WSendMail

In the following example, we can see how to send an eMail with an attached file named "welcome.png", with the WsendMail class, to my mailbox with an SMTP server that requires authentication in base64.

SET PROCEDURE TO WSendMail           && Class for SMTP
SET PROCEDURE TO WSocket    ADDITIVE && Class for the Socket
SET PROCEDURE TO FoxUtils   ADDITIVE && Utility classes
*|-- Mail Object.-
ObjMail = CREATEOBJECT("WSendMail")

*|-- Server
ObjMail.m_HostName = "ServerSMTP"
ObjMail.m_HostPort = 25

*|-- User and password.-
ObjMail.m_UsrSMTP  = "MyUser"
ObjMail.m_PassSMTP = "MyPassword"

*|-- Base64 authorization
ObjMail.m_TipoAut  = 1

*|-- Mail Originator.-
ObjMail.m_Originante = "ianni_roberto@hotmail.com"
ObjMail.m_De         = "ROBERTO IANNI "

*|-- Mail recipients
ObjMail.m_Destinatarios = "ianni_roberto@hotmail.com"
ObjMail.m_To            = "ROBERTO IANNI "

*|-- Other settings.-
ObjMail.m_TipoMailPartID = 0                    && GUID
ObjMail.m_TipoMail       = "T"                  && Text
ObjMail.m_Prioridad      = "A"                  && High
ObjMail.m_SeparadorDir   = ";"                  && Address separator

*|-- Mail.-
ObjMail.m_Body      = "Mail body "
ObjMail.m_Asunto    = "Test - WSendmail"

*|-- File Attachement.-
ObjMail.AdjuntarArchivo( "C:\welcome.png" )

*|-- Mail sending.-
IF( ObjMail.SendMail() == .F. )

   MESSAGEBOX( "Error: - " + ObjMail.m_Error )
ENDIF

RELEASE ObjMail

Conclusions

For this version, we have evolved the former class for mail dispatching, giving to it more intelligence and a better managing of errors and responses of the SMTP server, as well as the capability to attach binary files and SMTP authentication.

In order to achieve this, we had to create a new class named FoxUtils and a new FLL with the same name, whose tasks, even though related with mail dispatching, are mostly generic.

I understand that the WSendMail class complies with the RCF 822 regulation, and at least until I finished to develop it, I did not find any kind of problem.

We include in the article all the source code for the topic so that each one can implement it for their own systems and make use of it at will, depending upon the particular solution. It does not require any license, since it is Open Source and self sustaining for sending mails.

By the way, I can tell you that with the basis of these classes, I started to develop a system for sending and receiving email using SMTP and POP3, named "FoxLook".

This client is intended to run as Win32 and ASP.NET with C# for both Intranet and Internet, using, of course, the FOX classes as the core for both projects. The Project will be Open Source just as the classes and my purpose is that every one who wants to be in it, feel free to do so. At the beginning, I will concentrate the code via mail to my address, and I consider, for the future, to upload it to some web site so that it can be shared.

Source Code

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