Parser options as used by MimeParser as well as various Parse and TryParse methods in MimeKit.
		internal MimeEntityConstructorArgs (ParserOptions options, ContentType ctype, IEnumerable<Header> headers, bool toplevel)
		{
			ParserOptions = options;
			IsTopLevel = toplevel;
			ContentType = ctype;
			Headers = headers;
		}
Beispiel #2
0
		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);
			}
		}
Beispiel #3
0
 static ParserOptions()
 {
     Default = new ParserOptions ();
     Default.EnableRfc2047Workarounds = true;
     Default.RespectContentLength = false;
     Default.CharsetEncoding = Encoding.Default;
 }
Beispiel #4
0
		/// <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);
		}
Beispiel #5
0
		/// <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);
		}
Beispiel #6
0
		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);
		}
Beispiel #7
0
		/// <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);
		}
Beispiel #8
0
        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);
        }
Beispiel #9
0
		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);
		}
Beispiel #10
0
        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;
        }
Beispiel #11
0
        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));
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
        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()));
        }
Beispiel #14
0
 /// <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));
 }
Beispiel #15
0
 public static MimeMessage Join(ParserOptions options, IEnumerable <MessagePartial> partials)
 {
     return(Join(options, null, partials, true));
 }
Beispiel #16
0
        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);
            }
        }
Beispiel #17
0
 internal HeaderList(ParserOptions options)
 {
     table   = new Dictionary <string, Header> (icase);
     headers = new List <Header> ();
     Options = options;
 }
Beispiel #18
0
        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);
        }
Beispiel #19
0
		/// <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);
		}
Beispiel #20
0
        /// <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;
        }
Beispiel #21
0
        /// <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;
        }
Beispiel #22
0
 static byte[] EncodeMessageIdHeader(ParserOptions options, FormatOptions format, Encoding charset, string field, string value)
 {
     return(charset.GetBytes(" " + value + format.NewLine));
 }
Beispiel #23
0
        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;
        }
Beispiel #24
0
        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);
        }
Beispiel #25
0
        /// <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;
        }
Beispiel #26
0
        /// <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));
        }
Beispiel #27
0
        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;
        }
Beispiel #28
0
		static byte[] EncodeMessageIdHeader (ParserOptions options, FormatOptions format, Encoding charset, string field, string value)
		{
			return charset.GetBytes (" " + value + format.NewLine);
		}
Beispiel #29
0
 /// <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));
 }
Beispiel #30
0
		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 ());
		}
Beispiel #31
0
		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 ());
		}
Beispiel #32
0
 /// <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));
 }
Beispiel #33
0
		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 ());
		}
Beispiel #34
0
 /// <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));
 }
Beispiel #35
0
		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);
		}
Beispiel #36
0
 /// <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));
 }
Beispiel #37
0
		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);
        }
Beispiel #39
0
		/// <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);
        }
Beispiel #41
0
        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);
        }
Beispiel #42
0
        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);
        }
Beispiel #43
0
        /// <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;
        }
Beispiel #44
0
        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);
        }
Beispiel #45
0
        /// <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);
        }
Beispiel #46
0
        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);
        }
Beispiel #47
0
        /// <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;
        }
Beispiel #48
0
 /// <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));
 }
Beispiel #49
0
        /// <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);
        }
Beispiel #51
0
        /// <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;
        }
Beispiel #52
0
        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);
        }
Beispiel #53
0
        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;
        }
Beispiel #54
0
        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);
        }
Beispiel #55
0
        /// <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 ();
            }
        }
Beispiel #56
0
        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);
        }