I'm porting a project targeting net472
to netstandard
. The last System.Web
dependency I'm stuck with is HttpServerUtility.UrlTokenEncode(Byte[])
.
I found Microsoft.AspNetCore.WebUtilities
, which contains Base64UrlTextEncoder
and WebEncoders
, but those are not interchangeable with the UrlTokenEncode
/Decode
, as it appends / expects the number of =
padding characters at the end, e.g.:
var data = Encoding.UTF8.GetBytes("SO");
Convert.ToBase64String(data); // U08=
HttpServerUtility.UrlTokenEncode(data); // U081 - this is what's expected and
// the only thing UrlTokenDecode can handle
Base64UrlTextEncoder.Encode(data); // U08
WebEncoders.Base64UrlEncode(data); // U08
As far as I can tell, there are no other differences (I ran tests with random strings), but it also pulls in some other dependencies (Microsoft.Net.Http.Headers & Microsoft.Extensions.Primitives), that I don't really need in that project.
Is there any nuget package with a drop-in replacement? I'm thinking of implementing this myself, if not.
For those whom looks for the answer of this removed utility method and wish to migrate legacy application across, I have done some extract from M$ source.
private static string UrlTokenEncode(byte[] input)
{
if (input == null)
throw new ArgumentNullException("input");
if (input.Length < 1)
return String.Empty;
char[] base64Chars = null;
////////////////////////////////////////////////////////
// Step 1: Do a Base64 encoding
string base64Str = Convert.ToBase64String(input);
if (base64Str == null)
return null;
int endPos;
////////////////////////////////////////////////////////
// Step 2: Find how many padding chars are present in the end
for (endPos = base64Str.Length; endPos > 0; endPos--)
{
if (base64Str[endPos - 1] != '=') // Found a non-padding char!
{
break; // Stop here
}
}
////////////////////////////////////////////////////////
// Step 3: Create char array to store all non-padding chars,
// plus a char to indicate how many padding chars are needed
base64Chars = new char[endPos + 1];
base64Chars[endPos] = (char)((int)'0' + base64Str.Length - endPos); // Store a char at the end, to indicate how many padding chars are needed
////////////////////////////////////////////////////////
// Step 3: Copy in the other chars. Transform the "+" to "-", and "/" to "_"
for (int iter = 0; iter < endPos; iter++)
{
char c = base64Str[iter];
switch (c)
{
case '+':
base64Chars[iter] = '-';
break;
case '/':
base64Chars[iter] = '_';
break;
case '=':
Debug.Assert(false);
base64Chars[iter] = c;
break;
default:
base64Chars[iter] = c;
break;
}
}
return new string(base64Chars);
}
private static byte[] UrlTokenDecode(string input)
{
if (input == null)
throw new ArgumentNullException("input");
int len = input.Length;
if (len < 1)
return new byte[0];
///////////////////////////////////////////////////////////////////
// Step 1: Calculate the number of padding chars to append to this string.
// The number of padding chars to append is stored in the last char of the string.
int numPadChars = (int)input[len - 1] - (int)'0';
if (numPadChars < 0 || numPadChars > 10)
return null;
///////////////////////////////////////////////////////////////////
// Step 2: Create array to store the chars (not including the last char)
// and the padding chars
char[] base64Chars = new char[len - 1 + numPadChars];
////////////////////////////////////////////////////////
// Step 3: Copy in the chars. Transform the "-" to "+", and "*" to "/"
for (int iter = 0; iter < len - 1; iter++)
{
char c = input[iter];
switch (c)
{
case '-':
base64Chars[iter] = '+';
break;
case '_':
base64Chars[iter] = '/';
break;
default:
base64Chars[iter] = c;
break;
}
}
////////////////////////////////////////////////////////
// Step 4: Add padding chars
for (int iter = len - 1; iter < base64Chars.Length; iter++)
{
base64Chars[iter] = '=';
}
// Do the actual conversion
return Convert.FromBase64CharArray(base64Chars, 0, base64Chars.Length);
}
My KISS, solution, that works on netstandard1.6
:
public static class Utils
{
private static readonly Regex InvalidBase64UrlTokens = new Regex(
@"[^=a-z0-9]",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public static string Base64UrlTokenEncode(byte[] data)
{
var padding = 0;
var base64String = Convert.ToBase64String(data);
return InvalidBase64UrlTokens.Replace(base64String, m => {
switch (m.Value)
{
case "+": return "-";
case "=":
padding++;
return "";
default: return "_";
}
}) + padding;
}
}
Passes the following nunit tests:
[TestFixture]
public class Base64UrlTokenEncodingTests
{
private static IEnumerable<TestCaseData> CompareAgainstSystemWebImplementationCases()
{
var random = new Random(42);
for (var i = 0; i < 100; i++)
{
var bytes = new byte[i + 1];
random.NextBytes(bytes);
var name = Convert.ToBase64String(bytes);
var systemWeb = System.Web.HttpServerUtility.UrlTokenEncode(bytes);
yield return new TestCaseData(bytes).SetName(name).Returns(systemWeb);
}
}
[TestCaseSource(nameof(CompareAgainstSystemWebImplementationCases))]
public string CompareAgainstSystemWebImplementation(byte[] data) =>
Utils.Base64UrlTokenEncode(data);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With