Ext.cs 34 KB

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