// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceModel; using System.ServiceModel.Channels; using System.Data; using System.Web; using System.Web.Caching; using System.Net; using System.IO; namespace Spludlow { public class WebServices { public static Spludlow.Variations MimeVariations; private static Random Random = new Random(); static WebServices() { MimeVariations = new Variations(new string[] { "application/octet-stream byte bytes data", "text/plain text txt", "application/xml xml", "image/png png", "image/jpeg jpg jpeg", }); Spludlow.WebServices.SetServerCertificateValidation(); } public static void SetServerCertificateValidation() { if (System.Net.ServicePointManager.ServerCertificateValidationCallback != null) return; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate); } public static bool ValidateServerCertificate( object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors) { return true; } private static SpludlowWebServiceWCF.WCFClient CreateWCFClient(string address, bool shortTimout) { Binding binding; if (address.StartsWith("http://") == true) binding = new BasicHttpBinding(); else binding = new BasicHttpsBinding(); TimeSpan timeout = TimeSpan.FromMinutes(15); if (shortTimout == true) timeout = TimeSpan.FromSeconds(15); binding.ReceiveTimeout = timeout; binding.SendTimeout = timeout; EndpointAddress endPoint = new EndpointAddress(address); return new SpludlowWebServiceWCF.WCFClient(binding, endPoint); } public static CallSet PostWCF(string address, CallSet callSet) { if (callSet.CurrentMethod.Flags.HasFlag(CallFlags.NonPersistent) == true) return PostWCFRaw(address, callSet); int attemptsToTry = 4; int waitAttemptMilliSeconds = 8 * 1000; for (int attempt = 0; attempt < attemptsToTry; ++attempt) { if (attempt != 0) System.Threading.Thread.Sleep(waitAttemptMilliSeconds); try { return PostWCFRaw(address, callSet); } catch (FaultException ee) { Spludlow.Log.Error("WebServices, PostWCF; FaultException (Server):" + address, ee); throw ee; } catch (Exception ee) { if (ee is ServerTooBusyException || ee is EndpointNotFoundException || (ee is CommunicationException && ee.InnerException != null && !(ee.InnerException is QuotaExceededException))) { Spludlow.Log.Warning("WebServices, PostWCF; Attempt Failed (" + attempt + ")", ee); } else { Spludlow.Log.Error("WebServices, PostWCF; " + address, ee); throw ee; } } } throw new ApplicationException("WebServices, Post WCF; Failed all attempts"); } public static CallSet PostWCFRaw(string address, CallSet callSet) { address += "WCF.svc"; string callSetEncoded = null; try { callSetEncoded = Spludlow.Serialization.Write(callSet); } catch (Exception ee) { throw new ApplicationException("WebServices, PostWCFRaw; CallSet to pass Serialization Failed; " + ee.Message, ee); } SpludlowWebServiceWCF.WCFClient client = CreateWCFClient(address, callSet.CurrentMethod.Flags.HasFlag(CallFlags.NonPersistent)); try { callSetEncoded = client.Call(Spludlow.Security.SecurityKey, callSetEncoded); } finally { client.Close(); } try { callSet = (CallSet)Spludlow.Serialization.Read(callSetEncoded, typeof(CallSet).AssemblyQualifiedName); } catch (Exception ee) { throw new ApplicationException("WebServices, PostWCFRaw; CallSet result De-Serialization Failed; " + ee.Message, ee); } return callSet; } public static string ReceiveWCF(string securityKey, string infoXml) { CallSet callSet = null; try { if (Spludlow.Security.IsValidSecurityKey(securityKey) == false) throw new ApplicationException("WebServices, ReceiveWCF; InValid SecurityKey"); try { callSet = (CallSet)Spludlow.Serialization.Read(infoXml, typeof(CallSet)); } catch (Exception ee) { throw new ApplicationException("WebServices, ReceiveWCF; Serialization.Read " + ee.Message, ee); } callSet.CurrentMethod.PickUpQueue = null; callSet = Spludlow.Call.Route(callSet); string callSetEncoded; try { callSetEncoded = Spludlow.Serialization.Write(callSet); } catch (Exception ee) { throw new ApplicationException("WebServices, ReceiveWCF; Serialization.Write " + ee.Message, ee); } return callSetEncoded; } catch (Exception ee) { Spludlow.Log.Error("WebServices, ReceiveWCF; Error ", ee); Spludlow.Log.Error("WebServices, ReceiveWCF; CallSet " + ee.Message, callSet); throw ee; } } public static void ReceiveHTTP(HttpRequest request, HttpResponse response) { Dictionary> arguments = Arguments.Decode(request.ServerVariables["QUERY_STRING"]); string securityKey = request.Headers[Spludlow.Security.HttpHeaderNameSecurityKey]; if (securityKey == null && arguments.ContainsKey("key") == true) { securityKey = Arguments.LastValue(arguments, "key"); arguments.Remove("key"); } if (Spludlow.Security.IsValidSecurityKey(securityKey) == false) throw new ApplicationException("WebServices, ReceiveHTTP; InValid SecurityKey"); RunReceiveHTTP(request, response, arguments); } public static void RunReceiveHTTP(HttpRequest request, HttpResponse response) { Dictionary> arguments = Arguments.Decode(request.ServerVariables["QUERY_STRING"]); RunReceiveHTTP(request, response, arguments); } public static void RunReceiveHTTP(HttpRequest request, HttpResponse response, Dictionary> arguments) { response.Clear(); try { if (arguments.Count == 0) // Ping test { response.ContentType = "text/plain"; response.Write("Spludlow" + Environment.NewLine); response.Write(DateTime.Now + Environment.NewLine); } else { switch (request.HttpMethod) { case "GET": ReceiveHTTPGet(request, response, arguments); break; case "POST": ReceiveHTTPPost(request, response, arguments); break; default: throw new ApplicationException("WebServices, ReceiveHTTP; Unsupported HTTP Method: " + request.HttpMethod); } } } catch (Exception ee) { Spludlow.Log.Error("WebServices, ReceiveHTTP", ee); throw ee; // 500 on client } response.End(); } private static void ReceiveHTTPGet(HttpRequest request, HttpResponse response, Dictionary> arguments) { string host = Arguments.LastValue(arguments, "host"); if (host == null) host = Environment.MachineName; HttpResponseHeaders(response, arguments); // Route if (host != Environment.MachineName) { string address = HttpRouteAddress(host, request); response.BufferOutput = false; Spludlow.Net.Http.WebResponseInfo nextGetInfo = Spludlow.Net.Http.GetStream(address, response.OutputStream, Spludlow.Security.HttpHeader); return; } // Download string file = Arguments.LastValue(arguments, "file"); if (file != null) { file = GetProgramDataTempFile(file); if (response.ContentType == null) response.ContentType = "application/octet-stream"; response.TransmitFile(file); return; } string web = Arguments.LastValue(arguments, "web"); // Wont work with content type !!! if (web != null) { Spludlow.Net.Http.WebResponseInfo targetGetInfo = Spludlow.Net.Http.GetStream(web, response.OutputStream); if (response.ContentType == null) response.ContentType = targetGetInfo.ContentType; // complete ??? return; } object result; // DATA implemt CON= &TABLE string data = Arguments.LastValue(arguments, "data"); if (data != null) { Spludlow.Data.IDAL database = Spludlow.Data.DAL.Create("@Spludlow"); result = database.ExecuteScalar(data); if (result == null || result is DBNull) return; if (response.ContentType == null) response.ContentType = "text/plain"; Type type = result.GetType(); if (Spludlow.SimpleEncoding.IsSimple(type) == true) response.Write(Spludlow.SimpleEncoding.Encode(result, type.Name)); // standard log encoding to impliment else response.Write(type.Name); return; } // Spit back text from query string string text = Arguments.LastValue(arguments, "text"); if (text != null) { response.Write(text); return; } // ATM Spludlow.CallMethod method = new CallMethod(arguments); if (method.Assembly == null) throw new ApplicationException("Receive HTTP Get, ATM without Assembly"); HttpRun(new CallSet(method), request, response); } public static string LastArgument(Dictionary> arguments, string key, string defaultValue) { if (arguments.ContainsKey(key) == false || arguments[key].Count == 0) return defaultValue; return Arguments.LastValue(arguments, key); } private static void ReceiveHTTPPost(HttpRequest request, HttpResponse response, Dictionary> arguments) { string host = Arguments.LastValue(arguments, "host"); if (host == null) host = Environment.MachineName; HttpResponseHeaders(response, arguments); if (host != Environment.MachineName) { string address = HttpRouteAddress(host, request); response.BufferOutput = false; Spludlow.Net.Http.WebRequestInfo requestInfo = new Net.Http.WebRequestInfo(); requestInfo.Address = address.ToString(); requestInfo.Method = WebRequestMethods.Http.Post; requestInfo.GetStream = response.OutputStream; requestInfo.PostStream = request.GetBufferlessInputStream(); requestInfo.ContentType = null; requestInfo.Timeout = Spludlow.Net.Http.PostTimeout; requestInfo.Headers = Spludlow.Security.HttpHeader; Spludlow.Net.Http.WebResponseInfo responseInfo = Spludlow.Net.Http.WebOperation(requestInfo); return; } if (arguments.ContainsKey("updown") == true) // Used for testing. uploads and downloads in one HTTP POST { string serverFilename = Arguments.LastValue(arguments, "updown"); string serverTempFilename = serverFilename + ".tmp"; if (File.Exists(serverTempFilename) == true) { Spludlow.Log.Warning("POST Up-Down; Temp file already there, deleting: " + serverTempFilename); File.Delete(serverTempFilename); } Spludlow.Log.Info("POST Up-Down; Starting - Recieve POST to file"); using (FileStream stream = new FileStream(serverTempFilename, FileMode.Create)) { request.GetBufferlessInputStream().CopyTo(stream); } Spludlow.Log.Info("POST Up-Down; Finished - Recieve POST to file"); if (File.Exists(serverFilename) == true) { Spludlow.Log.Warning("POST Up-Down; Server file already there, deleting: " + serverFilename); File.Delete(serverFilename); } File.Move(serverTempFilename, serverFilename); Spludlow.Log.Info("POST Up-Down; Done POST Server file : " + serverFilename); response.ContentType = "application/octet-stream"; response.BufferOutput = false; Spludlow.Log.Info("POST Up-Down; Starting - Respose file"); using (FileStream stream = new FileStream(serverFilename, FileMode.Open)) { stream.CopyTo(response.OutputStream); } Spludlow.Log.Info("POST Up-Down; Finished - Respose file"); return; } if (arguments.ContainsKey("file") == true) // download to temp file and replace at last moment !!!!!!!!!!!!!!!!!!!! { string file = ""; if (arguments["file"].Count > 0) file = Arguments.LastValue(arguments, "file"); bool directFile = true; if (file.Length == 0 || file.Contains(@"\") == false) { if (file.Length == 0) file = null; file = CreateProgramDataTempFilename(Path.GetExtension(file)); // Maybe no extention if upload without filename !!!!!!!!!!!!!!! directFile = false; } // request.ContentType; check for compressed string targetFilename = null; if (directFile == true && File.Exists(file) == true) { targetFilename = file; file = file + ".tmp"; if (File.Exists(file) == true) { File.Delete(file); Spludlow.Log.Warning("ReceiveHTTPPost FILE; Delete existing temp file:\t" + targetFilename); } } using (FileStream stream = new FileStream(file, FileMode.Create)) { using (Stream inputStream = request.GetBufferlessInputStream()) { inputStream.CopyTo(stream); //Spludlow.WebServices.CopyToSlow(inputStream, stream); } } if (targetFilename != null) { File.Delete(targetFilename); Spludlow.Log.Warning("ReceiveHTTPPost FILE; Delete existing file:\t" + targetFilename); File.Move(file, targetFilename); file = targetFilename; } response.ContentType = "text/plain"; response.Write(file); return; } // post text back as file ??????????????????? // post binary as file ???? not implimented if (arguments.ContainsKey("text") == true) { string text = Spludlow.Net.Http.PostedText(request); response.Write(text); return; } // Print from body if (arguments.ContainsKey("print") == true) { string printType = LastArgument(arguments, "print", "void"); string printerInfo = LastArgument(arguments, "printer", null); // TODO // description printjob description // history app key for using history // ?? specify previouslyu uploaded file ??? belonf in GET if (request.ContentType == "text/plain") { string printData = Spludlow.Net.Http.PostedText(request); Spludlow.Printing.Printer.Print(printData, printType, printerInfo); // Put on queye and pdf beolw ???????????????? address???? return; } if (request.ContentType == "application/pdf") // shoudl implemet byte[] input in ghost script !!!!! { using (Spludlow.TempDirectory tempDir = new TempDirectory()) { string tempFilename = tempDir + @"\HttpPrint-" + Spludlow.Text.TimeStamp() + ".pdf"; Spludlow.Net.Http.PostedData(request, tempFilename); Spludlow.Printing.GhostScript.PrintPdf(tempFilename, "A4", false, printType, printerInfo); // Page size ?????????????????????????pdf need to specify PAGE SIZE and ornitaion and pages !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } return; } throw new ApplicationException("WebServices HTTP POST; ContentType not supported:\t" + request.ContentType); } // ATM CallSet callSet = null; Spludlow.CallMethod method = new CallMethod(arguments); if (method.Assembly == null) { if (request.ContentType == "text/plain") // call text format callSet = new CallSet(CallText.Read(Spludlow.Net.Http.PostedText(request))); if (request.ContentType == "application/xml") // Serialized CallSet callSet = (CallSet)Spludlow.Serialization.Read(Spludlow.Net.Http.PostedText(request), typeof(CallSet).AssemblyQualifiedName); if (callSet == null) throw new ApplicationException("POST request.ContentType not supported. Only pain text call format or an XML CallSet:\t" + request.ContentType); } else { callSet = new CallSet(method); // Allow body containing one paramter !!!!!!!!!!!! } HttpRun(callSet, request, response); } private static string HttpRouteAddress(string host, HttpRequest request) { string address = Spludlow.Config.HostAddress(host); if (address.StartsWith("@") == true) address = address.Substring(1); address += "HTTP.aspx"; if (request.Url.OriginalString.Contains("?")) address += "?" + Spludlow.Text.ChopAfter(request.Url.OriginalString, "?"); return address; } private static void HttpResponseHeaders(HttpResponse response, Dictionary> arguments) { response.ContentType = null; string mime = Arguments.LastValue(arguments, "mime"); if (mime != null) { mime = MimeVariations.Match(mime); response.ContentType = mime; } string dispositionType = "attachment"; string inline = Arguments.LastValue(arguments, "inline"); if (inline != null) dispositionType = "inline"; string filename = Arguments.LastValue(arguments, "filename"); if (filename != null) response.AddHeader("Content-Disposition", dispositionType + "; filename=\"" + filename + "\""); } private static void HttpRun(CallSet callSet, HttpRequest request, HttpResponse response) { object result = Spludlow.Call.Run(callSet); // ?? add to hop before CALL.Run run in this process if (result == null || response.ContentType == null) return; Type resultType = result.GetType(); // other types where text goes back ? if (response.ContentType == "text/plain" || response.ContentType == "application/bat") // same encoding as log display (datasets as text tables) called displayData { if (resultType.Name == "String") { response.Write((string)result); return; } if (Spludlow.SimpleEncoding.IsSimple(resultType.Name) == true) response.Write(Spludlow.SimpleEncoding.Encode(result, resultType.Name)); else Spludlow.Log.Error("ATM result text not SimpleEncoding"); // more detail return; } if (response.ContentType == "application/xml") // standard xml data set { try { Spludlow.Serialization.Write(result, response.OutputStream); } catch (Exception ee) { Spludlow.Log.Error("ATM result error XML Serialization", ee); } return; } // application/x-7z-compressed application/zip if (resultType != typeof(byte[])) { Spludlow.Log.Error("ATM result not byte[]"); // more detail return; } //response.BufferOutput = false; using (MemoryStream stream = new MemoryStream((byte[])result)) stream.CopyTo(response.OutputStream); // seen error with large results (dartsboard at 600 dpi) never get error on server ?? } public static string CreateProgramDataTempFilename(string extention) // Could still get created between calls here but random number should help { string tempFilename = null; do { if (tempFilename != null) Spludlow.Log.Warning("CreateProgramDataTempFilename; Name colision: " + tempFilename); int random; lock (Random) random = Random.Next(0x7FFFFFFF); StringBuilder text = new StringBuilder(); text.Append(Spludlow.Config.ProgramData); text.Append(@"\Temp\"); text.Append(Environment.MachineName); text.Append("@"); text.Append(Spludlow.Text.TimeStamp()); text.Append("_"); text.Append(random.ToString("X08")); text.Append(extention); tempFilename = text.ToString(); } while (File.Exists(tempFilename) == true); return tempFilename; } public static string GetProgramDataTempFile(string filename) { if (filename.StartsWith("@") == false) { if (filename.Contains(@"\") == false) throw new ApplicationException("GetProgramDataTempFile; Not staring with @ sign and not a full path: " + filename); if (File.Exists(filename) == false) throw new ApplicationException("GetProgramDataTempFile; Full path not staring with @ sign and file does not exist: " + filename); return filename; } filename = filename.Substring(1); StringBuilder text = new StringBuilder(); text.Append(Spludlow.Config.ProgramData); text.Append(@"\Temp\"); text.Append(filename); string localFilename = text.ToString(); if (File.Exists(localFilename) == true) return localFilename; int index = filename.IndexOf("@"); if (index == -1) throw new ApplicationException("GetProgramDataTempFile; Not @ sign to delimet host: " + filename); string host = filename.Substring(0, index); Spludlow.Call.Download(host, "@" + filename, localFilename); return localFilename; } public static void CleanProgramDataTempFiles(DateTime beforeDate) { string directory = Spludlow.Config.ProgramData + @"\Temp"; Spludlow.Diagnostics.DeleteFilesWithReport(directory, beforeDate); } public static Cache GetCache() { Cache cache = null; HttpContext context = HttpContext.Current; if (context != null) cache = context.Cache; if (cache == null) cache = HttpRuntime.Cache; return cache; } } }