/* XML-RPC.NET library Copyright (c) 2001-2006, Charles Cook 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. */ // TODO: overriding default mapping action in a struct should not affect nested structs namespace CookComputing.XmlRpc { using System; using System.Collections; using System.Globalization; using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; struct Fault { public int faultCode; public string faultString; } public class XmlRpcSerializer { // public properties public int Indentation { get { return m_indentation; } set { m_indentation = value; } } int m_indentation = 2; public XmlRpcNonStandard NonStandard { get { return m_nonStandard; } set { m_nonStandard = value; } } XmlRpcNonStandard m_nonStandard = XmlRpcNonStandard.None; public bool UseIndentation { get { return m_bUseIndentation; } set { m_bUseIndentation = value; } } bool m_bUseIndentation = true; public bool UseIntTag { get { return m_useIntTag; } set { m_useIntTag = value; } } bool m_useIntTag; public Encoding XmlEncoding { get { return m_encoding; } set { m_encoding = value; } } Encoding m_encoding = null; // private properties bool AllowInvalidHTTPContent { get { return (m_nonStandard & XmlRpcNonStandard.AllowInvalidHTTPContent) != 0; } } bool AllowNonStandardDateTime { get { return (m_nonStandard & XmlRpcNonStandard.AllowNonStandardDateTime) != 0; } } bool AllowStringFaultCode { get { return (m_nonStandard & XmlRpcNonStandard.AllowStringFaultCode) != 0; } } bool IgnoreDuplicateMembers { get { return (m_nonStandard & XmlRpcNonStandard.IgnoreDuplicateMembers) != 0; } } bool MapEmptyDateTimeToMinValue { get { return (m_nonStandard & XmlRpcNonStandard.MapEmptyDateTimeToMinValue) != 0; } } bool MapZerosDateTimeToMinValue { get { return (m_nonStandard & XmlRpcNonStandard.MapZerosDateTimeToMinValue) != 0; } } public void SerializeRequest(Stream stm, XmlRpcRequest request) { XmlTextWriter xtw = new XmlTextWriter(stm, m_encoding); ConfigureXmlFormat(xtw); xtw.WriteStartDocument(); xtw.WriteStartElement("", "methodCall", ""); { ParameterInfo[] pis = null; if (request.mi != null) { pis = request.mi.GetParameters(); } // TODO: use global action setting MappingAction mappingAction = MappingAction.Error; if (request.xmlRpcMethod == null) xtw.WriteElementString("methodName", request.method); else xtw.WriteElementString("methodName", request.xmlRpcMethod); xtw.WriteStartElement("", "params", ""); try { for (int i=0; i < request.args.Length; i++) { if (pis != null) { if (i >= pis.Length) throw new XmlRpcInvalidParametersException("Number of request " + "parameters greater than number of proxy method parameters."); if (Attribute.IsDefined(pis[i], typeof(ParamArrayAttribute))) { Array ary = (Array)request.args[i]; foreach (object o in ary) { if (o == null) throw new XmlRpcNullParameterException( "Null parameter in params array"); xtw.WriteStartElement("", "param", ""); Serialize(xtw, o, mappingAction); xtw.WriteEndElement(); } break; } } if (request.args[i] == null) { throw new XmlRpcNullParameterException(String.Format( "Null method parameter #{0}", i+1)); } xtw.WriteStartElement("", "param", ""); Serialize(xtw, request.args[i], mappingAction); xtw.WriteEndElement(); } } catch (XmlRpcUnsupportedTypeException ex) { throw new XmlRpcUnsupportedTypeException(ex.UnsupportedType, String.Format("A parameter is of, or contains an instance of, " + "type {0} which cannot be mapped to an XML-RPC type", ex.UnsupportedType)); } xtw.WriteEndElement(); } xtw.WriteEndElement(); xtw.Flush(); } #if (!COMPACT_FRAMEWORK) public XmlRpcRequest DeserializeRequest(Stream stm, Type svcType) { if (stm == null) throw new ArgumentNullException("stm", "XmlRpcSerializer.DeserializeRequest"); XmlDocument xdoc = new XmlDocument(); try { xdoc.Load(stm); } catch (Exception ex) { throw new XmlRpcIllFormedXmlException( "Request from client does not contain valid XML.", ex); } return DeserializeRequest(xdoc, svcType); } public XmlRpcRequest DeserializeRequest(TextReader txtrdr, Type svcType) { if (txtrdr == null) throw new ArgumentNullException("txtrdr", "XmlRpcSerializer.DeserializeRequest"); XmlDocument xdoc = new XmlDocument(); try { xdoc.Load(txtrdr); } catch (Exception ex) { throw new XmlRpcIllFormedXmlException( "Request from client does not contain valid XML.", ex); } return DeserializeRequest(xdoc, svcType); } public XmlRpcRequest DeserializeRequest(XmlDocument xdoc, Type svcType) { XmlRpcRequest request = new XmlRpcRequest(); XmlNode callNode = SelectSingleNode(xdoc, "methodCall"); if (callNode == null) { throw new XmlRpcInvalidXmlRpcException( "Request XML not valid XML-RPC - missing methodCall element."); } XmlNode methodNode = SelectSingleNode(callNode, "methodName"); if (methodNode == null) { throw new XmlRpcInvalidXmlRpcException( "Request XML not valid XML-RPC - missing methodName element."); } request.method = methodNode.FirstChild.Value; if (request.method == "") { throw new XmlRpcInvalidXmlRpcException( "Request XML not valid XML-RPC - empty methodName."); } request.mi = null; ParameterInfo[] pis = new ParameterInfo[1]; if (svcType != null) { // retrieve info for the method which handles this XML-RPC method XmlRpcServiceInfo svcInfo = XmlRpcServiceInfo.CreateServiceInfo(svcType); request.mi = svcInfo.GetMethodInfo(request.method); // if a service type has been specified and we cannot find the requested // method then we must throw an exception if (request.mi == null) { string msg = String.Format("unsupported method called: {0}", request.method); throw new XmlRpcUnsupportedMethodException(msg); } // method must be marked with XmlRpcMethod attribute Attribute attr = Attribute.GetCustomAttribute(request.mi, typeof(XmlRpcMethodAttribute)); if (attr == null) { throw new XmlRpcMethodAttributeException( "Method must be marked with the XmlRpcMethod attribute."); } pis = request.mi.GetParameters(); } XmlNode paramsNode = SelectSingleNode(callNode, "params"); if (paramsNode == null) { if (svcType != null) { if (pis.Length == 0) { request.args = new object[0]; return request; } else { throw new XmlRpcInvalidParametersException( "Method takes parameters and params element is missing."); } } else { request.args = new object[0]; return request; } } XmlNodeList paramNodes = paramsNode.ChildNodes; int paramsPos = -1; if (svcType != null && paramNodes.Count != pis.Length) { paramsPos = CheckParameterCount(paramNodes, pis); } ParseStack parseStack = new ParseStack("request"); // TODO: use global action setting MappingAction mappingAction = MappingAction.Error; int paramObjCount = (paramsPos == -1 ?paramNodes.Count : paramsPos + 1); Object[] paramObjs = new Object[paramObjCount]; // parse ordinary parameters int ordinaryParams = (paramsPos == -1 ?paramNodes.Count :paramsPos); for (int i = 0; i < ordinaryParams; i++) { XmlNode paramNode = paramNodes[i]; XmlNode valueNode = paramNode.FirstChild; XmlNode node = valueNode.FirstChild; if (svcType != null) { parseStack.Push(String.Format("parameter {0}", i + 1)); // TODO: why following commented out? // parseStack.Push(String.Format("parameter {0} mapped to type {1}", // i, pis[i].ParameterType.Name)); paramObjs[i] = ParseValue(node, pis[i].ParameterType, parseStack, mappingAction); } else { parseStack.Push(String.Format("parameter {0}", i)); paramObjs[i] = ParseValue(node, null, parseStack, mappingAction); } parseStack.Pop(); } // parse params parameters if (paramsPos != -1) { Type paramsType = pis[paramsPos].ParameterType.GetElementType(); Object[] args = new Object[1]; args[0] = paramNodes.Count - paramsPos; Array varargs = (Array)CreateArrayInstance(pis[paramsPos].ParameterType, args); for (int i = 0; i < varargs.Length; i++) { XmlNode paramNode = paramNodes[i + paramsPos]; XmlNode valueNode = paramNode.FirstChild; XmlNode node = valueNode.FirstChild; parseStack.Push(String.Format("parameter {0}", i + 1 + paramsPos)); varargs.SetValue(ParseValue(node, paramsType, parseStack, mappingAction), i); parseStack.Pop(); } paramObjs[paramsPos] = varargs; } request.args = paramObjs; return request; } int CheckParameterCount(XmlNodeList paramNodes, ParameterInfo[] pis) { // returns position of params parameter or -1 int nodeCount = paramNodes.Count; if (nodeCount == 0 && pis.Length == 0) return -1; if (Attribute.IsDefined(pis[pis.Length - 1], typeof(ParamArrayAttribute))) { // note that params array parameter might have zero items if (nodeCount >= (pis.Length - 1)) return pis.Length - 1; } else { if (nodeCount == pis.Length) return -1; } throw new XmlRpcInvalidParametersException( "Method takes parameters and there is incorrect number of param " + "elements."); } public void SerializeResponse(Stream stm, XmlRpcResponse response) { Object ret = response.retVal; if (ret is XmlRpcFaultException) { SerializeFaultResponse(stm, (XmlRpcFaultException)ret); return; } XmlTextWriter xtw = new XmlTextWriter(stm, m_encoding); ConfigureXmlFormat(xtw); xtw.WriteStartDocument(); xtw.WriteStartElement("", "methodResponse", ""); xtw.WriteStartElement("", "params", ""); // "void" methods actually return an empty string value if (ret == null) { ret = ""; } xtw.WriteStartElement("", "param", ""); // TODO: use global action setting MappingAction mappingAction = MappingAction.Error; try { Serialize(xtw, ret, mappingAction); } catch (XmlRpcUnsupportedTypeException ex) { throw new XmlRpcInvalidReturnType(string.Format( "Return value is of, or contains an instance of, type {0} which " + "cannot be mapped to an XML-RPC type", ex.UnsupportedType)); } xtw.WriteEndElement(); xtw.WriteEndElement(); xtw.WriteEndElement(); xtw.Flush(); } #endif public XmlRpcResponse DeserializeResponse(Stream stm, Type svcType) { if (stm == null) throw new ArgumentNullException("stm", "XmlRpcSerializer.DeserializeResponse"); if (AllowInvalidHTTPContent) { Stream newStm = new MemoryStream(); Util.CopyStream(stm, newStm); stm = newStm; stm.Position = 0; while (true) { // for now just strip off any leading CR-LF characters int byt = stm.ReadByte(); if (byt == -1) throw new XmlRpcIllFormedXmlException( "Response from server does not contain valid XML."); if (byt != 0x0d && byt != 0x0a && byt != ' ' && byt != '\t') { stm.Position = stm.Position - 1; break; } } } XmlDocument xdoc = new XmlDocument(); try { xdoc.Load(stm); } catch (Exception ex) { throw new XmlRpcIllFormedXmlException( "Response from server does not contain valid XML.", ex); } return DeserializeResponse(xdoc, svcType); } public XmlRpcResponse DeserializeResponse(TextReader txtrdr, Type svcType) { if (txtrdr == null) throw new ArgumentNullException("txtrdr", "XmlRpcSerializer.DeserializeResponse"); XmlDocument xdoc = new XmlDocument(); try { xdoc.Load(txtrdr); } catch (Exception ex) { throw new XmlRpcIllFormedXmlException( "Response from server does not contain valid XML.", ex); } return DeserializeResponse(xdoc, svcType); } public XmlRpcResponse DeserializeResponse(XmlDocument xdoc, Type returnType) { XmlRpcResponse response = new XmlRpcResponse(); Object retObj = null; XmlNode methodResponseNode = SelectSingleNode(xdoc, "methodResponse"); if (methodResponseNode == null) { throw new XmlRpcInvalidXmlRpcException( "Response XML not valid XML-RPC - missing methodResponse element."); } // check for fault response XmlNode faultNode = SelectSingleNode(methodResponseNode, "fault"); if (faultNode != null) { ParseStack parseStack = new ParseStack("fault response"); // TODO: use global action setting MappingAction mappingAction = MappingAction.Error; XmlRpcFaultException faultEx = ParseFault(faultNode, parseStack, mappingAction); throw faultEx; } XmlNode paramsNode = SelectSingleNode(methodResponseNode, "params"); if (paramsNode == null && returnType != null) { if (returnType == typeof(void)) return null; else throw new XmlRpcInvalidXmlRpcException( "Response XML not valid XML-RPC - missing params element."); } XmlNode paramNode = SelectSingleNode(paramsNode, "param"); // TODO: verify single params node? if (paramNode == null && returnType != null) { if (returnType == typeof(void)) return null; else throw new XmlRpcInvalidXmlRpcException( "Response XML not valid XML-RPC - missing params element."); } XmlNode valueNode = SelectSingleNode(paramNode, "value"); if (valueNode == null) { throw new XmlRpcInvalidXmlRpcException( "Response XML not valid XML-RPC - missing value element."); } if (returnType == typeof(void)) { retObj = null; } else { ParseStack parseStack = new ParseStack("response"); // TODO: use global action setting MappingAction mappingAction = MappingAction.Error; XmlNode node = valueNode.FirstChild; retObj = ParseValue(node, returnType, parseStack, mappingAction); } response.retVal = retObj; return response; } //#if (DEBUG) public //#endif void Serialize( XmlTextWriter xtw, Object o, MappingAction mappingAction) { Serialize(xtw, o, mappingAction, new ArrayList(16)); } //#if (DEBUG) public //#endif void Serialize( XmlTextWriter xtw, Object o, MappingAction mappingAction, ArrayList nestedObjs) { if (nestedObjs.Contains(o)) throw new XmlRpcUnsupportedTypeException(nestedObjs[0].GetType(), "Cannot serialize recursive data structure"); nestedObjs.Add(o); try { xtw.WriteStartElement("", "value", ""); XmlRpcType xType = XmlRpcServiceInfo.GetXmlRpcType(o.GetType()); if (xType == XmlRpcType.tArray) { xtw.WriteStartElement("", "array", ""); xtw.WriteStartElement("", "data", ""); Array a = (Array) o; foreach (Object aobj in a) { if (aobj == null) throw new XmlRpcMappingSerializeException(String.Format( "Items in array cannot be null ({0}[]).", o.GetType().GetElementType())); Serialize(xtw, aobj, mappingAction, nestedObjs); } xtw.WriteEndElement(); xtw.WriteEndElement(); } else if (xType == XmlRpcType.tMultiDimArray) { Array mda = (Array)o; int[] indices = new int[mda.Rank]; BuildArrayXml(xtw, mda, 0, indices, mappingAction, nestedObjs); } else if (xType == XmlRpcType.tBase64) { byte[] buf = (byte[])o; xtw.WriteStartElement("", "base64", ""); xtw.WriteBase64(buf, 0, buf.Length); xtw.WriteEndElement(); } else if (xType == XmlRpcType.tBoolean) { bool boolVal; if (o is bool) boolVal = (bool)o; else boolVal = (bool)(XmlRpcBoolean)o; if (boolVal) xtw.WriteElementString("boolean", "1"); else xtw.WriteElementString("boolean", "0"); } else if (xType == XmlRpcType.tDateTime) { DateTime dt; if (o is DateTime) dt = (DateTime)o; else dt = (XmlRpcDateTime)o; string sdt = dt.ToString("yyyyMMdd'T'HH':'mm':'ss", DateTimeFormatInfo.InvariantInfo); xtw.WriteElementString("dateTime.iso8601", sdt); } else if (xType == XmlRpcType.tDouble) { double doubleVal; if (o is double) doubleVal = (double)o; else doubleVal = (XmlRpcDouble)o; xtw.WriteElementString("double", doubleVal.ToString(null, CultureInfo.InvariantCulture)); } else if (xType == XmlRpcType.tHashtable) { xtw.WriteStartElement("", "struct", ""); XmlRpcStruct xrs = o as XmlRpcStruct; foreach (object obj in xrs.Keys) { string skey = obj as string; xtw.WriteStartElement("", "member", ""); xtw.WriteElementString("name", skey); Serialize(xtw, xrs[skey], mappingAction, nestedObjs); xtw.WriteEndElement(); } xtw.WriteEndElement(); } else if (xType == XmlRpcType.tInt32) { if (UseIntTag) xtw.WriteElementString("int", o.ToString()); else xtw.WriteElementString("i4", o.ToString()); } else if (xType == XmlRpcType.tString) { xtw.WriteElementString("string", (string)o); } else if (xType == XmlRpcType.tStruct) { MappingAction structAction = StructMappingAction(o.GetType(), mappingAction); xtw.WriteStartElement("", "struct", ""); MemberInfo[] mis = o.GetType().GetMembers(); foreach (MemberInfo mi in mis) { if (mi.MemberType == MemberTypes.Field) { FieldInfo fi = (FieldInfo)mi; string member = fi.Name; Attribute attrchk = Attribute.GetCustomAttribute(fi, typeof(XmlRpcMemberAttribute)); if (attrchk != null && attrchk is XmlRpcMemberAttribute) { string mmbr = ((XmlRpcMemberAttribute)attrchk).Member; if (mmbr != "") member = mmbr; } if (fi.GetValue(o) == null) { MappingAction memberAction = MemberMappingAction(o.GetType(), fi.Name, structAction); if (memberAction == MappingAction.Ignore) continue; throw new XmlRpcMappingSerializeException(@"Member """ + member + @""" of struct """ + o.GetType().Name + @""" cannot be null."); } xtw.WriteStartElement("", "member", ""); xtw.WriteElementString("name", member); Serialize(xtw, fi.GetValue(o), mappingAction, nestedObjs); xtw.WriteEndElement(); } else if (mi.MemberType == MemberTypes.Property) { PropertyInfo pi = (PropertyInfo)mi; string member = pi.Name; Attribute attrchk = Attribute.GetCustomAttribute(pi, typeof(XmlRpcMemberAttribute)); if (attrchk != null && attrchk is XmlRpcMemberAttribute) { string mmbr = ((XmlRpcMemberAttribute)attrchk).Member; if (mmbr != "") member = mmbr; } if (pi.GetValue(o, null) == null) { MappingAction memberAction = MemberMappingAction(o.GetType(), pi.Name, structAction); if (memberAction == MappingAction.Ignore) continue; } xtw.WriteStartElement("", "member", ""); xtw.WriteElementString("name", member); Serialize(xtw, pi.GetValue(o, null), mappingAction, nestedObjs); xtw.WriteEndElement(); } } xtw.WriteEndElement(); } else if (xType == XmlRpcType.tVoid) xtw.WriteElementString("string", ""); else throw new XmlRpcUnsupportedTypeException(o.GetType()); xtw.WriteEndElement(); } catch(System.NullReferenceException) { throw new XmlRpcNullReferenceException("Attempt to serialize data " + "containing null reference"); } finally { nestedObjs.RemoveAt(nestedObjs.Count - 1); } } void BuildArrayXml( XmlTextWriter xtw, Array ary, int CurRank, int[] indices, MappingAction mappingAction, ArrayList nestedObjs) { xtw.WriteStartElement("", "array", ""); xtw.WriteStartElement("", "data", ""); if (CurRank < (ary.Rank-1)) { for (int i=0; i=0; f--) { indices[f] = (e/div)%dimLengths[f]; div*=dimLengths[f]; } ret.SetValue(elements[e], indices); } return ret; } void ParseMultiDimElements(XmlNode node, int Rank, int CurRank, Type elemType, ArrayList elements, int[] dimLengths, ParseStack parseStack, MappingAction mappingAction) { if (node.Name != "array") { throw new XmlRpcTypeMismatchException( "param element does not contain array element."); } XmlNode dataNode = SelectSingleNode(node, "data"); int nodeCount = dataNode.ChildNodes.Count; //!! check that multi dim array is not jagged if (dimLengths[CurRank] != 0 && nodeCount != dimLengths[CurRank]) { throw new XmlRpcNonRegularArrayException( "Multi-dimensional array must not be jagged."); } dimLengths[CurRank] = nodeCount; // in case first array at this rank if (CurRank < (Rank-1)) { foreach (XmlNode vNode in dataNode.ChildNodes) { XmlNode arrayNode = SelectSingleNode(vNode, "array"); ParseMultiDimElements(arrayNode, Rank, CurRank+1, elemType, elements, dimLengths, parseStack, mappingAction); } } else { foreach (XmlNode vNode in dataNode.ChildNodes) { XmlNode vvNode = vNode.FirstChild; elements.Add(ParseValue(vvNode, elemType, parseStack, mappingAction)); } } } Object ParseStruct( XmlNode node, Type valueType, ParseStack parseStack, MappingAction mappingAction) { if (valueType.IsPrimitive) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains struct value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(valueType) + " expected " + StackDump(parseStack)); } #if !FX1_0 if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>)) { valueType = valueType.GetGenericArguments()[0]; } #endif // Note: mapping action on a struct is only applied locally - it // does not override the global mapping action when members of the // struct are parsed MappingAction localAction = mappingAction; if (valueType != null) { parseStack.Push("struct mapped to type " + valueType.Name); localAction = StructMappingAction(valueType, mappingAction); } else { parseStack.Push("struct"); } // create map of field names and remove each name from it as // processed so we can determine which fields are missing // TODO: replace HashTable with lighter collection Hashtable names = new Hashtable(); foreach (FieldInfo fi in valueType.GetFields()) { names.Add(fi.Name, fi.Name); } foreach (PropertyInfo pi in valueType.GetProperties()) { names.Add(pi.Name, pi.Name); } XmlNodeList members = node.ChildNodes; Object retObj = Activator.CreateInstance(valueType); int fieldCount = 0; foreach (XmlNode member in members) { if (member.Name != "member") continue; XmlNode nameNode; bool dupName; XmlNode valueNode; bool dupValue; SelectTwoNodes(member, "name", out nameNode, out dupName, "value", out valueNode, out dupValue); if (nameNode == null) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains a member with missing name element" + " " + StackDump(parseStack)); if (dupName) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains member with more than one name element" + " " + StackDump(parseStack)); string name = nameNode.FirstChild.Value; if (valueNode == null) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains struct member " + name + " with missing value " + " " + StackDump(parseStack)); if (dupValue) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains member with more than one value element" + " " + StackDump(parseStack)); string structName = GetStructName(valueType, name); if (structName != null) name = structName; if (names.Contains(name)) names.Remove(name); else { if (!IgnoreDuplicateMembers && (valueType.GetField(name) != null || valueType.GetProperty(name) != null)) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains struct value with duplicate member " + nameNode.FirstChild.Value + " " + StackDump(parseStack)); else continue; // ignore duplicate member } MemberInfo[] mis = valueType.GetMember(name); if (mis.Length == 0) { continue; // allow unexpected members } Object valObj = null; switch (mis[0].MemberType) { case MemberTypes.Field: FieldInfo fi = (FieldInfo)mis[0]; if (valueType == null) parseStack.Push(String.Format("member {0}", name)); else parseStack.Push(String.Format("member {0} mapped to type {1}", name,fi.FieldType.Name)); try { valObj = ParseValue(valueNode.FirstChild, fi.FieldType, parseStack, mappingAction); } catch(XmlRpcInvalidXmlRpcException) { if (valueType != null && localAction == MappingAction.Error) { MappingAction memberAction = MemberMappingAction(valueType, name, MappingAction.Error); if (memberAction == MappingAction.Error) throw; } } finally { parseStack.Pop(); } fi.SetValue(retObj, valObj); break ; case MemberTypes.Property : PropertyInfo pi = (PropertyInfo)mis[0] ; if (valueType == null) parseStack.Push(String.Format("member {0}", name)); else parseStack.Push(String.Format("member {0} mapped to type {1}", name,pi.PropertyType.Name)); valObj = ParseValue(valueNode.FirstChild, pi.PropertyType, parseStack, mappingAction); parseStack.Pop(); pi.SetValue(retObj, valObj, null); break ; } fieldCount++; } if (localAction == MappingAction.Error && names.Count > 0) ReportMissingMembers(valueType, names, parseStack); parseStack.Pop(); return retObj; } void ReportMissingMembers( Type valueType, Hashtable names, ParseStack parseStack) { StringBuilder sb = new StringBuilder(); int errorCount = 0; string sep = ""; foreach (string s in names.Keys) { MappingAction memberAction = MemberMappingAction(valueType, s, MappingAction.Error); if (memberAction == MappingAction.Error) { sb.Append(sep); sb.Append(s); sep = " "; errorCount++; } } if (errorCount > 0) { string plural = ""; if (errorCount > 1) plural = "s"; throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains struct value with missing non-optional member" + plural + ": " + sb.ToString() + " " + StackDump(parseStack)); } } string GetStructName(Type ValueType, string XmlRpcName) { // given a member name in an XML-RPC struct, check to see whether // a field has been associated with this XML-RPC member name, return // the field name if it has else return null if (ValueType == null) return null; foreach (FieldInfo fi in ValueType.GetFields()) { Attribute attr = Attribute.GetCustomAttribute(fi, typeof(XmlRpcMemberAttribute)); if (attr != null && attr is XmlRpcMemberAttribute && ((XmlRpcMemberAttribute)attr).Member == XmlRpcName) { string ret = fi.Name; return ret; } } return null; } MappingAction StructMappingAction( Type type, MappingAction currentAction) { // if struct member has mapping action attribute, override the current // mapping action else just return the current action if (type == null) return currentAction; Attribute attr = Attribute.GetCustomAttribute(type, typeof(XmlRpcMissingMappingAttribute)); if (attr != null) return ((XmlRpcMissingMappingAttribute)attr).Action; return currentAction; } MappingAction MemberMappingAction( Type type, string memberName, MappingAction currentAction) { // if struct member has mapping action attribute, override the current // mapping action else just return the current action if (type == null) return currentAction; Attribute attr = null; FieldInfo fi = type.GetField(memberName); if (fi != null) attr = Attribute.GetCustomAttribute(fi, typeof(XmlRpcMissingMappingAttribute)); else { PropertyInfo pi = type.GetProperty(memberName); attr = Attribute.GetCustomAttribute(pi, typeof(XmlRpcMissingMappingAttribute)); } if (attr != null) return ((XmlRpcMissingMappingAttribute)attr).Action; return currentAction; } Object ParseHashtable( XmlNode node, Type valueType, ParseStack parseStack, MappingAction mappingAction) { XmlRpcStruct retObj = new XmlRpcStruct(); parseStack.Push("struct mapped to XmlRpcStruct"); try { XmlNodeList members = node.ChildNodes; foreach (XmlNode member in members) { if (member.Name != "member") continue; XmlNode nameNode; bool dupName; XmlNode valueNode; bool dupValue; SelectTwoNodes(member, "name", out nameNode, out dupName, "value", out valueNode, out dupValue); if (nameNode == null) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains a member with missing name element" + " " + StackDump(parseStack)); if (dupName) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains member with more than one name element" + " " + StackDump(parseStack)); string rpcName = nameNode.FirstChild.Value; if (valueNode == null) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains struct member " + rpcName + " with missing value " + " " + StackDump(parseStack)); if (dupValue) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains member with more than one value element" + " " + StackDump(parseStack)); if (retObj.Contains(rpcName)) { if (!IgnoreDuplicateMembers) throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains struct value with duplicate member " + nameNode.FirstChild.Value + " " + StackDump(parseStack)); else continue; } object valObj; parseStack.Push(String.Format("member {0}", rpcName)); try { valObj = ParseValue(valueNode.FirstChild, null, parseStack, mappingAction); } finally { parseStack.Pop(); } retObj.Add(rpcName, valObj); } } finally { parseStack.Pop(); } return retObj; } Object ParseInt( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(Object) && ValueType != typeof(System.Int32) #if !FX1_0 && ValueType != typeof(int?) #endif && ValueType != typeof(XmlRpcInt)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains int value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } int retVal; parseStack.Push("integer"); try { XmlNode valueNode = node.FirstChild; if (valueNode == null) { throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType +" contains invalid int element " + StackDump(parseStack)); } try { String strValue = valueNode.Value; retVal = Int32.Parse(strValue); } catch(Exception) { throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains invalid int value " + StackDump(parseStack)); } } finally { parseStack.Pop(); } if (ValueType == typeof(XmlRpcInt)) return new XmlRpcInt(retVal); else return retVal; } Object ParseString( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(System.String) && ValueType != typeof(Object)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains string value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } string ret; parseStack.Push("string"); try { if (node.FirstChild == null) ret = ""; else ret = node.FirstChild.Value; } finally { parseStack.Pop(); } return ret; } Object ParseBoolean( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(Object) && ValueType != typeof(System.Boolean) #if !FX1_0 && ValueType != typeof(bool?) #endif && ValueType != typeof(XmlRpcBoolean)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains boolean value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } bool retVal; parseStack.Push("boolean"); try { string s = node.FirstChild.Value; if (s == "1") { retVal = true; } else if (s == "0") { retVal = false; } else { throw new XmlRpcInvalidXmlRpcException( "reponse contains invalid boolean value " + StackDump(parseStack)); } } finally { parseStack.Pop(); } if (ValueType == typeof(XmlRpcBoolean)) return new XmlRpcBoolean(retVal); else return retVal; } Object ParseDouble( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(Object) && ValueType != typeof(System.Double) #if !FX1_0 && ValueType != typeof(double?) #endif && ValueType != typeof(XmlRpcDouble)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains double value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } Double retVal; parseStack.Push("double"); try { retVal = Double.Parse(node.FirstChild.Value, CultureInfo.InvariantCulture.NumberFormat); } catch(Exception) { throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains invalid double value " + StackDump(parseStack)); } finally { parseStack.Pop(); } if (ValueType == typeof(XmlRpcDouble)) return new XmlRpcDouble(retVal); else return retVal; } Object ParseDateTime( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(Object) && ValueType != typeof(System.DateTime) #if !FX1_0 && ValueType != typeof(DateTime?) #endif && ValueType != typeof(XmlRpcDateTime)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains dateTime.iso8601 value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } DateTime retVal; parseStack.Push("dateTime"); try { XmlNode child = node.FirstChild; if (child == null) { if (MapEmptyDateTimeToMinValue) return DateTime.MinValue; else throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains empty dateTime value " + StackDump(parseStack)); } string s = child.Value; try { // XML-RPC spec yyyyMMddThh:mm:ss string dateTimeFormat = "yyyyMMdd'T'HH':'mm':'ss"; if (AllowNonStandardDateTime) { if (s.IndexOf("T") == 8) { if (s.EndsWith("Z")) { // WordPress yyyyMMddThh:mm:ssZ dateTimeFormat = "yyyyMMdd'T'HH':'mm':'ss'Z'"; } } else { if (s.EndsWith("Z")) { // TypePad yyyy-MM-ddThh:mm:ssZ dateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"; } else { // other yyyy-MM-ddThh:mm:ss dateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"; } } } if (MapZerosDateTimeToMinValue && s.StartsWith("0000") && (s == "00000000T00:00:00" || s == "0000-00-00T00:00:00Z" || s == "00000000T00:00:00Z" || s == "0000-00-00T00:00:00")) retVal = DateTime.MinValue; else retVal = DateTime.ParseExact(s, dateTimeFormat, DateTimeFormatInfo.InvariantInfo); } catch(Exception) { throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains invalid dateTime value " + StackDump(parseStack)); } } finally { parseStack.Pop(); } if (ValueType == typeof(XmlRpcDateTime)) return new XmlRpcDateTime(retVal); else return retVal; } Object ParseBase64( XmlNode node, Type ValueType, ParseStack parseStack, MappingAction mappingAction) { if (ValueType != null && ValueType != typeof(byte[]) && ValueType != typeof(Object)) { throw new XmlRpcTypeMismatchException(parseStack.ParseType + " contains base64 value where " + XmlRpcServiceInfo.GetXmlRpcTypeString(ValueType) + " expected " + StackDump(parseStack)); } byte[] ret; parseStack.Push("base64"); try { if (node.FirstChild == null) ret = new byte[0]; else { string s = node.FirstChild.Value; try { ret = Convert.FromBase64String(s); } catch (Exception) { throw new XmlRpcInvalidXmlRpcException(parseStack.ParseType + " contains invalid base64 value " + StackDump(parseStack)); } } } finally { parseStack.Pop(); } return ret; } XmlRpcFaultException ParseFault( XmlNode faultNode, ParseStack parseStack, MappingAction mappingAction) { XmlNode valueNode = SelectSingleNode(faultNode, "value"); XmlNode structNode = SelectSingleNode(valueNode, "struct"); if (structNode == null) { throw new XmlRpcInvalidXmlRpcException( "struct element missing from fault response."); } Fault fault; try { fault = (Fault)ParseValue(structNode, typeof(Fault), parseStack, mappingAction); } catch(Exception ex) { // some servers incorrectly return fault code in a string if (AllowStringFaultCode) throw; else { FaultStructStringCode faultStrCode; try { faultStrCode = (FaultStructStringCode)ParseValue(structNode, typeof(FaultStructStringCode), parseStack, mappingAction); fault.faultCode = Convert.ToInt32(faultStrCode.faultCode); fault.faultString = faultStrCode.faultString; } catch (Exception) { // use exception from when attempting to parse code as integer throw ex; } } } return new XmlRpcFaultException(fault.faultCode, fault.faultString); } struct FaultStruct { public int faultCode; public string faultString; } struct FaultStructStringCode { public string faultCode; public string faultString; } public void SerializeFaultResponse( Stream stm, XmlRpcFaultException faultEx) { FaultStruct fs; fs.faultCode = faultEx.FaultCode; fs.faultString = faultEx.FaultString; XmlTextWriter xtw = new XmlTextWriter(stm, m_encoding); ConfigureXmlFormat(xtw); xtw.WriteStartDocument(); xtw.WriteStartElement("", "methodResponse", ""); xtw.WriteStartElement("", "fault", ""); Serialize(xtw, fs, MappingAction.Error); xtw.WriteEndElement(); xtw.WriteEndElement(); xtw.Flush(); } void ConfigureXmlFormat( XmlTextWriter xtw) { if (m_bUseIndentation) { xtw.Formatting = Formatting.Indented; xtw.Indentation = m_indentation; } else { xtw.Formatting = Formatting.None; } } string StackDump(ParseStack parseStack) { StringBuilder sb = new StringBuilder(); foreach (string elem in parseStack) { sb.Insert(0, elem); sb.Insert(0, " : "); } sb.Insert(0, parseStack.ParseType); sb.Insert(0, "["); sb.Append("]"); return sb.ToString(); } XmlNode SelectSingleNode(XmlNode node, string name) { XmlNodeList nodes = node.ChildNodes; for (int i = 0; i < nodes.Count; i++) { if (nodes[i].Name == name) return nodes[i]; } return null; } void SelectTwoNodes(XmlNode node, string name1, out XmlNode node1, out bool dup1, string name2, out XmlNode node2, out bool dup2) { node1 = node2 = null; dup1 = dup2 = false; XmlNodeList nodes = node.ChildNodes; for (int i = 0; i < nodes.Count; i++) { if (nodes[i].Name == name1) { if (node1 == null) node1 = nodes[i]; else dup1 = true; } else if (nodes[i].Name == name2) { if (node2 == null) node2 = nodes[i]; else dup2 = true; } } } // TODO: following to return Array? object CreateArrayInstance(Type type, object[] args) { #if (!COMPACT_FRAMEWORK) return Activator.CreateInstance(type, args); #else Object Arr = Array.CreateInstance(type.GetElementType(), (int)args[0]); return Arr; #endif } //#if (DEBUG) public //#endif class ParseStack : Stack { public ParseStack(string parseType) { m_parseType = parseType; } void Push(string str) { base.Push(str); } public string ParseType { get { return m_parseType; } } public string m_parseType = ""; } } }