none
4.0 SMTPClient.Send Attachments mit Umlauten im Dateinamen RRS feed

  • Question

  • Hallo,

    ich möchte erst mal vorrausschicken, dass es sich nicht um den BUG: SMTP Attachmentgröße unter .NET 4.0 handelt.

    Bei Versand von Attachments mit mehr als 80 Zeichen im kompletten Dateipfad und Umlauten im Dateinamen bekommt der Empfänger nur noch Müll (.dat) Datei.

    Im meinem Fall war die versendete Datei eine kleine PDF Datei von ein paar kBytes.

    Wenn man nach der Instanzierung des Attachment mit dem Dateipfad auf die Eigenschaft myAttachment.ContentType.Name betrachtet, tritt dort diese Problem schon zu Tage, was sonst erst der Empfänger zu Gesicht bekommt. Dort steht dann statt des Dateinamens schon Base64 codierter Text.

    Unter dem Framework 2.0 erreicht die Datei regulär den Empfänger. Auch bei einer Datei mit Umlauten die einen kürzeren Pfad als 80 Zeichen gibt es keine Probleme. Mischversand funktioniert soweit, dass die nicht betroffene Datei ankommt und der Rest als Müll.

    Als Workaround habe ich die Umlaute und andere Sonderzeichen entsprechend ersetzt, dann funktioniert der Versand einer 259 Zeichen (inkl. Pfad) langen Datei.

    Gibt es noch andere mögliche Varianten?

    Viele Grüße

    Oliver

    Wednesday, July 21, 2010 3:53 PM

Answers

  • Hallo Oliver,

    Hier eine mögliche Lösung in Form einer kleinen Helferklasse:

    public class AttachmentHelper
    {
     public static Attachment CreateAttachment(string attachmentFile, string displayName, TransferEncoding transferEncoding)
     {
      Attachment attachment = new Attachment(attachmentFile);
      attachment.TransferEncoding = transferEncoding;
    
      string tranferEncodingMarker = String.Empty;
      string encodingMarker = String.Empty;
      int maxChunkLength = 0;
    
      switch (transferEncoding)
      {
       case TransferEncoding.Base64:
        tranferEncodingMarker = "B";
        encodingMarker = "UTF-8";
        maxChunkLength = 30;
        break;
       case TransferEncoding.QuotedPrintable:
        tranferEncodingMarker = "Q";
        encodingMarker = "ISO-8859-1";
        maxChunkLength = 76;
        break;
       default:
        throw (new ArgumentException(String.Format("The specified TransferEncoding is not supported: {0}", transferEncoding, "transferEncoding")));
      }
    
      attachment.NameEncoding = Encoding.GetEncoding(encodingMarker);
    
      string encodingtoken = String.Format("=?{0}?{1}?", encodingMarker, tranferEncodingMarker);
      string softbreak = "?=";
      string encodedAttachmentName = encodingtoken;
    
      if (attachment.TransferEncoding == TransferEncoding.QuotedPrintable)
       encodedAttachmentName = HttpUtility.UrlEncode(displayName, Encoding.Default).Replace("+", " ").Replace("%", "=");
      else
       encodedAttachmentName = Convert.ToBase64String(Encoding.UTF8.GetBytes(displayName));
    
      encodedAttachmentName = SplitEncodedAttachmentName(encodingtoken, softbreak, maxChunkLength, encodedAttachmentName);
      attachment.Name = encodedAttachmentName;
    
      return attachment;
     }
    
     private static string SplitEncodedAttachmentName(string encodingtoken, string softbreak, int maxChunkLength, string encoded)
     {
      int splitLength = maxChunkLength - encodingtoken.Length - (softbreak.Length * 2);
      var parts = SplitByLength(encoded, splitLength);
    
      string encodedAttachmentName = encodingtoken;
    
      foreach (var part in parts)
       encodedAttachmentName += part + softbreak + encodingtoken;
    
      encodedAttachmentName = encodedAttachmentName.Remove(encodedAttachmentName.Length - encodingtoken.Length, encodingtoken.Length);
      return encodedAttachmentName;
     }
    
     private static IEnumerable<string> SplitByLength(string stringToSplit, int length)
     {
      while (stringToSplit.Length > length)
      {
       yield return stringToSplit.Substring(0, length);
       stringToSplit = stringToSplit.Substring(length);
      }
    
      if (stringToSplit.Length > 0) yield return stringToSplit;
     }
    }
    
    

     

    Und so könnte man die Helferklasse verwenden:

     

    namespace MailAttachmentEncoding
    {
     using System;
     using System.Collections.Generic;
     using System.IO;
     using System.Net.Mail;
     using System.Net.Mime;
     using System.Text;
     using System.Web;
    
     class Program
     {
      static void Main(string[] args)
      {
       string smtpServer = String.Emtpy;
       string userName = String.Empty;
       string password = String.Empty;
       string attachmentFilePath = String.Empty;
       string displayName = String.Emtpy;
    
       SmtpClient client = new SmtpClient(smtpServer);
       client.Credentials = new System.Net.NetworkCredential(userName, password);
    
       MailMessage msg = new MailMessage(fromAddress, toAddress, "Subject", "Body");
    
       Attachment attachment = 
        AttachmentHelper.CreateAttachment(attachmentFilePath, displayName, TransferEncoding.Base64);
    
       msg.Attachments.Add(attachment);
    
       client.Send(msg);
      }
     }
    }
    

    Alles, wie immer, ohne Gewähr :-) 
    Falls Du Fehler findest, würde ich mich freuen, wenn Du sie hier posten könntest.

    Gruß
    Marcel

    Thursday, July 22, 2010 1:43 PM

