Escaping by position
Whatever you type below is placed into the page in four different spots. PHV knows, at compile
time, where each <?= ?> lands in the HTML and treats the value accordingly.
1. Element content
Written as <p><?= $input ?></p>. Tags show up as text:
<script>alert("hi")</script> & 'quotes'
2. Quoted attribute
Written as title="<?= $input ?>". Quotes cannot end the attribute:
Hover this text to see the attribute value.
3. URL
The start of href only accepts a SafeUrl, made by safe_url($url),
which lets through http(s), mailto, tel and relative URLs and turns
anything else into #:
this link points to #
4. Inside <script>
A <script> only accepts a SafeJs, made by json_html($input). The
script below reads your text as a JavaScript string and counts its characters:
(the script did not run)
That <script> runs because PHV added a CSP nonce to it. A script that got in
through raw() would not get one, so the browser would block it.
What does not compile
Putting a plain string where escaping cannot protect it is an error, not a warning:
$ phv check bad.phv
error: a URL attribute cannot start with an unchecked value
--> bad.phv:4:14
|
4 | <a href="<?= $url ?>">this link</a>
| ^^^^
= note: use `safe_url($url)`, which only allows http(s), mailto, tel and relative URLs (blocks `javascript:`); `escape()` does not make a URL safe
The same goes for unquoted attributes, event handlers such as onclick, style and
plain strings inside <script>.
Source of this page
Read from disk by lib/source.phv as the page is served.
public/escape.phv
<?phv
import std.http.request;
import lib.source.sources;
$req = request();
$input = $req->query("input") ?? "<script>alert(\"hi\")</script> & 'quotes'";
$url = $req->query("url") ?? "javascript:alert(document.cookie)";
if ($input->len() > 200) {
$input = $input->slice(0, 200);
}
$body = render("views/escape.phv.html", input: $input, url: $url);
echo render(
"views/layout.phv.html",
title: "Escaping",
path: "/escape",
body: $body,
files: sources(["public/escape.phv", "views/escape.phv.html"]),
);
views/escape.phv.html
<?phv params(string $input, string $url) ?>
<h1>Escaping by position</h1>
<p class="lead">Whatever you type below is placed into the page in four different spots. PHV knows, at compile
time, where each <code><?= ?></code> lands in the HTML and treats the value accordingly.</p>
<form method="get" action="/escape" class="stack">
<label for="input">Any text</label>
<input id="input" name="input" value="<?= $input ?>" maxlength="200">
<label for="url">A link target</label>
<input id="url" name="url" value="<?= $url ?>">
<button>Update the page</button>
</form>
<div class="demo">
<h2>1. Element content</h2>
<p>Written as <code><p><?= $input ?></p></code>. Tags show up as text:</p>
<p class="out"><?= $input ?></p>
<h2>2. Quoted attribute</h2>
<p>Written as <code>title="<?= $input ?>"</code>. Quotes cannot end the attribute:</p>
<p class="out"><span title="<?= $input ?>">Hover this text to see the attribute value.</span></p>
<h2>3. URL</h2>
<p>The start of <code>href</code> only accepts a <code>SafeUrl</code>, made by <code>safe_url($url)</code>,
which lets through <code>http(s)</code>, <code>mailto</code>, <code>tel</code> and relative URLs and turns
anything else into <code>#</code>:</p>
<p class="out"><a href="<?= safe_url($url) ?>">this link</a> points to <code><?= safe_url($url) ?></code></p>
<h2>4. Inside <script></h2>
<p>A <code><script></code> only accepts a <code>SafeJs</code>, made by <code>json_html($input)</code>. The
script below reads your text as a JavaScript string and counts its characters:</p>
<p class="out" id="js-out">(the script did not run)</p>
<script>
const input = <?= json_html($input) ?>;
document.getElementById("js-out").textContent =
"JavaScript received a string of " + [...input].length + " characters.";
</script>
<p class="muted">That <code><script></code> runs because PHV added a CSP nonce to it. A script that got in
through <code>raw()</code> would not get one, so the browser would block it.</p>
</div>
<h2>What does not compile</h2>
<p>Putting a plain <code>string</code> where escaping cannot protect it is an error, not a warning:</p>
<pre class="term"><code>$ phv check bad.phv
error: a URL attribute cannot start with an unchecked value
--> bad.phv:4:14
|
4 | <a href="<?= $url ?>">this link</a>
| ^^^^
= note: use `safe_url($url)`, which only allows http(s), mailto, tel and relative URLs (blocks `javascript:`); `escape()` does not make a URL safe</code></pre>
<p>The same goes for unquoted attributes, event handlers such as <code>onclick</code>, <code>style</code> and
plain strings inside <code><script></code>.</p>