using System; using System.Collections.Generic; using System.Text; namespace Shadowsocks { /// /// HttpServerUtility URL Token のエンコード及びデコードを行うクラス。 /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokenencode /// https://docs.microsoft.com/ja-jp/dotnet/api/system.web.httpserverutility.urltokendecode /// /// /// HttpServerUtility URL Token 形式は、パディング無し base64url にパディング数を文字として追記した文字列です。 /// 例えば、0x00AA2 になります。 /// public static class HttpServerUtilityUrlToken { #if NETSTANDARD2_0 private static readonly byte[] EmptyBytes = Array.Empty(); #else private static readonly byte[] EmptyBytes = new byte[0]; #endif /// /// 配列を HttpServerUtility URL Token にエンコードします。 /// /// エンコード対象の 配列。 /// HttpServerUtility URL Token エンコード文字列。 の長さが 0 の場合は空文字列を返します。 /// is null. public static string Encode(byte[] bytes) { if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } return Encode(bytes, 0, bytes.Length); } /// /// 配列を HttpServerUtility URL Token にエンコードします。 /// /// エンコード対象の 配列。 /// エンコードの開始位置を示すオフセット。 /// エンコード対象の要素の数。 /// HttpServerUtility URL Token エンコード文字列。0 の場合は空文字列を返します。 /// is null. /// /// または が負の値です。 /// または を加算した値が の長さを超えています。 /// public static string Encode(byte[] bytes, int offset, int length) { if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } var encoded = Encode(bytes, offset, length, padding: false); if (encoded.Length == 0) { return ""; } var paddingLen = unchecked(~encoded.Length + 1) & 0b11; encoded += paddingLen; return encoded; } /// /// 配列を base64url にエンコードします。 /// /// エンコード対象の 配列。 /// エンコードの開始位置を示すオフセット。 /// エンコード対象の要素の数。 /// パディングをする場合は true、それ以外は false。既定値は false。 /// base64url エンコード文字列。 /// is null. /// /// または が負の値です。 /// または を加算した値が の長さを超えています。 /// public static string Encode(byte[] bytes, int offset, int length, bool padding = false) { var encoded = Convert.ToBase64String(bytes, offset, length); if (!padding) { encoded = encoded.TrimEnd('='); } return encoded .Replace('+', '-') .Replace('/', '_') ; } /// /// HttpServerUtility URL Token 文字列を 配列にデコードします。 /// /// HttpServerUtility URL Token にエンコードされた文字列。 /// デコード後の 配列。 が空文字列の場合は の空配列を返します。 /// is null. /// が HttpServerUtility URL Token 文字列ではありません。 public static byte[] Decode(string encoded) { if (encoded == null) { throw new ArgumentNullException(nameof(encoded)); } if (!TryDecode(encoded, out var result)) { throw new FormatException("HttpServerUtility URL Token 文字列ではありません。"); } return result; } /// /// HttpServerUtility URL Token でエンコードされた文字列をデコードします。 /// /// HttpServerUtility URL Token エンコードされた文字列。 /// デコード後の 配列。 が空文字列の場合は の空配列が設定されます。失敗した場合は null。 /// デコードに成功した場合は true、それ以外は false public static bool TryDecode(string encoded, out byte[] result) { if (encoded == null) { goto Failure; } if (encoded.Length == 0) { result = EmptyBytes; return true; } var paddingLen = encoded[encoded.Length - 1] - '0'; if (paddingLen < 0 || paddingLen > 3) { goto Failure; } var base64Str = encoded .Substring(0, encoded.Length - 1) .Replace('-', '+') .Replace('_', '/'); if (paddingLen > 0) { base64Str += new string('=', paddingLen); } try { result = Convert.FromBase64String(base64Str); return true; } catch (FormatException) { goto Failure; } Failure: result = null; return false; } } }