Вы находитесь на странице: 1из 58

Send (SMTP) and Retrieve (POP3) Email with Ease under VB.

NET
By David Ross Goben
Copyright 2011 by David Ross Goben All rights reserved. Last Update: Wednesday, March 23, 2011
This is a sample excerpt from the free PDF e-book, Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0, by David Ross Goben. Download this e-book, and its free companion, Navigating Your Way Through Visual Basic 6.0 Upgrades to Visual Basic .NET, also by David Ross Goben, at www.slideshare.net/davidrossgoben. They are also available on Google Docs at https://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout =list&num=50. The Google site also has a VBNetEmail.zip source file package featuring all VB.NET classes and utilities.

Table of Contents
Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET.............................................................................2
Adding the VB6 MAPISession and MAPIMessage Controls to VB.NET............................................................................ 2 PART ONE
Sending Email under VB.NET using Native Methods................................................................................................................. 3 Quick and Dirty Email Senders ................................................................................................................................................ 3 TCP Ports, SSL Authentication, and Creating Credentials ...................................................................................................... 5 An Email Sender with a Lot of Muscle ..................................................................................................................................... 6
Sending Email Messages as HTML ...................................................................................................................................................8 Sending Alternate Message Views ...................................................................................................................................................10 Sending Alternate Message Views with Different Context Types and Transfer Encoding .............................................................13

PART TWO
Encoding and Decoding Email Data .......................................................................................................................................... 18
Allowing Users to Specify Content-Type and Content-Transfer-Encoding Options ................................................................................19 Determining if Text can be Encoded As Quoted-Printable, Base64, or 7Bit ...........................................................................................21 Converting 8-Bit HTML Data to 7-Bit without Loss of Integrity.................................................................................................................21 Converting 8-Bit Text Data to 7-Bit without Data Loss .............................................................................................................................22 Decoding Quoted-Printable Text ...............................................................................................................................................................23 Translating Base64 Data Back to Its Original Format ..............................................................................................................................23 Translating BinHex Data Back to its Original Format ...............................................................................................................................26

PART THREE
Receiving Email under VB.NET using Native Methods ............................................................................................................ 27
Connecting to a POP3 Server ...................................................................................................................................................................30 Checking for a POP3 Server Response ...................................................................................................................................................31 Checking for Being Connected to a POP3 Server....................................................................................................................................31 Getting a Response from the POP3 Server..............................................................................................................................................31 Submitting a Request to the POP3 Server ...............................................................................................................................................33 Disconnecting from the POP3 Server .......................................................................................................................................................33 Getting Email Statistics from the POP3 Server ........................................................................................................................................34 Getting an Email Reference List from the POP3 Server ..........................................................................................................................34 Get an Email Header from the POP3 Server ............................................................................................................................................35 Retrieve an Email from the POP3 Server .................................................................................................................................................36 Deleting an Email from the POP3 Server .................................................................................................................................................36 Reset (Undo) All Deletes from the POP3 Server......................................................................................................................................37 Send a Keep-Alive NOOP Command to the POP3 Server ....................................................................................................................37 Disposing of Resources .............................................................................................................................................................................37 Using the Completed POP3 Class ............................................................................................................................................................38

PART FOUR
Email Data Blocks Made Easy ...................................................................................................................................................... 39
Easily Extracting the Component Parts from an Email File......................................................................................................................42

Compiling Everything into an Email Class Library ......................................................................................................................... 44


Building the VBNetMail Class Library .......................................................................................................................................................46 Accessing your New VBNetEmail Class Library DLL from another Project ............................................................................................46

The Complete SMTP.VB File ........................................................................................................................................................ 47 The Complete POP3.VB File ........................................................................................................................................................ 48 The Complete Utilities.VB File....................................................................................................................................................... 52

About the Author.............................................................................................................................................................57

Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET
Brays whisper distantly beneath midnight mists as spent developers, pinned by looming deadlines and their brains threat of total collapse, rasp desperate prayers against an ominous sense of impending doom, working at a fevers pitch to hammer out viable code (which, according to Murphys Law of Looming Deadlines, is an absolute impossibility). Suddenly, in utter horror, they crash into a brick wall; and this after having assured their skeptical client of how superior VB.NET was over that antiquated VB6 the client revered. They realize too late that their strategy for the clients email handler is unworkable: VB.NET does not provide the MAPI controls they thought it did. Another soul-torn howl trebles against the muffled parapets of the valley.

Plain and simple, MAPI (Messaging Application Program Interface) is not yet a .NET technology; it is still COM technology (Common Object Model), used by ASP (Active Server Page), IIS (Internet Information Server), and any other COM application that access email. Being COM-based, you should not expect to see the VB.NET Toolbox sport controls such as VB6s MAPISession or MAPMessage. Under the VB6 implementation of MAPI, it used the MAPISession control to (what else?) manage a MAPI session. The MAPIMessage control was used to process email messages, both incoming (POP3; Post Office Protocol Version 3) and outgoing (SMTP; Simple Mail Transfer Protocol). Presently, .NET is set up for outbound email, but it lacks a class supporting inbound email, even though both of these technologies are simple TCP Clients (Transmission Control Protocol). I think it may have something to do with many people wanting to read email using eye-candy apps, such as Outlook or Mail. But this does not remove the need for an inbound class in more controlled environments.
Although it is easy to write VB.NET code to support POP3 Inbound Email services, as I will show you, I have found only one other person (I have since found more, but none provide robust solutions), and he works at Microsoft, who has developed any sort of VB.NET code to demonstrate this ability (albeit his solution was just a simple example with very limited capability). But by the time I found his article within the catacombs of MSDN, I had already put the finishing touches on my own full-featured POP3 Inbound Email solution.

Adding the VB6 MAPISession and MAPIMessage Controls to VB.NET But before diving into these VB.NET solutions, let us first take a look at the kind support that is presently available to developers who are upgrading VB6 MAPI applications to VB.NET.
When you upgrade a VB6 MAPI application to VB.NET, you will notice that the upgraded application will still have the VB6 MAPISession and MAPIMessage controls on any form that had them before. This is because their control sources have been copied locally and are referenced internally. Under .NET, a copy of the COM-based MSMAPI32.DLL is converted into a non-COM version (its DLLRegisterServer() entry is disabled) and saved to a project-local file named Interop.MSMAPI32.DLL. But, because both VB6 controls actually accessed this DLL provider through the MSMAPI32.OCX ActiveX interface, another project-local non-COM DLL named AxInterop.MSMAPI32.DLL is internally compiled by .NET that will duplicate both the ActiveX visual Interface construction services for the controls, as well as the function mapping services to the new Interop.MSMAPI32.DLL. Having found these controls on their upgraded applications, many developers also want to add them to other VB.NET projects so they can take advantage of them there, but they cannot seem to find a way to easily access the new DLLs from those new projects. It is doable, but it requires numerous coding hacks. But relax. Why not just add these two VB6 controls to your VB.NET Toolbox and access them directly?
1. 2. 3. 4. With any form up on the Visual Studio screen so that the IDE toolbox is active, right-click a toolbox category you want to add the MAPI controls to (if you want to add them to their own category, such as to one named COM, right-click any category and select the Add Tab option, then type the name of your category, such as COM, press ENTER, then right-click that tab). Select the Choose Items option, and wait (a long while) for the IDE to build a massive control reference list from the computer. Once the Choose Toolbox Items dialog is finally displayed select the COM Components tab. Scroll down and put checkmarks in the check boxes for Microsoft MAPI Messages Control, Version 6.0, and Microsoft MAPI Sessions Control, Version 6.0. (Both of these are actually linked to MSMAPI32.OCX, which in turn drilled down to MAPI32.DLL, but they will now both link to a new .NET-compiled axInterop.MSMAPI32.DLL, and drill down to Interop.MSMAPI32.DLL).

5.

Click the OK button, and you will find these two controls now in your selected Toolbox category list, and you can begin using these controls just exactly as you would had been using them under VB6.

