/// <summary>
        /// Tries to parse a version from a header such as Mime-Version.
        /// </summary>
        /// <remarks>
        /// Parses a MIME version string from the supplied buffer starting at the given index
        /// and spanning across the specified number of bytes.
        /// </remarks>
        /// <returns><c>true</c>, if the version was successfully parsed, <c>false</c> otherwise.</returns>
        /// <param name="buffer">The raw byte buffer to parse.</param>
        /// <param name="startIndex">The index into the buffer to start parsing.</param>
        /// <param name="length">The length of the buffer to parse.</param>
        /// <param name="version">The parsed version.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="buffer"/> is <c>null</c>.
        /// </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 TryParseVersion(byte[] buffer, int startIndex, int length, out Version version)
        {
            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");
            }

            List <int> values   = new List <int> ();
            int        endIndex = startIndex + length;
            int        index    = startIndex;
            int        value;

            version = null;

            do
            {
                if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false) || index >= endIndex)
                {
                    return(false);
                }

                if (!ParseUtils.TryParseInt32(buffer, ref index, endIndex, out value))
                {
                    return(false);
                }

                values.Add(value);

                if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
                {
                    return(false);
                }

                if (index >= endIndex)
                {
                    break;
                }

                if (buffer[index++] != (byte)'.')
                {
                    return(false);
                }
            } while (index < endIndex);

            switch (values.Count)
            {
            case 4: version = new Version(values[0], values[1], values[2], values[3]); break;

            case 3: version = new Version(values[0], values[1], values[2]); break;

            case 2: version = new Version(values[0], values[1]); break;

            default: return(false);
            }

            return(true);
        }
        /// <summary>
        /// Enumerates the message-id references such as those that can be found in
        /// the In-Reply-To or References header.
        /// </summary>
        /// <remarks>
        /// Incrementally parses Message-Ids (such as those from a References header
        /// in a MIME message) from the supplied buffer starting at the given index
        /// and spanning across the specified number of bytes.
        /// </remarks>
        /// <returns>The references.</returns>
        /// <param name="buffer">The raw byte buffer to parse.</param>
        /// <param name="startIndex">The index into the buffer to start parsing.</param>
        /// <param name="length">The length of the buffer to parse.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="buffer"/> is <c>null</c>.
        /// </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 IEnumerable <string> EnumerateReferences(byte[] buffer, int startIndex, int length)
        {
            byte[]          sentinels = { (byte)'>' };
            int             endIndex  = startIndex + length;
            int             index     = startIndex;
            InternetAddress addr;
            string          msgid;

            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");
            }

            do
            {
                if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
                {
                    break;
                }

                if (index >= endIndex)
                {
                    break;
                }

                if (buffer[index] == '<')
                {
                    if (!InternetAddress.TryParseMailbox(ParserOptions.Default, buffer, startIndex, ref index, endIndex, "", 65001, false, out addr))
                    {
                        break;
                    }

                    msgid = ((MailboxAddress)addr).Address;

                    // Note: some message-id's are broken and in the form local-part@domain@domain
                    // https://github.com/jstedfast/MailKit/issues/138
                    while (index < endIndex && buffer[index] == (byte)'@')
                    {
                        int    saved = index;
                        string domain;

                        index++;

                        if (!ParseUtils.TryParseDomain(buffer, ref index, endIndex, sentinels, false, out domain))
                        {
                            index = saved;
                            break;
                        }

                        msgid += "@" + domain;
                    }

                    yield return(msgid);
                }
                else if (!ParseUtils.SkipWord(buffer, ref index, endIndex, false))
                {
                    index++;
                }
            } while (index < endIndex);

            yield break;
        }
