URL Encode / Decode — C# source
Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.
This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// url-encode — percent encode/decode, component (encodeURIComponent) and full-URI (encodeURI) scope.
// Language: C# (.NET 8, standard library only). Port of src/tools/UrlEncodeTool.tsx — same
// contract as this dir's go.go: each byte outside the safe set becomes %XX (uppercase hex);
// component keeps A-Za-z0-9-_.!~*'(), full URI also keeps ;,/?:@&=+$#; decode is strict (bad %
// pair, invalid UTF-8 -> error) and full-URI scope leaves encoded reserved bytes intact.
using System.Text;
static class UrlEncode
{
const string Hex = "0123456789ABCDEF";
const string Bad = "malformed URI sequence";
static bool CompSafe(byte c) =>
(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|| "-_.!~*'()".Contains((char)c);
static bool UriExtra(byte c) => ";,/?:@&=+$#".Contains((char)c);
public static string Encode(string s, bool fullUri)
{
var b = new StringBuilder(s.Length * 3);
foreach (byte c in Encoding.UTF8.GetBytes(s))
{
if (CompSafe(c) || (fullUri && UriExtra(c))) b.Append((char)c);
else b.Append('%').Append(Hex[c >> 4]).Append(Hex[c & 0xf]);
}
return b.ToString();
}
static int Unhex(char c) =>
c is >= '0' and <= '9' ? c - '0'
: c is >= 'a' and <= 'f' ? c - 'a' + 10
: c is >= 'A' and <= 'F' ? c - 'A' + 10
: -1;
public static (string Output, string? Error) Decode(string s, bool fullUri)
{
var bytes = new List<byte>(s.Length);
for (int i = 0; i < s.Length;)
{
if (s[i] != '%') { bytes.Add((byte)s[i++]); continue; }
if (i + 2 >= s.Length) return ("", Bad); // truncated tail
int hi = Unhex(s[i + 1]), lo = Unhex(s[i + 2]);
if (hi < 0 || lo < 0) return ("", Bad); // bad hex digit
byte c = (byte)(hi << 4 | lo);
if (fullUri && c < 0x80 && UriExtra(c)) // decodeURI keeps encoded reserved
bytes.AddRange(new[] { (byte)'%', (byte)s[i + 1], (byte)s[i + 2] });
else
bytes.Add(c);
i += 3;
}
try
{
// throwOnInvalidBytes -> strict decoder: overlong/surrogate/truncated UTF-8 throws
var utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
return (utf8.GetString(bytes.ToArray()), null);
}
catch (DecoderFallbackException) { return ("", Bad); }
}
static void Main()
{
Console.WriteLine($"enc: {Encode("hello world & café", fullUri: false)}");
Console.WriteLine($"uri: {Encode("hello world & café", fullUri: true)}");
var r = Decode("hello%20world%20%26%20caf%C3%A9", fullUri: false);
Console.WriteLine($"dec: {r.Error ?? r.Output}");
r = Decode("a%2Fb%3Fc%2Cd", fullUri: true); // reserved stay escaped in decodeURI
Console.WriteLine($"duri: {r.Error ?? r.Output}");
r = Decode("a%2Fb%3Fc%2Cd", fullUri: false); // ...but decode in component scope
Console.WriteLine($"dcmp: {r.Error ?? r.Output}");
r = Decode("100%", fullUri: false);
Console.WriteLine($"bad: {r.Error ?? r.Output}");
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →