Lesson 9 / الدرس 9
Text that turns into markup / نصٌّ يتحول إلى وسوم
Printing what a visitor typed straight into a page hands them control of that page — for everyone who reads it afterwards, not just for themselves. One function fixes it, and the discipline is applying it every single time.
طباعة ما كتبه زائر مباشرةً في صفحة تسلّمه التحكم في تلك الصفحة — لكل من يقرؤها بعده، لا له وحده. ودالة واحدة تصلح ذلك، والانضباط هو تطبيقها في كل مرة دون استثناء.
A browser cannot tell the difference between markup you wrote and markup that arrived in a comment box. It parses both. So the moment a visitor's text is printed into a page unchanged, whatever tags they included become part of that page — and the people who suffer are the next visitors, who never typed anything.
Watch a comment stop being a comment
<h1>Comments</h1>
<!-- Sara typed: Great lesson, thank you -->
<p class="comment"><b>Sara:</b> Great lesson, thank you</p>
<!-- Someone typed the line below into the same box. -->
<!-- The server printed it without changing anything. -->
<p class="comment"><b>Guest:</b> Nice work</p>
<h2 style="color:#b3261e">SITE CLOSED — email us your password</h2>
<p style="margin-top:24px">Everything below the heading is still
the comment. It was text when it was typed and it is markup now.</p>
<script> works, and a script in someone else's page can read their session and act as them. This site's Content-Security-Policy stops the script half of that demonstration, which is a second wall, not the first one.
<script> يعمل، والبرنامج النصّي في صفحة غيرك يستطيع قراءة جلسته والتصرّف بصفته. وسياسة أمن المحتوى في هذا الموقع تمنع نصف البرنامج النصّي من ذلك العرض، وهي جدار ثانٍ لا الأول.Escape on the way out, every time
<?php
// This site's helper, in src/helpers.php. Yours should look the same.
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// ENT_QUOTES escape ' as well as " — without it, a value inside
// a single-quoted attribute can break out of it
// ENT_SUBSTITUTE invalid UTF-8 becomes a replacement character rather
// than an empty string, which is how a bad byte can
// silently delete the rest of a value
?>
<p><b><?= e($comment['author']) ?>:</b> <?= e($comment['body']) ?></p>
<a href="/user/<?= e($comment['author_id']) ?>">Profile</a>
<img src="<?= e($avatar) ?>" alt="<?= e($comment['author']) ?>">
& and the same value is wrong the moment you need it somewhere that is not HTML: an email, a CSV, a JSON response. & وصارت القيمة نفسها خاطئة لحظة تحتاجها في موضع ليس HTML: بريد أو CSV أو استجابة JSON.| Where the value lands | What escaping it needs |
|---|---|
| Between tags, or in an attribute | e() — htmlspecialchars |
| Inside a href or src | e(), and check the scheme is http/https first |
| Inside a <script> block | json_encode — HTML escaping is the wrong language here |
| Inside a CSS value | Do not. Pick from a list of values you wrote |
| Into an SQL query | Nothing — use a placeholder, which the next chapter covers |
The href row catches people out: e() handles the quotes but not the scheme, and javascript:alert(1) contains no character htmlspecialchars touches. Escaping answers "can this break out of where I put it", not "is this safe" — a URL needs both questions asked.
href يوقع الناس: فـe() تعالج علامات التنصيص لا المخطط، وjavascript:alert(1) لا تحوي محرفًا تمسّه htmlspecialchars. فالتهريب يجيب «أيستطيع هذا الخروج من موضعه الذي وضعته فيه» لا «أهذا آمن» — والرابط يحتاج طرح السؤالين.e() حتى على قيم أتت من ملف في المستودع.<?= بلا e( بعدها. فكل نتيجة إمّا مقصودة — HTML كتبه مؤلف ويُراد له حمل وسوم — وإمّا ثغرة، وينبغي أن يكون النوع الأول من القلة بحيث يحمل كلٌّ منه تعليقًا يقول لماذا.Try it live / جرّب بنفسك
Check yourself / اختبر نفسك
1. Who is harmed when a site prints a comment without escaping it?
That is what makes it serious rather than merely untidy. A script running in someone else's page can read their session and act as them, and they did nothing but read a comment.
2. Why escape when printing rather than when saving?
Escape-on-input produces a database full of & that is wrong everywhere except one output format, and cannot be undone reliably once it is there.
3. Why is e($url) not enough for an href?
Two different questions: "can this escape where I put it" and "is this an acceptable value". A URL needs both, so check the scheme is http or https as well.
Score / النتيجة: 0 / 3
Your task / مهمتك
Take a page that shows something a visitor wrote — a comment list, a profile, a search results page. Mark every place a value is printed, say where that value came from, and say what escaping it needs for the place it lands in.
خذ صفحة تعرض شيئًا كتبه زائر — قائمة تعليقات أو ملف تعريف أو صفحة نتائج بحث. علّم كل موضع تُطبع فيه قيمة، وقل من أين أتت تلك القيمة، وقل ما التهريب الذي تحتاجه للموضع الذي تحطّ فيه.
- Every printed value marked, with where it came from كل قيمة مطبوعة معلَّمة، ومن أين أتت
- For each one, the escaping its destination needs — between tags, in an attribute, in a URL لكلٍّ، التهريب الذي تحتاجه وجهتها — بين وسوم، أو في سمة، أو في رابط
- One value you would have trusted, and the form that could set it قيمة واحدة كنت ستثق بها، والنموذج الذي يستطيع ضبطها