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 にパディング数を文字として追記した文字列です。
/// 例えば、0x00 は AA2 になります。
///
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;
}
}
}