NOTE: If you do not find these entries in the Choose Toolbox Items dialog box, then you may not or no longer have the VB6 redistributables on your system, so you will have to minimally install the free Runtime Distribution Pack for Service Pack 6 for Visual Basic 6.0, available from Microsoft (http://www.microsoft.com/downloads/details.aspx?FamilyId=7B9BA2617A9C-43E7-9117-F673077FFB3C&displaylang=en). You are allowed to do this even if you no longer own VB6.

PART ONE Sending Email under VB.NET using Native Methods VB.NET has its own Outbound SMTP Email class that supports sending email, and without a need to add the more resource-hungry form controls, as we had to do with VB6. Because this technique is more accessible than supporting inbound email under VB.NET, we will first look at sending email out. Some people think that you simply hit a system-linked Send button and a message they had just typed is automatically launched into the labyrinths of the internet with possibly little or no code from you. Were that it be so easy. But hopefully you will now be able to make your clients think you made it so. Back in the old days of software engineering, say the early 1990s, we processed email through a thing called a Berkeley Socket (circa 1983). This socket simply described the endpoint of a bidirectional inter-process communication flow across an Internet Protocol-based network. It was sometimes a real trick to program for, depending on the platform, but when it functioned correctly, it was a work of art.
In a pinch we launched a TelNet client and manually typed the various commands to log on to an email server, send, receive, and read email, and finally disconnect. Those were cryptic and unforgiving days. But looking back to those times, I have to wonder if we were either brilliant geniuses or major drool-monkeys, because we thought back in those younger and smarter years that it was all simple childs play. Quick and Dirty Email Senders Nowadays, we have built-in tools to do most of the hard stuff for us, such as the System.Net Namespace. This class library provides the .NET SMTP Outbound Mail class. To use it, in the heading of your form or module, above the class declaration of the file you will want to implement it, I will ask you to add this line:
Imports System.Net, System.Text, VB = Microsoft.VisualBasic 'Most the code in this article REQUIRES this Imports line!

NOTE: Some people import System.NET.Mail just to avoid typing Mail. later in their code, but we will also need access to the System.Net.Mime namespace, and even later the System.Net.Sockets namespace. Note that the third part of the line, the declaration of VB, is not really necessary, but BOY is it handy to have in most every class or module we write. NOTE: MIME (or Mime) is an anagram for Multipurpose Internet Mail Extensions.

First, if you want to send a fast note to someone, most servers will allow you to use the following method:
'******************************************************************************* ' Function Name : BrainDeadSimpleEmailSend ' Purpose : Send super simple email message (works with most SMTP servers) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <daviddingus@att.net> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <bob.dingus@cox.com> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.comcast.net", "authsmtp.juno.com", etc. '******************************************************************************* Public Sub BrainDeadSimpleEmailSend(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String) Dim smtpEmail As New Mail.SmtpClient(smtpHost) 'create new SMTP client using TCP port 25 smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email End Sub

NOTE: The FROM and TO email addresses can be simple, such as bob.dingus@cox.com, or more trendy formats, such as Bernard Shaw Fullo <FulloBS@highschnozez.com> or even Coat Mahatma <mahatmacoat@classydresser.com>. If the data contains angle brackets, the Mail object will use only the data contained within them. If there are no angle brackets, then the mail object will surround the data with angle brackets, assuming that the entire text is an email address.

NOTE: Yahoo, Gmail and Juno are internet-based services providing both internet and SMTP/POP3 access. Unlike the other two, Yahoo, normally free, requires an additional monthly fee for SMTP/POP3 access. Juno provides this service freely to their subscribers. Gmail, a free service, provides it if you set an option in the POP Download section of the Forwarding and POP/IMAP option within its internet account Settings. You will, of course, have to set up SMTP/POP3 accounts and access within your favorite local email application for all three, such as Windows Mail, Outlook Express, or Outlook.

The above method actually works for most SMTP servers. For example, Comcast and Juno both support this interface. I use this for quick messages (though they can also be major literary works), like most people post text messages on their cell phones. However, I think it would be a bit difficult to drive down the road with a desktop PC and keyboard in hand, trying to steer while I thumb a quick message.
NOTE: Texting while driving is illegal here in Florida, as it should be. In 2010 I witnessed 6 accidents and 1 fatality due to driver texting, primarily by young people, though I must concede that they could have been more distracted by their stereo systems blasting so loudly that it made both their eyes bounce from one side of their head to the other, impairing their vision.

The SMTP Host is the address of your email providers SMTP server. SMTP is a TCP/IP (Transmission Control Protocol/Internet Protocol) process used for sending and receiving email. However, because SMTP is limited in its capability to queue messages at its receiving end, it is typically used with one of two other protocols, like POP3 or IMAP (Internet Message Access Protocol). But that is a topic we will cover after we resolve the email sending issues that many thousands of developers are presently having. For a much more robust method that supports most-all servers, including those that use security layers, like Gmail, you might try the following method to send a quick email with no attachments:
'******************************************************************************* ' Function Name : QuickiEMail ' Purpose : Send a simple email message (but packed with a lot of muscle) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <daviddingus@att.net> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <bob.dingus@cox.com> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Function QuickiEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Try Dim smtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client smtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... smtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom smtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else smtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email using text/plain content type and QuotedPrintable encoding Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Send Error") Return False 'return a failure flag End Try Return True 'if no error, then return a success flag End Function

With the QuickiEMail() method, you can send full, plain text message to one recipient with no attachments, hence; quick and dirty. With this method, you supply it with the full email address for whom the email is from (you, for example), you also provide the full email address of the person you are sending it to, a subject for the email (what it is concerning), the body of the text message, and the SMTP host (such as smtp.comcast.net, authsmtp.juno.com, or smtp.gmail.com). 4

If you must use a TCP Port other than Port 25, such as Gmail requires (using Secure TCP Port 587), then include the needed port number. Also, if you will be using a secure TCP Port, then you will also require SSL Authentication, so include Yes for the Boolean usesSSL flag. By setting usesSSL to True, this will in turn mean that you will need to also supply credentials. Do not panic. This is easy. Just look below. TCP Ports, SSL Authentication, and Creating Credentials By default, most Outbound Servers use TCP Port 25, and they use TCP Port 110 for their Inbound Server. However, some may differ, such as Gmail, as mentioned above, which uses secure TCP Port 587 for their Outbound Server and TCP Port 995 for their Inbound Server. But this is primarily because Gmail requires an SSL (Secure Socket Layer) to process mail. In these cases you will need to set the optional parameter to that TCP Port, such as in the following hard-coded example:
Dim smtpEmail As New Mail.SmtpClient("smtp.EngulfNDevour.com", 465) 'Create new SMTP client using Secure TCP Port 465. smtpEmail.EnableSsl = True 'True if SSL authentication required. SmtpEmail.UseDefaultCredentials = True 'Typical for NTLM, negotiate, and Kerberos-based authentication.

NOTE: Yes, yes, I know; TCP port 587 uses a newer breed of security called TLS (Transport Layer Security). But first, it is still an SSL. Second, SSL technology provides identical security. And third, you are not going to find some sort of EnableTls property in the Mail object. Refer to www.sans.org/reading_room/whitepapers/protocols/ssl-tls-beginners-guide_1029.

The above DefaultCredentials property represents the system credentials for the current security context in which the application is running. For a client application, these are usually the Windows credentials (username, password, and domain) of the user running the application (for ASP.NET applications, the default credentials are the user credentials of the logged-in user, or the user being impersonated).
NOTE: To examine your default credentials, access the System.Net.CredentialCache.DefaultNetworkCredentials property.

However, setting the UseDefaultCredentials property to True will apply only to a Microsoft NT LAN Manager (NTLM) using intranet-based Negotiate authentication and Kerberos-based authentication. All others will have to create a credential. This is no big whoop, as you are about to see.
NOTE: To make sure that IIS supports both the Kerberos protocol and the NTLM protocol, you must confirm that the Negotiate security header is set in the NTAuthenticationProviders metabase property. For Negotiate authentication to function correctly, several exchanges must take place on the same connection. Therefore, Negotiate authentication cannot be used if an intervening proxy does not support keep-alive connections. NOTE: The centralized account management supported by NT Active Directory Services requires a corresponding authentication protocol for network log-on. Based on RFC 1510 (http://www.ietf.org/rfc/rfc1510.txt), the Kerberos protocol provides enhanced authentication for the distributed computing environment and standardization to interoperate with other operating systems. FUNNY DIGRESSION: the Term NT stands for New Technology. It was adopted when Microsoft and IBM parted on their joint OS2 venture. IBM, slow about everything (a self-study showed it took them 9 weeks to ship an empty box), they refused to adopt the revolutionary, advanced technology Microsoft was quickly developing without it being time-tested (meaning proven; this is why NASA uses 20-year-old technology), so Microsoft made its own version of OS2 that used it, naming it NT. Now, think about how many times you have read or heard even Microsoft mentioning the term NT Technology?

If you are not accessing a local IIS (intranet) SMTP server, you will need to supply a new credential for an SSL SMTP client through a new NetworkCredential Object with a Username and Password provided to it (if Domains differ, then that must be supplied as well), as shown in this hard-coded example:
Dim smtpEmail As New Mail.SmtpClient("smtp.gmail.com", 587) 'create new Gmail SMTP client with SSL (TLS) for outgoing eMail smtpEmail.EnableSsl = True 'True if SSL authentication required smtpEmail.Credentials = New NetworkCredential("Norio.Nachamichi.Jones@gmail.com","MomBNipp0neze") 'new credential with Username, Password

NOTE: You cannot use decorated usernames for creating a network credential. You will not be able to use something like Tukool Firwurds <iBeAnicn@chic.net>. You would have to provide just the actual email address: iBeAnicn@chic.net.

Although the above methods work in most domains, highly secure domains may require more than an SSL certificate to reach the outside world. For example, if you are a minion at the Engulf & Devour Credit Corp., you may need to apply code that bypasses massive firewalls and multi-layer proxies, which any high school youth worth their salt can usually break through before Second Period. 5

As indicated above, you will need to create a NetworkCredential object if you access a server through an SSL layer, such as Gmail, because you will not be able to use default credentials. Compare these examples, demonstrating default access through Juno, and SSL access through Gmail:
QuickiEMail("Idjut@juno.com", "editor@nyt.com", "Letter to the Editor", "Your paper lines my dog cages.", "authsmtp.juno.com") QuickiEMail("Bob <btchn@gmail.com>", "ed@abrv.com", "Ltr 2 Ed", "Yr ppr lns m dg cgz 2.", "smtp.gmail.com", 587, True, "btchn@gmail.com", "Psswd#6")

NOTE: Gmail, like other SSL servers, employ the users full email address for their certificates. As such, by leaving the SSLUsername field blank, the users email address (as long as it is just the full email address) will be used.

But even so, just the above QuickiEMail() method supports most emails that people need to transmit, and is, in fact, all the outgoing email support than a great deal of people will ever require. An Email Sender with Some Muscle If you may have multiple recipients, multiple optional BCC (Blind Carbon Copy) recipients, multiple optional CC (Carbon Copy) recipients, multiple attachments, or if you want to send the body text as HTML format, or send alternate views of the message body, you will require a method with a whole lot more muscle, like the following SendEmail() method:
'******************************************************************************* ' Function Name : SendEMail ' Purpose : Send a more complex email message '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, "David Dingus <david.dingus@att.net>" ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus <bob.dingus@cox.com>" ' : If multiple recipients, separate each full email address using a semicolon (;) ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. May be raw text or HTML code ' IsHTML : True if the strBody data is HTML, or the type of data that would be contained within an HTML Body block. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' AltView : A System.Net.Mail.AlternateView object, such as Rich Text or HTML. ' : If need be, set AltView.ContentType.MediaType and AltView.TransferEncoding to properly format the AlternateView. ' : For example: AltView.ContentType.MediaType = Mime.MediaTypeNames.Text.Rtf ' : AltView.TransferEncoding = Mime.TransferEncoding.SevenBit ' StrCC : Send "carbon copies" of email to this or these recipients. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strBcc : Blind Carbon Copy. Hide this or these recipients from view by others. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strAttachments: A single filepath, or a list of filepaths to send to the recipient. ' : If multiple attachments, separate each filepath using a semicolon (;) (C:\my data\win32.txt; c:\jokes.rtf) ' : The contents of the attachments will be encoded and sent. ' : If you wish to send the attachment by specifying content type (MediaType) and content transfer encoding ' : (Encoding), then follow the attachment name with the MediaType and optional encoding (default is ' : application/octet-stream,Base64) by placing them within parentheses, and separated by a comma. For example: ' : C:\My Files\API32.txt (text/plain, SevenBit); C:\telnet.exe (application/octet-stream, Base64) ' : Where: The MediaType is determined from the System.Net.Mime.MediaTypeNames class, which ' : can specify Application, Image, or Text lists. For example, the above content type, ' : "text\plain", was defined by acquiring System.Net.Mime.MediaTypeNames.Text.Plain. ' : The second parameter, Encoding, is determined by the following the values specified by the ' : System.Net.Mime.TrasperEncoding enumeration: ' : QuotedPrintable (acquired by System.Net.Mime.TransferEncoding.QuotedPrintable.ToString) ' : Base64 (acquired by System.Net.Mime.TransferEncoding.Base64.ToString) ' : SevenBit (acquired by System.Net.Mime.TransferEncoding.SevenBit.ToString) ' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential with a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Function SendEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal IsHTML As Boolean, _ ByVal smtpHost As String, _ Optional ByVal AltView As Mail.AlternateView = Nothing, _ Optional ByVal strCC As String = vbNullString, _ Optional ByVal strBcc As String = vbNullString, _ Optional ByVal strAttachments As String = vbNullString, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Dim Email As New Mail.MailMessage 'create a new mail message With Email .From = New Mail.MailAddress(strFrom) 'add FROM to mail message (must be a Mail Address object) '------------------------------------------Dim Ary() As String = Split(strTo, ";") 'add TO to mail message (possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .To.Add(Trim(Ary(Idx))) 'add each TO recipent (primary recipients) Next '------------------------------------------.Subject = strSubject 'add SUBJECT text line to mail message '-------------------------------------------

.Body = strBody 'add BODY text of email to mail message. .IsBodyHtml = IsHTML 'indicate if the message body is actually HTML text. '------------------------------------------If AltView IsNot Nothing Then 'if an alternate view of plaint text message is defined... .AlternateViews.Add(AltView) 'add the alternate view End If '------------------------------------------If CBool(Len(strCC)) Then 'add CC (Carbon Copy) email addresses to mail message Ary = Split(strCC, ";") '(possible list of email addresses, separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .CC.Add(Trim(Ary(Idx))) 'add each recipent Next End If '------------------------------------------If CBool(Len(strBcc)) Then 'add Bcc (Blind Carbon Copy) email addresses to mail message Ary = Split(strBcc, ";") '(possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .Bcc.Add(Trim(Ary(Idx))) 'add each recipent (hidden recipents) Next End If '------------------------------------------If CBool(Len(strAttachments)) Then 'add any attachments to mail message Ary = Split(strAttachments, ";") '(possible list of file paths, separated each with ";") For Idx As Integer = 0 To UBound(Ary) 'process each attachment Dim attach As String = Trim(Ary(Idx)) 'get attachment data If Len(attach) <> 0 Then 'if an attachment present... Dim I As Integer = InStr(attach, "(") 'check for formatting instructions If CBool(I) Then 'formatting present? Dim Fmt As String 'yes, so set up format cache Fmt = Mid(attach, I + 1, Len(attach) - I - 1) 'get format data attach = Trim(VB.Left(attach, I - 1)) 'strip format data from the attachment path Dim Atch As New Mail.Attachment(attach) 'create a new attachment Dim fmts() As String = Split(Fmt, ",") 'break formatting up For I = 0 To UBound(fmts) 'process each format specification Fmt = Trim(fmts(I)) 'grab a format instruction If CBool(Len(Fmt)) Then 'data defined? Select Case I 'yes, so determine which type of instruction to process Case 0 'index 0 specified MediaType Atch.ContentType.MediaType = Fmt 'set media type to attachment Case 1 'index 1 specifes Encoding Select Case LCase(Fmt) 'check the encoding types and process accordingly Case "quotedprintable", "quoted-printable" Atch.TransferEncoding = Mime.TransferEncoding.QuotedPrintable Case "sevenbit", "7bit" Atch.TransferEncoding = Mime.TransferEncoding.SevenBit Case Else Atch.TransferEncoding = Mime.TransferEncoding.Base64 End Select End Select End If Next .Attachments.Add(Atch) 'add attachment to email Else .Attachments.Add(New Mail.Attachment(attach)) 'add filepath (if no format specified, encoded in effiecient Base64) End If End If Next End If End With '----------------------------------------------------------------------'now open the email server... Try Dim SmtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client on the SMTP server SmtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... SmtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom SmtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else SmtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If SmtpEmail.Send(Email) 'finally, send the email... Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Error") Return False 'return failure flag End Try Return True 'return success flag End Function

Notice that in this version we have added more recipients. A lot more. There is a TO list, a Carbon Copy list (CC), and a Blind Carbon Copy list (BCC; not viewable by TO or CC recipients). The CC list is an archaic vestige that just forgot to go away. It was essential back in the days when you could specify only a single email address in the TO field. The BCC was essential for sending copies to other concerned parties, but it was not essential, or the sender did not want the TO or CC viewers to know that these BCC recipients were being sent copies. Some claim there is no use for a BCC list, but I beg to differ.
NOTE: Microsoft Mail features BCC, but it does not seem to work, under Vista, anyway.

The important thing to notice about these three recipient fields is that you separate each recipient with a semicolon (;). Notice also that in my code that I did a check on each after I split them into an array, to ensure that a field was not empty. Most email applications simply slap a semicolon on the tail of each email address, so splitting them into an array may leave any last array element empty. The Attachments, strAttachments, we handle just like TO, CC and BCC. The file paths to the attachments are separated by semicolons. The Mail.Attachments collection object takes care of loading the actual file data. Attachments are appended to the end of the email. As noted in the comments above the method, you can also declare the encoding and display formats for an attachment; otherwise they will default to binary (application/octet-stream) and encoded using the Base64 method, which, as all, converts them to encoded 7-bit text, which all emails must be formatted to for internet processing. An email is actually a series of bytes (in email lingo, these are octets; 8-bits), formatted as 7-bit ASCII text (ergo, the 8th bit is never used). As such, even binary attachments are encoded into blocks of ASCII text, sometimes formatted as ASCII Hexadecimal (hex; 0 through 9, and A through F, allowing for a Base16 numbering system, though this doubles the data size), where each byte is represented by two 7-bit characters, and each character represents a nibble, or 4 bits (engineers must always be hungry). However, many servers now support various types of encoding, like Base64, to better transport 8bit/binary data in the 7-bit-only catacombs of the internet (at a cost of the datas footprint being only 25% larger). For example, my API32.txt file, which, by its extension, is certainly a text file, looks like gobbledygook at the bottom of my email when converted using the default Base64 encoding. Here is a sampling of its beginning:
NOTE: My comments in the SendEmail() method header regarding attachments, where I discuss formatting the attachment to different content types and encoding, we will leave for later, when I actually discovered these solutions.
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 21 Feb 2011 21:16:00 -0500 Subject: Test Content-Type: multipart/mixed; boundary=--boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 ----boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is just a test ----boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 Content-Type: application/octet-stream; name=API32.txt The Name parameter identifies this as an attachment (and it was handled as a binary stream) Content-Transfer-Encoding: base64 How the file is encoded (I would have rather had this be text/plain, with 7bit encoding) JyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0NCicNCicgICAgIEFQSTMyLlRYVCAtLSBXaW4zMiBBUEkg VHlwZSBEZWNsYXJhdGlvbnMgZm9yIFZpc3VhbCBCYXNpYw0KJw0KJyAgICAgICAgICAgICAg ICAgICAgICAgQ29weXJpZ2h0IChDKSAxOTk2IERlc2F3YXJlDQonDQonICBZb3UgaGF2ZSBh IHJveWFsdHktZnJlZSByaWdodCB0byB1c2UsIG1vZGlmeSwgcmVwcm9kdWNlIGFuZCBkaXN0 ... The encoded definition of several thousand more API declarations, structures, and constants continues from here.

NOTE: In the back of my mind, when I encountered this during my initial email tests, I was really wondering about this Base64 Content Transfer Encoding. But, we will return to this when we examine alternate views and attachments, where it will make more sense to us. We will also learn how we can exploit it and very easily decode it (and also how to avoid it).

Two other parameters you may have noticed in the SendEmail() method were IsHTML and AltView.

Sending Email Messages as HTML The IsHTML parameter in the SendEMail() method in turn sets the state of the Mail.MailMessage objects Boolean IsBodyHtml flag. If it is set to True, then the SMTP interface will know to set the body text formatting flag to text/html instead of its usual text/plain. It is actually up to email reader software to use that information and determine how to present the data. For example, some plain text readers will simply show the raw data, regardless. However, others will bring up a web interface, such as a WebBrowser control, envelop the text within an HTML body, and present that to the user.

This is quite easy to do, but there is a simple test will you need to perform, because some main body HTML is sent without HTML/BODY tags, but most are. The easiest test is to simply check to see if </HTML> is contained within the message. If not, all you have to do is prepend the text <HTML><BODY> in front of the message, and append the text </BODY></HTML> behind it. And that is all! Consider this test, where String variable Msg is assumed to contain the HTML-formatted text of the message body:
If Not CBool(InStr(1, Msg, "</HTML>", CompareMethod.Text)) Then 'Msg contains an HTML wrapper? Msg = "<HTML><BODY>" & Msg & "</BODY></HTML>" 'no so add one to it End If

Suppose I sent the following urgent code red email message (note the True for the IsHTML parameter):
Dim Msg As String = "<b>This is bold text</b><p><u>This should be underlined</u><p>" 'some simple HTML text SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", Msg, True, "smtp.80micro.com")

My Gmail account will receive an email with the following at the bottom of the data (sans my notes):
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 21 Feb 2011 21:39:04 -0500 Subject: Test Content-Type: text/html; charset=us-ascii Used second to determine how to display or process the data Content-Transfer-Encoding: quoted-printable Used first to determine how to decode the data <b>This is bold text</b><p><u>This should be underlined</u><p> The HTML formatted text without HTML or BODY tags

Once I strip out the header data (I will show this later), I end up with a String variable I named Msg that contains the text <b>This is bold text</b><p><u>This should be underlined</u><p>. I would expose it within my Web Browser interface control, WebBrowser1, using code similar to the following:
'set the web browser to the same location as my usual text display control Me.WebBrowser1.Bounds = Me.RichTextBox1.Bounds Me.RichTextBox1.Visible = False 'hide my plain text/Richtext textbox If Not CBool(InStr(1, Msg, "</HTML>", CompareMethod.Text)) Then 'Does my Msg contain an HTML wrapper? Msg = "<HTML><BODY>" & Msg & "</BODY></HTML>" 'no so add one to it End If 'set web browser contents to that of my email message body and display it Me.WebBrowser1.DocumentText = Msg Me.WebBrowser1.Visible = True 'expose the web browser

The two lines of text are dutifully displayed using the HTML formatting I assigned to them. The first line was Bold, and the second line was underlined. The urgent code red message was processed and read in time! The world is once again safe.
There is so much more to explore if you truly want to create a full-featured email processor. The Internet Message Format document, RFC 2822 (http://www.ietf.org/rfc/rfc2822.txt), outlines all the gory details of email formatting.

I should close this sub-section by saying that the message data on multi-line documents should be further processed to ensure that the data is properly formatted for your web browser, rich text box, or simple text box, such as by decoding special tags that may have been added, such as to represent Unicode text within the simple 7-bit ASCII text format required for email transactions, for example. Data in an email is a series of lines, each terminated by a vbCrLf (Carriage Return and Linefeed codes 13 and 10, respectively). Often these line terminators are not a part of the original text, but are required to limit the line width of the email data. In these cases they are tagged, such as by using a soft return flag, like the equals sign = used by Quoted-Printable encoding. These must be decoded and removed before displaying the text. Also with Quoted-Printable-encoded text, if a space character precedes a vbCrLf, then that space is converted to a special hexadecimal format, =20, which I habitually call Hex-Tags. Also, if there are any 8-bit characters embedded in the message, then you should convert them to Hex-Tags, otherwise the system will automatically encode the data to Base64, regardless of what you really want, such as Quoted-Printable, simply to ensure 100% original data integrity. Fortunately, I will later present very simple functions to allow Quoted-Printable encoding of 8-bit text that will convert any 8-bit codes to 7-bit without losing integrity, as well as decode Quoted-Printableencoded and Base64-encoded data back to its original form. 9

Sending Alternate Message Views Another thing I want to explore with you is one of the more interesting parameters I have listed for the SendEMail() method, and that is AltView. The AltView parameter is declared as a System.Net.Mail.AlternativeView object. Even though my SendEMail() method presently allows for only one alternative view, which is usually all we ever really need, the .NET SMTP processor will actually allow for as many as you want, in compliance to RFC 2822, as though we would want to spend the rest of our miserable lives toiling over the various formatting of a single email to say Thanks for the $1 on my birthday to Great Aunt Ethel. However, the real reason for this is more mundane: some people simply want to send both a pretty version of their email and a plain text version for those who may want to view them on a cell phone. Technically, most email processors will, by default, display the alternate view, or the first alternate view they can support, leaving the Plain Text version as the last ditch option. Typically, most of us tend to send plain text emails, even if they are in fact formatted by a Rich Text or HTML editor, failing to add emphasis, bolding, italics, or underlining. Phooey! I still remember the thrill I got when I hit a button on my Selectric II Typewriter and it bolded what I typed, or got my first TypeBall we called them golf balls that supported italics. I did not even blink when I had to go though the time and effort of changing the TypeBall just to change fonts. For me, at least, plain text is so monotonous and pass.
NOTE: Were I forced back to the days of no easy bolding, underlining, or italics, I think I would suffer a pulmonary embolism, as may be apparent from reading my document; these features afford us a platform for succinct expression.

Most people prefer their emails to be formatted as HTML or as Rich Text, but they also want the option to process their text as Plain Text for those who want to read their email on a cell phone, or who are vision-impaired (Cant see out of one eye, and Im blind as a bat in the other, as Grandpa often said). Most of you are aware that in a VB-written Rich Text editor, using a RichTextBox control, you have easy access to the Plain Text version and the Rich Text version of a document. Its Text property provides the Plain Text version, and its Rtf property provides the Rich Text version. But did you know that accessing plain text from HTML formatting can also be easy? An HTML version can be provided by accessing the DocumentText property of their WebBrowser control, as most of you are already aware. But a Plain Text rendering has always seemed to be an issue. I have seen a number of home-spun HTML editors that provide a Plain Text version of their data by manually stripping out all the HTML Text Tags, plus any special formatting that might be stored within or between them, on top of going through the often arduous task of interpreting all the special HTML Entities in order to provide that simple plain-text version. This adds up to a whole lot of work.
NOTE: An HTML Text Tag is a thing starting with < and ending with >, such as <HTML> or </BODY>. NOTE: HTML Entities start with an ampersand (&) and end with a semicolon (;), such as &lt; to represent an intentional <,&gt; for an intentional >, &nbsp; (non-breaking space) for a blank space where a space might normally be ignored, plus ;; for an intentional ;. Following is a list of the HTML Reserved Entities:
Character " ' & < > Entity Number &#34; &#39; &#38; &#60; &#62; Entity Name &quot; &apos; &amp; &lt; &gt; Description quotation mark apostrophe ampersand less-than greater-than

As you can see, there are two versions of tags; one that includes the decimal ASCII code, and one that includes the typical or classic representation. The Entity Number also allows 8- or 16-bit extended characters to be displayed by a 7-bit source.

10

Even though I have seen a number of utilities, both commercial and shareware, that offer this kind of service, you can in fact bypass them and remove better than 75% of that work with this 1-line function:
'******************************************************************************* ' Function Name : QConvertHTML2Text ' Purpose : Short-Form Convert HTML formatted text to plain text ' : ' Returns : Provided a simple HTML source string, it will return a Plain Text ' : string with HTML code removed. '******************************************************************************* Public Function QConvertHTML2Text(ByVal HTMLText As String) As String Return RegularExpressions.Regex.Replace(HTMLText.Replace("&nbsp;", " ").Replace("&quot;", """").Replace("&apos;", _ "'"), "<[^>]*>", "").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function

The above function will also remove all the extra data within the HTML tags. Just invoke it like this: Dim PlainText As String = QConvertHTML2Text(HtmlText).
NOTE: In a pinch, copy from the HTML display of a WebBrowser control, paste it to Notepad, then copy it as plain text, but this does not provide for line formatting, and can sometimes look almost as disorganized as a college dorm room.

However, the following enhanced method does 99% of the work that any commercial package offers:
'******************************************************************************* ' Function Name : ConvertHTML2Text ' Purpose : Convert HTML formatted text to plain text ' : ' Returns : Provided a complex HTML string, it will return a Plain Text string ' : with all HTML codes and formatting removed from it. ' : ' NOTE : Numerous of these conversions will convert the text to 8-bit, ' : though most of these sysmbols will not be encountered in most ' : HTML documents we produce. But regardless of that, if you wish ' : to make this conversion the main body message of an email, you ' : may have to further convert this using ForceQuotedPrintable() ' : to maintain Quoted-Printable encoding and avoid Base64, even ' : though this is typically not an issue. However, some few really ' : primitive email readers, typically those that simply allow you ' : to preview email messages, without fully loading them, will not ' ' know how to support Base64, or will not bother with it, but simply ' : display the raw data. RFC 2045 requires email handlers to support it. '******************************************************************************* Public Function ConvertHTML2Text(ByVal HTMLText As String) As String 'instantiate an initially blank StringBuilder object Dim Sb As New StringBuilder() 'first remove leading whitespace of each line and append the result to the StringBuilder Dim ary() As String = Split(HTMLText, vbCrLf) For Each S As String In ary Sb.Append(S.TrimStart(Chr(9), " "c)) Next 'replace reserved entities (except <, >, and &) Sb.Replace("&quot;", """").Replace("&apos;", "'").Replace("&nbsp;", " ") 'replace HTML paragraph, line breaks, and table entry terminators with vbCrLf Sb.Replace("<p>", vbCrLf).Replace("<P>", vbCrLf).Replace("</p>", vbCrLf).Replace("</P>", vbCrLf).Replace("<br>", _ vbCrLf).Replace("<BR>", vbCrLf).Replace("</td>", vbCrLf).Replace("</TD>", vbCrLf) 'replace ISO 8859-1 Symbols (160-255). Note that any matches will make the text 8-bit Sb.Replace("&iexcl;", "").Replace("&cent;", "").Replace("&pound;", "").Replace("&curren;", _ "").Replace("&yen;", "").Replace("&brvbar;", "").Replace("&sect;", "").Replace("&uml;", _ "").Replace("&copy;", "").Replace("&ordf;", "").Replace("&laquo;", "").Replace("&not;", _ "").Replace("&shy;", "-").Replace("&reg;", "").Replace("&macr;", "").Replace("&deg;", _ "").Replace("&plusmn;", "").Replace("&sup2;", "").Replace("&sup3;", "").Replace("&acute;", _ "").Replace("&micro;", "").Replace("&para;", "").Replace("&middot;", "").Replace("&cedil;", _ "").Replace("&sup1;", "").Replace("&ordm;", "").Replace("&raquo;", "").Replace("&frac14;", _ "").Replace("&frac12;", "").Replace("&frac34;", "").Replace("&iquest;", "").Replace("&times;", _ "").Replace("&divide;", "") 'replace ISO 8859-1 characters. Note that any matches will make the text 8-bit Sb.Replace("&Agrave;", "").Replace("&Aacute;", "").Replace("&Acirc;", "").Replace("&Atilde;", "").Replace("&Auml;", _ "").Replace("&Aring;", "").Replace("&AElig;", "").Replace("&Ccedil;", "").Replace("&Egrave;", _ "").Replace("&Eacute;", "").Replace("&Ecirc;", "").Replace("&Euml;", "").Replace("&Igrave;", _ "").Replace("&Iacute;", "").Replace("&Icirc;", "").Replace("&Iuml;", "").Replace("&ETH;", _ "").Replace("&Ntilde;", "").Replace("&Ograve;", "").Replace("&Oacute;", "").Replace("&Ocirc;", _ "").Replace("&Otilde;", "").Replace("&Ouml;", "").Replace("&Oslash;", "").Replace("&Ugrave;", _ "").Replace("&Uacute;", "").Replace("&Ucirc;", "").Replace("&Uuml;", "").Replace("&Yacute;", _ "").Replace("&THORN;", "").Replace("&szlig;", "").Replace("&agrave;", "").Replace("&aacute;", _ "").Replace("&acirc;", "").Replace("&atilde;", "").Replace("&auml;", "").Replace("&aring;", _ "").Replace("&aelig;", "").Replace("&ccedil;", "").Replace("&egrave;", "").Replace("&eacute;", _ "").Replace("&ecirc;", "").Replace("&euml;", "").Replace("&igrave;", "").Replace("&iacute;", _ "").Replace("&icirc;", "").Replace("&iuml;", "").Replace("&eth;", "").Replace("&ntilde;", _ "").Replace("&ograve;", "").Replace("&oacute;", "").Replace("&ocirc;", "").Replace("&otilde;", _ "").Replace("&ouml;", "").Replace("&oslash;", "").Replace("&ugrave;", "").Replace("&uacute;", _ "").Replace("&ucirc;", "").Replace("&uuml;", "").Replace("&yacute;", "").Replace("&thorn;", _ "").Replace("&yuml;", "") 'replace Math Symbols Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&forall;", "").Replace("&part;", "").Replace("&exist;", "").Replace("&empty;", "").Replace("&nabla;", _ "").Replace("&isin;", "").Replace("&notin;", "").Replace("&ni;", "").Replace("&prod;", _ "").Replace("&sum;", "").Replace("&minus;", "").Replace("&lowast;", "").Replace("&radic;", _ "").Replace("&prop;", "").Replace("&infin;", "").Replace("&ang;", "").Replace("&and;", _

11

"").Replace("&or;", "").Replace("&cap;", "").Replace("&cup;", "").Replace("&int;", _ "").Replace("&there4;", "").Replace("&sim;", "").Replace("&cong;", "").Replace("&asymp;", _ "").Replace("&ne;", "").Replace("&equiv;", "").Replace("&le;", "").Replace("&ge;", _ "").Replace("&sub;", "").Replace("&sup;", "").Replace("&nsub;", "").Replace("&sube;", _ "").Replace("&supe;", "").Replace("&oplus;", "").Replace("&otimes;", "").Replace("&perp;", _ "").Replace("&sdot;", "") 'replace Greek Letters Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&Alpha;", "").Replace("&Beta;", "").Replace("&Gamma;", "").Replace("&Delta;", "").Replace("&Epsilon;", _ "").Replace("&Zeta;", "").Replace("&Eta;", "").Replace("&Theta;", "").Replace("&Iota;", _ "").Replace("&Kappa;", "").Replace("&Lambda;", "").Replace("&Mu;", "").Replace("&Nu;", _ "").Replace("&Xi;", "").Replace("&Omicron;", "").Replace("&Pi;", "").Replace("&Rho;", _ "").Replace("&Sigma;", "").Replace("&Tau;", "").Replace("&Upsilon;", "").Replace("&Phi;", _ "").Replace("&Chi;", "").Replace("&Psi;", "").Replace("&Omega;", " ").Replace("&alpha;", _ "").Replace("&beta;", "").Replace("&gamma;", "").Replace("&delta;", "").Replace("&epsilon;", _ "").Replace("&zeta;", "").Replace("&eta;", "").Replace("&theta;", "").Replace("&iota;", _ "").Replace("&kappa;", "").Replace("&lambda;", "").Replace("&mu;", "").Replace("&nu;", _ "").Replace("&xi;", "").Replace("&omicron;", "").Replace("&pi;", "").Replace("&rho;", _ "").Replace("&sigmaf;", "").Replace("&sigma;", "").Replace("&tau;", "").Replace("&upsilon;", _ "").Replace("&phi;", "").Replace("&chi;", "").Replace("&psi;", "").Replace("&omega;", _ "").Replace("&thetasym;", " ").Replace("&upsih;", " ").Replace("&piv;", " ") 'replace Other Entities Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&OElig;", "").Replace("&oelig;", "").Replace("&Scaron;", "").Replace("&scaron;", "").Replace("&Yuml;", _ "").Replace("&fnof;", "").Replace("&circ;", "").Replace("&tilde;", "").Replace("&ensp;", _ " ").Replace("&emsp;", " ").Replace("&thinsp;", "").Replace("&ndash;", "").Replace("&mdash;", _ "").Replace("&lsquo;", "").Replace("&rsquo;", "").Replace("&sbquo;", "").Replace("&ldquo;", _ " ").Replace("&rdquo;", " ").Replace("&bdquo;", "").Replace("&dagger;", "").Replace("&Dagger;", _ "").Replace("&bull;", "").Replace("&hellip;", "").Replace("&permil;", "").Replace("&prime;", _ "").Replace("&Prime;", "").Replace("&lsaquo;", "").Replace("&rsaquo;", "").Replace("&oline;", _ "").Replace("&euro;", "").Replace("&trade;", "").Replace("&larr;", "").Replace("&uarr;", _ "").Replace("&rarr;", "").Replace("&darr;", "").Replace("&harr;", "").Replace("&crarr;", _ "").Replace("&lceil;", "").Replace("&rceil;", "").Replace("&lfloor;", "").Replace("&rfloor;", _ "").Replace("&loz;", "").Replace("&spades;", "").Replace("&clubs;", "").Replace("&hearts;", _ "").Replace("&diams;", "") 'replace special ASCII coding entities that were not captured by the above. Note that values > 127 will make the text 8-bit For Idx As Integer = 1 To 255 'See www.w3schools.com/tags/ref_entities.asp Sb.Replace("&#" & Idx.ToString & ";", Chr(Idx)) 'replace most common numeric entities Next 'Ensure header definitions are followed by vbCrLf Dim NewText As String = RegularExpressions.Regex.Replace(Sb.ToString(), "</H[^>]*>", vbCrLf) 'Also seek out other Unicode encoded number entities not covered by the above and individually update them Dim Idy As Integer = InStr(NewText, "&#") 'check for a numeric entity Do While Idy <> 0 'loop as long as we find one Dim Idz As Integer = InStr(Idy, NewText, ";") 'find terminating semicolon Dim S As String = Mid(NewText, Idy, Idz - Idy + 1) 'grab expression RegularExpressions.Regex.Replace(NewText, S, Chr(CInt(Mid(S, 3, Len(S) - 3)))) 'replace expression InStr(Idy + 1, NewText, "&#") Loop 'strip remaining HTML text tags, replace < and > placeholders, convert ampersand, replace ;; with ;, then return result Return RegularExpressions.Regex.Replace(NewText, "<[^>]*>", "").Replace("&lt;", _ "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function

12

Suppose you wanted to send both A Rich Text and a Plain Text version of an email to someone, so if they were trendy and wanted to view the chic version of your email as Rich Text, they could. Or, if they were viewing your email on their trendy Blackberry, Droid, iPhone, or other small Email-enabled portable device, they could read your email in plain text, which reads much better on small screens. You typically pass the Plain Text version as the strBody parameter of the SendEMail() method. The Rich Text or HTML version would be sent as an alternate view. Email is progressing to the point to where most emails are in HTML format, which is why we have the IsBodyHtml parameter on a MailMessage object, allowing us to transmit the main message body as HTML. However, Rich Text is becoming so common that I expect one day to see an industryrecognized IsBodyRtf parameter, though I would really hope that the main body would be forced to Plain Text, and IsHtmlBody be revoked. Or, to be diplomatic, simply allow the user to specify the ContentType and Content-Transfer-Encoding of the message body they send. Consider the following, where early on in my trials I tried to send an email with a RTF alternate view:
'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf) 'send our email as plain text and with a rich text alternater view SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

As you see, creating an AlternateView object seems almost too easy. You can see it using the Rich Text data for the alternate view, and the plain text for the message body parameter. But look at my result:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 22 Feb 2011 17:19:08 -0500 Subject: Test Content-Type: multipart/alternative; This email is multi-part (either alternate view or attachments added) boundary=--boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Declare unique boundary: will be used to mark the boundaries of each part ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcZGVmZjBcZGVmbGFuZzEwMzN7XGZvbnR0Ymx7XGYw XGZuaWxcZmNoYXJzZXQwIFRpbWVzIE5ldyBSb21hbjt9e1xmMVxmbmlsXGZjaGFyc2V0MCBN aWNyb3NvZnQgU2FucyBTZXJpZjt9fQ0Ke1xjb2xvcnRibCA7XHJlZDBcZ3JlZW4wXGJsdWUw O30NClx2aWV3a2luZDRcdWMxXHBhcmRcY2YxXGYwXGZzMjQgVGhpcyBpcyBhIFxiIHRlc3Qg XGIwIG9mIFxpIFJpY2ggVGV4dCBkYXRhXGkwIC5ccGFyDQpccGFyZFxjZjBcZjFcZnMxN1xw YXINCn0NCg== ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321-- First REAL boundary marker. Main body of email follows (usually plain text) Used as clue toward how to process data after it is decoded Encoded as Quoted-Printable. All controls codes rendered visible (=xx) This first blank line is NOT part of the message body (MUST BE BLANK) Note the decoration (=0A); a necessary result of email. Easy to address. End boundary for main body, as well as start boundary for next part This reports plain text using UTF-8 (8-bit Unicode) Encoded using Base64 method This first blank line is NOT part of the attachment (MUST BE BLANK) Base64-encoded data (is this rich text?.......Yes, it is, but encoded. When it is decoded, it will be able to be processed as rich text)

End boundary for second part. Side note: note the extra "--" on both sides

What the? (Well, that is what I first said. The notes tend to indicate garnered knowledge since then.)
NOTE: Even though I did not realize it at the time I first saw this, I can in fact fully use and properly translate the above alternate view data with unbelievable ease. But that is getting ahead of what I had thought I was trying to do

Sending Alternate Message Views with Different Context Types and Transfer Encoding Theres that Base64 thing again. Also, my Rich Text version of the message looks like the cat was dancing on the keyboard, chasing the fish on the computers screen saver. But for this implementation, the MSDN documentation states in its remarks: The default media type is plain text and the default encoding is ASCII. Well, thats kinda true, except that UTF-8 (8-bit Unicode Transformation Format) is not actually ASCII (Standard ASCII, also known as simply ASCII, is strictly 7-bit code). Microsofts version of ASCII it is a transformation format for translating between 16-bit Unicode and 8-bit Extended-ASCII code (UTF-8). And there is also that Base64 part. Something smells fishy, and its not the fish on the screen saver. In my more rational mind, I acknowledged that the first part, the plain text version, looks fine. But the Rich Text version is tagged, according to the Content-Type, as plain text, UTF-8. Yet the ContentTransfer-Encoding indicates Base64. I know I have seen many other Rich Text emails, and they show 13

that the Content-Type is text/richtext, but with them you can even see the HTML-like Rich Text encoding of the message body perfectly fine. So instead of shaking my fists at the heavens in forlorn, unquenchable yearning for success, I decided that it was time to hit the books and find out what I was not (yet) doing. Scanning the internet yielded no useful results. I found countless requests for help from people in the same boat as me (well 438,000 anyway; a crowded boat), but the knowledgeable respondents, always trying to be helpful (except for the non-social jerks who just want to make everyone else as miserable as they are), replied with some resigned candor that the hapless inquisitors were stuck with Base64 encoding, and we are presently incapable of changing that harsh truth any time soon under VB.NET. After more time searching MSDN resources, I found another overload (which, dummy me, I should have found in the first place), specifying another parameter for the CreateAlternateViewFromString() method. With it, I could add a second parameter that was a System.Net.Mime.ContentType object. Hmm. Maybe it was not fish I smelled, after all. Is it maybe bovine scat? If we look at this ContentType object, we can specify the text that Content-Type reports, such as text/plain, text/richtext, or whatever I choose (this would be handy for encrypted emails, where you could specify text indicating which decryption method for your custom email reader to use). Constructing a ContentType object is easy. For example:
Dim ContentType As New Mime.ContentType(Mime.MediaTypeNames.Text.RichText)'create an alternate view of the RTF data from our RichTextBox

NOTE: All the code in this article will assume that you have at least imported System.NET. and System.Text.

Now suppose we sent that email again


'create a custom ContentType object to "specify text/richtext" Dim ContentType As New Mime.ContentType(Mime.MediaTypeNames.Text.RichText)'create an alternate view of the RTF data from our RichTextBox Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf, ContentType) 'send our email as plain text and with a rich text alternater view SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

DOH! The results are the same! The only difference between the current version and the previous version is that instead of the Content-Type declaring text/plain, it states text/richtext, but the Content-Transfer-Encoding was still set to Base64 (right now, Im starting to hate Base64 a lot).
NOTE: Unbeknownst to me at the time, but this just gave me all the information I needed to actually use that data!

But Wait! I did more checking and I realized that there were actually 3 overloads to the CreateAlternateViewFromString() method (I guess I tend to go blind at 2 oclock in the morning, even though I have plenty of time to sleep and still get up at 5 AM). I searched really hard to find MSDN examples that demonstrated them all (which took some real doing. By the way, all the examples I did find on MSDN were for C# and C++, as though VB code was totally incapable of doing these simple things. Idjuts). The third overload did not expect just a Mime parameter, but it expected a System.Text.Encoding class and then the System.Net.Mime.MediaTypeNames string. (I can already sense a move into the right direction)

Encoding specifies a series of conversion classes. Usually, we use these classes to convert one type of text to another. But here, by specifying a method, like System.Text.Encoding.ASCII, we do not use the method, but rather we supply that method to SMTP, which will in turn use it to convert our data. Based on the kind of conversion we specify from the Encoding class, the Content-Transfer-Encoding mechanism (which I had been having issues with) will be internally set to one of 3 values: quotedprintable, where all control codes are marked with 7-bit Hex-Tags (= followed by a 2-character hexadecimal value, such as =0D for a vbCr), base64, where binary or 8-bit code, or text containing 8bit code will be compressed into a string of 7-bit text tokens, or 7bit, which is unencoded 7-bit data.

14

Therefore, if we are passing ASCII data, such as Plain Text, HTML, or Rich Text, it will be encoded as Quoted-Printable as long as we pass a text-based method from Encoding. If we leave this field blank, or the data is not text, or it contains 8-bit text, it can be expected to be processed as Base64. And that is why we have been getting Base64 as the Content-Transfer-Encoding type all this time on attachments.
NOTE: An AlternateView object is actually embedded within an email as an Attachment. The only difference between the two is that an AlternateView will not specify a filename parameter in the Content-Type field. For example, ContentType: text/plain; charset=us-ascii versus Content-Type: text/plain; name="Baby Names.txt". Note further that the specified default filename in an Attachment will not be quoted if there are no spaces embedded within it.

MediaTypeNames specifies 3 subclass types of interest, which are most notably useful for regular attachments: Application, Image, and Text. Each of these in turn has subtypes, which is why you see the Context-Type field indicate text/plain or application/octet-stream. The first part is the Type, and the second part, after the slash, is the Subtype. All this will, again, be discussed in the next section. Presently, we are most interested in the Text type. But we must remember that this field is only text and informational to the email application. It does nothing to the data; it simply specifies how the decoded data should be processed. In fact, if you specify text/plain, but the data contains 8-bit characters, you will find that the encoding method will be forced to Base64, which is actually valid and legal.
NOTE: Rather than depend on using the System.Net.Mime.MediaTypeNames class to determine this Content-Type data, you can simply supply your own string, as long as your recipients email software can use that information.

With the above gained knowledge, we will try transmitting our email again:
'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf, _ System.Text.Encoding.ASCII, _ System.Net.Mime.MediaTypeNames.Text.RichText) 'send our email as plain text and with a rich text alternater view SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

And now, as you can see, our email result is inching much closer to my vision of it being perfect:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 22 Feb 2011 20:56:39 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 End of message body, start of an attachment block. Content-Type: text/richtext; charset=us-ascii This attachment is an alternate view, because a NAME parameter is not specified. Content-Transfer-Encoding: quoted-printable Because data is encoded Quoted-Printable, you will see the (=) Hex-Tags decorating the following RTF message block. {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Times= Because the text is encoded Quoted-Printable, Hex-Tags have been New Roman;}{\f1\fnil\fcharset0 Microsoft Sans Serif;}}=0D=0A{\colortbl= added for all control codes to ensure total original media content. ;\red0\green0\blue0;}=0D=0A\viewkind4\uc1\pard\cf1\f0\fs24 This is a \b= test \b0 of \i Rich Text data\i0 .\par=0D=0A\pard\cf0\f1\fs17\par=0D=0A}= =0D=0A ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9

In the above Rich Text data, an equals sign =precedes each physical end of line, which must be removed along with the vbCrLf following it. They are used to mark a soft line return, dictated by its requirements, not by the original text. This equals sign also precedes document special codes (having a value less than 32) that are rendered to Hex-Tags by being converted to 7-bit Hex format (for example, =OD represents Hex(&HD), or a Carriage Return, and =0A represents Hex(&HA), or a Linefeed). This, and any other hex digits that are formatted as a Hex-Tag, will have to be translated back to their original codes (the fact that the encoding reported quoted-printable should clue you in to that). You may also see a lot of =20 tags, which represents a non-breaking space, inserted in places where it would otherwise be removed or ignored. Note also that an actual = in the text will be, ironically, encoded to =3D; the hex code for =.
NOTE: Though Quoted-Printable encoding will change conversion to Base64 if it finds bytes codes greater than 127 (8-bit data), we will later provide a method to encode those 8-bit codes to Hex-Tags, so we can send them Quoted-Printable, or even as 7bit.

15

For example, if a string variable named Msg contains the above lines of RTF data, we could filter it to a more perfect form using the following general quick-clean code:
Me.RichTextBox1.Rtf = Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " "c).Replace("=3D", "="c)

Hence, the above RTF data becomes perfectly formatted to its original form:
{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Times New Roman;}{\f1\fnil\fcharset0 Microsoft Sans Serif;}} {\colortbl ;\red0\green0\blue0;} \viewkind4\uc1\pard\cf1\f0\fs24 This is a \b test \b0 of \i Rich Text data\i0 .\par \pard\cf0\f1\fs17\par }