Beispiel #3
0
        static bool TryParseUnknownDateFormat(IList <DateToken> tokens, byte[] text, out DateTimeOffset date)
        {
            int?      day = null, month = null, year = null, tzone = null;
            int       hour = 0, minute = 0, second = 0;
            bool      numericMonth = false;
            bool      haveWeekday  = false;
            bool      haveTime     = false;
            DayOfWeek weekday;
            TimeSpan  offset;

            for (int i = 0; i < tokens.Count; i++)
            {
                int value;

                if (!haveWeekday && tokens[i].IsWeekday)
                {
                    if (TryGetWeekday(tokens[i], text, out weekday))
                    {
                        haveWeekday = true;
                        continue;
                    }
                }

                if ((month == null || numericMonth) && tokens[i].IsMonth)
                {
                    if (TryGetMonth(tokens[i], text, out value))
                    {
                        if (numericMonth)
                        {
                            numericMonth = false;
                            day          = month;
                        }

                        month = value;
                        continue;
                    }
                }

                if (!haveTime && tokens[i].IsTimeOfDay)
                {
                    if (TryGetTimeOfDay(tokens[i], text, out hour, out minute, out second))
                    {
                        haveTime = true;
                        continue;
                    }
                }

                if (tzone == null && tokens[i].IsTimeZone)
                {
                    if (TryGetTimeZone(tokens[i], text, out value))
                    {
                        tzone = value;
                        continue;
                    }
                }

                if (tokens[i].IsNumeric)
                {
                    if (tokens[i].Length == 4)
                    {
                        if (year == null)
                        {
                            if (TryGetYear(tokens[i], text, out value))
                            {
                                year = value;
                            }
                        }
                        else if (tzone == null)
                        {
                            if (TryGetTimeZone(tokens[i], text, out value))
                            {
                                tzone = value;
                            }
                        }

                        continue;
                    }

                    if (tokens[i].Length > 2)
                    {
                        continue;
                    }

                    // Note: we likely have either YYYY[-/]MM[-/]DD or MM[-/]DD[-/]YY
                    int endIndex = tokens[i].StartIndex + tokens[i].Length;
                    int index    = tokens[i].StartIndex;

                    ParseUtils.TryParseInt32(text, ref index, endIndex, out value);

                    if (month == null && value > 0 && value <= 12)
                    {
                        numericMonth = true;
                        month        = value;
                        continue;
                    }

                    if (day == null && value > 0 && value <= 31)
                    {
                        day = value;
                        continue;
                    }

                    if (year == null && value >= 69)
                    {
                        year = 1900 + value;
                        continue;
                    }
                }

                // WTF is this??
            }

            if (year == null || month == null || day == null)
            {
                date = new DateTimeOffset();
                return(false);
            }

            if (!haveTime)
            {
                hour = minute = second = 0;
            }

            if (tzone != null)
            {
                int minutes = tzone.Value % 100;
                int hours   = tzone.Value / 100;

                offset = new TimeSpan(hours, minutes, 0);
            }
            else
            {
                offset = new TimeSpan(0);
            }

            try {
                date = new DateTimeOffset(year.Value, month.Value, day.Value, hour, minute, second, offset);
            } catch (ArgumentOutOfRangeException) {
                date = new DateTimeOffset();
                return(false);
            }

            return(true);
        }
Beispiel #4
0
        static bool TryGetTimeZone(DateToken token, byte[] text, out int tzone)
        {
            tzone = 0;

            if (token.IsNumericZone)
            {
                int endIndex = token.StartIndex + token.Length;
                int index    = token.StartIndex;
                int sign;

                if (text[index] == (byte)'-')
                {
                    sign = -1;
                }
                else if (text[index] == (byte)'+')
                {
                    sign = 1;
                }
                else
                {
                    return(false);
                }

                index++;

                if (!ParseUtils.TryParseInt32(text, ref index, endIndex, out tzone) || index != endIndex)
                {
                    return(false);
                }

                tzone *= sign;
            }
            else if (token.IsAlphaZone)
            {
                if (token.Length > 3)
                {
                    return(false);
                }

                var name = Encoding.ASCII.GetString(text, token.StartIndex, token.Length);

                if (!timezones.TryGetValue(name, out tzone))
                {
                    return(false);
                }
            }
            else if (token.IsNumeric)
            {
                int endIndex = token.StartIndex + token.Length;
                int index    = token.StartIndex;

                if (!ParseUtils.TryParseInt32(text, ref index, endIndex, out tzone) || index != endIndex)
                {
                    return(false);
                }
            }

            if (tzone < -1200 || tzone > 1400)
            {
                return(false);
            }

            return(true);
        }
