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 (C11). 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, truncated/overlong/surrogate
* UTF-8 -> error) and full-URI scope leaves encoded reserved bytes intact, like decodeURI. */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct result { char *out; const char *err; }; /* err == NULL on success */
#define BAD { NULL, "malformed URI sequence" }
/* component scope leaves A-Za-z0-9-_.!~*'() unescaped (ASCII-only "C" locale) */
static int comp_safe(unsigned char c) { return isalnum(c) || strchr("-_.!~*'()", c) != NULL; }
static int uri_extra(unsigned char c) { return strchr(";/?:@&=+$,#", c) != NULL; }
static char *encode(const char *s, int full_uri) /* caller frees; output <= 3x input */
{
static const char HEX[] = "0123456789ABCDEF";
char *b = malloc(3 * strlen(s) + 1);
size_t n = 0;
for (size_t i = 0; s[i]; i++) {
unsigned char c = (unsigned char)s[i];
if (comp_safe(c) || (full_uri && uri_extra(c))) b[n++] = (char)c;
else b[n++] = '%', b[n++] = HEX[c >> 4], b[n++] = HEX[c & 0xf];
}
b[n] = '\0';
return b;
}
static int unhex(char c) /* -1 on bad digit; the '\0' sentinel of a truncated tail maps there too */
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* strict UTF-8 walk — rejects truncated, overlong, surrogate and >U+10FFFF sequences */
static int utf8_ok(const unsigned char *p, size_t n)
{
size_t i = 0;
while (i < n) {
unsigned char c = p[i], lo = 0x80, hi = 0xBF; /* lo..hi bound the 2nd byte */
size_t len;
if (c < 0x80) { i++; continue; }
else if (c >= 0xC2 && c <= 0xDF) len = 2;
else if (c == 0xE0) { len = 3, lo = 0xA0; } /* E0 80..9F would be overlong */
else if ((c >= 0xE1 && c <= 0xEC) || c == 0xEE || c == 0xEF) len = 3;
else if (c == 0xED) { len = 3, hi = 0x9F; } /* ED A0..BF are surrogates */
else if (c == 0xF0) { len = 4, lo = 0x90; } /* F0 80..8F would be overlong */
else if (c >= 0xF1 && c <= 0xF3) len = 4;
else if (c == 0xF4) { len = 4, hi = 0x8F; } /* cap at U+10FFFF */
else return 0;
if (i + len > n || p[i + 1] < lo || p[i + 1] > hi) return 0;
for (size_t k = 2; k < len; k++)
if (p[i + k] < 0x80 || p[i + k] > 0xBF) return 0;
i += len;
}
return 1;
}
static struct result decode(const char *s, int full_uri)
{
unsigned char *b = malloc(strlen(s) + 1); /* decoded output never exceeds input length */
size_t n = 0;
for (size_t i = 0; s[i]; ) {
if (s[i] != '%') { b[n++] = s[i++]; continue; }
int hi = unhex(s[i + 1]), lo = unhex(s[i + 2]);
if (hi < 0 || lo < 0) { free(b); return (struct result)BAD; }
unsigned char c = (unsigned char)(hi << 4 | lo);
if (full_uri && c < 0x80 && uri_extra(c)) /* decodeURI keeps encoded reserved */
b[n++] = '%', b[n++] = s[i + 1], b[n++] = s[i + 2];
else
b[n++] = c;
i += 3;
}
if (!utf8_ok(b, n)) { free(b); return (struct result)BAD; }
b[n] = '\0';
return (struct result){ (char *)b, NULL };
}
static void show(const char *label, struct result r) { printf("%s%s\n", label, r.err ? r.err : r.out); }
int main(void)
{
printf("enc: %s\n", encode("hello world & café", 0));
printf("uri: %s\n", encode("hello world & café", 1));
show("dec: ", decode("hello%20world%20%26%20caf%C3%A9", 0));
show("duri: ", decode("a%2Fb%3Fc%2Cd", 1)); /* reserved stay escaped in decodeURI */
show("dcmp: ", decode("a%2Fb%3Fc%2Cd", 0)); /* ...but component scope decodes them */
show("bad: ", decode("100%", 0));
return 0;
}
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 →