But one thing it does not do is take care of other possible hex codes, which we will inevitably encounter. So I end up where I was, unless I choose to implement code to slog through the data that could easily chew up a lot of resources while it processes. An example of such an interim solution follows:
Msg = Msg.Replace("=" & vbCrLf, vbNullString) 'clean up line termination tags For idx As Integer = &H1 To &HFF 'process the whole ASCII gambit (=00 never used) Dim Hx As String = Hex(idx) 'convert to hex value If idx < 16 Then Hx = "0" & Hx 'leading zero if less than 16 (1-9, A-F) Msg = Msg.Replace("=" & Hx, Chr(idx)) 'replace hex data with single character code Next Me.RichTextBox1.Rtf = Msg 'stuff result to rich text box

But wait a minute! If you recall from an earlier article, I mentioned something about the StringBuilder object that was 200 times faster than standard strings. Manipulations use very little resources, and what resources they do use will get flushed away in a flash when we leave the function. We will explore such support in our next section, where we will cover E-Z decoding of Quoted-Printable and Base64 data. However, let us stop what we are doing and simply think about ASCII-type encoding for a moment. ASCII refers to 7-bit code (128 character codes), even though now we often think of it in terms of 8-bits (256 codes). But that is all because of the immense presence of the Microsoft Windows environment. On the internet, though, where server standards must conform to the least-common-denominator, Unix is King, and many (ancient) versions of Unix use only standard ASCII. If we look under .NET, the ASCII encoding class actually does support 7-bit ASCII, so some of you may be wondering why your control codes are being translated to Hex-Tags, when they are supposed to be a part of standard ASCII? Because the text is being encoded as Quoted-Printable, and this specification always renders all of its non-displayable ASCII tokens as displayable Hex-Tags, so not one character of that data is lost.
NOTE: One of the cardinal rules of email processing is that not one byte of a transmitted message can ever be allowed to be lost or altered in such a way that they cannot be fully restored to their original format. Hence, even if you send a plain text message, but the plain text contains some 8-bit codes, such as Chr(149), , the text will be forced to being encoded to Base64, so that the data can be transmitted over the internet as 7-bit ASCII data and can afterward be fully restored.

So what I really need is unencoded 7-bit US-ASCII support. Hmm. I first thought about UTF-7, which is a 7-bit version of Unicode, but then I realized that the same result would follow, due to QuotedPrintable encoding. However, according my references, the unencoded 7bit option is exactly what I need. But how do I get there? It seemed that no matter what I had been trying, I was getting no closer to attaining my actual goal: sending the rich text alternate view as unencoded rich text data. I would have to think outside the box I had worked myself into. That was when I considered that an AlternateView and an Attachment, like everything else in .NET, is an object. And objects seem to always have a lot of properties. So I took a look at Attachment and AlternateView properties. Eureka! I instantly discovered two members that were perfect to my purpose: The first was ContextType, and the other was TransferEncoding (the little hairs on the back of my neck are starting to rise). And even better, they were both malleable properties (that is, they are properties that we can both read from and write to). I did not expect that they would be so easy to find, considering everyone who has been complaining about this on the internet. But the important thing was that I now had exactly what I need. 16

After a little exploring, I figured out that all I had to do was set the MediaType string property of ContextType. I could use System.Net.Mime.MediaTypeNames again to set it. TransferEncoding was an integer enumeration, offering me a choice of QuotedPrintable (0), Base64 (1), or SevenBit (2). A fourth option, Unknown, spits only venomous messages of non-support into our eyes, if we try to play with it. But the good news is I can use the SevenBit option! So I try again, this time using the following code:
'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf) rtfView.ContentType.MediaType = Mime.MediaTypeNames.Text.RichText 'format our new view as rich text... rtfView.TransferEncoding = Mime.TransferEncoding.SevenBit 'and send unencoded as 7-bit ASCII 'send our email as plain text and with a rich text alternater view SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

The results were spectacular. You may also want to apply SevenBit encoding to HTML and Plain Text data, as long as they do not contain any embedded 8-bit coding. Consider my final result:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 25 Feb 2011 14:22:46 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A Note the vbLf terminator here in the plain text ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def Content-Type: text/richtext; charset=utf-8 Note text/richtext Content-Transfer-Encoding: 7bit Note 7bit encoding (or rather, 7bit indicating NO ENCODING) {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Times New Roman;}{\f1\fnil\fcharset0 Microsoft Sans Serif;}} {\colortbl ;\red0\green0\blue0;} \viewkind4\uc1\pard\cf1\f0\fs24 This is a \b test \b0 of \i Rich Text data\i0 .\par \cf0\f1\fs17\par } Blank line is due to an additional embedded vbCrLf code in the Rich Text data ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def

Were we to plug the above raw Rich Text data without modification into the Rtf property of a RichTextBox, we would see the result shown on the right.

17

PART TWO Encoding and Decoding Email Data When we send and receive email, the parameters noted for the Content-Type and Content-TransferEncoding fields are of great importance. If you provide the wrong information, the receiving email reader can get confused. When there is confusion at either end, they default to assuming a Content Type of application/octet-stream, or a binary stream, and Base64 for Content-Transfer-Encoding. The goal is never to lose original content. For example, if you specify a Quoted-Printable-class encoder, but the data is 8-bit, then the transmitter will supersede your option and specify Base64 encoding.

But where does all this come from? Where and how can we tell VB.NET how to transmit these instructions? What if I can barely understand application/octet-stream, let along spell it consistently? And what in blue blazes is Base64 encoding, anyway?
NOTE: Blue Blazes refers to very hot Hell fire, coming from the extremely hot blue flame given off by the likes of propane.

The .NET MIME processor has two built-in classes that will help you easily resolve the Content-Type and Content-Transfer-Encoding issues. The first helper is the System.Net.Mime.TrasferEncoding enumeration, and the second helper is the System.Net.Mime.ContentType class. Based on the kind of conversion we specify for encoding, the Content-Transfer-Encoding mechanism will reflect one of three values (a fourth, Unknown, is presently defined, but is never used):
Member: QuotedPrintable Base64 SevenBit Unknown Description: (0) Encodes data that consists of printable characters in the US-ASCII character set. See RFC 2045 Section 6.7. All Text encoders default to this (US-ASCII, UTF7, UTF8, etc.). Maximum line length is 75 characters. Stored in email as quoted-printable. (1) Encodes stream-based data. See RFC 2045 Section 6.8. Best for Binary, or any code you do not want to deal with control-code conversion Hex-Tags. Maximum line length is 75 characters. Stored in email as base64. (2) Used for data that is not encoded. The data is in 7-bit US-ASCII characters with a total line length of no longer than 1000 characters. See RFC 2045 Section 2.7. Stored in email as 7bit. (-1) This is currently not supported and will never be selected by the SMTP server.

NOTE: RFC 2045 can be found at www.ietf.org/rfc/rfc2045.txt. Also, the MSDN documentation actually erroneously references RFC 2406, which in fact documents the IP Encapsulating Security Payload (ESP). RFC 2045 refers correctly to Multipurpose Internet Mail Extensions, (MIME) Part One: Format of Internet Message Bodies.

Therefore, if we are passing ASCII data, such as Plain Text, HTML, or Rich Text, it will be encoded as QuotedPrintable as long as we specify a text-based method from Encoding. If we leave this field blank, or the data is not 7-bit text, it can be expected to be processed as Base64. And that is why we tend to get Base64 as the Content-Transfer-Encoding type a lot on Attachments and Alternate Views. For specifying the data for the Content-Type field, we look to System.Net.Mime.MediaTypeNames. MediaTypeNames specifies 3 types of interest, which are most notably useful for regular attachments:
Member: Application Image Text Description: Specifies the kind of application data in an email message attachment or AlternateView Specifies the type of image data in an email message attachment or AlternateView Specifies the type of text data in an email message attachment or AlternateView

The MediaTypeNames.Application type exposes the following String members:


Member: Octet Pdf Rtf Soap Zip Context-Type Field Reports: application/octet-stream application/pdf application/rtf application/soap+xml application/zip Description: Specifies that the Application data is not interpreted (an Octet-stream (Byte-stream)). Specifies that the Application data is in Portable Document Format (PDF). Specifies that the Application data is in Rich Text Format (RTF). Specifies that the Application data is a SOAP document (Simple Object Access Protocol). Specifies that the Application data is compressed.

If you want to specify other sub-types, you can, but there is no guarantee that someone elses email processor will support it. The .NET MediaTypeNames class strictly follows the RFC 2045 specification. The MediaTypeNames.Image type exposes the following String members:
Member: Gif Jpeg Tiff Context-Type Field Reports: image/gif image/jpeg image/tiff Description: Specifies that the Image data is in Graphics Interchange Format (GIF). Specifies that the Image data is in Joint Photographic Experts Group (JPEG) format. Specifies that the Image data is in Tagged Image File Format (TIFF).

18

The MediaTypeNames.Text type exposes the following String members:


Member: Html Plain RichText Xml Context-Type Field Reports: text/html text/plain text/richtext text/xml Description: Specifies that the Text data is Specifies that the Text data is Specifies that the Text data is Specifies that the Text data is in HTML format. in Plain Text format. in Rich Text Format (RTF). in XML format.

Allowing Users to Specify Content-Type and Content-Transfer-Encoding Options Of course, the options you offer your email application users can differ, or can even be similar to the previously mentioned tables. However, you will need to have a mechanism in place for either interpreting their Attachment/Alternate View choices, or for allowing them to explicitly specify them. The easier you make it for them, the more trust and loyalty they will have for your products. Just do not make it so brain-dead-simple that you begin to restrict them in their options. If you refer to the ReadEmail() method comments, you will notice that the specification for an Alternate View showed you how to stipulate a different Content-Type and Content-Transfer-Encoding type by using VB programming methods. Well, this all well and good when you are programming such code, but it is not very convenient for your users if they wish to specify such things, nor is adding parameterized declarations for an attachment. For example, consider having to hand-type C:\My Files\API32.txt (text/plain, SevenBit), and even more so for a binary file, like C:\telnet.exe (application/octet-stream,Base64). It would of course be more convenient to allow them to select the files from a browser and for you to either interpret their attachment and determine how best to transmit them (this is not as difficult as you might think), or allow them, maybe as an advanced user option, to specify both parts themselves from dropdown ComboBox controls, and then you would simply wrap them up in a manner recognizable by the SendEmail() method, though I would imagine that you would customize the code to your needs, such as adding trendy features like specifying email recipients by name, rather than just their full email addresses. This makes selecting recipients more convenient, such as using a current naming format that is popular and is already supported by the .NET Mail object, like the following: "David Ross Goben" <david.ross.goben@gmail.com>. Consider the following structure and code to simplify defining Content-Type text:
'******************************************************************************* ' Enum MediaTypes: Enumeration used by GetMediaType '******************************************************************************* Public Enum MediaTypes As Integer ApplicationOctet ' 0 = Integer Value ApplicationPdf ' 1 ApplicationRtf ' 2 ApplicationSoap ' 3 ApplicationZip ' 4 ImageGif ' 5 ImageJpeg ' 6 ImageTiff ' 7 TextHtml ' 8 TextPlain ' 9 TextRich '10 TextXml '11 End Enum '******************************************************************************* ' Function Name : GetMediaType ' Purpose : Provide easy access to System.Net.Mime.MediaTypes text ' : ' Returns : provided a MediaTypes enumeration value, a string representing ' : the selected type will be returned. '******************************************************************************* Public Function GetMediaType(ByVal MediaType As MediaTypes) As String Select Case MediaType Case MediaTypes.ApplicationPdf Return "application/pdf" Case MediaTypes.ApplicationRtf Return "application/rtf" Case MediaTypes.ApplicationSoap Return "application/soap+xml" Case MediaTypes.ApplicationZip Return "application/zip" Case MediaTypes.ImageGif Return "image/gif" Case MediaTypes.ImageJpeg Return "image/jpeg"

19

Case MediaTypes.ImageTiff Return "image/tiff" Case MediaTypes.TextHtml Return "text/html" Case MediaTypes.TextPlain Return "text/plain" Case MediaTypes.TextRich Return "text/richtext" Case MediaTypes.TextXml Return "text/xml" Case Else Return "application/octet-stream" End Select End Function

For options to the user, you can present those options to them however you wish (or they wish), such as the text displayed within the enumeration in a ListBox or ComboBox control, presented to them in prettier or simpler text, such as Application/Octet or even Binary, or even by first separating the three types, Application, Image, and Text, and then present secondary subtype lists for each. The methods for how to do this are many. Simply ensure that the final values match those listed in the comments for their integer values. You can then supply this integer value (or first cast it, using something like DirectCast(Value, MediaTypes)) to the GetMediaType() function, which will return the appropriate, email-friendly Content-Type text. For simplifying Content-Transfer-Encoding, consider the following simple structure and method:
'******************************************************************************* ' Enum TransferEncodings: Enumeration used by GetTransferEncoding '******************************************************************************* Public Enum TransferEncodings As Integer QuotedPrintable ' 0 = Integer value Base64 ' 1 SevenBit ' 2 End Enum '******************************************************************************* ' Function Name : GetTransferEncoding ' Purpose : Provide easy access to System.Net.Mime.TransferEncoding data ' : ' Returns : Provided a TransferEncodings value, a TransferEncoding value ' : is returned. '******************************************************************************* Public Function GetTransferEncoding(ByVal TransferEncoding As TransferEncodings) As System.Net.Mime.TransferEncoding Return DirectCast(TransferEncoding, System.Net.Mime.TransferEncoding) End Function

Using techniques similar to those already outlined for GetMediaType(), you can do the same thing for your users, allowing them to specify how they want their data to be encoded.

Ultimately, you are the one who must specifically inform the SMTP server of the type and encoding. Since an Alternate View objects MediaType parameter (MyAlternateView.ContentType.MediaType) expects a string, setting it is easy you just supply it with the string you got from the GetMediaType() function. Also, because the Alternate Views Transfer Encoding (MyAlternateView.TrasferEncoding) expects an integer cast to System.Net.Mime.TrasferEncoding, you can simply provide it the value returned by the GetTransferEncoding() method, or, simply use the (no real code) DirectCast() method to cast their ComboBox selection index (0-2) to System.Net.Mime.TrasnferEncoding.

20

Determining if Text can be Encoded As Quoted-Printable, Base64, or 7Bit When you are preparing to specify coding, or the lack thereof for text, you need to ensure that if you are going to submit it as using as Quoted-Printable or 7Bit, that it will not be forced to Base64, if it ends up containing any 8-bit data, unless that is what you want to happen. If not, you can first scan it with the following little function, which will quickly determine if the code contains 8-bit data:
'******************************************************************************* ' Function Name : TextNeedsEncoding ' Purpose : Determine if HTML text, Rich Text, or Plain Text requires ' : 8-bit code translation to 7-bit Quoted-Printable tags. ' : ' Returns : Provided a source string, it returns a boolena flag. ' : If the returned value is true, the source contains 8-bit data ' : and will be encoded by server. ' : ' NOTES : If text data contains 8-bit values, the default .NET ' : SMTP processor will force this code to be encoded to Base64, ' : even if only a single byte is 8-bit. ' : ' : To avoid this, the Force7BitHtml() method can be invoked on ' : HTML text to ensure that it is 7-bit encoded so that it can ' : be processed as Quoted-Printable or as 7Bit. The ForceQuotedPrintable() ' : method performs essential conversions for non-HTML text, but this ' : would be best served in Attachments and Alternate Views. '******************************************************************************* Public Function TextNeedsEncoding(ByVal Message As String) As Boolean Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array For Each B As Byte In Byt If CBool(B And &H80) Then Return True Next Return False End Function

If the TextNeedsEncoding() method returns True and you are processing HTML text, you can pass the HTML text through the Force7BitHtml() function (listed below), which will ensure, upon return, that all codes in the HTML text is 7-bit, and that any 8-bit or 16-bit code that was in it is dutifully converted to 7-bit HTML Entity Numbers. If you are passing the function Rich Text or Plaint Text, you can specifically Hex-Tag its 8-bit characters (something that the default Quoted-Printable encoders will not do, even though it is permitted) using the ForceQuotedPrintable() function (listed further below).
Converting 8-Bit HTML Data to 7-Bit without Loss of Integrity Consider the following simple HTML upgrade function that will transparently convert any HTML text that contains 8-bit or 16-bit data to fully compatible 7-bit HTML text:
'******************************************************************************* ' Function Name : Force7BitHtml ' Purpose : Method to convert 8-bit code in an HTML message to 7-bit. ' : ' Returns : Provided a string containing HTML code, it will return a string ' : containing HTML code that does not have any 8-bit data embedded. ' : ' NOTES : If any characters in an HTML text string are 8-bit (values ' : greater than 127), then they are converted into a special ' : 7-bit HTML Entity Number, For Example, code 149 () is an 8-bit ' : value that can be changed to HTML "&#149;", which will ensure ' : that it will still be displayed on the HTML page, but the HTML ' : souce code will no longer carry an actual 8-bit value. If such ' : code had not been corrected, the encoding of the data would be ' : forced to change from quoted-printable to Base64, because that ' : would be the only way the email processor could guarantee that ' : the email text was fully intact. '******************************************************************************* Public Function Force7BitHtml(ByVal HtmlSource As String) As String Dim Sb As New StringBuilder 'set up string builder for appending data For Idx As Integer = 1 To Len(HtmlSource) Dim C As Integer = AscW(Mid(HtmlSource, Idx, 1)) 'get a single character from the source Select Case C 'check each character Case Is > &H7F, Is < 0 'if 8-bit or unicode code Sb.Append("&#" & C.ToString & ";") 'convert to 7-bit HTML ecoder Case Else Sb.Append(ChrW(C)) 'else save text regardless End Select Next Return Sb.ToString End Function

21

Converting 8-Bit Text Data to 7-Bit without Data Loss If you test a text string with the TextNeedsEncoding() function and it comes back True, you can either encode it using Base64, or, if you want it to remain readable in raw format, you can convert it to a HexTagged 7-bit text format by passing it through the ForceQuotedPrintable() function, listed here:
'******************************************************************************* ' Function Name : ForceQuotedPrintable ' Purpose : Force 8-bit code in a text message to 7-bit, without data loss. ' : ' Returns : Provided a source string that contains 8-bit data, the 8-bit ' : data is converted to Hex-Tags, and the returned string is 7-bit. ' : ' NOTES : if any characters in a text string are 8-bit (values greater ' : than 127), then they are converted into special 7-bit tags. ' : For Example, code 149 () is an 8-bit value that can be changed ' : to hex "=95", which will ensure that it will still be displayed ' : in the text, but the text data will no longer carry an actual ' : of the data would be forced to change from quoted-printable or 7bit ' : to Base64, because that would be the only way the email processor ' : Base64, because that would be the only way the email processor ' : could guarantee that the email text was fully intact. However, ' : you will have to use the DecodeQuotedPrintable() method to convert ' : it back to its original text form. ' : ' : The Encoded text will begin with "=00". Because unencoded null codes ' : are not permitted in email data, you can use this to instantly ' : determine on the receiving end that this code will need to be ' : processed by DecodeQuotedPrintable() a second time (if initially ' : encoded as Quoted-Printable). A second pass would be required, ' : because if this translated code was afterward encoded as Quoted' : Printable, and all the "=xx" byte-translations, would be ' : reinterpreted as "=3Dxx", which DecodeQuotedPrintable() would ' : convert back to "=xx", so passing through a second time would ' : properly convert the additional encoding. Further, by checking the ' : text startiing with "=00", you would know that you would need to ' : double-decode the text. Also, you would want to initially skip this ' : initial tag when passing it the second time to DecodeQuotedPrintable(): ' : ' : Dim Result As String = DecodeQuotedPrintable(Message) 'initially decode Quoted-Printable text ' : If VB.Left(Result, 3) = "=00" Then 'tagged as pre-encoded? ' : Return DecodeQuotedPrintable(Mid(Result, 4)) 'yes, so decode again and return, less initial null byte ' : Else ' : Return Result 'otherwise, return result of decoding ' : End If '******************************************************************************* Public Function ForceQuotedPrintable(ByVal Message As String) As String

Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) Dim Sb As New StringBuilder("=00") For Each B As Byte In Byt Select Case B Case Is > &H7F Sb.Append("=" & Hex(B)) Case Else Sb.Append(Chr(B)) End Select Next Return Sb.ToString
End Function

'convert message to byte array 'set up string builder for appending data 'check each byte 'if 8-bit code 'convert to 7-bit tag 'else save text regardless

The ForceQuotedPrintable() method uses the same encoding tags for 8-bit code that are used by Quoted-Printable encoding for control codes. However, if you also pass it as Quoted-Printable, this means that you will have to decode it twice (discussed next). The first time to decode the = tags added by the internal SMTP processor, and a second time to decode the tags that you added using the above method prior to SMTP encoding the data as Quoted-Printable. You may notice that the ForceQuotedPrintable() method leads the result text with =00. You can simply check for this in your email processor, and if found, as shown in the above comments, you can decode it a second time. This can be done because the above encoder may add Hex-Tags for 8-bit values, but the internal SMTP processor will not know that these are Hex-Tags, because it would not expect them, so it would encode =95 to =3D95, where the =3D is the equals sign encoded (this symbol has a hex ASCII value of &H3D). This will decode to =95, and then the =95 will in turn have to be decoded in a second pass (&H95 is 149 decimal, and represents the symbol). Of course, you can also encode this data in an attachment or alternate text as 7Bit, but you should still check for the leading =00 tag, to see if you will still need to decode it back to 8-bit text.
NOTE: Be reminded that other email processors will not have this kind of arcane knowledge, and so their users will still see any Hex-Tag encoding that the ForceQuotedPrintable() method had placed in there.

