File: System\Xml\XmlMtomReader.cs
Project: ndp\cdf\src\WCF\Serialization\System.Runtime.Serialization.csproj (System.Runtime.Serialization)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.Xml
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Runtime;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Security;
    using System.Security.Permissions;
 
    public interface IXmlMtomReaderInitializer
    {
        void SetInput(byte[] buffer, int offset, int count, Encoding[] encodings, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize, OnXmlDictionaryReaderClose onClose);
        void SetInput(Stream stream, Encoding[] encodings, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize, OnXmlDictionaryReaderClose onClose);
    }
 
    class XmlMtomReader : XmlDictionaryReader, IXmlLineInfo, IXmlMtomReaderInitializer
    {
        Encoding[] encodings;
        XmlDictionaryReader xmlReader;
        XmlDictionaryReader infosetReader;
        MimeReader mimeReader;
        Dictionary<string, MimePart> mimeParts;
        OnXmlDictionaryReaderClose onClose;
        bool readingBinaryElement;
        int maxBufferSize;
        int bufferRemaining;
        MimePart part;
 
        public XmlMtomReader()
        {
        }
 
        internal static void DecrementBufferQuota(int maxBuffer, ref int remaining, int size)
        {
            if (remaining - size <= 0)
            {
                remaining = 0;
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomBufferQuotaExceeded, maxBuffer)));
            }
            else
            {
                remaining -= size;
            }
        }
 
        void SetReadEncodings(Encoding[] encodings)
        {
            if (encodings == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encodings");
 
            for (int i = 0; i < encodings.Length; i++)
            {
                if (encodings[i] == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(String.Format(CultureInfo.InvariantCulture, "encodings[{0}]", i));
            }
 
            this.encodings = new Encoding[encodings.Length];
            encodings.CopyTo(this.encodings, 0);
        }
 
        void CheckContentType(string contentType)
        {
            if (contentType != null && contentType.Length == 0)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.MtomContentTypeInvalid), "contentType"));
        }
 
        public void SetInput(byte[] buffer, int offset, int count, Encoding[] encodings, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize, OnXmlDictionaryReaderClose onClose)
        {
            SetInput(new MemoryStream(buffer, offset, count), encodings, contentType, quotas, maxBufferSize, onClose);
        }
 
        public void SetInput(Stream stream, Encoding[] encodings, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize, OnXmlDictionaryReaderClose onClose)
        {
            SetReadEncodings(encodings);
            CheckContentType(contentType);
            Initialize(stream, contentType, quotas, maxBufferSize);
            this.onClose = onClose;
        }
 
        void Initialize(Stream stream, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
 
            this.maxBufferSize = maxBufferSize;
            this.bufferRemaining = maxBufferSize;
 
            string boundary, start, startInfo;
 
            if (contentType == null)
            {
                MimeMessageReader messageReader = new MimeMessageReader(stream);
                MimeHeaders messageHeaders = messageReader.ReadHeaders(this.maxBufferSize, ref this.bufferRemaining);
                ReadMessageMimeVersionHeader(messageHeaders.MimeVersion);
                ReadMessageContentTypeHeader(messageHeaders.ContentType, out boundary, out start, out startInfo);
                stream = messageReader.GetContentStream();
                if (stream == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageInvalidContent)));
            }
            else
            {
                ReadMessageContentTypeHeader(new ContentTypeHeader(contentType), out boundary, out start, out startInfo);
            }
 
            this.mimeReader = new MimeReader(stream, boundary);
            this.mimeParts = null;
            this.readingBinaryElement = false;
 
            MimePart infosetPart = (start == null) ? ReadRootMimePart() : ReadMimePart(GetStartUri(start));
            byte[] infosetBytes = infosetPart.GetBuffer(this.maxBufferSize, ref this.bufferRemaining);
            int infosetByteCount = (int)infosetPart.Length;
 
            Encoding encoding = ReadRootContentTypeHeader(infosetPart.Headers.ContentType, this.encodings, startInfo);
            CheckContentTransferEncodingOnRoot(infosetPart.Headers.ContentTransferEncoding);
 
            IXmlTextReaderInitializer initializer = xmlReader as IXmlTextReaderInitializer;
 
            if (initializer != null)
                initializer.SetInput(infosetBytes, 0, infosetByteCount, encoding, quotas, null);
            else
                xmlReader = XmlDictionaryReader.CreateTextReader(infosetBytes, 0, infosetByteCount, encoding, quotas, null);
        }
 
        public override XmlDictionaryReaderQuotas Quotas
        {
            get
            {
                return this.xmlReader.Quotas;
            }
        }
 
        void ReadMessageMimeVersionHeader(MimeVersionHeader header)
        {
            if (header != null && header.Version != MimeVersionHeader.Default.Version)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageInvalidMimeVersion, header.Version, MimeVersionHeader.Default.Version)));
        }
 
        void ReadMessageContentTypeHeader(ContentTypeHeader header, out string boundary, out string start, out string startInfo)
        {
            if (header == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageContentTypeNotFound)));
 
            if (String.Compare(MtomGlobals.MediaType, header.MediaType, StringComparison.OrdinalIgnoreCase) != 0
                || String.Compare(MtomGlobals.MediaSubtype, header.MediaSubtype, StringComparison.OrdinalIgnoreCase) != 0)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageNotMultipart, MtomGlobals.MediaType, MtomGlobals.MediaSubtype)));
 
            string type;
            if (!header.Parameters.TryGetValue(MtomGlobals.TypeParam, out type) || MtomGlobals.XopType != type)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageNotApplicationXopXml, MtomGlobals.XopType)));
 
            if (!header.Parameters.TryGetValue(MtomGlobals.BoundaryParam, out boundary))
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageRequiredParamNotSpecified, MtomGlobals.BoundaryParam)));
            if (!MailBnfHelper.IsValidMimeBoundary(boundary))
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomBoundaryInvalid, boundary)));
 
            if (!header.Parameters.TryGetValue(MtomGlobals.StartParam, out start))
                start = null;
 
            if (!header.Parameters.TryGetValue(MtomGlobals.StartInfoParam, out startInfo))
                startInfo = null;
        }
 
        Encoding ReadRootContentTypeHeader(ContentTypeHeader header, Encoding[] expectedEncodings, string expectedType)
        {
            if (header == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootContentTypeNotFound)));
 
            if (String.Compare(MtomGlobals.XopMediaType, header.MediaType, StringComparison.OrdinalIgnoreCase) != 0
                || String.Compare(MtomGlobals.XopMediaSubtype, header.MediaSubtype, StringComparison.OrdinalIgnoreCase) != 0)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootNotApplicationXopXml, MtomGlobals.XopMediaType, MtomGlobals.XopMediaSubtype)));
 
            string charset;
            if (!header.Parameters.TryGetValue(MtomGlobals.CharsetParam, out charset)
                || charset == null || charset.Length == 0)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootRequiredParamNotSpecified, MtomGlobals.CharsetParam)));
            Encoding encoding = null;
            for (int i = 0; i < encodings.Length; i++)
            {
                if (String.Compare(charset, expectedEncodings[i].WebName, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    encoding = expectedEncodings[i];
                    break;
                }
            }
            if (encoding == null)
            {
                // Check for alternate names
                if (String.Compare(charset, "utf-16LE", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    for (int i = 0; i < encodings.Length; i++)
                    {
                        if (String.Compare(expectedEncodings[i].WebName, Encoding.Unicode.WebName, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            encoding = expectedEncodings[i];
                            break;
                        }
                    }
                }
                else if (String.Compare(charset, "utf-16BE", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    for (int i = 0; i < encodings.Length; i++)
                    {
                        if (String.Compare(expectedEncodings[i].WebName, Encoding.BigEndianUnicode.WebName, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            encoding = expectedEncodings[i];
                            break;
                        }
                    }
                }
 
                if (encoding == null)
                {
                    StringBuilder expectedCharSetStr = new StringBuilder();
                    for (int i = 0; i < encodings.Length; i++)
                    {
                        if (expectedCharSetStr.Length != 0)
                            expectedCharSetStr.Append(" | ");
                        expectedCharSetStr.Append(encodings[i].WebName);
                    }
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootUnexpectedCharset, charset, expectedCharSetStr.ToString())));
                }
            }
 
            if (expectedType != null)
            {
                string rootType;
                if (!header.Parameters.TryGetValue(MtomGlobals.TypeParam, out rootType)
                    || rootType == null || rootType.Length == 0)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootRequiredParamNotSpecified, MtomGlobals.TypeParam)));
                if (rootType != expectedType)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootUnexpectedType, rootType, expectedType)));
            }
 
            return encoding;
        }
 
        // 7bit is default encoding in the absence of content-transfer-encoding header 
        void CheckContentTransferEncodingOnRoot(ContentTransferEncodingHeader header)
        {
            if (header != null && header.ContentTransferEncoding == ContentTransferEncoding.Other)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomContentTransferEncodingNotSupported,
                                                                                      header.Value,
                                                                                      ContentTransferEncodingHeader.SevenBit.ContentTransferEncodingValue,
                                                                                      ContentTransferEncodingHeader.EightBit.ContentTransferEncodingValue,
                                                                                      ContentTransferEncodingHeader.Binary.ContentTransferEncodingValue)));
        }
 
        void CheckContentTransferEncodingOnBinaryPart(ContentTransferEncodingHeader header)
        {
            if (header == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomContentTransferEncodingNotPresent,
                    ContentTransferEncodingHeader.Binary.ContentTransferEncodingValue)));
            else if (header.ContentTransferEncoding != ContentTransferEncoding.Binary)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomInvalidTransferEncodingForMimePart,
                    header.Value, ContentTransferEncodingHeader.Binary.ContentTransferEncodingValue)));
        }
 
        string GetStartUri(string startUri)
        {
            if (startUri.StartsWith("<", StringComparison.Ordinal))
            {
                if (startUri.EndsWith(">", StringComparison.Ordinal))
                    return startUri;
                else
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomInvalidStartUri, startUri)));
            }
            else
                return String.Format(CultureInfo.InvariantCulture, "<{0}>", startUri);
        }
 
        public override bool Read()
        {
            bool retVal = xmlReader.Read();
 
            if (xmlReader.NodeType == XmlNodeType.Element)
            {
                XopIncludeReader binaryDataReader = null;
                if (xmlReader.IsStartElement(MtomGlobals.XopIncludeLocalName, MtomGlobals.XopIncludeNamespace))
                {
                    string uri = null;
                    while (xmlReader.MoveToNextAttribute())
                    {
                        if (xmlReader.LocalName == MtomGlobals.XopIncludeHrefLocalName && xmlReader.NamespaceURI == MtomGlobals.XopIncludeHrefNamespace)
                            uri = xmlReader.Value;
                        else if (xmlReader.NamespaceURI == MtomGlobals.XopIncludeNamespace)
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomXopIncludeInvalidXopAttributes, xmlReader.LocalName, MtomGlobals.XopIncludeNamespace)));
                    }
                    if (uri == null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomXopIncludeHrefNotSpecified, MtomGlobals.XopIncludeHrefLocalName)));
 
                    MimePart mimePart = ReadMimePart(uri);
 
                    CheckContentTransferEncodingOnBinaryPart(mimePart.Headers.ContentTransferEncoding);
 
                    this.part = mimePart;
                    binaryDataReader = new XopIncludeReader(mimePart, xmlReader);
                    binaryDataReader.Read();
 
                    xmlReader.MoveToElement();
                    if (xmlReader.IsEmptyElement)
                    {
                        xmlReader.Read();
                    }
                    else
                    {
                        int xopDepth = xmlReader.Depth;
                        xmlReader.ReadStartElement();
 
                        while (xmlReader.Depth > xopDepth)
                        {
                            if (xmlReader.IsStartElement() && xmlReader.NamespaceURI == MtomGlobals.XopIncludeNamespace)
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomXopIncludeInvalidXopElement, xmlReader.LocalName, MtomGlobals.XopIncludeNamespace)));
 
                            xmlReader.Skip();
                        }
 
                        xmlReader.ReadEndElement();
                    }
                }
 
                if (binaryDataReader != null)
                {
                    this.xmlReader.MoveToContent();
                    this.infosetReader = this.xmlReader;
                    this.xmlReader = binaryDataReader;
                    binaryDataReader = null;
                }
            }
 
            if (xmlReader.ReadState == ReadState.EndOfFile && infosetReader != null)
            {
                // Read past the containing EndElement if necessary
                if (!retVal)
                    retVal = infosetReader.Read();
 
                this.part.Release(this.maxBufferSize, ref this.bufferRemaining);
                xmlReader = infosetReader;
                infosetReader = null;
            }
 
            return retVal;
        }
 
        MimePart ReadMimePart(string uri)
        {
            MimePart part = null;
 
            if (uri == null || uri.Length == 0)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomInvalidEmptyURI)));
 
            string contentID = null;
            if (uri.StartsWith(MimeGlobals.ContentIDScheme, StringComparison.Ordinal))
                contentID = String.Format(CultureInfo.InvariantCulture, "<{0}>", Uri.UnescapeDataString(uri.Substring(MimeGlobals.ContentIDScheme.Length)));
            else if (uri.StartsWith("<", StringComparison.Ordinal))
                contentID = uri;
 
            if (contentID == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomInvalidCIDUri, uri)));
 
            if (mimeParts != null && mimeParts.TryGetValue(contentID, out part))
            {
                if (part.ReferencedFromInfoset)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMimePartReferencedMoreThanOnce, contentID)));
            }
            else
            {
                int maxMimeParts = AppSettings.MaxMimeParts;
                while (part == null && mimeReader.ReadNextPart())
                {
                    MimeHeaders headers = mimeReader.ReadHeaders(this.maxBufferSize, ref this.bufferRemaining);
                    Stream contentStream = mimeReader.GetContentStream();
                    if (contentStream == null)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageInvalidContentInMimePart)));
 
                    ContentIDHeader contentIDHeader = (headers == null) ? null : headers.ContentID;
                    if (contentIDHeader == null || contentIDHeader.Value == null)
                    {
                        // Skip content if Content-ID header is not present
                        int size = 256;
                        byte[] bytes = new byte[size];
 
                        int read = 0;
                        do
                        {
                            read = contentStream.Read(bytes, 0, size);
                        }
                        while (read > 0);
                        continue;
                    }
 
                    string currentContentID = headers.ContentID.Value;
                    MimePart currentPart = new MimePart(contentStream, headers);
                    if (mimeParts == null)
                        mimeParts = new Dictionary<string, MimePart>();
 
                    mimeParts.Add(currentContentID, currentPart);
 
                    if (mimeParts.Count > maxMimeParts)
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MaxMimePartsExceeded, maxMimeParts, AppSettings.MaxMimePartsAppSettingsString)));
 
                    if (currentContentID.Equals(contentID))
                        part = currentPart;
                    else
                        currentPart.GetBuffer(this.maxBufferSize, ref this.bufferRemaining);
                }
 
                if (part == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomPartNotFound, uri)));
            }
 
            part.ReferencedFromInfoset = true;
            return part;
        }
 
        MimePart ReadRootMimePart()
        {
            MimePart part = null;
 
            if (!mimeReader.ReadNextPart())
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomRootPartNotFound)));
 
            MimeHeaders headers = mimeReader.ReadHeaders(this.maxBufferSize, ref this.bufferRemaining);
            Stream contentStream = mimeReader.GetContentStream();
            if (contentStream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.MtomMessageInvalidContentInMimePart)));
            part = new MimePart(contentStream, headers);
 
            return part;
        }
 
        void AdvanceToContentOnElement()
        {
            if (NodeType != XmlNodeType.Attribute)
            {
                MoveToContent();
            }
        }
 
        public override int AttributeCount
        {
            get
            {
                return xmlReader.AttributeCount;
            }
        }
 
        public override string BaseURI
        {
            get
            {
                return xmlReader.BaseURI;
            }
        }
 
        public override bool CanReadBinaryContent
        {
            get
            {
                return xmlReader.CanReadBinaryContent;
            }
        }
 
        public override bool CanReadValueChunk
        {
            get
            {
                return xmlReader.CanReadValueChunk;
            }
        }
 
        public override bool CanResolveEntity
        {
            get
            {
                return xmlReader.CanResolveEntity;
            }
        }
 
        public override void Close()
        {
            xmlReader.Close();
            mimeReader.Close();
            OnXmlDictionaryReaderClose onClose = this.onClose;
            this.onClose = null;
            if (onClose != null)
            {
                try
                {
                    onClose(this);
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e)) throw;
 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
                }
            }
        }
 
        public override int Depth
        {
            get
            {
                return xmlReader.Depth;
            }
        }
 
        public override bool EOF
        {
            get
            {
                return xmlReader.EOF;
            }
        }
 
        public override string GetAttribute(int index)
        {
            return xmlReader.GetAttribute(index);
        }
 
        public override string GetAttribute(string name)
        {
            return xmlReader.GetAttribute(name);
        }
 
        public override string GetAttribute(string name, string ns)
        {
            return xmlReader.GetAttribute(name, ns);
        }
 
        public override string GetAttribute(XmlDictionaryString localName, XmlDictionaryString ns)
        {
            return xmlReader.GetAttribute(localName, ns);
        }
