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:
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
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.
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.
IF( !THIS.m_SocketConn.Enviar( ENTER + "." + ENTER ) ) RETURN .F. ENDIF
*|-- 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
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