/// <summary> /// Parses a bodypart entry from the body structure and advances the /// read pointer. /// </summary> /// <param name="partNumber">The designated part specifier by which the body /// part is refered to by the server.</param> /// <param name="parenthesis">Set to true if the bodypart is enclosed /// in parenthesis.</param> /// <returns></returns> private Bodypart ParseBodypart(string partNumber, bool parenthesis = true) { Bodypart part = new Bodypart(partNumber); // Mandatory fields: // "Type"² "Subtype"² ("Attribute" "Value")² "Id"² "Description"² "Encoding"² Size³ // ² String value, but can be NIL, ³ Integer value part.Type = ContentTypeMap.fromString(reader.ReadWord()); part.Subtype = reader.ReadWord(); part.Parameters = reader.ReadList(); part.Id = reader.ReadWord(); part.Description = reader.ReadWord(); part.Encoding = ContentTransferEncodingMap.fromString(reader.ReadWord()); part.Size = reader.ReadInteger(); if (part.Type == ContentType.Text) part.Lines = reader.ReadInteger(); try { ParseOptionalFields(part, parenthesis); } catch (EndOfStringException) {} return part; }
/// <summary> /// Creates an instance of the AlternateView class used by the MailMessage class /// to store alternate views of the mail message's content. /// </summary> /// <param name="part">The MIME body part to create the alternate view from.</param> /// <param name="bytes">An array of bytes composing the content of the /// alternate view</param> /// <returns>An initialized instance of the AlternateView class</returns> private static AlternateView CreateAlternateView(Bodypart part, byte[] bytes) { MemoryStream stream = new MemoryStream(bytes); string contentType = part.Type.ToString().ToLower() + "/" + part.Subtype.ToLower(); AlternateView view = new AlternateView(stream, new System.Net.Mime.ContentType(contentType)); try { view.ContentId = ParseMessageId(part.Id); } catch {} return view; }
/// <summary> /// Creates an instance of the Attachment class used by the MailMessage class /// to store mail message attachments. /// </summary> /// <param name="part">The MIME body part to create the attachment from.</param> /// <param name="bytes">An array of bytes composing the content of the /// attachment</param> /// <returns>An initialized instance of the Attachment class</returns> private static Attachment CreateAttachment(Bodypart part, byte[] bytes) { MemoryStream stream = new MemoryStream(bytes); string name = part.Disposition.Filename ?? Path.GetRandomFileName(); Attachment attachment = new Attachment(stream, name); try { attachment.ContentId = ParseMessageId(part.Id); } catch {} string contentType = part.Type.ToString().ToLower() + "/" + part.Subtype.ToLower(); attachment.ContentType = new System.Net.Mime.ContentType(contentType); return attachment; }
/// <summary> /// Adds a body part to an existing MailMessage instance. /// </summary> /// <param name="message">Extension method for the MailMessage class.</param> /// <param name="part">The body part to add to the MailMessage instance.</param> /// <param name="content">The content of the body part.</param> internal static void AddBodypart(this MailMessage message, Bodypart part, string content) { Encoding encoding = part.Parameters.ContainsKey("Charset") ? Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII; // decode content if it was encoded byte[] bytes; switch (part.Encoding) { case ContentTransferEncoding.QuotedPrintable: bytes = encoding.GetBytes(Util.QPDecode(content, encoding)); break; case ContentTransferEncoding.Base64: bytes = Util.Base64Decode(content); break; default: bytes = Encoding.ASCII.GetBytes(content); break; } // If the MIME part contains text and the MailMessage's Body fields haven't been // initialized yet, put it there. if (message.Body == string.Empty && part.Type == ContentType.Text) { message.Body = encoding.GetString(bytes); message.BodyEncoding = encoding; message.IsBodyHtml = part.Subtype.ToLower() == "html"; return; } if (part.Disposition.Type == ContentDispositionType.Attachment) message.Attachments.Add(CreateAttachment(part, bytes)); else message.AlternateViews.Add(CreateAlternateView(part, bytes)); }
/// <summary> /// Glue method to create a bodypart from a MIMEPart instance. /// </summary> /// <param name="mimePart"> /// The MIMEPart instance to create the /// bodypart instance from. /// </param> /// <returns>An initialized instance of the Bodypart class.</returns> private static Bodypart BodypartFromMIME(MIMEPart mimePart) { var contentType = ParseMIMEField( mimePart.header["Content-Type"]); var p = new Bodypart(null); var m = Regex.Match(contentType["value"], "(.+)/(.+)"); if (m.Success) { p.Type = ContentTypeMap.fromString(m.Groups[1].Value); p.Subtype = m.Groups[2].Value; } p.Encoding = ContentTransferEncodingMap.fromString( mimePart.header["Content-Transfer-Encoding"]); p.Id = mimePart.header["Content-Id"]; foreach (var k in contentType.AllKeys) p.Parameters.Add(k, contentType[k]); p.Size = mimePart.body.Length; if (mimePart.header["Content-Disposition"] != null) { var disposition = ParseMIMEField( mimePart.header["Content-Disposition"]); p.Disposition.Type = ContentDispositionTypeMap.fromString( disposition["value"]); p.Disposition.Filename = disposition["Filename"]; foreach (var k in disposition.AllKeys) p.Disposition.Attributes.Add(k, disposition[k]); } return p; }
/// <summary> /// Adds a body part to an existing MailMessage instance. /// </summary> /// <param name="message">Extension method for the MailMessage class.</param> /// <param name="part">The body part to add to the MailMessage instance.</param> /// <param name="content">The content of the body part.</param> internal static void AddBodypart(this MailMessage message, Bodypart part, string content) { var encoding = part.Parameters.ContainsKey("Charset") ? Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII; // decode content if it was encoded byte[] bytes; try { switch (part.Encoding) { case ContentTransferEncoding.QuotedPrintable: bytes = encoding.GetBytes(Util.QPDecode(content, encoding)); break; case ContentTransferEncoding.Base64: bytes = Util.Base64Decode(content); break; default: bytes = Encoding.ASCII.GetBytes(content); break; } } catch { // If it's not a valid Base64 or quoted-printable encoded string // just leave the data as is bytes = Encoding.ASCII.GetBytes(content); } // If the MailMessage's Body fields haven't been initialized yet, put it there. // Some weird (i.e. spam) mails like to omit content-types so don't check for // that here and just assume it's text. if (message.Body == string.Empty) { message.Body = encoding.GetString(bytes); message.BodyEncoding = encoding; message.IsBodyHtml = part.Subtype.ToLower() == "html"; return; } if (part.Disposition.Type == ContentDispositionType.Attachment) message.Attachments.Add(CreateAttachment(part, bytes)); else message.AlternateViews.Add(CreateAlternateView(part, bytes)); }
/// <summary> /// Creates an instance of the Attachment class used by the MailMessage class to store mail /// message attachments. /// </summary> /// <param name="part">The MIME body part to create the attachment from.</param> /// <param name="bytes">An array of bytes composing the content of the attachment.</param> /// <returns>An initialized instance of the Attachment class.</returns> static Attachment CreateAttachment(Bodypart part, byte[] bytes) { MemoryStream stream = new MemoryStream(bytes); string name = part.Disposition.Filename; // Many MUAs put the file name in the name parameter of the content-type header instead of // the filename parameter of the content-disposition header. if (String.IsNullOrEmpty(name) && part.Parameters.ContainsKey("name")) name = part.Parameters["name"]; if (String.IsNullOrEmpty(name)) name = Path.GetRandomFileName(); Attachment attachment = new Attachment(stream, name); try { attachment.ContentId = ParseMessageId(part.Id); } catch { } try { attachment.ContentType = new System.Net.Mime.ContentType( part.Type.ToString().ToLower() + "/" + part.Subtype.ToLower()); } catch { attachment.ContentType = new System.Net.Mime.ContentType(); } // Workaround: filename from Attachment constructor is ignored with Mono. attachment.Name = name; attachment.ContentDisposition.FileName = name; return attachment; }
/// <summary> /// Adds a body part to an existing MailMessage instance. /// </summary> /// <param name="message">Extension method for the MailMessage class.</param> /// <param name="part">The body part to add to the MailMessage instance.</param> /// <param name="content">The content of the body part.</param> internal static void AddBodypart(this MailMessage message, Bodypart part, string content) { Encoding encoding = part.Parameters.ContainsKey("Charset") ? Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII; // Decode the content if it is encoded. byte[] bytes; try { switch (part.Encoding) { case ContentTransferEncoding.QuotedPrintable: bytes = encoding.GetBytes(Util.QPDecode(content, encoding)); break; case ContentTransferEncoding.Base64: bytes = Util.Base64Decode(content); break; default: bytes = Encoding.ASCII.GetBytes(content); break; } } catch { // If it's not a valid Base64 or quoted-printable encoded string just leave the data as is. bytes = Encoding.ASCII.GetBytes(content); } // If the part has a name it most likely is an attachment and it should go into the // Attachments collection. bool hasName = part.Parameters.ContainsKey("name"); // If the MailMessage's Body fields haven't been initialized yet, put it there. Some weird // (i.e. spam) mails like to omit content-types so we don't check for that here and just // assume it's text. if (String.IsNullOrEmpty(message.Body) && part.Disposition.Type != ContentDispositionType.Attachment) { message.Body = encoding.GetString(bytes); message.BodyEncoding = encoding; message.IsBodyHtml = part.Subtype.ToLower() == "html"; return; } // Check for alternative view. string ContentType = ParseMIMEField(message.Headers["Content-Type"])["value"]; bool preferAlternative = string.Compare(ContentType, "multipart/alternative", true) == 0; // Many attachments are missing the disposition-type. If it's not defined as alternative // and it has a name attribute, assume it is Attachment rather than an AlternateView. if (part.Disposition.Type == ContentDispositionType.Attachment || (part.Disposition.Type == ContentDispositionType.Unknown && preferAlternative == false && hasName)) message.Attachments.Add(CreateAttachment(part, bytes)); else message.AlternateViews.Add(CreateAlternateView(part, bytes)); }
/// <summary> /// Adds a body part to an existing MailMessage instance. /// </summary> /// <param name="message">Extension method for the MailMessage class.</param> /// <param name="part">The body part to add to the MailMessage instance.</param> /// <param name="content">The content of the body part.</param> internal static void AddBodypart(this MailMessage message, Bodypart part, string content, bool getHtml = false) { Encoding encoding = part.Parameters.ContainsKey("Charset") ? Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII; // Decode the content if it is encoded. byte[] bytes; try { switch (part.Encoding) { case ContentTransferEncoding.QuotedPrintable: bytes = encoding.GetBytes(Util.QPDecode(content, encoding)); break; case ContentTransferEncoding.Base64: bytes = Util.Base64Decode(content); break; default: bytes = Encoding.ASCII.GetBytes(content); break; } } catch { // If it's not a valid Base64 or quoted-printable encoded string just leave the data as is. bytes = Encoding.ASCII.GetBytes(content); } // If the part has a name it most likely is an attachment and it should go into the // Attachments collection. bool hasName = part.Parameters.ContainsKey("name"); // If the MailMessage's Body fields haven't been initialized yet, put it there. Some weird // (i.e. spam) mails like to omit content-types so we don't check for that here and just // assume it's text. if (part.Disposition.Type != ContentDispositionType.Attachment) { message.BodyEncoding = encoding; message.IsBodyHtml = part.Subtype.ToLower() == "html"; if (!string.IsNullOrEmpty(message.Body)) { if (getHtml && message.IsBodyHtml) { message.Body = encoding.GetString(bytes); } else { message.Body += (message.IsBodyHtml ? ("[[--html--]]") : "") + encoding.GetString(bytes); } return; } message.Body = encoding.GetString(bytes); return; } // Check for alternative view. string ContentType = ParseMIMEField(message.Headers["Content-Type"])["value"]; bool preferAlternative = string.Compare(ContentType, "multipart/alternative", true) == 0; // Many attachments are missing the disposition-type. If it's not defined as alternative // and it has a name attribute, assume it is Attachment rather than an AlternateView. if (part.Disposition.Type == ContentDispositionType.Attachment || (part.Disposition.Type == ContentDispositionType.Unknown && preferAlternative == false && hasName)) { message.Attachments.Add(CreateAttachment(part, bytes)); } else { message.AlternateViews.Add(CreateAlternateView(part, bytes)); } }
/// <summary> /// Parses the mandatory extra fields that are present if the bodypart is /// of type message/rfc822 (see RFC 3501, p. 75). /// </summary> /// <param name="part">The bodypart instance the parsed fields will be /// added to.</param> private void ParseMessage822Fields(Bodypart part) { // We just skip over most of this extra information as it is useless // to us. // Mandatory fields: // "Envelope" "Bodystructure" "Lines" SkipParenthesizedExpression(); SkipParenthesizedExpression(); part.Lines = reader.ReadInteger(); }
/// <summary> /// Parses the optional fields of a bodypart entry from the body structure /// and advances the read pointer. /// </summary> /// <param name="part">The bodypart instance the parsed fields will be /// added to.</param> /// <param name="parenthesis">Set to true if the bodypart entry is enclosed /// in parenthesis.</param> private void ParseOptionalFields(Bodypart part, bool parenthesis = true) { // Optional fields: // "Md5"² ("Disposition" ("Attribute" "Value"))² "Language"² "Location"² if (parenthesis && reader.Peek(true) == ')') { reader.Read(); return; } part.Md5 = reader.ReadWord(); if (parenthesis && reader.Peek(true) == ')') { reader.Read(); return; } part.Disposition = reader.ReadDisposition(); if (parenthesis && reader.Peek(true) == ')') { reader.Read(); return; } part.Language = reader.ReadWord(); if (parenthesis && reader.Peek(true) == ')') { reader.Read(); return; } part.Location = reader.ReadWord(); if (parenthesis) reader.SkipUntil(')'); }
/// <summary> /// Adds a body part to an existing MailMessage instance. /// </summary> /// <param name="message">Extension method for the MailMessage class.</param> /// <param name="part">The body part to add to the MailMessage instance.</param> /// <param name="content">The content of the body part.</param> internal static void AddBodypart(this MailMessage message, Bodypart part, string content) { Encoding encoding = part.Parameters.ContainsKey("Charset") ? Util.GetEncoding(part.Parameters["Charset"]) : Encoding.ASCII; // Decode the content if it is encoded. byte[] bytes; try { switch (part.Encoding) { case ContentTransferEncoding.QuotedPrintable: bytes = encoding.GetBytes(Util.QPDecode(content, encoding)); break; case ContentTransferEncoding.Base64: bytes = Util.Base64Decode(content); break; default: bytes = Encoding.ASCII.GetBytes(content); break; } } catch { // If it's not a valid Base64 or quoted-printable encoded string // just leave the data as is bytes = Encoding.ASCII.GetBytes(content); } // Always a attachment if we have name? Because Inline != AlternateView ? bool haveName = part.Parameters.ContainsKey("name"); // If the MailMessage's Body fields haven't been initialized yet, put it there. // Some weird (i.e. spam) mails like to omit content-types so we don't check for // that here and just assume it's text. if (String.IsNullOrEmpty(message.Body) && part.Disposition.Type != ContentDispositionType.Attachment) { message.Body = encoding.GetString(bytes); message.BodyEncoding = encoding; message.IsBodyHtml = part.Subtype.ToLower() == "html"; return; } string ContentType = ParseMIMEField(message.Headers["Content-Type"])["value"]; bool preferAlternative = string.Compare(ContentType, "multipart/alternative", true) == 0; // handle "multipart/mixed" ? // Many attachments are missing Type, If we have a filename and not multipart/alternative then use attachment. if (part.Disposition.Type == ContentDispositionType.Attachment || (part.Disposition.Type == ContentDispositionType.Unknown && !preferAlternative && haveName)) message.Attachments.Add(CreateAttachment(part, bytes)); else message.AlternateViews.Add(CreateAlternateView(part, bytes)); }