gg
Home › Blog › Escaping is not one thing: it depends on the context

Escaping is not one thing: it depends on the context

2026-08-28 · 4 min read
webdatasecurity

Percent-encoding gets talked about as though it were a single operation you either did or forgot. It is not. It is a family of operations, and which member of the family is correct depends entirely on where the string is going. The same characters can be perfectly ordinary in one position and structural in another, and a structural character left unescaped does not corrupt your data — it silently changes the shape of the URL.

The mechanism, briefly

A URL is text, and a small set of its characters carry meaning about the URL's own structure: / separates path segments, ? begins the query, & separates query pairs, = splits a pair, # begins the fragment. Percent-encoding is how you say "this one is data, not punctuation". Take the character's byte, write a %, then the byte in two hexadecimal digits. A space is byte 0x20, so it becomes %20. Non-ASCII goes through UTF-8 first, one escape per byte: é is two bytes and becomes %C3%A9.

That much is mechanical. The judgement is deciding which characters count as punctuation, and that is a property of the slot, not of the string.

One string, three destinations

Take the value q1 r&d/2026+notes. It has a space, an ampersand, a slash and a plus, which is enough to expose every disagreement worth knowing about.

  1. As a single path segment it becomes q1%20r%26d%2F2026%2Bnotes. Everything that is not a letter, a digit or one of a small unreserved set is escaped, including the slash — because inside one segment a slash is not part of the name, it would end the segment and start another.
  2. As a query value it becomes q1%20r%26d/2026%2Bnotes. The slash stays literal, because a slash in a query has no structural role and readers are used to seeing it there. The ampersand still has to go, because it would end the value. The plus still has to go, for the reason below.
  3. In a form body — the application/x-www-form-urlencoded shape a browser sends on a normal form submission — it becomes q1+r%26d%2F2026%2Bnotes. Here the space is written as a bare +, the slash is escaped again, and the plus that was in the original data becomes %2B so it cannot be read back as a space.

Three outputs, all correct, none interchangeable. Feed the second one to something expecting the third and the slash arrives as an escape it did not expect. Feed the third to something expecting the first and your space turns into a plus sign in the stored value.

The two characters that cause most of the trouble

+ is the classic. In a form body it means a space. In a path it means a plus sign. In a query string it depends on who wrote the parser: the form convention leaked into query strings decades ago and most libraries still honour it, so a literal plus in a query value that is not escaped as %2B has a good chance of arriving as a space. That is not an error anything will report. It is a value that quietly changed.

/ is the mirror image. It is a separator in a path and inert in a query, so the correct treatment reverses depending on which side of the ? you are on. A filename with a slash in it needs escaping in the path and does not need it in a query — and escaping it in the query anyway is harmless, which is why the blunt-instrument approach usually wins.

Two ways this goes wrong in practice

Escaping the whole URL instead of the parts. Run a finished URL through a component encoder and you get https%3A%2F%2F at the front: the encoder cannot tell your structural slashes from data, because by then there is no difference to see. Escape each value before you assemble the URL, never after.

Escaping twice. A % is itself a character that needs escaping, so encoding an already-encoded string turns %20 into %2520. One decode later you have %20 as literal text rather than a space. When a value arrives with visible escapes still in it, the usual cause is that it was encoded on the way in and again on the way out.

The tool

The URL Encode tool does the first of the three: it escapes everything except letters, digits and the characters - _ . ! ~ * ' ( ), which is what JavaScript's encodeURIComponent does. That makes it the right default for a single value going into any slot, because it never leaves a structural character behind. It runs in your browser, so what you paste is not uploaded. What it cannot do is tell you which slot you meant — that part is still yours, and it is the part that decides whether the result is right.

Try it free — with the steps shown

The URL Encode runs in your browser and shows exactly how it got the answer, so the method sticks.

Open URL Encode

More from the blog