#if NO
        public override ArraySegment<byte> GetSubset(bool advance) 
        { 
            return xmlReader.GetSubset(advance); 
        }
#endif
        public override bool HasAttributes
        {
            get
            {
                return xmlReader.HasAttributes;
            }
        }
 
        public override bool HasValue
        {
            get
            {
                return xmlReader.HasValue;
            }
        }
 
        public override bool IsDefault
        {
            get
            {
                return xmlReader.IsDefault;
            }
        }
 
        public override bool IsEmptyElement
        {
            get
            {
                return xmlReader.IsEmptyElement;
            }
        }
 
        public override bool IsLocalName(string localName)
        {
            return xmlReader.IsLocalName(localName);
        }
 
        public override bool IsLocalName(XmlDictionaryString localName)
        {
            return xmlReader.IsLocalName(localName);
        }
 
        public override bool IsNamespaceUri(string ns)
        {
            return xmlReader.IsNamespaceUri(ns);
        }
 
        public override bool IsNamespaceUri(XmlDictionaryString ns)
        {
            return xmlReader.IsNamespaceUri(ns);
        }
 
        public override bool IsStartElement()
        {
            return xmlReader.IsStartElement();
        }
 
        public override bool IsStartElement(string localName)
        {
            return xmlReader.IsStartElement(localName);
        }
 
        public override bool IsStartElement(string localName, string ns)
        {
            return xmlReader.IsStartElement(localName, ns);
        }
 
        public override bool IsStartElement(XmlDictionaryString localName, XmlDictionaryString ns)
        {
            return xmlReader.IsStartElement(localName, ns);
        }
#if NO
        public override bool IsStartSubsetElement()
        {
            return xmlReader.IsStartSubsetElement();
        }
