//------------------------------------------------------------ // 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 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(); 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 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 headers = new Dictionary(); 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 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 Parameters { get { if (this.parameters == null) { if (Value != null) ParseValue(); else this.parameters = new Dictionary(); } return this.parameters; } } void ParseValue() { if (this.parameters == null) { int offset = 0; this.parameters = new Dictionary(); 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; } } }