22

Decoding Quoted-Printable Text Quoted-Printable text has a line length limit set at about 75 characters (including the line terminator). I see it more as a when you find out that you are at 72-75 characters, then do a forced soft line wrap. All text encoded using the Quoted-Printable method will have Hex-Tag encoding for any control codes, except maybe extremely simply 1-line notes. Regardless, run all Quoted-Printable encoded data through the following DecodeQuotedPrintable() method to render them into their original format.
'******************************************************************************* ' Function Name : DecodeQuotedPrintable ' Purpose : Method to clean typical control translations, or all of them. ' : This should be invoked for all data coded Quoted-Printable. ' : ' Returns : Provided a raw message string block, it returns a decoded string. ' : ' NOTES : Typical cleaning involves changing "=0D" to vbCr, "=0A" to vbLf, ' : "=20" to a space, and "=3D" to "=", plus any line wrap ' : terminators at the end of lines to vbNullstring. ' : ' : A StringBuilder object will be used, which will very quickly ' : do a replacement of all control code translations using fewer ' : resources, and what resources that are used will be instantly ' : flushed when the method exits. '******************************************************************************* Public Function DecodeQuotedPrintable(ByVal Message As String, Optional ByVal QuickClean As Boolean = False) As String 'set up StringBuilder object with data stripped of any line continuation tags Dim Msg As New StringBuilder(Message.Replace("=" & vbCrLf, vbNullString)) If QuickClean Then 'perform a quick clean (clean up common basics) Return Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " ").Replace("=3D", "=").ToString Else 'perform total cleaning 'store 2-character hex values that require a leading "0" Dim HxData As String = "X0102030405060708090A0B0C0D0E0F" For Idx As Integer = 1 To &HF 'initially process codes 1-15, which require a leading zero Msg.Replace("=" & Mid(HxData, Idx << 1, 2), Chr(Idx)) 'replace hex data with single character code (SHIFT is faster) Next For idx As Integer = &H10 To &HFF 'process the whole 8-bit extended ASCII gambit Msg.Replace("=" & Hex(idx), Chr(idx)) 'replace hex data with single character code Next Return Msg.ToString 'return result string End If End Function

The above function will execute much faster than the conversion method demonstrated a little earlier, plus it can perform an optional quick clean for minor jobs, or a detailed cleaning in just about an instant (but I wish a StringBuilder would do blind replacements (MID()-like operations) so I could make it even faster, plus a Find method to emulate Instr()-type functionality would also be nice). Basically, what this method does is convert the Hex-Tags with their 2-character hexadecimal digits (0-9, A-F) back to their original 8-bit character codes (> &H7F), or their original control codes (< &H20). A special case was spaces =20, which, if at the end of a line, we often lost, so they were encoded. On top of that, because there was an imposed 75-character line limit, soft line-wraps were added by place a = at the end of a forced returned line. The DecodeQuotedPrintable() method reverses that, providing you with pristine text data that was formatted exactly as it had been provided to the server. Translating Base64 Data Back to Its Original Format For all the trouble I had earlier with Base64, converting it to Text or a binary format is not difficult at all. Base64, for those of you who have been waiting with bated breath for a definition, is really simple enough, and is laid out much like a Lucky Charms secret decoder ring, but with some Umph! (And why does their breath smell like fish bait? Or was that restrained, or anticipating breath?) Base64 represents binary (or 8-bit) data in an ASCII string by translating the data into a radix-64 format. Various forms of this are commonly used in applications when there is a need to encode binary data that needs be stored and transferred over media that are designed to deal with strictly 7-bit text data. This is to guarantee that the data remains fully intact without modification during transport, and can be decoded to 100% of its orginal form. This type of encoding was needed to initially support dialup communication between systems running different operating systems. For example, Base64-type ecoding for an uppercase-only, symbol-rich character set was implemented as uuencode by UNIX, and BinHex by the 23

TRS-80 (this format was later adapted for the Macintosh). Because of their compatability, a messaging application could make better assumptions about what characters were safe to use.
Base64, as used by MIME, has a 64-character set defined in the table shown to the right, where a plain ASCII character is used to represent a numeric index: Each byte of text is composed of 8 bits. Were we to look at any bytes in radix-2 (Base2, or Binary); it would look like a string of 8 ones and/or zeros.
For example, each letter of my name, David, is represented by the decimal ASCII codes 68, 97, 118, 105, and 100. In hexadecimal, which can be computed using the Hex() function, it would be &H44, &H61, &H76, &H69, and &H64. These are represented in binary streams 01000100, 01100001, 01110110, 01101001, and 01100100. Together, we get 0100010001100001011101100110100101100100. Rather confusing, I know (but strangely, it is actually quite easy to translate in your head, once you know hexadecimal).
NOTE: Each letter of a hex value 0 to F (15 decimal) represents 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, and 1111 in Binary, where the position values from left to right are 8, 4, 2, and 1.
Char Index A B C D E F G H I J K L M N O P 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 Char Index Q R S T U V W X Y Z a b c d e f 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Char Index g h i j k l m n o p q r s t u v 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 Char Index w x y z 0 1 2 3 4 5 6 7 8 9 + / 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

If we look at this stream as a table that is sectioned off at 8 bits, it is less confusing, and less at 4 binary bits. But we are more interested in breaking this up into Base64, which uses only 6 bits (values 0-63, or 00-3F hex, or 00|0000 to 11|1111 binary). So examine this long stream of bits in that light:

You can compare the bit patters to the upper, 8-bit blocks for Base256, and the lower, 6-bit blocks for Base64. The Hex index takes 6 consecutive bits and converts them to Base16, or hexadecimal. That is, the first 2 bits comprise the first hex value (0-3), and the remaining 4 bits comprise the second hex value (0-F). So if we take the first 6 bits of the 8-bit value for the letter D (0100|0100), which is 01|0001, we get 11 Hex, or 1 x 16 + 1, or 17. The value 17 corresponds to the ASCII letter R in the previous Base64 table.

Explaining all this can make it seem complicated, but to actually do it is amazingly easy. You may notice a couple of other things. First, we padded the 8-bit data out 2 extra bits, so that we could accommodate the 6-bits required for the last Base64 value. Second, you may notice that 3 8-bit values fit nicely into 4 6-bit values (24-bits total). Because of this, what Base64 conversion does is process the original 8-bit values in groups of 3 bytes at a time, which yields 4 Base64 values for each 3-byte group. However, it is clear that the original data cannot, and should not be always divisible by 3. So conversion routine uses padding to fill things out. It pads the data by adding 1 to 3 = symbols at the end of the encoding run (this is why you will frequently see them trailing Base64-encoded data). What these pad characters are used for is to pad out missing codes from the final 4-character Base64 grouping. They are treated as null values and tossed away, but the decoder, when grabbing values, will first check if it is the end of the data, and if not, it will simply slog in the next set of 4 characters from 24

the feeder stream. So throwing in some padding is like throwing some old clodhoppers into the bottom of the last bushel of pears it makes it look as full as the others (I actually did this on my grandparents farm, and the accentuated and prolonged stinging on my hindquarters informed me that this was an inappropriate course of action, warranting further personal review within the acetic seclusion of the TVless, toy-less, book-less confines of Grandmas sewing room, unless I wanted to flip on the radio and catch up on pork-belly futures but the good news was, I did figure out how to sew).
NOTE: Clodhoppers are big, heavy shoes farmers use to protect their feet and ankles while walking through tilled fields.

Suppose we took the Base64-encoded data that was in one of my earlier, and at the time presumed failed, AlternateView attempts for my Rich Text data:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 22 Feb 2011 17:19:08 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 This blank line after the Content-Transfer-Encoding line is required, and is not part of the message e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcZGVmZjBcZGVmbGFuZzEwMzN7XGZvbnR0Ymx7XGYw XGZuaWxcZmNoYXJzZXQwIFRpbWVzIE5ldyBSb21hbjt9e1xmMVxmbmlsXGZjaGFyc2V0MCBN aWNyb3NvZnQgU2FucyBTZXJpZjt9fQ0Ke1xjb2xvcnRibCA7XHJlZDBcZ3JlZW4wXGJsdWUw O30NClx2aWV3a2luZDRcdWMxXHBhcmRcY2YxXGYwXGZzMjQgVGhpcyBpcyBhIFxiIHRlc3Qg XGIwIG9mIFxpIFJpY2ggVGV4dCBkYXRhXGkwIC5ccGFyDQpccGFyZFxjZjBcZjFcZnMxN1xw YXINCn0NCg== ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321--

If this data (marked in blue in the above block) was stored in a string variable named b64Data, then we could stuff it into our RichTextBox1 control using the following single line of code:
Me.RichTextBox1.Rtf = Encoding.UTF8.GetChars(System.Convert.FromBase64String(b64Data.Replace(vbCrLf, vbNullString)))

And that is it! But I think you may want to also look at what I did a little less cryptically:
b64Data = b64Data.Replace(vbCrLf, vbNullString) Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) Me.RichTextBox1.Rtf = Encoding.UTF8.GetChars(Byt) 'remove artificial email line terminators 'convert from Base64 to a byte array of ASCII values 'convert 8-bit ASCII values in Byte array to a String Array

But what about translating binary data? Actually, the first two lines of code will properly decrypt binary code from Base64 format to a binary array of bytes, which means we are already half way there! If we want to save this array of bytes to a file, and it was a JPEG file, which the Content-Type file may have informed us of by stating Content-Type: image/jpeg; name=NewImage.JPG, we could do this:
b64Data = b64Data.Replace(vbCrLf, vbNullString) Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) Dim sw As New System.IO.FileStream("C:\MiscIO\NewImage.JPG", IO.FileMode.OpenOrCreate) sw.Write(Byt, 0, UBound(Byt) + 1) sw.Close() 'remove artificial email line terminators 'convert from Base64 to a binary byte array 'create destination 'write file from Byte array. Stream all bytes 'close file (automatically invoke Dispose)

But suppose we want to display it within a PictureBox control without first saving it to a file? What would we do then? The easy and fast way is to instead simply take advantage of a MemoryStream object, which is very much like a file stream, except that the stream remains entirely in memory:
b64Data = b64Data.Replace(vbCrLf, vbNullString) Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) Dim ImgStrm As New System.IO.MemoryStream(Byt) Me.PictureBox1.Image = Image.FromStream(ImgStrm) ImgStrm.Close() 'remove artificial email line terminators 'convert from Base64 to a byte array of ASCII values 'convert byte array to a memory stream 'import the memory stream to an image object 'close the memory stream (automatically invoke Dispose)

Or, how about this one-liner that will do exactly the same thing:
Me.PictureBox1.Image = Image.FromStream(New System.IO.MemoryStream(System.Convert.FromBase64String(b64Data.Replace(vbCrLf, vbNullString))))

NOTE: Do not worry about closing the memory stream, as we did in the previous example. It will automatically close when it falls out of scope, which happens after the picture box received the image. This is the real beauty with VB.NET being a fully-functional object oriented programming language, and finally having real, and thankfully strict scoping rules.

25

Consider the following two functions, which will decode raw Base64-encoded message string data:
'******************************************************************************* ' Function Name : DecodeBase64ToStr ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Function DecodeBase64ToStr(ByVal strData As String) As String Return Encoding.UTF8.GetChars(DecodeBase64ToBytes(strData)) End Function '******************************************************************************* ' Function Name : DecodeBase64ToBytes ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded binary Byte Array ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* 'this modification returns a Byte Array of the Base64 encoded source data Public Function DecodeBase64ToBytes(ByVal strData As String) As Byte() Return System.Convert.FromBase64String(strData.Replace(vbCrLf, vbNullString)) End Function

The first function, DecodeBase64ToStr() is meant to process text data that has been encoded to Base64, but is know to have a text result (based upon the result of the Content-Type field, for example). The second method, DecodeBase64ToBytes(), is meant to process a binary data result, returning a Byte array. You can afterward process the byte array as you see fit, such as saving it to a file, or rendering it if it is a renderable object, such as an image, using the Memory Stream technique shown, above. Notice, finally that the DecodeBase64ToStr() method takes full advantage of the DecodeBase64ToBytes() method, because the data must always first be rendered to a Byte array. Translating BinHex Data Back to its Original Format BinHex, first written by Tim Mann for the TRS-80. It is actually an inefficinet format, converting every byte into two, but it was essential back in the mid-1980s to tranfer binary data over the internet by such services as CompuServe, which for some time did not support 8-bit data transfer. BinHex was a fix that just stuck, and would have come along even if Tim had not written it. Even the MacIntosh adopted it.
NOTE: Anyone who knocks the TRS-80 computer, calling it by such derogatory terms as Trash-80, is speaking from a position of total ignorance. It was an essential technological platform that enabled you and me to be sitting here today with our PCs and our iMacs, our smart phones, tablet PCs, laptops, PDAs, and most other gadgets. Had the TRS-80 failed and had not become a leader in the PC market, you would be looking at a much different and more primitive world right now.

For those who discover that they have such data, they can decode it quite easily. Just supply the DecodeBinHex() function with a raw hex string, such as the data provided in an email, and it will return a Byte array with the converted data. Considering the following method:
'******************************************************************************* ' Function Name : DecodeBinHex ' Purpose : Decode a provided raw email message string that is encoded to BinHex. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Function DecodeBinHex(ByVal StrData As String) As Byte() Dim Src() As Byte = Encoding.UTF8.GetBytes(StrData.Replace(vbCrLf, vbNullString).ToUpper) Dim Result() As Byte 'init output buffer ReDim Result(UBound(Src) \ 2) 'set initial dimension to 1024 bytes (includes offset 0) Dim Index As Integer = 0 'init index for Result() array For Idx As Integer = 0 To UBound(Src) Step 2 'scan the string, 2 hex characters at a time Dim CL As Integer = Src(Idx) - 48 'Convert "0" - "F" to 0-F If CL > 10 Then CL -= 7 Dim CR As Integer = Src(Idx + 1) - 48 'do the same for the right hex digit If CR > 10 Then CR -= 7 If Index > UBound(Result) Then ReDim Preserve Result(Index + 255) 'bump by 256 (allow for Index offset) End If Result(Index) = CByte(CL * 16 + CR) 'stuff byte value Index += 1 'bump index Next ReDim Preserve Result(Index - 1) 'set array to final size Return Result 'return the final result End Function

26

Like with the DecodeBase64ToBytes() method, mentioned earilier, you can use the returned array to save to a file, or render it to an image, if that is its purpose. However, if you would like to convert to text, if that was its origin, then you can convert the returned Byte array like this:
Dim NewStr As String = Encoding.UTF8.GetChars(DecodeBinHex(strData))

PART THREE Receiving Email Under VB.NET using Native Methods This third part of this article is to read email into VB.NET. We are going to do this with ease, because none of the methods I present here are either long or complicated. Once you examine all the methods that we will use to define our email reading class, POP3, you may really start to wonder why Microsoft did not choose to make POP3 processing as easily accessible as SMTP processing. Perhaps they figured, and maybe rightly so, that if anyone can write an email processor that supports the RFC 2045 specification, then they likely had sufficient knowledge to easily handle TCP Clients without an easier front end. There are hundreds of thousands of programmer queries on the internet asking for how access POP3 from VB.NET (and those are just the ones who took time out of their day to say they needed POP3 front end code). Most solutions simply direct you to a few samples of comment-challenged C# or C++ code. Most VB.NET gurus will simply say that POP3 is impossible to do under VB.NET.
But because it was impossible, it took me all afternoon to come up with my own VB.NET solution, and it works better than any C# or C++ version I have yet seen. I must admit that after having fully developed this solution, I finally did find a solitary VB.NET article, last updated on 26 March, 2003, on MSDN by Duncan Mackenzie (see Checking My E-mail at http://msdn.microsoft.com/en-us/library/ms973213.aspx), who is the Microsoft Visual Basic .NET Content Strategist for MSDN, which implements a very simple sample solution. His example approach was very rudimentary (I almost said basic no pun intended), slow, it did not provide for SSL certificates, it featured little functionality, and it used a lot of code where I needed only a little just to accomplish the same thing. But I am not knocking him; he was writing this for VB2002, and he was just throwing out an example, not a full implementation. Plus, working with the .NET Framework is much easier now than it was back in the wild and wooly days of ot 3 or even ot 1.

My solution was inspired by C# code pieced together by Randy Charles Morin, author of the KBCafe.com blog site (see How to POP3 in C# www.kbcafe.com/articles/HowTo.POP3.CSharp.pdf, circa May 2002, but my current link is to a recently updated version of the article). Randy sifted various C# solutions and tacked together his own. Bill Dean also made a 5 August 2003 C# submission to The Code Project (http://www.codeproject.com/KB/IP/pop3client.aspx), which was inspired by Agus Kurniawans article on using C# to communicate with a POP3 server, submitted to The Code Project on 19 January, 2002 (http://www.codeproject.com/KB/IP/popapp.aspx). For all those listed above, their code interfaces are very simplistic, and even rougher around the edges than that of Mackenzies work. For example, not one of them could access Gmail, and interpreting email was not addressed (yep, you are going to find a utility here to easily break emails up into its parts). My initial goal was simply to see why those internet gurus, some of whom, based on their writing skills, I can easily imagine sitting there in 3-day-old boxers, scarfing down a bag of M&Ms and flushing the sugar away with Diet Pepsi, were saying that this kind of solution was not possible under VB.NET.
After building a VB.NET version familiarly modeled after Randys approach, I then took great pains to significantly optimize the code for both size and speed, and to address limitations he pointed out (which resulted in two complete rewrites of my code), and further enhancing it to accommodate many more functions to conform more fully to the POP3 specifications (see Post Office Protocol Version 3 at http://www.ietf.org/rfc/rfc1939.txt), to include optional TCP Ports and SSL support. I finally fully commented the code. By the way, what is it with most developers and their inability to comment their code? Do comments have cooties?

27

NOTE: Cooties was a term first used in a 1917 Service Manual, and is likely to have come from the Malayan word kutu, meaning lice. The trenches of World War I were a veritable breeding ground for every kind of parasitic pest imaginable.

We will create a single file, but we will include 3 classes. The file will be a class module. So go ahead and create a class file named POP3.vb. This shall result in the following shell code being generated:
Public Class POP3 End Class

We will leave this alone for now and create our other two simple classes below it. The second class, named POP3Message, shall define a message class, which will contain each email message. It will only hold fields, much like its abstract class cousin, a Structure, but we will define it as an object to avoid Boxing as we work with it (Boxing is the process of wrapping a structure within a class shell so that it can be operated on as a reference object, rather than as a value object. This process has a tiny price in time, but is more work than we require). It is declared below:
'------------------------------------------------------------------------------' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------Public Class POP3Message Public MailID As Integer = 0 'message number Public ByteCount As Integer = 0 'length of message in bytes Public Retrieved As Boolean = False 'flag indicating if the message has be retrieved Public Message As String = vbNullString 'the text of the message Public Overrides Function ToString() As String Return Message End Function End Class

NOTE: Do not be nervous about adding more than one class to a file. This is perfectly acceptable. In fact, if these other classes were used only within the POP3 class, we could have embedded them within the body of that class, at the same level as its methods. In fact, you might be able to see how .NET Namespaces are structured from this. You will see this concept much more clearly once we put these classes together to create a class library, which we will be able to import into our applications.

As you can see, the above class object will store the message number as an integer. This is the reference number that the email server assigns to that message, which is normally an incremental index, but we should not make any assumptions about it. It also has a field storing the messages size in bytes. A Boolean flag acts to indicate if we have actually retrieved the message text, or just its references, and then we store the message itself as a string. Because email is transmitted as text, even HTML and Rich Text format, and binary attachments are tacked on as encoded 7-bit ASCII strings, text is usually formatted to certain line widths, and each line is terminated by a Carriage Return and Linefeed (vbCrLf). The email program usually translates this encoding and displays any formatted representation within a text box, rich text box, or web control. Our third class, POP3Exception, is simply an error class, so that we can differentiate POP3 errors from coding errors:
'------------------------------------------------------------------------------' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with clsPOP3 '------------------------------------------------------------------------------Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

It may not look like much, but it holds just as much capability and functionality as any other Exception object. That is the real beauty of inheritance. Now we are ready to construct our main class, POP3. To do everything I want to do in this class, I already know that I will need to import 2 namespaces: 28

1.

I will first import System.Net, from which I can access the Sockets and Security namespaces. The Sockets namespace is important because through it I will be able to access a POP3 server though a TC/IP Client class. The Security namespace is important if we will require SSL authentication, such as Gmail requires. The second namespace to import is System.Text, because I will need to translate between Unicode strings and Byte arrays. Although it is possible to long-path these namespaces in the code without a compilation cost of even 1 byte, I like to list the namespaces used at the top for self-documentation purposes, to inform reviewers of my use of resources.

2.

I will also need to have local fields that will live as long as the class instance. Among those I will need a Boolean flag that will indicate if we will require SSL authentication or not. We will also need to store two stream objects; one for a standard non-SSL Network Stream, and a possible second SSL Stream. Both of these streams shall be used for POP3 mail server communication. Regardless, the Network Stream is essential for both types, because an SSL piggybacks itself onto a Network Stream. Finally, I want to store the last line read from the server in case we ever want to check it further. Therefore, the main body of my file, with all class bodies, becomes the following:
Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' POP3 - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'This VB.NET Code was inspired by C# code originally written Randy Charles Morin, 'author of KBCafe.com. ' ' I have optimized the heck out of the code to speed I/O and program execution, ' forcing a complete rewrite, I have added MANY language and POP3 enhancements, ' cleaned up a lot of clutter, and added Port and SSL support. ' Oh! And I include REAL comments. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, System.Text '------------------------------------------------------------------------------' Class Name : POP3 ' Purpose : POP3 Interface Class '------------------------------------------------------------------------------Public Class POP3 Inherits Sockets.TcpClient 'this class shall inherit all the functionality of a TC/IP Client Dim Stream As Sockets.NetworkStream Dim UsesSSL As Boolean = False Dim SslStream As Security.SslStream Dim SslStreamDisposed As Boolean = False Public LastLineRead As String = vbNullString 'non-SSL stream object 'True if SLL authentication required 'set to SSL stream supporting SSL authentication if UsesSSL is True 'true if we disposed of SSL Stream object 'copy of the last response line read from the TCP server

'>>>>>>>>>ANY MORE CODE WILL BE INSERTED HERE HERE<<<<<<<<< End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------Public Class POP3Message Public number As Integer = 0 'message number Public bytes As Integer = 0 'length of message in bytes Public retrieved As Boolean = False 'flag indicating if the message has be retrieved Public message As String = vbNullString 'the text of the message End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with clsPOP3 '------------------------------------------------------------------------------Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

The first and last thing we need to do after defining this is to connect to the POP3 server, and of course, to disconnect from it when we are finished. Plus, we may as well find out if we have any email.

29

Connecting to a POP3 Server Connecting to the server involves making a connection to the server, typically through Port 110, but we must keep it open for other ports in case they are defined. We then have to submit our email Username and Password to it. We will also want to save the server name off in case we will require later SSL authentication.
Connect()is

a method that is built into the socket library, so we will have to overload it to add our own customizations on top of it. This is not as scary as it sounds, because we can still access the original method declared for the TCPClient base class that our POP3 class inherited from, which is a good thing, because we will in fact need to invoke it. Connecting to the POP3 Host Server is always the first thing we do. Once we have done that, we will be able to issue all other POP3 commands, until we either remove our connection to the POP3 class, which breaks our connection to the server, or we can explicitly disconnect from the server. We will have this choice in terminating POP3 server communication, as you will learn, to provide us with the ability to remove or not removed downloaded emails from the Host Server. We will also require a method to submit queries to the server, which will also mean that we need a resource to check responses from the server (its replies to our queries). Communication with the server is just a long series of our requests and its responses. We will examine each of these parts in their turn. Following is the Connect() method:
'******************************************************************************* ' Sub Name : Connect (This is the first the we do with a POP3 object) ' Purpose : Connect to the server using the Server, User Name, Password, ' : and a flag indicating if SSL authentication is required ' : ' Returns : Nothing ' : ' Typical TelNet I/O: 'telnet mail.domain.net 110 (submit) '+OK POP3 mail.domain.net v2011.83 server ready 'USER myusername (submit) '+OK User name accepted, password please 'PASS mysecretpassword (submit) '+OK Mailbox open, 3 messages (the server locks and opens the appropriate maildrop) '******************************************************************************* Public Overloads Sub Connect(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) If Connected Then Disconnect() 'check underlying boolean flag to see if we are presently connected, and if so, disconnect that session UsesSSL = UseSSL 'set flag True or False for SSL authentication MyBase.Connect(Server, InPort) 'now connect to the server via our base class Stream = MyBase.GetStream 'before we can check for a response, we first have to set up a non-SSL stream If UsesSSL Then 'do we also need to use SSL authentication? SslStream = _ New Security.SslStream(Stream) 'yes, so build an SSL stream object on top of the Network Stream SslStream.AuthenticateAsClient(Server) 'add authentication as a client to the server End If If Not CheckResponse() Then Exit Sub 'exit if an error was encountered If CBool(Len(Username)) Then 'if the username is defined (some servers will reject submissions) Me.Submit("USER " & Username & vbCrLf) 'submit user name If Not CheckResponse() Then Exit Sub 'exit if an error was encountered End If If CBool(Len(Password)) Then 'if the password is defined (some servers will reject submissions) Me.Submit("PASS " & Password & vbCrLf) 'submit password If Not CheckResponse() Then Exit Sub 'exit if an error was encountered End If End Sub

As you can see, we begin out adventure by initiating communication with the POP3 server. We do this by providing the Connect() method the host server address, our email username (or our whole email address, if your server requires it), our email password, a port number if it differs from TCP Port 110, and True if our POP3 server will require a Secure Socket Layer. The first thing that Connect() does is check to see if we have a valid POP3 connection. After that, we stow away important references, such as our SSL choice. If we will use SSL, we will set our UsesSSL flag to True. 30

Next, we access the base classs Connect() method and submit the server name we wish to access, such as pop3.comcast.net or pop.gmail.com. By default, we submit Port 110 for communication. This is the port most often used by mail servers. However, some servers use different ports, but this is usually because they also use SSL Authentication. Gmail does this. For example, it uses a secure TCP Port 995 for POP3.
NOTE: Some servers let anyone and everyone on. In such cases, some few servers sometimes do not even accept usernames and/or passwords and will reject them (or accept any and all responses), or allow you to use a generic response, such as guest or user and password. In those cases, we have to allow for that.

Checking for a POP3 Server Response We next invoke CheckResponse() to see if a request was accepted. If so, a returned string begins with +OK. Because this test runs rampant throughout a TCP client session, I broke it out into a separate function that returns True if it was successful (the response, stored in our public string, LastLineRead, begins with +OK), and False if it was not (an error begins with -ERR). This test follows most other submissions. Here is the CheckResponse() method:
'******************************************************************************* ' Function Name : CheckResponse ' Purpose : Check the response to a POP3 command ' : ' Returns : Boolean flag. True = Success, False = Failure ' : ' NOTE : All status responses from the server begin with: ' : +OK (OK; Success, or request granted) ' or : -ERR (NAGATIVE; error) '******************************************************************************* Public Function CheckResponse() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode LastLineRead = Me.Response 'check response (and save response line) If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return False 'return failure flag End If Return True 'else return success flag End Function

This method in turn invokes the Response() method. But what is important here is that we get our first look at returned data from the POP3 server, and if we encounter an error, such as the first 3 letters of the response line not being +OK, then we throw our POP3Exception error, transmitting the returned text line to the message pump hidden with the exception object. You may even want to preview what the full responses are by examining the LastLineRead text; the rest of the +OK message can be informative (as are error messages, which all start with -ERR), such as +OK Gpop ready for requests from 12.34.567.89 fahrvergnugen8.0. Checking for Being Connected to a POP3 Server The IsConnected() method consolidates a lot of checks to the underlying base classs Boolean Connected property. It throws a POP3ExceptionError if the server is not connected, indicating that it is not in a Transaction state:
'******************************************************************************* ' Function Name : IsConnected ' Purpose : Return connected to Server state, throw error if not ' : ' Returns : Boolean Flag. True if connected to server ' : '******************************************************************************* Public Function IsConnected() As Boolean If Not Connected Then 'if not connected, throw an exception Throw New POP3Exception("Not Connected to an POP3 Server.") Return False 'return failure flag End If Return True 'Indicate that we are in the TRANSACTION state) End Function

