| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960 | using System;using System.Collections.Generic;using System.IO;using System.IO.Compression;using System.Net;using System.Text;using System.Threading.Tasks;using MediaBrowser.Model.Services;using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;using WebSocketState = System.Net.WebSockets.WebSocketState;namespace SocketHttpListener{    /// <summary>    /// Provides a set of static methods for the websocket-sharp.    /// </summary>    public static class Ext    {        #region Private Const Fields        private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";        #endregion        #region Private Methods        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);                //ds.Close(); // "BFINAL" set to 1.                output.Position = 0;                return output;            }        }        private static byte[] decompress(this byte[] value)        {            if (value.Length == 0)                return value;            using (var input = new MemoryStream(value))            {                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, true);                return output;            }        }        private static byte[] decompressToArray(this Stream stream)        {            using (var decomp = stream.decompress())            {                return decomp.ToArray();            }        }        private static byte[] readBytes(this Stream stream, byte[] buffer, int offset, int length)        {            var len = stream.Read(buffer, offset, length);            if (len < 1)                return buffer.SubArray(0, offset);            var tmp = 0;            while (len < length)            {                tmp = stream.Read(buffer, offset + len, length - len);                if (tmp < 1)                    break;                len += tmp;            }            return len < length                   ? buffer.SubArray(0, offset + len)                   : buffer;        }        private static bool readBytes(          this Stream stream, byte[] buffer, int offset, int length, Stream dest)        {            var bytes = stream.readBytes(buffer, offset, length);            var len = bytes.Length;            dest.Write(bytes, 0, len);            return len == offset + length;        }        #endregion        #region Internal Methods        internal static byte[] Append(this ushort code, string reason)        {            using (var buffer = new MemoryStream())            {                var tmp = code.ToByteArrayInternally(ByteOrder.Big);                buffer.Write(tmp, 0, 2);                if (reason != null && reason.Length > 0)                {                    tmp = Encoding.UTF8.GetBytes(reason);                    buffer.Write(tmp, 0, tmp.Length);                }                return buffer.ToArray();            }        }        internal static string CheckIfClosable(this WebSocketState state)        {            return state == WebSocketState.CloseSent                   ? "While closing the WebSocket connection."                   : state == WebSocketState.Closed                     ? "The WebSocket connection has already been closed."                     : null;        }        internal static string CheckIfOpen(this WebSocketState state)        {            return state == WebSocketState.Connecting                   ? "A WebSocket connection isn't established."                   : state == WebSocketState.CloseSent                     ? "While closing the WebSocket connection."                     : state == WebSocketState.Closed                       ? "The WebSocket connection has already been closed."                       : null;        }        internal static string CheckIfValidControlData(this byte[] data, string paramName)        {            return data.Length > 125                   ? string.Format("'{0}' length must be less.", paramName)                   : null;        }        internal static Stream Compress(this Stream stream, CompressionMethod method)        {            return method == CompressionMethod.Deflate                   ? stream.compress()                   : stream;        }        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 void CopyTo(this Stream src, Stream dest, bool setDefaultPosition)        {            var readLen = 0;            var bufferLen = 256;            var buffer = new byte[bufferLen];            while ((readLen = src.Read(buffer, 0, bufferLen)) > 0)            {                dest.Write(buffer, 0, readLen);            }            if (setDefaultPosition)                dest.Position = 0;        }        internal static byte[] Decompress(this byte[] value, CompressionMethod method)        {            return method == CompressionMethod.Deflate                   ? value.decompress()                   : value;        }        internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)        {            return method == CompressionMethod.Deflate                   ? stream.decompressToArray()                   : stream.ToByteArray();        }        /// <summary>        /// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,        /// and invokes the specified Action<int> delegate at the same time.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="value"/> equals <paramref name="c"/>;        /// otherwise, <c>false</c>.        /// </returns>        /// <param name="value">        /// An <see cref="int"/> to compare.        /// </param>        /// <param name="c">        /// A <see cref="char"/> to compare.        /// </param>        /// <param name="action">        /// An Action<int> delegate that references the method(s) called at        /// the same time as comparing. An <see cref="int"/> parameter to pass to        /// the method(s) is <paramref name="value"/>.        /// </param>        /// <exception cref="ArgumentOutOfRangeException">        /// <paramref name="value"/> isn't between 0 and 255.        /// </exception>        internal static bool EqualsWith(this int value, char c, Action<int> action)        {            if (value < 0 || value > 255)                throw new ArgumentOutOfRangeException(nameof(value));            action(value);            return value == c - 0;        }        internal static string GetMessage(this CloseStatusCode code)        {            return code == CloseStatusCode.ProtocolError                   ? "A WebSocket protocol error has occurred."                   : code == CloseStatusCode.IncorrectData                     ? "An incorrect data has been received."                     : code == CloseStatusCode.Abnormal                       ? "An exception has occurred."                       : code == CloseStatusCode.InconsistentData                         ? "An inconsistent data has been received."                         : code == CloseStatusCode.PolicyViolation                           ? "A policy violation has occurred."                           : code == CloseStatusCode.TooBig                             ? "A too big data has been received."                             : code == CloseStatusCode.IgnoreExtension                               ? "WebSocket client did not receive expected extension(s)."                               : code == CloseStatusCode.ServerError                                 ? "WebSocket server got an internal error."                                 : code == CloseStatusCode.TlsHandshakeFailure                                   ? "An error has occurred while handshaking."                                   : string.Empty;        }        internal static string GetNameInternal(this string nameAndValue, string separator)        {            var i = nameAndValue.IndexOf(separator);            return i > 0                   ? nameAndValue.Substring(0, i).Trim()                   : null;        }        internal static string GetValueInternal(this string nameAndValue, string separator)        {            var i = nameAndValue.IndexOf(separator);            return i >= 0 && i < nameAndValue.Length - 1                   ? nameAndValue.Substring(i + 1).Trim()                   : null;        }        internal static bool IsCompressionExtension(this string value, CompressionMethod method)        {            return value.StartsWith(method.ToExtensionString());        }        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.NoStatusCode ||                   code == (ushort)CloseStatusCode.Abnormal ||                   code == (ushort)CloseStatusCode.TlsHandshakeFailure;        }        internal static bool IsReserved(this CloseStatusCode code)        {            return code == CloseStatusCode.Undefined ||                   code == CloseStatusCode.NoStatusCode ||                   code == CloseStatusCode.Abnormal ||                   code == CloseStatusCode.TlsHandshakeFailure;        }        internal static bool IsText(this string value)        {            var len = value.Length;            for (var i = 0; i < len; i++)            {                char c = value[i];                if (c < 0x20 && !"\r\n\t".Contains(c))                    return false;                if (c == 0x7f)                    return false;                if (c == '\n' && ++i < len)                {                    c = value[i];                    if (!" \t".Contains(c))                        return false;                }            }            return true;        }        internal static bool IsToken(this string value)        {            foreach (char c in value)                if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c))                    return false;            return true;        }        internal static string Quote(this string value)        {            return value.IsToken()                   ? value                   : string.Format("\"{0}\"", value.Replace("\"", "\\\""));        }        internal static byte[] ReadBytes(this Stream stream, int length)        {            return stream.readBytes(new byte[length], 0, length);        }        internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)        {            using (var result = new MemoryStream())            {                var count = length / bufferLength;                var rem = (int)(length % bufferLength);                var buffer = new byte[bufferLength];                var end = false;                for (long i = 0; i < count; i++)                {                    if (!stream.readBytes(buffer, 0, bufferLength, result))                    {                        end = true;                        break;                    }                }                if (!end && rem > 0)                    stream.readBytes(new byte[rem], 0, rem, result);                return result.ToArray();            }        }        internal static async Task<byte[]> ReadBytesAsync(this Stream stream, int length)        {            var buffer = new byte[length];            var len = await stream.ReadAsync(buffer, 0, length).ConfigureAwait(false);            var bytes = len < 1                ? new byte[0]                : len < length                  ? stream.readBytes(buffer, len, length - len)                  : buffer;            return bytes;        }        internal static string RemovePrefix(this string value, params string[] prefixes)        {            var i = 0;            foreach (var prefix in prefixes)            {                if (value.StartsWith(prefix))                {                    i = prefix.Length;                    break;                }            }            return i > 0                   ? value.Substring(i)                   : value;        }        internal static T[] Reverse<T>(this T[] array)        {            var len = array.Length;            T[] 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[] separator)        {            var len = value.Length;            var separators = new string(separator);            var buffer = new StringBuilder(32);            var quoted = false;            var escaped = false;            char c;            for (var i = 0; i < len; i++)            {                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 (separators.Contains(c))                {                    if (!quoted)                    {                        yield return buffer.ToString();                        buffer.Length = 0;                        continue;                    }                }                else                {                }                buffer.Append(c);            }            if (buffer.Length > 0)                yield return buffer.ToString();        }        internal static byte[] ToByteArray(this Stream stream)        {            using (var output = new MemoryStream())            {                stream.Position = 0;                stream.CopyTo(output);                return output.ToArray();            }        }        internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order)        {            var bytes = BitConverter.GetBytes(value);            if (!order.IsHostOrder())                Array.Reverse(bytes);            return bytes;        }        internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order)        {            var bytes = BitConverter.GetBytes(value);            if (!order.IsHostOrder())                Array.Reverse(bytes);            return bytes;        }        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().ToLowerInvariant());            if (parameters == null || parameters.Length == 0)                return m;            return string.Format("{0}; {1}", m, parameters.ToString("; "));        }        internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)        {            return new List<TSource>(source);        }        internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)        {            return BitConverter.ToUInt16(src.ToHostOrder(srcOrder), 0);        }        internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)        {            return BitConverter.ToUInt64(src.ToHostOrder(srcOrder), 0);        }        internal static string TrimEndSlash(this string value)        {            value = value.TrimEnd('/');            return value.Length > 0                   ? value                   : "/";        }        internal static string Unquote(this string value)        {            var start = value.IndexOf('\"');            var end = value.LastIndexOf('\"');            if (start < end)                value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\"");            return value.Trim();        }        internal static void WriteBytes(this Stream stream, byte[] value)        {            using (var src = new MemoryStream(value))            {                src.CopyTo(stream);            }        }        #endregion        #region Public Methods        /// <summary>        /// Determines whether the specified <see cref="string"/> contains any of characters        /// in the specified array of <see cref="char"/>.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;        /// otherwise, <c>false</c>.        /// </returns>        /// <param name="value">        /// A <see cref="string"/> to test.        /// </param>        /// <param name="chars">        /// An array of <see cref="char"/> that contains characters to find.        /// </param>        public static bool Contains(this string value, params char[] chars)        {            return chars == null || chars.Length == 0                   ? true                   : value == null || value.Length == 0                     ? false                     : value.IndexOfAny(chars) != -1;        }        /// <summary>        /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry        /// with the specified <paramref name="name"/>.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="collection"/> contains the entry        /// with <paramref name="name"/>; otherwise, <c>false</c>.        /// </returns>        /// <param name="collection">        /// A <see cref="QueryParamCollection"/> to test.        /// </param>        /// <param name="name">        /// A <see cref="string"/> that represents the key of the entry to find.        /// </param>        public static bool Contains(this QueryParamCollection collection, string name)        {            return collection == null || collection.Count == 0                   ? false                   : collection[name] != null;        }        /// <summary>        /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry        /// with the specified both <paramref name="name"/> and <paramref name="value"/>.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="collection"/> contains the entry        /// with both <paramref name="name"/> and <paramref name="value"/>;        /// otherwise, <c>false</c>.        /// </returns>        /// <param name="collection">        /// A <see cref="QueryParamCollection"/> to test.        /// </param>        /// <param name="name">        /// A <see cref="string"/> that represents the key of the entry to find.        /// </param>        /// <param name="value">        /// A <see cref="string"/> that represents the value of the entry to find.        /// </param>        public static bool Contains(this QueryParamCollection collection, string name, string value)        {            if (collection == null || collection.Count == 0)                return false;            var values = collection[name];            if (values == null)                return false;            foreach (var v in values.Split(','))                if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))                    return true;            return false;        }        /// <summary>        /// Emits the specified <c>EventHandler<TEventArgs></c> delegate        /// if it isn't <see langword="null"/>.        /// </summary>        /// <param name="eventHandler">        /// An <c>EventHandler<TEventArgs></c> to emit.        /// </param>        /// <param name="sender">        /// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.        /// </param>        /// <param name="e">        /// A <c>TEventArgs</c> that represents the event data.        /// </param>        /// <typeparam name="TEventArgs">        /// The type of the event data generated by the event.        /// </typeparam>        public static void Emit<TEventArgs>(          this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)          where TEventArgs : EventArgs        {            if (eventHandler != null)                eventHandler(sender, e);        }        /// <summary>        /// Gets the description of the specified HTTP status <paramref name="code"/>.        /// </summary>        /// <returns>        /// A <see cref="string"/> that represents the description of the HTTP status code.        /// </returns>        /// <param name="code">        /// One of <see cref="HttpStatusCode"/> enum values, indicates the HTTP status codes.        /// </param>        public static string GetDescription(this HttpStatusCode code)        {            return ((int)code).GetStatusDescription();        }        /// <summary>        /// Gets the description of the specified HTTP status <paramref name="code"/>.        /// </summary>        /// <returns>        /// A <see cref="string"/> that represents the description of the HTTP status code.        /// </returns>        /// <param name="code">        /// An <see cref="int"/> that represents the HTTP status code.        /// </param>        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;        }        /// <summary>        /// Determines whether the specified <see cref="ByteOrder"/> is host        /// (this computer architecture) byte order.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="order"/> is host byte order;        /// otherwise, <c>false</c>.        /// </returns>        /// <param name="order">        /// One of the <see cref="ByteOrder"/> enum values, to test.        /// </param>        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>        /// Determines whether the specified <see cref="string"/> is a predefined scheme.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="value"/> is a predefined scheme; otherwise, <c>false</c>.        /// </returns>        /// <param name="value">        /// A <see cref="string"/> to test.        /// </param>        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 == 'n')            {                c = value[1];                return c == 'e'                       ? value == "news" || value == "net.pipe" || value == "net.tcp"                       : value == "nntp";            }            return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");        }        /// <summary>        /// Determines whether the specified <see cref="string"/> is a URI string.        /// </summary>        /// <returns>        /// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.        /// </returns>        /// <param name="value">        /// A <see cref="string"/> to test.        /// </param>        public static bool MaybeUri(this string value)        {            if (value == null || value.Length == 0)                return false;            var i = value.IndexOf(':');            if (i == -1)                return false;            if (i >= 10)                return false;            return value.Substring(0, i).IsPredefinedScheme();        }        /// <summary>        /// Retrieves a sub-array from the specified <paramref name="array"/>.        /// A sub-array starts at the specified element position.        /// </summary>        /// <returns>        /// An array of T that receives a sub-array, or an empty array of T if any problems        /// with the parameters.        /// </returns>        /// <param name="array">        /// An array of T that contains the data to retrieve a sub-array.        /// </param>        /// <param name="startIndex">        /// An <see cref="int"/> that contains the zero-based starting position of a sub-array        /// in <paramref name="array"/>.        /// </param>        /// <param name="length">        /// An <see cref="int"/> that contains the number of elements to retrieve a sub-array.        /// </param>        /// <typeparam name="T">        /// The type of elements in the <paramref name="array"/>.        /// </typeparam>        public static T[] SubArray<T>(this T[] array, int startIndex, int length)        {            if (array == null || array.Length == 0)                return new T[0];            if (startIndex < 0 || length <= 0)                return new T[0];            if (startIndex + length > array.Length)                return new T[0];            if (startIndex == 0 && array.Length == length)                return array;            T[] subArray = new T[length];            Array.Copy(array, startIndex, subArray, 0, length);            return subArray;        }        /// <summary>        /// Converts the order of the specified array of <see cref="byte"/> to the host byte order.        /// </summary>        /// <returns>        /// An array of <see cref="byte"/> converted from <paramref name="src"/>.        /// </returns>        /// <param name="src">        /// An array of <see cref="byte"/> to convert.        /// </param>        /// <param name="srcOrder">        /// One of the <see cref="ByteOrder"/> enum values, indicates the byte order of        /// <paramref name="src"/>.        /// </param>        /// <exception cref="ArgumentNullException">        /// <paramref name="src"/> is <see langword="null"/>.        /// </exception>        public static byte[] ToHostOrder(this byte[] src, ByteOrder srcOrder)        {            if (src == null)                throw new ArgumentNullException(nameof(src));            return src.Length > 1 && !srcOrder.IsHostOrder()                   ? src.Reverse()                   : src;        }        /// <summary>        /// Converts the specified <paramref name="array"/> to a <see cref="string"/> that        /// concatenates the each element of <paramref name="array"/> across the specified        /// <paramref name="separator"/>.        /// </summary>        /// <returns>        /// A <see cref="string"/> converted from <paramref name="array"/>,        /// or <see cref="String.Empty"/> if <paramref name="array"/> is empty.        /// </returns>        /// <param name="array">        /// An array of T to convert.        /// </param>        /// <param name="separator">        /// A <see cref="string"/> that represents the separator string.        /// </param>        /// <typeparam name="T">        /// The type of elements in <paramref name="array"/>.        /// </typeparam>        /// <exception cref="ArgumentNullException">        /// <paramref name="array"/> is <see langword="null"/>.        /// </exception>        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;            if (separator == null)                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();        }        /// <summary>        /// Executes the specified <c>Action<int></c> delegate <paramref name="n"/> times.        /// </summary>        /// <param name="n">        /// An <see cref="int"/> is the number of times to execute.        /// </param>        /// <param name="action">        /// An <c>Action<int></c> delegate that references the method(s) to execute.        /// An <see cref="int"/> parameter to pass to the method(s) is the zero-based count of        /// iteration.        /// </param>        public static void Times(this int n, Action<int> action)        {            if (n > 0 && action != null)                for (int i = 0; i < n; i++)                    action(i);        }        /// <summary>        /// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.        /// </summary>        /// <returns>        /// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>        /// if <paramref name="uriString"/> isn't successfully converted.        /// </returns>        /// <param name="uriString">        /// A <see cref="string"/> to convert.        /// </param>        public static Uri ToUri(this string uriString)        {            return Uri.TryCreate(                     uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res)                   ? res                   : null;        }        /// <summary>        /// URL-decodes the specified <see cref="string"/>.        /// </summary>        /// <returns>        /// A <see cref="string"/> that receives the decoded string, or the <paramref name="value"/>        /// if it's <see langword="null"/> or empty.        /// </returns>        /// <param name="value">        /// A <see cref="string"/> to decode.        /// </param>        public static string UrlDecode(this string value)        {            return value == null || value.Length == 0                   ? value                   : WebUtility.UrlDecode(value);        }        #endregion    }}
 |