/// <summary> /// Parses the ms-tnef stream from the provided <see cref="System.IO.Stream" />. /// </summary> /// <param name="path">A <see cref="System.String" /> specifying the path on which to save the attachments found. Specify the <b>null</b> reference to save them in memory as <see cref="System.IO.MemoryStream" /> instances instead.</param> /// <returns><b>true</b> if parsing is successful. <b>false</b> otherwise.</returns> public bool Parse(String path) { if (_reader == null || !_reader.BaseStream.CanRead) return false; int sig = ReadInt(); if (sig != TnefSignature) { return false; } bool error = false; Attachments = new List<SharpAttachment>(); ushort key = ReadUInt16(); System.Text.Encoding enc = SharpMimeHeader.EncodingDefault; SharpAttachment attachment_cur = null; for (Byte cur = ReadByte(); cur != Byte.MinValue; cur = ReadByte()) { TnefLvlType lvl = (TnefLvlType)SharpMimeTools.ParseEnum(typeof(TnefLvlType), cur, TnefLvlType.Unknown); // Type int type = ReadInt(); // Size int size = ReadInt(); // Attibute name and type TnefAttribute att_n = (TnefAttribute)SharpMimeTools.ParseEnum(typeof(TnefAttribute), (ushort)((type << 16) >> 16), TnefAttribute.Unknown); TnefDataType att_t = (TnefDataType)SharpMimeTools.ParseEnum(typeof(TnefDataType), (ushort)(type >> 16), TnefDataType.Unknown); if (lvl == TnefLvlType.Unknown || att_n == TnefAttribute.Unknown || att_t == TnefDataType.Unknown) { error = true; break; } // Read data Byte[] buffer = ReadBytes(size); // Read checkSum ushort checksum = ReadUInt16(); // Verify checksum if (!VerifyChecksum(buffer, checksum)) { error = true; break; } size = buffer.Length; if (lvl == TnefLvlType.Message) { // Text body if (att_n == TnefAttribute.Body) { if (att_t == TnefDataType.atpString) { _body = enc.GetString(buffer, 0, size); } // Message mapi props (html body, rtf body, ...) } else if (att_n == TnefAttribute.MapiProps) { ReadMapi(buffer); // Stream Codepage } else if (att_n == TnefAttribute.OEMCodepage) { uint codepage1 = (uint)(buffer[0] + (buffer[1] << 8) + (buffer[2] << 16) + (buffer[3] << 24)); if (codepage1 > 0) { try { enc = System.Text.Encoding.GetEncoding((int)codepage1); } catch (Exception e) { Trace.Fail(e.Message, e.StackTrace); } } } } else if (lvl == TnefLvlType.Attachment) { // Attachment start if (att_n == TnefAttribute.AttachRendData) { String name = String.Concat("generated_", key, "_", (Attachments.Count + 1), ".binary"); if (path == null) { attachment_cur = new SharpAttachment(); } else { attachment_cur = new SharpAttachment(new FileInfo(Path.Combine(path, name))); } // Attachment name attachment_cur.Name = name; } else if (att_n == TnefAttribute.AttachTitle) { if (attachment_cur != null && att_t == TnefDataType.atpString && buffer != null) { // NULL terminated string if (buffer[size - 1] == '\0') { size--; } if (size > 0) { String name = enc.GetString(buffer, 0, size); if (name.Length > 0) { attachment_cur.Name = name; // Content already saved, so we have to rename if (attachment_cur.SavedFile != null && attachment_cur.SavedFile.Exists) { try { attachment_cur.SavedFile.MoveTo(Path.Combine(path, attachment_cur.Name)); } catch (Exception e) { Trace.Fail(e.Message, e.StackTrace); } } } } } // Modification and creation date } else if (att_n == TnefAttribute.AttachModifyDate || att_n == TnefAttribute.AttachCreateDate) { if (attachment_cur != null && att_t == TnefDataType.atpDate && buffer != null && size == 14) { int pos = 0; DateTime date = new DateTime((buffer[pos++] + (buffer[pos++] << 8)), (buffer[pos++] + (buffer[pos++] << 8)), (buffer[pos++] + (buffer[pos++] << 8)), (buffer[pos++] + (buffer[pos++] << 8)), (buffer[pos++] + (buffer[pos++] << 8)), (buffer[pos++] + (buffer[pos++] << 8))); if (att_n == TnefAttribute.AttachModifyDate) { attachment_cur.LastWriteTime = date; } else if (att_n == TnefAttribute.AttachCreateDate) { attachment_cur.CreationTime = date; } } // Attachment data } else if (att_n == TnefAttribute.AttachData) { if (attachment_cur != null && att_t == TnefDataType.atpByte && buffer != null) { if (attachment_cur.SavedFile != null) { FileStream stream = null; try { stream = attachment_cur.SavedFile.OpenWrite(); } catch (Exception e) { error = true; break; } stream.Write(buffer, 0, size); stream.Flush(); attachment_cur.Size = stream.Length; stream.Close(); stream = null; attachment_cur.SavedFile.Refresh(); // Is name has changed, we have to rename if (attachment_cur.SavedFile.Name != attachment_cur.Name) try { attachment_cur.SavedFile.MoveTo(Path.Combine(path, attachment_cur.Name)); } catch (Exception e) { Trace.Fail(e.Message, e.StackTrace); } } else { attachment_cur.Stream.Write(buffer, 0, size); attachment_cur.Stream.Flush(); if (attachment_cur.Stream.CanSeek) attachment_cur.Stream.Seek(0, SeekOrigin.Begin); attachment_cur.Size = attachment_cur.Stream.Length; } Attachments.Add(attachment_cur); } // Attachment mapi props } else if (att_n == TnefAttribute.Attachment) { ReadMapi(buffer); } } } if (Attachments.Count == 0) Attachments = null; return !error; }
private String ReplaceUrlTokens(String url, SharpAttachment attachment) { if (url == null || url.Length == 0 || url.IndexOf('[') == -1 || url.IndexOf(']') == -1) return url; if (url.IndexOf("[MessageID]") != -1) { url = url.Replace("[MessageID]", HttpUtility.UrlEncode(SharpMimeTools.Rfc2392Url(MessageID))); } if (attachment != null && attachment.ContentID != null) { if (url.IndexOf("[ContentID]") != -1) { url = url.Replace("[ContentID]", HttpUtility.UrlEncode(SharpMimeTools.Rfc2392Url(attachment.ContentID))); } if (url.IndexOf("[Name]") != -1) { if (attachment.SavedFile != null) { url = url.Replace("[Name]", HttpUtility.UrlEncode(attachment.SavedFile.Name)); } else { url = url.Replace("[Name]", HttpUtility.UrlEncode(attachment.Name)); } } } return url; }
private void UuDecode(String path) { if (_body.Length == 0 || _body.IndexOf("begin ") == -1 || _body.IndexOf("end") == -1) return; StringBuilder sb = new StringBuilder(); using (StringReader reader = new StringReader(_body)) { Stream stream = null; SharpAttachment attachment = null; for (String line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (stream == null) { // Found the start point of uuencoded content if (line.Length > 10 && line[0] == 'b' && line[1] == 'e' && line[2] == 'g' && line[3] == 'i' && line[4] == 'n' && line[5] == ' ' && line[9] == ' ') { String name = Path.GetFileName(line.Substring(10)); // In-Memory decoding if (path == null) { attachment = new SharpAttachment(); stream = attachment.Stream; // Filesystem decoding } else { attachment = new SharpAttachment(new FileInfo(Path.Combine(path, name))); stream = attachment.SavedFile.OpenWrite(); } attachment.Name = name; // Not uuencoded line, so add it to new body } else { sb.Append(line); sb.Append(Environment.NewLine); } } else { // Content finished if (line.Length == 3 && line == "end") { stream.Flush(); if (stream.Length > 0) { attachment.Size = stream.Length; Attachments.Add(attachment); } // When decoding to a file, close the stream. if (attachment.SavedFile != null || stream.Length == 0) { stream.Close(); } attachment = null; stream = null; // Decode and write uuencoded line } else { SharpMimeTools.UuDecodeLine(line, stream); } } } if (stream != null && stream.CanRead) { stream.Close(); stream = null; } } _body = sb.ToString(); sb = null; }
private void ParseMessage(SharpMimeMessage part, MimeTopLevelMediaType types, bool html, SharpDecodeOptions options, String preferredtextsubtype, String path) { if ((types & part.Header.TopLevelMediaType) != part.Header.TopLevelMediaType) { return; } switch (part.Header.TopLevelMediaType) { case MimeTopLevelMediaType.multipart: case MimeTopLevelMediaType.message: // TODO: allow other subtypes of "message" if (part.Header.TopLevelMediaType.Equals(MimeTopLevelMediaType.message)) { // Only message/rfc822 allowed, other subtypes ignored if (part.Header.SubType == "rfc822") { // If NotRecursiveRfc822 option is set, handle part as an attachment if ((options & SharpDecodeOptions.NotRecursiveRfc822) == SharpDecodeOptions.NotRecursiveRfc822) { goto case anmar.SharpMimeTools.MimeTopLevelMediaType.application; } } else { break; } } if (part.Header.SubType.Equals("alternative") && part.PartsCount > 0) { SharpMimeMessage altenative = null; // Get the first mime part of the alternatives that has a accepted Mime-Type for (int i = part.PartsCount; i > 0; i--) { SharpMimeMessage item = part.GetPart(i - 1); if ((types & part.Header.TopLevelMediaType) != part.Header.TopLevelMediaType || (!html && item.Header.TopLevelMediaType.Equals(MimeTopLevelMediaType.text) && item.Header.SubType.Equals("html")) ) { continue; } // First allowed one. if (altenative == null) { altenative = item; // We don't have to select body part based on subtype if not asked for, or not a text one // or it's already the preferred one if (preferredtextsubtype == null || item.Header.TopLevelMediaType != MimeTopLevelMediaType.text || (preferredtextsubtype != null && item.Header.SubType == preferredtextsubtype)) { break; } // This one is preferred over the last part } else if (preferredtextsubtype != null && item.Header.TopLevelMediaType == MimeTopLevelMediaType.text && item.Header.SubType == preferredtextsubtype) { altenative = item; break; } } if (altenative != null) { // If message body as html is allowed and part has a Content-ID field // add an anchor to mark this body part if (html && part.Header.Contains("Content-ID") && (options & SharpDecodeOptions.NamedAnchors) == SharpDecodeOptions.NamedAnchors) { // There is a previous text body, so enclose it in <pre> if (!HasHtmlBody && _body.Length > 0) { _body = String.Concat("<pre>", HttpUtility.HtmlEncode(_body), "</pre>"); HasHtmlBody = true; } // Add the anchor _body = String.Concat(_body, "<a name=\"", SharpMimeTools.Rfc2392Url(MessageID), "_", SharpMimeTools.Rfc2392Url(part.Header.ContentID), "\"></a>"); } ParseMessage(altenative, types, html, options, preferredtextsubtype, path); } // TODO: Take into account each subtype of "multipart" and "message" } else if (part.PartsCount > 0) { foreach (SharpMimeMessage item in part) { ParseMessage(item, types, html, options, preferredtextsubtype, path); } } break; case MimeTopLevelMediaType.text: if ((part.Disposition == null || !part.Disposition.Equals("attachment")) && (part.Header.SubType.Equals("plain") || part.Header.SubType.Equals("html"))) { bool body_was_html = HasHtmlBody; // HTML content not allowed if (part.Header.SubType.Equals("html")) { if (!html) break; else HasHtmlBody = true; } if (html && part.Header.Contains("Content-ID") && (options & SharpDecodeOptions.NamedAnchors) == SharpDecodeOptions.NamedAnchors) { HasHtmlBody = true; } if (HasHtmlBody && !body_was_html && !String.IsNullOrEmpty(_body)) { _body = String.Concat("<pre>", HttpUtility.HtmlEncode(_body), "</pre>"); } // If message body is html and this part has a Content-ID field // add an anchor to mark this body part if (HasHtmlBody && part.Header.Contains("Content-ID") && (options & SharpDecodeOptions.NamedAnchors) == SharpDecodeOptions.NamedAnchors) { _body = String.Concat(_body, "<a name=\"", SharpMimeTools.Rfc2392Url(MessageID), "_", SharpMimeTools.Rfc2392Url(part.Header.ContentID), "\"></a>"); } if (HasHtmlBody && part.Header.SubType.Equals("plain")) { _body = String.Concat(_body, "<pre>", HttpUtility.HtmlEncode(part.BodyDecoded), "</pre>"); } else _body = String.Concat(_body, part.BodyDecoded); } else { if ((types & MimeTopLevelMediaType.application) != MimeTopLevelMediaType.application) { return; } goto case anmar.SharpMimeTools.MimeTopLevelMediaType.application; } break; case MimeTopLevelMediaType.application: case MimeTopLevelMediaType.audio: case MimeTopLevelMediaType.image: case MimeTopLevelMediaType.video: // Attachments not allowed. if ((options & SharpDecodeOptions.AllowAttachments) != SharpDecodeOptions.AllowAttachments) break; SharpAttachment attachment = null; // Save to a file if (path != null) { FileInfo file = part.DumpBody(path, true); if (file != null) { attachment = new SharpAttachment(file); attachment.Name = file.Name; attachment.Size = file.Length; } // Save to a stream } else { using (MemoryStream stream = new MemoryStream()) { if (part.DumpBody(stream)) { if (stream != null && stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); attachment = new SharpAttachment(stream); if (part.Name != null) attachment.Name = part.Name; else attachment.Name = String.Concat("generated_", part.GetHashCode(), ".", part.Header.SubType); attachment.Size = stream.Length; } } } if (attachment != null && part.Header.SubType == "ms-tnef" && (options & SharpDecodeOptions.DecodeTnef) == SharpDecodeOptions.DecodeTnef) { // Try getting attachments form a tnef stream Stream stream = attachment.Stream; SharpTnefMessage tnef = new SharpTnefMessage(stream); if (tnef.Parse(path)) { if (tnef.Attachments != null) { Attachments.AddRange(tnef.Attachments); } attachment.Close(); // Delete the raw tnef file if (attachment.SavedFile != null) { if (stream != null && stream.CanRead) { stream.Close(); stream = null; } attachment.SavedFile.Delete(); } attachment = null; tnef.Close(); } else { // The read-only stream is no longer needed and locks the file if (attachment.SavedFile != null && stream != null && stream.CanRead) { stream.Close(); stream = null; } } stream = null; tnef = null; } if (attachment != null) { if (part.Disposition != null && part.Disposition == "inline") { attachment.Inline = true; } attachment.MimeTopLevelMediaType = part.Header.TopLevelMediaType; attachment.MimeMediaSubType = part.Header.SubType; // Store attachment's CreationTime if (part.Header.ContentDispositionParameters.ContainsKey("creation-date")) attachment.CreationTime = SharpMimeTools.parseDate(part.Header.ContentDispositionParameters["creation-date"]); // Store attachment's LastWriteTime if (part.Header.ContentDispositionParameters.ContainsKey("modification-date")) attachment.LastWriteTime = SharpMimeTools.parseDate(part.Header.ContentDispositionParameters["modification-date"]); if (part.Header.Contains("Content-ID")) attachment.ContentID = part.Header.ContentID; Attachments.Add(attachment); } break; default: break; } }