// // XmlMtomDictionaryReader.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2009 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections.Generic; using System.IO; using System.Net.Mime; using System.Reflection; using System.Text; using System.Xml; namespace System.Xml { internal class XmlMtomDictionaryReader : XmlDictionaryReader { public XmlMtomDictionaryReader ( Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas) { this.stream = stream; this.encoding = encoding; this.quotas = quotas; Initialize (); } public XmlMtomDictionaryReader ( Stream stream, Encoding [] encodings, string contentType, XmlDictionaryReaderQuotas quotas, int maxBufferSize, OnXmlDictionaryReaderClose onClose) { this.stream = stream; this.encodings = encodings; content_type = contentType != null ? CreateContentType (contentType) : null; this.quotas = quotas; this.max_buffer_size = maxBufferSize; on_close = onClose; Initialize (); } Stream stream; Encoding encoding; Encoding [] encodings; ContentType content_type; XmlDictionaryReaderQuotas quotas; int max_buffer_size; OnXmlDictionaryReaderClose on_close; Dictionary readers = new Dictionary (); void Initialize () { var nt = new NameTable (); initial_reader = new NonInteractiveStateXmlReader (String.Empty, nt, ReadState.Initial); eof_reader = new NonInteractiveStateXmlReader (String.Empty, nt, ReadState.EndOfFile); xml_reader = initial_reader; } ContentType CreateContentType (string contentTypeString) { ContentType c = null; foreach (var s_ in contentTypeString.Split (';')) { var s = s_.Trim (); if (c == null) { // first one c = new ContentType (s); continue; } int idx = s.IndexOf ('='); if (idx < 0) throw new XmlException ("Invalid content type header"); var val = StripBraces (s.Substring (idx + 1)); c.Parameters [s.Substring (0, idx)] = val; } return c; } XmlReader xml_reader, initial_reader, eof_reader, part_reader; XmlReader Reader { get { return part_reader ?? xml_reader; } } public override bool EOF { get { return Reader == eof_reader; } } public override void Close () { if (!EOF && on_close != null) on_close (this); xml_reader = eof_reader; } public override bool Read () { if (EOF) return false; if (Reader == initial_reader) SetupPrimaryReader (); if (part_reader != null) part_reader = null; if (!Reader.Read ()) { xml_reader = eof_reader; return false; } if (Reader.LocalName == "Include" && Reader.NamespaceURI == "http://www.w3.org/2004/08/xop/include") { string cid = Reader.GetAttribute ("href"); if (!cid.StartsWith ("cid:")) throw new XmlException ("Cannot resolve non-cid href attribute value in XOP Include element"); cid = cid.Substring (4); if (!readers.ContainsKey (cid)) ReadToIdentifiedStream (cid); part_reader = new MultiPartedXmlReader (Reader, readers [cid]); } return true; } void SetupPrimaryReader () { ReadOptionalMimeHeaders (); if (current_content_type != null) content_type = current_content_type; if (content_type == null) throw new XmlException ("Content-Type header for the MTOM message was not found"); if (content_type.Boundary == null) throw new XmlException ("Content-Type header for the MTOM message must contain 'boundary' parameter"); if (encoding == null && content_type.CharSet != null) encoding = Encoding.GetEncoding (content_type.CharSet); if (encoding == null && encodings == null) throw new XmlException ("Encoding specification is required either in the constructor argument or the content-type header"); // consume the first identifier. string ident = "--" + content_type.Boundary; string idline; while (true) { idline = ReadAsciiLine ().Trim (); if (idline == null) return; if (idline.Length != 0) break; } if (!idline.StartsWith (ident, StringComparison.Ordinal)) throw new XmlException (String.Format ("Unexpected boundary line was found. Expected boundary is '{0}' but it was '{1}'", content_type.Boundary, idline)); string start = content_type.Parameters ["start"]; ReadToIdentifiedStream (start); xml_reader = XmlReader.Create (readers [start].CreateTextReader ()); } int buffer_length; byte [] buffer; int peek_char; ContentType current_content_type; int content_index; string current_content_id, current_content_encoding; void ReadToIdentifiedStream (string id) { while (true) { if (!ReadNextStream ()) throw new XmlException (String.Format ("The stream '{0}' did not appear", id)); if (current_content_id == id || id == null) break; } } bool ReadNextStream () { ReadOptionalMimeHeaders (); string ident = "--" + content_type.Boundary; StringBuilder sb = new StringBuilder (); while (true) { string n = ReadAsciiLine (); if (n == null && sb.Length == 0) return false; else if (n == null || n.StartsWith (ident, StringComparison.Ordinal)) break; sb.Append (n); } readers.Add (current_content_id, new MimeEncodedStream (current_content_id, current_content_encoding, sb.ToString ())); return true; } void ReadOptionalMimeHeaders () { peek_char = stream.ReadByte (); if (peek_char == '-') // no header return; ReadMimeHeaders (); } string ReadAllHeaderLines () { string s = String.Empty; while (true) { var n = ReadAsciiLine (); if (n.Length == 0) return s; n = n.TrimEnd (); s += n; if (n [n.Length - 1] != ';') s += '\n'; } } void ReadMimeHeaders () { foreach (var s in ReadAllHeaderLines ().Split ('\n')) { if (s.Length == 0) continue; int idx = s.IndexOf (':'); if (idx < 0) throw new XmlException (String.Format ("Unexpected header string: {0}", s)); string v = StripBraces (s.Substring (idx + 1).Trim ()); switch (s.Substring (0, idx).ToLower ()) { case "content-type": current_content_type = CreateContentType (v); break; case "content-id": current_content_id = v; break; case "content-transfer-encoding": current_content_encoding = v; break; } } } string StripBraces (string s) { // could be foo, , "foo" and "". if (s.Length >= 2 && s [0] == '"' && s [s.Length - 1] == '"') s = s.Substring (1, s.Length - 2); if (s.Length >= 2 && s [0] == '<' && s [s.Length - 1] == '>') s = s.Substring (1, s.Length - 2); return s; } string ReadAsciiLine () { if (buffer == null) buffer = new byte [1024]; int bpos = 0; int b = peek_char; bool skipRead = b >= 0; peek_char = -1; while (true) { if (skipRead) skipRead = false; else b = stream.ReadByte (); if (b < 0) { if (bpos > 0) throw new XmlException ("The stream ends without end of line"); return null; } if (b == '\r') { b = stream.ReadByte (); if (b < 0) { buffer [bpos++] = (byte) '\r'; break; } else if (b == '\n') break; buffer [bpos++] = (byte) '\r'; skipRead = true; } else buffer [bpos++] = (byte) b; if (bpos == buffer.Length) { var newbuf = new byte [buffer.Length << 1]; Array.Copy (buffer, 0, newbuf, 0, buffer.Length); buffer = newbuf; } } return Encoding.ASCII.GetString (buffer, 0, bpos); } // The rest are just reader delegation. public override int AttributeCount { get { return Reader.AttributeCount; } } public override string BaseURI { get { return Reader.BaseURI; } } public override int Depth { get { return Reader.Depth; } } public override bool HasValue { get { return Reader.HasValue; } } public override bool IsEmptyElement { get { return Reader.IsEmptyElement; } } public override string LocalName { get { return Reader.LocalName; } } public override string NamespaceURI { get { return Reader.NamespaceURI; } } public override XmlNameTable NameTable { get { return Reader.NameTable; } } public override XmlNodeType NodeType { get { return Reader.NodeType; } } public override string Prefix { get { return Reader.Prefix; } } public override ReadState ReadState { get { return Reader.ReadState; } } public override string Value { get { return Reader.Value; } } public override bool MoveToElement () { return Reader.MoveToElement (); } public override string GetAttribute (int index) { return Reader.GetAttribute (index); } public override string GetAttribute (string name) { return Reader.GetAttribute (name); } public override string GetAttribute (string localName, string namespaceURI) { return Reader.GetAttribute (localName, namespaceURI); } public override void MoveToAttribute (int index) { Reader.MoveToAttribute (index); } public override bool MoveToAttribute (string name) { return Reader.MoveToAttribute (name); } public override bool MoveToAttribute (string localName, string namespaceURI) { return Reader.MoveToAttribute (localName, namespaceURI); } public override bool MoveToFirstAttribute () { return Reader.MoveToFirstAttribute (); } public override bool MoveToNextAttribute () { return Reader.MoveToNextAttribute (); } public override string LookupNamespace (string prefix) { return Reader.LookupNamespace (prefix); } public override bool ReadAttributeValue () { return Reader.ReadAttributeValue (); } public override void ResolveEntity () { Reader.ResolveEntity (); } } class NonInteractiveStateXmlReader : DummyStateXmlReader { public NonInteractiveStateXmlReader (string baseUri, XmlNameTable nameTable, ReadState readState) : base (baseUri, nameTable, readState) { } public override int Depth { get { return 0; } } public override bool HasValue { get { return false; } } public override string Value { get { return String.Empty; } } public override XmlNodeType NodeType { get { return XmlNodeType.None; } } } class MultiPartedXmlReader : DummyStateXmlReader { public MultiPartedXmlReader (XmlReader reader, MimeEncodedStream value) : base (reader.BaseURI, reader.NameTable, reader.ReadState) { this.owner = reader; this.value = value.CreateTextReader ().ReadToEnd (); } XmlReader owner; string value; public override int Depth { get { return owner.Depth; } } public override bool HasValue { get { return true; } } public override string Value { get { return value; } } public override XmlNodeType NodeType { get { return XmlNodeType.Text; } } } abstract class DummyStateXmlReader : XmlReader { protected DummyStateXmlReader (string baseUri, XmlNameTable nameTable, ReadState readState) { base_uri = baseUri; name_table = nameTable; read_state = readState; } string base_uri; XmlNameTable name_table; ReadState read_state; public override string BaseURI { get { return base_uri; } } public override bool EOF { get { return false; } } public override void Close () { throw new NotSupportedException (); } public override bool Read () { throw new NotSupportedException (); } // The rest are just reader delegation. public override int AttributeCount { get { return 0; } } public override bool IsEmptyElement { get { return false; } } public override string LocalName { get { return String.Empty; } } public override string NamespaceURI { get { return String.Empty; } } public override XmlNameTable NameTable { get { return name_table; } } public override string Prefix { get { return String.Empty; } } public override ReadState ReadState { get { return read_state; } } public override bool MoveToElement () { return false; } public override string GetAttribute (int index) { return null; } public override string GetAttribute (string name) { return null; } public override string GetAttribute (string localName, string namespaceURI) { return null; } public override void MoveToAttribute (int index) { throw new ArgumentOutOfRangeException (); } public override bool MoveToAttribute (string name) { return false; } public override bool MoveToAttribute (string localName, string namespaceURI) { return false; } public override bool MoveToFirstAttribute () { return false; } public override bool MoveToNextAttribute () { return false; } public override string LookupNamespace (string prefix) { return null; } public override bool ReadAttributeValue () { return false; } public override void ResolveEntity () { throw new InvalidOperationException (); } } class MimeEncodedStream { public MimeEncodedStream (string id, string contentEncoding, string value) { Id = id; ContentEncoding = contentEncoding; EncodedString = value; } public string Id { get; set; } public string ContentEncoding { get; set; } public string EncodedString { get; set; } public string DecodedBase64String { get { return Convert.ToBase64String (Encoding.ASCII.GetBytes (EncodedString)); } } public TextReader CreateTextReader () { switch (ContentEncoding) { case "7bit": case "8bit": return new StringReader (EncodedString); default: return new StringReader (DecodedBase64String); } } } }