cybersecurity · beginner · ~20 min

Bidirectional %XX URL encoding with an allow-list

Per-byte allow-list encoding — the RFC 3986 unreserved set.

Challenge

Percent-encode a string for safe inclusion in a URL, leaving only the RFC 3986 "unreserved" bytes untouched.

Task

Implement int url_encode(const char *in, char *out, int cap). For each byte of in:

  • if it is in [A-Za-z0-9-._~] (the unreserved set), copy it as-is;
  • otherwise emit three bytes %XX, where XX is the byte's value as uppercase hex.

Always NUL-terminate out.

Input

  • in: a NUL-terminated string the grader provides.
  • out: the destination buffer.
  • cap: the size of out in bytes.

Output

Returns the number of bytes written, excluding the terminating NUL. Returns -1 if the encoded result (plus its NUL) would not fit in cap.

Example

url_encode("hello", out, 128)   ->   5,  out = "hello"
url_encode("a b", out, 128)     ->   5,  out = "a%20b"
url_encode("a&b=c", out, 128)   ->   9,  out = "a%26b%3Dc"
url_encode("~_-.", out, 128)    ->   4,  out = "~_-."   (all unreserved)
url_encode("", out, 128)        ->   0,  out = ""
url_encode("hello", out, 3)     ->   -1  (won't fit)

Edge cases

  • Empty input: returns 0.
  • All-unreserved input is copied verbatim; ~ is unreserved (do not encode it).
  • cap too small (including cap == 0): return -1.

Rules

  • Use uppercase hex. Encode everything outside the unreserved set; reserve a byte for the NUL.

Why this matters

Every byte that crosses an HTTP URL boundary must be %XX-encoded if it's not in the safe set. Doing it correctly is the most basic step in any URL builder.

Input format

A NUL-terminated string in, a destination buffer out, and its size cap.

Output format

Bytes written excluding the NUL; -1 if the result would not fit. out is NUL-terminated.

Constraints

Uppercase hex; encode everything except the unreserved set [A-Za-z0-9-._~].

Starter code

int url_encode(const char *in, char *out, int cap) { /* TODO */ (void)in; (void)out; (void)cap; return -1; }

Common mistakes

Encoding ~ (it's in the safe set). Lowercase hex (some validators reject).

Edge cases to handle

Empty input. All-safe input. All-unsafe input. cap=0.

Complexity

O(n).

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.