protected static IList <object> ParseResult(TextReader textReader, CpAction action, bool sourceSupportsUPnP11) { IList <object> outParameterValues = new List <object>(); using (XmlReader reader = XmlReader.Create(textReader, UPnPConfiguration.DEFAULT_XML_READER_SETTINGS)) { reader.MoveToContent(); // Parse SOAP envelope reader.ReadStartElement("Envelope", UPnPConsts.NS_SOAP_ENVELOPE); reader.ReadStartElement("Body", UPnPConsts.NS_SOAP_ENVELOPE); // Reader is positioned at the action element string serviceTypeVersion_URN = reader.NamespaceURI; string type; int version; // Parse service and action if (!ParserHelper.TryParseTypeVersion_URN(serviceTypeVersion_URN, out type, out version)) { throw new ArgumentException("Invalid service type or version"); } string actionName = reader.LocalName; if (!actionName.EndsWith("Response") || actionName.Substring(0, actionName.Length - "Response".Length) != action.Name) { throw new ArgumentException("Invalid action name in result message"); } IEnumerator <CpArgument> formalArgumentEnumer = action.OutArguments.GetEnumerator(); if (!SoapHelper.ReadEmptyStartElement(reader)) { // Parse and check output parameters while (reader.NodeType != XmlNodeType.EndElement) { string argumentName = reader.Name; // Arguments don't have a namespace, so take full name if (!formalArgumentEnumer.MoveNext()) // Too many arguments { throw new ArgumentException("Invalid out argument count"); } if (formalArgumentEnumer.Current.Name != argumentName) { throw new ArgumentException("Invalid argument name"); } object value; if (SoapHelper.ReadNull(reader)) { value = null; } else { formalArgumentEnumer.Current.SoapParseArgument(reader, !sourceSupportsUPnP11, out value); } outParameterValues.Add(value); } } if (formalArgumentEnumer.MoveNext()) // Too few arguments { throw new ArgumentException("Invalid out argument count"); } } return(outParameterValues); }
/// <summary> /// Helper method to be used within the <see cref="SoapDeserializeValue"/> method to check if the serialization is /// a <c>null</c> value. /// The reader will read the end element tag after it encountered a <c>null</c> value. /// </summary> /// <param name="reader">XML reader which is positioned at the starting tag of the XML element enclosing the value.</param> protected bool SoapReadNull(XmlReader reader) { return(SoapHelper.ReadNull(reader)); }
/// <summary> /// Deserializes the contents of the given XML <paramref name="reader"/>'s current element in the formatting rules /// given by this data type. /// </summary> /// <param name="reader">XML reader whose current element's value will be deserialized. /// The reader's position is the start of the parent element, the result should go. After this method returns, the reader /// must have read the end element.</param> /// <returns>Deserialized object of this data type (may be <c>null</c>).</returns> public object SoapDeserializeValue(XmlReader reader) { object result; if (SoapHelper.ReadNull(reader)) { result = null; } else { switch (_upnpTypeName) { case "ui1": case "ui2": case "ui4": case "ui8": case "i1": case "i2": case "i4": case "i8": case "int": case "r4": case "r8": case "number": case "fixed.14.4": case "float": case "char": result = reader.ReadElementContentAs(_dotNetType, null); break; case "string": case "uuid": result = reader.ReadElementContentAsString(); break; case "date": result = System.DateTime.ParseExact(reader.ReadElementContentAsString(), "yyyy-MM-dd", null); break; case "dateTime": result = System.DateTime.ParseExact(reader.ReadElementContentAsString(), "s", null); break; case "dateTime.tz": result = System.DateTime.ParseExact(reader.ReadElementContentAsString(), "u", null).ToLocalTime(); break; case "time": result = System.DateTime.ParseExact(reader.ReadElementContentAsString(), "T", null); break; case "time.tz": result = System.DateTime.ParseExact(reader.ReadElementContentAsString(), "hh:mm:ss", null).ToLocalTime(); break; case "boolean": result = reader.ReadElementContentAs(_dotNetType, null); break; case "bin.base64": result = reader.ReadElementContentAs(_dotNetType, null); break; case "bin.hex": result = EncodingUtils.FromHexString(reader.ReadElementContentAsString()); break; case "uri": result = new Uri(reader.ReadElementContentAsString()); break; default: throw new NotImplementedException(string.Format("UPnP standard data type '{0}' is not implemented", _upnpTypeName)); } // Reader will already have read the end element in the methods ReadElementContentXXX } return(result); }
protected static IList <object> ParseResult(TextReader textReader, CpAction action, bool sourceSupportsUPnP11) { object[] outParameterValues = new object[action.OutArguments.Count]; using (XmlReader reader = XmlReader.Create(textReader, UPnPConfiguration.DEFAULT_XML_READER_SETTINGS)) { reader.MoveToContent(); // Parse SOAP envelope reader.ReadStartElement("Envelope", UPnPConsts.NS_SOAP_ENVELOPE); reader.ReadStartElement("Body", UPnPConsts.NS_SOAP_ENVELOPE); // Reader is positioned at the action element string serviceTypeVersion_URN = reader.NamespaceURI; string type; int version; // Parse service and action if (!ParserHelper.TryParseTypeVersion_URN(serviceTypeVersion_URN, out type, out version)) { throw new ArgumentException("Invalid service type or version"); } string actionName = reader.LocalName; if (!actionName.EndsWith("Response") || actionName.Substring(0, actionName.Length - "Response".Length) != action.Name) { throw new ArgumentException("Invalid action name in result message"); } // UPnP spec says we have to be able to handle return values being out // of order to support UPnP 1.0 devices. See UPnP-arch-DeviceArchitecture-v1.1 // section 2.5.4. We need a dictionary to make this easy. IDictionary <string, int> formalArgIdxDictionary = new Dictionary <string, int>(); for (int i = 0; i < action.OutArguments.Count; i++) { formalArgIdxDictionary.Add(action.OutArguments[i].Name, i); } int outArgCount = 0; if (!SoapHelper.ReadEmptyStartElement(reader)) { // Parse and check output parameters while (reader.NodeType != XmlNodeType.EndElement) { string argumentName = reader.Name; // Arguments don't have a namespace, so take full name int formalArgumentIndex; if (!formalArgIdxDictionary.TryGetValue(argumentName, out formalArgumentIndex)) { throw new ArgumentException("Invalid argument name"); } CpArgument formalArgument = action.OutArguments[formalArgumentIndex]; // Get the argument value and store it in the correct position in the return list. object value = null; if (!SoapHelper.ReadNull(reader)) { formalArgument.SoapParseArgument(reader, !sourceSupportsUPnP11, out value); } outParameterValues[formalArgumentIndex] = value; outArgCount++; // Don't allow duplicates of the same argument. formalArgIdxDictionary.Remove(formalArgument.Name); } } if (outArgCount != action.OutArguments.Count) // Too few arguments { throw new ArgumentException("Invalid out argument count"); } } return(outParameterValues); }
/// <summary> /// Handler method for SOAP control requests. /// </summary> /// <param name="service">The service whose action was called.</param> /// <param name="messageStream">The stream which contains the HTTP message body with the SOAP envelope.</param> /// <param name="streamEncoding">Encoding of the <paramref name="messageStream"/>.</param> /// <param name="subscriberSupportsUPnP11">Should be set if the requester sent a user agent header which denotes a UPnP /// version of 1.1. If set to <c>false</c>, in- and out-parameters with extended data type will be deserialized/serialized /// using the string-equivalent of the values.</param> /// <param name="context">Context object holding data for the current action call.</param> /// <param name="result">SOAP result - may be an action result, a SOAP fault or <c>null</c> if no body should /// be sent in the HTTP response.</param> /// <returns>HTTP status code to be sent. Should be /// <list> /// <item><see cref="HttpStatusCode.OK"/> If the action could be evaluated correctly and produced a SOAP result.</item> /// <item><see cref="HttpStatusCode.InternalServerError"/> If the result is a SOAP fault.</item> /// <item><see cref="HttpStatusCode.BadRequest"/> If the message stream was malformed.</item> /// </list> /// </returns> public static HttpStatusCode HandleRequest(DvService service, Stream messageStream, Encoding streamEncoding, bool subscriberSupportsUPnP11, CallContext context, out string result) { UPnPError res; try { IList <object> inParameterValues = null; // Default to null if there aren't parameters, will be lazily initialized later DvAction action; using (StreamReader streamReader = new StreamReader(messageStream, streamEncoding)) using (XmlReader reader = XmlReader.Create(streamReader, UPnPConfiguration.DEFAULT_XML_READER_SETTINGS)) { reader.MoveToContent(); // Parse SOAP envelope reader.ReadStartElement("Envelope", UPnPConsts.NS_SOAP_ENVELOPE); reader.ReadStartElement("Body", UPnPConsts.NS_SOAP_ENVELOPE); // Reader is positioned at the action element string serviceTypeVersion_URN = reader.NamespaceURI; string type; int version; // Parse service and action if (!ParserHelper.TryParseTypeVersion_URN(serviceTypeVersion_URN, out type, out version)) { throw new MediaPortal.Utilities.Exceptions.InvalidDataException("Unable to parse service type and version URN '{0}'", serviceTypeVersion_URN); } string actionName = reader.LocalName; if (!service.Actions.TryGetValue(actionName, out action)) { result = CreateFaultDocument(401, "Invalid Action"); return(HttpStatusCode.InternalServerError); } IEnumerator <DvArgument> formalArgumentEnumer = action.InArguments.GetEnumerator(); if (!SoapHelper.ReadEmptyStartElement(reader)) // Action name { while (reader.NodeType != XmlNodeType.EndElement) { string argumentName = reader.Name; // Arguments don't have a namespace, so take full name if (!formalArgumentEnumer.MoveNext() || formalArgumentEnumer.Current.Name != argumentName) { // Too many arguments result = CreateFaultDocument(402, "Invalid Args"); return(HttpStatusCode.InternalServerError); } object value; if (SoapHelper.ReadNull(reader)) { value = null; } else { res = formalArgumentEnumer.Current.SoapParseArgument(reader, !subscriberSupportsUPnP11, out value); if (res != null) { result = CreateFaultDocument(res.ErrorCode, res.ErrorDescription); return(HttpStatusCode.InternalServerError); } } if (inParameterValues == null) { inParameterValues = new List <object>(); } inParameterValues.Add(value); } } if (formalArgumentEnumer.MoveNext()) { // Too few arguments result = CreateFaultDocument(402, "Invalid Args"); return(HttpStatusCode.InternalServerError); } } IList <object> outParameterValues; // Invoke action try { res = action.InvokeAction(inParameterValues, out outParameterValues, false, context); // outParameterValues can be null if the action has no output parameters. Setting it to an empty list makes // it easier to check parameter count later. if (outParameterValues == null) { outParameterValues = EMPTY_OBJECT_LIST; } } catch (Exception e) { UPnPConfiguration.LOGGER.Warn("SOAPHandler: Error invoking UPnP action '{0}'", e, action.Name); result = CreateFaultDocument(501, "Action Failed"); return(HttpStatusCode.InternalServerError); } if (res != null) { result = CreateFaultDocument(res.ErrorCode, res.ErrorDescription); return(HttpStatusCode.InternalServerError); } // Check output parameters IList <DvArgument> formalArguments = action.OutArguments; if (outParameterValues.Count != formalArguments.Count) { result = CreateFaultDocument(501, "Action Failed"); return(HttpStatusCode.InternalServerError); } IList <OutParameter> outParams = formalArguments.Select((t, i) => new OutParameter(t, outParameterValues[i])).ToList(); result = CreateResultDocument(action, outParams, !subscriberSupportsUPnP11); return(HttpStatusCode.OK); } catch (Exception e) { string message = "Error handling SOAP request: " + e.Message; UPnPConfiguration.LOGGER.Warn(message); // Don't log the whole exception; it's only a communication error with a client result = CreateFaultDocument(500, message); // Also send message to client return(HttpStatusCode.InternalServerError); } }