locked
Bugs in System.Net.Mail.MailMessage (.NET 2.0 SP1)

    Question

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

    1. 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.
    2. 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.
    3. 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?

    Norbert
    Sunday, January 13, 2008 1:42 PM

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 AM
    Moderator
  • 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 Ulland
    Monday, 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,

    David
    Monday, May 4, 2009 10:37 PM
    Moderator
  • 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,

    David
    Thursday, November 19, 2009 9:37 PM
    Moderator
  • 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