Lesson 38 of 100
Security15 min5 checksPHP 8.5

Escape Text for HTML Output with htmlspecialchars

Learn to turn untrusted text into safe HTML text using htmlspecialchars with explicit ENT_QUOTES and UTF-8, then build a tested wrapper function.

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 &amp;
  • " becomes &quot; when double quotes are converted
  • ' becomes &#039; under ENT_QUOTES with HTML 4.01 rules
  • < becomes &lt;
  • > becomes &gt;

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:

Example
<?php
$name = '<b>Tom & "Jerry"</b>';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'), "\n";
Output
&lt;b&gt;Tom &amp; &quot;Jerry&quot;&lt;/b&gt;

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.

Example
<?php
$value = "O'Reilly";
$safe = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
echo "<input value='{$safe}'>", "\n";
Output
<input value='O&#039;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 &amp; in your input becomes &amp;amp;. That is correct for raw text, because the user really typed those five characters and they should be displayed.

Example
<?php
$flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401;
echo htmlspecialchars('&amp; <', $flags, 'UTF-8'), "\n";
echo bin2hex(htmlspecialchars("atta\x80ck", $flags, 'UTF-8')), "\n";
Output
&amp;amp; &lt;
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 &amp; 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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Return escaped text with explicit UTF-8 and ENT_QUOTES; do not render HTML
  • Implement and test a deterministic PHP 8.4 function
Your task

Make escape_html() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.