31

Getting a Response from the POP3 Server The Response() method is where all the really interesting things happen. Here we will finally implement our communication streams to receive real information from the POP3 server.
'******************************************************************************* ' Function Name : Response ' Purpose : get response from server (read from the mail stream into a buffer) ' : ' Returns : string of data from the server ' : ' NOTE : If a dataSize value > 1 is supplied, then those number of bytes will be streamed in. ' : Otherwise, the data will be read in a line at a time, and end with the line end code (Linefeed (vbLf) 10 decimal) '******************************************************************************* Public Function Response(Optional ByVal dataSize As Integer = 1) As String Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim ServerBufr() As Byte 'establish buffer Dim Index As Integer = 0 'init server buffer index and character counter If dataSize > 1 Then 'did invoker specify a data length to read? '------------------------------------------------------ReDim ServerBufr(dataSize - 1) 'size to dataSize to read as a single stream block (allow for 0 index) Dim dtsz As Integer = dataSize Dim sz As Integer 'variable to store actual number of bytes read from the stream Do While Index < dataSize 'while we have not read the entire message... If UsesSSL Then 'process through SSL Stream if secure stream sz = SslStream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from SSLstream Else 'else process through general TCP Stream sz = Stream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from Network Stream End If If sz = 0 Then Return vbNullString 'we lost data, so we could not read the string Index += sz 'bump index for data count actually read dtsz -= sz 'drop amount left in buffer Loop Else '-----------------------------------------------------ReDim ServerBufr(255) 'initially dimension buffer to 256 bytes (including 0 offset) Do If UsesSSL Then 'process through SSL Stream if secure stream ServerBufr(Index) = CByte(SslStream.ReadByte) 'read a byte from SSLstream Else 'else process through general TCP Stream ServerBufr(Index) = CByte(Stream.ReadByte) 'read a byte from Network stream End If If ServerBufr(Index) = -1 Then Exit Do 'end of stream if -1 encountered Index += 1 'bump our offset index and counter If ServerBufr(Index - 1) = 10 Then Exit Do 'done with line if Newline code (10; Linefeed) read in If Index > UBound(ServerBufr) Then 'if the index points past end of buffer... ReDim Preserve ServerBufr(Index + 256) 'then bump buffer another 256 bytes, but keep existing data End If Loop 'loop until line read in End If Return enc.GetString(ServerBufr, 0, Index) 'decode from a byte array into a string and return the string End Function

Here we read from either the Network Stream (Stream) or the SSL Stream (SslStream), depending on whether we require SSL authentication to communicate with the TCP Server or not. If we do not specify a buffer size, we read the port one byte at a time until we encounter an end of line code (vbLf ; 10).
Every C#/C++ example I have ever seen first reads a stream byte into a single-element intermediate buffer array using the stream.Read() method, which requires a destination byte array. However, I have noticed that this can hang until an eventual timeout (usually 30-60 seconds) if the signal drops off, so we end up trying to read beyond the end of the provided stream, which is a more concerning issue when reading one byte at a time. I chose to use the ReadByte() method instead, which can read to a Byte variable (though I choose to read it directly into the ServerBufr Byte array instead), but more importantly, it will return a -1 code immediately if we moved beyond the end of the stream or if the connection drops off.

If the Stream.ReadByte() method or the SslStream.ReadByte() methods do not report back that they failed to read from their stream, then the index/counter is incremented. We loop until we get an end of a line code (a value of 10, representing a Linefeed character, typically following a Carriage Return), or if no data was received (it could mean that the signal dropped off). Once done reading bytes, the byte array is translated to a Unicode string (the default string format for VB.NET) and it is returned to the invoker.
If a buffer size was supplied, the ServerBufr is immediately dimensioned to that size (-1 is applied for the 0 offset). We then continuously try to fill the buffer full. However, the server actually returns data to us in blocks, not in one big blobby mess of data. For example, the header of an email actually consists of a number of headers, and the body of an email is considered another block, as are attachments and alternate

32

views. And even then, if the data block is longer than the servers stream buffer, it will break it up into a sequence of sub-blocks. Documentation says that the default maximum stream buffer size is 998 bytes, though it is not uncommon to find buffers that are larger, such as 1400 bytes. This size is implementationdefined, even though our Stream and SslStream objects have a SetLength property that is supposed to allow you to set the stream buffer size, this is not yet supported, as the documentation also states.

Submitting a Request to the POP3 Server The one thing left that we must look at, which is so far unresolved, is the Submit() method:
'******************************************************************************* ' Sub Name : Submit ' Purpose : Submit a request to the server ' : ' Returns : Nothing ' : ' NOTE : Command name must be in UPPERCASE, such as "PASS pw1Smorf". ' : "pass pw1Smorf" would not be acceptable, though some servers do allow for this. '******************************************************************************* Public Sub Submit(ByVal message As String) Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim WriteBuffer() As Byte = enc.GetBytes(message) 'converts the submitted string into to a sequence of bytes If UsesSSL Then 'using SSL authentication? SslStream.Write(WriteBuffer, 0, WriteBuffer.Length) 'yes, so write SSL buffer Else Stream.Write(WriteBuffer, 0, WriteBuffer.Length) 'else write to Network buffer End If End Sub

We convert our Unicode request to a Byte array, and then send it to the appropriate stream, depending on whether we use SSL authentication or not. One thing you may not realize is that we can directly access this method and submit a command directly. Just be sure that the command word is uppercase. Disconnecting from the POP3 Server A final primary thing that we may need to do, if we want the server to enter its usual UPDATE state, is to disconnect from the POP3 Server. That is accomplished through the Disconnect() method:
'******************************************************************************* ' Sub Name : Disconnect (This is the last the we do with a POP3 object) ' Purpose : Disconnect from the server and have it enter the UPDATE mode ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'QUIT (submit) '+OK Sayonara ' ' NOTE: When the client issues the QUIT command from the TRANSACTION state, ' the POP3 session enters the UPDATE state. (Note that if the client ' issues the QUIT command from the AUTHORIZATION state, the POP3 ' session terminates but does NOT enter the UPDATE state.) ' ' If a session terminates for some reason other than a client-issued ' QUIT command, the POP3 session does NOT enter the UPDATE state and ' MUST NOT remove any messages from the maildrop. ' ' The POP3 server removes all messages marked as deleted from the ' maildrop and replies as to the status of this operation. If there ' is an error, such as a resource shortage, encountered while removing ' messages, the maildrop may result in having some or none of the ' messages marked as deleted be removed. In no case may the server ' remove any messages not marked as deleted. ' ' Whether the removal was successful or not, the server the releases ' any exclusive-access lock on the maildrop and closes the TCP connection. '******************************************************************************* Public Sub Disconnect() Me.Submit("QUIT" & vbCrLf) 'submit quit request CheckResponse() 'check response If UsesSSL Then 'SSL authentication used? SslStream.Dispose() 'dispose of created SSL stream object if so SslStreamDisposed = True End If End Sub

We check for a response, but we throw it away, at least at this level, because we are leaving, regardless. Also, because we had to instantiate our SSL Stream (but only if we require SSL authentication), we dispose of this resource. We do not have to concern ourselves with the Network Stream object, because we only maintained a pointer to it. The actual Stream object is taken care of by the underlying TCPClient base class, which will dispose of that object for us (we did not instantiate the Network Stream object with New, because it was already instantiated by the underlying code of the base class). 33

Of course, now that we can connect, log on, and disconnect from the POP3 server, it would also be a really good idea to be able to read some data from it, such as an email maybe, before we disconnect. Getting Email Statistics from the POP3 Server The first thing we may want to do after connecting is to get some statistics, such as how many emails we have in our mail drop on the server, and how many bytes this data occupies on the server. These can be both served with our Statistics() method:
'******************************************************************************* ' Function Name : Statistics ' Purpose : Get the number of email messages and the total size as any integer array ' : ' Returns : 2-selement interger array. ' : Element(0) is the number of user email messages on the server ' : Element(1) is the total bytes of all messages taken up on the server ' : ' Typical telNet I/O: 'STAT (submit) '+OK 3 16487 (3 records (emails/messages) totaling 16487 bytes (octets)) '******************************************************************************* Public Function Statistics() As Integer() If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("STAT" & vbCrLf) 'submit Statistics request LastLineRead = Me.Response 'check response If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return Nothing 'return failure flag End If Dim msgInfo() As String = Split(LastLineRead, " "c) 'separate by spaces, which divide its fields Dim Result(1) As Integer Result(0) = Integer.Parse(msgInfo(1)) 'get the number of emails Result(1) = Integer.Parse(msgInfo(2)) 'get the size of the email messages Return Result End Function

This returns an integer array. Result(0) will contain the number of email messages residing in your mail drop, and Result(1) contains the total number of bytes the messages occupy on the server.
NOTE: Most online servers are Unix, and store line terminators as a vbLf, rather than vbCrLf, so you should expect a slight discrepancy when you load files, because your TCP connection will read and convert a lone vbLf to vbCrLf.

Getting an Email Reference List from the POP3 Server The next thing we may want to do after connecting is to get a list of emails that are available on the server. Our List() method takes care of this, constructing a ListArray of POP3Message objects from the server, and then returning them to the invoker:
'******************************************************************************* ' Function Name : List ' Purpose : Get the drop listing from the maildrop ' : ' Returns : Any Arraylist of POP3Message objects ' : ' Typical telNet I/O: 'LIST (submit) '+OK Mailbox scan listing follows '1 2532 (record index and size in bytes) '2 1610 '3 12345 '. (end of records terminator) '******************************************************************************* Public Function List() As ArrayList If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("LIST" & vbCrLf) 'submit List request If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing ' 'get a list of emails waiting on the server for the authenticated user ' Dim retval As New ArrayList 'set aside message list storage Do Dim response As String = Me.Response 'check response If (response = "." & vbCrLf) Then 'done with list? Exit Do 'yes End If Dim msg As New POP3Message 'establish a new message Dim msgInfo() As String = Split(response, " "c) 'separate by spaces, which divide its fields msg.number = Integer.Parse(msgInfo(0)) 'get the list item number msg.bytes = Integer.Parse(msgInfo(1)) 'get the size of the email message msg.retrieved = False 'indicate its message body is not yet retreived retval.Add(msg) 'add a new entry into the retrieval list Loop Return retval 'return the list End Function

34

The List() method constructs a list of POP3Message objects, but you may notice that we do not fill their Message field with anything, just their server reference number and the number of bytes that the message occupies. That is because when we submit a List request, the server returns a simple string for each email that it has pending for the Username. This simple string consists of two numeric text values, separated by a space, such as 1 2532. The first is the reference index for each item in its mail drop for the Username, and the other is the number of bytes the message occupies on the server. There are also a number of peripheral POP3 commands that we can exploit to round out our email retriever, such as retrieving the actual email message text (gee, what a unique idea), deleting an email from the server, resetting any deletions (great for OOPS! situations), and we can even grab just the header of the email, and optionally a specified number of additional lines from the body of the email. These peripheral methods will be listed one at a time, below. Get an Email Header from the POP3 Server
'******************************************************************************* ' Function Name : GetHeader ' Purpose : Grab the email header and optionally a number of lines from the body ' : ' Returns : Gets the Email header of the selected email. If an integer value is ' : provided, that number of body lines will be returned. The returned ' : object is the submitted POP3Message. ' : ' Typical telNet I/O: 'TOP 1 0 (submit request for record 1's message header only, 0=no lines of body) '+OK Top of message follows ' xxxxx (header for current record is transmitted) '. (end of record terminator) ' 'TOP 1 10 (submit request for record 1's message header plus 10 lines of body data) '+OK Top of message follows ' xxxxx (header for current record is transmitted) ' xxxxx (first 10 lines of body) '. (end of record terminator) '******************************************************************************* Public Function GetHeader(ByRef msg As POP3Message, Optional ByVal BodyLines As Integer = 0) As POP3Message If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("TOP " & msg.number.ToString & " " & BodyLines.ToString & vbCrLf) If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing msg.message = vbNullString 'erasde current contents of the message ' 'now process message data by binding the lines into a single string ' Do Dim response As String = Me.Response 'grab message line If response = "." & vbCrLf Then 'end of data? Exit Do 'yes, done with the loop if so End If msg.message &= response 'else build message by appending the new line Loop Return msg 'return new filled Message object End Function

The GetHeader()method allows you to grab only the header of the message, or the header plus a specified number of body text lines. By submitting a POP3Message to the GetHeader() function, this method will return a pointer to the POP3Message object that you had submitted, containing the header of the message. If it returns Nothing, then there was an error in trying to retrieve the header. The Header of email is interesting, except to mere mortals. They have no use for it, which they think contains nothing but routing data. However, though they may not realize it, the last few lines of the header actually do have some interest to them. Consider the following trailing end of an email header:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 25 Feb 2011 17:59:01 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_570b636e-3a77-40a7-bf02-5b178a2cff5b

The lines beginning with the fields From:, To:, and Subject: are easily extractable. The Date: field displays the Date and Time as local, with an offset value to GMT (Greenwich Mean Time; originally referring to mean solar time at the Royal Observatory, Greenwich in England. It is now often used to refer to Coordinated Universal Time (UTC)). But regardless, the above time means that now was 5 hours ago in Greenwich, England). 35

You can also grab the Content-Type and, if multipart/alternative, which it will always report if there are alternative views or attachments included, it will also include a boundary= parameter, which informs you of what the boundary between the various parts will consist of. If no alternative views or attachments, then the Content-Type will contain the type of data, such as text/plain, and maybe a parameter containing character set used for the message. Alternative views and attachments, after a boundary marker, also have a Content-Type field, which will informs you of the format the data is formatted as, after decoding, and if an attachment, a name parameter that will tell you the default filename (name) to store the data under, if the user wants to save the attachment to disc.
Retrieve an Email from the POP3 Server
'******************************************************************************* ' Function Name : Retrieve ' Purpose : Retrieve email from POP3 server for the provided POP3Message object ' : ' Returns : The submitted POP3 Message object with its Message property filled, ' : and its ByteCount property properly fitted to the message size. ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. So, if we ' : do not submit a POP3 QUIT (the Disconnect() method), but just close ' : out the POP3 object, the message(s) will not be deleted. ' : Even so, most Windows-based server-processors will add an additional ' : CR for each LF, but the reported email size does not account for them. ' : So we must retreive more data to account for this. ' ' Typical telNet I/O: 'RETR 1 (submit request to retrive record index 1 (cannot be an index marked for deletion)) '+OK 2532 octets (an octet is an fancy term for a 8-bit byte) ' xxxx (message header and message are retreived) '. (end of record terminator) '******************************************************************************* Public Function Retrieve(ByRef msg As POP3Message) As POP3Message If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("RETR " & msg.number.ToString & vbCrLf) 'issue request for indicated message number If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing msg.message = Me.Response(msg.bytes) 'grab message line 'the stream reader automatically convers the NewLine code, vbLf, to vbCrLf, so the files is not yet 'fully read. For example, a files that was 233 lines will therefore have 233 more characters not 'yet read from the files when it has reached its reported data size. So we will scan these in. 'But even if this was not the case, the trailing "." & vbCrLf is still pending. Do Dim S As String = Response() 'grab more data If S = "." & vbCrLf Then 'end of data? Exit Do 'If so, then exit app End If msg.message &= S 'else tack data to end of message Loop 'keep trying msg.bytes = Len(msg.message) 'ensure full size updated Return msg 'return new message object End Function

By submitting a POP3Message to the Retrieve() function, presumably its present message string being empty or ignorable, it will fill its Message property with the entire email, including both the full header and body of the message. It will also return a pointer to the message, except if there was a failure. Deleting an Email from the POP3 Server
'******************************************************************************* ' Sub Name : Delete ' Purpose : Delete an email ' : ' Returns : Nothing ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. ' ' Typical telNet I/O: 'DELE 1 (submit request to delete record index 1) '+OK Message deleted '******************************************************************************* Public Sub Delete(ByVal msgHdr As POP3Message) If Not IsConnected() Then Exit Sub 'exit if not in TRANSACTION mode Me.Submit("DELE " & msgHdr.number.ToString & vbCrLf) 'submit Delete request CheckResponse() 'check response End Sub

The Delete() method will delete a specified message. Just provide the selected POP3Message object.

36

Reset (Undo) All Deletes from the POP3 Server


'******************************************************************************* ' Sub Name : Reset ' Purpose : Reset any deletion (automatic or manual) of all email from ' : the current session. ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'RSET (submit) '+OK Reset state '******************************************************************************* Public Sub Reset() If Not IsConnected() Then Exit Sub 'exit if not in TRANSACTION mode Me.Submit("RSET" & vbCrLf) 'submit Reset request CheckResponse() 'check response End Sub

The Reset() method will undelete emails that you selected with Delete() during the current session. Send a Keep-Alive NOOP Command to the POP3 Server
'******************************************************************************* ' Function Name : NOOP (No Operation) ' Purpose : Does nothing. Juts gets a position response from the server ' : ' Returns : Boolean flag. False if disconnected, else True if connected. ' : ' NOTE : This NO OPERATION command is useful when you have a server that ' : automatically disconnects after a certain idle period of activity. ' : This command can be issued by a timer that also monitors users ' : inactivity, and issues a NOOP to reset the server timer. ' : ' Typical telNet I/O: 'NOOP (submit) '+OK '******************************************************************************* Public Function NOOP() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode Me.Submit("NOOP") Return CheckResponse() End Function

The NOOP() method will do nothing but obtain a response from the server. As noted, above, this method can be used just to tickle the server. Often servers will disconnect from a user after a certain period of time. But obtaining a response from the server, this timer is reset to the beginning of its counter, thus keeping the connection alive. If you have such a server, you could maintain your own timer that times out before the server time, and automatically submits a NOOP request to the server. Of course, you will want to reset your own timer (Me.myTimer.Enabled=False: Me.myTimer.Enabled=True) each time you issue a command to the server, to reset your own timer. Disposing of Resources
'******************************************************************************* ' Function Name : Finalize ' Purpose : remove SSL Stream object if not removed '******************************************************************************* Protected Overrides Sub Finalize() If Not SslStreamDisposed Then 'SSL Stream object Disposed? SslStream.Dispose() 'no, so do it End If MyBase.Finalize() 'then do normal finalization End Sub

Normally, when you issue a Disconnect() command, the resources are released. But you may not want to always do that. Many email providers will delete the message from the server once you have retrieved it, or they make it an option. However, if the server does not receive a QUIT command (issued by Disconnect()), then it will not enter into its UPDATE state, where servers often delete retrieved messages. If it does not enter the UPDATE state, then the emails will remain on the server. However, if you do not issue a Disconnect() command, the created resources will not be removed. This is not really a big issue, because the .NET Garbage Collector will detect that the resources are no longer referenced and remove them, but it is always a good idea to take care of that. Hence, we added a Finalize() method that will be issued by the Garbage Collector before destroying the POP3 object. 37

NOTE: You should never invoke the Finalize method yourself. Let the system automatically take care of that for you.

Using the Completed POP3 Class Now that we have defined all the parts of our POP3 class (the complete class listing is at the end of this article), we need to be able to use it. Even though the following is a short-cut, consider invoking the following sample intermediary method, during testing anyway, which will connect to the POP3 host server, grab all the emails, and disconnect. However, during testing, as you prod and modify it to implement your own test designs, it might be a good idea to leave your email on the server, to use for later testing. In that case, you should skip invoking the Disconnect() method. No worries; once our objects go out of scope, or are set to Nothing, the connection will be lost (if you are concerned about waiting for the Garbage Collector do not waste another bead of sweat. It runs as a separate thread in the background, and it runs much more often than most people would imagine. Were if up to me, I would have it running constantly in the background). The following sample method will return the grabbed emails to the invoker as an ArrayList filled with all your Email Messages as POP3Message objects:
'******************************************************************************* ' Sub Name : ReadPOP3 ' Purpose : Samples method to Read a POP3 account maildrop ' NOTE : This method should be modified to suit your application. For ' : example, the text may be HTML or Rich Text format. The raw text ' : should be plugged into the appropriate medium, such as ' : "Me.RichTextBox1.Rtf = msg2.Message" for proper viewing. '******************************************************************************* Public Function ReadPOP3(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) As ArrayList Try Dim InMail As New POP3 'create a new POP3 connection InMail.Connect(Server, Username, Password, InPort, UseSSL) 'Connect to user account '----------------------------------------------------------Dim Stats() As Integer = InMail.Statistics() 'get maildrop statistics (number of message, total byte size) If Stats(0) = 0 Then 'check number of messages for being 0 (none) Return Nothing 'no email found in the maildrop End If Dim localList As New ArrayList 'set up list of emails that will contain message text For Each msg As POP3Message In InMail.List 'parse each header object (contains only size and index) localList.Add(InMail.Retrieve(msg)) 'add a message object with message text Next 'process all messages '----------------------------------------------------------InMail.Disconnect() 'disconnect from server (SKIP THIS TO KEEP EMAILS ON SERVER) Return localList 'return list of filled POP3Messages to invoker Catch e As POP3Exception MsgBox(e.Message, _ MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, _ "Error Encountered") Catch e As Exception MsgBox(e.Message, _ MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, _ "Error Encountered") End Try Return Nothing End Function 'POP3-side error

'general programming error

The first three parameters are mandatory, though in the rare cases of an all-are-welcome server, you may need to provide a blank Username and/or Password. The Server is always mandatory, such as pop.gmail.com or authpop.juno.com. Also, though on most log-ins, the Username is the part of the users email address that comes before the @ (At) sign, such as Im1Idjut, some require the entire email address; most notably Gmail, which needs to have the entire email address, such as Im1Idjut@gmail.com.

The last two parameters are required only if your POP3 TCP Port is different or your server needs SSL authentication. For example, the parameter list for a Comcast account would be ("pop3.comcast.net", "Im1Idjut", "CantRecall1"), specifying Server, Username, and Password. But on Gmail, the parameter list would be ("pop.gmail.com", "Im1Idjut@gmail.com", "CantRecall1", 995, True).
The first thing we do is instantiate an instance of our POP3 class. We then connect to the server. Finally, we build an ArrayList named localList that will contain full email messages. Once we complete that, we disconnect from the server (but only do this when you are done with the connection and you want the server to enter the UPDATE mode), and then we return the list to the invoker, who can read them at their leisure.

38

Suppose we have a routine supporting a button, cmdReceive, that will invoke the ReadPOP3() method, and then loop through it, displaying each message in a message box (though rather than absolute parameters, I can only hope that your POP3 applications will process variables or text box data):
Private Sub cmdReceive_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReceive.Click Me.cmdReceive.Enabled = False 'disable Receive button Dim EmailBag As ArrayList = ReadPOP3("pop.gmail.com", "bob.the.builder@gmail.com", "YudLk2Knw", 995, True) If EmailBag IsNot Nothing AndAlso EmailBag.Count <> 0 Then 'if data was found For Each msg As VBNetEmail.POP3Message In EmailBag 'display each email Me.TextBox1.Text = msg.message 'stuff in the text box and display in a MsgBox MsgBox(msg.message, MsgBoxStyle.OkOnly, "Message # " & msg.number.ToString & " of " & EmailBag.Count.ToString) Next 'process all messages Else 'transfer here if one loves you MsgBox("No email found on server.", MsgBoxStyle.OkOnly Or MsgBoxStyle.Information, "No Email") End If Me.cmdReceive.Enabled = True 're-enable Receive button End Sub

This is all. You may want to design an interactive email program. In such cases you would want to keep a connection open, and allow the user to fiddle with their email to their hearts content, such as giving them time to read them, reply to them, forward the same jokes for the twenty-third time, though you would want to plagiarize the really good ones. Using the methods provided in the POP3 class, you can do quite a bit with it.

PART FOUR Email Data Blocks Made Easy There is a lot of information we can gather from just a little bit of data when it comes to an email. Lets first us look at a simple email and extract its important parts, and what we can do with them:
From: mercedes_silver@80micro.com To: david.ross.goben@gmail.com Date: 25 Feb 2011 19:07:02 -0500 Subject: Test Content-Type: text/html; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test=0D=0AThis is a second test=0D=0A FROM Field (FROM, TO, SUBJECT, and DATE Fields can be placed in any order within their 4-field zone). TO Field. DATE Field (always d mmm, yyyy HH:MM:SS zzz). SUBJECT Field. CONTENT-TYPE Field. The Message body data is Text and is formatted as HTML. Content-Transfer-Encoding Field. Quoted-Printable means data is Hex-Tag encoded. This line, following Content-Transfer-Encoding, is ALWAYS blank. It is NOT part of the message. Data is here, until the end of the data, or until a boundary marker is encountered.

The above is just a simple email; the type that my QuickiEmail() or BrainDeadSimpleEmailSend() would mail out. There is of course other routine data located above it. But you may be wondering how the data is formatted, or how to separate Alternate Views from Attachments, or how to decode them, or what did you do that the boss volunteered you to develop an email interface? Relax. It is all easy. To solve all these issues, we are soon going to add a single method to our library that will break all this information out for you. If you refer to RFC 2045 (www.ietf.org/rfc/rfc2045.txt), Multipurpose Internet Mail Extensions, (MIME) Part One: Format of Internet Message Bodies, you will find all answers. Or, you could let me read them, and then simply let me show you how to use them. From our simple example, above, we can clearly see 6 fields defined: From, To, Date, Subject, Content-Type and Content-Transfer-Encoding. Each is followed by a colon : to delimit the end of the field, separating it from its data. From, To, and Subject are easy enough. The Date field stores the date and time as d mmm, yyyy hh:mm:ss zzz. That is, day, month, year, then hours, minutes, seconds, followed by the Zulu-time offset (the number of hours against Greenwich, England time). The data for a field can consist of one or more parameters. If there is more than one, they are separated from each other by a semicolon ;. A parameter ending in vbCrLf marks the end of the parameter(s). Between parameters, intervening white space (non-displayable characters), such as spaces, tabs, Carriage Returns, Linefeeds, and such, are not counted as data. If the line ends in a ;, then the next line is considered a continuation of the previous line. For example, you may often see this:
Content-Type: text/plan; ";" indicates more parameters defined, so this is not the end of the field parameters Name=API32.TXT continuations of previous lines are led by white space (non-printable characters: spaces, tabs, etc.)

The above is considered one line of data. It is clear that the next line is a continuation, because the previous line ended in a semicolon, indicating another parameter is pending. 39

You may also notice in message bodies and the like, something like this (part of my signature block):
This is a test =20 =20 David Ross Goben \|/ ~ ~ (@ @)=20 --oOO-{_}-OOo------------------------------------------------------------= ------- --- Author of "A Gnostic Cycle: Exploring the Origin of Christianity" line line line line line line ended normally consisted only of vbCrLf ended with blank space, so encoded consisted only of vbCrLf ended with blank space, so encoded consisted only of vbCrLf

a space preceded end of line, so encoded non-breaking line continuation tag (=) continuation of previous line data simply chopped off due to my 80-char plain text limit

Notice the =20 Hex-Tags. These represent spaces prior to an end of line. This means that between the text and my signature are 4 lines, two of which have a space in them. Notice that there was a space down by @), as well. This can all be easily cleaned up by the DecodeQuotedPrintable() method. Notice on the long, dashed line that at 73 characters, there was an email-impose end of line, tagged by =, and followed by a vbCrLf. What this means is that when this message is processed, the line will actually continue on the next line, where we find 7 more dashes. This equals an 80-character line once we strip off the = and vbCrLf. The final 3 dashes are lost in the translation, being that I have set the line limit of plain text data set at 80 characters in Outlook, so we have to live with this overflow. Now, consider the following abbreviated email (it was actually quite long, even with a small image (302x244), but it contains some interesting differences from previous raw emails we have examined):
From: "Mercedes Silver" Mercedes_silver@80micro.com To: <david.ross.goben@gmail.com> Subject: Check out this scary thing! Date: Sun, 27 Feb 2011 12:31:07 -0500 Message-ID: <8883802E7DC3402AA08372C008416D95@David> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_0000_01CBD67A.365E6060" X-Priority: 3 (Normal) X-MSMail-Priority: Normal X-Mailer: Microsoft Outlook, Build 1234.5.6789 Importance: Normal From Field. To Field. Subject Field. Date Field (d mm, yyyy HH:MM:SS zzz). (these (these (these (these 4 4 4 4 fields fields fields fields can can can can be be be be in in in in any any any any order) order) order) order)

