PageRenderTime 38ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/WCFWebApi/src/Microsoft.Server.Common/Microsoft/Server/Common/UrlUtility.cs

#
C# | 655 lines | 510 code | 102 blank | 43 comment | 129 complexity | 09e28ef6ac3ab9202103fcb394d6bbca MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. //------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //------------------------------------------------------------
  4. namespace Microsoft.Server.Common
  5. {
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Specialized;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Runtime.Serialization;
  11. using System.Text;
  12. // copied from System.Web.HttpUtility code (renamed here) to remove dependency on System.Web.dll
  13. public static class UrlUtility
  14. {
  15. // Query string parsing support
  16. public static NameValueCollection ParseQueryString(string query)
  17. {
  18. return ParseQueryString(query, Encoding.UTF8);
  19. }
  20. public static NameValueCollection ParseQueryString(string query, Encoding encoding)
  21. {
  22. if (query == null)
  23. {
  24. throw Fx.Exception.ArgumentNull("query");
  25. }
  26. if (encoding == null)
  27. {
  28. throw Fx.Exception.ArgumentNull("encoding");
  29. }
  30. if (query.Length > 0 && query[0] == '?')
  31. {
  32. query = query.Substring(1);
  33. }
  34. return new HttpValueCollection(query, encoding);
  35. }
  36. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  37. public static string UrlEncode(string value)
  38. {
  39. if (value == null)
  40. {
  41. return null;
  42. }
  43. return UrlEncode(value, Encoding.UTF8);
  44. }
  45. // URL encodes a path portion of a URL string and returns the encoded string.
  46. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  47. public static string UrlPathEncode(string value)
  48. {
  49. if (value == null)
  50. {
  51. return null;
  52. }
  53. // recurse in case there is a query string
  54. int i = value.IndexOf('?');
  55. if (i >= 0)
  56. {
  57. return UrlPathEncode(value.Substring(0, i)) + value.Substring(i);
  58. }
  59. // encode DBCS characters and spaces only
  60. return UrlEncodeSpaces(UrlEncodeNonAscii(value, Encoding.UTF8));
  61. }
  62. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  63. public static string UrlEncode(string value, Encoding encoding)
  64. {
  65. if (value == null)
  66. {
  67. return null;
  68. }
  69. return Encoding.ASCII.GetString(UrlEncodeToBytes(value, encoding));
  70. }
  71. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  72. public static string UrlEncodeUnicode(string value)
  73. {
  74. if (value == null)
  75. {
  76. return null;
  77. }
  78. return UrlEncodeUnicodeStringToStringInternal(value, false);
  79. }
  80. // Helper to encode the non-ASCII url characters only
  81. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  82. public static string UrlEncodeNonAscii(string value, Encoding encoding)
  83. {
  84. if (string.IsNullOrEmpty(value))
  85. {
  86. return value;
  87. }
  88. if (encoding == null)
  89. {
  90. encoding = Encoding.UTF8;
  91. }
  92. byte[] bytes = encoding.GetBytes(value);
  93. bytes = UrlEncodeBytesToBytesInternalNonAscii(bytes, 0, bytes.Length, false);
  94. return Encoding.ASCII.GetString(bytes);
  95. }
  96. // Helper to encode spaces only
  97. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  98. public static string UrlEncodeSpaces(string value)
  99. {
  100. if (value != null && value.IndexOf(' ') >= 0)
  101. {
  102. value = value.Replace(" ", "%20");
  103. }
  104. return value;
  105. }
  106. public static byte[] UrlEncodeToBytes(string value, Encoding encoding)
  107. {
  108. if (value == null)
  109. {
  110. return null;
  111. }
  112. byte[] bytes = encoding.GetBytes(value);
  113. return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
  114. }
  115. [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "Ported from WCF")]
  116. public static string UrlDecode(string value, Encoding encoding)
  117. {
  118. if (value == null)
  119. {
  120. return null;
  121. }
  122. return UrlDecodeStringFromStringInternal(value, encoding);
  123. }
  124. // Implementation for encoding
  125. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", Justification = "Ported from WCF")]
  126. public static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
  127. {
  128. int countSpaces = 0;
  129. int countUnsafe = 0;
  130. // count them first
  131. for (int i = 0; i < count; i++)
  132. {
  133. char ch = (char)bytes[offset + i];
  134. if (ch == ' ')
  135. {
  136. countSpaces++;
  137. }
  138. else if (!IsSafe(ch))
  139. {
  140. countUnsafe++;
  141. }
  142. }
  143. // nothing to expand?
  144. if (!alwaysCreateReturnValue && countSpaces == 0 && countUnsafe == 0)
  145. {
  146. return bytes;
  147. }
  148. // expand not 'safe' characters into %XX, spaces to +s
  149. byte[] expandedBytes = new byte[count + (countUnsafe * 2)];
  150. int pos = 0;
  151. for (int i = 0; i < count; i++)
  152. {
  153. byte b = bytes[offset + i];
  154. char ch = (char)b;
  155. if (IsSafe(ch))
  156. {
  157. expandedBytes[pos++] = b;
  158. }
  159. else if (ch == ' ')
  160. {
  161. expandedBytes[pos++] = (byte)'+';
  162. }
  163. else
  164. {
  165. expandedBytes[pos++] = (byte)'%';
  166. expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
  167. expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
  168. }
  169. }
  170. return expandedBytes;
  171. }
  172. public static bool IsNonAsciiByte(byte value)
  173. {
  174. return value >= 0x7F || value < 0x20;
  175. }
  176. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", Justification = "Ported from WCF")]
  177. public static byte[] UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
  178. {
  179. int countNonAscii = 0;
  180. // count them first
  181. for (int i = 0; i < count; i++)
  182. {
  183. if (IsNonAsciiByte(bytes[offset + i]))
  184. {
  185. countNonAscii++;
  186. }
  187. }
  188. // nothing to expand?
  189. if (!alwaysCreateReturnValue && countNonAscii == 0)
  190. {
  191. return bytes;
  192. }
  193. // expand not 'safe' characters into %XX, spaces to +s
  194. byte[] expandedBytes = new byte[count + (countNonAscii * 2)];
  195. int pos = 0;
  196. for (int i = 0; i < count; i++)
  197. {
  198. byte b = bytes[offset + i];
  199. if (IsNonAsciiByte(b))
  200. {
  201. expandedBytes[pos++] = (byte)'%';
  202. expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
  203. expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
  204. }
  205. else
  206. {
  207. expandedBytes[pos++] = b;
  208. }
  209. }
  210. return expandedBytes;
  211. }
  212. private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)
  213. {
  214. int l = s.Length;
  215. StringBuilder sb = new StringBuilder(l);
  216. for (int i = 0; i < l; i++)
  217. {
  218. char ch = s[i];
  219. if ((ch & 0xff80) == 0)
  220. { // 7 bit?
  221. if (ignoreAscii || IsSafe(ch))
  222. {
  223. sb.Append(ch);
  224. }
  225. else if (ch == ' ')
  226. {
  227. sb.Append('+');
  228. }
  229. else
  230. {
  231. sb.Append('%');
  232. sb.Append(IntToHex((ch >> 4) & 0xf));
  233. sb.Append(IntToHex(ch & 0xf));
  234. }
  235. }
  236. else
  237. { // arbitrary Unicode?
  238. sb.Append("%u");
  239. sb.Append(IntToHex((ch >> 12) & 0xf));
  240. sb.Append(IntToHex((ch >> 8) & 0xf));
  241. sb.Append(IntToHex((ch >> 4) & 0xf));
  242. sb.Append(IntToHex(ch & 0xf));
  243. }
  244. }
  245. return sb.ToString();
  246. }
  247. private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
  248. {
  249. int count = s.Length;
  250. UrlDecoder helper = new UrlDecoder(count, e);
  251. // go through the string's chars collapsing %XX and %uXXXX and
  252. // appending each char as char, with exception of %XX constructs
  253. // that are appended as bytes
  254. for (int pos = 0; pos < count; pos++)
  255. {
  256. char ch = s[pos];
  257. if (ch == '+')
  258. {
  259. ch = ' ';
  260. }
  261. else if (ch == '%' && pos < count - 2)
  262. {
  263. if (s[pos + 1] == 'u' && pos < count - 5)
  264. {
  265. int h1 = HexToInt(s[pos + 2]);
  266. int h2 = HexToInt(s[pos + 3]);
  267. int h3 = HexToInt(s[pos + 4]);
  268. int h4 = HexToInt(s[pos + 5]);
  269. if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
  270. { // valid 4 hex chars
  271. ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
  272. pos += 5;
  273. // only add as char
  274. helper.AddChar(ch);
  275. continue;
  276. }
  277. }
  278. else
  279. {
  280. int h1 = HexToInt(s[pos + 1]);
  281. int h2 = HexToInt(s[pos + 2]);
  282. if (h1 >= 0 && h2 >= 0)
  283. { // valid 2 hex chars
  284. byte b = (byte)((h1 << 4) | h2);
  285. pos += 2;
  286. // don't add as char
  287. helper.AddByte(b);
  288. continue;
  289. }
  290. }
  291. }
  292. if ((ch & 0xFF80) == 0)
  293. {
  294. helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
  295. }
  296. else
  297. {
  298. helper.AddChar(ch);
  299. }
  300. }
  301. return helper.GetString();
  302. }
  303. // Private helpers for URL encoding/decoding
  304. private static int HexToInt(char h)
  305. {
  306. return (h >= '0' && h <= '9') ? h - '0' :
  307. (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
  308. (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
  309. -1;
  310. }
  311. private static char IntToHex(int n)
  312. {
  313. // WCF CHANGE: CHANGED FROM Debug.Assert() to Fx.Assert()
  314. Fx.Assert(n < 0x10, "n < 0x10");
  315. if (n <= 9)
  316. {
  317. return (char)(n + (int)'0');
  318. }
  319. else
  320. {
  321. return (char)(n - 10 + (int)'a');
  322. }
  323. }
  324. // Set of safe chars, from RFC 1738.4 minus '+'
  325. private static bool IsSafe(char ch)
  326. {
  327. if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))
  328. {
  329. return true;
  330. }
  331. switch (ch)
  332. {
  333. case '-':
  334. case '_':
  335. case '.':
  336. case '!':
  337. case '*':
  338. case '\'':
  339. case '(':
  340. case ')':
  341. return true;
  342. }
  343. return false;
  344. }
  345. // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes
  346. internal class UrlDecoder
  347. {
  348. int charBufferSize;
  349. // Accumulate characters in a special array
  350. int numChars;
  351. char[] charBuffer;
  352. // Accumulate bytes for decoding into characters in a special array
  353. int numBytes;
  354. byte[] byteBuffer;
  355. // Encoding to convert chars to bytes
  356. Encoding charEncoding;
  357. internal UrlDecoder(int bufferSize, Encoding encoding)
  358. {
  359. this.charBufferSize = bufferSize;
  360. this.charEncoding = encoding;
  361. // byte buffer created on demand
  362. this.charBuffer = new char[bufferSize];
  363. }
  364. internal void FlushBytes()
  365. {
  366. if (this.numBytes > 0)
  367. {
  368. this.numChars += this.charEncoding.GetChars(this.byteBuffer, 0, this.numBytes, this.charBuffer, this.numChars);
  369. this.numBytes = 0;
  370. }
  371. }
  372. internal void AddChar(char ch)
  373. {
  374. if (this.numBytes > 0)
  375. {
  376. this.FlushBytes();
  377. }
  378. this.charBuffer[this.numChars++] = ch;
  379. }
  380. internal void AddByte(byte b)
  381. {
  382. //// if there are no pending bytes treat 7 bit bytes as characters
  383. //// this optimization is temp disable as it doesn't work for some encodings
  384. ////if (_numBytes == 0 && ((b & 0x80) == 0)) {
  385. //// AddChar((char)b);
  386. ////}
  387. ////else
  388. ////
  389. ////{
  390. if (this.byteBuffer == null)
  391. {
  392. this.byteBuffer = new byte[this.charBufferSize];
  393. }
  394. this.byteBuffer[this.numBytes++] = b;
  395. ////}
  396. }
  397. internal string GetString()
  398. {
  399. if (this.numBytes > 0)
  400. {
  401. this.FlushBytes();
  402. }
  403. if (this.numChars > 0)
  404. {
  405. return new string(this.charBuffer, 0, this.numChars);
  406. }
  407. else
  408. {
  409. return string.Empty;
  410. }
  411. }
  412. }
  413. [Serializable]
  414. internal class HttpValueCollection : NameValueCollection
  415. {
  416. [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Ported from WCF")]
  417. internal HttpValueCollection(string str, Encoding encoding)
  418. : base(StringComparer.OrdinalIgnoreCase)
  419. {
  420. if (!string.IsNullOrEmpty(str))
  421. {
  422. this.FillFromString(str, true, encoding);
  423. }
  424. IsReadOnly = false;
  425. }
  426. protected HttpValueCollection(SerializationInfo info, StreamingContext context)
  427. : base(info, context)
  428. {
  429. }
  430. public override string ToString()
  431. {
  432. return this.ToString(true, null);
  433. }
  434. internal void FillFromString(string s, bool urlencoded, Encoding encoding)
  435. {
  436. int l = (s != null) ? s.Length : 0;
  437. int i = 0;
  438. while (i < l)
  439. {
  440. // find next & while noting first = on the way (and if there are more)
  441. int si = i;
  442. int ti = -1;
  443. while (i < l)
  444. {
  445. char ch = s[i];
  446. if (ch == '=')
  447. {
  448. if (ti < 0)
  449. {
  450. ti = i;
  451. }
  452. }
  453. else if (ch == '&')
  454. {
  455. break;
  456. }
  457. i++;
  458. }
  459. // extract the name / value pair
  460. string name = null;
  461. string value = null;
  462. if (ti >= 0)
  463. {
  464. name = s.Substring(si, ti - si);
  465. value = s.Substring(ti + 1, i - ti - 1);
  466. }
  467. else
  468. {
  469. value = s.Substring(si, i - si);
  470. }
  471. // add name / value pair to the collection
  472. if (urlencoded)
  473. {
  474. this.Add(
  475. UrlUtility.UrlDecode(name, encoding),
  476. UrlUtility.UrlDecode(value, encoding));
  477. }
  478. else
  479. {
  480. this.Add(name, value);
  481. }
  482. // trailing '&'
  483. if (i == l - 1 && s[i] == '&')
  484. {
  485. this.Add(null, string.Empty);
  486. }
  487. i++;
  488. }
  489. }
  490. string ToString(bool urlencoded, IDictionary excludeKeys)
  491. {
  492. int n = Count;
  493. if (n == 0)
  494. {
  495. return string.Empty;
  496. }
  497. StringBuilder s = new StringBuilder();
  498. string key, keyPrefix, item;
  499. for (int i = 0; i < n; i++)
  500. {
  501. key = GetKey(i);
  502. if (excludeKeys != null && key != null && excludeKeys[key] != null)
  503. {
  504. continue;
  505. }
  506. if (urlencoded)
  507. {
  508. key = UrlUtility.UrlEncodeUnicode(key);
  509. }
  510. keyPrefix = (!string.IsNullOrEmpty(key)) ? (key + "=") : string.Empty;
  511. ArrayList values = (ArrayList)BaseGet(i);
  512. int numValues = (values != null) ? values.Count : 0;
  513. if (s.Length > 0)
  514. {
  515. s.Append('&');
  516. }
  517. if (numValues == 1)
  518. {
  519. s.Append(keyPrefix);
  520. item = (string)values[0];
  521. if (urlencoded)
  522. {
  523. item = UrlUtility.UrlEncodeUnicode(item);
  524. }
  525. s.Append(item);
  526. }
  527. else if (numValues == 0)
  528. {
  529. s.Append(keyPrefix);
  530. }
  531. else
  532. {
  533. for (int j = 0; j < numValues; j++)
  534. {
  535. if (j > 0)
  536. {
  537. s.Append('&');
  538. }
  539. s.Append(keyPrefix);
  540. item = (string)values[j];
  541. if (urlencoded)
  542. {
  543. item = UrlUtility.UrlEncodeUnicode(item);
  544. }
  545. s.Append(item);
  546. }
  547. }
  548. }
  549. return s.ToString();
  550. }
  551. }
  552. }
  553. }