All replies

  • Hallo Oliver

    auch der ist nicht neu:

    http://social.msdn.microsoft.com/Forums/en-US/netfxnetcom/thread/67414337-19a6-4128-b1a0-212404cc2cb1

    wobei evtl. (ungeprüft) kann man da explizit mit zB
     attachment.NameEncoding = Encoding.UTF8
    Einfluss nehmen.

     

    Wednesday, July 21, 2010 4:15 PM
  • Hallo Oliver,

    Hier eine mögliche Lösung in Form einer kleinen Helferklasse:

    public class AttachmentHelper
    {
     public static Attachment CreateAttachment(string attachmentFile, string displayName, TransferEncoding transferEncoding)
     {
      Attachment attachment = new Attachment(attachmentFile);
      attachment.TransferEncoding = transferEncoding;
    
      string tranferEncodingMarker = String.Empty;
      string encodingMarker = String.Empty;
      int maxChunkLength = 0;
    
      switch (transferEncoding)
      {
       case TransferEncoding.Base64:
        tranferEncodingMarker = "B";
        encodingMarker = "UTF-8";
        maxChunkLength = 30;
        break;
       case TransferEncoding.QuotedPrintable:
        tranferEncodingMarker = "Q";
        encodingMarker = "ISO-8859-1";
        maxChunkLength = 76;
        break;
       default:
        throw (new ArgumentException(String.Format("The specified TransferEncoding is not supported: {0}", transferEncoding, "transferEncoding")));
      }
    
      attachment.NameEncoding = Encoding.GetEncoding(encodingMarker);
    
      string encodingtoken = String.Format("=?{0}?{1}?", encodingMarker, tranferEncodingMarker);
      string softbreak = "?=";
      string encodedAttachmentName = encodingtoken;
    
      if (attachment.TransferEncoding == TransferEncoding.QuotedPrintable)
       encodedAttachmentName = HttpUtility.UrlEncode(displayName, Encoding.Default).Replace("+", " ").Replace("%", "=");
      else
       encodedAttachmentName = Convert.ToBase64String(Encoding.UTF8.GetBytes(displayName));
    
      encodedAttachmentName = SplitEncodedAttachmentName(encodingtoken, softbreak, maxChunkLength, encodedAttachmentName);
      attachment.Name = encodedAttachmentName;
    
      return attachment;
     }
    
     private static string SplitEncodedAttachmentName(string encodingtoken, string softbreak, int maxChunkLength, string encoded)
     {
      int splitLength = maxChunkLength - encodingtoken.Length - (softbreak.Length * 2);
      var parts = SplitByLength(encoded, splitLength);
    
      string encodedAttachmentName = encodingtoken;
    
      foreach (var part in parts)
       encodedAttachmentName += part + softbreak + encodingtoken;
    
      encodedAttachmentName = encodedAttachmentName.Remove(encodedAttachmentName.Length - encodingtoken.Length, encodingtoken.Length);
      return encodedAttachmentName;
     }
    
     private static IEnumerable<string> SplitByLength(string stringToSplit, int length)
     {
      while (stringToSplit.Length > length)
      {
       yield return stringToSplit.Substring(0, length);
       stringToSplit = stringToSplit.Substring(length);
      }
    
      if (stringToSplit.Length > 0) yield return stringToSplit;
     }
    }
    
    

     

    Und so könnte man die Helferklasse verwenden:

     

    namespace MailAttachmentEncoding
    {
     using System;
     using System.Collections.Generic;
     using System.IO;
     using System.Net.Mail;
     using System.Net.Mime;
     using System.Text;
     using System.Web;
    
     class Program
     {
      static void Main(string[] args)
      {
       string smtpServer = String.Emtpy;
       string userName = String.Empty;
       string password = String.Empty;
       string attachmentFilePath = String.Empty;
       string displayName = String.Emtpy;
    
       SmtpClient client = new SmtpClient(smtpServer);
       client.Credentials = new System.Net.NetworkCredential(userName, password);
    
       MailMessage msg = new MailMessage(fromAddress, toAddress, "Subject", "Body");
    
       Attachment attachment = 
        AttachmentHelper.CreateAttachment(attachmentFilePath, displayName, TransferEncoding.Base64);
    
       msg.Attachments.Add(attachment);
    
       client.Send(msg);
      }
     }
    }
    

    Alles, wie immer, ohne Gewähr :-) 
    Falls Du Fehler findest, würde ich mich freuen, wenn Du sie hier posten könntest.

    Gruß
    Marcel

    Thursday, July 22, 2010 1:43 PM
  • Hallo zusammen,

    Bitte sieht euch auch an die Antwort von Tratcher (MSFT) von Friday, July 23, 2010 5:23 PM zum gleichen Thema.

    Grüße,

    Robert

    Monday, July 26, 2010 12:51 PM
    Moderator
  • Hallo Robert,

    Danke für den Link. Ich hätte den Code (s. unten) nicht geschrieben, wenn ich nicht gewußt hätte, dass es mit der aktuellen Implementation eben nicht klappt. Die im Link erwähnten RFC-limits beziehen sich nur auf die Länge einer Zeile. Aber man kann eben den Name des Anhangs auf mehrere Zeilen umbrechen, wie es ja die meisten E-Mail-Clients machen. Bis der Patch veröffentlicht wird, kann man sich mit meinem Code behelfen.

    RFC 2045: 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.

    http://www.ietf.org/rfc/rfc2045.txt

    Gruß
    Marcel

    • Edited by Marcel Roma Monday, July 26, 2010 2:36 PM RFC 2045-Zitat
    Monday, July 26, 2010 1:43 PM
  • Hallo Marcel,

    Gerne habe ich diesen Link gepostet und ich bedanke Dir für den Workaround mit Deinem hilfreichen Code.

    Ich werde das Problem noch verfolgen bis der Patch/Hotfix rauskommt.

    Grüße,

    Robert

    Tuesday, July 27, 2010 11:36 AM
    Moderator
  • Hallo Robert,

    Weißt Du zufällig, ob zwischenzeitlich ein Patch/Hotfix herausgegeben wurde?

    Gruß
    Marcel

    Tuesday, August 24, 2010 9:32 AM
  • Hi Marcel,

    nutze deinen Workaround seit längerem in einem Tool. Bisher kein Problem. Bis jetzt. Ein Kunde nutzt ein TicketSystem mit eigenem MailClient der diese Mails nicht interpretieren konnte.

    Der "soft" Line Break ist "?=".

    Wird ein langer Dateiname in Chunks zerlegt grenzen diese mit "...?==?..." aneinander.

    Die meisten Clients und Smtp Server können das interpretieren. Andere nicht. Laut Rfc muss da ein Whitespace rein.

    Also "?= =?".

     

    Grüße Benny

    Thursday, September 15, 2011 8:19 AM
  • Hallo Benny,

    Danke für deine Rückmeldung! Ich bin mir nicht ganz sicher, ob ich diese richtig bzw. im richtigen Kontext verstehe, also frag ich mal lieber nach:

    Meinst Du, dass man als soft line break statt "?=" die Zeichenfolge "?= " verwenden sollte, also inkl. Leerzeichen nach dem Gleichheitszeichen?

    Und gleich noch eine andere Frage: Auf welches RFC beziehst Du dich in deinem Beitrag? RFC 2045 (MIME: Format of Internet Message Bodies) sagt bezügl. Whitespaces:

    "White Space Octets with values of 9 and 32 MAY be
    represented as US-ASCII TAB (HT) and SPACE characters,
    respectively, but MUST NOT be so represented at the end
    of an encoded line.  Any TAB (HT) or SPACE characters
    on an encoded line MUST thus be followed on that line
    by a printable character.  In particular, an "=" at the
    end of an encoded line, indicating a soft line break
    (see rule #5) may follow one or more TAB (HT) or SPACE
    characters."

    Ich interpretiere dies so, dass ein Oktet mit dem Wert 32 (Leerzeichen) nie als solches (sprich: unkodiert) nach dem soft-break-Zeichen "=" in einer MIME-Nachricht (Quoted-Printable Content-Transfer-Encoding) folgen kann.

    Grüße
    Marcel

    Monday, September 19, 2011 4:47 PM