If a semicolon ends a line, the next line will be a continuation. Notice we have a boundary declaration set within quotation marks. Obvious email extension as allowed by RFC 2045. Can be ignored.

------=_NextPart_000_0000_01CBD67A.365E6060 Begin next block (note the 2 extra dashes at the beginning). Content-Type: multipart/alternative; boundary="----=_NextPart_001_0001_01CBD67A.365E8770" Notice we have ANOTHER boundary declaration, which is used here to wrap message types, both the main (plain), and alternate (HTML). ------=_NextPart_001_0001_01CBD67A.365E8770 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Picture image is attached. Beginning of main body message area. Main body is plain text Encoding, or rather, the lack thereof in the case of 7bit. Notice that a Content-Transfer-Encoding line is followed by a REQUIRED blank line. Beginning of plain text message.

Mercedes ------=_NextPart_001_0001_01CBD67A.365E8770 End of plain text message, start of HTML alternate view. Content-Type: text/html; data is HTML. charset="us-ascii" Content-Transfer-Encoding: quoted-printable indicates text could have control conversions (though HTML usually covers most). The email is so short, there will not be any actual control code translation. <html> <body> Picture image is attached.</p> <p> <p> &nbsp; Mercedes<p> HTML forcing a leading space by inserting a non-breaking space. </body> </html> ------=_NextPart_001_0001_01CBD67A.365E8770-- End of HTML alternate view. ------=_NextPart_000_0000_01CBD67A.365E6060 Beginning of second boundary data. Start of binary image. Content-Type: image/jpeg; name="David Goben.JPG" filename for data. Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="David Goben.JPG" optional disposition field, which specifically indicates an attachment, plus a specific filename (some emails do not have a disposition, so depend on name, above) /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy Binary image data. MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAD0AS4DASIA This blank area is ACTUALLY filed with about a page and a half of encoding... +4v9XWWZt7COWIKD7Dy6p/8ADPPhL/oI63/3/i/+N0UV3x2RxS3D/hnnwkP+Yjrf/f6L/wCN0f8A DPPhL/oI63/3/i/+N0UVRI5P2e/CKNk32ssPQzx4P5R11s/w+0mbw8dESe8htTH5W6JkD4xjqVI/ Siis6m6Kich/wzz4S/6COt/9/ov/AI3R/wAM8+Ev+gjrf/f+L/43RRWgjbtPhDoFnpI01LzU2gD7 8tLHuz/3xVm/+F2iaikazXWoAR9Nkif1SiivMlGPtL26np0JyUVZn//Z ------=_NextPart_000_0000_01CBD67A.365E6060-- End of second boundary data (note the 2 extra dashes at the beginning and end).

40

Notice that an email can have multiple boundaries defined. I do not understand why, because one would have served this email, but for some reason Microsoft Outlook chose to separate the main body and the alternate view from the attachment(s), which are grouped by the outer blocking. This is also why some simple email readers have trouble reading Outlook email files (and the fact that they pack them full of self-defined extensions that are not specified by the RFC 2045 document, though they may actually be useful and help define a future specification, which were last updated in 1996). Further, notice that boundary definitions can be optionally enclosed within quotation marks, and that a boundary will be unique to each email, being unlike any other line in its text. Another thing to notice is the Content-Disposition line. This simply identifies the data block as an attachment. But we already know this from the previous Content-Type field. The only thing that RFC 2045 has to say about this field is that it should be ignored. So we will ignore it. Issues that you may encounter regarding the Content-Type field will be in interpreting its fields. The first part is simple enough, if you refer back to our tables, but notice that the first one is different if we have attachments (attachments and/or alternate views). When the email has attachments of any sort, the first parameter for Content-Type is supposed to be multipart/alternative, which indicates that we have attachments, or we have alternate views. However, some email processors, such as Outlook, also can specify multipart/mixed. This is an allowed user-defined extension. But we can still use it. We just need to check its left side for multipart. If multipart leads, then we can be sure that the second parameter will specify a boundary definition string (which Microsoft Outlook, but few others, embraces within quotation marks). Because some email applications, such as Outlook, use multiple boundaries, we simply need to be sure to absorb each one, and to test for each one, but we can treat any as though they were a single boundary, simplifying our processing. That is, when any of the boundaries are encountered, then simply assume we have hit a boundary and think nothing of it beyond that. However, as we check for boundaries, we simply need to check each line for containing the boundary using an Instr() function, because we really do not need to concern ourselves with the extra -- leaders or trailers that we will also find decorating a boundary within the body of the email. If the first part of the Content-Type specifies a data type, such as application/rtf or text/plain or image/jpeg, the second parameter will either be name=, or it will be a display format, such as charset=us-ascii, or something similar. We will only concern ourselves when this second parameter begins with name=, which will only happen when we are in an attachment, and the data can be saved to a file, using the specified filename as a default, but only if the user chooses to save it. Otherwise, the data is either the main message, or it is an alternate view. Issues that you may encounter with regard to reading the Content-Transfer-Encoding field will be how their parameter tags are actually embedded in the emails, as opposed to how you specified them. This is because they cannot be declared in an enumeration the same way the RFC 2045 document dictates that they must be provided. Consider the following table:
How We Declare the CTE Value QuotedPrintable SevenBit Base64 How They are Shipped/Received with Email quoted-printable 7bit base64

When you read email, look to the data in the right column for what you will find. If it is not of these values, we are instructed to assume the encoding is base64, regardless of what the Content-TransferEncoding field specifies. 41

Easily Extracting the Component Parts from an Email File Of course, a header contains many more fields than those shown previously, but the important headers fields, to us at least, are the TO, FROM, DATE, and SUBJECT fields. To grab this important information, the main message body, plus all attachments and alternate text, simply provide the Message parameter of a POP3Message object to the GetEmailInfo() method (defined below). The Retreive() method of POP3 takes a POP3Message object that has its MailID number set to the ID of the mail that you want retrieved, which has previously been initiated by the POP3 class List() method, and the ByteCount parameter is set to the length of the email, and its returns the POP3Message object that has its Message parameter filled with an entire email; header, attachments, and all. It is the complete message as it is stored on the server (though, as previously noted, any lone vbLf codes will be replaced by vbCrLf). Provided the entire message data as a string, the GetEmailInfo() method will break that message data up and distribute it primarily to within one, or several EmailItem class objects within an outer EmailInfo class object, depending on the number of Alternate Views and Attachments included in the message. Both the Alternate Views and the Attachments are maintained within Array Lists that allow it to store multiples of each. You can check these ArrayList objects, named AlternateViews and Attachments within the EmailInfo object, and if their Count parameter is non-zero, you can extract each EmailItem object from their Items collection, and inspect them as you see fit. There is one EmailItem object, named MessageBody, which is reserved for the main body of the email.
The Remainder of the EmailInfo object stores the e-mails FROM, TO, DATE, and SUBJECT properties.
'******************************************************************************* ' Function Name : GetEmailInfo ' Purpose : Break email down into its component parts. ' : ' Returns : EmailInfo object with component parts of email broken down. ' : ' NOTES : This method uses classes EmailItems and EmailInfo. ' : The Message Body, and each AlternnateView or Attachment are ' : contained within EmailItem objects within the EmailIngo object. ' : ' : An EmailItem contains fields for FROM, TO, SUBJECT, Content-Type, ' : a flag indicating if the ContentTypeData is a filename or if it is ' : text formatting, content-transfer-encoding data, and the raw encoded, ' : data, whether it is a message or binary information. If the content' : transfer encoding is set to "base64", the data should be decoded ' : using the DecodeBase64() method. If it is "quoted-printable", the ' : data should be decoded using DecodeQuotedPrintable(). If it is ' : "7bit", it is 7-bit data and does not need to be decoded. '******************************************************************************* Public Function GetEmailInfo(ByVal MailMessage As String) As EmailInfo Dim Info As New EmailInfo 'structure to hold breakdown of email Dim Ary() As String = Split(MailMessage, vbCrLf) 'break full email into lines Dim Idx As Integer = 0 'index into Ary() Dim MX As Integer = UBound(Ary) + 1 'find end if list+1 Dim Boundaries As New Collections.Generic.List(Of String) 'boundary definitions Dim Dim Dim Dim IsMultiPart As Boolean = False SeekingEncoding As Boolean = False BuildingDataBlock As Boolean = False HaveMessageBody As Boolean = False 'true 'true 'true 'true if if if if we we we we have multiple parts are looking for encoding are building a data block have the message body defined

Dim ContentType As String = vbNullString Dim ContentTypeIsName As Boolean = False Dim ContentTypeData As String = vbNullString Dim ContentEncoding As String = vbNullString Dim ContentBody As String = vbNullString '----------------------------------------------------------Dim Inheader As Integer = 4 Do Dim S As String = Ary(Idx) ' ' check for important header items ' If CBool(Len(S)) AndAlso CBool(Inheader) Then Dim I As Integer = InStr(S, ":") If CBool(I) Then If VB.Right(S, 1) = ";" Then Idx += 1 S &= Ary(Idx).Trim(Chr(9), " "c) End If Select Case LCase(VB.Left(S, I)) Case "from:" Info.FromData = Trim(Mid(S, I + 1)) Inheader -= 1

'hold last-defined Content Type 'true of Content Type specified a file 'if block isan attachment 'hold last-defined Content Transfer Encoding 'block data accumulator 'flag for gathering To, From, Date, Subject 'grab a line of data from the email

'if we are currently in the header... 'find field delimiter 'found one? 'line continues? 'yes, so bump index 'append next line next line 'yes, check for one of 4 fields 'Found FROM field 'stuff to structure 'drop 1 from flag

42

Case "to:" 'Found TO field Info.ToData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "date:" 'Found DATE field Info.DateData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "subject:" 'Found SUBJECT field Info.SubjectData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag End Select End If If Not CBool(Inheader) Then 'if InHeader flag is zero SeekingEncoding = True 'start looking for a Content-Transfer-Encoding field S = vbNullString 'purge current data End If End If '------------------------------------------------------' check for boundaries '------------------------------------------------------If CBool(Len(S)) AndAlso CBool(Boundaries.Count) Then 'check any defined boundaries For Idy As Integer = 0 To Boundaries.Count - 1 If CBool(InStr(S, Boundaries.Item(Idy), CompareMethod.Text)) Then If BuildingDataBlock Then Dim Itm As New EmailItem 'create a new item Itm.ContentType = ContentType 'store content type Itm.ContentTypeData = ContentTypeData 'save filename or character set Itm.ContentTypeDataIsFilename = ContentTypeIsName 'save flag indicating if Attachment Itm.ContentEncoding = ContentEncoding 'store encoding Itm.ContentBody = ContentBody 'store data ContentBody = vbNullString 'reset accumulator If HaveMessageBody Then 'already have a message body? If ContentTypeIsName Then 'if an attachment Info.Attachments.Add(Itm) 'add an attachment Else 'otherwise an alternate view Info.AlternateViews.Add(Itm) End If Else Info.MessageBody = Itm 'else stuff new item to message body HaveMessageBody = True 'indicate we now have a message body End If ContentTypeData = vbNullString 'reset filename/charset BuildingDataBlock = False 'turn off building flag End If SeekingEncoding = True 'turn block seeing on again S = vbNullString 'purge current data Exit For End If Next End If '------------------------------------------------------' build data block '------------------------------------------------------If BuildingDataBlock Then ContentBody &= S & vbCrLf 'add a line to content data End If '------------------------------------------------------' if seeking encoding '------------------------------------------------------If CBool(Len(S)) AndAlso SeekingEncoding Then 'are we seeking TCE? Dim I As Integer = InStr(S, ":") 'yes, check for field delimiter If CBool(I) Then 'did we find one? Select Case LCase(VB.Left(S, I)) 'yes, check for types '======================================================= Case "content-type:" 'Content type? ContentType = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data If VB.Right(S, 1) = ";" Then 'more to add? Idx += 1 'yes, so bump index ContentType &= Ary(Idx).Trim(Chr(9), " "c) 'grab next line End If ContentTypeIsName = False 'init flag specifying a file as false Dim sbAry() As String = Split(ContentType, ";") 'now check the content type data ContentType = sbAry(0) 'keep first part for ContentType If StrComp(VB.Left(sbAry(0), 10), "multipart/", CompareMethod.Text) = 0 Then 'multipart, so grab second parameter (boundary definition), and strip any quotes Dim Bnd As String = Trim(Mid(sbAry(1), InStr(sbAry(1), "=") + 1)).Replace("""", vbNullString) Boundaries.Add(Bnd) 'and add a boundary ElseIf StrComp(VB.Left(sbAry(1), 5), "name=", CompareMethod.Text) = 0 Then ContentTypeIsName = True 'attachment if a filename specified (otherwise a view) sbAry = Split(sbAry(1), "=") 'multipart, so grab second parameter 'get second part of second parameter (filename definition) ContentTypeData = sbAry(1).Trim().Replace("""", vbNullString) 'strip any quotes Else ContentTypeData = sbAry(1) 'AlternateView, so stuff display character set End If '=================================================== Case "content-transfer-encoding:" ContentEncoding = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip required following blank line End Select End If End If Idx += 1 'bump array index Loop While Idx < MX '----------------------------------------------------------Return Info 'return with filled data block End Function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

43

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailItem (used by EmailInfo class) ' Purpose : Stores structure of an email block '******************************************************************************* Public Class EmailItem Public ContentType As String = vbNullString 'CONTENT-TYPE data Public ContentTypeData As String = vbNullString 'filename or text encoding Public ContentTypeDataIsFilename As Boolean = False 'True if ContentTypeData specifies a filename Public ContentEncoding As String = vbNullString 'CONTENT-TRANSFER-ENCODING data Public ContentBody As String = vbNullString 'raw data of block End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailInfo (used by GetEmailInfo method) ' Purpose : Store component parts of an Email '******************************************************************************* Public Class EmailInfo Public FromData As String = vbNullString 'FROM: Public ToData As String = vbNullString 'TO: Public DateData As String = vbNullString 'DATE: Public SubjectData As String = vbNullString 'SUBJECT: Public MessageBody As EmailItem 'contents of message body Public AlternateViews As New Collections.Generic.List(Of EmailItem) 'list of alternate views Public Attachments As New Collections.Generic.List(Of EmailItem) 'list of attachments End Class

Compiling Everything into a Class Library My email sending methods, BrainDeadSimpleEmailSend(), QuickiEMail(), and SendEMail(), I embed in a class wrapper name SMTP. To do this is really easy (see the full listing later, if you want to just copy it). I just select the menu options Project / Add Class, and type SMTP into the name field. Do not worry about it maintaining the default .vb file extension. It will be re-added if you do not type it in. This creates the class shell:
Public Class SMTP End Class

I then decorate its heading with essential references and identification, being sure to include the imports I had needed for the three methods, like so:
Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' SMTP - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, VB = Microsoft.VisualBasic '------------------------------------------------------------------------------' Class Name : SMTP ' Purpose : SMTP Interface Class '------------------------------------------------------------------------------Public Class SMTP Class Body is here End Class

I finally copy my three methods, BrainDeadSimpleEmailSend(), QuickiEMail(), and SendEMail(), within the SMTP class body. And that is all there is to creating the class. Easy, breezy, nice n easy! But what this means, if we leave it at that, as an included class file, is that to invoke any of its methods from this class, we must first instantiate an instance of this local SMTP class. For the SMTP class, I would name an instance of it OutMail, and for the POP3 class, I would name an instance of it InMail. For example, to send an email, I must now use code similar to the following:
'create a new instance of the SMTP class Dim OutMail As New SMTP 'build a message and send an email Dim S As String = "This is a test" & vbCrLf & "This is a second test" & vbCrLf OutMail.SendEMail("mercedes_silver@80micro.com", "david.ross.goben@gmail.com", "Test", S, False, "smtp.80micro.com") 'disengage the OutMail instance when we are done with it (at least put it within your Form_Closing() Event) OutMail = Nothing

44

I will also take all my utilities and add them to a new class file named Utilities.vb. And like with the SMTP class utilities, you will have to instantiate a Utilities class (this class will be covered, shortly). UNLESS, we build a class library, reference the library from our application, and then import the class library into our project file. This way, to access a method within the SMTP class, we can simply specify SMP.methodname, or Utilities.methodname without having to specify either class. Of course, you will also have to change all the Public methods defined within the SMP and Utilities classes to Public Shared methods. However, if you do wish to still instantiate instances of SMP and Utilities classes to local objects, simply do not declare them as Shared, much as they presently are. This is a personal choice for you to make. Either way, it is not quite as easy for our POP3 class. This is because the POP3 class inherits from System.Net.Sockets.TcpClient, so we would still have to instantiate an instance of this class. But that is no big deal, because so far, that is exactly what we have been doing. The only difference is, with the class library we are about to build, that we would no longer be referencing a locally compiled class file. But how would we reference this class library? No problem. It is as easy as stepping on an upturned rake and rearranging your face with its handle. But before we get to those simple steps, how about we actually build this class library, so we can quickly prove that these steps are as simple as I claim them to be?
We have already defined our SMPT and POP3 class files (their complete listings are at the end of this article). But we also want to create a Utilities class (after all, we have already been talking about the poor thing as though it already exists). We would create the Utilities class exactly the same way as we did the SMTP class. We would add a new class and name it Utilities. We would then initially tattoo its new class shell like this:
Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Utilities - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Text, VB = Microsoft.VisualBasic Public Class Utilities End Class

Next, we would add the following items to this Class file, all of which we have explored earlier, during the course of this article/novella (it is also fully listed at the end of this article):
Method/Enum/Class Item DecodeBase64ToStr() DecodeBase64ToBytes() DecodeQuotedPrintable() DecodeHexDec() TextNeedsEncoding() Force7BitHtml() ForceQuotedPrintable() QConvertHTML2Text() ConvertHTML2Text() Enum MediaTypes GetMediaType() Enum TransferEncodings GetTransferEncoding() GetEmailInfo() Class EmailItem Class EmailInfo Description Decode Base64-encoded files to their original format and return a string. Decode Base64-encoded files to their original format and return a Byte Array. Method to clean typical control translations, or all of them. This should be invoked for all data coded Quoted-Printable. Decode Base16-encoded files to their original format, where each two characters prepresents a binary Byte. Determine if HTML text, Rich Text, or Plain Text requires 8-bit code translation to 7-bit Quoted-Printable tags. Method to convert 8-bit code in an HTML message to 7-bit. Method to force 8-bit code in a text message to 7-bit, without losing any data. Short-Form Convert HTML formatted text to plain text. Convert HTML formatted text to plain text. Enumeration used by GetMediaType. Provide easy access to System.Net.Mime.MediaTypes text. Structure used by GetTransferEncoding Provide easy access to System.Net.Mime.TransferEncoding data. Break email down into its component parts. (used by GetEmailInfo) (used by GetEmailInfo)

Once we have all three classed fully defined (and there are no errors laughing at us), we should further wrap these three classes within a Class Library project named VBNetEmail, and build them for easy access by our application. This is incredibly easy to do.

45

Building the VBNetMail Class Library To incorporate our three classes into a Class Library project named VBNetEmail is really easy. The first thing is to save off and close your project that contains the classes SMTP, POP3, and Utilities, and remember where you saved them! Copy them from the listings at the end of this article, if you need to.
Now, create a new Class Library project named VBNetEmail. Once created, in Project Explorer, you will see a Class File, Class1.vb. Simply right-click it and choose Delete. We do not need it; we already have the class files we require: SMTP.vb, POP3.vb, and Utilities.vb. Next, select the menu options Project / Add Existing Item Browse to the location where you saved your POP3, SMTP, and Utilities class files, select them, then click the Add button. The three class files should then appear in your Project Explorer. (We now have all we need to compile our class library.) Select the menu options Build / Build VBNetEmail. We will end up with a DLL file named VBNetEmail.dll (look within your Projects folder, Projects\VBNetEmail\VBNetEmail\obj\Release, for it). I tend to test them in place, though later, once proven, I copy them to a class library. I even do this for my related utilities, adding them to a Utilities class, such as those used within the article.

Accessing your New VBNetEmail Class Library DLL from another Project Once you have built the VBNetEmail.dll file, you can add it to other projects in just a few easy steps.
In the Project Properties for your new application, make sure the References tab is selected, and choose the ADD button. Select the Browse Tab, and then browse to your VBNetEmail Class Library project (you should find yourself initially within your current project). Choose the Up One Level button to back out of any current project; it is the folder icon with a curved green up arrow: Drill down into the VBNetEmail project to find the Obj folder, and open it. Within the Obj folder, select and open the Release folder. Within the Release folder, click the VBNetEmail.dll file and then select the OK button (if you have one-click enabled, OK may be auto-selected). You will now find a reference to VBNetEmail listed in your References list. For the next project, you can simply choose the Recent Tab and very quickly select the VBNetEmail.dll file. Finally, in the project file that you will need to access the classes in, place the line Imports VBNetEmail above the first class declaration at the top of the file.

You can now access your classes as though they were included in your project (well, in a way, they are). Remember, the methods for the SMTP and Utilities classes can be accessed by simply specifying SMTP or Utilities, hitting the dot, and selecting the method you want to use. To use the Inbound POP3 email class, you must instantiate an instance of the class, and then access its methods through that instance. You are now ready for some serious email processing.

46