#endif
        public override string LocalName
        {
            get
            {
                return xmlReader.LocalName;
            }
        }
 
        public override string LookupNamespace(string ns)
        {
            return xmlReader.LookupNamespace(ns);
        }
 
        public override void MoveToAttribute(int index)
        {
            xmlReader.MoveToAttribute(index);
        }
 
        public override bool MoveToAttribute(string name)
        {
            return xmlReader.MoveToAttribute(name);
        }
 
        public override bool MoveToAttribute(string name, string ns)
        {
            return xmlReader.MoveToAttribute(name, ns);
        }
 
        public override bool MoveToElement()
        {
            return xmlReader.MoveToElement();
        }
 
        public override bool MoveToFirstAttribute()
        {
            return xmlReader.MoveToFirstAttribute();
        }
 
        public override bool MoveToNextAttribute()
        {
            return xmlReader.MoveToNextAttribute();
        }
 
        public override string Name
        {
            get
            {
                return xmlReader.Name;
            }
        }
 
        public override string NamespaceURI
        {
            get
            {
                return xmlReader.NamespaceURI;
            }
        }
 
        public override XmlNameTable NameTable
        {
            get
            {
                return xmlReader.NameTable;
            }
        }
 
        public override XmlNodeType NodeType
        {
            get
            {
                return xmlReader.NodeType;
            }
        }
 
        public override string Prefix
        {
            get
            {
                return xmlReader.Prefix;
            }
        }
 
        public override char QuoteChar
        {
            get
            {
                return xmlReader.QuoteChar;
            }
        }
 
        public override bool ReadAttributeValue()
        {
            return xmlReader.ReadAttributeValue();
        }
 
        public override object ReadContentAs(Type returnType, IXmlNamespaceResolver namespaceResolver)
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAs(returnType, namespaceResolver);
        }
 
        public override byte[] ReadContentAsBase64()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsBase64();
        }
 
        public override int ReadValueAsBase64(byte[] buffer, int offset, int count)
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadValueAsBase64(buffer, offset, count);
        }
 
        public override int ReadContentAsBase64(byte[] buffer, int offset, int count)
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsBase64(buffer, offset, count);
        }
 
        public override int ReadElementContentAsBase64(byte[] buffer, int offset, int count)
        {
            if (!readingBinaryElement)
            {
                if (IsEmptyElement)
                {
                    Read();
                    return 0;
                }
 
                ReadStartElement();
                readingBinaryElement = true;
            }
 
            int i = ReadContentAsBase64(buffer, offset, count);
 
            if (i == 0)
            {
                ReadEndElement();
                readingBinaryElement = false;
            }
 
            return i;
        }
 
        public override int ReadElementContentAsBinHex(byte[] buffer, int offset, int count)
        {
            if (!readingBinaryElement)
            {
                if (IsEmptyElement)
                {
                    Read();
                    return 0;
                }
 
                ReadStartElement();
                readingBinaryElement = true;
            }
 
            int i = ReadContentAsBinHex(buffer, offset, count);
 
            if (i == 0)
            {
                ReadEndElement();
                readingBinaryElement = false;
            }
 
            return i;
        }
 
        public override int ReadContentAsBinHex(byte[] buffer, int offset, int count)
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsBinHex(buffer, offset, count);
        }
 
        public override bool ReadContentAsBoolean()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsBoolean();
        }
 
        public override int ReadContentAsChars(char[] chars, int index, int count)
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsChars(chars, index, count);
        }
 
        public override DateTime ReadContentAsDateTime()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsDateTime();
        }
 
        public override decimal ReadContentAsDecimal()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsDecimal();
        }
 
        public override double ReadContentAsDouble()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsDouble();
        }
 
        public override int ReadContentAsInt()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsInt();
        }
 
        public override long ReadContentAsLong()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsLong();
        }
#if NO
        public override ICollection ReadContentAsList()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsList();
        }
