// 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.IO; using System.Net; using System.Web; using System.Data; // Post unicode to text file ?? namespace Spludlow.Net { /// /// HTTP file transfer methods used by the framework /// /// These methods are optimised for massive file trasfer so web server caching is disabled to avoid memory hogging by IIS (getting this to work was not easy) /// /// Have tested with GET far exeding server memory, including through middle-man (Spludlow Router) from inside remote network. /// /// NOTE: POST has a limit (in IIS) of 2GB so don't POST anything over 1.9GB otherwise IIS will just fail siglently, just do a GET from the other end /// /// NOTE: Some web servers don't like caching being disabled. EWS for example does not play with these methods /// public class Http { public const int PostTimeout = 1 * 60 * 60 * 1000; // Large POST 2GB Max (2GB @ 5Mbits/s = 1h) public class WebResponseInfo { public long ContentLength; public string ContentType; public byte[] Data; public string Text; public string[][] Headers; } public class WebRequestInfo { public string Address; public string Method; public Stream GetStream; public Stream PostStream; public string ContentType; public string[][] Headers; public NetworkCredential Credential; public int Timeout; public string Referer; public string UserAgent; public Encoding ResponseTextEncoding = Encoding.Default; public bool DontRetry = false; } static Http() { } public static string WebDestination(string url) { return WebDestination(url, null); } public static string WebDestination(string url, string userAgent) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.UserAgent = userAgent; using (WebResponse response = request.GetResponse()) return response.ResponseUri.ToString(); } public static WebResponseInfo PostText(string address, string text) { return PostText(address, text, Encoding.Default); } public static WebResponseInfo PostText(string address, string text, Encoding encoding) { // encoding same both ways ???? string contentType = null; using (MemoryStream stream = new MemoryStream(encoding.GetBytes(text))) return PostStream(address, stream, contentType, null, null, null, null, encoding, false); } public static WebResponseInfo PostText(string address, string text, string contentType, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding encoding) { // encoding same both ways ???? using (MemoryStream stream = new MemoryStream(encoding.GetBytes(text))) return PostStream(address, stream, contentType, headers, credential, referer, userAgent, encoding, false); } public static WebResponseInfo PostFile(string address, string filename) { return PostFile(address, filename, "application/octet-stream", null, null, null, null, Encoding.Default); } public static WebResponseInfo PostFile(string address, string filename, string contentType, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding) { using (FileStream stream = new FileStream(filename, FileMode.Open)) return PostStream(address, stream, contentType, headers, credential, referer, userAgent, responseTextEncoding, false); } public static WebResponseInfo PostStream(string address, Stream stream, string contentType, string[][] headers) { return PostStream(address, stream, contentType, headers, null, null, null, Encoding.Default, false); } public static WebResponseInfo PostStream(string address, Stream stream, string contentType, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) { WebRequestInfo requestInfo = new WebRequestInfo(); requestInfo.Address = address; requestInfo.Method = WebRequestMethods.Http.Post; //requestInfo.GetStream = stream; requestInfo.PostStream = stream; requestInfo.ContentType = contentType; requestInfo.Headers = headers; requestInfo.Credential = credential; requestInfo.Timeout = PostTimeout; requestInfo.Referer = referer; requestInfo.UserAgent = userAgent; requestInfo.ResponseTextEncoding = responseTextEncoding; requestInfo.DontRetry = dontRetry; return WebOperation(requestInfo); } public static WebResponseInfo GetDataFile(string address, string filename) { return GetDataFile(address, filename, null, null, null, null, false); } public static WebResponseInfo GetDataFile(string address, string filename, string[][] headers, NetworkCredential credential, string referer, string userAgent) { return GetDataFile(address, filename, headers, credential, referer, userAgent, false); } public static WebResponseInfo GetDataFile(string address, string filename, string[][] headers, NetworkCredential credential, string referer, string userAgent, bool dontRetry) { try { using (FileStream stream = new FileStream(filename, FileMode.Create)) return GetStream(address, stream, headers, credential, referer, userAgent, dontRetry); } catch { File.Delete(filename); throw; } } public static string GetText(string address) { return GetText(address, null, null, null, null, Encoding.Default, false); } public static string GetText(string address, Encoding responseTextEncoding) { return GetText(address, null, null, null, null, responseTextEncoding, false); } public static string GetText(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent) { return GetText(address, headers, credential, referer, userAgent, Encoding.Default, false); } public static string GetText(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) { WebResponseInfo info = Get(address, headers, credential, referer, userAgent, responseTextEncoding, dontRetry); if (info.Text != null) return info.Text; if (info.Data != null) return Spludlow.Text.GetString(info.Data, responseTextEncoding); return null; } public static WebResponseInfo Get(string address) { return Get(address, null, null, null, null, Encoding.Default, false); } //public static WebResponseInfo Get(string address, Encoding responseTextEncoding) //{ //} //public static WebResponseInfo Get(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) //{ //} //public static WebResponseInfo Get(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) //{ //} //public static WebResponseInfo Get(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) //{ //} //public static WebResponseInfo Get(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) //{ //} public static WebResponseInfo Get(string address, string[][] headers, NetworkCredential credential, string referer, string userAgent, Encoding responseTextEncoding, bool dontRetry) { WebRequestInfo requestInfo = new WebRequestInfo(); requestInfo.Address = address; requestInfo.Method = WebRequestMethods.Http.Get; //requestInfo.GetStream = stream; //requestInfo.PostStream; //requestInfo.ContentType; requestInfo.Headers = headers; requestInfo.Credential = credential; //requestInfo.Timeout = 0; requestInfo.Referer = referer; requestInfo.UserAgent = userAgent; requestInfo.ResponseTextEncoding = responseTextEncoding; requestInfo.DontRetry = dontRetry; return WebOperation(requestInfo); } public static WebResponseInfo GetStream(string address, Stream stream) { return GetStream(address, stream, null, null, null, null, false); } public static WebResponseInfo GetStream(string address, Stream stream, bool dontRetry) { return GetStream(address, stream, null, null, null, null, dontRetry); } public static WebResponseInfo GetStream(string address, Stream stream, string[][] headers) { return GetStream(address, stream, headers, null, null, null, false); } public static WebResponseInfo GetStream(string address, Stream stream, string[][] headers, bool dontRetry) { return GetStream(address, stream, headers, null, null, null, dontRetry); } public static WebResponseInfo GetStream(string address, Stream stream, string[][] headers, NetworkCredential credential) { return GetStream(address, stream, headers, credential, null, null, false); } public static WebResponseInfo GetStream(string address, Stream stream, string[][] headers, NetworkCredential credential, bool dontRetry) { return GetStream(address, stream, headers, credential, null, null, dontRetry); } public static WebResponseInfo GetStream(string address, Stream stream, string[][] headers, NetworkCredential credential, string referer, string userAgent, bool dontRetry) { WebRequestInfo requestInfo = new WebRequestInfo(); requestInfo.Address = address; requestInfo.Method = WebRequestMethods.Http.Get; requestInfo.GetStream = stream; //requestInfo.PostStream; //requestInfo.ContentType; requestInfo.Headers = headers; requestInfo.Credential = credential; //requestInfo.Timeout = 0; requestInfo.Referer = referer; requestInfo.UserAgent = userAgent; //requestInfo.ResponseTextEncoding = Encoding.Default; requestInfo.DontRetry = dontRetry; return WebOperation(requestInfo); } public static WebResponseInfo WebOperation(WebRequestInfo requestInfo) { if (requestInfo.DontRetry == true) return WebOperationRaw(requestInfo); int attemptsToTry = 4; int waitAttemptMilliSeconds = 8 * 1000; Exception error = null; string info = requestInfo.Method + ":" + requestInfo.Address; for (int attempt = 0; attempt < attemptsToTry; ++attempt) { if (attempt != 0) System.Threading.Thread.Sleep(waitAttemptMilliSeconds); if (requestInfo.GetStream != null && attempt > 0) requestInfo.GetStream.Position = 0; if (requestInfo.PostStream != null && attempt > 0) requestInfo.PostStream.Position = 0; try { return WebOperationRaw(requestInfo); } catch (Exception ee) { error = ee; if (ee is WebException) { WebException webException = (WebException)ee; HttpStatusCode statusCode = HttpStatusCode.OK; if (webException.Response != null) statusCode = ((HttpWebResponse)webException.Response).StatusCode; if (statusCode == HttpStatusCode.ServiceUnavailable || (webException.InnerException != null && (webException.InnerException is System.Net.Sockets.SocketException || webException.InnerException is IOException))) { Spludlow.Log.Warning("HTTP, Attempt Failed WebException (" + attempt + "): " + info, ee); continue; } } if (ee is IOException && ee.InnerException != null && ee.InnerException is System.Net.Sockets.SocketException) { Spludlow.Log.Warning("HTTP, Attempt Failed IOException (" + attempt + "): " + info, ee); continue; } Spludlow.Log.Error("HTTP Error: " + info, ee); throw ee; } } if (error == null) error = new ApplicationException("Unknown Error"); throw new ApplicationException("HTTP, Failed all attempts: " + info + ", " + error.Message, error); } public static WebResponseInfo WebOperationRaw(WebRequestInfo requestInfo) { Spludlow.WebServices.SetServerCertificateValidation(); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestInfo.Address); request.Method = requestInfo.Method; request.ContentType = requestInfo.ContentType; AddHeaders(request, requestInfo.Headers); request.Credentials = requestInfo.Credential; if (requestInfo.Timeout > 0) request.Timeout = requestInfo.Timeout; request.UserAgent = requestInfo.UserAgent; request.Referer = requestInfo.Referer; if (requestInfo.PostStream != null) { request.AllowWriteStreamBuffering = false; // Otherwise will hog memory in client EWS does not like !!!!!!!!!!!!! request.SendChunked = true; using (Stream requestStream = request.GetRequestStream()) { requestInfo.PostStream.CopyTo(requestStream); } } using (WebResponse response = request.GetResponse()) { WebResponseInfo info = new WebResponseInfo(); info.ContentType = response.ContentType; List responseHeaders = new List(); foreach (string headerKey in response.Headers) responseHeaders.Add(new string[] { headerKey, response.Headers[headerKey] }); info.Headers = responseHeaders.ToArray(); using (Stream responseStream = response.GetResponseStream()) { if (requestInfo.GetStream == null) { if (response.ContentType.Contains("xml") || response.ContentType.StartsWith("text")) { info.Text = Spludlow.Io.Files.ReadAllText(responseStream, requestInfo.ResponseTextEncoding); info.ContentLength = info.Text.Length; } else { info.Data = Spludlow.Io.Files.ReadAllBytes(responseStream); info.ContentLength = info.Data.LongLength; } } else { responseStream.CopyTo(requestInfo.GetStream); } return info; } } } private static void AddHeaders(HttpWebRequest request, string[][] headers) { if (headers != null) { foreach (string[] headerPair in headers) { if (headerPair == null || headerPair.Length == 0) continue; string value = ""; if (headerPair.Length > 1) value = headerPair[1]; request.Headers.Add(headerPair[0], value); } } } public static string PostedText(HttpRequest request) { return PostedText(request, Encoding.Default); } public static string PostedText(HttpRequest request, Encoding encoding) { using (StreamReader reader = new StreamReader(request.InputStream, encoding)) return reader.ReadToEnd(); } public static byte[] PostedData(HttpRequest request) { using (MemoryStream memoryStream = new MemoryStream()) { request.InputStream.CopyTo(memoryStream); return memoryStream.ToArray(); } } public static void PostedData(HttpRequest request, string filename) { using (FileStream fileStream = new FileStream(filename, FileMode.Create)) request.InputStream.CopyTo(fileStream); } public static string StatusCode(int statusCode) { string filename = Spludlow.Config.ProgramData + @"\Data\HttpStatusCodes.txt"; string cacheKey = "Spludlow.Http.StatusCode"; DataTable table = (DataTable)Spludlow.Caching.Get(cacheKey); if (table == null) { table = Spludlow.Data.TextTable.ReadFile(filename); Spludlow.Caching.Set(cacheKey, table); } DataRow row = table.Rows.Find(statusCode); if (row == null) return ""; return (string)row["Description"]; } public static DataTable StatusCodes() { string url = "http://www.iana.org/assignments/http-status-codes/http-status-codes.txt"; string filename = Spludlow.Config.ProgramData + @"\Data\HttpStatusCodes.txt"; if (File.Exists(filename) == true) return Spludlow.Data.TextTable.ReadFile(filename); string text = Spludlow.Net.Http.GetText(url, Encoding.UTF8); DataTable table = Spludlow.Data.TextTable.ReadText("HttpStatusCodes", new string[] { "Code Description RFC", "Int32* String String", }); Dictionary topStatuses = new Dictionary(); using (StringReader reader = new StringReader(text)) { string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (line.Length < 7) continue; int index; if (Char.IsDigit(line[0]) == true && line.Substring(1, 3) == "xx:") { int topStatusCode = Int32.Parse(line.Substring(0, 1)); index = line.IndexOf("-"); line = line.Substring(0, index); string topName = line.Substring(5).Trim(); topStatuses.Add(topStatusCode, topName); continue; } if (Char.IsDigit(line[0]) == false || Char.IsDigit(line[1]) == false || Char.IsDigit(line[2]) == false) continue; if (line[3] != ' ' || line[line.Length - 1] != ']') continue; int topCode = Int32.Parse(line.Substring(0, 1)); int code = Int32.Parse(line.Substring(0, 3)); line = line.Substring(6); index = line.IndexOf("["); string name = line.Substring(0, index).Trim(); string rfc = line.Substring(index).Trim(); string topStatus = topStatuses[topCode]; table.Rows.Add(code, topStatus + ":" + name, rfc); } } Spludlow.Data.TextTable.Write(filename, table, Encoding.UTF8); return table; } public static bool IsUserAgentReal(string userAgent) { userAgent = userAgent.Trim(); // Seen 47 - 198 in real life if (userAgent.Length < 48 ||userAgent.Length > 256) return false; if (userAgent.StartsWith("Mozilla/") == false) return false; int index = userAgent.IndexOf(" "); if (index == -1) return false; string start = userAgent.Substring(0, index).Trim(); // "Mozilla/5.0" = 11 if (start.Length < 9 || start.Length > 12) return false; float version = 0; if (Single.TryParse(start.Substring(8), out version) == false) return false; string details = userAgent.Substring(index + 1).Trim(); if (details.Contains("http://") == true || details.Contains("https://") == true || details.Contains("@") == true) return false; return true; } } }