internal MimeEntityConstructorArgs (ParserOptions options, ContentType ctype, IEnumerable<Header> headers, bool toplevel) { ParserOptions = options; IsTopLevel = toplevel; ContentType = ctype; Headers = headers; }
internal MimeMessage (ParserOptions options, IEnumerable<Header> headers) { addresses = new Dictionary<string, InternetAddressList> (StringComparer.OrdinalIgnoreCase); Headers = new HeaderList (options); // initialize our address lists foreach (var name in StandardAddressHeaders) { var list = new InternetAddressList (); list.Changed += InternetAddressListChanged; addresses.Add (name, list); } references = new MessageIdList (); references.Changed += ReferencesChanged; inreplyto = null; Headers.Changed += HeadersChanged; // add all of our message headers... foreach (var header in headers) { if (header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) continue; Headers.Add (header); } }
static ParserOptions() { Default = new ParserOptions (); Default.EnableRfc2047Workarounds = true; Default.RespectContentLength = false; Default.CharsetEncoding = Encoding.Default; }
/// <summary> /// Initializes a new instance of the <see cref="MimeKit.Header"/> class. /// </summary> /// <remarks> /// Creates a new message or entity header for the specified field and /// value pair. The encoding is used to determine which charset to use /// when encoding the value according to the rules of rfc2047. /// </remarks> /// <param name="charset">The charset that should be used to encode the /// header value.</param> /// <param name="id">The header identifier.</param> /// <param name="value">The value of the header.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="charset"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="value"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="id"/> is not a valid <see cref="HeaderId"/>. /// </exception> public Header (Encoding charset, HeaderId id, string value) { if (charset == null) throw new ArgumentNullException ("charset"); if (id == HeaderId.Unknown) throw new ArgumentOutOfRangeException ("id"); if (value == null) throw new ArgumentNullException ("value"); Options = ParserOptions.Default.Clone (); Field = id.ToHeaderName (); Id = id; SetValue (charset, value); }
/// <summary> /// Initializes a new instance of the <see cref="MimeKit.Header"/> class. /// </summary> /// <remarks> /// Creates a new message or entity header for the specified field and /// value pair. The encoding is used to determine which charset to use /// when encoding the value according to the rules of rfc2047. /// </remarks> /// <param name="encoding">The character encoding that should be used to /// encode the header value.</param> /// <param name="id">The header identifier.</param> /// <param name="value">The value of the header.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="encoding"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="value"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="id"/> is not a valid <see cref="HeaderId"/>. /// </exception> public Header (Encoding encoding, HeaderId id, string value) { if (encoding == null) throw new ArgumentNullException ("encoding"); if (id == HeaderId.Unknown) throw new ArgumentOutOfRangeException ("id"); if (value == null) throw new ArgumentNullException ("value"); Options = ParserOptions.Default.Clone (); Field = id.ToHeaderName (); Id = id; rawField = Encoding.ASCII.GetBytes (Field); SetValue (encoding, value); }
static byte[] EncodeUnstructuredHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { if (format.International) { var folded = Fold (format, field, value); return Encoding.UTF8.GetBytes (folded); } var encoded = Rfc2047.EncodeText (format, charset, value); return Rfc2047.FoldUnstructuredHeader (format, field, encoded); }
/// <summary> /// Load a <see cref="MimeMessage"/> from the specified stream. /// </summary> /// <remarks> /// <para>Loads a <see cref="MimeMessage"/> from the given stream, using the /// specified <see cref="ParserOptions"/>.</para> /// <para>If <paramref name="persistent"/> is <c>true</c> and <paramref name="stream"/> is seekable, then /// the <see cref="MimeParser"/> will not copy the content of <see cref="MimePart"/>s into memory. Instead, /// it will use a <see cref="MimeKit.IO.BoundStream"/> to reference a substream of <paramref name="stream"/>. /// This has the potential to not only save mmeory usage, but also improve <see cref="MimeParser"/> /// performance.</para> /// </remarks> /// <returns>The parsed message.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="persistent"><c>true</c> if the stream is persistent; otherwise <c>false</c>.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (stream == null) throw new ArgumentNullException ("stream"); var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent); return parser.ParseMessage (cancellationToken); }
static bool TryParseNameValuePair(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out NameValuePair pair) { int valueIndex, startIndex; bool encoded = false; int? id = null; string name; pair = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } startIndex = index; if (!SkipParamName(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Invalid parameter name token at offset {0}", startIndex), startIndex, index); } return(false); } name = Encoding.ASCII.GetString(text, startIndex, index - startIndex); if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } if (text[index] == (byte)'*') { // the parameter is either encoded or it has a part id index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } int value; if (ParseUtils.TryParseInt32(text, ref index, endIndex, out value)) { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } if (text[index] == (byte)'*') { encoded = true; index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } } id = value; } else { encoded = true; } } if (text[index] != (byte)'=') { if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } } valueIndex = index; if (text[index] == (byte)'"') { ParseUtils.SkipQuoted(text, ref index, endIndex, throwOnError); } else if (options.ParameterComplianceMode == RfcComplianceMode.Strict) { ParseUtils.SkipToken(text, ref index, endIndex); } else { // Note: Google Docs, for example, does not always quote name/filename parameters // with spaces in the name. See https://github.com/jstedfast/MimeKit/issues/106 // for details. while (index < endIndex && text[index] != (byte)';' && text[index] != (byte)'\r' && text[index] != (byte)'\n') { index++; } } pair = new NameValuePair { ValueLength = index - valueIndex, ValueStart = valueIndex, Encoded = encoded, Name = name, Id = id }; return(true); }
static byte[] EncodeContentDisposition (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var disposition = ContentDisposition.Parse (options, value); var encoded = disposition.Encode (format, charset); return Encoding.UTF8.GetBytes (encoded); }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out InternetAddress address) { address = null; if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; if (index == endIndex) { if (throwOnError) throw new ParseException ("No address found.", index, index); return false; } // keep track of the start & length of the phrase int startIndex = index; int length = 0; while (index < endIndex && ParseUtils.Skip8bitWord (text, ref index, endIndex, throwOnError)) { length = index - startIndex; do { if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; // Note: some clients don't quote dots in the name if (index >= endIndex || text[index] != (byte) '.') break; index++; } while (true); } if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; // specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- // / "," / ";" / ":" / "\" / <"> ; string, to use // / "." / "[" / "]" ; within a word. if (index >= endIndex || text[index] == (byte) ',' || text[index] == ';') { // we've completely gobbled up an addr-spec w/o a domain byte sentinel = index < endIndex ? text[index] : (byte) ','; string name, addrspec; // rewind back to the beginning of the local-part index = startIndex; if (!TryParseAddrspec (text, ref index, endIndex, sentinel, throwOnError, out addrspec)) return false; ParseUtils.SkipWhiteSpace (text, ref index, endIndex); if (index < endIndex && text[index] == '(') { int comment = index; if (!ParseUtils.SkipComment (text, ref index, endIndex)) { if (throwOnError) throw new ParseException (string.Format ("Incomplete comment token at offset {0}", comment), comment, index); return false; } comment++; name = Rfc2047.DecodePhrase (options, text, comment, (index - 1) - comment).Trim (); } else { name = string.Empty; } address = new MailboxAddress (name, addrspec); return true; } if (text[index] == (byte) ':') { // rfc2822 group address int codepage = -1; string name; if (length > 0) { name = Rfc2047.DecodePhrase (options, text, startIndex, length, out codepage); } else { name = string.Empty; } if (codepage == -1) codepage = 65001; return TryParseGroup (options, text, startIndex, ref index, endIndex, MimeUtils.Unquote (name), codepage, throwOnError, out address); } if (text[index] == (byte) '<') { // rfc2822 angle-addr token int codepage = -1; string name; if (length > 0) { name = Rfc2047.DecodePhrase (options, text, startIndex, length, out codepage); } else { name = string.Empty; } if (codepage == -1) codepage = 65001; return TryParseMailbox (options, text, startIndex, ref index, endIndex, MimeUtils.Unquote (name), codepage, throwOnError, out address); } if (text[index] == (byte) '@') { // we're either in the middle of an addr-spec token or we completely gobbled up an addr-spec w/o a domain string name, addrspec; // rewind back to the beginning of the local-part index = startIndex; if (!TryParseAddrspec (text, ref index, endIndex, (byte) ',', throwOnError, out addrspec)) return false; ParseUtils.SkipWhiteSpace (text, ref index, endIndex); if (index < endIndex && text[index] == '(') { int comment = index; if (!ParseUtils.SkipComment (text, ref index, endIndex)) { if (throwOnError) throw new ParseException (string.Format ("Incomplete comment token at offset {0}", comment), comment, index); return false; } comment++; name = Rfc2047.DecodePhrase (options, text, comment, (index - 1) - comment).Trim (); } else { name = string.Empty; } address = new MailboxAddress (name, addrspec); return true; } if (throwOnError) throw new ParseException (string.Format ("Invalid address token at offset {0}", startIndex), startIndex, index); return false; }
internal static MimeEntity Create(ParserOptions options, ContentType ctype, IEnumerable <Header> headers, bool toplevel) { var entity = new MimeEntityConstructorInfo(options, ctype, headers, toplevel); var subtype = ctype.MediaSubtype.ToLowerInvariant(); var type = ctype.MediaType.ToLowerInvariant(); if (CustomMimeTypes.Count > 0) { var mimeType = string.Format("{0}/{1}", type, subtype); lock (CustomMimeTypes) { ConstructorInfo ctor; if (CustomMimeTypes.TryGetValue(mimeType, out ctor)) { return((MimeEntity)ctor.Invoke(new object[] { entity })); } } } if (type == "message") { if (subtype == "partial") { return(new MessagePartial(entity)); } return(new MessagePart(entity)); } if (type == "multipart") { #if !__MOBILE__ if (subtype == "encrypted") { return(new MultipartEncrypted(entity)); } if (subtype == "signed") { return(new MultipartSigned(entity)); } #endif return(new Multipart(entity)); } #if !__MOBILE__ if (type == "application") { switch (subtype) { case "x-pkcs7-signature": case "pkcs7-signature": return(new ApplicationPkcs7Signature(entity)); case "x-pgp-encrypted": case "pgp-encrypted": return(new ApplicationPgpEncrypted(entity)); case "x-pgp-signature": case "pgp-signature": return(new ApplicationPgpSignature(entity)); case "x-pkcs7-mime": case "pkcs7-mime": return(new ApplicationPkcs7Mime(entity)); } } #endif if (type == "text") { return(new TextPart(entity)); } return(new MimePart(entity)); }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentType"/> instance. /// </summary> /// <returns><c>true</c>, if the content type was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options.</param> /// <param name="buffer">The input buffer.</param> /// <param name="startIndex">The starting index of the input buffer.</param> /// <param name="type">The parsed content type.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="startIndex"/> is out of range. /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, int startIndex, out ContentType type) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); if (startIndex < 0 || startIndex >= buffer.Length) throw new ArgumentOutOfRangeException ("startIndex"); int index = startIndex; return TryParse (options, buffer, ref index, buffer.Length, false, out type); }
static byte[] EncodeReceivedHeader(ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var tokens = new List <ReceivedTokenValue> (); var rawValue = charset.GetBytes(value); var encoded = new StringBuilder(); int lineLength = field.Length + 1; bool date = false; int index = 0; int count = 0; while (index < rawValue.Length) { ReceivedTokenValue token = null; int startIndex = index; if (!ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false) || index >= rawValue.Length) { tokens.Add(new ReceivedTokenValue(startIndex, index - startIndex)); break; } while (index < rawValue.Length && !rawValue[index].IsWhitespace()) { index++; } var atom = charset.GetString(rawValue, startIndex, index - startIndex); for (int i = 0; i < ReceivedTokens.Length; i++) { if (atom == ReceivedTokens[i].Atom) { ReceivedTokens[i].Skip(rawValue, ref index); if (ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false)) { if (index < rawValue.Length && rawValue[index] == (byte)';') { date = true; index++; } } token = new ReceivedTokenValue(startIndex, index - startIndex); break; } } if (token == null) { if (ParseUtils.SkipCommentsAndWhiteSpace(rawValue, ref index, rawValue.Length, false)) { while (index < rawValue.Length && !rawValue[index].IsWhitespace()) { index++; } } token = new ReceivedTokenValue(startIndex, index - startIndex); } tokens.Add(token); ParseUtils.SkipWhiteSpace(rawValue, ref index, rawValue.Length); if (date && index < rawValue.Length) { // slurp up the date (the final token) tokens.Add(new ReceivedTokenValue(index, rawValue.Length - index)); break; } } foreach (var token in tokens) { var text = charset.GetString(rawValue, token.StartIndex, token.Length).TrimEnd(); if (count > 0 && lineLength + text.Length + 1 > format.MaxLineLength) { encoded.Append(format.NewLine); encoded.Append('\t'); lineLength = 1; count = 0; } else { encoded.Append(' '); lineLength++; } lineLength += text.Length; encoded.Append(text); count++; } encoded.Append(format.NewLine); return(charset.GetBytes(encoded.ToString())); }
/// <summary> /// Joins the specified message/partial parts into the complete message. /// </summary> /// <remarks> /// Combines all of the message/partial fragments into its original, /// complete, message. /// </remarks> /// <returns>The re-combined message.</returns> /// <param name="options">The parser options to use.</param> /// <param name="message">The message that contains the first `message/partial` part.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="message"/>is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>The last partial does not have a Total.</para> /// <para>-or-</para> /// <para>The number of partials provided does not match the expected count.</para> /// <para>-or-</para> /// <para>One or more partials is missing.</para> /// </exception> public static MimeMessage Join(ParserOptions options, MimeMessage message, IEnumerable <MessagePartial> partials) { return(Join(options, message, partials, false)); }
public static MimeMessage Join(ParserOptions options, IEnumerable <MessagePartial> partials) { return(Join(options, null, partials, true)); }
static MimeMessage Join(ParserOptions options, MimeMessage message, IEnumerable <MessagePartial> partials, bool allowNullMessage) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (!allowNullMessage && message == null) { throw new ArgumentNullException(nameof(message)); } if (partials == null) { throw new ArgumentNullException(nameof(partials)); } var parts = partials.ToList(); if (parts.Count == 0) { return(null); } parts.Sort(PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) { throw new ArgumentException("The last partial does not have a Total.", nameof(partials)); } int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) { throw new ArgumentException("The number of partials provided does not match the expected count.", nameof(partials)); } string id = parts[0].Id; using (var chained = new ChainedStream()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) { throw new ArgumentException("One or more partials is missing.", nameof(partials)); } var content = parts[i].Content; chained.Add(content.Open()); } var parser = new MimeParser(options, chained); var joined = parser.ParseMessage(); if (message != null) { CombineHeaders(message, joined); } return(joined); } }
internal HeaderList(ParserOptions options) { table = new Dictionary <string, Header> (icase); headers = new List <Header> (); Options = options; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ParameterList paramList) { var rfc2184 = new Dictionary <string, List <NameValuePair> > (icase); var @params = new List <NameValuePair> (); List <NameValuePair> parts; paramList = null; do { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { break; } // handle empty parameter name/value pairs if (text[index] == (byte)';') { index++; continue; } NameValuePair pair; if (!TryParseNameValuePair(text, ref index, endIndex, throwOnError, out pair)) { return(false); } if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (pair.Id.HasValue) { if (rfc2184.TryGetValue(pair.Name, out parts)) { parts.Add(pair); } else { parts = new List <NameValuePair> (); rfc2184.Add(pair.Name, parts); @params.Add(pair); parts.Add(pair); } } else { @params.Add(pair); } if (index >= endIndex) { break; } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format("Invalid parameter list token at offset {0}", index), index, index); } return(false); } index++; } while (true); paramList = new ParameterList(); var hex = new HexDecoder(); foreach (var param in @params) { int startIndex = param.ValueStart; int length = param.ValueLength; Decoder decoder = null; string value; if (param.Id.HasValue) { parts = rfc2184[param.Name]; parts.Sort(); value = string.Empty; for (int i = 0; i < parts.Count; i++) { startIndex = parts[i].ValueStart; length = parts[i].ValueLength; if (parts[i].Encoded) { bool flush = i + 1 >= parts.Count || !parts[i + 1].Encoded; value += DecodeRfc2184(ref decoder, hex, text, startIndex, length, flush); } else if (length >= 2 && text[startIndex] == (byte)'"') { // FIXME: use Rfc2047.Unquote()?? value += CharsetUtils.ConvertToUnicode(options, text, startIndex + 1, length - 2); hex.Reset(); } else if (length > 0) { value += CharsetUtils.ConvertToUnicode(options, text, startIndex, length); hex.Reset(); } } hex.Reset(); } else if (param.Encoded) { value = DecodeRfc2184(ref decoder, hex, text, startIndex, length, true); hex.Reset(); } else if (length >= 2 && text[startIndex] == (byte)'"') { // FIXME: use Rfc2047.Unquote()?? value = Rfc2047.DecodeText(options, text, startIndex + 1, length - 2); } else if (length > 0) { value = Rfc2047.DecodeText(options, text, startIndex, length); } else { value = string.Empty; } paramList.Add(new Parameter(param.Name, value)); } return(true); }
/// <summary> /// Load a <see cref="MimeMessage"/> from the specified file. /// </summary> /// <remarks> /// Loads a <see cref="MimeMessage"/> from the file at the given path, using the /// specified <see cref="ParserOptions"/>. /// </remarks> /// <returns>The parsed message.</returns> /// <param name="options">The parser options.</param> /// <param name="fileName">The name of the file to load.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="fileName"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="fileName"/> is a zero-length string, contains only white space, or /// contains one or more invalid characters as defined by /// <see cref="System.IO.Path.InvalidPathChars"/>. /// </exception> /// <exception cref="System.IO.DirectoryNotFoundException"> /// <paramref name="fileName"/> is an invalid file path. /// </exception> /// <exception cref="System.IO.FileNotFoundException"> /// The specified file path could not be found. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// The user does not have access to read the specified file. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeMessage Load (ParserOptions options, string fileName, CancellationToken cancellationToken = default (CancellationToken)) { if (options == null) throw new ArgumentNullException ("options"); if (fileName == null) throw new ArgumentNullException ("fileName"); using (var stream = File.Open (fileName, FileMode.Open, FileAccess.Read)) return Load (options, stream, cancellationToken); }
/// <summary> /// Parses the given text into a new <see cref="MimeKit.InternetAddress"/> instance. /// </summary> /// <remarks> /// Parses a single <see cref="MailboxAddress"/> or <see cref="GroupAddress"/>. If the text contains /// more data, then parsing will fail. /// </remarks> /// <returns>The parsed <see cref="MimeKit.InternetAddress"/>.</returns> /// <param name="options">The parser options to use.</param> /// <param name="text">The text.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="text"/> is <c>null</c>.</para> /// </exception> /// <exception cref="MimeKit.ParseException"> /// <paramref name="text"/> could not be parsed. /// </exception> public static InternetAddress Parse(ParserOptions options, string text) { if (options == null) throw new ArgumentNullException ("options"); if (text == null) throw new ArgumentNullException ("text"); var buffer = Encoding.UTF8.GetBytes (text); int endIndex = buffer.Length; InternetAddress address; int index = 0; TryParse (options, buffer, ref index, endIndex, true, out address); ParseUtils.SkipCommentsAndWhiteSpace (buffer, ref index, endIndex, true); if (index != endIndex) throw new ParseException (string.Format ("Unexpected token at offset {0}", index), index, index); return address; }
/// <summary> /// Parse the specified input buffer into a new instance of the <see cref="MimeKit.ContentType"/> class. /// </summary> /// <returns>The parsed <see cref="MimeKit.ContentType"/>.</returns> /// <param name="options">The parser options.</param> /// <param name="buffer">The input buffer.</param> /// <param name="startIndex">The start index of the buffer.</param> /// <param name="length">The length of the buffer.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="startIndex"/> and <paramref name="length"/> do not specify /// a valid range in the byte array. /// </exception> /// <exception cref="MimeKit.ParseException"> /// The <paramref name="buffer"/> could not be parsed. /// </exception> public static ContentType Parse(ParserOptions options, byte[] buffer, int startIndex, int length) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); if (startIndex < 0 || startIndex > buffer.Length) throw new ArgumentOutOfRangeException ("startIndex"); if (length < 0 || startIndex + length > buffer.Length) throw new ArgumentOutOfRangeException ("length"); int index = startIndex; ContentType type; TryParse (options, buffer, ref index, startIndex + length, true, out type); return type; }
static byte[] EncodeMessageIdHeader(ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { return(charset.GetBytes(" " + value + format.NewLine)); }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentType contentType) { string type, subtype; int start; contentType = null; if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; start = index; if (!SkipType (text, ref index, endIndex)) { if (throwOnError) throw new ParseException (string.Format ("Invalid type token at position {0}", start), start, index); return false; } type = Encoding.ASCII.GetString (text, start, index - start); if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; if (index >= endIndex || text[index] != (byte) '/') { if (throwOnError) throw new ParseException (string.Format ("Expected '/' at position {0}", index), index, index); return false; } // skip over the '/' index++; if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; start = index; if (!ParseUtils.SkipToken (text, ref index, endIndex)) { if (throwOnError) throw new ParseException (string.Format ("Invalid atom token at position {0}", start), start, index); return false; } subtype = Encoding.ASCII.GetString (text, start, index - start); if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; contentType = new ContentType (type, subtype); if (index >= endIndex) return true; if (text[index] != (byte) ';') { if (throwOnError) throw new ParseException (string.Format ("Expected ';' at position {0}", index), index, index); return false; } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; if (index >= endIndex) return true; ParameterList parameters; if (!ParameterList.TryParse (options, text, ref index, endIndex, throwOnError, out parameters)) return false; contentType.Parameters = parameters; return true; }
internal static unsafe bool TryParse(ParserOptions options, byte *input, int length, bool strict, out Header header) { byte *inend = input + length; byte *start = input; byte *inptr = input; // find the end of the field name if (strict) { while (inptr < inend && IsAtom(*inptr)) { inptr++; } } else { while (inptr < inend && *inptr != (byte)':' && !IsBlankOrControl(*inptr)) { inptr++; } } if (inptr == inend || *inptr != ':') { header = null; return(false); } var chars = new char[(int)(inptr - start)]; fixed(char *outbuf = chars) { char *outptr = outbuf; while (start < inptr) { *outptr++ = (char)*start++; } } var field = new string (chars); inptr++; int count = (int)(inend - inptr); var value = new byte[count]; fixed(byte *outbuf = value) { byte *outptr = outbuf; while (inptr < inend) { *outptr++ = *inptr++; } } header = new Header(options, field, value); return(true); }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.InternetAddress"/> instance. /// </summary> /// <remarks> /// Parses a single <see cref="MailboxAddress"/> or <see cref="GroupAddress"/>. If the buffer contains /// more data, then parsing will fail. /// </remarks> /// <returns><c>true</c>, if the address was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options to use.</param> /// <param name="buffer">The input buffer.</param> /// <param name="address">The parsed address.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, out InternetAddress address) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); int endIndex = buffer.Length; int index = 0; if (!TryParse (options, buffer, ref index, endIndex, false, out address)) return false; if (!ParseUtils.SkipCommentsAndWhiteSpace (buffer, ref index, endIndex, false)) { address = null; return false; } if (index != endIndex) { address = null; return false; } return true; }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.Header"/> instance. /// </summary> /// <remarks> /// Parses a header from the supplied buffer starting at the specified index. /// </remarks> /// <returns><c>true</c>, if the header was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options to use.</param> /// <param name="buffer">The input buffer.</param> /// <param name="startIndex">The starting index of the input buffer.</param> /// <param name="header">The parsed header.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="startIndex"/> is out of range. /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, int startIndex, out Header header) { int length = buffer.Length - startIndex; return(TryParse(options, buffer, startIndex, length, out header)); }
static bool TryParseGroup(ParserOptions options, byte[] text, int startIndex, ref int index, int endIndex, string name, int codepage, bool throwOnError, out InternetAddress address) { Encoding encoding = Encoding.GetEncoding (codepage); List<InternetAddress> members; address = null; // skip over the ':' index++; if (index >= endIndex) { if (throwOnError) throw new ParseException (string.Format ("Incomplete address group at offset {0}", startIndex), startIndex, index); return false; } if (InternetAddressList.TryParse (options, text, ref index, endIndex, true, throwOnError, out members)) address = new GroupAddress (encoding, name, members); else address = new GroupAddress (encoding, name); if (index >= endIndex || text[index] != (byte) ';') { if (throwOnError) throw new ParseException (string.Format ("Expected to find ';' at offset {0}", index), startIndex, index); while (index < endIndex && text[index] != (byte) ';') index++; } else { index++; } return true; }
static byte[] EncodeMessageIdHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { return charset.GetBytes (" " + value + format.NewLine); }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.Header"/> instance. /// </summary> /// <remarks> /// Parses a header from the specified buffer. /// </remarks> /// <returns><c>true</c>, if the header was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options to use.</param> /// <param name="buffer">The input buffer.</param> /// <param name="header">The parsed header.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, out Header header) { return(TryParse(options, buffer, 0, buffer.Length, out header)); }
static byte[] EncodeDkimSignatureHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var encoded = new StringBuilder (); int lineLength = field.Length + 1; var token = new StringBuilder (); int index = 0; while (index < value.Length) { while (index < value.Length && IsWhiteSpace (value[index])) index++; int startIndex = index; string name; while (index < value.Length && value[index] != '=') { if (!IsWhiteSpace (value[index])) token.Append (value[index]); index++; } name = value.Substring (startIndex, index - startIndex); while (index < value.Length && value[index] != ';') { if (!IsWhiteSpace (value[index])) token.Append (value[index]); index++; } if (index < value.Length && value[index] == ';') { token.Append (';'); index++; } if (lineLength + token.Length + 1 > format.MaxLineLength || name == "bh" || name == "b") { encoded.Append (format.NewLine); encoded.Append ('\t'); lineLength = 1; } else { encoded.Append (' '); lineLength++; } if (token.Length > format.MaxLineLength) { switch (name) { case "z": EncodeDkimHeaderList (format, encoded, ref lineLength, token.ToString (), '|'); break; case "h": EncodeDkimHeaderList (format, encoded, ref lineLength, token.ToString (), ':'); break; default: EncodeDkimLongValue (format, encoded, ref lineLength, token.ToString ()); break; } } else { encoded.Append (token.ToString ()); lineLength += token.Length; } token.Length = 0; } encoded.Append (format.NewLine); return charset.GetBytes (encoded.ToString ()); }
static byte[] EncodeReceivedHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var tokens = new List<ReceivedTokenValue> (); var rawValue = charset.GetBytes (value); var encoded = new StringBuilder (); int lineLength = field.Length + 1; bool date = false; int index = 0; int count = 0; while (index < rawValue.Length) { ReceivedTokenValue token = null; int startIndex = index; if (!ParseUtils.SkipCommentsAndWhiteSpace (rawValue, ref index, rawValue.Length, false) || index >= rawValue.Length) { tokens.Add (new ReceivedTokenValue (startIndex, index - startIndex)); break; } while (index < rawValue.Length && !rawValue[index].IsWhitespace ()) index++; var atom = charset.GetString (rawValue, startIndex, index - startIndex); for (int i = 0; i < ReceivedTokens.Length; i++) { if (atom == ReceivedTokens[i].Atom) { ReceivedTokens[i].Skip (rawValue, ref index); if (ParseUtils.SkipCommentsAndWhiteSpace (rawValue, ref index, rawValue.Length, false)) { if (index < rawValue.Length && rawValue[index] == (byte) ';') { date = true; index++; } } token = new ReceivedTokenValue (startIndex, index - startIndex); break; } } if (token == null) { if (ParseUtils.SkipCommentsAndWhiteSpace (rawValue, ref index, rawValue.Length, false)) { while (index < rawValue.Length && !rawValue[index].IsWhitespace ()) index++; } token = new ReceivedTokenValue (startIndex, index - startIndex); } tokens.Add (token); ParseUtils.SkipWhiteSpace (rawValue, ref index, rawValue.Length); if (date && index < rawValue.Length) { // slurp up the date (the final token) tokens.Add (new ReceivedTokenValue (index, rawValue.Length - index)); break; } } foreach (var token in tokens) { var text = charset.GetString (rawValue, token.StartIndex, token.Length).TrimEnd (); if (count > 0 && lineLength + text.Length + 1 > format.MaxLineLength) { encoded.Append (format.NewLine); encoded.Append ('\t'); lineLength = 1; count = 0; } else { encoded.Append (' '); lineLength++; } lineLength += text.Length; encoded.Append (text); count++; } encoded.Append (format.NewLine); return charset.GetBytes (encoded.ToString ()); }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified stream. /// </summary> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, Stream stream) { return(Load(options, stream, CancellationToken.None)); }
static byte[] EncodeReferencesHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var encoded = new StringBuilder (); int lineLength = field.Length + 1; int count = 0; foreach (var reference in MimeUtils.EnumerateReferences (value)) { if (count > 0 && lineLength + reference.Length + 3 > format.MaxLineLength) { encoded.Append (format.NewLine); encoded.Append ('\t'); lineLength = 1; count = 0; } else { encoded.Append (' '); lineLength++; } encoded.Append ('<').Append (reference).Append ('>'); lineLength += reference.Length + 2; count++; } encoded.Append (format.NewLine); return charset.GetBytes (encoded.ToString ()); }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified file. /// </summary> /// <returns>The parsed entity.</returns> /// <param name="options">The parser options.</param> /// <param name="fileName">The name of the file to load.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="fileName"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// The specified file path is empty. /// </exception> /// <exception cref="System.IO.FileNotFoundException"> /// The specified file could not be found. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// The user does not have access to read the specified file. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, string fileName) { return(Load(options, fileName, CancellationToken.None)); }
static byte[] EncodeContentType (ParserOptions options, FormatOptions format, Encoding charset, string field, string value) { var contentType = ContentType.Parse (options, value); var encoded = contentType.Encode (format, charset); return Encoding.UTF8.GetBytes (encoded); }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified content stream. /// </summary> /// <remarks> /// This method is mostly meant for use with APIs such as <see cref="System.Net.HttpWebResponse"/> /// where the headers are parsed separately from the content. /// </remarks> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="contentType">The Content-Type of the stream.</param> /// <param name="content">The content stream.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="contentType"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="content"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, ContentType contentType, Stream content) { return(Load(options, contentType, content, CancellationToken.None)); }
internal MimeMessage (ParserOptions options) { addresses = new Dictionary<string, InternetAddressList> (StringComparer.OrdinalIgnoreCase); Headers = new HeaderList (options); // initialize our address lists foreach (var name in StandardAddressHeaders) { var list = new InternetAddressList (); list.Changed += InternetAddressListChanged; addresses.Add (name, list); } references = new MessageIdList (); references.Changed += ReferencesChanged; inreplyto = null; Headers.Changed += HeadersChanged; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentDisposition disposition) { string type; int atom; disposition = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format(CultureInfo.InvariantCulture, "Expected atom token at position {0}", index), index, index); } return(false); } atom = index; if (text[index] == '"') { if (throwOnError) { throw new ParseException(string.Format(CultureInfo.InvariantCulture, "Unxpected qstring token at position {0}", atom), atom, index); } // Note: This is a work-around for broken mailers that quote the disposition value... // // See https://github.com/jstedfast/MailKit/issues/486 for details. if (!ParseUtils.SkipQuoted(text, ref index, endIndex, throwOnError)) { return(false); } type = CharsetUtils.ConvertToUnicode(options, text, atom, index - atom); type = MimeUtils.Unquote(type); if (string.IsNullOrEmpty(type)) { type = Attachment; } } else { if (!ParseUtils.SkipAtom(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format(CultureInfo.InvariantCulture, "Invalid atom token at position {0}", atom), atom, index); } // Note: this is a work-around for broken mailers that do not specify a disposition value... // // See https://github.com/jstedfast/MailKit/issues/486 for details. if (index > atom || text[index] != (byte)';') { return(false); } type = Attachment; } else { type = Encoding.ASCII.GetString(text, atom, index - atom); } } disposition = new ContentDisposition(); disposition.disposition = type; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { return(true); } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format(CultureInfo.InvariantCulture, "Expected ';' at position {0}", index), index, index); } return(false); } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { return(true); } ParameterList parameters; if (!ParameterList.TryParse(options, text, ref index, endIndex, throwOnError, out parameters)) { return(false); } disposition.Parameters = parameters; return(true); }
/// <summary> /// Load a <see cref="MimeMessage"/> from the specified stream. /// </summary> /// <remarks> /// Loads a <see cref="MimeMessage"/> from the given stream, using the /// specified <see cref="ParserOptions"/>. /// </remarks> /// <returns>The parsed message.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeMessage Load (ParserOptions options, Stream stream, CancellationToken cancellationToken = default (CancellationToken)) { return Load (options, stream, false, cancellationToken); }
internal static bool TryParseMailbox(ParserOptions options, byte[] text, int startIndex, ref int index, int endIndex, string name, int codepage, bool throwOnError, out InternetAddress address) { DomainList route = null; Encoding encoding; try { encoding = Encoding.GetEncoding(codepage); } catch { encoding = Encoding.UTF8; } address = null; // skip over the '<' index++; if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete mailbox at offset {0}", startIndex), startIndex, index); } return(false); } if (text[index] == (byte)'@') { if (!DomainList.TryParse(text, ref index, endIndex, throwOnError, out route)) { if (throwOnError) { throw new ParseException(string.Format("Invalid route in mailbox at offset {0}", startIndex), startIndex, index); } return(false); } if (index + 1 >= endIndex || text[index] != (byte)':') { if (throwOnError) { throw new ParseException(string.Format("Incomplete route in mailbox at offset {0}", startIndex), startIndex, index); } return(false); } index++; } string addrspec; if (!TryParseAddrspec(text, ref index, endIndex, (byte)'>', throwOnError, out addrspec)) { return(false); } if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex || text[index] != (byte)'>') { if (options.AddressParserComplianceMode == RfcComplianceMode.Strict) { if (throwOnError) { throw new ParseException(string.Format("Unexpected end of mailbox at offset {0}", startIndex), startIndex, index); } return(false); } } else { index++; } if (route != null) { address = new MailboxAddress(encoding, name, route, addrspec); } else { address = new MailboxAddress(encoding, name, addrspec); } return(true); }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out InternetAddress address) { address = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index == endIndex) { if (throwOnError) { throw new ParseException("No address found.", index, index); } return(false); } // keep track of the start & length of the phrase int startIndex = index; int length = 0; while (index < endIndex && ParseUtils.Skip8bitWord(text, ref index, endIndex, throwOnError)) { length = index - startIndex; do { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } // Note: some clients don't quote dots in the name if (index >= endIndex || text[index] != (byte)'.') { break; } index++; } while (true); } if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } // specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- // / "," / ";" / ":" / "\" / <"> ; string, to use // / "." / "[" / "]" ; within a word. if (index >= endIndex || text[index] == (byte)',' || text[index] == ';') { // we've completely gobbled up an addr-spec w/o a domain byte sentinel = index < endIndex ? text[index] : (byte)','; string name, addrspec; // rewind back to the beginning of the local-part index = startIndex; if (!TryParseAddrspec(text, ref index, endIndex, sentinel, throwOnError, out addrspec)) { return(false); } ParseUtils.SkipWhiteSpace(text, ref index, endIndex); if (index < endIndex && text[index] == '(') { int comment = index; if (!ParseUtils.SkipComment(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Incomplete comment token at offset {0}", comment), comment, index); } return(false); } comment++; name = Rfc2047.DecodePhrase(options, text, comment, (index - 1) - comment).Trim(); } else { name = string.Empty; } address = new MailboxAddress(name, addrspec); return(true); } if (text[index] == (byte)':') { // rfc2822 group address int codepage = -1; string name; if (length > 0) { name = Rfc2047.DecodePhrase(options, text, startIndex, length, out codepage); } else { name = string.Empty; } if (codepage == -1) { codepage = 65001; } return(TryParseGroup(options, text, startIndex, ref index, endIndex, MimeUtils.Unquote(name), codepage, throwOnError, out address)); } if (text[index] == (byte)'<') { // rfc2822 angle-addr token int codepage = -1; string name; if (length > 0) { name = Rfc2047.DecodePhrase(options, text, startIndex, length, out codepage); } else { name = string.Empty; } if (codepage == -1) { codepage = 65001; } return(TryParseMailbox(text, startIndex, ref index, endIndex, MimeUtils.Unquote(name), codepage, throwOnError, out address)); } if (text[index] == (byte)'@') { // we're either in the middle of an addr-spec token or we completely gobbled up an addr-spec w/o a domain string name, addrspec; // rewind back to the beginning of the local-part index = startIndex; if (!TryParseAddrspec(text, ref index, endIndex, (byte)',', throwOnError, out addrspec)) { return(false); } ParseUtils.SkipWhiteSpace(text, ref index, endIndex); if (index < endIndex && text[index] == '(') { int comment = index; if (!ParseUtils.SkipComment(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Incomplete comment token at offset {0}", comment), comment, index); } return(false); } comment++; name = Rfc2047.DecodePhrase(options, text, comment, (index - 1) - comment).Trim(); } else { name = string.Empty; } address = new MailboxAddress(name, addrspec); return(true); } if (throwOnError) { throw new ParseException(string.Format("Invalid address token at offset {0}", startIndex), startIndex, index); } return(false); }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentDisposition disposition) { string type; int atom; disposition = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } atom = index; if (!ParseUtils.SkipAtom(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Invalid atom token at position {0}", atom), atom, index); } return(false); } type = Encoding.ASCII.GetString(text, atom, index - atom); if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } disposition = new ContentDisposition(); disposition.disposition = type; if (index >= endIndex) { return(true); } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format("Expected ';' at position {0}", index), index, index); } return(false); } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { return(true); } ParameterList parameters; if (!ParameterList.TryParse(options, text, ref index, endIndex, throwOnError, out parameters)) { return(false); } disposition.Parameters = parameters; return(true); }
/// <summary> /// Parse the specified input buffer into a new instance of the <see cref="MimeKit.ContentType"/> class. /// </summary> /// <returns>The parsed <see cref="MimeKit.ContentType"/>.</returns> /// <param name="options">The parser options.</param> /// <param name="buffer">The input buffer.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="MimeKit.ParseException"> /// The <paramref name="buffer"/> could not be parsed. /// </exception> public static ContentType Parse(ParserOptions options, byte[] buffer) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); ContentType type; int index = 0; TryParse (options, buffer, ref index, buffer.Length, true, out type); return type; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ParameterList paramList) { var rfc2231 = new Dictionary <string, List <NameValuePair> > (MimeUtils.OrdinalIgnoreCase); var @params = new List <NameValuePair> (); List <NameValuePair> parts; paramList = null; do { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { break; } // handle empty parameter name/value pairs if (text[index] == (byte)';') { index++; continue; } NameValuePair pair; if (!TryParseNameValuePair(options, text, ref index, endIndex, throwOnError, out pair)) { return(false); } if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (pair.Id.HasValue) { if (rfc2231.TryGetValue(pair.Name, out parts)) { parts.Add(pair); } else { parts = new List <NameValuePair> (); rfc2231[pair.Name] = parts; @params.Add(pair); parts.Add(pair); } } else { @params.Add(pair); } if (index >= endIndex) { break; } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format("Invalid parameter list token at offset {0}", index), index, index); } return(false); } index++; } while (true); paramList = new ParameterList(); var hex = new HexDecoder(); foreach (var param in @params) { var method = ParameterEncodingMethod.Default; int startIndex = param.ValueStart; int length = param.ValueLength; var buffer = param.Value; Encoding encoding = null; Decoder decoder = null; Parameter parameter; string value; if (param.Id.HasValue) { method = ParameterEncodingMethod.Rfc2231; parts = rfc2231[param.Name]; parts.Sort(); value = string.Empty; for (int i = 0; i < parts.Count; i++) { startIndex = parts[i].ValueStart; length = parts[i].ValueLength; buffer = parts[i].Value; if (parts[i].Encoded) { bool flush = i + 1 >= parts.Count || !parts[i + 1].Encoded; Encoding charset; // Note: Some mail clients mistakenly quote encoded parameter values when they shouldn't if (length >= 2 && buffer[startIndex] == (byte)'"' && buffer[startIndex + length - 1] == (byte)'"') { startIndex++; length -= 2; } value += DecodeRfc2231(out charset, ref decoder, hex, buffer, startIndex, length, flush); encoding = encoding ?? charset; } else if (length >= 2 && buffer[startIndex] == (byte)'"') { var quoted = CharsetUtils.ConvertToUnicode(options, buffer, startIndex, length); value += MimeUtils.Unquote(quoted); hex.Reset(); } else if (length > 0) { value += CharsetUtils.ConvertToUnicode(options, buffer, startIndex, length); hex.Reset(); } } hex.Reset(); } else if (param.Encoded) { // Note: param value is not supposed to be quoted, but issue #239 illustrates // that this can happen in the wild. Hopefully we will not need to worry // about quoted-pairs. if (length >= 2 && buffer[startIndex] == (byte)'"') { if (buffer[startIndex + length - 1] == (byte)'"') { length--; } startIndex++; length--; } value = DecodeRfc2231(out encoding, ref decoder, hex, buffer, startIndex, length, true); method = ParameterEncodingMethod.Rfc2231; hex.Reset(); } else if (!paramList.Contains(param.Name)) { // Note: If we've got an rfc2231-encoded version of the same parameter, then // we'll want to choose that one as opposed to the ASCII variant (i.e. this one). // // While most mail clients that I know of do not send multiple parameters of the // same name, rfc6266 suggests that HTTP servers are using this approach to work // around HTTP clients that do not (yet) implement support for the rfc2231 // encoding of parameter values. Since none of the MIME specifications provide // any suggestions for dealing with this, following rfc6266 seems to make the // most sense, even though it is meant for HTTP clients and servers. int codepage = -1; if (length >= 2 && text[startIndex] == (byte)'"') { var quoted = Rfc2047.DecodeText(options, buffer, startIndex, length, out codepage); value = MimeUtils.Unquote(quoted); } else if (length > 0) { value = Rfc2047.DecodeText(options, buffer, startIndex, length, out codepage); } else { value = string.Empty; } if (codepage != -1 && codepage != 65001) { encoding = CharsetUtils.GetEncoding(codepage); method = ParameterEncodingMethod.Rfc2047; } } else { continue; } if (paramList.table.TryGetValue(param.Name, out parameter)) { parameter.Encoding = encoding; parameter.Value = value; } else if (encoding != null) { paramList.Add(encoding, param.Name, value); parameter = paramList[paramList.Count - 1]; } else { paramList.Add(param.Name, value); parameter = paramList[paramList.Count - 1]; } parameter.EncodingMethod = method; } return(true); }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.ContentType"/> instance. /// </summary> /// <returns><c>true</c>, if the content type was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options.</param> /// <param name="buffer">The input buffer.</param> /// <param name="type">The parsed content type.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, out ContentType type) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); int index = 0; return TryParse (options, buffer, ref index, buffer.Length, false, out type); }
static bool TryParseNameValuePair(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out NameValuePair pair) { int valueIndex, valueLength, startIndex; bool encoded = false; int? id = null; byte[] value; string name; pair = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } startIndex = index; if (!SkipParamName(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Invalid parameter name token at offset {0}", startIndex), startIndex, index); } return(false); } name = Encoding.ASCII.GetString(text, startIndex, index - startIndex); if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } if (text[index] == (byte)'*') { // the parameter is either encoded or it has a part id index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } int identifier; if (ParseUtils.TryParseInt32(text, ref index, endIndex, out identifier)) { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } if (text[index] == (byte)'*') { encoded = true; index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } } id = identifier; } else { encoded = true; } } if (text[index] != (byte)'=') { if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { if (throwOnError) { throw new ParseException(string.Format("Incomplete parameter at offset {0}", startIndex), startIndex, index); } return(false); } valueIndex = index; value = text; if (text[index] == (byte)'"') { ParseUtils.SkipQuoted(text, ref index, endIndex, throwOnError); valueLength = index - valueIndex; } else if (options.ParameterComplianceMode == RfcComplianceMode.Strict) { ParseUtils.SkipToken(text, ref index, endIndex); valueLength = index - valueIndex; } else { // Note: Google Docs, for example, does not always quote name/filename parameters // with spaces in the name. See https://github.com/jstedfast/MimeKit/issues/106 // for details. while (index < endIndex && text[index] != (byte)';' && text[index] != (byte)'\r' && text[index] != (byte)'\n') { index++; } valueLength = index - valueIndex; if (index < endIndex && text[index] != (byte)';') { // Note: https://github.com/jstedfast/MimeKit/issues/159 adds to this suckage // by having a multi-line unquoted value with spaces... don't you just love // mail software written by people who have never heard of standards? using (var memory = new MemoryStream()) { memory.Write(text, valueIndex, valueLength); do { while (index < endIndex && (text[index] == (byte)'\r' || text[index] == (byte)'\n')) { index++; } valueIndex = index; while (index < endIndex && text[index] != (byte)';' && text[index] != (byte)'\r' && text[index] != (byte)'\n') { index++; } memory.Write(text, valueIndex, index - valueIndex); } while (index < endIndex && text[index] != ';'); value = memory.ToArray(); valueLength = value.Length; valueIndex = 0; } } // Trim trailing white space characters to work around issues such as the // one described in https://github.com/jstedfast/MimeKit/issues/278 while (valueLength > valueIndex && value[valueLength - 1].IsWhitespace()) { valueLength--; } } pair = new NameValuePair { ValueLength = valueLength, ValueStart = valueIndex, Encoded = encoded, Value = value, Name = name, Id = id }; return(true); }
/// <summary> /// Parses the given input buffer into a new <see cref="MimeKit.InternetAddress"/> instance. /// </summary> /// <remarks> /// Parses a single <see cref="MailboxAddress"/> or <see cref="GroupAddress"/>. If the buffer contains /// more data, then parsing will fail. /// </remarks> /// <returns>The parsed <see cref="MimeKit.InternetAddress"/>.</returns> /// <param name="options">The parser options to use.</param> /// <param name="buffer">The input buffer.</param> /// <param name="startIndex">The starting index of the input buffer.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="startIndex"/>is out of range. /// </exception> /// <exception cref="MimeKit.ParseException"> /// <paramref name="buffer"/> could not be parsed. /// </exception> public static InternetAddress Parse(ParserOptions options, byte[] buffer, int startIndex) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); if (startIndex < 0 || startIndex > buffer.Length) throw new ArgumentOutOfRangeException ("startIndex"); int endIndex = buffer.Length; InternetAddress address; int index = startIndex; TryParse (options, buffer, ref index, endIndex, true, out address); ParseUtils.SkipCommentsAndWhiteSpace (buffer, ref index, endIndex, true); if (index != endIndex) throw new ParseException (string.Format ("Unexpected token at offset {0}", index), index, index); return address; }
/// <summary> /// Load a <see cref="MimeEntity"/> from the specified stream. /// </summary> /// <remarks> /// Loads a <see cref="MimeEntity"/> from the given stream, using the /// specified <see cref="ParserOptions"/>. /// </remarks> /// <returns>The parsed MIME entity.</returns> /// <param name="options">The parser options.</param> /// <param name="stream">The stream.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="stream"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.FormatException"> /// There was an error parsing the entity. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> public static MimeEntity Load(ParserOptions options, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { return(Load(options, stream, false, cancellationToken)); }
/// <summary> /// Tries to parse the given input buffer into a new <see cref="MimeKit.InternetAddress"/> instance. /// </summary> /// <remarks> /// Parses a single <see cref="MailboxAddress"/> or <see cref="GroupAddress"/>. If the buffer contains /// more data, then parsing will fail. /// </remarks> /// <returns><c>true</c>, if the address was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options to use.</param> /// <param name="buffer">The input buffer.</param> /// <param name="startIndex">The starting index of the input buffer.</param> /// <param name="length">The number of bytes in the input buffer to parse.</param> /// <param name="address">The parsed address.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="buffer"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="startIndex"/> and <paramref name="length"/> do not specify /// a valid range in the byte array. /// </exception> public static bool TryParse(ParserOptions options, byte[] buffer, int startIndex, int length, out InternetAddress address) { if (options == null) throw new ArgumentNullException ("options"); if (buffer == null) throw new ArgumentNullException ("buffer"); if (startIndex < 0 || startIndex > buffer.Length) throw new ArgumentOutOfRangeException ("startIndex"); if (length < 0 || length > (buffer.Length - startIndex)) throw new ArgumentOutOfRangeException ("length"); int endIndex = startIndex + length; int index = startIndex; if (!TryParse (options, buffer, ref index, endIndex, false, out address)) return false; if (!ParseUtils.SkipCommentsAndWhiteSpace (buffer, ref index, endIndex, false)) { address = null; return false; } if (index != endIndex) { address = null; return false; } return true; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool isGroup, bool throwOnError, out List <InternetAddress> addresses) { var flags = throwOnError ? InternetAddress.AddressParserFlags.Parse : InternetAddress.AddressParserFlags.TryParse; var list = new List <InternetAddress> (); InternetAddress address; addresses = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index == endIndex) { if (throwOnError) { throw new ParseException("No addresses found.", index, index); } return(false); } while (index < endIndex) { if (isGroup && text[index] == (byte)';') { break; } if (!InternetAddress.TryParse(options, text, ref index, endIndex, flags, out address)) { // skip this address... while (index < endIndex && text[index] != (byte)',' && (!isGroup || text[index] != (byte)';')) { index++; } } else { list.Add(address); } // Note: we loop here in case there are any null addresses between commas do { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex || text[index] != (byte)',') { break; } index++; } while (true); } addresses = list; return(true); }
/// <summary> /// Tries to parse the given text into a new <see cref="MimeKit.InternetAddress"/> instance. /// </summary> /// <remarks> /// Parses a single <see cref="MailboxAddress"/> or <see cref="GroupAddress"/>. If the text contains /// more data, then parsing will fail. /// </remarks> /// <returns><c>true</c>, if the address was successfully parsed, <c>false</c> otherwise.</returns> /// <param name="options">The parser options to use.</param> /// <param name="text">The text.</param> /// <param name="address">The parsed address.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="text"/> is <c>null</c>. /// </exception> public static bool TryParse(ParserOptions options, string text, out InternetAddress address) { if (options == null) throw new ArgumentNullException ("options"); if (text == null) throw new ArgumentNullException ("text"); var buffer = Encoding.UTF8.GetBytes (text); int endIndex = buffer.Length; int index = 0; if (!TryParse (options, buffer, ref index, endIndex, false, out address)) return false; if (!ParseUtils.SkipCommentsAndWhiteSpace (buffer, ref index, endIndex, false)) { address = null; return false; } if (index != endIndex) { address = null; return false; } return true; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ContentType contentType) { string type, subtype; int start; contentType = null; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } start = index; if (!SkipType(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Invalid type token at position {0}", start), start, index); } return(false); } type = Encoding.ASCII.GetString(text, start, index - start); if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex || text[index] != (byte)'/') { if (throwOnError) { throw new ParseException(string.Format("Expected '/' at position {0}", index), index, index); } return(false); } // skip over the '/' index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } start = index; if (!ParseUtils.SkipToken(text, ref index, endIndex)) { if (throwOnError) { throw new ParseException(string.Format("Invalid atom token at position {0}", start), start, index); } return(false); } subtype = Encoding.ASCII.GetString(text, start, index - start); if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } contentType = new ContentType(type, subtype); if (index >= endIndex) { return(true); } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format("Expected ';' at position {0}", index), index, index); } return(false); } index++; if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { return(true); } ParameterList parameters; if (!ParameterList.TryParse(options, text, ref index, endIndex, throwOnError, out parameters)) { return(false); } contentType.Parameters = parameters; return(true); }
internal static bool TryParseMailbox(ParserOptions options, byte[] text, int startIndex, ref int index, int endIndex, string name, int codepage, bool throwOnError, out InternetAddress address) { Encoding encoding = Encoding.GetEncoding (codepage); DomainList route = null; address = null; // skip over the '<' index++; if (index >= endIndex) { if (throwOnError) throw new ParseException (string.Format ("Incomplete mailbox at offset {0}", startIndex), startIndex, index); return false; } if (text[index] == (byte) '@') { if (!DomainList.TryParse (text, ref index, endIndex, throwOnError, out route)) { if (throwOnError) throw new ParseException (string.Format ("Invalid route in mailbox at offset {0}", startIndex), startIndex, index); return false; } if (index + 1 >= endIndex || text[index] != (byte) ':') { if (throwOnError) throw new ParseException (string.Format ("Incomplete route in mailbox at offset {0}", startIndex), startIndex, index); return false; } index++; } string addrspec; if (!TryParseAddrspec (text, ref index, endIndex, (byte) '>', throwOnError, out addrspec)) return false; if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; if (index >= endIndex || text[index] != (byte) '>') { if (options.AddressParserComplianceMode == RfcComplianceMode.Strict) { if (throwOnError) throw new ParseException (string.Format ("Unexpected end of mailbox at offset {0}", startIndex), startIndex, index); return false; } } else { index++; } if (route != null) address = new MailboxAddress (encoding, name, route, addrspec); else address = new MailboxAddress (encoding, name, addrspec); return true; }
internal static bool TryParse(ParserOptions options, byte[] text, ref int index, int endIndex, bool throwOnError, out ParameterList paramList) { var rfc2184 = new Dictionary <string, List <NameValuePair> > (icase); var @params = new List <NameValuePair> (); List <NameValuePair> parts; paramList = null; do { if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (index >= endIndex) { break; } // handle empty parameter name/value pairs if (text[index] == (byte)';') { index++; continue; } NameValuePair pair; if (!TryParseNameValuePair(text, ref index, endIndex, throwOnError, out pair)) { return(false); } if (!ParseUtils.SkipCommentsAndWhiteSpace(text, ref index, endIndex, throwOnError)) { return(false); } if (pair.Id.HasValue) { if (rfc2184.TryGetValue(pair.Name, out parts)) { parts.Add(pair); } else { parts = new List <NameValuePair> (); rfc2184[pair.Name] = parts; @params.Add(pair); parts.Add(pair); } } else { @params.Add(pair); } if (index >= endIndex) { break; } if (text[index] != (byte)';') { if (throwOnError) { throw new ParseException(string.Format("Invalid parameter list token at offset {0}", index), index, index); } return(false); } index++; } while (true); paramList = new ParameterList(); var hex = new HexDecoder(); foreach (var param in @params) { int startIndex = param.ValueStart; int length = param.ValueLength; Decoder decoder = null; string value; if (param.Id.HasValue) { parts = rfc2184[param.Name]; parts.Sort(); value = string.Empty; for (int i = 0; i < parts.Count; i++) { startIndex = parts[i].ValueStart; length = parts[i].ValueLength; if (parts[i].Encoded) { bool flush = i + 1 >= parts.Count || !parts[i + 1].Encoded; // Note: Some mail clients mistakenly quote encoded parameter values when they shouldn't if (length >= 2 && text[startIndex] == (byte)'"' && text[startIndex + length - 1] == (byte)'"') { startIndex++; length -= 2; } value += DecodeRfc2184(ref decoder, hex, text, startIndex, length, flush); } else if (length >= 2 && text[startIndex] == (byte)'"') { var quoted = CharsetUtils.ConvertToUnicode(options, text, startIndex, length); value += MimeUtils.Unquote(quoted); hex.Reset(); } else if (length > 0) { value += CharsetUtils.ConvertToUnicode(options, text, startIndex, length); hex.Reset(); } } hex.Reset(); } else if (param.Encoded) { value = DecodeRfc2184(ref decoder, hex, text, startIndex, length, true); hex.Reset(); } else if (!paramList.Contains(param.Name)) { // Note: If we've got an rfc2184-encoded version of the same parameter, then // we'll want to choose that one as opposed to the ASCII variant (i.e. this one). // // While most mail clients that I know of do not send multiple parameters of the // same name, rfc6266 suggests that HTTP servers are using this approach to work // around HTTP clients that do not (yet) implement support for the rfc2184/2231 // encoding of parameter values. Since none of the MIME specifications provide // any suggestions for dealing with this, following rfc6266 seems to make the // most sense, even though it is meant for HTTP clients and servers. if (length >= 2 && text[startIndex] == (byte)'"') { var quoted = Rfc2047.DecodeText(options, text, startIndex, length); value = MimeUtils.Unquote(quoted); } else if (length > 0) { value = Rfc2047.DecodeText(options, text, startIndex, length); } else { value = string.Empty; } } else { continue; } paramList[param.Name] = value; } return(true); }
/// <summary> /// Join the specified message/partial parts into the complete message. /// </summary> /// <param name="options">The parser options to use.</param> /// <param name="partials">The list of partial message parts.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="partials"/>is <c>null</c>.</para> /// </exception> public static MimeMessage Join(ParserOptions options, IEnumerable<MessagePartial> partials) { if (options == null) throw new ArgumentNullException ("options"); if (partials == null) throw new ArgumentNullException ("partials"); var parts = partials.ToList (); if (parts.Count == 0) return null; parts.Sort (PartialCompare); if (!parts[parts.Count - 1].Total.HasValue) throw new ArgumentException ("partials"); int total = parts[parts.Count - 1].Total.Value; if (parts.Count != total) throw new ArgumentException ("partials"); string id = parts[0].Id; using (var chained = new ChainedStream ()) { // chain all of the partial content streams... for (int i = 0; i < parts.Count; i++) { int number = parts[i].Number.Value; if (number != i + 1) throw new ArgumentException ("partials"); var content = parts[i].ContentObject; content.Stream.Seek (0, SeekOrigin.Begin); var filtered = new FilteredStream (content.Stream); filtered.Add (DecoderFilter.Create (content.Encoding)); chained.Add (filtered); } var parser = new MimeParser (options, chained); return parser.ParseMessage (); } }
public static void Main(string[] args) { AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler); string dataPath = GetLocalAppDataPath(); string MBoxViewerPath = Path.Combine(dataPath, "MBoxViewer"); string MailServicePath = Path.Combine(MBoxViewerPath, "MailService"); string TempPath = Path.Combine(MailServicePath, "Temp"); string okFilePath = MailServicePath + "\\ForwardMailSuccess.txt"; string errorFilePath = MailServicePath + "\\ForwardMailError.txt"; string errorFilePathOldInstance = MailServicePath + "\\ForwardMailError2.txt"; System.IO.DirectoryInfo info = Directory.CreateDirectory(TempPath); string loggerFilePath = FindKeyinArgs(args, "--logger-file"); var logger = new FileLogger(); logger.Open(loggerFilePath); logger.Log("Logger Open"); try { // File.Delete doesn't seem to generate exceptions if file doesn't exist //if (File.Exists(okFilePath) File.Delete(okFilePath); //if (File.Exists(errorFilePath) File.Delete(errorFilePath); File.Delete(errorFilePathOldInstance); } catch (Exception ex) { string txt = String.Format("Delete Critical Files Failed\n{0}\n{1}\n{2}\n\n{3}", okFilePath, errorFilePath, errorFilePathOldInstance, ex.Message); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, txt); logger.Log("Exception in Delete Critical Files: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitMailAddress); } int numArgs = args.GetLength(0); if ((numArgs <= 0) || ((numArgs % 2) != 0)) { string errorText = String.Format("Invalid command argument list: {0} .", String.Join(" ", args)); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, errorText + "\n"); logger.Log(errorText); System.Environment.Exit(ExitCodes.ExitCmdArguments); } /* * if (numArgs <= 0) * { * logger.Log(@"Usage: --from addr --to addr1,addr2,.. --cc addr1,addr2,.. -bcc addr1,addr2,.. * --user login-user-name --passwd --login-user-password --smtp smtp-server-name", ""); * Debug.Assert(true == false); * System.Environment.Exit(1); * } */ string instance = ""; IniFile smtpIni = null; EMailInfo mailInfo = new EMailInfo(); SMTPConfig smtpConfig = new SMTPConfig(); string smtpConfigFilePath = ""; string UserPassword = ""; string protocolLoggerFilePath = ""; int tcpListenPort = 0; logger.Log("Command line argument list:"); for (int j = 0, i = 0; j < numArgs; j = j + 2, i++) { string key = args[j]; string val = args[j + 1]; if (!key.StartsWith("--")) { string errorText = String.Format("Invalid key: {0} ", key); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, errorText + "\n"); logger.Log(errorText); System.Environment.Exit(ExitCodes.ExitCmdArguments); } if ((j + 1) >= numArgs) { string errorText = String.Format("Found key: {0} without value.", key); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, errorText + "\n"); logger.Log(errorText); System.Environment.Exit(ExitCodes.ExitCmdArguments); } if (key.CompareTo("--instance-id") == 0) { instance = val; } else if (key.CompareTo("--smtp-protocol-logger") == 0) { protocolLoggerFilePath = val; } else if (key.CompareTo("--from") == 0) { mailInfo.From = val; } else if (key.CompareTo("--to") == 0) { mailInfo.To = val; } else if (key.CompareTo("--cc") == 0) { mailInfo.CC = val; } else if (key.CompareTo("--bcc") == 0) { mailInfo.BCC = val; } else if (key.CompareTo("--user") == 0) { mailInfo.To = val; } else if (key.CompareTo("--passwd") == 0) { UserPassword = val; } else if (key.CompareTo("--smtp-cnf") == 0) { smtpConfigFilePath = val; } else if (key.CompareTo("--tcp-port") == 0) { tcpListenPort = int.Parse(val); } else if (key.CompareTo("--eml-file") == 0) { mailInfo.EmlFilePath = val; } else if (key.CompareTo("--mail-text-file") == 0) { mailInfo.TextFilePath = val; } else if (key.CompareTo("--logger-file") == 0) { ; // see FindKeyinArgs(args, "--logger-file"); } else { logger.Log(String.Format(" Unknown Key: {0} {1}", args[j], args[j + 1])); } logger.Log(String.Format(" {0} {1}", args[j], args[j + 1])); } if (smtpConfigFilePath.Length == 0) { string errorText = String.Format("required --smtp-cnf command line argument missing."); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, errorText + "\n"); logger.Log(errorText); System.Environment.Exit(ExitCodes.ExitCmdArguments); } if (!File.Exists(smtpConfigFilePath)) { string errorText = String.Format("SMTP configuration file {0} doesn't exist.", smtpConfigFilePath); bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, errorText + "\n"); logger.Log(errorText); System.Environment.Exit(ExitCodes.ExitCmdArguments); } try { if (protocolLoggerFilePath.Length > 0) { File.Delete(protocolLoggerFilePath); } } catch (Exception /*ex*/) {; } // ignore smtpIni = new IniFile(smtpConfigFilePath); string ActiveMailService = smtpIni.IniReadValue("MailService", "ActiveMailService"); smtpConfig.MailServiceName = smtpIni.IniReadValue(ActiveMailService, "MailServiceName"); smtpConfig.SmtpServerAddress = smtpIni.IniReadValue(ActiveMailService, "SmtpServerAddress"); smtpConfig.SmtpServerPort = int.Parse(smtpIni.IniReadValue(ActiveMailService, "SmtpServerPort")); smtpConfig.UserAccount = smtpIni.IniReadValue(ActiveMailService, "UserAccount"); if (UserPassword.Length > 0) { smtpConfig.UserPassword = UserPassword; } else { smtpConfig.UserPassword = smtpIni.IniReadValue(ActiveMailService, "UserPassword"); } smtpConfig.EncryptionType = int.Parse(smtpIni.IniReadValue(ActiveMailService, "EncryptionType")); logger.Log(smtpConfig.ToString()); // Uncomment when you exec this application from MBoxViewer //smtpConfig.UserPassword = ""; if (smtpConfig.UserPassword.Length == 0) { logger.Log("Waiting to receive password"); smtpConfig.UserPassword = WaitForPassword(tcpListenPort, logger, errorFilePath); if (smtpConfig.UserPassword.Length > 0) { logger.Log("Received non empty password"); //logger.Log("Received non empty password: "******"Received empty password"); } int found = smtpConfig.UserPassword.IndexOf(":"); if (found <= 0) { // Old instance , log to differnt file bool rval = FileUtils.CreateWriteCloseFile(errorFilePathOldInstance, "Received invalid id:password. Exitting\n"); System.Environment.Exit(ExitCodes.ExitCmdArguments); } string id = smtpConfig.UserPassword.Substring(0, found); string passwd = smtpConfig.UserPassword.Substring(found + 1); smtpConfig.UserPassword = passwd; logger.Log("Command line instance id: ", instance); logger.Log("Received instance id: ", id); //logger.Log("Received password: "******"Received password: "******"xxxxxxxxxxxx"); if (id.CompareTo(instance) != 0) { // Old instance , log to different file bool rval = FileUtils.CreateWriteCloseFile(errorFilePathOldInstance, "This is old instance. Exitting\n"); System.Environment.Exit(ExitCodes.ExitCmdArguments); } } MimeKit.ParserOptions opt = new MimeKit.ParserOptions(); var From = new MailboxAddress("", smtpConfig.UserAccount); // InternetAddressList CCList = new InternetAddressList(); InternetAddressList BCCList = new InternetAddressList(); InternetAddressList ToList = new InternetAddressList(); try { if (mailInfo.To.Length > 0) { ToList = MimeKit.InternetAddressList.Parse(opt, mailInfo.To); } if (mailInfo.CC.Length > 0) { CCList = MimeKit.InternetAddressList.Parse(opt, mailInfo.CC); } if (mailInfo.BCC.Length > 0) { BCCList = MimeKit.InternetAddressList.Parse(opt, mailInfo.BCC); } } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Parsing Internet Address list Failed\n", ex.Message); logger.Log("Exception in InternetAddressList.Parse: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitMailAddress); } // string emlFilePath = mailInfo.EmlFilePath; // create the main textual body of the message var text = new TextPart("plain"); try { text.Text = File.ReadAllText(mailInfo.TextFilePath, Encoding.UTF8); } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Building Mime Mesage Failed\n", ex.Message); logger.Log("Exception in Building Mime Message: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitMimeMessage); } logger.Log("Forwarding Eml file:", emlFilePath); MimeMessage msg = null; try { var message = new MimeMessage(); message = MimeKit.MimeMessage.Load(emlFilePath); List <MimeMessage> mimeMessages = new List <MimeMessage>(); mimeMessages.Add(message); msg = BuildMimeMessageWithEmlAsRFC822Attachment(text, mimeMessages, From, ToList, CCList, BCCList); } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Building Mime Mesage Failed\n", ex.Message); logger.Log("Exception in Building Mime Message: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitMimeMessage); } logger.Log("BuildMimeMessageWithEmlAsRFC822Attachment Done"); //string msgAsString = MailkitConvert.ToString(msg); //string msgAsString = msg.ToString(); //logger.Log("\n", msgAsString); // OAUTH2 works on Google but requires verification by Google and it seems to be chargable option if number of users > 100 // Another problem is that ClientSecret can't be hardcoded in the application // For now we will just rely on User Account and User Password for authentication SaslMechanism oauth2 = null;; bool useOAUTH2 = false; if (useOAUTH2) { string appClientId = "xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"; string appClientSecret = "yyyyyyyyyyyyyyyyyyyyyyyyyyy"; var accessScopes = new[] { // that is the only scope that works per info from jstedfast "https://mail.google.com/", }; var clientSecrets = new ClientSecrets { ClientId = appClientId, ClientSecret = appClientSecret }; oauth2 = GetAuth2Token(smtpConfig.UserAccount, clientSecrets, accessScopes); } IProtocolLogger smtpProtocolLogger = null; if (protocolLoggerFilePath.Length > 0) { smtpProtocolLogger = new ProtocolLogger(protocolLoggerFilePath); } else { smtpProtocolLogger = new NullProtocolLogger(); } using (var client = new SmtpClient(smtpProtocolLogger)) { try { client.Connect(smtpConfig.SmtpServerAddress, smtpConfig.SmtpServerPort, (SecureSocketOptions)smtpConfig.EncryptionType); } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Connect to SMTP Server Failed\n", ex.Message); logger.Log("Exception in Connect: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitConnect); } logger.Log(String.Format("Connected to {0} mail service", smtpConfig.MailServiceName)); try { if (useOAUTH2) { client.Authenticate(oauth2); } else { client.Authenticate(smtpConfig.UserAccount, smtpConfig.UserPassword); } } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "SMTP Authentication Failed\n", ex.Message); logger.Log("Exception in Authenticate: ", ex.ToString()); System.Environment.Exit(ExitCodes.ExitAuthenticate); } logger.Log("SMTP Authentication Succeeded"); // Clear smtpConfig.UserPassword in case it cores smtpConfig.UserPassword = ""; try { client.Send(msg); } catch (Exception ex) { bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Send Failed\n", ex.Message); logger.Log("Exception in Send to SMTP Server: ", ex.ToString()); //string msgString = MailkitConvert.ToString(msg); //string msgAsString = msg.ToString(); // To help to investigate Warning place at the begining of the serialized MimeMessage // X - MimeKit - Warning: Do NOT use ToString() to serialize messages! Use one of the WriteTo() methods instead! //logger.Log("\n", msgString); System.Environment.Exit(ExitCodes.ExitSend); } string txt = "Mail Sending Succeeded"; logger.Log(txt); bool retval = FileUtils.CreateWriteCloseFile(okFilePath, txt); try { client.Disconnect(true); } catch (Exception ex) { // Ignore, not a fatal error //bool rval = FileUtils.CreateWriteCloseFile(errorFilePath, "Send Failed\n", ex.Message); logger.Log("Exception in Disconnect to SMTP Server: ", ex.ToString()); } logger.Log("SMTP Client Disconnected. All done."); } System.Environment.Exit(ExitCodes.ExitOk); }