#endif
        public override object ReadContentAsObject()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsObject();
        }
 
        public override float ReadContentAsFloat()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsFloat();
        }
 
        public override string ReadContentAsString()
        {
            AdvanceToContentOnElement();
            return xmlReader.ReadContentAsString();
        }
 
        public override string ReadInnerXml()
        {
            return xmlReader.ReadInnerXml();
        }
 
        public override string ReadOuterXml()
        {
            return xmlReader.ReadOuterXml();
        }
 
        public override ReadState ReadState
        {
            get
            {
                if (xmlReader.ReadState != ReadState.Interactive && infosetReader != null)
                    return infosetReader.ReadState;
 
                return xmlReader.ReadState;
            }
        }
 
        public override int ReadValueChunk(char[] buffer, int index, int count)
        {
            return xmlReader.ReadValueChunk(buffer, index, count);
        }
 
        public override void ResolveEntity()
        {
            xmlReader.ResolveEntity();
        }
 
        public override XmlReaderSettings Settings
        {
            get
            {
                return xmlReader.Settings;
            }
        }
 
        public override void Skip()
        {
            xmlReader.Skip();
        }
 
        public override string this[int index]
        {
            get
            {
                return xmlReader[index];
            }
        }
 
        public override string this[string name]
        {
            get
            {
                return xmlReader[name];
            }
        }
 
        public override string this[string name, string ns]
        {
            get
            {
                return xmlReader[name, ns];
            }
        }
 
        public override string Value
        {
            get
            {
                return xmlReader.Value;
            }
        }
 
        public override Type ValueType
        {
            get
            {
                return xmlReader.ValueType;
            }
        }
 
        public override string XmlLang
        {
            get
            {
                return xmlReader.XmlLang;
            }
        }
 
        public override XmlSpace XmlSpace
        {
            get
            {
                return xmlReader.XmlSpace;
            }
        }
 
        public bool HasLineInfo()
        {
            if (xmlReader.ReadState == ReadState.Closed)
                return false;
 
            IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo;
            if (lineInfo == null)
                return false;
            return lineInfo.HasLineInfo();
        }
 
        public int LineNumber
        {
            get
            {
                if (xmlReader.ReadState == ReadState.Closed)
                    return 0;
 
                IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo;
                if (lineInfo == null)
                    return 0;
                return lineInfo.LineNumber;
            }
        }
 
        public int LinePosition
        {
            get
            {
                if (xmlReader.ReadState == ReadState.Closed)
                    return 0;
 
                IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo;
                if (lineInfo == null)
                    return 0;
                return lineInfo.LinePosition;
            }
        }
 
        internal class MimePart
        {
            Stream stream;
            MimeHeaders headers;
            byte[] buffer;
            bool isReferencedFromInfoset;
 
            internal MimePart(Stream stream, MimeHeaders headers)
            {
                this.stream = stream;
                this.headers = headers;
            }
 
            internal Stream Stream
            {
                get { return stream; }
            }
 
            internal MimeHeaders Headers
            {
                get { return headers; }
            }
 
            internal bool ReferencedFromInfoset
            {
                get { return isReferencedFromInfoset; }
                set { isReferencedFromInfoset = value; }
            }
 
            internal long Length
            {
                get { return stream.CanSeek ? stream.Length : 0; }
            }
 
            internal byte[] GetBuffer(int maxBuffer, ref int remaining)
            {
                if (buffer == null)
                {
                    MemoryStream bufferedStream = stream.CanSeek ? new MemoryStream((int)stream.Length) : new MemoryStream();
                    int size = 256;
                    byte[] bytes = new byte[size];
 
                    int read = 0;
 
                    do
                    {
                        read = stream.Read(bytes, 0, size);
                        XmlMtomReader.DecrementBufferQuota(maxBuffer, ref remaining, read);
                        if (read > 0)
                            bufferedStream.Write(bytes, 0, read);
                    }
                    while (read > 0);
 
                    bufferedStream.Seek(0, SeekOrigin.Begin);
                    buffer = bufferedStream.GetBuffer();
                    stream = bufferedStream;
                }
                return buffer;
            }
 
            internal void Release(int maxBuffer, ref int remaining)
            {
                remaining += (int)this.Length;
                this.headers.Release(ref remaining);
            }
        }
 
        internal class XopIncludeReader : XmlDictionaryReader, IXmlLineInfo
        {
            int chunkSize = 4096;  // Just a default.  Serves as a max chunk size.
            int bytesRemaining;
 
            MimePart part;
            ReadState readState;
            XmlDictionaryReader parentReader;
            string stringValue;
            int stringOffset;
            XmlNodeType nodeType;
            MemoryStream binHexStream;
            byte[] valueBuffer;
            int valueOffset;
            int valueCount;
            bool finishedStream;
 
            public XopIncludeReader(MimePart part, XmlDictionaryReader reader)
            {
                if (part == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("part");
                if (reader == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
 
                this.part = part;
                this.parentReader = reader;
                this.readState = ReadState.Initial;
                this.nodeType = XmlNodeType.None;
                this.chunkSize = Math.Min(reader.Quotas.MaxBytesPerRead, chunkSize);
                this.bytesRemaining = this.chunkSize;
                this.finishedStream = false;
            }
 
            public override XmlDictionaryReaderQuotas Quotas
            {
                get
                {
                    return this.parentReader.Quotas;
                }
            }
 
            public override XmlNodeType NodeType
            {
                get
                {
                    return (readState == ReadState.Interactive) ? nodeType : parentReader.NodeType;
                }
            }
 
            public override bool Read()
            {
                bool retVal = true;
                switch (readState)
                {
                    case ReadState.Initial:
                        readState = ReadState.Interactive;
                        nodeType = XmlNodeType.Text;
                        break;
                    case ReadState.Interactive:
                        if (this.finishedStream || (this.bytesRemaining == this.chunkSize && this.stringValue == null))
                        {
                            readState = ReadState.EndOfFile;
                            nodeType = XmlNodeType.EndElement;
                        }
                        else
                        {
                            this.bytesRemaining = this.chunkSize;
                        }
                        break;
                    case ReadState.EndOfFile:
                        nodeType = XmlNodeType.None;
                        retVal = false;
                        break;
                }
                this.stringValue = null;
                this.binHexStream = null;
                this.valueOffset = 0;
                this.valueCount = 0;
                this.stringOffset = 0;
                CloseStreams();
                return retVal;
            }
 
            public override int ReadValueAsBase64(byte[] buffer, int offset, int count)
            {
                if (buffer == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
 
                if (offset < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
                if (offset > buffer.Length)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, buffer.Length)));
                if (count < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
                if (count > buffer.Length - offset)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset)));
 
                if (this.stringValue != null)
                {
                    count = Math.Min(count, this.valueCount);
                    if (count > 0)
                    {
                        Buffer.BlockCopy(this.valueBuffer, this.valueOffset, buffer, offset, count);
                        this.valueOffset += count;
                        this.valueCount -= count;
                    }
                    return count;
                }
 
                if (this.bytesRemaining < count)
                    count = this.bytesRemaining;
 
                int read = 0;
                if (readState == ReadState.Interactive)
                {
                    while (read < count)
                    {
                        int actual = part.Stream.Read(buffer, offset + read, count - read);
                        if (actual == 0)
                        {
                            this.finishedStream = true;
                            break;
                        }
                        read += actual;
                    }
                }
                this.bytesRemaining -= read;
                return read;
            }
 
            public override int ReadContentAsBase64(byte[] buffer, int offset, int count)
            {
                if (buffer == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
 
                if (offset < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
                if (offset > buffer.Length)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, buffer.Length)));
                if (count < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
                if (count > buffer.Length - offset)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset)));
 
                if (this.valueCount > 0)
                {
                    count = Math.Min(count, this.valueCount);
                    Buffer.BlockCopy(this.valueBuffer, this.valueOffset, buffer, offset, count);
                    this.valueOffset += count;
                    this.valueCount -= count;
                    return count;
                }
 
                if (this.chunkSize < count)
                    count = this.chunkSize;
 
                int read = 0;
                if (readState == ReadState.Interactive)
                {
                    while (read < count)
                    {
                        int actual = part.Stream.Read(buffer, offset + read, count - read);
                        if (actual == 0)
                        {
                            this.finishedStream = true;
                            if (!Read())
                                break;
                        }
                        read += actual;
                    }
                }
                this.bytesRemaining = this.chunkSize;
                return read;
            }
 
            public override int ReadContentAsBinHex(byte[] buffer, int offset, int count)
            {
                if (buffer == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
 
                if (offset < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
                if (offset > buffer.Length)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, buffer.Length)));
                if (count < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
                if (count > buffer.Length - offset)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset)));
 
                if (this.chunkSize < count)
                    count = this.chunkSize;
 
                int read = 0;
                int consumed = 0;
                while (read < count)
                {
                    if (binHexStream == null)
                    {
                        try
                        {
                            binHexStream = new MemoryStream(new BinHexEncoding().GetBytes(this.Value));
                        }
                        catch (FormatException e) // Wrap format exceptions from decoding document contents
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(e.Message, e));
                        }
                    }
 
                    int actual = binHexStream.Read(buffer, offset + read, count - read);
                    if (actual == 0)
                    {
                        this.finishedStream = true;
                        if (!Read())
                            break;
 
                        consumed = 0;
                    }
 
                    read += actual;
                    consumed += actual;
                }
 
                // Trim off the consumed chars
                if (this.stringValue != null && consumed > 0)
                {
                    this.stringValue = this.stringValue.Substring(consumed * 2);
                    this.stringOffset = Math.Max(0, this.stringOffset - consumed * 2);
 
                    this.bytesRemaining = this.chunkSize;
                }
                return read;
            }
 
            public override int ReadValueChunk(char[] chars, int offset, int count)
            {
                if (chars == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("chars");
 
                if (offset < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
                if (offset > chars.Length)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, chars.Length)));
                if (count < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
                if (count > chars.Length - offset)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset)));
 
                if (readState != ReadState.Interactive)
                    return 0;
 
                // Copy characters from the Value property
                string str = this.Value;
                count = Math.Min(stringValue.Length - stringOffset, count);
                if (count > 0)
                {
                    stringValue.CopyTo(stringOffset, chars, offset, count);
                    stringOffset += count;
                }
                return count;
            }
 
            public override string Value
            {
                get
                {
                    if (readState != ReadState.Interactive)
                        return String.Empty;
 
                    if (stringValue == null)
                    {
                        // Compute the bytes to read
                        int byteCount = this.bytesRemaining;
                        byteCount -= byteCount % 3;
 
                        // Handle trailing bytes
                        if (this.valueCount > 0 && this.valueOffset > 0)
                        {
                            Buffer.BlockCopy(this.valueBuffer, this.valueOffset, this.valueBuffer, 0, this.valueCount);
                            this.valueOffset = 0;
                        }
                        byteCount -= this.valueCount;
 
                        // Resize buffer if needed
                        if (this.valueBuffer == null)
                        {
                            this.valueBuffer = new byte[byteCount];
                        }
                        else if (this.valueBuffer.Length < byteCount)
                        {
                            Array.Resize(ref this.valueBuffer, byteCount);
                        }
                        byte[] buffer = this.valueBuffer;
 
                        // Fill up the buffer
                        int offset = 0;
                        int read = 0;
                        while (byteCount > 0)
                        {
                            read = part.Stream.Read(buffer, offset, byteCount);
                            if (read == 0)
                            {
                                this.finishedStream = true;
                                break;
                            }
 
                            this.bytesRemaining -= read;
                            this.valueCount += read;
                            byteCount -= read;
                            offset += read;
                        }
 
                        // Convert the bytes
                        this.stringValue = Convert.ToBase64String(buffer, 0, this.valueCount);
                    }
                    return this.stringValue;
                }
            }
 
            public override string ReadContentAsString()
            {
                int stringContentQuota = this.Quotas.MaxStringContentLength;
                StringBuilder sb = new StringBuilder();
                do
                {
                    string val = this.Value;
                    if (val.Length > stringContentQuota)
                        XmlExceptionHelper.ThrowMaxStringContentLengthExceeded(this, this.Quotas.MaxStringContentLength);
                    stringContentQuota -= val.Length;
                    sb.Append(val);
                } while (Read());
                return sb.ToString();
            }
 
            public override int AttributeCount
            {
                get { return 0; }
            }
 
            public override string BaseURI
            {
                get { return parentReader.BaseURI; }
            }
 
            public override bool CanReadBinaryContent
            {
                get { return true; }
            }
 
            public override bool CanReadValueChunk
            {
                get { return true; }
            }
 
            public override bool CanResolveEntity
            {
                get { return parentReader.CanResolveEntity; }
            }
 
            public override void Close()
            {
                CloseStreams();
                readState = ReadState.Closed;
            }
 
            void CloseStreams()
            {
                if (binHexStream != null)
                {
                    binHexStream.Close();
                    binHexStream = null;
                }
            }
 
            public override int Depth
            {
                get
                {
                    return (readState == ReadState.Interactive) ? parentReader.Depth + 1 : parentReader.Depth;
                }
            }
 
            public override bool EOF
            {
                get { return readState == ReadState.EndOfFile; }
            }
 
            public override string GetAttribute(int index)
            {
                return null;
            }
 
            public override string GetAttribute(string name)
            {
                return null;
            }
 
            public override string GetAttribute(string name, string ns)
            {
                return null;
            }
 
            public override string GetAttribute(XmlDictionaryString localName, XmlDictionaryString ns)
            {
                return null;
            }
 
            public override bool HasAttributes
            {
                get { return false; }
            }
 
            public override bool HasValue
            {
                get { return readState == ReadState.Interactive; }
            }
 
            public override bool IsDefault
            {
                get { return false; }
            }
 
            public override bool IsEmptyElement
            {
                get { return false; }
            }
 
            public override bool IsLocalName(string localName)
            {
                return false;
            }
 
            public override bool IsLocalName(XmlDictionaryString localName)
            {
                return false;
            }
 
            public override bool IsNamespaceUri(string ns)
            {
                return false;
            }
 
            public override bool IsNamespaceUri(XmlDictionaryString ns)
            {
                return false;
            }
 
            public override bool IsStartElement()
            {
                return false;
            }
 
            public override bool IsStartElement(string localName)
            {
                return false;
            }
 
            public override bool IsStartElement(string localName, string ns)
            {
                return false;
            }
 
            public override bool IsStartElement(XmlDictionaryString localName, XmlDictionaryString ns)
            {
                return false;
            }
#if NO
            public override bool IsStartSubsetElement()
            {
                return false;
            }
