Ext.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Model.Services;
  9. using WebSocketState = System.Net.WebSockets.WebSocketState;
  10. namespace SocketHttpListener
  11. {
  12. /// <summary>
  13. /// Provides a set of static methods for the websocket-sharp.
  14. /// </summary>
  15. public static class Ext
  16. {
  17. #region Private Const Fields
  18. private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
  19. #endregion
  20. #region Private Methods
  21. private static MemoryStream compress(this Stream stream)
  22. {
  23. var output = new MemoryStream();
  24. if (stream.Length == 0)
  25. return output;
  26. stream.Position = 0;
  27. using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
  28. {
  29. stream.CopyTo(ds);
  30. //ds.Close(); // "BFINAL" set to 1.
  31. output.Position = 0;
  32. return output;
  33. }
  34. }
  35. private static byte[] decompress(this byte[] value)
  36. {
  37. if (value.Length == 0)
  38. return value;
  39. using (var input = new MemoryStream(value))
  40. {
  41. return input.decompressToArray();
  42. }
  43. }
  44. private static MemoryStream decompress(this Stream stream)
  45. {
  46. var output = new MemoryStream();
  47. if (stream.Length == 0)
  48. return output;
  49. stream.Position = 0;
  50. using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
  51. {
  52. ds.CopyTo(output, true);
  53. return output;
  54. }
  55. }
  56. private static byte[] decompressToArray(this Stream stream)
  57. {
  58. using (var decomp = stream.decompress())
  59. {
  60. return decomp.ToArray();
  61. }
  62. }
  63. private static async Task<byte[]> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length)
  64. {
  65. var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false);
  66. if (len < 1)
  67. return buffer.SubArray(0, offset);
  68. var tmp = 0;
  69. while (len < length)
  70. {
  71. tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false);
  72. if (tmp < 1)
  73. {
  74. break;
  75. }
  76. len += tmp;
  77. }
  78. return len < length
  79. ? buffer.SubArray(0, offset + len)
  80. : buffer;
  81. }
  82. private static async Task<bool> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest)
  83. {
  84. var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false);
  85. var len = bytes.Length;
  86. dest.Write(bytes, 0, len);
  87. return len == offset + length;
  88. }
  89. #endregion
  90. #region Internal Methods
  91. internal static async Task<byte[]> AppendAsync(this ushort code, string reason)
  92. {
  93. using (var buffer = new MemoryStream())
  94. {
  95. var tmp = code.ToByteArrayInternally(ByteOrder.Big);
  96. await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false);
  97. if (reason != null && reason.Length > 0)
  98. {
  99. tmp = Encoding.UTF8.GetBytes(reason);
  100. await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false);
  101. }
  102. return buffer.ToArray();
  103. }
  104. }
  105. internal static string CheckIfClosable(this WebSocketState state)
  106. {
  107. return state == WebSocketState.CloseSent
  108. ? "While closing the WebSocket connection."
  109. : state == WebSocketState.Closed
  110. ? "The WebSocket connection has already been closed."
  111. : null;
  112. }
  113. internal static string CheckIfOpen(this WebSocketState state)
  114. {
  115. return state == WebSocketState.Connecting
  116. ? "A WebSocket connection isn't established."
  117. : state == WebSocketState.CloseSent
  118. ? "While closing the WebSocket connection."
  119. : state == WebSocketState.Closed
  120. ? "The WebSocket connection has already been closed."
  121. : null;
  122. }
  123. internal static string CheckIfValidControlData(this byte[] data, string paramName)
  124. {
  125. return data.Length > 125
  126. ? string.Format("'{0}' length must be less.", paramName)
  127. : null;
  128. }
  129. internal static Stream Compress(this Stream stream, CompressionMethod method)
  130. {
  131. return method == CompressionMethod.Deflate
  132. ? stream.compress()
  133. : stream;
  134. }
  135. internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
  136. {
  137. foreach (T elm in source)
  138. if (condition(elm))
  139. return true;
  140. return false;
  141. }
  142. internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition)
  143. {
  144. var readLen = 0;
  145. var bufferLen = 256;
  146. var buffer = new byte[bufferLen];
  147. while ((readLen = src.Read(buffer, 0, bufferLen)) > 0)
  148. {
  149. dest.Write(buffer, 0, readLen);
  150. }
  151. if (setDefaultPosition)
  152. dest.Position = 0;
  153. }
  154. internal static byte[] Decompress(this byte[] value, CompressionMethod method)
  155. {
  156. return method == CompressionMethod.Deflate
  157. ? value.decompress()
  158. : value;
  159. }
  160. internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
  161. {
  162. return method == CompressionMethod.Deflate
  163. ? stream.decompressToArray()
  164. : stream.ToByteArray();
  165. }
  166. /// <summary>
  167. /// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,
  168. /// and invokes the specified Action&lt;int&gt; delegate at the same time.
  169. /// </summary>
  170. /// <returns>
  171. /// <c>true</c> if <paramref name="value"/> equals <paramref name="c"/>;
  172. /// otherwise, <c>false</c>.
  173. /// </returns>
  174. /// <param name="value">
  175. /// An <see cref="int"/> to compare.
  176. /// </param>
  177. /// <param name="c">
  178. /// A <see cref="char"/> to compare.
  179. /// </param>
  180. /// <param name="action">
  181. /// An Action&lt;int&gt; delegate that references the method(s) called at
  182. /// the same time as comparing. An <see cref="int"/> parameter to pass to
  183. /// the method(s) is <paramref name="value"/>.
  184. /// </param>
  185. /// <exception cref="ArgumentOutOfRangeException">
  186. /// <paramref name="value"/> isn't between 0 and 255.
  187. /// </exception>
  188. internal static bool EqualsWith(this int value, char c, Action<int> action)
  189. {
  190. if (value < 0 || value > 255)
  191. throw new ArgumentOutOfRangeException(nameof(value));
  192. action(value);
  193. return value == c - 0;
  194. }
  195. internal static string GetMessage(this CloseStatusCode code)
  196. {
  197. return code == CloseStatusCode.ProtocolError
  198. ? "A WebSocket protocol error has occurred."
  199. : code == CloseStatusCode.IncorrectData
  200. ? "An incorrect data has been received."
  201. : code == CloseStatusCode.Abnormal
  202. ? "An exception has occurred."
  203. : code == CloseStatusCode.InconsistentData
  204. ? "An inconsistent data has been received."
  205. : code == CloseStatusCode.PolicyViolation
  206. ? "A policy violation has occurred."
  207. : code == CloseStatusCode.TooBig
  208. ? "A too big data has been received."
  209. : code == CloseStatusCode.IgnoreExtension
  210. ? "WebSocket client did not receive expected extension(s)."
  211. : code == CloseStatusCode.ServerError
  212. ? "WebSocket server got an internal error."
  213. : code == CloseStatusCode.TlsHandshakeFailure
  214. ? "An error has occurred while handshaking."
  215. : string.Empty;
  216. }
  217. internal static string GetNameInternal(this string nameAndValue, string separator)
  218. {
  219. var i = nameAndValue.IndexOf(separator);
  220. return i > 0
  221. ? nameAndValue.Substring(0, i).Trim()
  222. : null;
  223. }
  224. internal static string GetValueInternal(this string nameAndValue, string separator)
  225. {
  226. var i = nameAndValue.IndexOf(separator);
  227. return i >= 0 && i < nameAndValue.Length - 1
  228. ? nameAndValue.Substring(i + 1).Trim()
  229. : null;
  230. }
  231. internal static bool IsCompressionExtension(this string value, CompressionMethod method)
  232. {
  233. return value.StartsWith(method.ToExtensionString());
  234. }
  235. internal static bool IsPortNumber(this int value)
  236. {
  237. return value > 0 && value < 65536;
  238. }
  239. internal static bool IsReserved(this ushort code)
  240. {
  241. return code == (ushort)CloseStatusCode.Undefined ||
  242. code == (ushort)CloseStatusCode.NoStatusCode ||
  243. code == (ushort)CloseStatusCode.Abnormal ||
  244. code == (ushort)CloseStatusCode.TlsHandshakeFailure;
  245. }
  246. internal static bool IsReserved(this CloseStatusCode code)
  247. {
  248. return code == CloseStatusCode.Undefined ||
  249. code == CloseStatusCode.NoStatusCode ||
  250. code == CloseStatusCode.Abnormal ||
  251. code == CloseStatusCode.TlsHandshakeFailure;
  252. }
  253. internal static bool IsText(this string value)
  254. {
  255. var len = value.Length;
  256. for (var i = 0; i < len; i++)
  257. {
  258. char c = value[i];
  259. if (c < 0x20 && !"\r\n\t".Contains(c))
  260. return false;
  261. if (c == 0x7f)
  262. return false;
  263. if (c == '\n' && ++i < len)
  264. {
  265. c = value[i];
  266. if (!" \t".Contains(c))
  267. return false;
  268. }
  269. }
  270. return true;
  271. }
  272. internal static bool IsToken(this string value)
  273. {
  274. foreach (char c in value)
  275. if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c))
  276. return false;
  277. return true;
  278. }
  279. internal static string Quote(this string value)
  280. {
  281. return value.IsToken()
  282. ? value
  283. : string.Format("\"{0}\"", value.Replace("\"", "\\\""));
  284. }
  285. internal static Task<byte[]> ReadBytesAsync(this Stream stream, int length)
  286. => stream.ReadBytesAsync(new byte[length], 0, length);
  287. internal static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength)
  288. {
  289. using (var result = new MemoryStream())
  290. {
  291. var count = length / bufferLength;
  292. var rem = (int)(length % bufferLength);
  293. var buffer = new byte[bufferLength];
  294. var end = false;
  295. for (long i = 0; i < count; i++)
  296. {
  297. if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false))
  298. {
  299. end = true;
  300. break;
  301. }
  302. }
  303. if (!end && rem > 0)
  304. {
  305. await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false);
  306. }
  307. return result.ToArray();
  308. }
  309. }
  310. internal static string RemovePrefix(this string value, params string[] prefixes)
  311. {
  312. var i = 0;
  313. foreach (var prefix in prefixes)
  314. {
  315. if (value.StartsWith(prefix))
  316. {
  317. i = prefix.Length;
  318. break;
  319. }
  320. }
  321. return i > 0
  322. ? value.Substring(i)
  323. : value;
  324. }
  325. internal static T[] Reverse<T>(this T[] array)
  326. {
  327. var len = array.Length;
  328. T[] reverse = new T[len];
  329. var end = len - 1;
  330. for (var i = 0; i <= end; i++)
  331. reverse[i] = array[end - i];
  332. return reverse;
  333. }
  334. internal static IEnumerable<string> SplitHeaderValue(
  335. this string value, params char[] separator)
  336. {
  337. var len = value.Length;
  338. var separators = new string(separator);
  339. var buffer = new StringBuilder(32);
  340. var quoted = false;
  341. var escaped = false;
  342. char c;
  343. for (var i = 0; i < len; i++)
  344. {
  345. c = value[i];
  346. if (c == '"')
  347. {
  348. if (escaped)
  349. escaped = !escaped;
  350. else
  351. quoted = !quoted;
  352. }
  353. else if (c == '\\')
  354. {
  355. if (i < len - 1 && value[i + 1] == '"')
  356. escaped = true;
  357. }
  358. else if (separators.Contains(c))
  359. {
  360. if (!quoted)
  361. {
  362. yield return buffer.ToString();
  363. buffer.Length = 0;
  364. continue;
  365. }
  366. }
  367. else
  368. {
  369. }
  370. buffer.Append(c);
  371. }
  372. if (buffer.Length > 0)
  373. yield return buffer.ToString();
  374. }
  375. internal static byte[] ToByteArray(this Stream stream)
  376. {
  377. using (var output = new MemoryStream())
  378. {
  379. stream.Position = 0;
  380. stream.CopyTo(output);
  381. return output.ToArray();
  382. }
  383. }
  384. internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order)
  385. {
  386. var bytes = BitConverter.GetBytes(value);
  387. if (!order.IsHostOrder())
  388. Array.Reverse(bytes);
  389. return bytes;
  390. }
  391. internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order)
  392. {
  393. var bytes = BitConverter.GetBytes(value);
  394. if (!order.IsHostOrder())
  395. Array.Reverse(bytes);
  396. return bytes;
  397. }
  398. internal static string ToExtensionString(
  399. this CompressionMethod method, params string[] parameters)
  400. {
  401. if (method == CompressionMethod.None)
  402. return string.Empty;
  403. var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant());
  404. if (parameters == null || parameters.Length == 0)
  405. return m;
  406. return string.Format("{0}; {1}", m, parameters.ToString("; "));
  407. }
  408. internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)
  409. {
  410. src.ToHostOrder(srcOrder);
  411. return BitConverter.ToUInt16(src, 0);
  412. }
  413. internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)
  414. {
  415. src.ToHostOrder(srcOrder);
  416. return BitConverter.ToUInt64(src, 0);
  417. }
  418. internal static string TrimEndSlash(this string value)
  419. {
  420. value = value.TrimEnd('/');
  421. return value.Length > 0
  422. ? value
  423. : "/";
  424. }
  425. internal static string Unquote(this string value)
  426. {
  427. var start = value.IndexOf('\"');
  428. var end = value.LastIndexOf('\"');
  429. if (start < end)
  430. value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\"");
  431. return value.Trim();
  432. }
  433. internal static void WriteBytes(this Stream stream, byte[] value)
  434. {
  435. using (var src = new MemoryStream(value))
  436. {
  437. src.CopyTo(stream);
  438. }
  439. }
  440. #endregion
  441. #region Public Methods
  442. /// <summary>
  443. /// Determines whether the specified <see cref="string"/> contains any of characters
  444. /// in the specified array of <see cref="char"/>.
  445. /// </summary>
  446. /// <returns>
  447. /// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
  448. /// otherwise, <c>false</c>.
  449. /// </returns>
  450. /// <param name="value">
  451. /// A <see cref="string"/> to test.
  452. /// </param>
  453. /// <param name="chars">
  454. /// An array of <see cref="char"/> that contains characters to find.
  455. /// </param>
  456. public static bool Contains(this string value, params char[] chars)
  457. {
  458. return chars == null || chars.Length == 0
  459. ? true
  460. : value == null || value.Length == 0
  461. ? false
  462. : value.IndexOfAny(chars) != -1;
  463. }
  464. /// <summary>
  465. /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
  466. /// with the specified <paramref name="name"/>.
  467. /// </summary>
  468. /// <returns>
  469. /// <c>true</c> if <paramref name="collection"/> contains the entry
  470. /// with <paramref name="name"/>; otherwise, <c>false</c>.
  471. /// </returns>
  472. /// <param name="collection">
  473. /// A <see cref="QueryParamCollection"/> to test.
  474. /// </param>
  475. /// <param name="name">
  476. /// A <see cref="string"/> that represents the key of the entry to find.
  477. /// </param>
  478. public static bool Contains(this QueryParamCollection collection, string name)
  479. {
  480. return collection == null || collection.Count == 0
  481. ? false
  482. : collection[name] != null;
  483. }
  484. /// <summary>
  485. /// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
  486. /// with the specified both <paramref name="name"/> and <paramref name="value"/>.
  487. /// </summary>
  488. /// <returns>
  489. /// <c>true</c> if <paramref name="collection"/> contains the entry
  490. /// with both <paramref name="name"/> and <paramref name="value"/>;
  491. /// otherwise, <c>false</c>.
  492. /// </returns>
  493. /// <param name="collection">
  494. /// A <see cref="QueryParamCollection"/> to test.
  495. /// </param>
  496. /// <param name="name">
  497. /// A <see cref="string"/> that represents the key of the entry to find.
  498. /// </param>
  499. /// <param name="value">
  500. /// A <see cref="string"/> that represents the value of the entry to find.
  501. /// </param>
  502. public static bool Contains(this QueryParamCollection collection, string name, string value)
  503. {
  504. if (collection == null || collection.Count == 0)
  505. return false;
  506. var values = collection[name];
  507. if (values == null)
  508. return false;
  509. foreach (var v in values.Split(','))
  510. if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
  511. return true;
  512. return false;
  513. }
  514. /// <summary>
  515. /// Emits the specified <c>EventHandler&lt;TEventArgs&gt;</c> delegate
  516. /// if it isn't <see langword="null"/>.
  517. /// </summary>
  518. /// <param name="eventHandler">
  519. /// An <c>EventHandler&lt;TEventArgs&gt;</c> to emit.
  520. /// </param>
  521. /// <param name="sender">
  522. /// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
  523. /// </param>
  524. /// <param name="e">
  525. /// A <c>TEventArgs</c> that represents the event data.
  526. /// </param>
  527. /// <typeparam name="TEventArgs">
  528. /// The type of the event data generated by the event.
  529. /// </typeparam>
  530. public static void Emit<TEventArgs>(
  531. this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)
  532. where TEventArgs : EventArgs
  533. {
  534. if (eventHandler != null)
  535. eventHandler(sender, e);
  536. }
  537. /// <summary>
  538. /// Gets the description of the specified HTTP status <paramref name="code"/>.
  539. /// </summary>
  540. /// <returns>
  541. /// A <see cref="string"/> that represents the description of the HTTP status code.
  542. /// </returns>
  543. /// <param name="code">
  544. /// One of <see cref="HttpStatusCode"/> enum values, indicates the HTTP status codes.
  545. /// </param>
  546. public static string GetDescription(this HttpStatusCode code)
  547. {
  548. return ((int)code).GetStatusDescription();
  549. }
  550. /// <summary>
  551. /// Gets the description of the specified HTTP status <paramref name="code"/>.
  552. /// </summary>
  553. /// <returns>
  554. /// A <see cref="string"/> that represents the description of the HTTP status code.
  555. /// </returns>
  556. /// <param name="code">
  557. /// An <see cref="int"/> that represents the HTTP status code.
  558. /// </param>
  559. public static string GetStatusDescription(this int code)
  560. {
  561. switch (code)
  562. {
  563. case 100: return "Continue";
  564. case 101: return "Switching Protocols";
  565. case 102: return "Processing";
  566. case 200: return "OK";
  567. case 201: return "Created";
  568. case 202: return "Accepted";
  569. case 203: return "Non-Authoritative Information";
  570. case 204: return "No Content";
  571. case 205: return "Reset Content";
  572. case 206: return "Partial Content";
  573. case 207: return "Multi-Status";
  574. case 300: return "Multiple Choices";
  575. case 301: return "Moved Permanently";
  576. case 302: return "Found";
  577. case 303: return "See Other";
  578. case 304: return "Not Modified";
  579. case 305: return "Use Proxy";
  580. case 307: return "Temporary Redirect";
  581. case 400: return "Bad Request";
  582. case 401: return "Unauthorized";
  583. case 402: return "Payment Required";
  584. case 403: return "Forbidden";
  585. case 404: return "Not Found";
  586. case 405: return "Method Not Allowed";
  587. case 406: return "Not Acceptable";
  588. case 407: return "Proxy Authentication Required";
  589. case 408: return "Request Timeout";
  590. case 409: return "Conflict";
  591. case 410: return "Gone";
  592. case 411: return "Length Required";
  593. case 412: return "Precondition Failed";
  594. case 413: return "Request Entity Too Large";
  595. case 414: return "Request-Uri Too Long";
  596. case 415: return "Unsupported Media Type";
  597. case 416: return "Requested Range Not Satisfiable";
  598. case 417: return "Expectation Failed";
  599. case 422: return "Unprocessable Entity";
  600. case 423: return "Locked";
  601. case 424: return "Failed Dependency";
  602. case 500: return "Internal Server Error";
  603. case 501: return "Not Implemented";
  604. case 502: return "Bad Gateway";
  605. case 503: return "Service Unavailable";
  606. case 504: return "Gateway Timeout";
  607. case 505: return "Http Version Not Supported";
  608. case 507: return "Insufficient Storage";
  609. }
  610. return string.Empty;
  611. }
  612. /// <summary>
  613. /// Determines whether the specified <see cref="ByteOrder"/> is host
  614. /// (this computer architecture) byte order.
  615. /// </summary>
  616. /// <returns>
  617. /// <c>true</c> if <paramref name="order"/> is host byte order;
  618. /// otherwise, <c>false</c>.
  619. /// </returns>
  620. /// <param name="order">
  621. /// One of the <see cref="ByteOrder"/> enum values, to test.
  622. /// </param>
  623. public static bool IsHostOrder(this ByteOrder order)
  624. {
  625. // true : !(true ^ true) or !(false ^ false)
  626. // false: !(true ^ false) or !(false ^ true)
  627. return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
  628. }
  629. /// <summary>
  630. /// Determines whether the specified <see cref="string"/> is a predefined scheme.
  631. /// </summary>
  632. /// <returns>
  633. /// <c>true</c> if <paramref name="value"/> is a predefined scheme; otherwise, <c>false</c>.
  634. /// </returns>
  635. /// <param name="value">
  636. /// A <see cref="string"/> to test.
  637. /// </param>
  638. public static bool IsPredefinedScheme(this string value)
  639. {
  640. if (value == null || value.Length < 2)
  641. return false;
  642. var c = value[0];
  643. if (c == 'h')
  644. return value == "http" || value == "https";
  645. if (c == 'w')
  646. return value == "ws" || value == "wss";
  647. if (c == 'f')
  648. return value == "file" || value == "ftp";
  649. if (c == 'n')
  650. {
  651. c = value[1];
  652. return c == 'e'
  653. ? value == "news" || value == "net.pipe" || value == "net.tcp"
  654. : value == "nntp";
  655. }
  656. return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");
  657. }
  658. /// <summary>
  659. /// Determines whether the specified <see cref="string"/> is a URI string.
  660. /// </summary>
  661. /// <returns>
  662. /// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.
  663. /// </returns>
  664. /// <param name="value">
  665. /// A <see cref="string"/> to test.
  666. /// </param>
  667. public static bool MaybeUri(this string value)
  668. {
  669. if (value == null || value.Length == 0)
  670. return false;
  671. var i = value.IndexOf(':');
  672. if (i == -1)
  673. return false;
  674. if (i >= 10)
  675. return false;
  676. return value.Substring(0, i).IsPredefinedScheme();
  677. }
  678. /// <summary>
  679. /// Retrieves a sub-array from the specified <paramref name="array"/>.
  680. /// A sub-array starts at the specified element position.
  681. /// </summary>
  682. /// <returns>
  683. /// An array of T that receives a sub-array, or an empty array of T if any problems
  684. /// with the parameters.
  685. /// </returns>
  686. /// <param name="array">
  687. /// An array of T that contains the data to retrieve a sub-array.
  688. /// </param>
  689. /// <param name="startIndex">
  690. /// An <see cref="int"/> that contains the zero-based starting position of a sub-array
  691. /// in <paramref name="array"/>.
  692. /// </param>
  693. /// <param name="length">
  694. /// An <see cref="int"/> that contains the number of elements to retrieve a sub-array.
  695. /// </param>
  696. /// <typeparam name="T">
  697. /// The type of elements in the <paramref name="array"/>.
  698. /// </typeparam>
  699. public static T[] SubArray<T>(this T[] array, int startIndex, int length)
  700. {
  701. if (array == null || array.Length == 0)
  702. return new T[0];
  703. if (startIndex < 0 || length <= 0)
  704. return new T[0];
  705. if (startIndex + length > array.Length)
  706. return new T[0];
  707. if (startIndex == 0 && array.Length == length)
  708. return array;
  709. T[] subArray = new T[length];
  710. Array.Copy(array, startIndex, subArray, 0, length);
  711. return subArray;
  712. }
  713. /// <summary>
  714. /// Converts the order of the specified array of <see cref="byte"/> to the host byte order.
  715. /// </summary>
  716. /// <returns>
  717. /// An array of <see cref="byte"/> converted from <paramref name="src"/>.
  718. /// </returns>
  719. /// <param name="src">
  720. /// An array of <see cref="byte"/> to convert.
  721. /// </param>
  722. /// <param name="srcOrder">
  723. /// One of the <see cref="ByteOrder"/> enum values, indicates the byte order of
  724. /// <paramref name="src"/>.
  725. /// </param>
  726. /// <exception cref="ArgumentNullException">
  727. /// <paramref name="src"/> is <see langword="null"/>.
  728. /// </exception>
  729. public static void ToHostOrder(this byte[] src, ByteOrder srcOrder)
  730. {
  731. if (src == null)
  732. {
  733. throw new ArgumentNullException(nameof(src));
  734. }
  735. if (src.Length > 1 && !srcOrder.IsHostOrder())
  736. {
  737. Array.Reverse(src);
  738. }
  739. }
  740. /// <summary>
  741. /// Converts the specified <paramref name="array"/> to a <see cref="string"/> that
  742. /// concatenates the each element of <paramref name="array"/> across the specified
  743. /// <paramref name="separator"/>.
  744. /// </summary>
  745. /// <returns>
  746. /// A <see cref="string"/> converted from <paramref name="array"/>,
  747. /// or <see cref="String.Empty"/> if <paramref name="array"/> is empty.
  748. /// </returns>
  749. /// <param name="array">
  750. /// An array of T to convert.
  751. /// </param>
  752. /// <param name="separator">
  753. /// A <see cref="string"/> that represents the separator string.
  754. /// </param>
  755. /// <typeparam name="T">
  756. /// The type of elements in <paramref name="array"/>.
  757. /// </typeparam>
  758. /// <exception cref="ArgumentNullException">
  759. /// <paramref name="array"/> is <see langword="null"/>.
  760. /// </exception>
  761. public static string ToString<T>(this T[] array, string separator)
  762. {
  763. if (array == null)
  764. throw new ArgumentNullException(nameof(array));
  765. var len = array.Length;
  766. if (len == 0)
  767. return string.Empty;
  768. if (separator == null)
  769. separator = string.Empty;
  770. var buff = new StringBuilder(64);
  771. (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
  772. buff.Append(array[len - 1].ToString());
  773. return buff.ToString();
  774. }
  775. /// <summary>
  776. /// Executes the specified <c>Action&lt;int&gt;</c> delegate <paramref name="n"/> times.
  777. /// </summary>
  778. /// <param name="n">
  779. /// An <see cref="int"/> is the number of times to execute.
  780. /// </param>
  781. /// <param name="action">
  782. /// An <c>Action&lt;int&gt;</c> delegate that references the method(s) to execute.
  783. /// An <see cref="int"/> parameter to pass to the method(s) is the zero-based count of
  784. /// iteration.
  785. /// </param>
  786. public static void Times(this int n, Action<int> action)
  787. {
  788. if (n > 0 && action != null)
  789. for (int i = 0; i < n; i++)
  790. action(i);
  791. }
  792. /// <summary>
  793. /// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
  794. /// </summary>
  795. /// <returns>
  796. /// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
  797. /// if <paramref name="uriString"/> isn't successfully converted.
  798. /// </returns>
  799. /// <param name="uriString">
  800. /// A <see cref="string"/> to convert.
  801. /// </param>
  802. public static Uri ToUri(this string uriString)
  803. {
  804. return Uri.TryCreate(
  805. uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res)
  806. ? res
  807. : null;
  808. }
  809. /// <summary>
  810. /// URL-decodes the specified <see cref="string"/>.
  811. /// </summary>
  812. /// <returns>
  813. /// A <see cref="string"/> that receives the decoded string, or the <paramref name="value"/>
  814. /// if it's <see langword="null"/> or empty.
  815. /// </returns>
  816. /// <param name="value">
  817. /// A <see cref="string"/> to decode.
  818. /// </param>
  819. public static string UrlDecode(this string value)
  820. {
  821. return value == null || value.Length == 0
  822. ? value
  823. : WebUtility.UrlDecode(value);
  824. }
  825. #endregion
  826. }
  827. }