01 · THE IDEAREAD → TRY → LEARN
Why escaping matters
HTML gives a few characters special meaning. A < can start a tag, & can start an entity, and quotes can end an attribute value. If you insert user-supplied text into a page without changing those characters, the browser may treat that text as markup instead of as plain text. That is the root of cross-site scripting (XSS) bugs.
Escaping solves this by replacing each special character with an entity that the browser displays as the original character. The text stays visible, but it can no longer change the structure of the document.
In this lesson our function only returns an escaped string. It does not render HTML, and it does not try to decide which tags are acceptable.
The tool: htmlspecialchars
PHP's htmlspecialchars() converts five characters:
&becomes&"becomes"when double quotes are converted'becomes'underENT_QUOTESwith HTML 4.01 rules<becomes<>becomes>
The signature has four parameters: the string, a flags bitmask, an encoding and a double_encode switch. Since PHP 8.1 the default flags are ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401. Even so, writing the flags and encoding yourself makes the intent obvious to readers and protects you if you copy the code into a project with different expectations.
A first worked example
Here every special character in a small snippet is converted:
$name = '<b>Tom & "Jerry"</b>';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'), "\n";<b>Tom & "Jerry"</b>
The angle brackets, the ampersand and both double quotes are replaced. The browser would show the original text literally.
Quotes and attributes
Text placed inside an attribute value needs quote protection. If the attribute is wrapped in single quotes and the data contains a single quote, the value could end early. ENT_QUOTES converts both kinds of quote, so one function call works for both attribute styles.
$value = "O'Reilly";
$safe = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
echo "<input value='{$safe}'>", "\n";<input value='O'Reilly'>
The printed line is just a string; nothing here renders a page.
Encoding, invalid bytes and double encoding
The encoding argument tells PHP how to read the bytes of the string. Use 'UTF-8' when your text is UTF-8. If the input contains an invalid byte sequence, the function returns an empty string unless ENT_SUBSTITUTE or ENT_IGNORE is present. ENT_SUBSTITUTE swaps the bad bytes for the Unicode replacement character, which keeps the rest of the text. The manual discourages ENT_IGNORE because silently dropping bytes may have security implications.
By default double_encode is true, so an existing & in your input becomes &amp;. That is correct for raw text, because the user really typed those five characters and they should be displayed.
$flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401;
echo htmlspecialchars('& <', $flags, 'UTF-8'), "\n";
echo bin2hex(htmlspecialchars("atta\x80ck", $flags, 'UTF-8')), "\n";&amp; < 61747461efbfbd636b
The second line shows hexadecimal bytes: ef bf bd is the replacement character sitting where the invalid 80 byte was.
What escaping does not do
htmlspecialchars() prepares text for an HTML text or quoted-attribute context. It is not a sanitizer for rich HTML, and it does not make a value safe inside JavaScript, CSS or a URL. Escape at the point of output, for the context where the value is used.
Exercise contract
Write escape_html(string $text): string. It must return the text escaped with ENT_QUOTES, substitution of invalid bytes, HTML 4.01 rules and the 'UTF-8' encoding. Required behavior:
- Plain text and the empty string come back unchanged.
<,>,&,"and'are all converted.- An existing entity such as
&is escaped again, not preserved. - Valid non-ASCII UTF-8 characters stay as they are.
The checks compare the returned string, not printed output.
Recap
Escape untrusted text when you output it. State your flags and encoding explicitly, include ENT_QUOTES, keep double_encode on for raw text, and remember that this protects HTML text contexts only.
Functions · Arrays · Types
- Return escaped text with explicit UTF-8 and ENT_QUOTES; do not render HTML
- Implement and test a deterministic PHP 8.4 function