cybersecurity · intermediate · ~25 min

HTML-escape user-provided text

Per-character expansion with bounded output.

Challenge

HTML-escape user text so it renders as literal characters instead of being interpreted as markup — the primary defense against XSS.

Task

Implement int html_escape(const char *in, char *out, size_t cap) that copies in into out, replacing each HTML-significant character with its entity:

char replacement
& &
< &lt;
> &gt;
" &quot;
' &#39;

All other characters are copied unchanged. Always NUL-terminate out.

Input

  • in: a NUL-terminated ASCII string the grader provides.
  • out: the destination buffer.
  • cap: the capacity of out in bytes (cap >= 1).

Output

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

Example

html_escape("plain", out, 256)        ->   5,   out = "plain"
html_escape("<b>&'\"a", out, 256)      ->   >0,  out = "&lt;b&gt;&amp;&#39;&quot;a"
html_escape("&&&&&", out, 5)          ->   -1   (won't fit)
html_escape("", out, 256)            ->   0,   out = ""

Edge cases

  • Empty input: returns 0, writes just the NUL.
  • Input with no special characters: copied verbatim.
  • A replacement that won't fit mid-escape: return -1.

Rules

  • Do not allocate (no malloc). Reserve one byte for the NUL when checking capacity.

Why this matters

HTML escaping is the primary defense against XSS. Hand-rolling the escape teaches you exactly which characters carry meaning in HTML.

Input format

A NUL-terminated ASCII string in, a destination out, and its capacity cap (>= 1).

Output format

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

Constraints

No malloc; cap >= 1; reserve a byte for the NUL.

Starter code

#include <stddef.h>
int html_escape(const char *in, char *out, size_t cap) { /* TODO */ return -1; }

Common mistakes

Escaping & last (your earlier & -> &amp; then gets re-escaped); using ' -> &apos; (not valid in HTML4); using bare snprintf-per-char (slow).

Edge cases to handle

Empty input. Input with no special chars. Buffer too small mid-escape.

Complexity

O(n).

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