#endif
            public override string LocalName
            {
                get
                {
                    return (readState == ReadState.Interactive) ? String.Empty : parentReader.LocalName;
                }
            }
 
            public override string LookupNamespace(string ns)
            {
                return parentReader.LookupNamespace(ns);
            }
 
            public override void MoveToAttribute(int index)
            {
            }
 
            public override bool MoveToAttribute(string name)
            {
                return false;
            }
 
            public override bool MoveToAttribute(string name, string ns)
            {
                return false;
            }
 
            public override bool MoveToElement()
            {
                return false;
            }
 
            public override bool MoveToFirstAttribute()
            {
                return false;
            }
 
            public override bool MoveToNextAttribute()
            {
                return false;
            }
 
            public override string Name
            {
                get
                {
                    return (readState == ReadState.Interactive) ? String.Empty : parentReader.Name;
                }
            }
 
            public override string NamespaceURI
            {
                get
                {
                    return (readState == ReadState.Interactive) ? String.Empty : parentReader.NamespaceURI;
                }
            }
 
            public override XmlNameTable NameTable
            {
                get { return parentReader.NameTable; }
            }
 
            public override string Prefix
            {
                get
                {
                    return (readState == ReadState.Interactive) ? String.Empty : parentReader.Prefix;
                }
            }
 
            public override char QuoteChar
            {
                get { return parentReader.QuoteChar; }
            }
 
            public override bool ReadAttributeValue()
            {
                return false;
            }
 
            public override string ReadInnerXml()
            {
                return ReadContentAsString();
            }
 
            public override string ReadOuterXml()
            {
                return ReadContentAsString();
            }
 
            public override ReadState ReadState
            {
                get { return readState; }
            }
 
            public override void ResolveEntity()
            {
            }
 
            public override XmlReaderSettings Settings
            {
                get { return parentReader.Settings; }
            }
 
            public override void Skip()
            {
                Read();
            }
 
            public override string this[int index]
            {
                get { return null; }
            }
 
            public override string this[string name]
            {
                get { return null; }
            }
 
            public override string this[string name, string ns]
            {
                get { return null; }
            }
 
            public override string XmlLang
            {
                get { return parentReader.XmlLang; }
            }
 
            public override XmlSpace XmlSpace
            {
                get { return parentReader.XmlSpace; }
            }
 
            public override Type ValueType
            {
                get
                {
                    return (readState == ReadState.Interactive) ? typeof(byte[]) : parentReader.ValueType;
                }
            }
 
            bool IXmlLineInfo.HasLineInfo()
            {
                return ((IXmlLineInfo)parentReader).HasLineInfo();
            }
 
            int IXmlLineInfo.LineNumber
            {
                get
                {
                    return ((IXmlLineInfo)parentReader).LineNumber;
                }
            }
 
            int IXmlLineInfo.LinePosition
            {
                get
                {
                    return ((IXmlLineInfo)parentReader).LinePosition;
                }
            }
        }
    }
 
    internal class MimeMessageReader
    {
        static byte[] CRLFCRLF = new byte[] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
 
        bool getContentStreamCalled;
        MimeHeaderReader mimeHeaderReader;
        DelimittedStreamReader reader;
 
        public MimeMessageReader(Stream stream)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
 
            this.reader = new DelimittedStreamReader(stream);
            this.mimeHeaderReader = new MimeHeaderReader(this.reader.GetNextStream(CRLFCRLF));
        }
 
        public Stream GetContentStream()
        {
            if (getContentStreamCalled)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.MimeMessageGetContentStreamCalledAlready)));
 
            mimeHeaderReader.Close();
 
            Stream s = reader.GetNextStream(null);
 
            getContentStreamCalled = true;
 
            return s;
        }
 
        public MimeHeaders ReadHeaders(int maxBuffer, ref int remaining)
        {
            MimeHeaders headers = new MimeHeaders();
            while (mimeHeaderReader.Read(maxBuffer, ref remaining))
            {
                headers.Add(mimeHeaderReader.Name, mimeHeaderReader.Value, ref remaining);
            }
            return headers;
        }
    }
 
    internal class MimeReader
    {
        static byte[] CRLFCRLF = new byte[] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
 
        byte[] boundaryBytes;
        string content;
        Stream currentStream;
        MimeHeaderReader mimeHeaderReader;
        DelimittedStreamReader reader;
        byte[] scratch = new byte[2];
 
        public MimeReader(Stream stream, string boundary)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
            if (boundary == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("boundary");
 
            this.reader = new DelimittedStreamReader(stream);
            this.boundaryBytes = MimeWriter.GetBoundaryBytes(boundary);
 
            // Need to ensure that the content begins with a CRLF, in case the 
            // outer construct has consumed the trailing CRLF
            this.reader.Push(this.boundaryBytes, 0, 2);
        }
 
        public void Close()
        {
            this.reader.Close();
        }
 
        /// Gets the content preceding the first part of the MIME multi-part message
        public string Preface
        {
            get
            {
                if (content == null)
                {
                    Stream s = this.reader.GetNextStream(this.boundaryBytes);
                    content = new StreamReader(s, System.Text.Encoding.ASCII, false, 256).ReadToEnd();
                    s.Close();
                    if (content == null)
                        content = string.Empty;
                }
                return content;
            }
        }
 
        public Stream GetContentStream()
        {
            Fx.Assert(content != null, "");
 
            mimeHeaderReader.Close();
 
            return reader.GetNextStream(this.boundaryBytes);
        }
 
        public bool ReadNextPart()
        {
            string content = Preface;
 
            if (currentStream != null)
            {
                currentStream.Close();
                currentStream = null;
            }
 
            Stream stream = reader.GetNextStream(CRLFCRLF);
 
            if (stream == null)
                return false;
 
            if (BlockRead(stream, scratch, 0, 2) == 2)
            {
                if (scratch[0] == '\r' && scratch[1] == '\n')
                {
                    if (mimeHeaderReader == null)
                        mimeHeaderReader = new MimeHeaderReader(stream);
                    else
                        mimeHeaderReader.Reset(stream);
                    return true;
                }
                else if (scratch[0] == '-' && scratch[1] == '-')
                {
                    int read = BlockRead(stream, scratch, 0, 2);
 
                    if (read < 2 || (scratch[0] == '\r' && scratch[1] == '\n'))
                        return false;
                }
            }
 
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderTruncated)));
        }
 
        public MimeHeaders ReadHeaders(int maxBuffer, ref int remaining)
        {
            MimeHeaders headers = new MimeHeaders();
            while (mimeHeaderReader.Read(maxBuffer, ref remaining))
            {
                headers.Add(mimeHeaderReader.Name, mimeHeaderReader.Value, ref remaining);
            }
            return headers;
        }
 
        int BlockRead(Stream stream, byte[] buffer, int offset, int count)
        {
            int read = 0;
            do
            {
                int r = stream.Read(buffer, offset + read, count - read);
                if (r == 0)
                    break;
                read += r;
            } while (read < count);
            return read;
        }
 
    }
 
    internal class DelimittedStreamReader
    {
        bool canGetNextStream = true;
 
        // used for closing the reader, and validating that only one stream can be reading at a time.
        DelimittedReadStream currentStream;
 
        byte[] delimitter;
        byte[] matchBuffer;
        byte[] scratch;
 
        BufferedReadStream stream;
 
        public DelimittedStreamReader(Stream stream)
        {
            this.stream = new BufferedReadStream(stream);
        }
 
        public void Close()
        {
            this.stream.Close();
        }
 
        // Closes the current stream.  If the current stream is not the same as the caller, nothing is done.
        void Close(DelimittedReadStream caller)
        {
            if (currentStream == caller)
            {
                if (delimitter == null)
                {
                    stream.Close();
                }
                else
                {
                    if (scratch == null)
                    {
                        scratch = new byte[1024];
                    }
                    while (0 != Read(caller, this.scratch, 0, this.scratch.Length));
                }
 
                currentStream = null;
            }
        }
 
        // Gets the next logical stream delimitted by the given sequence.
        public Stream GetNextStream(byte[] delimitter)
        {
            if (currentStream != null)
            {
                currentStream.Close();
                currentStream = null;
            }
 
            if (!canGetNextStream)
                return null;
 
            this.delimitter = delimitter;
 
            canGetNextStream = delimitter != null;
 
            currentStream = new DelimittedReadStream(this);
 
            return currentStream;
        }
 
        enum MatchState
        {
            True,
            False,
            InsufficientData
        }
 
        MatchState MatchDelimitter(byte[] buffer, int start, int end)
        {
            if (this.delimitter.Length > end - start)
            {
                for (int i = end - start - 1; i >= 1; i--)
                {
                    if (buffer[start + i] != delimitter[i])
                        return MatchState.False;
                }
                return MatchState.InsufficientData;
            }
            for (int i = delimitter.Length - 1; i >= 1; i--)
            {
                if (buffer[start + i] != delimitter[i])
                    return MatchState.False;
            }
            return MatchState.True;
        }
 
        int ProcessRead(byte[] buffer, int offset, int read)
        {
            // nothing to process if 0 bytes were read
            if (read == 0)
                return read;
 
            for (int ptr = offset, end = offset + read; ptr < end; ptr++)
            {
                if (buffer[ptr] == delimitter[0])
                {
                    switch (MatchDelimitter(buffer, ptr, end))
                    {
                        case MatchState.True:
                            {
                                int actual = ptr - offset;
                                ptr += this.delimitter.Length;
                                this.stream.Push(buffer, ptr, end - ptr);
                                this.currentStream = null;
                                return actual;
                            }
                        case MatchState.False:
                            break;
                        case MatchState.InsufficientData:
                            {
                                int actual = ptr - offset;
                                if (actual > 0)
                                {
                                    this.stream.Push(buffer, ptr, end - ptr);
                                    return actual;
                                }
                                else
                                {
                                    return -1;
                                }
                            }
                    }
                }
            }
            return read;
        }
 
        int Read(DelimittedReadStream caller, byte[] buffer, int offset, int count)
        {
            if (this.currentStream != caller)
                return 0;
 
            int read = this.stream.Read(buffer, offset, count);
            if (read == 0)
            {
                this.canGetNextStream = false;
                this.currentStream = null;
                return read;
            }
 
            // If delimitter is null, read until the underlying stream returns 0 bytes
            if (this.delimitter == null)
                return read;
 
            // Scans the read data for the delimitter. If found, the number of bytes read are adjusted
            // to account for the number of bytes up to but not including the delimitter.
            int actual = ProcessRead(buffer, offset, read);
 
            if (actual < 0)
            {
                if (this.matchBuffer == null || this.matchBuffer.Length < this.delimitter.Length - read)
                    this.matchBuffer = new byte[this.delimitter.Length - read];
 
                int matched = this.stream.ReadBlock(this.matchBuffer, 0, this.delimitter.Length - read);
 
                if (MatchRemainder(read, matched))
                {
                    this.currentStream = null;
                    actual = 0;
                }
                else
                {
                    this.stream.Push(this.matchBuffer, 0, matched);
 
                    int i = 1;
                    for (; i < read; i++)
                    {
                        if (buffer[i] == this.delimitter[0])
                            break;
                    }
 
                    if (i < read)
                        this.stream.Push(buffer, offset + i, read - i);
 
                    actual = i;
                }
            }
 
            return actual;
        }
 
        bool MatchRemainder(int start, int count)
        {
            if (start + count != this.delimitter.Length)
                return false;
 
            for (count--; count >= 0; count--)
            {
                if (this.delimitter[start + count] != this.matchBuffer[count])
                    return false;
            }
            return true;
        }
 
        internal void Push(byte[] buffer, int offset, int count)
        {
            this.stream.Push(buffer, offset, count);
        }
 
        class DelimittedReadStream : Stream
        {
            DelimittedStreamReader reader;
 
            public DelimittedReadStream(DelimittedStreamReader reader)
            {
                if (reader == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
 
                this.reader = reader;
            }
 
            public override bool CanRead
            {
                get { return true; }
            }
 
            public override bool CanSeek
            {
                get { return false; }
            }
 
            public override bool CanWrite
            {
                get { return false; }
            }
 
            public override long Length
            {
#pragma warning suppress 56503 // Microsoft, required by the XmlReader
                get { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, this.GetType().FullName))); }
            }
 
            public override long Position
            {
                get
                {
#pragma warning suppress 56503 // Microsoft, required by the XmlReader
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, this.GetType().FullName)));
                }
                set { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, this.GetType().FullName))); }
            }
 
            public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, this.GetType().FullName)));
            }
 
            public override void Close()
            {
                reader.Close(this);
            }
 
            public override void EndWrite(IAsyncResult asyncResult)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, this.GetType().FullName)));
            }
 
            public override void Flush()
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, this.GetType().FullName)));
            }
 
            public override int Read(byte[] buffer, int offset, int count)
            {
                if (buffer == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
 
                if (offset < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
                if (offset > buffer.Length)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, buffer.Length)));
                if (count < 0)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
                if (count > buffer.Length - offset)
                    throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset)));
 
                return reader.Read(this, buffer, offset, count);
            }
 
            public override long Seek(long offset, SeekOrigin origin)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, this.GetType().FullName)));
            }
 
            public override void SetLength(long value)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, this.GetType().FullName)));
            }
 
            public override void Write(byte[] buffer, int offset, int count)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, this.GetType().FullName)));
            }
        }
 
    }
 
    internal class MimeHeaders
    {
        static class Constants
        {
            public const string ContentTransferEncoding = "content-transfer-encoding";
            public const string ContentID = "content-id";
            public const string ContentType = "content-type";
            public const string MimeVersion = "mime-version";
        }
 
        Dictionary<string, MimeHeader> headers = new Dictionary<string, MimeHeader>();
 
        public MimeHeaders()
        {
        }
 
        public ContentTypeHeader ContentType
        {
            get
            {
                MimeHeader header;
                if (headers.TryGetValue(Constants.ContentType, out header))
                    return header as ContentTypeHeader;
                return null;
            }
        }
 
        public ContentIDHeader ContentID
        {
            get
            {
                MimeHeader header;
                if (headers.TryGetValue(Constants.ContentID, out header))
                    return header as ContentIDHeader;
                return null;
            }
        }
 
        public ContentTransferEncodingHeader ContentTransferEncoding
        {
            get
            {
                MimeHeader header;
                if (headers.TryGetValue(Constants.ContentTransferEncoding, out header))
                    return header as ContentTransferEncodingHeader;
                return null;
            }
        }
 
        public MimeVersionHeader MimeVersion
        {
            get
            {
                MimeHeader header;
                if (headers.TryGetValue(Constants.MimeVersion, out header))
                    return header as MimeVersionHeader;
                return null;
            }
        }
 
        public void Add(string name, string value, ref int remaining)
        {
            if (name == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("name");
 
            if (value == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
 
            switch (name)
            {
                case Constants.ContentType:
                    Add(new ContentTypeHeader(value));
                    break;
                case Constants.ContentID:
                    Add(new ContentIDHeader(name, value));
                    break;
                case Constants.ContentTransferEncoding:
                    Add(new ContentTransferEncodingHeader(value));
                    break;
                case Constants.MimeVersion:
                    Add(new MimeVersionHeader(value));
                    break;
 
                // Skip any fields that are not recognized
                // Content-description is currently not stored since it is not used
                default:
                    remaining += value.Length * sizeof(char);
                    break;
            }
            remaining += name.Length * sizeof(char);
        }
 
        public void Add(MimeHeader header)
        {
            if (header == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("header");
 
            MimeHeader existingHeader;
            if (headers.TryGetValue(header.Name, out existingHeader))
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderHeaderAlreadyExists, header.Name)));
            else
                headers.Add(header.Name, header);
        }
 
        public void Release(ref int remaining)
        {
            foreach (MimeHeader header in this.headers.Values)
            {
                remaining += header.Value.Length * sizeof(char);
            }
        }
 
    }
 
    internal class MimeHeader
    {
        string name;
        string value;
 
        public MimeHeader(string name, string value)
        {
            if (name == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("name");
 
            this.name = name;
            this.value = value;
        }
 
        public string Name
        {
            get
            {
                return this.name;
            }
        }
 
        public string Value
        {
            get
            {
                return this.value;
            }
        }
    }
 
    internal class ContentTypeHeader : MimeHeader
    {
        public readonly static ContentTypeHeader Default = new ContentTypeHeader("application/octet-stream");
 
        public ContentTypeHeader(string value)
            : base("content-type", value)
        {
        }
 
        string mediaType;
        string subType;
        Dictionary<string, string> parameters;
 
        public string MediaType
        {
            get
            {
                if (this.mediaType == null && Value != null)
                    ParseValue();
 
                return this.mediaType;
            }
        }
 
        public string MediaSubtype
        {
            get
            {
                if (this.subType == null && Value != null)
                    ParseValue();
 
                return this.subType;
            }
        }
 
        public Dictionary<string, string> Parameters
        {
            get
            {
                if (this.parameters == null)
                {
                    if (Value != null)
                        ParseValue();
                    else
                        this.parameters = new Dictionary<string, string>();
                }
                return this.parameters;
            }
        }
 
        void ParseValue()
        {
            if (this.parameters == null)
            {
                int offset = 0;
                this.parameters = new Dictionary<string, string>();
                this.mediaType = MailBnfHelper.ReadToken(Value, ref offset, null);
                if (offset >= Value.Length || Value[offset++] != '/')
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeContentTypeHeaderInvalid)));
                this.subType = MailBnfHelper.ReadToken(Value, ref offset, null);
 
                while (MailBnfHelper.SkipCFWS(Value, ref offset))
                {
                    if (offset >= Value.Length || Value[offset++] != ';')
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeContentTypeHeaderInvalid)));
 
                    if (!MailBnfHelper.SkipCFWS(Value, ref offset))
                        break;
 
                    string paramAttribute = MailBnfHelper.ReadParameterAttribute(Value, ref offset, null);
                    if (paramAttribute == null || offset >= Value.Length || Value[offset++] != '=')
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeContentTypeHeaderInvalid)));
                    string paramValue = MailBnfHelper.ReadParameterValue(Value, ref offset, null);
 
                    this.parameters.Add(paramAttribute.ToLowerInvariant(), paramValue);
                }
 
                if (this.parameters.ContainsKey(MtomGlobals.StartInfoParam))
                {
                    // This allows us to maintain back compat with Orcas clients while allowing clients 
                    // following the spec (with action inside start-info) to interop with RFC 2387
                    string startInfo = this.parameters[MtomGlobals.StartInfoParam];
 
                    // we're only interested in finding the action here - skipping past the content type to the first ; 
                    int startInfoOffset = startInfo.IndexOf(';');
                    if (startInfoOffset > -1)
                    {
                        // keep going through the start-info string until we've reached the end of the stream
                        while (MailBnfHelper.SkipCFWS(startInfo, ref startInfoOffset))
                        {
                            // after having read through an attribute=value pair, we always expect to be at a ;
                            if (startInfo[startInfoOffset] == ';')
                            {
                                startInfoOffset++;
                            }
                            else
                            {
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeContentTypeHeaderInvalid)));
                            }
                            string paramAttribute = MailBnfHelper.ReadParameterAttribute(startInfo, ref startInfoOffset, null);
                            if (paramAttribute == null || startInfoOffset >= startInfo.Length || startInfo[startInfoOffset++] != '=')
                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeContentTypeHeaderInvalid)));
                            string paramValue = MailBnfHelper.ReadParameterValue(startInfo, ref startInfoOffset, null);
 
                            if (paramAttribute == MtomGlobals.ActionParam)
                            {
                                this.parameters[MtomGlobals.ActionParam] = paramValue;
                            }
                        }
                    }
                }
 
            }
        }
    }
 
    enum ContentTransferEncoding
    {
        SevenBit,
        EightBit,
        Binary,
        Other,
        Unspecified
    }
 
    internal class ContentTransferEncodingHeader : MimeHeader
    {
        ContentTransferEncoding contentTransferEncoding;
        string contentTransferEncodingValue;
 
        public readonly static ContentTransferEncodingHeader Binary = new ContentTransferEncodingHeader(ContentTransferEncoding.Binary, "binary");
        public readonly static ContentTransferEncodingHeader EightBit = new ContentTransferEncodingHeader(ContentTransferEncoding.EightBit, "8bit");
        public readonly static ContentTransferEncodingHeader SevenBit = new ContentTransferEncodingHeader(ContentTransferEncoding.SevenBit, "7bit");
 
        public ContentTransferEncodingHeader(string value)
            : base("content-transfer-encoding", value.ToLowerInvariant())
        {
        }
 
        public ContentTransferEncodingHeader(ContentTransferEncoding contentTransferEncoding, string value)
            : base("content-transfer-encoding", null)
        {
            this.contentTransferEncoding = contentTransferEncoding;
            this.contentTransferEncodingValue = value;
        }
 
        public ContentTransferEncoding ContentTransferEncoding
        {
            get
            {
                ParseValue();
                return this.contentTransferEncoding;
            }
        }
 
        public string ContentTransferEncodingValue
        {
            get
            {
                ParseValue();
                return this.contentTransferEncodingValue;
            }
        }
 
        void ParseValue()
        {
            if (this.contentTransferEncodingValue == null)
            {
                int offset = 0;
                this.contentTransferEncodingValue = (Value.Length == 0) ? Value : ((Value[0] == '"') ? MailBnfHelper.ReadQuotedString(Value, ref offset, null) : MailBnfHelper.ReadToken(Value, ref offset, null));
                switch (this.contentTransferEncodingValue)
                {
                    case "7bit":
                        this.contentTransferEncoding = ContentTransferEncoding.SevenBit;
                        break;
                    case "8bit":
                        this.contentTransferEncoding = ContentTransferEncoding.EightBit;
                        break;
                    case "binary":
                        this.contentTransferEncoding = ContentTransferEncoding.Binary;
                        break;
                    default:
                        this.contentTransferEncoding = ContentTransferEncoding.Other;
                        break;
                }
            }
        }
    }
 
    internal class ContentIDHeader : MimeHeader
    {
        public ContentIDHeader(string name, string value)
            : base(name, value)
        {
        }
    }
 
    internal class MimeVersionHeader : MimeHeader
    {
        public static readonly MimeVersionHeader Default = new MimeVersionHeader("1.0");
 
        public MimeVersionHeader(string value)
            : base("mime-version", value)
        {
        }
 
        string version;
 
        public string Version
        {
            get
            {
                if (this.version == null && Value != null)
                    ParseValue();
                return this.version;
            }
        }
 
        void ParseValue()
        {
            // shortcut for the most common case.
            if (Value == "1.0")
            {
                this.version = "1.0";
            }
            else
            {
                int offset = 0;
 
                if (!MailBnfHelper.SkipCFWS(Value, ref offset))
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeVersionHeaderInvalid)));
 
                StringBuilder builder = new StringBuilder();
                MailBnfHelper.ReadDigits(Value, ref offset, builder);
 
                if ((!MailBnfHelper.SkipCFWS(Value, ref offset) || offset >= Value.Length || Value[offset++] != '.') || !MailBnfHelper.SkipCFWS(Value, ref offset))
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeVersionHeaderInvalid)));
 
                builder.Append('.');
 
                MailBnfHelper.ReadDigits(Value, ref offset, builder);
 
                this.version = builder.ToString();
            }
        }
    }
 
    internal class MimeHeaderReader
    {
        enum ReadState
        {
            ReadName,
            SkipWS,
            ReadValue,
            ReadLF,
            ReadWS,
            EOF
        }
 
        string value;
        byte[] buffer = new byte[1024];
        int maxOffset;
        string name;
        int offset;
        ReadState readState = ReadState.ReadName;
        Stream stream;
 
        public MimeHeaderReader(Stream stream)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
 
            this.stream = stream;
        }
 
        public string Value
        {
            get
            {
                return value;
            }
        }
 
        public string Name
        {
            get
            {
                return name;
            }
        }
 
        public void Close()
        {
            stream.Close();
            readState = ReadState.EOF;
        }
 
        public bool Read(int maxBuffer, ref int remaining)
        {
            name = null;
            value = null;
 
            while (readState != ReadState.EOF)
            {
                if (offset == maxOffset)
                {
                    maxOffset = stream.Read(this.buffer, 0, this.buffer.Length);
                    offset = 0;
                    if (BufferEnd())
                        break;
                }
                if (ProcessBuffer(maxBuffer, ref remaining))
                    break;
            }
 
            return value != null;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Calls unsafe code", Safe = "Demands for FullTrust")]
        [SecuritySafeCritical]
        [PermissionSet(SecurityAction.Demand, Unrestricted = true)]
        bool ProcessBuffer(int maxBuffer, ref int remaining)
        {
            unsafe
            {
                fixed (byte* pBuffer = this.buffer)
                {
                    byte* start = pBuffer + this.offset;
                    byte* end = pBuffer + this.maxOffset;
                    byte* ptr = start;
 
                    switch (readState)
                    {
                        case ReadState.ReadName:
                            for (; ptr < end; ptr++)
                            {
                                if (*ptr == ':')
                                {
                                    AppendName(new string((sbyte*)start, 0, (int)(ptr - start)), maxBuffer, ref remaining);
                                    ptr++;
                                    goto case ReadState.SkipWS;
                                }
                                else
                                {
                                    // convert to lower case up front.
                                    if (*ptr >= 'A' && *ptr <= 'Z')
                                    {
                                        *ptr += 'a' - 'A';
                                    }
                                    else if (*ptr < 33 || *ptr > 126)
                                    {
                                        if (name == null && *ptr == (byte)'\r')
                                        {
                                            ptr++;
                                            if (ptr >= end || *ptr != '\n')
                                                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderMalformedHeader)));
                                            goto case ReadState.EOF;
                                        }
 
                                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeHeaderInvalidCharacter, (char)(*ptr), ((int)(*ptr)).ToString("X", CultureInfo.InvariantCulture))));
                                    }
                                }
                            }
                            AppendName(new string((sbyte*)start, 0, (int)(ptr - start)), maxBuffer, ref remaining);
                            readState = ReadState.ReadName;
                            break;
                        case ReadState.SkipWS:
                            for (; ptr < end; ptr++)
                                if (*ptr != (byte)'\t' && *ptr != ' ')
                                    goto case ReadState.ReadValue;
                            readState = ReadState.SkipWS;
                            break;
                        case ReadState.ReadValue:
                            start = ptr;
                            for (; ptr < end; ptr++)
                            {
                                if (*ptr == (byte)'\r')
                                {
                                    AppendValue(new string((sbyte*)start, 0, (int)(ptr - start)), maxBuffer, ref remaining);
                                    ptr++;
                                    goto case ReadState.ReadLF;
                                }
                                else if (*ptr == (byte)'\n')
                                {
                                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderMalformedHeader)));
                                }
                            }
                            AppendValue(new string((sbyte*)start, 0, (int)(ptr - start)), maxBuffer, ref remaining);
                            readState = ReadState.ReadValue;
                            break;
                        case ReadState.ReadLF:
                            if (ptr < end)
                            {
                                if (*ptr != (byte)'\n')
                                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderMalformedHeader)));
                                ptr++;
                                goto case ReadState.ReadWS;
                            }
                            readState = ReadState.ReadLF;
                            break;
                        case ReadState.ReadWS:
                            if (ptr < end)
                            {
                                if (*ptr != (byte)' ' && *ptr != (byte)'\t')
                                {
                                    readState = ReadState.ReadName;
                                    offset = (int)(ptr - pBuffer);
                                    return true;
                                }
                                goto case ReadState.ReadValue;
                            }
                            readState = ReadState.ReadWS;
                            break;
                        case ReadState.EOF:
                            readState = ReadState.EOF;
                            offset = (int)(ptr - pBuffer);
                            return true;
                    }
                    offset = (int)(ptr - pBuffer);
                }
            }
            return false;
        }
 
        bool BufferEnd()
        {
            if (maxOffset == 0)
            {
                if (readState != ReadState.ReadWS && readState != ReadState.ReadValue)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderMalformedHeader)));
 
                readState = ReadState.EOF;
                return true;
            }
            return false;
        }
 
        // Resets the mail field reader to the new stream to reuse buffers 
        public void Reset(Stream stream)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
 
            if (readState != ReadState.EOF)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.MimeReaderResetCalledBeforeEOF)));
 
            this.stream = stream;
            readState = ReadState.ReadName;
            maxOffset = 0;
            offset = 0;
        }
 
        // helper methods
 
        void AppendValue(string value, int maxBuffer, ref int remaining)
        {
            XmlMtomReader.DecrementBufferQuota(maxBuffer, ref remaining, value.Length * sizeof(char));
            if (this.value == null)
                this.value = value;
            else
                this.value += value;
        }
 
        void AppendName(string value, int maxBuffer, ref int remaining)
        {
            XmlMtomReader.DecrementBufferQuota(maxBuffer, ref remaining, value.Length * sizeof(char));
            if (this.name == null)
                this.name = value;
            else
                this.name += value;
        }
 
    }
 
    internal class BufferedReadStream : Stream
    {
        Stream stream;
        byte[] storedBuffer;
        int storedLength;
        int storedOffset;
        bool readMore;
 
        public BufferedReadStream(Stream stream)
            : this(stream, false)
        {
        }
 
        public BufferedReadStream(Stream stream, bool readMore)
        {
            if (stream == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
 
            this.stream = stream;
            this.readMore = readMore;
        }
 
        public override bool CanWrite
        {
            get { return false; }
        }
 
        public override bool CanSeek
        {
            get { return false; }
        }
 
        public override bool CanRead
        {
            get { return stream.CanRead; }
        }
 
        public override long Length
        {
            get
            {
#pragma warning suppress 56503 // Microsoft, required by the Stream contract
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, stream.GetType().FullName)));
            }
        }
 
        public override long Position
        {
            get
            {
#pragma warning suppress 56503 // Microsoft, required by the Stream contract
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, stream.GetType().FullName)));
            }
            set
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, stream.GetType().FullName)));
            }
        }
 
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            if (!CanRead)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ReadNotSupportedOnStream, stream.GetType().FullName)));
 
            return stream.BeginRead(buffer, offset, count, callback, state);
        }
 
        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, stream.GetType().FullName)));
        }
 
        public override void Close()
        {
            stream.Close();
        }
 
        public override int EndRead(IAsyncResult asyncResult)
        {
            if (!CanRead)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ReadNotSupportedOnStream, stream.GetType().FullName)));
 
            return stream.EndRead(asyncResult);
        }
 
        public override void EndWrite(IAsyncResult asyncResult)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, stream.GetType().FullName)));
        }
 
        public override void Flush()
        {
            stream.Flush();
        }
 
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (!CanRead)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ReadNotSupportedOnStream, stream.GetType().FullName)));
 
            if (buffer == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("buffer");
 
            if (offset < 0)
                throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
            if (offset > buffer.Length)
                throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, buffer.Length)));
            if (count < 0)
                throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
            if (count > buffer.Length - offset)
                throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, buffer.Length - offset)));
 
            int read = 0;
            if (this.storedOffset < this.storedLength)
            {
                read = Math.Min(count, this.storedLength - this.storedOffset);
                Buffer.BlockCopy(this.storedBuffer, this.storedOffset, buffer, offset, read);
                this.storedOffset += read;
                if (read == count || !this.readMore)
                    return read;
                offset += read;
                count -= read;
            }
            return read + stream.Read(buffer, offset, count);
        }
 
        public override int ReadByte()
        {
            if (this.storedOffset < this.storedLength)
                return (int)this.storedBuffer[this.storedOffset++];
            else
                return base.ReadByte();
        }
 
        public int ReadBlock(byte[] buffer, int offset, int count)
        {
            int read;
            int total = 0;
            while (total < count && (read = Read(buffer, offset + total, count - total)) != 0)
            {
                total += read;
            }
            return total;
        }
 
        public void Push(byte[] buffer, int offset, int count)
        {
            if (count == 0)
                return;
 
            if (this.storedOffset == this.storedLength)
            {
                if (this.storedBuffer == null || this.storedBuffer.Length < count)
                    this.storedBuffer = new byte[count];
                this.storedOffset = 0;
                this.storedLength = count;
            }
            else
            {
                // if there's room to just insert before existing data
                if (count <= this.storedOffset)
                    this.storedOffset -= count;
                // if there's room in the buffer but need to shift things over
                else if (count <= this.storedBuffer.Length - this.storedLength + this.storedOffset)
                {
                    Buffer.BlockCopy(this.storedBuffer, this.storedOffset, this.storedBuffer, count, this.storedLength - this.storedOffset);
                    this.storedLength += count - this.storedOffset;
                    this.storedOffset = 0;
                }
                else
                {
                    byte[] newBuffer = new byte[count + this.storedLength - this.storedOffset];
                    Buffer.BlockCopy(this.storedBuffer, this.storedOffset, newBuffer, count, this.storedLength - this.storedOffset);
                    this.storedLength += count - this.storedOffset;
                    this.storedOffset = 0;
                    this.storedBuffer = newBuffer;
                }
            }
            Buffer.BlockCopy(buffer, offset, this.storedBuffer, this.storedOffset, count);
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, stream.GetType().FullName)));
        }
 
        public override void SetLength(long value)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.SeekNotSupportedOnStream, stream.GetType().FullName)));
        }
 
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.WriteNotSupportedOnStream, stream.GetType().FullName)));
        }
    }
 
    internal static class MailBnfHelper
    {
        static bool[] s_fqtext = new bool[128];
        static bool[] s_ttext = new bool[128];
        static bool[] s_digits = new bool[128];
        static bool[] s_boundary = new bool[128];
 
        static MailBnfHelper()
        {
            // fqtext = %d1-9 / %d11 / %d12 / %d14-33 / %d35-91 / %d93-127
            for (int i = 1; i <= 9; i++) { s_fqtext[i] = true; }
            s_fqtext[11] = true;
            s_fqtext[12] = true;
            for (int i = 14; i <= 33; i++) { s_fqtext[i] = true; }
            for (int i = 35; i <= 91; i++) { s_fqtext[i] = true; }
            for (int i = 93; i <= 127; i++) { s_fqtext[i] = true; }
 
            // ttext = %d33-126 except '()<>@,;:\"/[]?='
            for (int i = 33; i <= 126; i++) { s_ttext[i] = true; }
            s_ttext['('] = false;
            s_ttext[')'] = false;
            s_ttext['<'] = false;
            s_ttext['>'] = false;
            s_ttext['@'] = false;
            s_ttext[','] = false;
            s_ttext[';'] = false;
            s_ttext[':'] = false;
            s_ttext['\\'] = false;
            s_ttext['"'] = false;
            s_ttext['/'] = false;
            s_ttext['['] = false;
            s_ttext[']'] = false;
            s_ttext['?'] = false;
            s_ttext['='] = false;
 
            // digits = %d48-57
            for (int i = 48; i <= 57; i++)
                s_digits[i] = true;
 
            // boundary = DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" / "," / "-" / "." / "/" / ":" / "=" / "?" / " "
            // cannot end with " "
            for (int i = '0'; i <= '9'; i++) { s_boundary[i] = true; }
            for (int i = 'A'; i <= 'Z'; i++) { s_boundary[i] = true; }
            for (int i = 'a'; i <= 'z'; i++) { s_boundary[i] = true; }
            s_boundary['\''] = true;
            s_boundary['('] = true;
            s_boundary[')'] = true;
            s_boundary['+'] = true;
            s_boundary['_'] = true;
            s_boundary[','] = true;
            s_boundary['-'] = true;
            s_boundary['.'] = true;
            s_boundary['/'] = true;
            s_boundary[':'] = true;
            s_boundary['='] = true;
            s_boundary['?'] = true;
            s_boundary[' '] = true;
        }
 
        public static bool SkipCFWS(string data, ref int offset)
        {
            int comments = 0;
            for (; offset < data.Length; offset++)
            {
                if (data[offset] > 127)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeHeaderInvalidCharacter, data[offset], ((int)data[offset]).ToString("X", CultureInfo.InvariantCulture))));
                else if (data[offset] == '\\' && comments > 0)
                    offset += 2;
                else if (data[offset] == '(')
                    comments++;
                else if (data[offset] == ')')
                    comments--;
                else if (data[offset] != ' ' && data[offset] != '\t' && comments == 0)
                    return true;
            }
            return false;
        }
 
        public static string ReadQuotedString(string data, ref int offset, StringBuilder builder)
        {
            // assume first char is the opening quote
            int start = ++offset;
            StringBuilder localBuilder = (builder != null ? builder : new StringBuilder());
            for (; offset < data.Length; offset++)
            {
                if (data[offset] == '\\')
                {
                    localBuilder.Append(data, start, offset - start);
                    start = ++offset;
                    continue;
                }
                else if (data[offset] == '"')
                {
                    localBuilder.Append(data, start, offset - start);
                    offset++;
                    return (builder != null ? null : localBuilder.ToString());
                }
                else if (!(data[offset] < s_fqtext.Length && s_fqtext[data[offset]]))
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeHeaderInvalidCharacter, data[offset], ((int)data[offset]).ToString("X", CultureInfo.InvariantCulture))));
                }
            }
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeReaderMalformedHeader)));
        }
 
        public static string ReadParameterAttribute(string data, ref int offset, StringBuilder builder)
        {
            if (!SkipCFWS(data, ref offset))
                return null;
 
            return ReadToken(data, ref offset, null);
        }
 
        public static string ReadParameterValue(string data, ref int offset, StringBuilder builder)
        {
            if (!SkipCFWS(data, ref offset))
                return string.Empty;
 
            if (offset < data.Length && data[offset] == '"')
                return ReadQuotedString(data, ref offset, builder);
            else
                return ReadToken(data, ref offset, builder);
        }
 
        public static string ReadToken(string data, ref int offset, StringBuilder builder)
        {
            int start = offset;
            for (; offset < data.Length; offset++)
            {
                if (data[offset] > s_ttext.Length)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.MimeHeaderInvalidCharacter, data[offset], ((int)data[offset]).ToString("X", CultureInfo.InvariantCulture))));
                }
                else if (!s_ttext[data[offset]])
                {
                    break;
                }
            }
            return data.Substring(start, offset - start);
        }
 
        public static string ReadDigits(string data, ref int offset, StringBuilder builder)
        {
            int start = offset;
            StringBuilder localBuilder = (builder != null ? builder : new StringBuilder());
            for (; offset < data.Length && data[offset] < s_digits.Length && s_digits[data[offset]]; offset++);
            localBuilder.Append(data, start, offset - start);
            return (builder != null ? null : localBuilder.ToString());
        }
 
        public static bool IsValidMimeBoundary(string data)
        {
            int length = (data == null) ? 0 : data.Length;
            if (length == 0 || length > 70 || data[length - 1] == ' ')
                return false;
 
            for (int i = 0; i < length; i++)
            {
                if (!(data[i] < s_boundary.Length && s_boundary[data[i]]))
                    return false;
            }
 
            return true;
        }
    }
 
}