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