// // Authors: // Lucas Ontivero lucasontivero@gmail.com // // Copyright (C) 2014 Lucas Ontivero // // 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. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using System.Xml; namespace Open.Nat { internal class SoapClient { private readonly string _serviceType; private readonly Uri _url; public SoapClient(Uri url, string serviceType) { _url = url; _serviceType = serviceType; } public async Task InvokeAsync(string operationName, IDictionary args) { NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "SOAPACTION: **{0}** url:{1}", operationName, _url); byte[] messageBody = BuildMessageBody(operationName, args); HttpWebRequest request = BuildHttpWebRequest(operationName, messageBody); if (messageBody.Length > 0) { using (var stream = await request.GetRequestStreamAsync()) { await stream.WriteAsync(messageBody, 0, messageBody.Length); } } using(var response = await GetWebResponse(request)) { var stream = response.GetResponseStream(); var contentLength = response.ContentLength; var reader = new StreamReader(stream, Encoding.UTF8); var responseBody = contentLength != -1 ? reader.ReadAsMany((int) contentLength) : reader.ReadToEnd(); var responseXml = GetXmlDocument(responseBody); NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Response: \n{0}", responseXml.ToPrintableXml()); response.Close(); return responseXml; } } private static async Task GetWebResponse(WebRequest request) { WebResponse response; try { response = await request.GetResponseAsync(); } catch (WebException ex) { NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "WebException status: {0}", ex.Status); // Even if the request "failed" we need to continue reading the response from the router response = ex.Response as HttpWebResponse; if (response == null) throw; } return response; } private HttpWebRequest BuildHttpWebRequest(string operationName, byte[] messageBody) { var request = WebRequest.CreateHttp(_url); request.KeepAlive = false; request.Method = "POST"; request.ContentType = "text/xml; charset=\"utf-8\""; request.Headers.Add("SOAPACTION", "\"" + _serviceType + "#" + operationName + "\""); request.ContentLength = messageBody.Length; return request; } private byte[] BuildMessageBody(string operationName, IEnumerable> args) { var sb = new StringBuilder(); sb.AppendLine(""); sb.AppendLine(" "); sb.AppendLine(" "); foreach (var a in args) { sb.AppendLine(" <" + a.Key + ">" + Convert.ToString(a.Value, CultureInfo.InvariantCulture) + ""); } sb.AppendLine(" "); sb.AppendLine(" "); sb.Append("\r\n\r\n"); string requestBody = sb.ToString(); NatDiscoverer.TraceSource.TraceEvent(TraceEventType.Verbose, 0, requestBody); byte[] messageBody = Encoding.UTF8.GetBytes(requestBody); return messageBody; } private XmlDocument GetXmlDocument(string response) { XmlNode node; var doc = new XmlDocument(); doc.LoadXml(response); var nsm = new XmlNamespaceManager(doc.NameTable); // Error messages should be found under this namespace nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); // Check to see if we have a fault code message. if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) { int code = Convert.ToInt32(node.GetXmlElementText("errorCode"), CultureInfo.InvariantCulture); string errorMessage = node.GetXmlElementText("errorDescription"); NatDiscoverer.TraceSource.LogWarn("Server failed with error: {0} - {1}", code, errorMessage); throw new MappingException(code, errorMessage); } return doc; } } }