The Complete SMTP.VB File


Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' SMTP - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, VB = Microsoft.VisualBasic '------------------------------------------------------------------------------' Class Name : SMTP ' Purpose : SMTP Interface Class '------------------------------------------------------------------------------Public Class SMTP '******************************************************************************* ' Function Name : BrainDeadSimpleEmailSend ' Purpose : Send super simple email message (works with most SMTP servers) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <daviddingus@att.net> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <bob.dingus@cox.com> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.comcast.net", "authsmtp.juno.com", etc. '******************************************************************************* Public Shared Sub BrainDeadSimpleEmailSend(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String) Dim smtpEmail As New Mail.SmtpClient(smtpHost) 'create new SMTP client using TCP Port 25 smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email End Sub '******************************************************************************* ' Function Name : QuickiEMail ' Purpose : Send a simple email message (but packed with a lot of muscle) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <daviddingus@att.net> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <bob.dingus@cox.com> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Shared Function QuickiEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Try Dim smtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client smtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... smtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom smtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else smtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email using text/plain content type and QuotedPrintable encoding Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Send Error") Return False 'return a failure flag End Try Return True 'if no error, then return a success flag End Function '******************************************************************************* ' Function Name : SendEMail ' Purpose : Send a more complex email message '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <daviddingus@att.net> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <bob.dingus@cox.com> ' : If multiple recipients, separate each full email address using a semicolon (;) ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. May be raw text or HTML code. ' IsHTML : True if the strBody data is HTML, or the type of data that would be contained within an HTML Body block. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' AltView : A System.Net.Mail.AlternateView object, such as Rich Text or HTML. ' : If need be, set AltView.ContentType.MediaType and AltView.TransferEncoding to properly format the AlternateView. ' : For example: AltView.ContentType.MediaType = Mime.MediaTypeNames.Text.Rtf ' : AltView.TransferEncoding = Mime.TransferEncoding.SevenBit ' StrCC : Send "carbon copies" of email to this or these recipients. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strBcc : Blind Carbon Copy. Hide this or these recipients from view by others. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strAttachments: A single filepath, or a list of filepaths to send to the recipient. ' : If multiple attachments, separate each filepath using a semicolon (;) (C:\my data\win32.txt; c:\jokes.rtf) ' : The contents of the attachments will be encoded and sent. ' : If you wish to send the attachment by specifying content type (MediaType) and content transfer encoding ' : (Encoding), then follow the attachment name with the MediaType and optional encoding (default is ' : application/octet-stream,Base64) by placing them within parentheses, and separated by a comma. For example: ' : C:\My Files\API32.txt (text/plain, SevenBit); C:\telnet.exe (application/octet-stream, Base64) ' : Where: The MediaType is determined from the System.Net.Mime.MediaTypeNames class, which ' : can specify Application, Image, or Text lists. For example, the above content type, ' : "text\plain", was defined by acquiring System.Net.Mime.MediaTypeNames.Text.Plain. ' : The second parameter, Encoding, is determined by the following the values specified by the ' : System.Net.Mime.TrasperEncoding enumeration: ' : QuotedPrintable (acquired by System.Net.Mime.TransferEncoding.QuotedPrintable.ToString) ' : Base64 (acquired by System.Net.Mime.TransferEncoding.Base64.ToString) ' : SevenBit (acquired by System.Net.Mime.TransferEncoding.SevenBit.ToString) ' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Shared Function SendEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal IsHTML As Boolean, _ ByVal smtpHost As String, _ Optional ByVal AltView As Mail.AlternateView = Nothing, _ Optional ByVal strCC As String = vbNullString, _ Optional ByVal strBcc As String = vbNullString, _ Optional ByVal strAttachments As String = vbNullString, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Dim Email As New Mail.MailMessage 'create a new mail message With Email .From = New Mail.MailAddress(strFrom) 'add FROM to mail message (must be a Mail Address object) '------------------------------------------Dim Ary() As String = Split(strTo, ";") 'add TO to mail message (possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .To.Add(Trim(Ary(Idx))) 'add each TO recipent (primary recipients)

47

Next '------------------------------------------.Subject = strSubject 'add SUBJECT text line to mail message '------------------------------------------.Body = strBody 'add BODY text of email to mail message. .IsBodyHtml = IsHTML 'indicate if the message body is actually HTML text. .IsBodyHtml = True '------------------------------------------If AltView IsNot Nothing Then 'if an alternate view of plaint text message is defined... .AlternateViews.Add(AltView) 'add the alternate view End If '------------------------------------------If CBool(Len(strCC)) Then 'add CC (Carbon Copy) email addresses to mail message Ary = Split(strCC, ";") '(possible list of email addresses, separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .CC.Add(Trim(Ary(Idx))) 'add each recipent Next End If '------------------------------------------If CBool(Len(strBcc)) Then 'add Bcc (Blind Carbon Copy) email addresses to mail message Ary = Split(strBcc, ";") '(possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .Bcc.Add(Trim(Ary(Idx))) 'add each recipent (hidden recipents) Next End If '------------------------------------------If CBool(Len(strAttachments)) Then 'add any attachments to mail message Ary = Split(strAttachments, ";") '(possible list of file paths, separated each with ";") For Idx As Integer = 0 To UBound(Ary) 'process each attachment Dim attach As String = Trim(Ary(Idx)) 'get attachment data If Len(attach) <> 0 Then 'if an attachment present... Dim I As Integer = InStr(attach, "(") 'check for formatting instructions If CBool(I) Then 'formatting present? Dim Fmt As String 'yes, so set up format cache Fmt = Mid(attach, I + 1, Len(attach) - I - 1) 'get format data attach = Trim(VB.Left(attach, I - 1)) 'strip format data from the attachment path Dim Atch As New Mail.Attachment(attach) 'create a new attachment Dim fmts() As String = Split(Fmt, ",") 'break formatting up For I = 0 To UBound(fmts) 'process each format specification Fmt = Trim(fmts(I)) 'grab a format instruction If CBool(Len(Fmt)) Then 'data defined? Select Case I 'yes, so determine which type of instruction to process Case 0 'index 0 specified MediaType Atch.ContentType.MediaType = Fmt 'set media type to attachment Case 1 'index 1 specifes Encoding Select Case LCase(Fmt) 'check the encoding types and process accordingly Case "quotedprintable", "quoted-printable" Atch.TransferEncoding = Mime.TransferEncoding.QuotedPrintable Case "sevenbit", "7bit" Atch.TransferEncoding = Mime.TransferEncoding.SevenBit Case Else Atch.TransferEncoding = Mime.TransferEncoding.Base64 End Select End Select End If Next .Attachments.Add(Atch) 'add attachment to email Else .Attachments.Add(New Mail.Attachment(attach)) 'add filepath (if no format specified, encoded in effiecient Base64) End If End If Next End If End With '----------------------------------------------------------------------'now open the email server... Try Dim SmtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client on the SMTP server SmtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... SmtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom SmtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else SmtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If SmtpEmail.Send(Email) 'finally, send the email... Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Error") Return False 'return failure flag End Try Return True 'return success flag End Function End Class

The Complete POP3.VB File


Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' POP3 - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'This VB.NET Code was inspired by C# code originally written Randy Charles Morin, 'author of KBCafe.com. ' ' I have optimized the heck out of the code to speed I/O and program execution, ' forcing a complete rewrite, I have added MANY language and POP3 enhancements, ' cleaned up a lot of clutter, and added Port and SSL support. ' Oh! And I include REAL comments. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, System.Text '------------------------------------------------------------------------------' Class Name : POP3 ' Purpose : POP3 Interface Class '------------------------------------------------------------------------------Public Class POP3 Inherits Sockets.TcpClient 'this class shall inherit all the functionality of a TC/IP Client Dim Stream As Sockets.NetworkStream Dim UsesSSL As Boolean = False Dim SslStream As Security.SslStream Dim SslStreamDisposed As Boolean = False Public LastLineRead As String = vbNullString 'non-SSL stream object 'True if SLL authentication required 'set to SSL stream supporting SSL authentication if UsesSSL is True 'true if we disposed of SSL Stream object 'copy of the last response line read from the TCP server

'******************************************************************************* ' Sub Name : Connect (This is the first the we do with a POP3 object) ' Purpose : Connect to the server using the Server, User Name, Password, ' : and a flag indicating if SSL authentication is required ' : ' Returns : Nothing ' : ' Typical TelNet I/O: 'telnet mail.domain.net 110 (submit) '+OK POP3 mail.domain.net v2011.83 server ready 'USER myusername (submit) '+OK User name accepted, password please 'PASS mysecretpassword (submit) '+OK Mailbox open, 3 messages (the server locks and opens the appropriate maildrop) '******************************************************************************* Public Overloads Sub Connect(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) If Connected Then Disconnect() 'check underlying boolean flag to see if we are presently connected, and if so, disconnect that session UsesSSL = UseSSL 'set flag True or False for SSL authentication MyBase.Connect(Server, InPort) 'now connect to the server via our base class Stream = MyBase.GetStream 'before we can check for a response, we first have to set up a non-SSL stream If UsesSSL Then 'do we also need to use SSL authentication? SslStream = _ New Security.SslStream(Stream) 'yes, so build an SSL stream object on top of the Network Stream

48

SslStream.AuthenticateAsClient(Server) End If

'add authentication as a client to the server

If Not CheckResponse() Then Exit Sub 'exit if an error was encountered If CBool(Len(Username)) Then 'if the username is defined (some servers will reject submissions) Me.Submit("USER " & Username & vbCrLf) 'submit user name If Not CheckResponse() Then Exit Sub 'exit if an error was encountered End If If CBool(Len(Password)) Then 'if the password is defined (some servers will reject submissions) Me.Submit("PASS " & Password & vbCrLf) 'submit password If Not CheckResponse() Then Exit Sub 'exit if an error was encountered End If End Sub '******************************************************************************* ' Function Name : CheckResponse ' Purpose : Check the response to a POP3 command ' : ' Returns : Boolean flag. True = Success, False = Failure ' : ' NOTE : All status responses from the server begin with: ' : +OK (OK; Success, or request granted) ' or : -ERR (NAGATIVE; error) '******************************************************************************* Public Function CheckResponse() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode LastLineRead = Me.Response 'check response (and save response line) If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return False 'return failure flag End If Return True 'else return success flag End Function '******************************************************************************* ' Function Name : IsConnected ' Purpose : Return connected to Server state, throw error if not ' : ' Returns : Boolean Flag. True if connected to server ' : '******************************************************************************* Public Function IsConnected() As Boolean If Not Connected Then 'if not connected, throw an exception Throw New POP3Exception("Not Connected to an POP3 Server.") Return False 'return failure flag End If Return True 'Indicate that we are in the TRANSACTION state) End Function '******************************************************************************* ' Function Name : Response ' Purpose : get response from server (read from the mail stream into a buffer) ' : ' Returns : string of data from the server ' : ' NOTE : If a dataSize value > 1 is supplied, then those number of bytes will be streamed in. ' : Otherwise, the data will be read in a line at a time, and end with the line end code (Linefeed (vbLf) 10 decimal) '******************************************************************************* Public Function Response(Optional ByVal dataSize As Integer = 1) As String Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim ServerBufr() As Byte 'establish buffer Dim Index As Integer = 0 'init server buffer index and character counter If dataSize > 1 Then 'did invoker specify a data length to read? '------------------------------------------------------ReDim ServerBufr(dataSize - 1) 'size to dataSize to read as a single stream block (allow for 0 index) Dim dtsz As Integer = dataSize Dim sz As Integer 'variable to store actual number of bytes read from the stream Do While Index < dataSize 'while we have not read the entire message... If UsesSSL Then 'process through SSL Stream if secure stream sz = SslStream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from SSLstream Else 'else process through general TCP Stream sz = Stream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from Network Stream End If If sz = 0 Then Return vbNullString 'we lost data, so we could not read the string Index += sz 'bump index for data count actually read dtsz -= sz 'drop amount left in buffer Loop Else '-----------------------------------------------------ReDim ServerBufr(255) 'initially dimension buffer to 256 bytes (including 0 offset) Do If UsesSSL Then 'process through SSL Stream if secure stream ServerBufr(Index) = CByte(SslStream.ReadByte) 'read a byte from SSLstream Else 'else process through general TCP Stream ServerBufr(Index) = CByte(Stream.ReadByte) 'read a byte from Network stream End If If ServerBufr(Index) = -1 Then Exit Do 'end of stream if -1 encountered Index += 1 'bump our offset index and counter If ServerBufr(Index - 1) = 10 Then Exit Do 'done with line if Newline code (10; Linefeed) read in If Index > UBound(ServerBufr) Then 'if the index points past end of buffer... ReDim Preserve ServerBufr(Index + 255) 'then bump buffer another 256 bytes (Inc Index), but keep existing data End If Loop 'loop until line read in End If Return enc.GetString(ServerBufr, 0, Index) 'decode from a byte array into a string and return the string End Function '******************************************************************************* ' Sub Name : Submit ' Purpose : Submit a request to the server ' : ' Returns : Nothing ' : ' NOTE : Command name must be in UPPERCASE, such as "PASS pw1Smorf". ' : "pass pw1Smorf" would not be acceptable, though some servers do allow for this. '******************************************************************************* Public Sub Submit(ByVal message As String) Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim WriteBuffer() As Byte = enc.GetBytes(message) 'converts the submitted string into to a sequence of bytes If UsesSSL Then 'using SSL authentication? SslStream.Write(WriteBuffer, 0, WriteBuffer.Length) 'yes, so write SSL buffer Else Stream.Write(WriteBuffer, 0, WriteBuffer.Length) 'else write to Network buffer End If End Sub '******************************************************************************* ' Sub Name : Disconnect (This is the last the we do with a POP3 object) ' Purpose : Disconnect from the server and have it enter the UPDATE mode ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'QUIT (submit) '+OK Sayonara ' ' NOTE: When the client issues the QUIT command from the TRANSACTION state, ' the POP3 session enters the UPDATE state. (Note that if the client ' issues the QUIT command from the AUTHORIZATION state, the POP3 ' session terminates but does NOT enter the UPDATE state.) ' ' If a session terminates for some reason other than a client-issued ' QUIT command, the POP3 session does NOT enter the UPDATE state and ' MUST NOT remove any messages from the maildrop. ' ' The POP3 server removes all messages marked as deleted from the ' maildrop and replies as to the status of this operation. If there ' is an error, such as a resource shortage, encountered while removing ' messages, the maildrop may result in having some or none of the ' messages marked as deleted be removed. In no case may the server ' remove any messages not marked as deleted. ' ' Whether the removal was successful or not, the server the releases ' any exclusive-access lock on the maildrop and closes the TCP connection. '******************************************************************************* Public Sub Disconnect() Me.Submit("QUIT" & vbCrLf) 'submit quit request CheckResponse() 'check response If UsesSSL Then 'SSL authentication used?

49

