2 // XmlMtomDictionaryReader.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2009 Novell, Inc. http://www.novell.com
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System
.Collections
.Generic
;
32 using System
.Net
.Mime
;
33 using System
.Reflection
;
39 internal class XmlMtomDictionaryReader
: XmlDictionaryReader
41 public XmlMtomDictionaryReader (
42 Stream stream
, Encoding encoding
,
43 XmlDictionaryReaderQuotas quotas
)
46 this.encoding
= encoding
;
52 public XmlMtomDictionaryReader (
53 Stream stream
, Encoding
[] encodings
, string contentType
,
54 XmlDictionaryReaderQuotas quotas
,
56 OnXmlDictionaryReaderClose onClose
)
59 this.encodings
= encodings
;
60 content_type
= contentType
!= null ? CreateContentType (contentType
) : null;
62 this.max_buffer_size
= maxBufferSize
;
70 Encoding
[] encodings
;
71 ContentType content_type
;
72 XmlDictionaryReaderQuotas quotas
;
74 OnXmlDictionaryReaderClose on_close
;
76 Dictionary
<string,MimeEncodedStream
> readers
= new Dictionary
<string,MimeEncodedStream
> ();
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
)
89 foreach (var s_
in contentTypeString
.Split (';')) {
93 c
= new ContentType (s
);
96 int idx
= s
.IndexOf ('=');
98 throw new XmlException ("Invalid content type header");
99 var val
= StripBraces (s
.Substring (idx
+ 1));
100 c
.Parameters
[s
.Substring (0, idx
)] = val
;
105 XmlReader xml_reader
, initial_reader
, eof_reader
, part_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)
118 xml_reader
= eof_reader
;
121 public override bool Read ()
126 if (Reader
== initial_reader
)
127 SetupPrimaryReader ();
129 if (part_reader
!= null)
132 if (!Reader
.Read ()) {
133 xml_reader
= eof_reader
;
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
]);
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
;
169 idline
= ReadAsciiLine ().Trim ();
172 if (idline
.Length
!= 0)
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 ());
188 ContentType current_content_type
;
190 string current_content_id
, current_content_encoding
;
192 void ReadToIdentifiedStream (string id
)
195 if (!ReadNextStream ())
196 throw new XmlException (String
.Format ("The stream '{0}' did not appear", id
));
197 if (current_content_id
== id
|| id
== null)
202 bool ReadNextStream ()
204 ReadOptionalMimeHeaders ();
205 string ident
= "--" + content_type
.Boundary
;
207 StringBuilder sb
= new StringBuilder ();
209 string n
= ReadAsciiLine ();
210 if (n
== null && sb
.Length
== 0)
212 else if (n
== null || n
.StartsWith (ident
, StringComparison
.Ordinal
))
216 readers
.Add (current_content_id
, new MimeEncodedStream (current_content_id
, current_content_encoding
, sb
.ToString ()));
220 void ReadOptionalMimeHeaders ()
222 peek_char
= stream
.ReadByte ();
223 if (peek_char
== '-') // no header
228 string ReadAllHeaderLines ()
230 string s
= String
.Empty
;
233 var n
= ReadAsciiLine ();
238 if (n
[n
.Length
- 1] != ';')
243 void ReadMimeHeaders ()
245 foreach (var s
in ReadAllHeaderLines ().Split ('\n')) {
248 int idx
= s
.IndexOf (':');
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 ()) {
254 current_content_type
= CreateContentType (v
);
257 current_content_id
= v
;
259 case "content-transfer-encoding":
260 current_content_encoding
= v
;
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);
276 string ReadAsciiLine ()
279 buffer
= new byte [1024];
282 bool skipRead
= b
>= 0;
288 b
= stream
.ReadByte ();
291 throw new XmlException ("The stream ends without end of line");
295 b
= stream
.ReadByte ();
297 buffer
[bpos
++] = (byte) '\r';
302 buffer
[bpos
++] = (byte) '\r';
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
);
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
{
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
)
457 this.value = value.CreateTextReader ().ReadToEnd ();
463 public override int Depth
{
464 get { return owner.Depth; }
467 public override bool HasValue
{
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
)
485 name_table
= nameTable
;
486 read_state
= readState
;
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
{
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 ()
546 public override string GetAttribute (int index
)
551 public override string GetAttribute (string name
)
556 public override string GetAttribute (string localName
, string namespaceURI
)
561 public override void MoveToAttribute (int index
)
563 throw new ArgumentOutOfRangeException ();
566 public override bool MoveToAttribute (string name
)
571 public override bool MoveToAttribute (string localName
, string namespaceURI
)
576 public override bool MoveToFirstAttribute ()
581 public override bool MoveToNextAttribute ()
586 public override string LookupNamespace (string prefix
)
591 public override bool ReadAttributeValue ()
596 public override void ResolveEntity ()
598 throw new InvalidOperationException ();
602 class MimeEncodedStream
604 public MimeEncodedStream (string id
, string contentEncoding
, string value)
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
) {
624 return new StringReader (EncodedString
);
626 return new StringReader (DecodedBase64String
);