Beispiel #5
0
        /// <summary>
        /// Parses a Message-Id header value.
        /// </summary>
        /// <remarks>
        /// Parses the Message-Id value, returning the addr-spec portion of the msg-id token.
        /// </remarks>
        /// <returns>The addr-spec portion of the msg-id token.</returns>
        /// <param name="buffer">The raw byte buffer to parse.</param>
        /// <param name="startIndex">The index into the buffer to start parsing.</param>
        /// <param name="length">The length of the buffer to parse.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="buffer"/> is <c>null</c>.
        /// </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 string ParseMessageId(byte[] buffer, int startIndex, int length)
        {
            ParseUtils.ValidateArguments(buffer, startIndex, length);

            byte[] sentinels = { (byte)'>' };
            int    endIndex  = startIndex + length;
            int    index     = startIndex;
            string msgid;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
            {
                return(null);
            }

            if (index >= endIndex)
            {
                return(null);
            }

            if (buffer[index] == '<')
            {
                // skip over the '<'
                index++;

                if (index >= endIndex)
                {
                    return(null);
                }
            }

            string localpart;

            if (!InternetAddress.TryParseLocalPart(buffer, ref index, endIndex, false, out localpart))
            {
                return(null);
            }

            if (index >= endIndex)
            {
                return(null);
            }

            if (buffer[index] == (byte)'>')
            {
                // The msgid token did not contain an @domain. Technically this is illegal, but for the
                // sake of maximum compatibility, I guess we have no choice but to accept it...
                return(localpart);
            }

            if (buffer[index] != (byte)'@')
            {
                // who the hell knows what we have here...
                return(null);
            }

            // skip over the '@'
            index++;

            if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
            {
                return(null);
            }

            if (index >= endIndex)
            {
                return(null);
            }

            if (buffer[index] == (byte)'>')
            {
                // The msgid token was in the form "<local-part@>". Technically this is illegal, but for
                // the sake of maximum compatibility, I guess we have no choice but to accept it...
                // https://github.com/jstedfast/MimeKit/issues/102
                return(localpart + "@");
            }

            string domain;

            if (!ParseUtils.TryParseDomain(buffer, ref index, endIndex, sentinels, false, out domain))
            {
                return(null);
            }

            msgid = localpart + "@" + domain;

            // Note: some Message-Id's are broken and in the form "<local-part@domain@domain>"
            // https://github.com/jstedfast/MailKit/issues/138
            while (index < endIndex && buffer[index] == (byte)'@')
            {
                index++;

                if (!ParseUtils.TryParseDomain(buffer, ref index, endIndex, sentinels, false, out domain))
                {
                    break;
                }

                msgid += "@" + domain;
            }

            return(msgid);
        }
Beispiel #6
0
        /// <summary>
        /// Enumerates the message-id references such as those that can be found in
        /// the In-Reply-To or References header.
        /// </summary>
        /// <remarks>
        /// Incrementally parses Message-Ids (such as those from a References header
        /// in a MIME message) from the supplied buffer starting at the given index
        /// and spanning across the specified number of bytes.
        /// </remarks>
        /// <returns>The references.</returns>
        /// <param name="buffer">The raw byte buffer to parse.</param>
        /// <param name="startIndex">The index into the buffer to start parsing.</param>
        /// <param name="length">The length of the buffer to parse.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="buffer"/> is <c>null</c>.
        /// </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 IEnumerable <string> EnumerateReferences(byte[] buffer, int startIndex, int length)
        {
            byte[] sentinels = { (byte)'>' };
            int    endIndex  = startIndex + length;
            int    index     = startIndex;
            string msgid;

            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");
            }

            do
            {
                if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
                {
                    break;
                }

                if (index >= endIndex)
                {
                    break;
                }

                if (buffer[index] == '<')
                {
                    int tokenIndex = index;

                    // skip over the '<'
                    index++;

                    if (index >= endIndex)
                    {
                        break;
                    }

                    string localpart;
                    if (!InternetAddress.TryParseLocalPart(buffer, ref index, endIndex, false, out localpart))
                    {
                        continue;
                    }

                    if (index >= endIndex)
                    {
                        break;
                    }

                    if (buffer[index] == (byte)'>')
                    {
                        // The msgid token did not contain an @domain. Technically this is illegal, but for the
                        // sake of maximum compatibility, I guess we have no choice but to accept it...
                        index++;

                        yield return(localpart);

                        continue;
                    }

                    if (buffer[index] != (byte)'@')
                    {
                        // who the hell knows what we have here... ignore it and continue on?
                        continue;
                    }

                    // skip over the '@'
                    index++;

                    if (!ParseUtils.SkipCommentsAndWhiteSpace(buffer, ref index, endIndex, false))
                    {
                        break;
                    }

                    if (index >= endIndex)
                    {
                        break;
                    }

                    if (buffer[index] == (byte)'>')
                    {
                        // The msgid token was in the form "<local-part@>". Technically this is illegal, but for
                        // the sake of maximum compatibility, I guess we have no choice but to accept it...
                        // https://github.com/jstedfast/MimeKit/issues/102
                        index++;

                        yield return(localpart + "@");

                        continue;
                    }

                    string domain;
                    if (!ParseUtils.TryParseDomain(buffer, ref index, endIndex, sentinels, false, out domain))
                    {
                        continue;
                    }

                    msgid = localpart + "@" + domain;

                    // Note: some Message-Id's are broken and in the form "<local-part@domain@domain>"
                    // https://github.com/jstedfast/MailKit/issues/138
                    while (index < endIndex && buffer[index] == (byte)'@')
                    {
                        int saved = index;

                        index++;

                        if (!ParseUtils.TryParseDomain(buffer, ref index, endIndex, sentinels, false, out domain))
                        {
                            index = saved;
                            break;
                        }

                        msgid += "@" + domain;
                    }

                    yield return(msgid);
                }
                else if (!ParseUtils.SkipWord(buffer, ref index, endIndex, false))
                {
                    index++;
                }
            } while (index < endIndex);

            yield break;
        }