SslStream.Dispose() 'dispose of created SSL stream object if so SslStreamDisposed = True End If End Sub '******************************************************************************* ' Function Name : Statistics ' Purpose : Get the number of email messages and the total size as any integer array ' : ' Returns : 2-selement interger array. ' : Element(0) is the number of user email messages on the server ' : Element(1) is the total bytes of all messages taken up on the server ' : ' Typical telNet I/O: 'STAT (submit) '+OK 3 16487 (3 records (emails/messages) totaling 16487 bytes (octets)) '******************************************************************************* Public Function Statistics() As Integer() If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("STAT" & vbCrLf) 'submit Statistics request LastLineRead = Me.Response 'check response If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return Nothing 'return failure flag End If Dim msgInfo() As String = Split(LastLineRead, " "c) 'separate by spaces, which divide its fields Dim Result(1) As Integer Result(0) = Integer.Parse(msgInfo(1)) 'get the number of emails Result(1) = Integer.Parse(msgInfo(2)) 'get the size of the email messages Return Result End Function '******************************************************************************* ' Function Name : List ' Purpose : Get the drop listing from the maildrop ' : ' Returns : Any Arraylist of POP3Message objects ' : ' Typical telNet I/O: 'LIST (submit) '+OK Mailbox scan listing follows '1 2532 (record index and size in bytes) '2 1610 '3 12345 '. (end of records terminator) '******************************************************************************* Public Function List() As ArrayList If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("LIST" & vbCrLf) 'submit List request If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing ' 'get a list of emails waiting on the server for the authenticated user ' Dim retval As New ArrayList 'set aside message list storage Do Dim response As String = Me.Response 'check response If (response = "." & vbCrLf) Then 'done with list? Exit Do 'yes End If Dim msg As New POP3Message 'establish a new message Dim msgInfo() As String = Split(response, " "c) 'separate by spaces, which divide its fields msg.MailID = Integer.Parse(msgInfo(0)) 'get the list item number msg.ByteCount = Integer.Parse(msgInfo(1)) 'get the size of the email message msg.Retrieved = False 'indicate its message body is not yet retreived retval.Add(msg) 'add a new entry into the retrieval list Loop Return retval 'return the list End Function '******************************************************************************* ' Function Name : GetHeader ' Purpose : Grab the email header and optionally a number of lines from the body ' : ' Returns : Gets the Email header of the selected email. If an integer value is ' : provided, that number of body lines will be returned. The returned ' : object is the submitted POP3Message. ' : ' Typical telNet I/O: 'TOP 1 0 (submit request for record 1's message header only, 0=no lines of body) '+OK Top of message follows ' xxxxx (header for current record is transmitted) '. (end of record terminator) ' 'TOP 1 10 (submit request for record 1's message header plus 10 lines of body data) '+OK Top of message follows ' xxxxx (header for current record is transmitted) ' xxxxx (first 10 lines of body) '. (end of record terminator) '******************************************************************************* Public Function GetHeader(ByRef msg As POP3Message, Optional ByVal BodyLines As Integer = 0) As POP3Message If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("TOP " & msg.MailID.ToString & " " & BodyLines.ToString & vbCrLf) If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing msg.Message = vbNullString 'erasde current contents of the message ' 'now process message data by binding the lines into a single string ' Do Dim response As String = Me.Response 'grab message line If response = "." & vbCrLf Then 'end of data? Exit Do 'yes, done with the loop if so End If msg.Message &= response 'else build message by appending the new line Loop Return msg 'return new filled Message object End Function '******************************************************************************* ' Function Name : Retrieve ' Purpose : Retrieve email from POP3 server for the provided POP3Message object ' : ' Returns : The submitted POP3 Message object with its Message property filled, ' : and its ByteCount property properly fitted to the message size. ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. So, if we ' : do not submit a POP3 QUIT (the Disconnect() method), but just close ' : out the POP3 object, the message(s) will not be deleted. ' : Even so, most Windows-based server-processors will add an additional ' : CR for each LF, but the reported email size does not account for them. ' : So we must retreive more data to account for this. ' ' Typical telNet I/O: 'RETR 1 (submit request to retrive record index 1 (cannot be an index marked for deletion)) '+OK 2532 octets (an octet is an fancy term for a 8-bit byte) ' xxxx (message header and message are retreived) '. (end of record terminator) '******************************************************************************* Public Function Retrieve(ByRef msg As POP3Message) As POP3Message If Not IsConnected() Then Return Nothing 'exit if not in TRANSACTION mode Me.Submit("RETR " & msg.MailID.ToString & vbCrLf) 'issue request for indicated message number If Not CheckResponse() Then Return Nothing 'check for a response, but if an error, return nothing msg.Message = Me.Response(msg.ByteCount) 'grab message line 'the stream reader automatically convers the NewLine code, vbLf, to vbCrLf, so the files is not yet 'fully read. For example, a files that was 233 lines will therefore have 233 more characters not 'yet read from the files when it has reached its reported data size. So we will scan these in. 'But even if this was not the case, the trailing "." & vbCrLf is still pending. Do Dim S As String = Response() 'grab more data If S = "." & vbCrLf Then 'end of data? Exit Do 'If so, then exit app End If msg.Message &= S 'else tack data to end of message Loop 'keep trying msg.ByteCount = Len(msg.Message) 'ensure full size updated Return msg 'return new message object End Function

50

'******************************************************************************* ' Sub Name : Delete ' Purpose : Delete an email ' : ' Returns : Nothing ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. ' ' Typical telNet I/O: 'DELE 1 (submit request to delete record index 1) '+OK Message deleted '******************************************************************************* Public Sub Delete(ByVal msgHdr As POP3Message) If Not IsConnected() Then Exit Sub 'exit if not in TRANSACTION mode Me.Submit("DELE " & msgHdr.MailID.ToString & vbCrLf) 'submit Delete request CheckResponse() 'check response End Sub '******************************************************************************* ' Sub Name : Reset ' Purpose : Reset any deletion (automatic or manual) of all email from ' : the current session. ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'RSET (submit) '+OK Reset state '******************************************************************************* Public Sub Reset() If Not IsConnected() Then Exit Sub 'exit if not in TRANSACTION mode Me.Submit("RSET" & vbCrLf) 'submit Reset request CheckResponse() 'check response End Sub '******************************************************************************* ' Function Name : NOOP (No Operation) ' Purpose : Does nothing. Juts gets a position response from the server ' : ' Returns : Boolean flag. False if disconnected, else True if connected. ' : ' NOTE : This NO OPERATION command is useful when you have a server that ' : automatically disconnects after a certain idle period of activity. ' : This command can be issued by a timer that also monitors users ' : inactivity, and issues a NOOP to reset the server timer. ' : ' Typical telNet I/O: 'NOOP (submit) '+OK '******************************************************************************* Public Function NOOP() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode Me.Submit("NOOP") Return CheckResponse() End Function '******************************************************************************* ' Function Name : Finalize ' Purpose : remove SSL Stream object if not removed '******************************************************************************* Protected Overrides Sub Finalize() If Not SslStreamDisposed Then 'SSL Stream object Disposed? SslStream.Dispose() 'no, so do it End If MyBase.Finalize() 'then do normal finalization End Sub End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------Public Class POP3Message Public MailID As Integer = 0 'message number Public ByteCount As Integer = 0 'length of message in bytes Public Retrieved As Boolean = False 'flag indicating if the message has be retrieved Public Message As String = vbNullString 'the text of the message Public Overrides Function ToString() As String Return Message End Function End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with clsPOP3 '------------------------------------------------------------------------------Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

51

The Complete Utilities.VB File


Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Utilities - Copyright 2011 by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Text, VB = Microsoft.VisualBasic Public Class Utilities '******************************************************************************* ' Function Name : DecodeBase64ToStr ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Shared Function DecodeBase64ToStr(ByVal strData As String) As String Return Encoding.UTF8.GetChars(DecodeBase64ToBytes(strData)) End Function '******************************************************************************* ' Function Name : DecodeBase64ToBytes ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded binary Byte Array ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* 'this modification returns a Byte Array of the Base64 encoded source data Public Shared Function DecodeBase64ToBytes(ByVal strData As String) As Byte() Return System.Convert.FromBase64String(strData.Replace(vbCrLf, vbNullString)) End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : DecodeQuotedPrintable ' Purpose : Method to clean typical control translations, or all of them. ' : This should be invoked for all data coded Quoted-Printable. ' : ' Returns : Provided a raw message string block, it returns a decoded string. ' : ' NOTES : Typical cleaning involves changing "=0D" to vbCr, "=0A" to vbLf, ' : "=20" to a space, and "=3D" to "=", plus any line wrap ' : terminators at the end of lines to vbNullstring. ' : ' : A StringBuilder object will be used, which will very quickly ' : do a replacement of all control code translations using fewer ' : resources, and what resources that are used will be instantly ' : flushed when the method exits. '******************************************************************************* Public Shared Function DecodeQuotedPrintable(ByVal Message As String, Optional ByVal QuickClean As Boolean = False) As String 'set up StringBuilder object with data stripped of any line continuation tags Dim Msg As New StringBuilder(Message.Replace("=" & vbCrLf, vbNullString)) If QuickClean Then 'perform a quick clean (clean up common basics) Return Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " ").Replace("=3D", "=").ToString Else 'perform total cleaning 'store 2-character hex values that require a leading "0" Dim HxData As String = "X0102030405060708090A0B0C0D0E0F" For Idx As Integer = 1 To &HF 'initially process codes 1-15, which require a leading zero Msg.Replace("=" & Mid(HxData, Idx << 1, 2), Chr(Idx)) 'replace hex data with single character code (SHIFT is faster) Next For idx As Integer = &H10 To &HFF 'process the whole 8-bit extended ASCII gambit Msg.Replace("=" & Hex(idx), Chr(idx)) 'replace hex data with single character code Next Return Msg.ToString 'return result string End If End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : DecodeBinHex ' Purpose : Decode a provided raw email message string that is encoded to BinHex. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Shared Function DecodeBinHex(ByVal StrData As String) As Byte() Dim Src() As Byte = Encoding.UTF8.GetBytes(StrData.Replace(vbCrLf, vbNullString).ToUpper) Dim Result() As Byte 'init output buffer ReDim Result(UBound(Src) \ 2) 'set initial dimension to 1024 bytes (includes offset 0) Dim Index As Integer = 0 'init index for Result() array For Idx As Integer = 0 To UBound(Src) Step 2 Dim CL As Integer = Src(Idx) - 48 If CL > 10 Then CL -= 7 Dim CR As Integer = Src(Idx + 1) - 48 If CR > 10 Then CR -= 7 If Index > UBound(Result) Then ReDim Preserve Result(Index + 255) End If Result(Index) = CByte(CL * 16 + CR) Index += 1 Next ReDim Preserve Result(Index - 1) Return Result End Function 'scan the string, 2 hex characters at a time 'Convert "0" - "F" to 0-F 'do the same for the right hex digit 'bump by 256 (allow for Index offset) 'stuff byte value 'bump index 'set array to final size 'return the final result

'========================================================================== '========================================================================== '******************************************************************************* ' Function Name : TextNeedsEncoding ' Purpose : Determine if HTML text, Rich Text, or Plain Text requires ' : 8-bit code translation to 7-bit Quoted-Printable tags. ' : ' Returns : Provided a source string, it returns a boolena flag. ' : If the returned value is true, the source contains 8-bit data ' : and will be encoded by server. ' : ' NOTES : If text data contains 8-bit values, the default .NET ' : SMTP processor will force this code to be encoded to Base64, ' : even if only a single byte is 8-bit. ' : ' : To avoid this, the Force7BitHtml() method can be invoked on ' : HTML text to ensure that it is 7-bit encoded so that it can ' : be processed as Quoted-Printable or as 7Bit. The ForceQuotedPrintable() ' : method performs essential conversions for non-HTML text, but this ' : would be best served in Attachments and Alternate Views. '******************************************************************************* Public Shared Function TextNeedsEncoding(ByVal Message As String) As Boolean Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array For Each B As Byte In Byt If CBool(B And &H80) Then Return True Next Return False End Function '========================================================================== '==========================================================================

52

'******************************************************************************* ' Function Name : Force7BitHtml ' Purpose : Method to convert 8-bit code in an HTML message to 7-bit. ' : ' Returns : Provided a string containing HTML code, it will return a string ' : containing HTML code that does not have any 8-bit data embedded. ' : ' NOTES : If any characters in an HTML text string are 8-bit (values ' : greater than 127), then they are converted into a special ' : 7-bit HTML Entity Number, For Example, code 149 () is an 8-bit ' : value that can be changed to HTML "&#149;", which will ensure ' : that it will still be displayed on the HTML page, but the HTML ' : souce code will no longer carry an actual 8-bit value. If such ' : code had not been corrected, the encoding of the data would be ' : forced to change from quoted-printable to Base64, because that ' : would be the only way the email processor could guarantee that ' : the email text was fully intact. '******************************************************************************* Public Shared Function Force7BitHtml(ByVal HtmlSource As String) As String Dim Sb As New StringBuilder 'set up string builder for appending data For Idx As Integer = 1 To Len(HtmlSource) Dim C As Integer = AscW(Mid(HtmlSource, Idx, 1)) 'get a single character from the source Select Case C 'check each character Case Is > &H7F, Is < 0 'if 8-bit or unicode code Sb.Append("&#" & C.ToString & ";") 'convert to 7-bit HTML ecoder Case Else Sb.Append(ChrW(C)) 'else save text regardless End Select Next Return Sb.ToString End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : ForceQuotedPrintable ' Purpose : Force 8-bit code in a text message to 7-bit, without data loss. ' : ' Returns : Provided a source string that contains 8-bit data, the 8-bit ' : data is converted to Hex-Tags, and the returned string is 7-bit. ' : ' NOTES : if any characters in a text string are 8-bit (values greater ' : than 127), then they are converted into special 7-bit tags. ' : For Example, code 149 () is an 8-bit value that can be changed ' : to hex "=95", which will ensure that it will still be displayed ' : in the text, but the text data will no longer carry an actual ' : of the data would be forced to change from quoted-printable or 7bit ' : to Base64, because that would be the only way the email processor ' : Base64, because that would be the only way the email processor ' : could guarantee that the email text was fully intact. However, ' : you will have to use the DecodeQuotedPrintable() method to convert ' : it back to its original text form. ' : ' : The Encoded text will begin with "=00". Because unencoded null codes ' : are not permitted in email data, you can use this to instantly ' : determine on the receiving end that this code will need to be ' : processed by DecodeQuotedPrintable() a second time (if initially ' : encoded as Quoted-Printable). A second pass would be required, ' : because if this translated code was afterward encoded as Quoted' : Printable, and all the "=xx" byte-translations, would be ' : reinterpreted as "=3Dxx", which DecodeQuotedPrintable() would ' : convert back to "=xx", so passing through a second time would ' : properly convert the additional encoding. Further, by checking the ' : text startiing with "=00", you would know that you would need to ' : double-decode the text. Also, you would want to initially skip this ' : initial tag when passing it the second time to DecodeQuotedPrintable(): ' : ' : Dim Result As String = DecodeQuotedPrintable(Message) 'initially decode Quoted-Printable text ' : If VB.Left(Result, 3) = "=00" Then 'tagged as pre-encoded? ' : Return DecodeQuotedPrintable(Mid(Result, 4)) 'yes, so decode again and return, less initial null byte ' : Else ' : Return Result 'otherwise, return result of decoding ' : End If '******************************************************************************* Public Shared Function ForceQuotedPrintable(ByVal Message As String) As String Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array Dim Sb As New StringBuilder("=00") 'set up string builder for appending data For Each B As Byte In Byt Select Case B 'check each byte Case Is > &H7F 'if 8-bit code Sb.Append("=" & Hex(B)) 'convert to 7-bit tag Case Else Sb.Append(Chr(B)) 'else save text regardless End Select Next Return Sb.ToString End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : QConvertHTML2Text ' Purpose : Short-Form Convert HTML formatted text to plain text ' : ' Returns : Provided a simple HTML source string, it will return a Plain Text ' : string with HTML code removed. '******************************************************************************* Public Shared Function QConvertHTML2Text(ByVal HTMLText As String) As String Return RegularExpressions.Regex.Replace(HTMLText.Replace("&nbsp;", " ").Replace("&quot;", """").Replace("&apos;", _ "'"), "<[^>]*>", "").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : ConvertHTML2Text ' Purpose : Convert HTML formatted text to plain text ' : ' Returns : Provided a complex HTML string, it will return a Plain Text string ' : with all HTML codes and formatting removed from it. ' : ' NOTE : Numerous of these conversions will convert the text to 8-bit, ' : though most of these sysmbols will not be encountered in most ' : HTML documents we produce. But regardless of that, if you wish ' : to make this conversion the main body message of an email, you ' : may have to further convert this using ForceQuotedPrintable() ' : to maintain Quoted-Printable encoding and avoid Base64, even ' : though this is typically not an issue. However, some few really ' : primitive email readers, typically those that simply allow you ' : to preview email messages, without fully loading them, will not ' ' know how to support Base64, or will not bother with it, but simply ' : display the raw data. RFC 2045 requires email handlers to support it. '******************************************************************************* Public Shared Function ConvertHTML2Text(ByVal HTMLText As String) As String 'instantiate an initially blank StringBuilder object Dim Sb As New StringBuilder() 'first remove leading whitespace of each line and append the result to the StringBuilder Dim ary() As String = Split(HTMLText, vbCrLf) For Each S As String In ary Sb.Append(S.TrimStart(Chr(9), " "c)) Next 'replace reserved entities (except <, >, and &) Sb.Replace("&quot;", """").Replace("&apos;", "'").Replace("&nbsp;", " ") 'replace HTML paragraph, line breaks, and table entry terminators with vbCrLf Sb.Replace("<p>", vbCrLf).Replace("<P>", vbCrLf).Replace("</p>", vbCrLf).Replace("</P>", vbCrLf).Replace("<br>", _ vbCrLf).Replace("<BR>", vbCrLf).Replace("</td>", vbCrLf).Replace("</TD>", vbCrLf) 'replace ISO 8859-1 Symbols (160-255). Note that any matches will make the text 8-bit Sb.Replace("&iexcl;", "").Replace("&cent;", "").Replace("&pound;", "").Replace("&curren;", _ "").Replace("&yen;", "").Replace("&brvbar;", "").Replace("&sect;", "").Replace("&uml;", _ "").Replace("&copy;", "").Replace("&ordf;", "").Replace("&laquo;", "").Replace("&not;", _ "").Replace("&shy;", "-").Replace("&reg;", "").Replace("&macr;", "").Replace("&deg;", _ "").Replace("&plusmn;", "").Replace("&sup2;", "").Replace("&sup3;", "").Replace("&acute;", _ "").Replace("&micro;", "").Replace("&para;", "").Replace("&middot;", "").Replace("&cedil;", _

53

"").Replace("&sup1;", "").Replace("&ordm;", "").Replace("&raquo;", "").Replace("&frac14;", _ "").Replace("&frac12;", "").Replace("&frac34;", "").Replace("&iquest;", "").Replace("&times;", _ "").Replace("&divide;", "") 'replace ISO 8859-1 characters. Note that any matches will make the text 8-bit Sb.Replace("&Agrave;", "").Replace("&Aacute;", "").Replace("&Acirc;", "").Replace("&Atilde;", "").Replace("&Auml;", _ "").Replace("&Aring;", "").Replace("&AElig;", "").Replace("&Ccedil;", "").Replace("&Egrave;", _ "").Replace("&Eacute;", "").Replace("&Ecirc;", "").Replace("&Euml;", "").Replace("&Igrave;", _ "").Replace("&Iacute;", "").Replace("&Icirc;", "").Replace("&Iuml;", "").Replace("&ETH;", _ "").Replace("&Ntilde;", "").Replace("&Ograve;", "").Replace("&Oacute;", "").Replace("&Ocirc;", _ "").Replace("&Otilde;", "").Replace("&Ouml;", "").Replace("&Oslash;", "").Replace("&Ugrave;", _ "").Replace("&Uacute;", "").Replace("&Ucirc;", "").Replace("&Uuml;", "").Replace("&Yacute;", _ "").Replace("&THORN;", "").Replace("&szlig;", "").Replace("&agrave;", "").Replace("&aacute;", _ "").Replace("&acirc;", "").Replace("&atilde;", "").Replace("&auml;", "").Replace("&aring;", _ "").Replace("&aelig;", "").Replace("&ccedil;", "").Replace("&egrave;", "").Replace("&eacute;", _ "").Replace("&ecirc;", "").Replace("&euml;", "").Replace("&igrave;", "").Replace("&iacute;", _ "").Replace("&icirc;", "").Replace("&iuml;", "").Replace("&eth;", "").Replace("&ntilde;", _ "").Replace("&ograve;", "").Replace("&oacute;", "").Replace("&ocirc;", "").Replace("&otilde;", _ "").Replace("&ouml;", "").Replace("&oslash;", "").Replace("&ugrave;", "").Replace("&uacute;", _ "").Replace("&ucirc;", "").Replace("&uuml;", "").Replace("&yacute;", "").Replace("&thorn;", _ "").Replace("&yuml;", "") 'replace Math Symbols Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&forall;", "").Replace("&part;", "").Replace("&exist;", "").Replace("&empty;", "").Replace("&nabla;", _ "").Replace("&isin;", "").Replace("&notin;", "").Replace("&ni;", "").Replace("&prod;", _ "").Replace("&sum;", "").Replace("&minus;", "").Replace("&lowast;", "").Replace("&radic;", _ "").Replace("&prop;", "").Replace("&infin;", "").Replace("&ang;", "").Replace("&and;", _ "").Replace("&or;", "").Replace("&cap;", "").Replace("&cup;", "").Replace("&int;", _ "").Replace("&there4;", "").Replace("&sim;", "").Replace("&cong;", "").Replace("&asymp;", _ "").Replace("&ne;", "").Replace("&equiv;", "").Replace("&le;", "").Replace("&ge;", _ "").Replace("&sub;", "").Replace("&sup;", "").Replace("&nsub;", "").Replace("&sube;", _ "").Replace("&supe;", "").Replace("&oplus;", "").Replace("&otimes;", "").Replace("&perp;", _ "").Replace("&sdot;", "") 'replace Greek Letters Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&Alpha;", "").Replace("&Beta;", "").Replace("&Gamma;", "").Replace("&Delta;", "").Replace("&Epsilon;", _ "").Replace("&Zeta;", "").Replace("&Eta;", "").Replace("&Theta;", "").Replace("&Iota;", _ "").Replace("&Kappa;", "").Replace("&Lambda;", "").Replace("&Mu;", "").Replace("&Nu;", _ "").Replace("&Xi;", "").Replace("&Omicron;", "").Replace("&Pi;", "").Replace("&Rho;", _ "").Replace("&Sigma;", "").Replace("&Tau;", "").Replace("&Upsilon;", "").Replace("&Phi;", _ "").Replace("&Chi;", "").Replace("&Psi;", "").Replace("&Omega;", " ").Replace("&alpha;", _ "").Replace("&beta;", "").Replace("&gamma;", "").Replace("&delta;", "").Replace("&epsilon;", _ "").Replace("&zeta;", "").Replace("&eta;", "").Replace("&theta;", "").Replace("&iota;", _ "").Replace("&kappa;", "").Replace("&lambda;", "").Replace("&mu;", "").Replace("&nu;", _ "").Replace("&xi;", "").Replace("&omicron;", "").Replace("&pi;", "").Replace("&rho;", _ "").Replace("&sigmaf;", "").Replace("&sigma;", "").Replace("&tau;", "").Replace("&upsilon;", _ "").Replace("&phi;", "").Replace("&chi;", "").Replace("&psi;", "").Replace("&omega;", _ "").Replace("&thetasym;", " ").Replace("&upsih;", " ").Replace("&piv;", " ") 'replace Other Entities Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&OElig;", "").Replace("&oelig;", "").Replace("&Scaron;", "").Replace("&scaron;", "").Replace("&Yuml;", _ "").Replace("&fnof;", "").Replace("&circ;", "").Replace("&tilde;", "").Replace("&ensp;", _ " ").Replace("&emsp;", " ").Replace("&thinsp;", "").Replace("&ndash;", "").Replace("&mdash;", _ "").Replace("&lsquo;", "").Replace("&rsquo;", "").Replace("&sbquo;", "").Replace("&ldquo;", _ " ").Replace("&rdquo;", " ").Replace("&bdquo;", "").Replace("&dagger;", "").Replace("&Dagger;", _ "").Replace("&bull;", "").Replace("&hellip;", "").Replace("&permil;", "").Replace("&prime;", _ "").Replace("&Prime;", "").Replace("&lsaquo;", "").Replace("&rsaquo;", "").Replace("&oline;", _ "").Replace("&euro;", "").Replace("&trade;", "").Replace("&larr;", "").Replace("&uarr;", _ "").Replace("&rarr;", "").Replace("&darr;", "").Replace("&harr;", "").Replace("&crarr;", _ "").Replace("&lceil;", "").Replace("&rceil;", "").Replace("&lfloor;", "").Replace("&rfloor;", _ "").Replace("&loz;", "").Replace("&spades;", "").Replace("&clubs;", "").Replace("&hearts;", _ "").Replace("&diams;", "") 'replace special ASCII coding entities that were not captured by the above. Note that values > 127 will make the text 8-bit For Idx As Integer = 1 To 255 'See www.w3schools.com/tags/ref_entities.asp Sb.Replace("&#" & Idx.ToString & ";", Chr(Idx)) 'replace most common numeric entities Next 'Ensure header definitions are followed by vbCrLf Dim NewText As String = RegularExpressions.Regex.Replace(Sb.ToString(), "</H[^>]*>", vbCrLf) 'Also seek out other Unicode encoded number entities not covered by the above and individually update them Dim Idy As Integer = InStr(NewText, "&#") 'check for a numeric entity Do While Idy <> 0 'loop as long as we find one Dim Idz As Integer = InStr(Idy, NewText, ";") 'find terminating semicolon Dim S As String = Mid(NewText, Idy, Idz - Idy + 1) 'grab expression RegularExpressions.Regex.Replace(NewText, S, Chr(CInt(Mid(S, 3, Len(S) - 3)))) 'replace expression InStr(Idy + 1, NewText, "&#") Loop 'strip remaining HTML text tags, replace < and > placeholders, convert ampersand, replace ;; with ;, then return result Return RegularExpressions.Regex.Replace(NewText, "<[^>]*>", "").Replace("&lt;", _ "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function '========================================================================== '========================================================================== '******************************************************************************* ' Enum MediaTypes: Structure used by GetMediaType '******************************************************************************* Public Enum MediaTypes As Integer ApplicationOctet ' 0 = Integer Value ApplicationPdf ' 1 ApplicationRtf ' 2 ApplicationSoap ' 3 ApplicationZip ' 4 ImageGif ' 5 ImageJpeg ' 6 ImageTiff ' 7 TextHtml ' 8 TextPlain ' 9 TextRich '10 TextXml '11 End Enum '******************************************************************************* ' Function Name : GetMediaType ' Purpose : Provide easy access to System.Net.Mime.MediaTypes text ' : ' Returns : provided a MediaTypes enumeration value, a string representing ' : the selected type will be returned. '******************************************************************************* Public Shared Function GetMediaType(ByVal MediaType As MediaTypes) As String Select Case MediaType Case MediaTypes.ApplicationPdf Return "application/pdf" Case MediaTypes.ApplicationRtf Return "application/rtf" Case MediaTypes.ApplicationSoap Return "application/soap+xml" Case MediaTypes.ApplicationZip Return "application/zip" Case MediaTypes.ImageGif Return "image/gif" Case MediaTypes.ImageJpeg Return "image/jpeg" Case MediaTypes.ImageTiff Return "image/tiff" Case MediaTypes.TextHtml Return "text/html" Case MediaTypes.TextPlain Return "text/plain" Case MediaTypes.TextRich Return "text/richtext" Case MediaTypes.TextXml Return "text/xml" Case Else Return "application/octet-stream" End Select End Function '========================================================================== '========================================================================== '******************************************************************************* ' Enum TransferEncodings: Structure used by GetTransferEncoding

54

'******************************************************************************* Public Enum TransferEncodings As Integer QuotedPrintable ' 0 = Integer value Base64 ' 1 SevenBit ' 2 End Enum '******************************************************************************* ' Function Name : GetTransferEncoding ' Purpose : Provide easy access to System.Net.Mime.TransferEncoding data ' : ' Returns : Provided a TransferEncodings value, a TransferEncoding value ' : is returned. '******************************************************************************* Public Shared Function GetTransferEncoding(ByVal TransferEncoding As TransferEncodings) As System.Net.Mime.TransferEncoding Return DirectCast(TransferEncoding, System.Net.Mime.TransferEncoding) End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : GetEmailInfo ' Purpose : Break email down into its component parts. ' : ' Returns : EmailInfo object with component parts of email broken down. ' : ' NOTES : This method uses classes EmailItems and EmailInfo. ' : The Message Body, and each AlternnateView or Attachment are ' : contained within EmailItem objects within the EmailIngo object. ' : ' : An EmailItem contains fields for FROM, TO, SUBJECT, Content-Type, ' : a flag indicating if the ContentTypeData is a filename or if it is ' : text formatting, content-transfer-encoding data, and the raw encoded, ' : data, whether it is a message or binary information. If the content' : transfer encoding is set to "base64", the data should be decoded ' : using the DecodeBase64() method. If it is "quoted-printable", the ' : data should be decoded using DecodeQuotedPrintable(). If it is ' : "7bit", it is 7-bit data and does not need to be decoded. '******************************************************************************* Public Shared Function GetEmailInfo(ByVal MailMessage As String) As EmailInfo Dim Info As New EmailInfo 'structure to hold breakdown of email Dim Ary() As String = Split(MailMessage, vbCrLf) 'break full email into lines Dim Idx As Integer = 0 'index into Ary() Dim MX As Integer = UBound(Ary) + 1 'find end if list+1 Dim Boundaries As New Collections.Generic.List(Of String) 'boundary definitions Dim Dim Dim Dim IsMultiPart As Boolean = False SeekingEncoding As Boolean = False BuildingDataBlock As Boolean = False HaveMessageBody As Boolean = False 'true 'true 'true 'true if if if if we we we we have multiple parts are looking for encoding are building a data block have the message body defined

Dim ContentType As String = vbNullString 'hold last-defined Content Type Dim ContentTypeIsName As Boolean = False 'true of Content Type specified a file Dim ContentTypeData As String = vbNullString 'if block isan attachment Dim ContentEncoding As String = vbNullString 'hold last-defined Content Transfer Encoding Dim ContentBody As String = vbNullString 'block data accumulator '----------------------------------------------------------Dim Inheader As Integer = 4 'flag for gathering To, From, Date, Subject Do Dim S As String = Ary(Idx) 'grab a line of data from the email ' ' check for important header items ' If CBool(Len(S)) AndAlso CBool(Inheader) Then 'if we are currently in the header... Dim I As Integer = InStr(S, ":") 'find field delimiter If CBool(I) Then 'found one? If VB.Right(S, 1) = ";" Then 'line continues? Idx += 1 'yes, so bump index S &= Ary(Idx).Trim(Chr(9), " "c) 'append next line next line End If Select Case LCase(VB.Left(S, I)) 'yes, check for one of 4 fields Case "from:" 'Found FROM field Info.FromData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "to:" 'Found TO field Info.ToData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "date:" 'Found DATE field Info.DateData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "subject:" 'Found SUBJECT field Info.SubjectData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag End Select End If If Not CBool(Inheader) Then 'if InHeader flag is zero SeekingEncoding = True 'start looking for a Content-Transfer-Encoding field S = vbNullString 'purge current data End If End If '------------------------------------------------------' check for boundaries '------------------------------------------------------If CBool(Len(S)) AndAlso CBool(Boundaries.Count) Then 'check any defined boundaries For Idy As Integer = 0 To Boundaries.Count - 1 If CBool(InStr(S, Boundaries.Item(Idy), CompareMethod.Text)) Then If BuildingDataBlock Then Dim Itm As New EmailItem 'create a new item Itm.ContentType = ContentType 'store content type Itm.ContentTypeData = ContentTypeData 'save filename or character set Itm.ContentTypeDataIsFilename = ContentTypeIsName 'save flag indicating if Attachment Itm.ContentEncoding = ContentEncoding 'store encoding Itm.ContentBody = ContentBody 'store data ContentBody = vbNullString 'reset accumulator If HaveMessageBody Then 'already have a message body? If CBool(Len(ContentTypeData)) Then 'if an attachment Info.Attachments.Add(Itm) 'add an attachment ContentTypeData = vbNullString 'reset filename Else 'otherwise an alternate view Info.AlternateViews.Add(Itm) End If Else Info.MessageBody = Itm 'else stuff new item to message body HaveMessageBody = True 'indicate we now have a message body End If BuildingDataBlock = False 'turn off building flag End If SeekingEncoding = True 'turn block seeing on again S = vbNullString 'purge current data Exit For End If Next End If '------------------------------------------------------' build data block '------------------------------------------------------If BuildingDataBlock Then ContentBody &= S & vbCrLf 'add a line to content data End If '------------------------------------------------------' if seeking encoding '------------------------------------------------------If CBool(Len(S)) AndAlso SeekingEncoding Then 'are we seeking TCE? Dim I As Integer = InStr(S, ":") 'yes, check for field delimiter If CBool(I) Then 'did we find one? Select Case LCase(VB.Left(S, I)) 'yes, check for types '======================================================= Case "content-type:" 'Content type? ContentType = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data If VB.Right(S, 1) = ";" Then 'more to add? Idx += 1 'yes, so bump index ContentType &= Ary(Idx).Trim(Chr(9), " "c) 'grab next line End If ContentTypeIsName = False 'init flag specifying a file as false Dim sbAry() As String = Split(ContentType, ";") 'now check the content type data ContentType = sbAry(0) 'keep only first part for ContentType If StrComp(VB.Left(sbAry(0), 10), "multipart/", CompareMethod.Text) = 0 Then 'multipart, so grab second parameter (boundary definition), and strip any quotes Dim Bnd As String = Trim(Mid(sbAry(1), InStr(sbAry(1), "=") + 1)).Replace("""", vbNullString)

55

Boundaries.Add(Bnd) 'and add a boundary ElseIf StrComp(VB.Left(sbAry(1), 5), "name=", CompareMethod.Text) = 0 Then ContentTypeIsName = True 'attachment if a filename specified (otherwise a view) sbAry = Split(sbAry(1), "=") 'multipart, so grab second parameter 'get second part of second parameter (boundary definition) ContentTypeData = sbAry(1).Trim().Replace("""", vbNullString) 'strip any quotes Else ContentTypeData = sbAry(1) 'AlternateView, so stuff display character set End If '=================================================== Case "content-transfer-encoding:" ContentEncoding = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip required following blank line End Select End If End If Idx += 1 'bump array index Loop While Idx < MX '----------------------------------------------------------Return Info 'return with filled data block End Function End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailItem (used by EmailInfo class) ' Purpose : Stores structure of an email block '******************************************************************************* Public Class EmailItem Public ContentType As String = vbNullString 'CONTENT-TYPE data Public ContentTypeData As String = vbNullString 'filename or text encoding Public ContentTypeDataIsFilename As Boolean = False 'True if ContentTypeData specifies a filename Public ContentEncoding As String = vbNullString 'CONTENT-TRANSFER-ENCODING data Public ContentBody As String = vbNullString 'raw data of block End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailInfo (used by GetEmailInfo method) ' Purpose : Store component parts of an Email '******************************************************************************* Public Class EmailInfo Public FromData As String = vbNullString 'FROM: Public ToData As String = vbNullString 'TO: Public DateData As String = vbNullString 'DATE: Public SubjectData As String = vbNullString 'SUBJECT: Public MessageBody As EmailItem 'contents of message body Public AlternateViews As New Collections.Generic.List(Of EmailItem) 'list of alternate views Public Attachments As New Collections.Generic.List(Of EmailItem) 'list of attachments End Class

Conclusion This concludes this article on Internet SMTP and POP3 Email processing, but it is certainly not the end of what can be done. For example, SMTP and POP3 both inherit from System.Net.Socket.TcpClient. What does this mean to you, you may ask? It means that you can create your own SMTP Mail object, and send email the way YOU want to send it. For instance, with the .NET Mail class you cannot send the main body of an email as Rich Text (you remember that IsBodyRtf option I mused about? You can add it in your own version of the class), or you cannot send a message body coded 7bit. With your own Mail class, you could, just as you can in Outlook or Windows Mail.

Actually, the underpinning of such a class is already wrapped up within our POP3 class. Hmmmmmm It looks like another article is in the works
David Ross Goben Last update: Wednesday, March 23, 2011, 7:53:41 AM Contact: david.ross.goben@gmail.com Please feel free to inform me of any errors or omissions.

56

About the Author David Ross Goben has been a professional software engineer, a writer, and an obsessive researcher. Of Jewish descent, he has extensively explored Biblical history, ancient cultural thinking, and ancient slang for over three decades, which has resulted in his seminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has written numerous books, manuals, and magazine articles, many uncredited, or authored under pen names. He has been involved in computing since long before personal computers were available even in kit form, he was a contributing Editor to 80 Micro Magazine, and there, co-wrote the Feedback Loop column with Beve Woodbury, under the pen name, Mercedes Silver. His interests include Cosmology, Quantum Physics, human-machine interaction, the Global Warming Myth, The Electric Universe Theory, Perpetual Energy Technology, Quartz Technology, Dream Walking, and the study of the bio-mechanical origins of life.
He is currently exploring the possibility that Albert Einstein got his Theory of Special Relativity wrong. Just about everyone is familiar with his equation, E=MC2, Energy (E) equals the Mass (M) times the speed of light (C) squared. But this Mass to Energy conversion is only half of the Special Relativity expression. The other half of Special Relativity involves Velocity (V), and is the calculation of Time Displacement (T): Einstein calculated E = MC2 / T, which would mean that at the speed of light, an object would attain infinite volume and infinite mass. Correct me if I am wrong, but even an ordinary mortal like me sees this as ridiculous, because this would mean that we would attain several quintillion times the volume and mass that exists in the entire universe (and even that may an infinite understatement). All you have to do is simply ponder it for a moment. However, reverse the order, where E = T / MC2, and all calculations yield the same results, save one, and that is that instead of attaining infinite volume and infinite mass at the speed of light, one instead moves into an alternate universe. What most people do not realize is that the very atoms of our body, at this very moment, are already vibrating at just under the speed of light. Just a tiny push and we would become multidimensional beings. I think this is why Quantum Physics, which is still nowhere near being a perfect model for planck-scale physics, will, even so, discover the Far World, or what the ancients called the Underworld or the Hidden World, or most everyone today refers to as the Afterlife or Heaven. Quantum Physics has already demonstrated that nothing in the universe has ever existed without Consciousness. So, where was Consciousness when the Universe was created out of literally nothing during a causal Singularity Event? Evidence shows that it existed. And that has been the crux of great debates throughout history. There is an easy experiment that can prove that Einsteins Theory of Special Relativity is inverted. Weigh a very heavy object, and then drop it onto a solid base, such as a concrete floor. Now weight the object again. It will weigh less for about 20 minutes; at which time its full weight will finally return. Where did the missing mass go to during those 20 minutes? If Einsteins calculation had been correct, the object would have actually weighed more after being dropped, not less.

57

The following Visual Basic documents are publicly available at: http://www.slideshare.net/DavidRossGoben, and at Google Docs at: http://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num=50.

58

Вам также может понравиться