1668 lines
48 KiB
C#
1668 lines
48 KiB
C#
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
|
|
namespace EonaCat.Network
|
|
{
|
|
public static class Ext
|
|
{
|
|
private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
|
|
private const int BUFFER_SIZE = 1024;
|
|
private static readonly byte[] _last = new byte[] { 0x00 };
|
|
private static readonly int _retry = 5;
|
|
public static bool Contains(this string value, params char[] chars)
|
|
{
|
|
return chars == null || chars.Length == 0
|
|
|| value != null && value.Length != 0
|
|
&& value.IndexOfAny(chars) > -1;
|
|
}
|
|
|
|
public static bool Contains(this NameValueCollection collection, string name)
|
|
{
|
|
return collection != null && collection.Count > 0 && collection[name] != null;
|
|
}
|
|
|
|
public static bool Contains(this NameValueCollection collection, string name, string value)
|
|
{
|
|
if (collection == null || collection.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var vals = collection[name];
|
|
if (vals == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var val in vals.Split(','))
|
|
{
|
|
if (val.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
|
|
{
|
|
if (eventHandler != null)
|
|
{
|
|
eventHandler(sender, e);
|
|
}
|
|
}
|
|
|
|
public static void Emit<TEventArgs>(
|
|
this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)
|
|
where TEventArgs : EventArgs
|
|
{
|
|
if (eventHandler != null)
|
|
{
|
|
eventHandler(sender, e);
|
|
}
|
|
}
|
|
|
|
public static CookieCollection GetCookies(this NameValueCollection headers, bool response)
|
|
{
|
|
var name = response ? "Set-Cookie" : "Cookie";
|
|
return headers != null && headers.Contains(name)
|
|
? CookieCollection.Parse(headers[name], response)
|
|
: new CookieCollection();
|
|
}
|
|
|
|
public static string GetDescription(this HttpStatusCode code)
|
|
{
|
|
return ((int)code).GetStatusDescription();
|
|
}
|
|
|
|
public static string GetStatusDescription(this int code)
|
|
{
|
|
switch (code)
|
|
{
|
|
case 100: return "Continue";
|
|
case 101: return "Switching Protocols";
|
|
case 102: return "Processing";
|
|
case 200: return "OK";
|
|
case 201: return "Created";
|
|
case 202: return "Accepted";
|
|
case 203: return "Non-Authoritative Information";
|
|
case 204: return "No Content";
|
|
case 205: return "Reset Content";
|
|
case 206: return "Partial Content";
|
|
case 207: return "Multi-Status";
|
|
case 300: return "Multiple Choices";
|
|
case 301: return "Moved Permanently";
|
|
case 302: return "Found";
|
|
case 303: return "See Other";
|
|
case 304: return "Not Modified";
|
|
case 305: return "Use Proxy";
|
|
case 307: return "Temporary Redirect";
|
|
case 400: return "Bad Request";
|
|
case 401: return "Unauthorized";
|
|
case 402: return "Payment Required";
|
|
case 403: return "Forbidden";
|
|
case 404: return "Not Found";
|
|
case 405: return "Method Not Allowed";
|
|
case 406: return "Not Acceptable";
|
|
case 407: return "Proxy Authentication Required";
|
|
case 408: return "Request Timeout";
|
|
case 409: return "Conflict";
|
|
case 410: return "Gone";
|
|
case 411: return "Length Required";
|
|
case 412: return "Precondition Failed";
|
|
case 413: return "Request Entity Too Large";
|
|
case 414: return "Request-Uri Too Long";
|
|
case 415: return "Unsupported Media Type";
|
|
case 416: return "Requested Range Not Satisfiable";
|
|
case 417: return "Expectation Failed";
|
|
case 422: return "Unprocessable Entity";
|
|
case 423: return "Locked";
|
|
case 424: return "Failed Dependency";
|
|
case 500: return "Internal Server Error";
|
|
case 501: return "Not Implemented";
|
|
case 502: return "Bad Gateway";
|
|
case 503: return "Service Unavailable";
|
|
case 504: return "Gateway Timeout";
|
|
case 505: return "Http Version Not Supported";
|
|
case 507: return "Insufficient Storage";
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
public static bool IsCloseStatusCode(this ushort value)
|
|
{
|
|
return value > 999 && value < 5000;
|
|
}
|
|
|
|
public static bool IsEnclosedIn(this string value, char c)
|
|
{
|
|
return value != null
|
|
&& value.Length > 1
|
|
&& value[0] == c
|
|
&& value[value.Length - 1] == c;
|
|
}
|
|
|
|
public static bool IsHostOrder(this ByteOrder order)
|
|
{
|
|
// true: !(true ^ true) or !(false ^ false)
|
|
// false: !(true ^ false) or !(false ^ true)
|
|
return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
|
|
}
|
|
|
|
/// <summary>
|
|
/// An extension method to determine if an IP address is internal
|
|
/// Class A Private IP Range: 10.0.0.0 ? 10.255.255.255
|
|
/// Class B Private IP Range: 172.16.0.0 ? 172.31.255.255
|
|
/// Class C Private IP Range: 192.168.0.0 ? 192.168.255.25
|
|
/// </summary>
|
|
/// <param name="address">The IP address that will be tested</param>
|
|
/// <returns>Returns true if the IP is internal, false if it is external</returns>
|
|
public static bool IsInternal(this System.Net.IPAddress address)
|
|
{
|
|
byte[] bytes = address.GetAddressBytes();
|
|
switch (bytes[0])
|
|
{
|
|
case 10:
|
|
return true;
|
|
case 172:
|
|
return bytes[1] < 32 && bytes[1] >= 16;
|
|
case 192:
|
|
return bytes[1] == 168;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static bool IsLocal(this System.Net.IPAddress address)
|
|
{
|
|
if (address == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (address.Equals(System.Net.IPAddress.Any))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (address.Equals(System.Net.IPAddress.Loopback))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Socket.OSSupportsIPv6)
|
|
{
|
|
if (address.Equals(System.Net.IPAddress.IPv6Any))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (address.Equals(System.Net.IPAddress.IPv6Loopback))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (address.IsInternal())
|
|
return true;
|
|
|
|
var host = System.Net.Dns.GetHostName();
|
|
var addrs = System.Net.Dns.GetHostAddresses(host);
|
|
foreach (var addr in addrs)
|
|
{
|
|
if (address.Equals(addr))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsNullOrEmpty(this string value)
|
|
{
|
|
return value == null || value.Length == 0;
|
|
}
|
|
|
|
public static bool IsPredefinedScheme(this string value)
|
|
{
|
|
if (value == null || value.Length < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var c = value[0];
|
|
if (c == 'h')
|
|
{
|
|
return value == "http" || value == "https";
|
|
}
|
|
|
|
if (c == 'w')
|
|
{
|
|
return value == "ws" || value == "wss";
|
|
}
|
|
|
|
if (c == 'f')
|
|
{
|
|
return value == "file" || value == "ftp";
|
|
}
|
|
|
|
if (c == 'g')
|
|
{
|
|
return value == "gopher";
|
|
}
|
|
|
|
if (c == 'm')
|
|
{
|
|
return value == "mailto";
|
|
}
|
|
|
|
if (c == 'n')
|
|
{
|
|
c = value[1];
|
|
return c == 'e'
|
|
? value == "news" || value == "net.pipe" || value == "net.tcp"
|
|
: value == "nntp";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsUpgradeTo(this HttpListenerRequest request, string protocol)
|
|
{
|
|
if (request == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(request));
|
|
}
|
|
|
|
if (protocol == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(protocol));
|
|
}
|
|
|
|
if (protocol.Length == 0)
|
|
{
|
|
throw new ArgumentException("An empty string.", nameof(protocol));
|
|
}
|
|
|
|
return request.Headers.Contains("Upgrade", protocol) &&
|
|
request.Headers.Contains("Connection", "Upgrade");
|
|
}
|
|
|
|
public static bool MaybeUri(this string value)
|
|
{
|
|
if (value == null || value.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var idx = value.IndexOf(':');
|
|
if (idx == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (idx >= 10)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var schm = value.Substring(0, idx);
|
|
return schm.IsPredefinedScheme();
|
|
}
|
|
|
|
public static T[] SubArray<T>(this T[] array, int startIndex, int length)
|
|
{
|
|
int len;
|
|
if (array == null || (len = array.Length) == 0)
|
|
{
|
|
return new T[0];
|
|
}
|
|
|
|
if (startIndex < 0 || length <= 0 || startIndex + length > len)
|
|
{
|
|
return new T[0];
|
|
}
|
|
|
|
if (startIndex == 0 && length == len)
|
|
{
|
|
return array;
|
|
}
|
|
|
|
var subArray = new T[length];
|
|
Array.Copy(array, startIndex, subArray, 0, length);
|
|
|
|
return subArray;
|
|
}
|
|
|
|
public static T[] SubArray<T>(this T[] array, long startIndex, long length)
|
|
{
|
|
long len;
|
|
if (array == null || (len = array.LongLength) == 0)
|
|
{
|
|
return new T[0];
|
|
}
|
|
|
|
if (startIndex < 0 || length <= 0 || startIndex + length > len)
|
|
{
|
|
return new T[0];
|
|
}
|
|
|
|
if (startIndex == 0 && length == len)
|
|
{
|
|
return array;
|
|
}
|
|
|
|
var subArray = new T[length];
|
|
Array.Copy(array, startIndex, subArray, 0, length);
|
|
|
|
return subArray;
|
|
}
|
|
|
|
public static void Times(this int n, Action action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
((ulong)n).times(action);
|
|
}
|
|
}
|
|
|
|
public static void Times(this long n, Action action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
((ulong)n).times(action);
|
|
}
|
|
}
|
|
|
|
public static void Times(this uint n, Action action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
((ulong)n).times(action);
|
|
}
|
|
}
|
|
|
|
public static void Times(this ulong n, Action action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
n.times(action);
|
|
}
|
|
}
|
|
|
|
public static void Times(this int n, Action<int> action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
action(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Times(this long n, Action<long> action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
for (long i = 0; i < n; i++)
|
|
{
|
|
action(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Times(this uint n, Action<uint> action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
for (uint i = 0; i < n; i++)
|
|
{
|
|
action(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Times(this ulong n, Action<ulong> action)
|
|
{
|
|
if (n > 0 && action != null)
|
|
{
|
|
for (ulong i = 0; i < n; i++)
|
|
{
|
|
action(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static T To<T>(this byte[] source, ByteOrder sourceOrder)
|
|
where T : struct
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(source));
|
|
}
|
|
|
|
if (source.Length == 0)
|
|
{
|
|
return default(T);
|
|
}
|
|
|
|
var type = typeof(T);
|
|
var buff = source.ToHostOrder(sourceOrder);
|
|
|
|
return type == typeof(bool)
|
|
? (T)(object)BitConverter.ToBoolean(buff, 0)
|
|
: type == typeof(char)
|
|
? (T)(object)BitConverter.ToChar(buff, 0)
|
|
: type == typeof(double)
|
|
? (T)(object)BitConverter.ToDouble(buff, 0)
|
|
: type == typeof(short)
|
|
? (T)(object)BitConverter.ToInt16(buff, 0)
|
|
: type == typeof(int)
|
|
? (T)(object)BitConverter.ToInt32(buff, 0)
|
|
: type == typeof(long)
|
|
? (T)(object)BitConverter.ToInt64(buff, 0)
|
|
: type == typeof(float)
|
|
? (T)(object)BitConverter.ToSingle(buff, 0)
|
|
: type == typeof(ushort)
|
|
? (T)(object)BitConverter.ToUInt16(buff, 0)
|
|
: type == typeof(uint)
|
|
? (T)(object)BitConverter.ToUInt32(buff, 0)
|
|
: type == typeof(ulong)
|
|
? (T)(object)BitConverter.ToUInt64(buff, 0)
|
|
: default(T);
|
|
}
|
|
|
|
public static byte[] ToByteArray<T>(this T value, ByteOrder order)
|
|
where T : struct
|
|
{
|
|
var type = typeof(T);
|
|
var bytes = type == typeof(bool)
|
|
? BitConverter.GetBytes((bool)(object)value)
|
|
: type == typeof(byte)
|
|
? new byte[] { (byte)(object)value }
|
|
: type == typeof(char)
|
|
? BitConverter.GetBytes((char)(object)value)
|
|
: type == typeof(double)
|
|
? BitConverter.GetBytes((double)(object)value)
|
|
: type == typeof(short)
|
|
? BitConverter.GetBytes((short)(object)value)
|
|
: type == typeof(int)
|
|
? BitConverter.GetBytes((int)(object)value)
|
|
: type == typeof(long)
|
|
? BitConverter.GetBytes((long)(object)value)
|
|
: type == typeof(float)
|
|
? BitConverter.GetBytes((float)(object)value)
|
|
: type == typeof(ushort)
|
|
? BitConverter.GetBytes((ushort)(object)value)
|
|
: type == typeof(uint)
|
|
? BitConverter.GetBytes((uint)(object)value)
|
|
: type == typeof(ulong)
|
|
? BitConverter.GetBytes((ulong)(object)value)
|
|
: WSClient.EmptyBytes;
|
|
|
|
if (bytes.Length > 1 && !order.IsHostOrder())
|
|
{
|
|
Array.Reverse(bytes);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
public static byte[] ToHostOrder(this byte[] source, ByteOrder sourceOrder)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(source));
|
|
}
|
|
|
|
return source.Length > 1 && !sourceOrder.IsHostOrder() ? source.Reverse() : source;
|
|
}
|
|
|
|
public static string ToString<T>(this T[] array, string separator)
|
|
{
|
|
if (array == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(array));
|
|
}
|
|
|
|
var len = array.Length;
|
|
if (len == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
separator ??= string.Empty;
|
|
|
|
var buff = new StringBuilder(64);
|
|
(len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
|
|
|
|
buff.Append(array[len - 1].ToString());
|
|
return buff.ToString();
|
|
}
|
|
|
|
public static Uri ToUri(this string value)
|
|
{
|
|
Uri.TryCreate(
|
|
value, value.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out Uri ret
|
|
);
|
|
|
|
return ret;
|
|
}
|
|
|
|
public static string UrlDecode(this string value)
|
|
{
|
|
return value != null && value.Length > 0
|
|
? HttpUtility.UrlDecode(value)
|
|
: value;
|
|
}
|
|
|
|
public static string UrlEncode(this string value)
|
|
{
|
|
return value != null && value.Length > 0
|
|
? HttpUtility.UrlEncode(value)
|
|
: value;
|
|
}
|
|
|
|
public static void WriteContent(this HttpListenerResponse response, byte[] content)
|
|
{
|
|
if (response == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(response));
|
|
}
|
|
|
|
if (content == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(content));
|
|
}
|
|
|
|
var len = content.LongLength;
|
|
if (len == 0)
|
|
{
|
|
response.Close();
|
|
return;
|
|
}
|
|
|
|
response.ContentLength64 = len;
|
|
var output = response.OutputStream;
|
|
if (len <= int.MaxValue)
|
|
{
|
|
output.Write(content, 0, (int)len);
|
|
}
|
|
else
|
|
{
|
|
output.WriteBytes(content, BUFFER_SIZE);
|
|
}
|
|
|
|
output.Close();
|
|
}
|
|
|
|
internal static byte[] Append(this ushort code, string reason)
|
|
{
|
|
var ret = code.InternalToByteArray(ByteOrder.Big);
|
|
if (reason != null && reason.Length > 0)
|
|
{
|
|
var buff = new List<byte>(ret);
|
|
buff.AddRange(Encoding.UTF8.GetBytes(reason));
|
|
ret = buff.ToArray();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal static void Close(this HttpListenerResponse response, HttpStatusCode code)
|
|
{
|
|
response.StatusCode = (int)code;
|
|
response.OutputStream.Close();
|
|
}
|
|
|
|
internal static void CloseWithAuthChallenge(
|
|
this HttpListenerResponse response, string challenge)
|
|
{
|
|
response.Headers.InternalSet("WWW-Authenticate", challenge, true);
|
|
response.Close(HttpStatusCode.Unauthorized);
|
|
}
|
|
|
|
internal static byte[] Compress(this byte[] data, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? data.compress()
|
|
: data;
|
|
}
|
|
|
|
internal static Stream Compress(this Stream stream, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? stream.compress()
|
|
: stream;
|
|
}
|
|
|
|
internal static byte[] CompressToArray(this Stream stream, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? stream.compressToArray()
|
|
: stream.ToByteArray();
|
|
}
|
|
|
|
internal static bool Contains<T>(
|
|
this IEnumerable<T> source, Func<T, bool> condition
|
|
)
|
|
{
|
|
foreach (T elm in source)
|
|
{
|
|
if (condition(elm))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal static bool ContainsTwice(this string[] values)
|
|
{
|
|
var len = values.Length;
|
|
var end = len - 1;
|
|
|
|
Func<int, bool> seek = null;
|
|
seek = idx =>
|
|
{
|
|
if (idx == end)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var val = values[idx];
|
|
for (var i = idx + 1; i < len; i++)
|
|
{
|
|
if (values[i] == val)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return seek(++idx);
|
|
};
|
|
|
|
return seek(0);
|
|
}
|
|
|
|
internal static T[] Copy<T>(this T[] source, int length)
|
|
{
|
|
var dest = new T[length];
|
|
Array.Copy(source, 0, dest, 0, length);
|
|
|
|
return dest;
|
|
}
|
|
|
|
internal static T[] Copy<T>(this T[] source, long length)
|
|
{
|
|
var dest = new T[length];
|
|
Array.Copy(source, 0, dest, 0, length);
|
|
|
|
return dest;
|
|
}
|
|
|
|
internal static void CopyTo(this Stream source, Stream destination, int bufferLength)
|
|
{
|
|
var buff = new byte[bufferLength];
|
|
var nread = 0;
|
|
while ((nread = source.Read(buff, 0, bufferLength)) > 0)
|
|
{
|
|
destination.Write(buff, 0, nread);
|
|
}
|
|
}
|
|
|
|
internal static void CopyToAsync(
|
|
this Stream source,
|
|
Stream destination,
|
|
int bufferLength,
|
|
Action completed,
|
|
Action<Exception> error)
|
|
{
|
|
var buff = new byte[bufferLength];
|
|
|
|
AsyncCallback callback = null;
|
|
callback = ar =>
|
|
{
|
|
try
|
|
{
|
|
var nread = source.EndRead(ar);
|
|
if (nread <= 0)
|
|
{
|
|
if (completed != null)
|
|
{
|
|
completed();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
destination.Write(buff, 0, nread);
|
|
source.BeginRead(buff, 0, bufferLength, callback, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
source.BeginRead(buff, 0, bufferLength, callback, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static byte[] Decompress(this byte[] data, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? data.decompress()
|
|
: data;
|
|
}
|
|
|
|
internal static Stream Decompress(this Stream stream, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? stream.decompress()
|
|
: stream;
|
|
}
|
|
|
|
internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
|
|
{
|
|
return method == CompressionMethod.Deflate
|
|
? stream.decompressToArray()
|
|
: stream.ToByteArray();
|
|
}
|
|
|
|
internal static bool EqualsWith(this int value, char c, Action<int> action)
|
|
{
|
|
action(value);
|
|
return value == c - 0;
|
|
}
|
|
|
|
internal static string GetAbsolutePath(this Uri uri)
|
|
{
|
|
if (uri.IsAbsoluteUri)
|
|
{
|
|
return uri.AbsolutePath;
|
|
}
|
|
|
|
var original = uri.OriginalString;
|
|
if (original[0] != '/')
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var idx = original.IndexOfAny(new[] { '?', '#' });
|
|
return idx > 0 ? original.Substring(0, idx) : original;
|
|
}
|
|
|
|
internal static string GetDnsSafeHost(this Uri uri, bool bracketIPv6)
|
|
{
|
|
return bracketIPv6 && uri.HostNameType == UriHostNameType.IPv6
|
|
? uri.Host
|
|
: uri.DnsSafeHost;
|
|
}
|
|
|
|
internal static string GetMessage(this CloseStatusCode code)
|
|
{
|
|
return code == CloseStatusCode.ProtocolError
|
|
? "A WebSocket protocol error has occurred."
|
|
: code == CloseStatusCode.UnsupportedData
|
|
? "Unsupported data has been received."
|
|
: code == CloseStatusCode.Abnormal
|
|
? "An exception has occurred."
|
|
: code == CloseStatusCode.InvalidData
|
|
? "Invalid data has been received."
|
|
: code == CloseStatusCode.PolicyViolation
|
|
? "A policy violation has occurred."
|
|
: code == CloseStatusCode.TooBig
|
|
? "A too big message has been received."
|
|
: code == CloseStatusCode.MandatoryExtension
|
|
? "WebSocket client didn't receive expected extension(s)."
|
|
: code == CloseStatusCode.ServerError
|
|
? "WebSocket server got an internal error."
|
|
: code == CloseStatusCode.TlsHandshakeFailure
|
|
? "An error has occurred during a TLS handshake."
|
|
: string.Empty;
|
|
}
|
|
|
|
internal static string GetName(this string nameAndValue, char separator)
|
|
{
|
|
var idx = nameAndValue.IndexOf(separator);
|
|
return idx > 0 ? nameAndValue.Substring(0, idx).Trim() : null;
|
|
}
|
|
|
|
internal static string GetValue(this string nameAndValue, char separator)
|
|
{
|
|
var idx = nameAndValue.IndexOf(separator);
|
|
return idx > -1 && idx < nameAndValue.Length - 1
|
|
? nameAndValue.Substring(idx + 1).Trim()
|
|
: null;
|
|
}
|
|
|
|
internal static string GetValue(this string nameAndValue, char separator, bool unquote)
|
|
{
|
|
var idx = nameAndValue.IndexOf(separator);
|
|
if (idx < 0 || idx == nameAndValue.Length - 1)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var val = nameAndValue.Substring(idx + 1).Trim();
|
|
return unquote ? val.Unquote() : val;
|
|
}
|
|
|
|
internal static byte[] InternalToByteArray(this ushort value, ByteOrder order)
|
|
{
|
|
var bytes = BitConverter.GetBytes(value);
|
|
if (!order.IsHostOrder())
|
|
{
|
|
Array.Reverse(bytes);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
internal static byte[] InternalToByteArray(this ulong value, ByteOrder order)
|
|
{
|
|
var bytes = BitConverter.GetBytes(value);
|
|
if (!order.IsHostOrder())
|
|
{
|
|
Array.Reverse(bytes);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
internal static bool IsCompressionExtension(this string value, CompressionMethod method)
|
|
{
|
|
return value.StartsWith(method.ToExtensionString());
|
|
}
|
|
|
|
internal static bool IsControl(this byte opcode)
|
|
{
|
|
return opcode > 0x7 && opcode < 0x10;
|
|
}
|
|
|
|
internal static bool IsControl(this OperationCode opcode)
|
|
{
|
|
return opcode >= OperationCode.Close;
|
|
}
|
|
|
|
internal static bool IsData(this byte opcode)
|
|
{
|
|
return opcode == 0x1 || opcode == 0x2;
|
|
}
|
|
|
|
internal static bool IsData(this OperationCode opcode)
|
|
{
|
|
return opcode == OperationCode.Text || opcode == OperationCode.Binary;
|
|
}
|
|
|
|
internal static bool IsPortNumber(this int value)
|
|
{
|
|
return value > 0 && value < 65536;
|
|
}
|
|
|
|
internal static bool IsReserved(this ushort code)
|
|
{
|
|
return code == (ushort)CloseStatusCode.Undefined
|
|
|| code == (ushort)CloseStatusCode.NoStatus
|
|
|| code == (ushort)CloseStatusCode.Abnormal
|
|
|| code == (ushort)CloseStatusCode.TlsHandshakeFailure;
|
|
}
|
|
|
|
internal static bool IsReserved(this CloseStatusCode code)
|
|
{
|
|
return code == CloseStatusCode.Undefined
|
|
|| code == CloseStatusCode.NoStatus
|
|
|| code == CloseStatusCode.Abnormal
|
|
|| code == CloseStatusCode.TlsHandshakeFailure;
|
|
}
|
|
|
|
internal static bool IsSupported(this byte opcode)
|
|
{
|
|
return Enum.IsDefined(typeof(OperationCode), opcode);
|
|
}
|
|
|
|
internal static bool IsText(this string value)
|
|
{
|
|
var len = value.Length;
|
|
|
|
for (var i = 0; i < len; i++)
|
|
{
|
|
var c = value[i];
|
|
if (c < 0x20)
|
|
{
|
|
if (!"\r\n\t".Contains(c))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (c == '\n')
|
|
{
|
|
i++;
|
|
if (i == len)
|
|
{
|
|
break;
|
|
}
|
|
|
|
c = value[i];
|
|
if (!" \t".Contains(c))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (c == 0x7f)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool IsToken(this string value)
|
|
{
|
|
foreach (var c in value)
|
|
{
|
|
if (c < 0x20)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (c >= 0x7f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_tspecials.Contains(c))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static string Quote(this string value)
|
|
{
|
|
return string.Format("\"{0}\"", value.Replace("\"", "\\\""));
|
|
}
|
|
|
|
internal static byte[] ReadBytes(this Stream stream, int length)
|
|
{
|
|
var buff = new byte[length];
|
|
var offset = 0;
|
|
try
|
|
{
|
|
var nread = 0;
|
|
while (length > 0)
|
|
{
|
|
nread = stream.Read(buff, offset, length);
|
|
if (nread == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
offset += nread;
|
|
length -= nread;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
return buff.SubArray(0, offset);
|
|
}
|
|
|
|
internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)
|
|
{
|
|
using (var dest = new MemoryStream())
|
|
{
|
|
try
|
|
{
|
|
var buff = new byte[bufferLength];
|
|
var nread = 0;
|
|
while (length > 0)
|
|
{
|
|
if (length < bufferLength)
|
|
{
|
|
bufferLength = (int)length;
|
|
}
|
|
|
|
nread = stream.Read(buff, 0, bufferLength);
|
|
if (nread == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
dest.Write(buff, 0, nread);
|
|
length -= nread;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
dest.Close();
|
|
return dest.ToArray();
|
|
}
|
|
}
|
|
|
|
internal static void ReadBytesAsync(
|
|
this Stream stream, int length, Action<byte[]> completed, Action<Exception> error
|
|
)
|
|
{
|
|
var buff = new byte[length];
|
|
var offset = 0;
|
|
var retry = 0;
|
|
|
|
AsyncCallback callback = null;
|
|
callback =
|
|
ar =>
|
|
{
|
|
try
|
|
{
|
|
var nread = stream.EndRead(ar);
|
|
if (nread == 0 && retry < _retry)
|
|
{
|
|
retry++;
|
|
stream.BeginRead(buff, offset, length, callback, null);
|
|
|
|
return;
|
|
}
|
|
|
|
if (nread == length)
|
|
{
|
|
if (completed != null)
|
|
{
|
|
completed(buff.SubArray(0, offset + nread));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
retry = 0;
|
|
|
|
offset += nread;
|
|
length -= nread;
|
|
|
|
stream.BeginRead(buff, offset, length, callback, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
stream.BeginRead(buff, offset, length, callback, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void ReadBytesAsync(
|
|
this Stream stream,
|
|
long length,
|
|
int bufferLength,
|
|
Action<byte[]> completed,
|
|
Action<Exception> error
|
|
)
|
|
{
|
|
var dest = new MemoryStream();
|
|
var buff = new byte[bufferLength];
|
|
var retry = 0;
|
|
|
|
Action<long> read = null;
|
|
read =
|
|
len =>
|
|
{
|
|
if (len < bufferLength)
|
|
{
|
|
bufferLength = (int)len;
|
|
}
|
|
|
|
stream.BeginRead(
|
|
buff,
|
|
0,
|
|
bufferLength,
|
|
ar =>
|
|
{
|
|
try
|
|
{
|
|
var nread = stream.EndRead(ar);
|
|
if (nread > 0)
|
|
{
|
|
dest.Write(buff, 0, nread);
|
|
}
|
|
|
|
if (nread == 0 && retry < _retry)
|
|
{
|
|
retry++;
|
|
read(len);
|
|
|
|
return;
|
|
}
|
|
|
|
if (nread == 0 || nread == len)
|
|
{
|
|
if (completed != null)
|
|
{
|
|
dest.Close();
|
|
completed(dest.ToArray());
|
|
}
|
|
|
|
dest.Dispose();
|
|
return;
|
|
}
|
|
|
|
retry = 0;
|
|
read(len - nread);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
dest.Dispose();
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
},
|
|
null
|
|
);
|
|
};
|
|
|
|
try
|
|
{
|
|
read(length);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
dest.Dispose();
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static string RemovePrefix(this string value, params string[] prefixes)
|
|
{
|
|
var idx = 0;
|
|
foreach (var prefix in prefixes)
|
|
{
|
|
if (value.StartsWith(prefix))
|
|
{
|
|
idx = prefix.Length;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return idx > 0 ? value.Substring(idx) : value;
|
|
}
|
|
|
|
internal static T[] Reverse<T>(this T[] array)
|
|
{
|
|
var len = array.Length;
|
|
var reverse = new T[len];
|
|
|
|
var end = len - 1;
|
|
for (var i = 0; i <= end; i++)
|
|
{
|
|
reverse[i] = array[end - i];
|
|
}
|
|
|
|
return reverse;
|
|
}
|
|
|
|
internal static IEnumerable<string> SplitHeaderValue(
|
|
this string value, params char[] separators)
|
|
{
|
|
var len = value.Length;
|
|
var seps = new string(separators);
|
|
|
|
var buff = new StringBuilder(32);
|
|
var escaped = false;
|
|
var quoted = false;
|
|
|
|
for (var i = 0; i < len; i++)
|
|
{
|
|
var c = value[i];
|
|
if (c == '"')
|
|
{
|
|
if (escaped)
|
|
{
|
|
escaped = !escaped;
|
|
}
|
|
else
|
|
{
|
|
quoted = !quoted;
|
|
}
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
if (i < len - 1 && value[i + 1] == '"')
|
|
{
|
|
escaped = true;
|
|
}
|
|
}
|
|
else if (seps.Contains(c))
|
|
{
|
|
if (!quoted)
|
|
{
|
|
yield return buff.ToString();
|
|
buff.Length = 0;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
buff.Append(c);
|
|
}
|
|
|
|
if (buff.Length > 0)
|
|
{
|
|
yield return buff.ToString();
|
|
}
|
|
}
|
|
|
|
internal static byte[] ToByteArray(this Stream stream)
|
|
{
|
|
using (var output = new MemoryStream())
|
|
{
|
|
stream.Position = 0;
|
|
stream.CopyTo(output, BUFFER_SIZE);
|
|
output.Close();
|
|
|
|
return output.ToArray();
|
|
}
|
|
}
|
|
|
|
internal static CompressionMethod ToCompressionMethod(this string value)
|
|
{
|
|
foreach (CompressionMethod method in Enum.GetValues(typeof(CompressionMethod)))
|
|
{
|
|
if (method.ToExtensionString() == value)
|
|
{
|
|
return method;
|
|
}
|
|
}
|
|
|
|
return CompressionMethod.None;
|
|
}
|
|
|
|
internal static string ToExtensionString(
|
|
this CompressionMethod method, params string[] parameters)
|
|
{
|
|
if (method == CompressionMethod.None)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var m = string.Format("permessage-{0}", method.ToString().ToLower());
|
|
if (parameters == null || parameters.Length == 0)
|
|
{
|
|
return m;
|
|
}
|
|
|
|
return string.Format("{0}; {1}", m, parameters.ToString("; "));
|
|
}
|
|
|
|
internal static System.Net.IPAddress ToIPAddress(this string value)
|
|
{
|
|
if (value == null || value.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (System.Net.IPAddress.TryParse(value, out System.Net.IPAddress addr))
|
|
{
|
|
return addr;
|
|
}
|
|
|
|
try
|
|
{
|
|
var addrs = System.Net.Dns.GetHostAddresses(value);
|
|
return addrs[0];
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
|
|
{
|
|
return new List<TSource>(source);
|
|
}
|
|
|
|
internal static string ToString(
|
|
this System.Net.IPAddress address, bool bracketIPv6
|
|
)
|
|
{
|
|
return bracketIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6
|
|
? string.Format("[{0}]", address.ToString())
|
|
: address.ToString();
|
|
}
|
|
|
|
internal static ushort ToUInt16(this byte[] source, ByteOrder sourceOrder)
|
|
{
|
|
return BitConverter.ToUInt16(source.ToHostOrder(sourceOrder), 0);
|
|
}
|
|
|
|
internal static ulong ToUInt64(this byte[] source, ByteOrder sourceOrder)
|
|
{
|
|
return BitConverter.ToUInt64(source.ToHostOrder(sourceOrder), 0);
|
|
}
|
|
|
|
internal static string TrimSlashFromEnd(this string value)
|
|
{
|
|
var ret = value.TrimEnd('/');
|
|
return ret.Length > 0 ? ret : "/";
|
|
}
|
|
|
|
internal static string TrimSlashOrBackslashFromEnd(this string value)
|
|
{
|
|
var ret = value.TrimEnd('/', '\\');
|
|
return ret.Length > 0 ? ret : value[0].ToString();
|
|
}
|
|
|
|
internal static bool TryCreateWebSocketUri(
|
|
this string uriString, out Uri result, out string message
|
|
)
|
|
{
|
|
result = null;
|
|
message = null;
|
|
|
|
var uri = uriString.ToUri();
|
|
if (uri == null)
|
|
{
|
|
message = "An invalid URI string.";
|
|
return false;
|
|
}
|
|
|
|
if (!uri.IsAbsoluteUri)
|
|
{
|
|
message = "A relative URI.";
|
|
return false;
|
|
}
|
|
|
|
var scheme = uri.Scheme;
|
|
if (!(scheme == "ws" || scheme == "wss"))
|
|
{
|
|
message = "The scheme part is not 'ws' or 'wss'.";
|
|
return false;
|
|
}
|
|
|
|
var port = uri.Port;
|
|
if (port == 0)
|
|
{
|
|
message = "The port part is zero.";
|
|
return false;
|
|
}
|
|
|
|
if (uri.Fragment.Length > 0)
|
|
{
|
|
message = "It includes the fragment component.";
|
|
return false;
|
|
}
|
|
|
|
result = port != -1
|
|
? uri
|
|
: new Uri(
|
|
$"{scheme}://{uri.Host}:{(scheme == "ws" ? 80 : 443)}{uri.PathAndQuery}"
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool TryGetUTF8DecodedString(this byte[] bytes, out string s)
|
|
{
|
|
s = null;
|
|
|
|
try
|
|
{
|
|
s = Encoding.UTF8.GetString(bytes);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool TryGetUTF8EncodedBytes(this string s, out byte[] bytes)
|
|
{
|
|
bytes = null;
|
|
|
|
try
|
|
{
|
|
bytes = Encoding.UTF8.GetBytes(s);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool TryOpenRead(
|
|
this FileInfo fileInfo, out FileStream fileStream
|
|
)
|
|
{
|
|
fileStream = null;
|
|
|
|
try
|
|
{
|
|
fileStream = fileInfo.OpenRead();
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static string Unquote(this string value)
|
|
{
|
|
var start = value.IndexOf('"');
|
|
if (start < 0)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var end = value.LastIndexOf('"');
|
|
var len = end - start - 1;
|
|
|
|
return len < 0
|
|
? value
|
|
: len == 0
|
|
? string.Empty
|
|
: value.Substring(start + 1, len).Replace("\\\"", "\"");
|
|
}
|
|
|
|
internal static string UTF8Decode(this byte[] bytes)
|
|
{
|
|
try
|
|
{
|
|
return Encoding.UTF8.GetString(bytes);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal static byte[] UTF8Encode(this string s)
|
|
{
|
|
return Encoding.UTF8.GetBytes(s);
|
|
}
|
|
|
|
internal static void WriteBytes(this Stream stream, byte[] bytes, int bufferLength)
|
|
{
|
|
using (var input = new MemoryStream(bytes))
|
|
{
|
|
input.CopyTo(stream, bufferLength);
|
|
}
|
|
}
|
|
|
|
internal static void WriteBytesAsync(
|
|
this Stream stream, byte[] bytes, int bufferLength, Action completed, Action<Exception> error)
|
|
{
|
|
var input = new MemoryStream(bytes);
|
|
input.CopyToAsync(
|
|
stream,
|
|
bufferLength,
|
|
() =>
|
|
{
|
|
if (completed != null)
|
|
{
|
|
completed();
|
|
}
|
|
|
|
input.Dispose();
|
|
},
|
|
ex =>
|
|
{
|
|
input.Dispose();
|
|
if (error != null)
|
|
{
|
|
error(ex);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static byte[] compress(this byte[] data)
|
|
{
|
|
if (data.LongLength == 0)
|
|
{
|
|
return data;
|
|
}
|
|
|
|
using (var input = new MemoryStream(data))
|
|
{
|
|
return input.compressToArray();
|
|
}
|
|
}
|
|
|
|
private static MemoryStream compress(this Stream stream)
|
|
{
|
|
var output = new MemoryStream();
|
|
if (stream.Length == 0)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
stream.Position = 0;
|
|
using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
|
|
{
|
|
stream.CopyTo(ds, BUFFER_SIZE);
|
|
ds.Close(); // BFINAL set to 1.
|
|
output.Write(_last, 0, 1);
|
|
output.Position = 0;
|
|
|
|
return output;
|
|
}
|
|
}
|
|
|
|
private static byte[] compressToArray(this Stream stream)
|
|
{
|
|
using (var output = stream.compress())
|
|
{
|
|
output.Close();
|
|
return output.ToArray();
|
|
}
|
|
}
|
|
|
|
private static byte[] decompress(this byte[] data)
|
|
{
|
|
if (data.LongLength == 0)
|
|
{
|
|
return data;
|
|
}
|
|
|
|
using (var input = new MemoryStream(data))
|
|
{
|
|
return input.decompressToArray();
|
|
}
|
|
}
|
|
|
|
private static MemoryStream decompress(this Stream stream)
|
|
{
|
|
var output = new MemoryStream();
|
|
if (stream.Length == 0)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
stream.Position = 0;
|
|
using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
|
|
{
|
|
ds.CopyTo(output, BUFFER_SIZE);
|
|
output.Position = 0;
|
|
|
|
return output;
|
|
}
|
|
}
|
|
|
|
private static byte[] decompressToArray(this Stream stream)
|
|
{
|
|
using (var output = stream.decompress())
|
|
{
|
|
output.Close();
|
|
return output.ToArray();
|
|
}
|
|
}
|
|
|
|
private static void times(this ulong n, Action action)
|
|
{
|
|
for (ulong i = 0; i < n; i++)
|
|
{
|
|
action();
|
|
}
|
|
}
|
|
}
|
|
} |