2010-06-21 Atsushi Enomoto <atsushi@ximian.com>
[mcs.git] / class / System.Runtime.Serialization / System.Xml / XmlMtomDictionaryReader.cs
blobb2ca93c6d99bfbe6fd9de1fd0b501def709e0e42
1 //
2 // XmlMtomDictionaryReader.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2009 Novell, Inc. http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Net.Mime;
33 using System.Reflection;
34 using System.Text;
35 using System.Xml;
37 namespace System.Xml
39 internal class XmlMtomDictionaryReader : XmlDictionaryReader
41 public XmlMtomDictionaryReader (
42 Stream stream, Encoding encoding,
43 XmlDictionaryReaderQuotas quotas)
45 this.stream = stream;
46 this.encoding = encoding;
47 this.quotas = quotas;
49 Initialize ();
52 public XmlMtomDictionaryReader (
53 Stream stream, Encoding [] encodings, string contentType,
54 XmlDictionaryReaderQuotas quotas,
55 int maxBufferSize,
56 OnXmlDictionaryReaderClose onClose)
58 this.stream = stream;
59 this.encodings = encodings;
60 content_type = contentType != null ? CreateContentType (contentType) : null;
61 this.quotas = quotas;
62 this.max_buffer_size = maxBufferSize;
63 on_close = onClose;
65 Initialize ();
68 Stream stream;
69 Encoding encoding;
70 Encoding [] encodings;
71 ContentType content_type;
72 XmlDictionaryReaderQuotas quotas;
73 int max_buffer_size;
74 OnXmlDictionaryReaderClose on_close;
76 Dictionary<string,MimeEncodedStream> readers = new Dictionary<string,MimeEncodedStream> ();
78 void Initialize ()
80 var nt = new NameTable ();
81 initial_reader = new NonInteractiveStateXmlReader (String.Empty, nt, ReadState.Initial);
82 eof_reader = new NonInteractiveStateXmlReader (String.Empty, nt, ReadState.EndOfFile);
83 xml_reader = initial_reader;
86 ContentType CreateContentType (string contentTypeString)
88 ContentType c = null;
89 foreach (var s_ in contentTypeString.Split (';')) {
90 var s = s_.Trim ();
91 if (c == null) {
92 // first one
93 c = new ContentType (s);
94 continue;
96 int idx = s.IndexOf ('=');
97 if (idx < 0)
98 throw new XmlException ("Invalid content type header");
99 var val = StripBraces (s.Substring (idx + 1));
100 c.Parameters [s.Substring (0, idx)] = val;
102 return c;
105 XmlReader xml_reader, initial_reader, eof_reader, part_reader;
106 XmlReader Reader {
107 get { return part_reader ?? xml_reader; }
110 public override bool EOF {
111 get { return Reader == eof_reader; }
114 public override void Close ()
116 if (!EOF && on_close != null)
117 on_close (this);
118 xml_reader = eof_reader;
121 public override bool Read ()
123 if (EOF)
124 return false;
126 if (Reader == initial_reader)
127 SetupPrimaryReader ();
129 if (part_reader != null)
130 part_reader = null;
132 if (!Reader.Read ()) {
133 xml_reader = eof_reader;
134 return false;
136 if (Reader.LocalName == "Include" && Reader.NamespaceURI == "http://www.w3.org/2004/08/xop/include") {
137 string cid = Reader.GetAttribute ("href");
138 if (!cid.StartsWith ("cid:"))
139 throw new XmlException ("Cannot resolve non-cid href attribute value in XOP Include element");
140 cid = cid.Substring (4);
141 if (!readers.ContainsKey (cid))
142 ReadToIdentifiedStream (cid);
143 part_reader = new MultiPartedXmlReader (Reader, readers [cid]);
145 return true;
148 void SetupPrimaryReader ()
150 ReadOptionalMimeHeaders ();
151 if (current_content_type != null)
152 content_type = current_content_type;
154 if (content_type == null)
155 throw new XmlException ("Content-Type header for the MTOM message was not found");
156 if (content_type.Boundary == null)
157 throw new XmlException ("Content-Type header for the MTOM message must contain 'boundary' parameter");
159 if (encoding == null && content_type.CharSet != null)
160 encoding = Encoding.GetEncoding (content_type.CharSet);
161 if (encoding == null && encodings == null)
162 throw new XmlException ("Encoding specification is required either in the constructor argument or the content-type header");
164 // consume the first identifier.
165 string ident = "--" + content_type.Boundary;
166 string idline;
168 while (true) {
169 idline = ReadAsciiLine ().Trim ();
170 if (idline == null)
171 return;
172 if (idline.Length != 0)
173 break;
175 if (!idline.StartsWith (ident, StringComparison.Ordinal))
176 throw new XmlException (String.Format ("Unexpected boundary line was found. Expected boundary is '{0}' but it was '{1}'", content_type.Boundary, idline));
178 string start = content_type.Parameters ["start"];
179 ReadToIdentifiedStream (start);
181 xml_reader = XmlReader.Create (readers [start].CreateTextReader ());
184 int buffer_length;
185 byte [] buffer;
186 int peek_char;
188 ContentType current_content_type;
189 int content_index;
190 string current_content_id, current_content_encoding;
192 void ReadToIdentifiedStream (string id)
194 while (true) {
195 if (!ReadNextStream ())
196 throw new XmlException (String.Format ("The stream '{0}' did not appear", id));
197 if (current_content_id == id || id == null)
198 break;
202 bool ReadNextStream ()
204 ReadOptionalMimeHeaders ();
205 string ident = "--" + content_type.Boundary;
207 StringBuilder sb = new StringBuilder ();
208 while (true) {
209 string n = ReadAsciiLine ();
210 if (n == null && sb.Length == 0)
211 return false;
212 else if (n == null || n.StartsWith (ident, StringComparison.Ordinal))
213 break;
214 sb.Append (n);
216 readers.Add (current_content_id, new MimeEncodedStream (current_content_id, current_content_encoding, sb.ToString ()));
217 return true;
220 void ReadOptionalMimeHeaders ()
222 peek_char = stream.ReadByte ();
223 if (peek_char == '-') // no header
224 return;
225 ReadMimeHeaders ();
228 string ReadAllHeaderLines ()
230 string s = String.Empty;
232 while (true) {
233 var n = ReadAsciiLine ();
234 if (n.Length == 0)
235 return s;
236 n = n.TrimEnd ();
237 s += n;
238 if (n [n.Length - 1] != ';')
239 s += '\n';
243 void ReadMimeHeaders ()
245 foreach (var s in ReadAllHeaderLines ().Split ('\n')) {
246 if (s.Length == 0)
247 continue;
248 int idx = s.IndexOf (':');
249 if (idx < 0)
250 throw new XmlException (String.Format ("Unexpected header string: {0}", s));
251 string v = StripBraces (s.Substring (idx + 1).Trim ());
252 switch (s.Substring (0, idx).ToLower ()) {
253 case "content-type":
254 current_content_type = CreateContentType (v);
255 break;
256 case "content-id":
257 current_content_id = v;
258 break;
259 case "content-transfer-encoding":
260 current_content_encoding = v;
261 break;
266 string StripBraces (string s)
268 // could be foo, <foo>, "foo" and "<foo>".
269 if (s.Length >= 2 && s [0] == '"' && s [s.Length - 1] == '"')
270 s = s.Substring (1, s.Length - 2);
271 if (s.Length >= 2 && s [0] == '<' && s [s.Length - 1] == '>')
272 s = s.Substring (1, s.Length - 2);
273 return s;
276 string ReadAsciiLine ()
278 if (buffer == null)
279 buffer = new byte [1024];
280 int bpos = 0;
281 int b = peek_char;
282 bool skipRead = b >= 0;
283 peek_char = -1;
284 while (true) {
285 if (skipRead)
286 skipRead = false;
287 else
288 b = stream.ReadByte ();
289 if (b < 0) {
290 if (bpos > 0)
291 throw new XmlException ("The stream ends without end of line");
292 return null;
294 if (b == '\r') {
295 b = stream.ReadByte ();
296 if (b < 0) {
297 buffer [bpos++] = (byte) '\r';
298 break;
300 else if (b == '\n')
301 break;
302 buffer [bpos++] = (byte) '\r';
303 skipRead = true;
305 else
306 buffer [bpos++] = (byte) b;
307 if (bpos == buffer.Length) {
308 var newbuf = new byte [buffer.Length << 1];
309 Array.Copy (buffer, 0, newbuf, 0, buffer.Length);
310 buffer = newbuf;
313 return Encoding.ASCII.GetString (buffer, 0, bpos);
316 // The rest are just reader delegation.
318 public override int AttributeCount {
319 get { return Reader.AttributeCount; }
322 public override string BaseURI {
323 get { return Reader.BaseURI; }
326 public override int Depth {
327 get { return Reader.Depth; }
330 public override bool HasValue {
331 get { return Reader.HasValue; }
334 public override bool IsEmptyElement {
335 get { return Reader.IsEmptyElement; }
338 public override string LocalName {
339 get { return Reader.LocalName; }
342 public override string NamespaceURI {
343 get { return Reader.NamespaceURI; }
346 public override XmlNameTable NameTable {
347 get { return Reader.NameTable; }
350 public override XmlNodeType NodeType {
351 get { return Reader.NodeType; }
354 public override string Prefix {
355 get { return Reader.Prefix; }
358 public override ReadState ReadState {
359 get { return Reader.ReadState; }
362 public override string Value {
363 get { return Reader.Value; }
366 public override bool MoveToElement ()
368 return Reader.MoveToElement ();
371 public override string GetAttribute (int index)
373 return Reader.GetAttribute (index);
376 public override string GetAttribute (string name)
378 return Reader.GetAttribute (name);
381 public override string GetAttribute (string localName, string namespaceURI)
383 return Reader.GetAttribute (localName, namespaceURI);
386 public override void MoveToAttribute (int index)
388 Reader.MoveToAttribute (index);
391 public override bool MoveToAttribute (string name)
393 return Reader.MoveToAttribute (name);
396 public override bool MoveToAttribute (string localName, string namespaceURI)
398 return Reader.MoveToAttribute (localName, namespaceURI);
401 public override bool MoveToFirstAttribute ()
403 return Reader.MoveToFirstAttribute ();
406 public override bool MoveToNextAttribute ()
408 return Reader.MoveToNextAttribute ();
411 public override string LookupNamespace (string prefix)
413 return Reader.LookupNamespace (prefix);
416 public override bool ReadAttributeValue ()
418 return Reader.ReadAttributeValue ();
421 public override void ResolveEntity ()
423 Reader.ResolveEntity ();
427 class NonInteractiveStateXmlReader : DummyStateXmlReader
429 public NonInteractiveStateXmlReader (string baseUri, XmlNameTable nameTable, ReadState readState)
430 : base (baseUri, nameTable, readState)
434 public override int Depth {
435 get { return 0; }
438 public override bool HasValue {
439 get { return false; }
442 public override string Value {
443 get { return String.Empty; }
446 public override XmlNodeType NodeType {
447 get { return XmlNodeType.None; }
451 class MultiPartedXmlReader : DummyStateXmlReader
453 public MultiPartedXmlReader (XmlReader reader, MimeEncodedStream value)
454 : base (reader.BaseURI, reader.NameTable, reader.ReadState)
456 this.owner = reader;
457 this.value = value.CreateTextReader ().ReadToEnd ();
460 XmlReader owner;
461 string value;
463 public override int Depth {
464 get { return owner.Depth; }
467 public override bool HasValue {
468 get { return true; }
471 public override string Value {
472 get { return value; }
475 public override XmlNodeType NodeType {
476 get { return XmlNodeType.Text; }
480 abstract class DummyStateXmlReader : XmlReader
482 protected DummyStateXmlReader (string baseUri, XmlNameTable nameTable, ReadState readState)
484 base_uri = baseUri;
485 name_table = nameTable;
486 read_state = readState;
489 string base_uri;
490 XmlNameTable name_table;
491 ReadState read_state;
493 public override string BaseURI {
494 get { return base_uri; }
497 public override bool EOF {
498 get { return false; }
501 public override void Close ()
503 throw new NotSupportedException ();
506 public override bool Read ()
508 throw new NotSupportedException ();
511 // The rest are just reader delegation.
513 public override int AttributeCount {
514 get { return 0; }
517 public override bool IsEmptyElement {
518 get { return false; }
521 public override string LocalName {
522 get { return String.Empty; }
525 public override string NamespaceURI {
526 get { return String.Empty; }
529 public override XmlNameTable NameTable {
530 get { return name_table; }
533 public override string Prefix {
534 get { return String.Empty; }
537 public override ReadState ReadState {
538 get { return read_state; }
541 public override bool MoveToElement ()
543 return false;
546 public override string GetAttribute (int index)
548 return null;
551 public override string GetAttribute (string name)
553 return null;
556 public override string GetAttribute (string localName, string namespaceURI)
558 return null;
561 public override void MoveToAttribute (int index)
563 throw new ArgumentOutOfRangeException ();
566 public override bool MoveToAttribute (string name)
568 return false;
571 public override bool MoveToAttribute (string localName, string namespaceURI)
573 return false;
576 public override bool MoveToFirstAttribute ()
578 return false;
581 public override bool MoveToNextAttribute ()
583 return false;
586 public override string LookupNamespace (string prefix)
588 return null;
591 public override bool ReadAttributeValue ()
593 return false;
596 public override void ResolveEntity ()
598 throw new InvalidOperationException ();
602 class MimeEncodedStream
604 public MimeEncodedStream (string id, string contentEncoding, string value)
606 Id = id;
607 ContentEncoding = contentEncoding;
608 EncodedString = value;
611 public string Id { get; set; }
612 public string ContentEncoding { get; set; }
613 public string EncodedString { get; set; }
615 public string DecodedBase64String {
616 get { return Convert.ToBase64String (Encoding.ASCII.GetBytes (EncodedString)); }
619 public TextReader CreateTextReader ()
621 switch (ContentEncoding) {
622 case "7bit":
623 case "8bit":
624 return new StringReader (EncodedString);
625 default:
626 return new StringReader (DecodedBase64String);