Asked by:
Bugs in System.Net.Mail.MailMessage (.NET 2.0 SP1)
-
Some time ago I reported about bugs in System.Net.Mail.MailMessage (.NET 2.0), hoping for a fix in SP1 of .NET 2.0. Well, unfortunately most of them still exist.
- With MailMessage.Headers there is a bug where headers will have white space in an encoded text. This will lead to non-RFC2047 compliant messages, which will increase the SPAM rating of the message. MailAddresses (also to be considered as headers) are effected as well.
-
Quoted-Printable encoding is not limited to a maximum of 76 characters in System.Net.Mail. RFC2045 requires that Quoted-Printable encoding encodes lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks must be used.
-
MailMessage.To.ToString() returns the encoded string only after the message was sent. According to the documentation this should be the case no matter whether the message was already sent or not.
As a matter of fact I hacked System.Net.Mail using Reflection, published here (relevant class: Bugfixer.cs).
I consider these bugs very simple to fix, so I'm just wondering what else I could do to make this happen?
NorbertSunday, January 13, 2008 1:42 PM
Question
All replies
-
Said issues are on our radar. We're on track to fix them in an upcoming release. Unfortunately I cannot be more specific than that.
and thanks for bringing this to our attention. Again. In fact, you should try again if you don't see this fixed in SP2. These posts are really processed and comes to our attention.
Wednesday, January 16, 2008 1:33 AMModerator -
Is there a solution to the quoted printable lines being too long yet? I can't imagine how this has gone un-fixed for years now.
Our application uses the .Net SMTP engine to deliver email alerts to users, and now because of this quoted printable bug, our emails are often marked as spam.
PLEASE tell me this is actually on the radar to be fixed. It was allegedly in 3.5 or 3.5 SP1, but it's still broken. It's not like these email RFC standards are new and a few bugs would be understandable. And now that spam software is looking for this specifically, it's unacceptable that Microsoft would allow their customers to be screwed for using their own built in libraries.
Best Regards,
Kevin UllandMonday, March 2, 2009 8:08 PM -
I'm using .NET 2.0 SP2 and still are making bad quoted-printable encoding. It's still spaces in quoted printable text.
Are there any method or routine for passing my quoted-printable conversion routine for making good quoted-printable encoding?Monday, March 30, 2009 11:51 AM -
We plan to address all of these issues in .NET 4.0.
Thx,
DavidMonday, May 4, 2009 10:37 PMOwner -
At what point will the quoted printable line length be addressed? I have the 4.0 beta framework and it looks like it's still an issue...
Friday, July 31, 2009 4:44 PM -
The fixes are in .NET 4.0 Beta 2. The Beta that you were using in July was Beta 1.
Thx,
DavidThursday, November 19, 2009 9:37 PMOwner -
If you don't want to use .NET 4.0 you can use .NET 2.0 but using the methods of that class that "patches" errors.
Option Strict On Option Explicit On Imports System.Collections.Generic Imports System.Text Imports System.Net.Mail Imports System.Reflection Imports System.Net.Mime Imports System Imports System.IO Public Class clsBugFixerSystemNetMail ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 before Servicepack 1: ''' TransferEncoding.SevenBit turns into header text "sevenbit", instead of "7bit". ''' "sevenbit" is not RFC compliant and causes problems with some mail clients. This method ''' does the correction. ''' </summary> ''' <seealso cref="http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=98783&SiteID=1"/> ''' <param name="att">all Attachments, AlternateViews und LinkedResources are based on AttachmentBase</param> '''(Obsolete("Only needed for .NET 2.0 before Servicepack 1", false)) Public Shared Sub CorrectSevenBitTransferEncoding(ByVal att As AttachmentBase) '' do nothing wiht binary or unknown transfer encoding Dim lArgs() As Object If ((att Is Nothing) Or (att.TransferEncoding = TransferEncoding.Base64) Or _ att.TransferEncoding = TransferEncoding.Unknown) Then Return End If If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or _ System.Environment.Version.Build <> 50727 Or System.Environment.Version.Revision > 900) Then If QPEncoder.IsSevenBit(att.ContentStream) Then att.TransferEncoding = TransferEncoding.SevenBit Else att.TransferEncoding = TransferEncoding.QuotedPrintable End If Return End If '' Get System.Net.Mime.MimePart: Dim mimePart As Object = GetType(System.Net.Mail.AttachmentBase).InvokeMember("part", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.GetField, Nothing, att, Nothing) '' get fields of mimePart Dim fi As FieldInfo() = mimePart.GetType().BaseType.GetFields(BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance) '' Get System.Net.Mime.HeaderCollection: Dim headerCollection As Object headerCollection = mimePart.GetType().BaseType.InvokeMember("headers", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or BindingFlags.GetField, _ Nothing, mimePart, Nothing) '' Get methods of System.Net.Mime.HeaderCollection: Dim i As MethodInfo() = headerCollection.GetType().GetMethods(BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance) '' System.Net.Mime.HeaderCollection: get buggy header value ReDim lArgs(0) lArgs(0) = "content-transfer-encoding" Dim contentTransferEncoding As String = CType(headerCollection.GetType().InvokeMember("Get", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or _ BindingFlags.InvokeMethod, Nothing, headerCollection, largs), String) '' System.Net.Mime.HeaderCollection: Set buggy value from "sevenbit" to "7bit" '' Need to check the string value, because att.TransferEncoding shows TransferEncoding.Unknown If (contentTransferEncoding = "sevenbit") Then ' set 7bit only, if content really is seven bit If (QPEncoder.IsSevenBit(att.ContentStream)) Then ReDim lArgs(1) lArgs(0) = "content-transfer-encoding" lArgs(1) = "7bit" headerCollection.GetType().InvokeMember("Set", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or _ BindingFlags.InvokeMethod, Nothing, headerCollection, lArgs) '' att.TransferEncoding will now show TranferEncoding.SevenBit (instead of TransferEncoding.Unknown) Else att.TransferEncoding = TransferEncoding.QuotedPrintable '/* '// set 8bit (which is not supported by System.Net.Mail) for 8bit content - not recommended 'headerCollection.GetType().InvokeMember("Set", ' BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy | ' BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance | ' BindingFlags.InvokeMethod, null, headerCollection, new object[] { "content-transfer-encoding", "8bit" }); '*/ End If End If End Sub ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 including .NET 3.5 SP1 released version: ''' system.net.mail has a bug where headers will have whitespace in the encoded text. ''' This will lead to non-RFC2047 compliant messages, which may lead to SPAM rating of the message. ''' See a list of SPAM scores at http://www.oucs.ox.ac.uk/network/smtp/relay/scores.txt. ''' ''' 'encoded-word's are designed to be recognized as 'atom's by an RFC 822 parser. As a consequence, unencoded white space ''' characters (such as SPACE and HTAB) are FORBIDDEN within an 'encoded-word'. For example, the character sequence ''' =?iso-8859-1?q?this is some text?= ''' would be parsed as four 'atom's, rather than as a single 'atom' (by an RFC 822 parser) or 'encoded-word' (by a parser which understands ''' 'encoded-words'). The correct way to encode the string "this is some text" is to encode the SPACE characters as well, e.g. ''' =?iso-8859-1?q?this=20is=20some=20text?= ''' </summary> Public Shared Sub CorrectSubjectEncodedWordRFC2047compliant(ByVal mailMessage As MailMessage) Dim largs() As Object If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727) Then Return End If '/* The _mailMessage.Subject cannot be changed directly. So the workaround is as follows: ' * 1. Set the subject to null, so that no subject header will be produced ' * 2. Add the subject as a header, of course with correct handling of whitespace in the encoded text. ' * ' * System.Net.MimeBasePart does the same in an internal static method: ' * this.Headers["subject"] = MimeBasePart.EncodeHeaderValue(this.subject, this.subjectEncoding, MimeBasePart.ShouldUseBase64Encoding(this.subjectEncoding)); ' */ '' Create an instance of System.Net.Mime.MimePart: Dim mimePart As Object = GetType(System.Net.Mail.AttachmentBase).InvokeMember("part", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.GetField, Nothing, New Attachment(New System.IO.MemoryStream(New ASCIIEncoding().GetBytes(String.Empty)), "dummy"), Nothing) Dim subject As String = mailMessage.Subject ReDim largs(0) largs(0) = mailMessage.SubjectEncoding Dim ShouldUseBase64Encoding As Boolean = Convert.ToBoolean(mimePart.GetType().BaseType.InvokeMember("ShouldUseBase64Encoding", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Static Or BindingFlags.InvokeMethod, _ Nothing, mimePart, largs)) ReDim largs(2) largs(0) = subject largs(1) = mailMessage.SubjectEncoding largs(2) = ShouldUseBase64Encoding subject = mimePart.GetType().BaseType.InvokeMember("EncodeHeaderValue", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Static Or BindingFlags.InvokeMethod, _ Nothing, mimePart, largs).ToString() '' Bug fix: this bug fix will only work, if ToggleMailHeaderInfoTypeForSubject() is applied before SmtpClient.Send() ! mailMessage.Subject = Nothing mailMessage.Headers.Remove("Subject") mailMessage.Headers.Add("Subject", subject.Replace(" ", "=20")) End Sub ''' <summary> ''' Helps to correct a bug in System.Net.Mail of .NET 2.0 including .NET 3.5 released version: ''' This is a helper function for CorrectSubjectEncodedWordRFC2047compliant(MailMessage mailMessage), which ''' makes sure that an individual MailMessage.Header["Subject"] will not be removed during SmtpClient.Send(). ''' To achieve this System.Net.Mail.MailHeaderInfo will be temporarily hacked by renaming ''' the known header for "Subject" to "SubjectRemoved", so the individual MailMessage.Header["Subject"] ''' will not be found. ''' </summary> ''' <remarks> ''' Remember to call this method again right after SmtpClient.Send(), so that ''' static System.Net.Mail.MailHeaderInfo fields will be restored. ''' </remarks> ''' <seealso cref="CorrectSubjectEncodedWordRFC2047compliant"/> Public Shared Sub ToggleMailHeaderInfoTypeForSubject(ByVal msg As MailMessage, ByVal doSubjectRemove As Boolean) Dim largs() As Object If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727 Or System.Environment.Version.Revision <= 900) Then Return End If '' Apply only if the subject has to be processed in a special way (due to its Encoding). '' Take care: IT MUST NOT BE RUN, if the MailMessage.Subject is set, because this would produce '' an additional MailMessage.Header of "SubjectRemoved" (caused by this method) If (doSubjectRemove And Not (String.IsNullOrEmpty(msg.Subject) And msg.Headers.GetValues("Subject") IsNot Nothing)) Then Return End If Dim asm As Assembly = Assembly.GetAssembly(GetType(MailAddress)) '' Retrieve Types of MailHeaderInfo, used in method PrepareHeaders() of System.Net.Mail.Message Dim mailHeaderInfoType As Type = asm.GetType("System.Net.Mail.MailHeaderInfo", False) Dim headerInfoType As Type = asm.GetType("System.Net.Mail.MailHeaderInfo+HeaderInfo", False) Dim mailHeaderIDtype As Type = asm.GetType("System.Net.Mail.MailHeaderID", False) '' Invoke System.Net.Mail.MailHeaderInfo.GetString(MailHeaderID id) '' in order to make sure that privat fields will be set by its constructor Dim mailHeaderInfoMethodInfo As MethodInfo = mailHeaderInfoType.GetMethod("GetString", BindingFlags.Static Or BindingFlags.NonPublic) ReDim largs(0) largs(0) = 2 mailHeaderInfoMethodInfo.Invoke(Nothing, largs) '' get array HeaderInfo[] m_HeaderInfo = new HeaderInfo[] { new HeaderInfo(MailHeaderID.Bcc, "Bcc", true), ... } '' HeaderInfo is a structure with: string NormalizedName, bool IsSingleton, (enum) MailHeaderID ID; Dim headerInfo As FieldInfo = mailHeaderInfoType.GetField("m_HeaderInfo", BindingFlags.Static Or BindingFlags.NonPublic) Dim headerInfoArray As Array = CType(headerInfo.GetValue(Nothing), Array) '' find the HeaderInfo for "Subject" and set the NormalizedName to "SubjectRemoved" (or vice versa) Dim i As Integer = 0 Do Dim normalizedNameInfo As FieldInfo = _ headerInfoType.GetField("NormalizedName", BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.GetField) If Not (i < headerInfoArray.Length - 1) Then Exit Do End If If (CType(normalizedNameInfo.GetValue(headerInfoArray.GetValue(i)), String) = "Subject") And doSubjectRemove Then ReDim largs(2) largs(0) = mailHeaderIDtype.GetField("Subject").GetValue(mailHeaderIDtype) largs(1) = "SubjectRemoved" largs(2) = True Dim headerInfoItem As Object = Activator.CreateInstance(headerInfoType, largs) headerInfoArray.SetValue(headerInfoItem, i) ElseIf (CType(normalizedNameInfo.GetValue(headerInfoArray.GetValue(i)), String) = "SubjectRemoved" And (Not doSubjectRemove)) Then ReDim largs(2) largs(0) = mailHeaderIDtype.GetField("Subject").GetValue(mailHeaderIDtype) largs(1) = "Subject" largs(2) = True Dim headerInfoItem As Object = Activator.CreateInstance(headerInfoType, largs) headerInfoArray.SetValue(headerInfoItem, i) End If i += 1 Loop ''now set "Subject" item of Dictionary<string, int> m_HeaderDictionary also to "SubjectRemoved" (or vice versa) Dim headerDictionaryInfo As FieldInfo = mailHeaderInfoType.GetField("m_HeaderDictionary", BindingFlags.Static Or BindingFlags.NonPublic) Dim m_HeaderDictionary As Dictionary(Of String, Integer) = CType(headerDictionaryInfo.GetValue(Nothing), Dictionary(Of String, Integer)) If (m_HeaderDictionary.ContainsKey("Subject") And doSubjectRemove) Then '' Rename the key from "Subject" to "SubjectRemoved" m_HeaderDictionary.Add("SubjectRemoved", m_HeaderDictionary("Subject")) m_HeaderDictionary.Remove("Subject") ElseIf (m_HeaderDictionary.ContainsKey("SubjectRemoved") And Not doSubjectRemove) Then '' Rename the key from "Subject" to "SubjectRemoved" m_HeaderDictionary.Add("Subject", m_HeaderDictionary("SubjectRemoved")) m_HeaderDictionary.Remove("SubjectRemoved") End If End Sub ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 including .NET 3.5 SP1 released version: ''' Quoted-Printable encoding is not limited to a maximum of 76 charchters. ''' This method does the correction. ''' ''' RFC2045: The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 ''' characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks ''' must be used. An equal sign as the last character on a encoded line indicates such a non-significant ("soft") ''' line break in the encoded text. ''' The 76 character limit does not count the trailing CRLF, but counts all other characters, including any equal signs. ''' ''' Behavior of .NET 2.0: Insert "soft" line break after a word that starts before 76th character ''' msg.Body = "mailmessage doesn't like veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery long strings"; ''' result (wrong): ''' content-type: text/plain; charset=iso-8859-1 ''' content-transfer-encoding: quoted-printable ''' mailmessage doesn't like veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery= ''' long strings ''' ''' correct: ''' content-type: text/plain; charset=iso-8859-1 ''' content-transfer-encoding: quoted-printable ''' ''' mailmessage doesn't like veryveryveryveryveryveryveryveryveryveryveryveryve= ''' ryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery long strings ''' </summary> ''' <seealso cref="http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=156052"/> ''' <param name="att">all Attachments, AlternateViews und LinkedResources are based on AttachmentBase</param> ''' <param name="encoding">Encoding</param> Public Shared Sub CorrectQuotedPrintableRFC2045compliant(ByVal att As AttachmentBase, ByVal encoding As Encoding) Dim lArgs() As Object If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727) Then Return End If If (att Is Nothing) Then Return End If If (att.TransferEncoding = TransferEncoding.QuotedPrintable) Then '' Get System.Net.Mime.MimePart: Dim mimePart As Object = GetType(System.Net.Mail.AttachmentBase).InvokeMember("part", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.GetField, Nothing, att, Nothing) '' Convert the content stream to a well formated MemoryStream Dim correctedStream As System.IO.MemoryStream = New System.IO.MemoryStream(encoding.GetBytes( _ QPEncoder.GetInstance().EncodeString(Stream2String(att.ContentStream, encoding), encoding))) '' Set System.Net.Mime.MimePart.stream: ReDim lArgs(0) lArgs(0) = correctedStream mimePart.GetType().InvokeMember("stream", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or BindingFlags.SetField, _ Nothing, mimePart, lArgs) '' Get System.Net.Mime.MimeBasePart.HeaderCollection: Dim headerCollection As Object headerCollection = mimePart.GetType().BaseType.InvokeMember("headers", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or BindingFlags.GetField, _ Nothing, mimePart, Nothing) '' System.Net.Mime.HeaderCollection: Undo cheating of content-transfer-encoding '' note the leading blank in " quoted-printable", which leads to a valid header '' but which is unknown to the .NET framework - so the stream will be sent unchanged. '' att.TransferEncoding will show as TransferEncoding.Unknown now - don't care!! ReDim lArgs(1) lArgs(0) = "content-transfer-encoding" lArgs(1) = " quoted-printable" headerCollection.GetType().InvokeMember("Set", _ BindingFlags.DeclaredOnly Or BindingFlags.FlattenHierarchy Or _ BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase Or BindingFlags.Instance Or _ BindingFlags.InvokeMethod, Nothing, headerCollection, lArgs) End If End Sub ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 (including .NET 3.5 SP1 released version): ''' system.net.mail has a bug where headers will have whitespace in the encoded text. ''' This will lead to non-RFC2047 compliant messages, which may lead to SPAM rating of the message. ''' See a list of SPAM scores at http://www.oucs.ox.ac.uk/network/smtp/relay/scores.txt. ''' ''' 'encoded-word's are designed to be recognized as 'atom's by an RFC 822 parser. As a consequence, unencoded white space ''' characters (such as SPACE and HTAB) are FORBIDDEN within an 'encoded-word'. For example, the character sequence ''' =?iso-8859-1?q?recipient display name?= ''' would be parsed as four 'atom's, rather than as a single 'atom' (by an RFC 822 parser) or 'encoded-word' (by a parser which understands ''' 'encoded-words'). The correct way to encode the string "this is some text" is to encode the SPACE characters as well, e.g. ''' =?iso-8859-1?q?recipient=20display=20name?= ''' </summary> ''' <param name="addr">MailAddress</param> ''' <param name="encoding">Encoding</param> Public Shared Sub CorrectMailAddressEncodedWordRFC2047compliant(ByRef addr As MailAddress, ByVal encoding As Encoding) Dim lArgs() As Object If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727) Then Return End If '' enforce encoding only if the address is not completely 7bit or it contains a hyphen If (QPEncoder.IsSevenBit(addr.ToString()) = False) Then addr = New MailAddress(addr.Address, addr.DisplayName, encoding) Dim encodedDisplayName As String = GetType(MailAddress).InvokeMember("encodedDisplayName", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.GetField, Nothing, addr, Nothing).ToString() ReDim lArgs(0) lArgs(0) = encodedDisplayName.Replace(" ", "=20") GetType(MailAddress).InvokeMember("encodedDisplayName", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.SetField, Nothing, addr, lArgs) Else If addr.DisplayName.Length > 0 Then addr = New MailAddress(addr.Address, addr.DisplayName) End If End If End Sub ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 before Servicepack 1 (equivalent to .NET 3.5 released version): ''' With To addresses, displayNameEncoded is never used (with CC, From etc. it is) ''' therefore we cannot use the To MailAddress collection of the mail message, ''' but instead we set the "to" headers ourselves. ''' </summary> ''' <param name="toAddr">To MailAddress to add.</param> ''' <param name="msg">MailMessage the To address will be added.</param> ''' <param name="toHeader">string to be used for the "To" address header</param> '''[Obsolete("Only needed and working for .NET 2.0 before Servicepack 1", false)] Public Shared Sub AddToAddressWithCorrectEncoding(ByVal toAddr As MailAddress, ByVal msg As MailMessage, ByVal toHeader As String) Dim lParam As String Dim lStrings() As String If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727 Or System.Environment.Version.Revision > 900) Then Return End If '' Remove the original To address, then add our own envelope header msg.To.Remove(toAddr) Dim oldTo As String = msg.Headers(toHeader) msg.Headers.Remove(toHeader) If String.IsNullOrEmpty(oldTo) = True Then lParam = String.Empty Else lParam = ", " End If ReDim lStrings(1) lStrings(0) = oldTo lStrings(1) = clsBugFixerSystemNetMail.GetEncodedMailAddress(toAddr) msg.Headers.Add(toHeader, String.Join(lParam, lStrings)) '' SmtpClient wants at least 1 To/CC/Bcc address, so we add the To address as Bcc as well msg.Bcc.Add(toAddr) End Sub ''' <summary> ''' Corrects a bug in System.Net.Mail of .NET 2.0 before Servicepack 1 (equivalent to .NET 3.5 released version): ''' After MailMessage.MailAddressCollection.Clear erroneously MailAddress is left in message, ''' in addition. This function removes the Headers that MailMessage.Clear() does not remove. ''' </summary> ''' <param name="msg">MailMessage</param> '''[Obsolete("Only needed and working for .NET 2.0 before Servicepack 1", false)] Public Shared Sub ClearMessageHeaders(ByVal msg As MailMessage) If (System.Environment.Version.Major <> 2 Or System.Environment.Version.MajorRevision <> 0 Or System.Environment.Version.Build <> 50727 Or System.Environment.Version.Revision > 900) Then Return End If msg.Headers.Remove("cc") msg.Headers.Remove("bcc") msg.Headers.Remove("sender") msg.Headers.Remove("from") msg.Headers.Remove("reply-to") End Sub ''' <summary> ''' Returns a mail address string, which display name is encoded when needed. ''' </summary> ''' <param name="paddr"></param> ''' <returns></returns> '''[Obsolete("Only needed and working for .NET 2.0 before Servicepack 1", false)] Private Shared Function GetEncodedMailAddress(ByVal paddr As MailAddress) As String '' DO NOT invoke member fullAddress of MailAddress, because '' ToEncodedString will construct the fullAddress in case it is null Return CType(GetType(MailAddress).InvokeMember("ToEncodedString", _ BindingFlags.DeclaredOnly Or _ BindingFlags.Public Or BindingFlags.NonPublic Or _ BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, paddr, Nothing), String) End Function ''' <summary> ''' Converts a stream to a string using systems's current ANSI codepage. ''' </summary> ''' <param name="pstream">System.IO.Stream stream</param> ''' <returns>string representation of the stream.</returns> Public Shared Function Stream2String(ByVal pstream As System.IO.Stream) As String Return Stream2String(pstream, Encoding.Default) End Function ''' <summary> ''' Converts a stream to a string. ''' </summary> ''' <param name="pstream"></param> ''' <param name="pencoding"></param> ''' <returns></returns> Public Shared Function Stream2String(ByVal pstream As System.IO.Stream, ByVal pencoding As Encoding) As String Dim lstreamPos As Long = pstream.Position pstream.Seek(0, SeekOrigin.Begin) Dim lbytes() As Byte ReDim lbytes(CInt(pstream.Length - 1)) pstream.Read(lbytes, 0, CType(pstream.Length, Integer)) pstream.Seek(lstreamPos, SeekOrigin.Begin) Return pencoding.GetString(lbytes) End Function End Class
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
I've creathed a method for sending e-mails using that class :
Public Sub EnviarviaSMTP(ByVal pServidorSMTP As String, ByVal pUsuario As String, ByVal pContrasena As String, _ ByVal pAsunto As String, ByVal pMensaje As String, ByVal pOriginatarioEmail As String, _ ByRef pDestinatarios() As String, Optional ByVal pDestinatariosCC() As String = Nothing, _ Optional ByRef pAdjuntos() As String = Nothing, _ Optional ByVal pTiempoEsperaEnvioMensaje As Integer = 5 * 60 * 1000, _ Optional ByVal pMensajeHTML As String = "") 'tiempoespera en milisegundos Dim lMensajeAEnviar As System.Net.Mail.MailMessage = New System.Net.Mail.MailMessage() 'para enviar el mensaje Dim lSubjectEncoding As Encoding = Encoding.GetEncoding("iso8859-1") Dim lClienteSMTP As System.Net.Mail.SmtpClient Dim lMailAddress As System.Net.Mail.MailAddress lMensajeAEnviar.From = New System.Net.Mail.MailAddress(pOriginatarioEmail) 'lMensajeAEnviar.To.Insert0, New System.Net.Mail.MailAddress(emaildestinatario)) For Each ldest As String In pDestinatarios lMailAddress = New System.Net.Mail.MailAddress(ldest) clsBugFixerSystemNetMail.CorrectMailAddressEncodedWordRFC2047compliant(lMailAddress, System.Text.Encoding.GetEncoding("iso-8859-1")) lMensajeAEnviar.To.Add(lMailAddress) Next ldest If Not (pDestinatariosCC Is Nothing) Then For Each ldestCC As String In pDestinatariosCC lMailAddress = New System.Net.Mail.MailAddress(ldestCC) clsBugFixerSystemNetMail.CorrectMailAddressEncodedWordRFC2047compliant(lMailAddress, System.Text.Encoding.GetEncoding("iso-8859-1")) lMensajeAEnviar.CC.Add(lMailAddress) Next ldestCC End If lMailAddress = New System.Net.Mail.MailAddress(pOriginatarioEmail) clsBugFixerSystemNetMail.CorrectMailAddressEncodedWordRFC2047compliant(lMailAddress, System.Text.Encoding.GetEncoding("iso-8859-1")) lMensajeAEnviar.Sender = lMailAddress 'lMensajeAEnviar.Headers.Item("Content Type") = pContentTypeMensaje lMensajeAEnviar.Body = pMensaje lMensajeAEnviar.BodyEncoding = Encoding.GetEncoding("iso8859-1") 'lMensajeAEnviar.ContentTransferEncoding = lmsgleido.ContentTransferEncoding lMensajeAEnviar.SubjectEncoding = lSubjectEncoding lMensajeAEnviar.Subject = pAsunto 'QPEncoder(pAsunto, lSubjectEncoding) clsBugFixerSystemNetMail.CorrectSubjectEncodedWordRFC2047compliant(lMensajeAEnviar) If Not (pAdjuntos Is Nothing) Then For Each lAdjunto As String In pAdjuntos Dim mimeattach As System.Net.Mail.Attachment mimeattach = New System.Net.Mail.Attachment(lAdjunto) mimeattach.TransferEncoding = Net.Mime.TransferEncoding.Base64 mimeattach.Name = System.IO.Path.GetFileName(lAdjunto) ' QPEncoder(System.IO.Path.GetFileName(lAdjunto), System.Text.Encoding.Default) lMensajeAEnviar.Attachments.Add(mimeattach) Next lAdjunto End If If pMensajeHTML <> "" Then 'http://blogs.interakting.co.uk/brad/archive/2007/06/12/67.aspx para enviar 'mensajes html con system.net.mail.mailmessage (lmensajeaenviar.AlternateViews.Add ) Dim lHTMLMens As System.Net.Mail.AlternateView lHTMLMens = System.Net.Mail.AlternateView.CreateAlternateViewFromString(pMensajeHTML, _ Nothing, System.Net.Mime.MediaTypeNames.Text.Html) lMensajeAEnviar.AlternateViews.Add(lHTMLMens) End If ErrorEnvioEmail = False Try lClienteSMTP = New System.Net.Mail.SmtpClient(pServidorSMTP, 25) lClienteSMTP.Credentials = New System.Net.NetworkCredential(pUsuario, pContrasena) lClienteSMTP.UseDefaultCredentials = False lClienteSMTP.EnableSsl = False lClienteSMTP.DeliveryMethod = Net.Mail.SmtpDeliveryMethod.Network lClienteSMTP.Timeout = pTiempoEsperaEnvioMensaje clsBugFixerSystemNetMail.ToggleMailHeaderInfoTypeForSubject(lMensajeAEnviar, True) 'HA DE LLAMARSE ANTES DEL SEND - usamos "arreglo" para que haga bien la codificación quoted-printable en subject lClienteSMTP.Send(lMensajeAEnviar) clsBugFixerSystemNetMail.ToggleMailHeaderInfoTypeForSubject(lMensajeAEnviar, False) 'HA DE LLAMARSE DESPUES DEL SEND lClienteSMTP = Nothing Catch ex As System.Net.Mail.SmtpException ErrorStrEnvioEmail = "En Función EnviarviaSMTP : Error " & ex.ToString() ErrorEnvioEmail = True End Try End Sub
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
Sorry but comments and variables are in spanish.
Parameters used in the function are:
pServidorSMTP : ip address of dns name of SMTP server to use
pUsuario : User for authenticating to that server
pContrasena : Password for the authentication
pAsunto : Subject of message
pMensaje : Body of message encoded in iso-8859-1 ("like a simple text message")
pOriginatarioEmail : Sender of e-mail
pDestinatarios : Array of e-mail destinations
pDestinatariosCC : optional parameter of CC e-mail destinations
pAdjuntos : optional array parameter of paths to attached files to e-mail
pTiempoEsperaEnvioMensaje : optional parameter of time that waits for sending e-mail in miliseconds. If that number of miliseconds past and message is not send an error occurs.
pMensajeHTML : Body of the message in HTML format. It's sended as an alternative view to Body message. If e-mail client support HTML then HTML is shown.
Sorry for bad english.- Proposed as answer by david develop Monday, November 19, 2012 8:40 AM
Friday, December 11, 2009 8:12 AM -
VB6 upgrader to VB.nET:
Would have been nice to mention that you have converted this code from MailMergeLib (written in C#) which is published on http://www.codeproject.com/KB/IP/MailMergeLib.aspx, wouldn't it?
Wednesday, December 23, 2009 10:47 PM -
I'm so sorry for forgetting mentioning it.
I decided to write the source code reference links but I forgot.
The version of the c# code used for converting to vb.net is of 30/8/2008.
I have recently converted and added to the class the new CorrectLinesStartingWithDot function from the version of 23/12/2008.
Here it is, but is not using IsSwitchedOn:
''' <summary> ''' When talking to an SMTP server, SmtpClient should double all dots at the beginning of a line ''' for all text-based content. This works fine if MailMessage.Body is used. But it does not work ''' if AlternateViews or text attachments are used. The result is, that these dots get lost or - ''' even worse - all content after the dot gets lost if it is followed by CRLF (RFC 2821 violation). ''' </summary> ''' <remarks> ''' This bug fix is not needed if MailMessage.Body is used. In this case .NET does ''' dot handling right. MailMergeMessage does not use it in order to have more control ''' over TransferEncoding and others. ''' This bug fix must be applied BEFORE CorrectQuotedPrintableRFC2045compliant. ''' </remarks> ''' <param name="att">All Attachments, AlternateViews und LinkedResources are based on AttachmentBase.</param> ''' <param name="encoding">System.Text.Encoding</param> Public Shared Sub CorrectLinesStartingWithDot(ByVal att As AttachmentBase, ByVal encoding As Encoding) ''no usado, isswitchedon ''If (Not IsSwitchedOn) Then '' Return ''End If If (System.Environment.Version.Major < 2 Or System.Environment.Version.Major > 4) Then Return End If If (att Is Nothing) Then Return End If If (encoding Is Nothing) Then encoding = encoding.UTF8 End If Const flags As BindingFlags = BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public '' Do nothing if it's not an attachment is not a "text" type. If (att.TransferEncoding <> TransferEncoding.QuotedPrintable) And (att.TransferEncoding <> TransferEncoding.SevenBit) Then Return End If '' Get the System.Net.Mime.MimePart: Dim mimePart As Object = GetType(System.Net.Mail.AttachmentBase).GetField("part", flags).GetValue(att) '' Convert the content stream into a strng Dim mimeContent As String = Stream2String(att.ContentStream, encoding) '' if the line starts with a dot, double the dot If (mimeContent.StartsWith(".")) Then mimeContent = "." + mimeContent End If mimeContent = mimeContent.Replace(Environment.NewLine + ".", Environment.NewLine + "..") '' Convert the content stream to a well formated MemoryStream Dim correctedStream As System.IO.MemoryStream = New System.IO.MemoryStream(encoding.GetBytes(mimeContent)) '' Set System.Net.Mime.MimePart.stream: mimePart.GetType().GetField("stream", flags).SetValue(mimePart, correctedStream) End Sub- Proposed as answer by david develop Monday, November 19, 2012 8:39 AM
Thursday, December 24, 2009 7:38 AM -
Hi there,
i'm using sytem.net.mail in our framework at work. We are experiencing problem with spaces in the attachement filename. For some reason a space is removed from
the file attahcment name once its sent to stmp server. Its stange because the space always removed at a difference place , each time. We first tought that it was a problem with the smtp GateWay. However the server guy told us that the data that they receive from us is already corrupted once they receive it. So i have few questions:
1. Is that a know problem?
2. Is there a fix for that without using any other API than system.net.mail( i checked your article on codeproject but can't find one)
3. Do you think that i could be rfc2047 encoding proglem(if yes how do fix it? Is it possible to do the encoding implicitly)
4. Is your MailMergLib library could fix that problem?
I know , its a lot of question!
Thanks!
Tuesday, June 1, 2010 2:54 PM -
Never mind,
i just fixed my problem using your MailMergLib.bugfix class. THanks!
Tuesday, June 1, 2010 6:56 PM