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

HttpServerUtilityUrlToken.cs 7.3 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace Shadowsocks
  5. {
  6. /// <summary>
  7. /// HttpServerUtility URL Token のエンコード及びデコードを行うクラス。
  8. /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokenencode
  9. /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokendecode
  10. /// </summary>
  11. /// <remarks>
  12. /// HttpServerUtility URL Token 形式は、パディング無し base64url にパディング数を文字として追記した文字列です。
  13. /// 例えば、<c>0x00</c> は <c>AA2</c> になります。
  14. /// </remarks>
  15. public static class HttpServerUtilityUrlToken
  16. {
  17. #if NETSTANDARD2_0
  18. private static readonly byte[] EmptyBytes = Array.Empty<byte>();
  19. #else
  20. private static readonly byte[] EmptyBytes = new byte[0];
  21. #endif
  22. /// <summary>
  23. /// <see cref="byte"/> 配列を HttpServerUtility URL Token にエンコードします。
  24. /// </summary>
  25. /// <param name="bytes">エンコード対象の <see cref="byte"/> 配列。</param>
  26. /// <returns>HttpServerUtility URL Token エンコード文字列。<paramref name="bytes"/> の長さが <c>0</c> の場合は空文字列を返します。</returns>
  27. /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is <c>null</c>.</exception>
  28. public static string Encode(byte[] bytes)
  29. {
  30. if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); }
  31. return Encode(bytes, 0, bytes.Length);
  32. }
  33. /// <summary>
  34. /// <see cref="byte"/> 配列を HttpServerUtility URL Token にエンコードします。
  35. /// </summary>
  36. /// <param name="bytes">エンコード対象の <see cref="byte"/> 配列。</param>
  37. /// <param name="offset">エンコードの開始位置を示すオフセット。</param>
  38. /// <param name="length">エンコード対象の要素の数。</param>
  39. /// <returns>HttpServerUtility URL Token エンコード文字列。<paramref name="length"/> が <c>0</c> の場合は空文字列を返します。</returns>
  40. /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is <c>null</c>.</exception>
  41. /// <exception cref="ArgumentOutOfRangeException">
  42. /// <paramref name="offset"/> または <paramref name="length"/> が負の値です。
  43. /// または <paramref name="offset"/> と <paramref name="length"/> を加算した値が <paramref name="bytes"/> の長さを超えています。
  44. /// </exception>
  45. public static string Encode(byte[] bytes, int offset, int length)
  46. {
  47. if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); }
  48. var encoded = Encode(bytes, offset, length, padding: false);
  49. if (encoded.Length == 0) { return ""; }
  50. var paddingLen = unchecked(~encoded.Length + 1) & 0b11;
  51. encoded += paddingLen;
  52. return encoded;
  53. }
  54. /// <summary>
  55. /// <see cref="byte"/> 配列を base64url にエンコードします。
  56. /// </summary>
  57. /// <param name="bytes">エンコード対象の <see cref="byte"/> 配列。</param>
  58. /// <param name="offset">エンコードの開始位置を示すオフセット。</param>
  59. /// <param name="length">エンコード対象の要素の数。</param>
  60. /// <param name="padding">パディングをする場合は <c>true</c>、それ以外は <c>false</c>。既定値は <c>false</c>。</param>
  61. /// <returns>base64url エンコード文字列。</returns>
  62. /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is <c>null</c>.</exception>
  63. /// <exception cref="ArgumentOutOfRangeException">
  64. /// <paramref name="offset"/> または <paramref name="length"/> が負の値です。
  65. /// または <paramref name="offset"/> と <paramref name="length"/> を加算した値が <paramref name="bytes"/> の長さを超えています。
  66. /// </exception>
  67. public static string Encode(byte[] bytes, int offset, int length, bool padding = false)
  68. {
  69. var encoded = Convert.ToBase64String(bytes, offset, length);
  70. if (!padding)
  71. {
  72. encoded = encoded.TrimEnd('=');
  73. }
  74. return encoded
  75. .Replace('+', '-')
  76. .Replace('/', '_')
  77. ;
  78. }
  79. /// <summary>
  80. /// HttpServerUtility URL Token 文字列を <see cref="byte"/> 配列にデコードします。
  81. /// </summary>
  82. /// <param name="encoded">HttpServerUtility URL Token にエンコードされた文字列。</param>
  83. /// <returns>デコード後の <see cref="byte"/> 配列。<paramref name="encoded"/> が空文字列の場合は <see cref="byte"/> の空配列を返します。</returns>
  84. /// <exception cref="ArgumentNullException"><paramref name="encoded"/> is <c>null</c>.</exception>
  85. /// <exception cref="FormatException"><paramref name="encoded"/> が HttpServerUtility URL Token 文字列ではありません。</exception>
  86. public static byte[] Decode(string encoded)
  87. {
  88. if (encoded == null) { throw new ArgumentNullException(nameof(encoded)); }
  89. if (!TryDecode(encoded, out var result)) { throw new FormatException("HttpServerUtility URL Token 文字列ではありません。"); }
  90. return result;
  91. }
  92. /// <summary>
  93. /// HttpServerUtility URL Token でエンコードされた文字列をデコードします。
  94. /// </summary>
  95. /// <param name="encoded">HttpServerUtility URL Token エンコードされた文字列。</param>
  96. /// <param name="result">デコード後の <see cref="byte"/> 配列。<paramref name="encoded"/> が空文字列の場合は <see cref="byte"/> の空配列が設定されます。失敗した場合は <c>null</c>。</param>
  97. /// <returns>デコードに成功した場合は <c>true</c>、それ以外は <c>false</c>。</returns>
  98. public static bool TryDecode(string encoded, out byte[] result)
  99. {
  100. if (encoded == null) { goto Failure; }
  101. if (encoded.Length == 0)
  102. {
  103. result = EmptyBytes;
  104. return true;
  105. }
  106. var paddingLen = encoded[encoded.Length - 1] - '0';
  107. if (paddingLen < 0 || paddingLen > 3) { goto Failure; }
  108. var base64Str = encoded
  109. .Substring(0, encoded.Length - 1)
  110. .Replace('-', '+')
  111. .Replace('_', '/');
  112. if (paddingLen > 0)
  113. {
  114. base64Str += new string('=', paddingLen);
  115. }
  116. try
  117. {
  118. result = Convert.FromBase64String(base64Str);
  119. return true;
  120. }
  121. catch (FormatException) { goto Failure; }
  122. Failure:
  123. result = null;
  124. return false;
  125. }
  126. }
  127. }