build-a-url-parser / lesson-17.md
Lesson 17 · Path and normalization

Percent-encoding normalization

A percent-escape that encodes an unreserved character is redundant, and hex digits can be either case, so normalization canonicalizes both. Today you add percent-encoding normalization to Normalize.

The goal

Normalize percent-escapes by decoding unreserved ones and uppercasing the rest.

Start here - the target
TO DO
Scenario: Canonicalizing percent-escapes
Giventhe strings "%7Euser" and "a%2fb"
Whenpercent-encoding normalization is applied
Then"%7Euser" becomes "~user", because %7E encodes the unreserved tilde
And"a%2fb" becomes "a%2Fb", keeping the reserved slash encoded but uppercasing the hex digits
Background

Two more percent-encoding facts make otherwise-identical URIs look different. First, an unreserved character should never be escaped: %7E is a tilde, and since ~ is unreserved, %7E and ~ are equivalent - normalization decodes these redundant escapes to their literal character. Second, the hex digits are case-insensitive, so %2f and %2F are the same byte; normalization uppercases the digits of any escape it keeps. Together these give every escaped string one canonical spelling.

The rule per escape is a clean fork: read the two hex digits, and if the byte they encode is unreserved, replace the whole triplet with that literal character; otherwise leave it escaped but with uppercase digits. So %7Euser becomes ~user, while a%2fb stays a%2Fb because a slash is reserved and must remain encoded - only its digit case is fixed. Apply this to the path, query, and fragment, which are the components that carry percent-escapes. Folding it into Normalize alongside case folding means a normalized URI has both canonical case and canonical escapes; one more rule and the normalization is complete.

Make it work
// for each "%XX": if the byte is unreserved, decode it to
// the literal char; otherwise keep "%" + UPPERCASE hex.
func normalizePercent(s string) string { /* walk, rewrite each escape */ }
CheckpointDONE
Normalize now canonicalizes percent-escapes in the path, query, and fragment. Commit and stop here.