1 //
2 // MessageFault.cs
3 //
4 // Author:
5 //	Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005-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.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Runtime.Serialization;
32 using System.Xml;
33 
34 namespace System.ServiceModel.Channels
35 {
36 	public abstract class MessageFault
37 	{
38 		// type members
39 
CreateFault(Message message, int maxBufferSize)40 		public static MessageFault CreateFault (Message message, int maxBufferSize)
41 		{
42 			try {
43 				if (message.Version.Envelope == EnvelopeVersion.Soap11)
44 					return CreateFault11 (message, maxBufferSize);
45 				else // common to None and SOAP12
46 					return CreateFault12 (message, maxBufferSize);
47 			} catch (XmlException ex) {
48 				throw new CommunicationException ("Received an invalid SOAP Fault message", ex);
49 			}
50 		}
51 
CreateFault11(Message message, int maxBufferSize)52 		static MessageFault CreateFault11 (Message message, int maxBufferSize)
53 		{
54 			FaultCode fc = null;
55 			FaultReason fr = null;
56 			string actor = null;
57 			XmlDictionaryReader r = message.GetReaderAtBodyContents ();
58 			r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
59 			r.MoveToContent ();
60 
61 			while (r.NodeType != XmlNodeType.EndElement) {
62 				switch (r.LocalName) {
63 				case "faultcode":
64 					fc = ReadFaultCode11 (r);
65 					break;
66 				case "faultstring":
67 					fr = new FaultReason (r.ReadElementContentAsString());
68 					break;
69 				case "faultactor":
70 					actor = r.ReadElementContentAsString();
71 					break;
72 				case "detail":
73 					return new XmlReaderDetailMessageFault (message, r, fc, fr, actor, null);
74 				default:
75 					throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
76 				}
77 				r.MoveToContent ();
78 			}
79 			r.ReadEndElement ();
80 
81 			if (fr == null)
82 				throw new XmlException ("Reason is missing in the Fault message");
83 
84 			return new SimpleMessageFault (fc, fr, false, null, null, actor, null);
85 		}
86 
CreateFault12(Message message, int maxBufferSize)87 		static MessageFault CreateFault12 (Message message, int maxBufferSize)
88 		{
89 			FaultCode fc = null;
90 			FaultReason fr = null;
91 			string node = null;
92 			XmlDictionaryReader r = message.GetReaderAtBodyContents ();
93 			r.ReadStartElement ("Fault", message.Version.Envelope.Namespace);
94 
95 			for (r.MoveToContent (); r.NodeType != XmlNodeType.EndElement; r.MoveToContent ()) {
96 				if (r.NamespaceURI != message.Version.Envelope.Namespace) {
97 					r.Skip ();
98 					continue;
99 				}
100 				switch (r.LocalName) {
101 				case "Code":
102 					fc = ReadFaultCode12 (r, message.Version.Envelope.Namespace);
103 					break;
104 				case "Reason":
105 					fr = ReadFaultReason12 (r, message.Version.Envelope.Namespace);
106 					break;
107 				case "Node":
108 					node = r.ReadElementContentAsString ();
109 					break;
110 				case "Role":
111 					r.Skip (); // no corresponding member to store.
112 					break;
113 				case "Detail":
114 					if (!r.IsEmptyElement)
115 						return new XmlReaderDetailMessageFault (message, r, fc, fr, null, node);
116 					r.Read ();
117 					break;
118 				default:
119 					throw new XmlException (String.Format ("Unexpected node {0} name {1}", r.NodeType, r.Name));
120 				}
121 			}
122 
123 			if (fr == null)
124 				throw new XmlException ("Reason is missing in the Fault message");
125 
126 			r.ReadEndElement ();
127 
128 			return new SimpleMessageFault (fc, fr, false, null, null, null, node);
129 		}
130 
ReadFaultCode11(XmlDictionaryReader r)131 		static FaultCode ReadFaultCode11 (XmlDictionaryReader r)
132 		{
133 			FaultCode subcode = null;
134 			XmlQualifiedName value = XmlQualifiedName.Empty;
135 
136 			if (r.IsEmptyElement)
137 				throw new ArgumentException ("Fault Code is mandatory in SOAP fault message.");
138 
139 			r.ReadStartElement ("faultcode");
140 			r.MoveToContent ();
141 			while (r.NodeType != XmlNodeType.EndElement) {
142 				if (r.NodeType == XmlNodeType.Element)
143 					subcode = ReadFaultCode11 (r);
144 				else
145 					value = (XmlQualifiedName) r.ReadContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver);
146 				r.MoveToContent ();
147 			}
148 			r.ReadEndElement ();
149 
150 			return new FaultCode (value.Name, value.Namespace, subcode);
151 		}
152 
ReadFaultCode12(XmlDictionaryReader r, string ns)153 		static FaultCode ReadFaultCode12 (XmlDictionaryReader r, string ns)
154 		{
155 			FaultCode subcode = null;
156 			XmlQualifiedName value = XmlQualifiedName.Empty;
157 
158 			if (r.IsEmptyElement)
159 				throw new ArgumentException ("either SubCode or Value element is mandatory in SOAP fault code.");
160 
161 			r.ReadStartElement (); // could be either Code or SubCode
162 			r.MoveToContent ();
163 			while (r.NodeType != XmlNodeType.EndElement) {
164 				switch (r.LocalName) {
165 				case "Subcode":
166 					subcode = ReadFaultCode12 (r, ns);
167 					break;
168 				case "Value":
169 					value = (XmlQualifiedName) r.ReadElementContentAs (typeof (XmlQualifiedName), r as IXmlNamespaceResolver, "Value", ns);
170 					break;
171 				default:
172 					throw new ArgumentException (String.Format ("Unexpected Fault Code subelement: '{0}'", r.LocalName));
173 				}
174 				r.MoveToContent ();
175 			}
176 			r.ReadEndElement ();
177 
178 			return new FaultCode (value.Name, value.Namespace, subcode);
179 		}
180 
ReadFaultReason12(XmlDictionaryReader r, string ns)181 		static FaultReason ReadFaultReason12 (XmlDictionaryReader r, string ns)
182 		{
183 			List<FaultReasonText> l = new List<FaultReasonText> ();
184 			if (r.IsEmptyElement)
185 				throw new ArgumentException ("One or more Text element is mandatory in SOAP fault reason text.");
186 
187 			r.ReadStartElement ("Reason", ns);
188 			for (r.MoveToContent ();
189 			     r.NodeType != XmlNodeType.EndElement;
190 			     r.MoveToContent ()) {
191 				string lang = r.GetAttribute ("lang", "http://www.w3.org/XML/1998/namespace");
192 				if (lang == null)
193 					throw new XmlException ("xml:lang is mandatory on fault reason Text");
194 				l.Add (new FaultReasonText (r.ReadElementContentAsString ("Text", ns), lang));
195 			}
196 			r.ReadEndElement ();
197 
198 			return new FaultReason (l);
199 		}
200 
CreateFault(FaultCode code, string reason)201 		public static MessageFault CreateFault (FaultCode code,
202 			string reason)
203 		{
204 			return CreateFault (code, new FaultReason (reason));
205 		}
206 
CreateFault(FaultCode code, FaultReason reason)207 		public static MessageFault CreateFault (FaultCode code,
208 			FaultReason reason)
209 		{
210 			return new SimpleMessageFault (code, reason,
211 				 false, null, null, null, null);
212 		}
213 
CreateFault(FaultCode code, FaultReason reason, object detail)214 		public static MessageFault CreateFault (FaultCode code,
215 			FaultReason reason, object detail)
216 		{
217 			return new SimpleMessageFault (code, reason,
218 				true, detail, new DataContractSerializer (detail.GetType ()), null, null);
219 		}
220 
CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer)221 		public static MessageFault CreateFault (FaultCode code,
222 			FaultReason reason, object detail,
223 			XmlObjectSerializer serializer)
224 		{
225 			return new SimpleMessageFault (code, reason, true,
226 				detail, serializer, String.Empty, String.Empty);
227 		}
228 
CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer, string actor)229 		public static MessageFault CreateFault (FaultCode code,
230 			FaultReason reason, object detail,
231 			XmlObjectSerializer serializer, string actor)
232 		{
233 			return new SimpleMessageFault (code, reason,
234 				true, detail, serializer, actor, String.Empty);
235 		}
236 
CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer, string actor, string node)237 		public static MessageFault CreateFault (FaultCode code,
238 			FaultReason reason, object detail,
239 			XmlObjectSerializer serializer, string actor, string node)
240 		{
241 			return new SimpleMessageFault (code, reason,
242 				true, detail, serializer, actor, node);
243 		}
244 
245 		// pretty simple implementation class
246 		internal abstract class BaseMessageFault : MessageFault
247 		{
248 			string actor, node;
249 			FaultCode code;
250 			FaultReason reason;
251 
BaseMessageFault(FaultCode code, FaultReason reason, string actor, string node)252 			protected BaseMessageFault (FaultCode code, FaultReason reason, string actor, string node)
253 			{
254 				this.code = code;
255 				this.reason = reason;
256 				this.actor = actor;
257 				this.node = node;
258 			}
259 
260 			public override string Actor {
261 				get { return actor; }
262 			}
263 
264 			public override FaultCode Code {
265 				get { return code; }
266 			}
267 
268 			public override string Node {
269 				get { return node; }
270 			}
271 
272 			public override FaultReason Reason {
273 				get { return reason; }
274 			}
275 		}
276 
277 		internal class SimpleMessageFault : BaseMessageFault
278 		{
279 			bool has_detail;
280 			object detail;
281 			XmlObjectSerializer formatter;
282 
SimpleMessageFault(FaultCode code, FaultReason reason, bool has_detail, object detail, XmlObjectSerializer formatter, string actor, string node)283 			public SimpleMessageFault (FaultCode code,
284 				FaultReason reason, bool has_detail,
285 				object detail, XmlObjectSerializer formatter,
286 				string actor, string node)
287 				: this (code, reason, detail, formatter, actor, node)
288 			{
289 				this.has_detail = has_detail;
290 			}
291 
SimpleMessageFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer formatter, string actor, string node)292 			public SimpleMessageFault (FaultCode code,
293 				FaultReason reason,
294 				object detail, XmlObjectSerializer formatter,
295 				string actor, string node)
296 				: base (code, reason, actor, node)
297 			{
298 				if (code == null)
299 					throw new ArgumentNullException ("code");
300 				if (reason == null)
301 					throw new ArgumentNullException ("reason");
302 
303 				this.detail = detail;
304 				this.formatter = formatter;
305 			}
306 
307 			public override bool HasDetail {
308 				// it is not simply "detail != null" since
309 				// null detail could become <ms:anyType xsi:nil="true" />
310 				get { return has_detail; }
311 			}
312 
OnWriteDetailContents(XmlDictionaryWriter writer)313 			protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
314 			{
315 				if (formatter == null && detail != null)
316 					formatter = new DataContractSerializer (detail.GetType ());
317 				if (formatter != null)
318 					formatter.WriteObject (writer, detail);
319 				else
320 					throw new InvalidOperationException ("There is no fault detail to write");
321 			}
322 
323 			public object Detail {
324 				get { return detail; }
325 			}
326 		}
327 
328 		class XmlReaderDetailMessageFault : BaseMessageFault
329 		{
330 			XmlDictionaryReader reader;
331 			bool consumed;
332 			bool has_detail;
333 
XmlReaderDetailMessageFault(Message message, XmlDictionaryReader reader, FaultCode code, FaultReason reason, string actor, string node)334 			public XmlReaderDetailMessageFault (Message message, XmlDictionaryReader reader, FaultCode code, FaultReason reason, string actor, string node)
335 				: base (code, reason, actor, node)
336 			{
337 				this.reader = reader;
338 				if (reader.IsEmptyElement)
339 					has_detail = false;
340 				reader.MoveToContent ();
341 				reader.ReadStartElement (); // consume the wrapper
342 				reader.MoveToContent ();
343 				has_detail = reader.NodeType != XmlNodeType.EndElement;
344 			}
345 
Consume()346 			void Consume ()
347 			{
348 				if (consumed)
349 					throw new InvalidOperationException ("The fault detail content is already consumed");
350 				consumed = true;
351 			}
352 
353 			public override bool HasDetail {
354 				get { return has_detail; }
355 			}
356 
OnGetReaderAtDetailContents()357 			protected override XmlDictionaryReader OnGetReaderAtDetailContents ()
358 			{
359 				Consume ();
360 				return reader;
361 			}
362 
OnWriteDetailContents(XmlDictionaryWriter writer)363 			protected override void OnWriteDetailContents (XmlDictionaryWriter writer)
364 			{
365 				if (!HasDetail)
366 					throw new InvalidOperationException ("There is no fault detail to write");
367 				Consume ();
368 				while (reader.NodeType != XmlNodeType.EndElement)
369 					writer.WriteNode (reader, false);
370 			}
371 		}
372 
373 		// instance members
374 
MessageFault()375 		protected MessageFault ()
376 		{
377 		}
378 
379 		[MonoTODO ("is this true?")]
380 		public virtual string Actor {
381 			get { return String.Empty; }
382 		}
383 
384 		public abstract FaultCode Code { get; }
385 
386 		public abstract bool HasDetail { get; }
387 
388 		[MonoTODO ("is this true?")]
389 		public virtual string Node {
390 			get { return String.Empty; }
391 		}
392 
393 		public abstract FaultReason Reason { get; }
394 
GetDetail()395 		public T GetDetail<T> ()
396 		{
397 			return GetDetail<T> (new DataContractSerializer (typeof (T)));
398 		}
399 
GetDetail(XmlObjectSerializer serializer)400 		public T GetDetail<T> (XmlObjectSerializer serializer)
401 		{
402 			if (!HasDetail)
403 				throw new InvalidOperationException ("This message does not have details.");
404 
405 			return (T) serializer.ReadObject (GetReaderAtDetailContents ());
406 		}
407 
GetReaderAtDetailContents()408 		public XmlDictionaryReader GetReaderAtDetailContents ()
409 		{
410 			return OnGetReaderAtDetailContents ();
411 		}
412 
WriteTo(XmlDictionaryWriter writer, EnvelopeVersion version)413 		public void WriteTo (XmlDictionaryWriter writer,
414 			EnvelopeVersion version)
415 		{
416 			writer.WriteStartElement ("Fault", version.Namespace);
417 			WriteFaultCode (writer, version, Code, false);
418 			WriteReason (writer, version);
419 			if (HasDetail)
420 				OnWriteDetail (writer, version);
421 			writer.WriteEndElement ();
422 		}
423 
WriteFaultCode(XmlDictionaryWriter writer, EnvelopeVersion version, FaultCode code, bool sub)424 		private void WriteFaultCode (XmlDictionaryWriter writer,
425 			EnvelopeVersion version, FaultCode code, bool sub)
426 		{
427 			if (version == EnvelopeVersion.Soap11) {
428 				writer.WriteStartElement ("", "faultcode", String.Empty);
429 				if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
430 					writer.WriteXmlnsAttribute ("a", code.Namespace);
431 				writer.WriteQualifiedName (code.Name, code.Namespace);
432 				writer.WriteEndElement ();
433 			} else { // Soap12
434 				writer.WriteStartElement (sub ? "Subcode" : "Code", version.Namespace);
435 				writer.WriteStartElement ("Value", version.Namespace);
436 				if (code.Namespace.Length > 0 && String.IsNullOrEmpty (writer.LookupPrefix (code.Namespace)))
437 					writer.WriteXmlnsAttribute ("a", code.Namespace);
438 				writer.WriteQualifiedName (code.Name, code.Namespace);
439 				writer.WriteEndElement ();
440 				if (code.SubCode != null)
441 					WriteFaultCode (writer, version, code.SubCode, true);
442 				writer.WriteEndElement ();
443 			}
444 		}
445 
WriteReason(XmlDictionaryWriter writer, EnvelopeVersion version)446 		private void WriteReason (XmlDictionaryWriter writer,
447 			EnvelopeVersion version)
448 		{
449 			if (version == EnvelopeVersion.Soap11) {
450 				foreach (FaultReasonText t in Reason.Translations) {
451 					writer.WriteStartElement ("", "faultstring", String.Empty);
452 					if (t.XmlLang != null)
453 						writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
454 					writer.WriteString (t.Text);
455 					writer.WriteEndElement ();
456 				}
457 			} else { // Soap12
458 				writer.WriteStartElement ("Reason", version.Namespace);
459 				foreach (FaultReasonText t in Reason.Translations) {
460 					writer.WriteStartElement ("Text", version.Namespace);
461 					if (t.XmlLang != null)
462 						writer.WriteAttributeString ("xml", "lang", null, t.XmlLang);
463 					writer.WriteString (t.Text);
464 					writer.WriteEndElement ();
465 				}
466 				writer.WriteEndElement ();
467 			}
468 		}
469 
WriteTo(XmlWriter writer, EnvelopeVersion version)470 		public void WriteTo (XmlWriter writer, EnvelopeVersion version)
471 		{
472 			WriteTo (XmlDictionaryWriter.CreateDictionaryWriter (
473 				writer), version);
474 		}
475 
OnGetReaderAtDetailContents()476 		protected virtual XmlDictionaryReader OnGetReaderAtDetailContents ()
477 		{
478 			if (!HasDetail)
479 				throw new InvalidOperationException ("There is no fault detail to read");
480 			MemoryStream ms = new MemoryStream ();
481 			using (XmlDictionaryWriter dw =
482 				XmlDictionaryWriter.CreateDictionaryWriter (
483 					XmlWriter.Create (ms))) {
484 				OnWriteDetailContents (dw);
485 			}
486 			ms.Seek (0, SeekOrigin.Begin);
487 			return XmlDictionaryReader.CreateDictionaryReader (
488 				XmlReader.Create (ms));
489 		}
490 
OnWriteDetail(XmlDictionaryWriter writer, EnvelopeVersion version)491 		protected virtual void OnWriteDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
492 		{
493 			OnWriteStartDetail (writer, version);
494 			OnWriteDetailContents (writer);
495 			writer.WriteEndElement ();
496 		}
497 
OnWriteStartDetail(XmlDictionaryWriter writer, EnvelopeVersion version)498 		protected virtual void OnWriteStartDetail (XmlDictionaryWriter writer, EnvelopeVersion version)
499 		{
500 			if (version == EnvelopeVersion.Soap11)
501 				writer.WriteStartElement ("detail", String.Empty);
502 			else // Soap12
503 				writer.WriteStartElement ("Detail", version.Namespace);
504 		}
505 
OnWriteDetailContents(XmlDictionaryWriter writer)506 		protected abstract void OnWriteDetailContents (XmlDictionaryWriter writer);
507 	}
508 }
509