You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
8.8 KiB

4 years ago
4 years ago
  1. using Apewer;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. namespace Apewer.Internals
  6. {
  7. internal class UrlEncoding
  8. {
  9. public static string Encode(string plain)
  10. {
  11. return TextUtility.FromBytes(Encode(TextUtility.Bytes(plain)));
  12. }
  13. public static string Decode(string escaped)
  14. {
  15. return Decode(escaped, Encoding.UTF8);
  16. }
  17. private UrlEncoding() { }
  18. #region encode static
  19. /// <summary>对 URL 编码。</summary>
  20. static byte[] Encode(byte[] bytes)
  21. {
  22. if (bytes == null) return null;
  23. if (bytes.Length < 1) return BytesUtility.Empty;
  24. var offset = 0;
  25. var count = bytes.Length;
  26. int num = 0;
  27. int num2 = 0;
  28. for (int i = 0; i < count; i++)
  29. {
  30. char c = (char)bytes[offset + i];
  31. if (c == ' ')
  32. {
  33. num++;
  34. }
  35. else if (!IsUrlSafeChar(c))
  36. {
  37. num2++;
  38. }
  39. }
  40. if (num == 0 && num2 == 0)
  41. {
  42. if (offset == 0 && bytes.Length == count)
  43. {
  44. return bytes;
  45. }
  46. byte[] array = new byte[count];
  47. Buffer.BlockCopy(bytes, offset, array, 0, count);
  48. return array;
  49. }
  50. byte[] array2 = new byte[count + num2 * 2];
  51. int num3 = 0;
  52. for (int j = 0; j < count; j++)
  53. {
  54. byte b = bytes[offset + j];
  55. char c2 = (char)b;
  56. if (IsUrlSafeChar(c2))
  57. {
  58. array2[num3++] = b;
  59. }
  60. else if (c2 == ' ')
  61. {
  62. array2[num3++] = 43;
  63. }
  64. else
  65. {
  66. array2[num3++] = 37;
  67. array2[num3++] = (byte)IntToHex(b >> 4 & 0xF);
  68. array2[num3++] = (byte)IntToHex(b & 0xF);
  69. }
  70. }
  71. return array2;
  72. }
  73. static bool IsUrlSafeChar(char ch)
  74. {
  75. //const string lower = "abcdefghijklmnopqrstuvwxyz";
  76. //const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  77. //const string numbers = "0123456789";
  78. //const string symbols = "!()*-._";
  79. if (ch >= 'a' && ch <= 'z') return true;
  80. if (ch >= 'A' && ch <= 'Z') return true;
  81. if (ch >= '0' && ch <= '9') return true;
  82. switch (ch)
  83. {
  84. case '!':
  85. case '(':
  86. case ')':
  87. case '*':
  88. case '-':
  89. case '.':
  90. case '_':
  91. return true;
  92. default:
  93. return false;
  94. }
  95. }
  96. static char IntToHex(int n)
  97. {
  98. if (n <= 9)
  99. {
  100. return (char)(n + 48);
  101. }
  102. return (char)(n - 10 + 97);
  103. }
  104. #endregion
  105. #region decode instance
  106. private int _bufferSize;
  107. private int _numChars;
  108. private char[] _charBuffer;
  109. private int _numBytes;
  110. private byte[] _byteBuffer;
  111. private Encoding _encoding;
  112. private void FlushBytes()
  113. {
  114. if (_numBytes > 0)
  115. {
  116. _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
  117. _numBytes = 0;
  118. }
  119. }
  120. internal UrlEncoding(int bufferSize, Encoding encoding)
  121. {
  122. _bufferSize = bufferSize;
  123. _encoding = encoding;
  124. _charBuffer = new char[bufferSize];
  125. }
  126. internal void AddChar(char ch)
  127. {
  128. if (_numBytes > 0)
  129. {
  130. FlushBytes();
  131. }
  132. _charBuffer[_numChars++] = ch;
  133. }
  134. internal void AddByte(byte b)
  135. {
  136. if (_byteBuffer == null)
  137. {
  138. _byteBuffer = new byte[_bufferSize];
  139. }
  140. _byteBuffer[_numBytes++] = b;
  141. }
  142. internal string GetString()
  143. {
  144. if (_numBytes > 0)
  145. {
  146. FlushBytes();
  147. }
  148. if (_numChars > 0)
  149. {
  150. return new string(_charBuffer, 0, _numChars);
  151. }
  152. return string.Empty;
  153. }
  154. #endregion
  155. #region decode static
  156. static string Decode(string value, Encoding encoding)
  157. {
  158. if (value == null)
  159. {
  160. return null;
  161. }
  162. int length = value.Length;
  163. var urlDecoder = new UrlEncoding(length, encoding);
  164. for (int i = 0; i < length; i++)
  165. {
  166. char c = value[i];
  167. switch (c)
  168. {
  169. case '+':
  170. c = ' ';
  171. goto default;
  172. case '%':
  173. if (i < length - 2)
  174. {
  175. if (value[i + 1] == 'u' && i < length - 5)
  176. {
  177. int num = HexToInt(value[i + 2]);
  178. int num2 = HexToInt(value[i + 3]);
  179. int num3 = HexToInt(value[i + 4]);
  180. int num4 = HexToInt(value[i + 5]);
  181. if (num >= 0 && num2 >= 0 && num3 >= 0 && num4 >= 0)
  182. {
  183. c = (char)(num << 12 | num2 << 8 | num3 << 4 | num4);
  184. i += 5;
  185. urlDecoder.AddChar(c);
  186. break;
  187. }
  188. }
  189. else
  190. {
  191. int num5 = HexToInt(value[i + 1]);
  192. int num6 = HexToInt(value[i + 2]);
  193. if (num5 >= 0 && num6 >= 0)
  194. {
  195. byte b = (byte)(num5 << 4 | num6);
  196. i += 2;
  197. urlDecoder.AddByte(b);
  198. break;
  199. }
  200. }
  201. }
  202. goto default;
  203. default:
  204. if ((c & 0xFF80) == 0)
  205. {
  206. urlDecoder.AddByte((byte)c);
  207. }
  208. else
  209. {
  210. urlDecoder.AddChar(c);
  211. }
  212. break;
  213. }
  214. }
  215. return ValidateString(urlDecoder.GetString());
  216. }
  217. static int HexToInt(char h)
  218. {
  219. if (h >= '0' && h <= '9')
  220. {
  221. return h - 48;
  222. }
  223. if (h >= 'a' && h <= 'f')
  224. {
  225. return h - 97 + 10;
  226. }
  227. if (h >= 'A' && h <= 'F')
  228. {
  229. return h - 65 + 10;
  230. }
  231. return -1;
  232. }
  233. static string ValidateString(string input)
  234. {
  235. var skipUtf16Validation = true;
  236. if (!skipUtf16Validation && !string.IsNullOrEmpty(input))
  237. {
  238. int num = -1;
  239. int num2 = 0;
  240. while (num2 < input.Length)
  241. {
  242. if (!char.IsSurrogate(input[num2]))
  243. {
  244. num2++;
  245. continue;
  246. }
  247. num = num2;
  248. break;
  249. }
  250. if (num < 0)
  251. {
  252. return input;
  253. }
  254. char[] array = input.ToCharArray();
  255. for (int i = num; i < array.Length; i++)
  256. {
  257. char c = array[i];
  258. if (char.IsLowSurrogate(c))
  259. {
  260. array[i] = '�';
  261. }
  262. else if (char.IsHighSurrogate(c))
  263. {
  264. if (i + 1 < array.Length && char.IsLowSurrogate(array[i + 1]))
  265. {
  266. i++;
  267. }
  268. else
  269. {
  270. array[i] = '�';
  271. }
  272. }
  273. }
  274. return new string(array);
  275. }
  276. return input;
  277. }
  278. #endregion
  279. }
  280. }