// 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;
}
}
}