123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Net.Mime;
- namespace Ant.Service.Utilities
- {
- /// <summary>
- /// This class is responsible for parsing a string array of lines
- /// containing a MIME message.
- /// </summary>
- public class MimeReader
- {
- private static readonly char[] HeaderWhitespaceChars = new char[] { ' ', '\t' };
- private Queue<string> _lines;
- /// <summary>
- /// Gets the lines.
- /// </summary>
- /// <value>The lines.</value>
- public Queue<string> Lines
- {
- get
- {
- return _lines;
- }
- }
- private MimeEntity _entity;
- /// <summary>
- /// Initializes a new instance of the <see cref="MimeReader"/> class.
- /// </summary>
- private MimeReader()
- {
- _entity = new MimeEntity();
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="MimeReader"/> class.
- /// </summary>
- /// <param name="entity">The entity.</param>
- /// <param name="lines">The lines.</param>
- private MimeReader(MimeEntity entity, Queue<string> lines)
- : this()
- {
- if (entity == null)
- {
- throw new ArgumentNullException("entity");
- }
- if (lines == null)
- {
- throw new ArgumentNullException("lines");
- }
- _lines = lines;
- _entity = new MimeEntity(entity);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="MimeReader"/> class.
- /// </summary>
- /// <param name="lines">The lines.</param>
- public MimeReader(string[] lines)
- : this()
- {
- if (lines == null)
- {
- throw new ArgumentNullException("lines");
- }
- _lines = new Queue<string>(lines);
- }
- /// <summary>
- /// Parse headers into _entity.Headers NameValueCollection.
- /// </summary>
- private int ParseHeaders()
- {
- string lastHeader = string.Empty;
- string line = string.Empty;
- // the first empty line is the end of the headers.
- while (_lines.Count > 0 && !string.IsNullOrEmpty(_lines.Peek()))
- {
- line = _lines.Dequeue();
- //if a header line starts with a space or tab then it is a continuation of the
- //previous line.
- if (line.StartsWith(" ") || line.StartsWith(Convert.ToString('\t')))
- {
- _entity.Headers[lastHeader] = string.Concat(_entity.Headers[lastHeader], line);
- continue;
- }
- int separatorIndex = line.IndexOf(':');
- if (separatorIndex < 0)
- {
- System.Diagnostics.Debug.WriteLine("Invalid header:{0}", line);
- continue;
- } //This is an invalid header field. Ignore this line.
- string headerName = line.Substring(0, separatorIndex);
- string headerValue = line.Substring(separatorIndex + 1).Trim(HeaderWhitespaceChars);
- _entity.Headers.Add(headerName.ToLower(), headerValue);
- lastHeader = headerName;
- }
- if (_lines.Count > 0)
- {
- _lines.Dequeue();
- } //remove closing header CRLF.
- return _entity.Headers.Count;
- }
- /// <summary>
- /// Processes mime specific headers.
- /// </summary>
- /// <returns>A mime entity with mime specific headers parsed.</returns>
- private void ProcessHeaders()
- {
- foreach (string key in _entity.Headers.AllKeys)
- {
- switch (key)
- {
- case "content-description":
- _entity.ContentDescription = _entity.Headers[key];
- break;
- case "content-disposition":
- _entity.ContentDisposition = new ContentDisposition(_entity.Headers[key]);
- break;
- case "content-id":
- _entity.ContentId = _entity.Headers[key];
- break;
- case "content-transfer-encoding":
- _entity.TransferEncoding = _entity.Headers[key];
- _entity.ContentTransferEncoding = MimeReader.GetTransferEncoding(_entity.Headers[key]);
- break;
- case "content-type":
- _entity.SetContentType(MimeReader.GetContentType(_entity.Headers[key]));
- break;
- case "mime-version":
- _entity.MimeVersion = _entity.Headers[key];
- break;
- }
- }
- }
- /// <summary>
- /// Creates the MIME entity.
- /// </summary>
- /// <returns>A mime entity containing 0 or more children representing the mime message.</returns>
- public MimeEntity CreateMimeEntity()
- {
- try
- {
- ParseHeaders();
- ProcessHeaders();
- ParseBody();
- SetDecodedContentStream();
- return _entity;
- }
- catch
- {
- return null;
- }
- }
- /// <summary>
- /// Sets the decoded content stream by decoding the EncodedMessage
- /// and writing it to the entity content stream.
- /// </summary>
- /// <param name="entity">The entity containing the encoded message.</param>
- private void SetDecodedContentStream()
- {
- switch (_entity.ContentTransferEncoding)
- {
- case System.Net.Mime.TransferEncoding.Base64:
- _entity.Content = new MemoryStream(Convert.FromBase64String(_entity.EncodedMessage.ToString()), false);
- break;
- case System.Net.Mime.TransferEncoding.QuotedPrintable:
- _entity.Content = new MemoryStream(GetBytes(QuotedPrintableEncoding.Decode(_entity.EncodedMessage.ToString())), false);
- break;
- case System.Net.Mime.TransferEncoding.SevenBit:
- default:
- _entity.Content = new MemoryStream(GetBytes(_entity.EncodedMessage.ToString()), false);
- break;
- }
- }
- /// <summary>
- /// Gets a byte[] of content for the provided string.
- /// </summary>
- /// <param name="decodedContent">Content.</param>
- /// <returns>A byte[] containing content.</returns>
- private byte[] GetBytes(string content)
- {
- using (MemoryStream stream = new MemoryStream())
- {
- using (StreamWriter writer = new StreamWriter(stream))
- {
- writer.Write(content);
- }
- return stream.ToArray();
- }
- }
- /// <summary>
- /// Parses the body.
- /// </summary>
- private void ParseBody()
- {
- if (_entity.HasBoundary)
- {
- while (_lines.Count > 0
- && !string.Equals(_lines.Peek(), _entity.EndBoundary))
- {
- /*Check to verify the current line is not the same as the parent starting boundary.
- If it is the same as the parent starting boundary this indicates existence of a
- new child entity. Return and process the next child.*/
- if (_entity.Parent != null
- && string.Equals(_entity.Parent.StartBoundary, _lines.Peek()))
- {
- return;
- }
- if (string.Equals(_lines.Peek(), _entity.StartBoundary))
- {
- AddChildEntity(_entity, _lines);
- } //Parse a new child mime part.
- else if (string.Equals(_entity.ContentType.MediaType, MediaTypes.MessageRfc822, StringComparison.InvariantCultureIgnoreCase)
- && string.Equals(_entity.ContentDisposition.DispositionType, DispositionTypeNames.Attachment, StringComparison.InvariantCultureIgnoreCase))
- {
- /*If the content type is message/rfc822 the stop condition to parse headers has already been encountered.
- But, a content type of message/rfc822 would have the message headers immediately following the mime
- headers so we need to parse the headers for the attached message now. This is done by creating
- a new child entity.*/
- AddChildEntity(_entity, _lines);
- break;
- }
- else
- {
- _entity.EncodedMessage.Append(string.Concat(_lines.Dequeue(), Pop3Commands.Crlf));
- } //Append the message content.
- }
- } //Parse a multipart message.
- else
- {
- while (_lines.Count > 0)
- {
- _entity.EncodedMessage.Append(string.Concat(_lines.Dequeue(), Pop3Commands.Crlf));
- }
- } //Parse a single part message.
- }
- /// <summary>
- /// Adds the child entity.
- /// </summary>
- /// <param name="entity">The entity.</param>
- private void AddChildEntity(MimeEntity entity, Queue<string> lines)
- {
- /*if (entity == null)
- {
- return;
- }
- if (lines == null)
- {
- return;
- }*/
- MimeReader reader = new MimeReader(entity, lines);
- entity.Children.Add(reader.CreateMimeEntity());
- }
- /// <summary>
- /// Gets the type of the content.
- /// </summary>
- /// <param name="contentType">Type of the content.</param>
- /// <returns></returns>
- public static ContentType GetContentType(string contentType)
- {
- if (string.IsNullOrEmpty(contentType))
- {
- contentType = "text/plain; charset=us-ascii";
- }
- return new ContentType(contentType);
- }
- /// <summary>
- /// Gets the type of the media.
- /// </summary>
- /// <param name="mediaType">Type of the media.</param>
- /// <returns></returns>
- public static string GetMediaType(string mediaType)
- {
- if (string.IsNullOrEmpty(mediaType))
- {
- return "text/plain";
- }
- return mediaType.Trim();
- }
- /// <summary>
- /// Gets the type of the media main.
- /// </summary>
- /// <param name="mediaType">Type of the media.</param>
- /// <returns></returns>
- public static string GetMediaMainType(string mediaType)
- {
- int separatorIndex = mediaType.IndexOf('/');
- if (separatorIndex < 0)
- {
- return mediaType;
- }
- else
- {
- return mediaType.Substring(0, separatorIndex);
- }
- }
- /// <summary>
- /// Gets the type of the media sub.
- /// </summary>
- /// <param name="mediaType">Type of the media.</param>
- /// <returns></returns>
- public static string GetMediaSubType(string mediaType)
- {
- int separatorIndex = mediaType.IndexOf('/');
- if (separatorIndex < 0)
- {
- if (mediaType.Equals("text"))
- {
- return "plain";
- }
- return string.Empty;
- }
- else
- {
- if (mediaType.Length > separatorIndex)
- {
- return mediaType.Substring(separatorIndex + 1);
- }
- else
- {
- string mainType = GetMediaMainType(mediaType);
- if (mainType.Equals("text"))
- {
- return "plain";
- }
- return string.Empty;
- }
- }
- }
- /// <summary>
- /// Gets the transfer encoding.
- /// </summary>
- /// <param name="transferEncoding">The transfer encoding.</param>
- /// <returns></returns>
- /// <remarks>
- /// The transfer encoding determination follows the same rules as
- /// Peter Huber's article w/ the exception of not throwing exceptions
- /// when binary is provided as a transferEncoding. Instead it is left
- /// to the calling code to check for binary.
- /// </remarks>
- public static TransferEncoding GetTransferEncoding(string transferEncoding)
- {
- switch (transferEncoding.Trim().ToLowerInvariant())
- {
- case "7bit":
- case "8bit":
- return System.Net.Mime.TransferEncoding.SevenBit;
- case "quoted-printable":
- return System.Net.Mime.TransferEncoding.QuotedPrintable;
- case "base64":
- return System.Net.Mime.TransferEncoding.Base64;
- case "binary":
- default:
- return System.Net.Mime.TransferEncoding.Unknown;
- }
- }
- }
- }
|