Mundarija (22)
- Bu darsda
- 1. Nega bu kerak?
- 2. Qo'lda shablon va escape
- 2.1 Muammoni ko'rish
- 2.2 Escape hamma narsani hal qilmaydi
- 3. Express va EJS
- 3.1 res.render
- 3.2 Shablon va partial'lar
- 3.3 Natija
- 4. Uch engine yonma-yon
- 5. Zamonaviy alternativalar
- 6. API qadami: shablon kerakmi? (holat 17, 3-qism)
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): chek shabloni
- 3-mashq (qiyin): xavfsiz havola
- 4-mashq: API qadami
- 10. Real ishda
- Xulosa
- Manbalar
Express template engine'lari: EJS, Pug, Handlebars va server-side HTML
Qisqacha: Template engine — HTML shabloniga ma'lumot qo'yib, tayyor sahifa yasaydigan kutubxona. Express'da
app.set("view engine", "ejs")vares.render("royxat", { vazifalar })yetadi. Uchta mashhuri — EJS, Pug va Handlebars — sukut bo'yicha matnni escape qiladi (<→<). Lekin har birida "xom" chiqarish bor (<%- %>,!=,{{{ }}}) — XSS shu yerdan kiradi.vazifalar-apiJSON qaytaradi, shuning uchun shablon unga kirmadi — bu dars faqat tajriba.
Bu darsda
- Template engine nima va qachon kerakligini tushuntira olasiz.
- Express'da
res.renderbilan EJS sahifasini chiqarasiz, partial ulaysiz. - EJS, Pug va Handlebars sintaksisini taniysiz va o'lchov bilan taqqoslaysiz.
- Auto-escape qayerda himoya qiladi va qayerda qilmaydi — bilasiz.
Oldin bilishingiz kerak: res obyekti, Mashhur middleware'lar, XSS va DOM xavfsizligi, Kontentni o'zgartirish va XSS xavfi.
1. Nega bu kerak?
Siz hozirgacha ikki xil dunyoda ishladingiz. Frontend'da (17–23-qismlar) HTML'ni brauzerda React yasadi. Backend'da (24–25) server JSON qaytardi. Lekin HTML'ni serverda yasash kerak bo'lgan joylar hali ham ko'p:
- Email xatlari — "Parolni tiklash", "Buyurtmangiz qabul qilindi". Email mijozi JavaScript bajarmaydi — HTML tayyor kelishi shart.
- Admin panel — 5 kishi ishlatadigan ichki sahifa uchun alohida React loyiha qurish ortiqcha.
- Oddiy sahifalar — "Xizmat ishlamayapti", to'lov cheki, PDF uchun HTML.
Template engine bu ishni "qolip" bilan qiladi. Novvoyxonani tasavvur qiling: non qolipi bir xil, xamir har safar boshqa. Shablon — qolip, ma'lumot (vazifalar) — xamir, natija — tayyor HTML sahifa.
Satrlarni qo'lda yopishtirsa ham bo'lardi: "<li>" + matn + "</li>". Lekin bu ikki muammo beradi. Kod tez o'qilmas holga keladi. Eng yomoni — matn ichida <script> bo'lsa, u sahifada bajariladi.
2. Qo'lda shablon va escape
2.1 Muammoni ko'rish
Template engine'ning asosiy xizmati — escape (maxsus belgilarni zararsizlantirish). HTML'da <, >, &, ", ' maxsus ma'noga ega. Ularni < kabi "entity"larga aylantirsangiz, brauzer ularni matn sifatida ko'rsatadi, teg sifatida emas. Bu g'oyani XSS darsida ko'rgansiz.
Quyidagi blokda kichik shablon funksiyasini o'zimiz yozamiz. U teg qilingan shablon (tagged template, tagged template darsi) — har ${} qiymatini escape qiladi. «Ishga tushir» bilan sinang:
const ENTITY = { "&": "&", "<": "<", ">": ">",
'"': """, "'": "'" };
const esc = (x) => String(x).replace(/[&<>"']/g, (b) => ENTITY[b]);
// html`...` — har ${qiymat} escape qilinadi
function html(qismlar, ...qiymatlar) {
return qismlar.reduce((acc, q, i) =>
acc + esc(qiymatlar[i - 1]) + q);
}
const vazifalar = [
{ matn: "Non olish #uy", bajarildi: false },
{ matn: '<img src=x onerror="alert(1)">', bajarildi: true },
];
for (const v of vazifalar) {
const sinf = v.bajarildi ? "tayyor" : "";
console.log(html`<li class="${sinf}">${v.matn}</li>`);
}
console.log("xom:", `<li>${vazifalar[1].matn}</li>`);Konsolda:
<li class="">Non olish #uy</li>
<li class="tayyor"><img src=x onerror="alert(1)"></li>
xom: <li><img src=x onerror="alert(1)"></li>Ikkinchi va uchinchi qatorni solishtiring. Escape bilan hujumchining rasmi oddiy matn bo'lib qoldi. Xom satrda esa brauzer <img> tegini yaratadi, rasm yuklanmaydi va onerror ichidagi kod bajariladi.
2.2 Escape hamma narsani hal qilmaydi
Escape faqat HTML matni va qo'shtirnoqli atribut ichida himoya qiladi. Boshqa joylarda u yetmaydi:
const ENTITY = { "&": "&", "<": "<", ">": ">",
'"': """, "'": "'" };
const esc = (x) => String(x).replace(/[&<>"']/g, (b) => ENTITY[b]);
const havola = "javascript:alert(document.cookie)";
console.log(`<a href="${esc(havola)}">Profil</a>`);
const xavfsizmi = (url) => /^https?:\/\//i.test(url);
const profil = "https://example.com/profil";
console.log(xavfsizmi(havola), xavfsizmi(profil));Konsolda:
<a href="javascript:alert(document.cookie)">Profil</a>
false truejavascript: satrida birorta ham maxsus belgi yo'q — escape uni o'zgartirmadi. Foydalanuvchi havolani bossa, kod ishlaydi. Shuning uchun URL'ni oq ro'yxat bilan tekshirasiz: faqat http:// va https://. <script> ichiga, style atributiga yoki onclick ga foydalanuvchi ma'lumotini qo'yish esa umuman taqiqlanadi. Har kontekst o'z qoidasiga ega.
Tekshirib ko'ring:
<div title=${esc(ism)}>— qo'shtirnoqsiz atribut.ism=x onmouseover=alert(1)bo'lsa nima bo'ladi?
Javob
Escape bu satrda hech narsani o'zgartirmaydi: unda <, >, ", ', & yo'q. Natija: <div title=x onmouseover=alert(1)> — brauzer bo'shliqni atributlar chegarasi deb biladi va onmouseover alohida atribut bo'ladi. Qoida: atribut qiymati har doim qo'shtirnoq ichida. Template engine'lar ham shunday shablon kutadi.
3. Express va EJS
3.1 res.render
Express shablonlarni res.render(nom, ma'lumot) bilan chiqaradi (res obyekti). Ikki sozlama kerak: qaysi engine va shablonlar qayerda.
import express from "express"; // + npm i ejs
const app = express();
app.set("view engine", "ejs"); // .ejs fayllar
app.set("views", "./views"); // sukut: ./views
app.get("/", (req, res) => {
res.render("royxat", { vazifalar }); // views/royxat.ejs
});app.set("view engine", "ejs") yozilganda Express ejs paketini o'zi import qiladi — kodda import ejs shart emas. Lekin paket o'rnatilgan bo'lishi kerak: npm i ejs.
3.2 Shablon va partial'lar
views/royxat.ejs:
<%- include("qismlar/bosh", { sarlavha: "Vazifalar" }) %>
<h1>Vazifalar (<%= vazifalar.length %>)</h1>
<ul>
<% for (const v of vazifalar) { -%>
<li class="<%= v.bajarildi ? "tayyor" : "" %>"><%= v.matn %></li>
<% } -%>
</ul>
<%- include("qismlar/oxir") %>EJS'da uch xil teg bor:
| Teg | Nima qiladi |
|---|---|
<%= x %> |
x ni escape qilib chiqaradi |
<%- x %> |
x ni xom chiqaradi (escape yo'q) |
<% kod %> |
JS kodi: for, if — chiqarmaydi |
-%> |
Tegdan keyingi qator ko'chishini olib tashlaydi |
Partial — bir nechta sahifada takrorlanadigan bo'lak (sarlavha, menyu, pastki qism). EJS'da include() bilan ulanadi. include tayyor HTML qaytaradi, shuning uchun u <%- (xom) bilan chiqariladi — bu xavfsiz, chunki partial ichidagi qiymatlar o'zi <%= bilan escape qilingan. views/qismlar/bosh.ejs:
<!doctype html>
<html lang="uz">
<head><meta charset="utf-8"><title><%= sarlavha %></title></head>
<body>Layout — har sahifani o'rab turadigan umumiy qolip. EJS'ning o'zida layout yo'q, shuning uchun bosh va oxir partial'lari bilan quriladi. Pug'da extends/block, Handlebars'da express-handlebars paketining layouts papkasi bor.
3.3 Natija
Biz ishga tushirdik (express 5.3.0, ejs 7.0.1). Ma'lumot — ikki vazifa, biri hujumchinikidek. Ikkinchi <li> ichiga qarang:
200 text/html; charset=utf-8
<!doctype html>
<html lang="uz">
<head><meta charset="utf-8"><title>Vazifalar</title></head>
<body>
<h1>Vazifalar (2)</h1>
<ul>
<li class="">Non olish #uy</li>
<li class="tayyor"><img src=x onerror="alert(1)"></li>
</ul>
</body></html>Nima bo'ldi? Bosh va oxir partial'lari sahifani o'radi, res.render Content-Type: text/html ni o'zi qo'ydi. <%= v.matn %> — escape qilingan, yuqorida qo'lda yozgan funksiyamiz bilan bir xil natija. Mavjud bo'lmagan shablon so'ralsa — xato xato middleware'iga boradi:
500 Failed to lookup view "yoq-shablon" in views directory "./views""./views papkasida yoq-shablon ko'rinishi topilmadi". Bu xabarni foydalanuvchiga ko'rsatmang — u server papka tuzilmasini oshkor qiladi.
flowchart LR
A["GET /"] --> B["handler:<br/>res.render('royxat', data)"]
B --> C["views/royxat.ejs<br/>+ qismlar/*.ejs"]
C --> D["EJS: kompilyatsiya<br/>(keshlanadi)"]
D --> E["HTML satri"]
E --> F["200 text/html"]Diagrammadagi "kompilyatsiya" qadamiga qarang. Shablon birinchi marta JS funksiyasiga aylantiriladi, keyin faqat funksiya chaqiriladi. Express NODE_ENV=production da shu funksiyani keshlaydi (view cache). Development'da esa har so'rovda faylni qayta o'qiydi — shablonni tahrirlasangiz, serverni qayta ishga tushirmasdan ko'rasiz.
Tekshirib ko'ring: Admin "e'lon matnini" HTML formatlash bilan saqlaydi va siz uni
<%- elon %>bilan chiqarasiz. Qanday xavf bor va nima qilish kerak?
Javob
Xom chiqarish (stored XSS): admin akkaunti o'g'irlansa yoki admin bilmasdan <script> joylasa, kod har foydalanuvchida bajariladi. Yechim: HTML'ni saqlashdan oldin yoki chiqarishdan oldin sanitizatsiya qilish — faqat ruxsat etilgan teglarni qoldiradigan kutubxona bilan (masalan, DOMPurify). Yoki HTML o'rniga Markdown saqlab, xavfsiz renderer ishlatish. Sanitizatsiya React XSS himoyasi darsida chuqur.
4. Uch engine yonma-yon
Kanon t16 bir xil ro'yxatni uchala engine'da yasagan. Sintaksis:
| EJS | Pug | Handlebars | |
|---|---|---|---|
| Escape bilan | <%= x %> |
= x yoki #{x} |
{{x}} |
| Xom | <%- x %> |
!= x |
{{{x}}} |
| Sikl | <% for … %> |
each v in vazifalar |
{{#each vazifalar}} |
| Layout | partial bilan | extends / block |
express-handlebars |
Pug — chekinish (indent) asosidagi alohida til, HTML teglari yozilmaydi (li.tayyor= v.matn). Handlebars — "mantiqsiz" shablon: ichida JS kodi yozilmaydi, faqat yordamchilar (helpers). EJS — oddiy HTML ichida oddiy JS. HTML va JS biladigan dasturchi uchun eng qisqa yo'l.
Uchalasining escape natijasi (kanon chiqishi, qisqartirilgan). Qo'shtirnoq qanday kodlanganiga va oxirgi qatorga qarang:
ejs:
<li class="tayyor"><script>alert("xss")</script></li>
pug va handlebars:
<li class="tayyor"><script>alert("xss")</script></li>
xom teg (uchalasida):
<p>xom: <script>alert("xss")</script></p>Qo'shtirnoq turlicha kodlangan (" va ") — brauzer uchun ikkalasi bir xil. Oxirgi qatorga e'tibor bering: xom teg uchala engine'da ham <script> ni o'zgartirmay chiqardi.
Narx (kanon o'lchovi, 2026-10-24):
- ejs 7.0.11 paket0,2 MB
- handlebars 4.7.106 paket5,2 MB
- pug 3.0.451 paket6,9 MB
Manba: IlmHamroh kanon o'lchovi t16, 2026-10-24: har paket alohida bo'sh papkaga npm i (prod), Node 24.21.0
Grafikda farq 35 baravargacha: EJS bitta paket, Pug esa 51 paket o'rnatadi. Tezlikda esa Pug oldinda: kompilyatsiyadan keyin sekundiga 1 075 656 render, EJS 223 930, Handlebars 235 333. Lekin bu kichik ro'yxatda o'lchangan. Real sahifada vaqtni ma'lumotlar bazasi so'rovi oladi, shablon emas — 0.004 ms va 0.001 ms farqni foydalanuvchi sezmaydi.
5. Zamonaviy alternativalar
Template engine'lar 2010-yillar uslubi. Bugun server HTML'i uchun yana ikki yo'l bor:
- React serverda. Siz bilgan JSX bilan:
renderToString(<Royxat vazifalar={v} />). React escape'ni o'zi qiladi, xom chiqarish faqatdangerouslySetInnerHTMLbilan — nomidan xavfli ekani ko'rinib turadi. Next.js aynan shunday ishlaydi (Next.js qismi). - Email shablonlari — React Email yoki MJML kabi vositalar: oddiy komponentdan email mijozlari tushunadigan eski uslubdagi HTML (jadvallar, inline CSS) yasaydi.
Qachon nima:
| Vaziyat | Tanlov |
|---|---|
| To'liq frontend, SEO | Next.js |
| Kichik admin sahifa, Express ichida | EJS |
| Email xatlari | React Email / MJML yoki EJS |
| Faqat JSON API | Shablon kerak emas |
6. API qadami: shablon kerakmi? (holat 17, 3-qism)
vazifalar-api ga shablon kirmadi. Sabab oddiy: API faqat JSON qaytaradi, HTML'ni frontend (vazifalar React ilovasi) yasaydi. public/index.html — o'zgarmas statik sahifa, uni express.static beradi (Mashhur middleware'lar). Shablon qo'shish — yangi bog'liqlik va yangi XSS yuzasi, foydasi esa yo'q.
Kodda o'zgarish yo'q — hali commit qilmang, holat 17 ning commiti keyingi darsda. Kelajakda email kerak bo'ladi ("parolni tiklash" — auth qismida). O'shanda shablon tanlovi shu darsdagi jadval bilan qilinadi.
7. Hujumchi nigohi
| Hujum | Qayerda | Himoya |
|---|---|---|
| Stored XSS | <%- matn %>, {{{matn}}}, != matn |
Faqat escape qiluvchi teg; HTML kerak bo'lsa — sanitizatsiya |
javascript: havola |
href="<%= url %>" |
URL oq ro'yxati: faqat http(s):// |
| Qo'shtirnoqsiz atribut | title=<%= x %> |
Atribut doim "…" ichida |
| Shablon nomi foydalanuvchidan | res.render(req.query.sahifa) |
Nomlar ro'yxati; foydalanuvchi matnini nom qilmang |
| Xato matni | Failed to lookup view … in views directory … |
Prod'da umumiy xato sahifasi |
To'rtinchi qator alohida xavfli. Foydalanuvchi shablon nomini tanlasa, u boshqa faylni ochishga urinadi. Bundan ham yomoni — shablonni foydalanuvchi matnidan kompilyatsiya qilish (ejs.render(req.body.shablon)). Bu SSTI (Server-Side Template Injection): EJS ichida JS ishlaydi, demak hujumchi serverda kod bajaradi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Cannot find module 'ejs' |
view engine berilgan, paket yo'q |
npm i ejs |
Failed to lookup view |
Nom yoki views papkasi noto'g'ri |
app.set("views", …), nomni tekshiring |
Sahifada <b> ko'rinadi |
Partial <%= bilan ulangan |
include — <%- bilan |
| Shablon o'zgarishi ko'rinmaydi | Prod'da view cache |
Qayta ishga tushirish |
alert ishladi |
Xom teg | <%=, sanitizatsiya |
9. Mashqlar
Yechimlar kurs/mashqlar/25/16-shablonlar/ da.
1-mashq (oson): to'ldiring
- EJS'da escape qilib chiqaradigan teg —
<% - Handlebars'da xom chiqarish —
- Express'da shablonni chiqaradigan metod —
res. <belgisining HTML entity'si —
Yechim
- = —
<%= x %>;<%- x %>esa xom. - {{{x}}} — uch qavs; ikki qavs
{{x}}escape qiladi. - render —
res.render("royxat", { vazifalar }). - < — "less than".
2-mashq (o'rta): chek shabloni
html teg funksiyasidan foydalanib, chekYasa(buyurtma) funksiyasini yozing. U <table> qaytarsin: har taom — qator (nom, soni, summa), oxirida "Jami". Taom nomi escape qilinsin. Sinov: «Bahor» oshxonasi buyurtmasi — osh 2 × 35 000, ko'k choy 1 × 5 000 va nomi <b>Manti</b> bo'lgan taom 1 × 30 000. Yordam: reduce — reduce darsi, html — «Qo'lda shablon va escape» bo'limida. Toza JS — «Ishga tushir» bilan ishlaydi.
Yechim
const ENTITY = { "&": "&", "<": "<", ">": ">",
'"': """, "'": "'" };
const esc = (x) => String(x).replace(/[&<>"']/g, (b) => ENTITY[b]);
function html(qismlar, ...qiymatlar) {
return qismlar.reduce((acc, q, i) =>
acc + esc(qiymatlar[i - 1]) + q);
}
function chekYasa(taomlar) {
const qatorlar = taomlar.map((t) =>
html`<tr><td>${t.nom}</td><td>${t.soni}</td>` +
html`<td>${t.soni * t.narx}</td></tr>`);
const jami = taomlar.reduce((s, t) => s + t.soni * t.narx, 0);
// qatorlar allaqachon xavfsiz HTML — qayta escape qilinmaydi
return ["<table>", ...qatorlar,
html`<tr><th colspan="2">Jami</th><td>${jami}</td></tr>`,
"</table>"].join("\n");
}
console.log(chekYasa([
{ nom: "Osh", soni: 2, narx: 35000 },
{ nom: "Ko'k choy", soni: 1, narx: 5000 },
{ nom: "<b>Manti</b>", soni: 1, narx: 30000 },
]));Konsolda:
<table>
<tr><td>Osh</td><td>2</td><td>70000</td></tr>
<tr><td>Ko'k choy</td><td>1</td><td>5000</td></tr>
<tr><td><b>Manti</b></td><td>1</td><td>30000</td></tr>
<tr><th colspan="2">Jami</th><td>105000</td></tr>
</table>Diqqat: qatorlar — tayyor HTML. Uni yana html ichiga ${} bilan qo'ysak, ikki marta escape bo'lib, sahifada <tr> ko'rinardi. EJS'da include ni <%- bilan ulash ham shu sababdan.
3-mashq (qiyin): xavfsiz havola
havolaYasa(matn, url) funksiyasini yozing: url faqat http: yoki https: protokolida bo'lsa <a href="…">matn</a>, aks holda faqat escape qilingan matn qaytarsin. Protokolni regex bilan emas, URL klassi bilan aniqlang (URL darsi) — new URL xato bersa, havola yo'q. To'rt holatni sinang: oddiy https, javascript:, katta harfli JAVASCRIPT:, nisbiy /profil.
Yechim
const ENTITY = { "&": "&", "<": "<", ">": ">",
'"': """, "'": "'" };
const esc = (x) => String(x).replace(/[&<>"']/g, (b) => ENTITY[b]);
function havolaYasa(matn, url) {
let protokol;
try {
protokol = new URL(url).protocol;
} catch {
return esc(matn); // nisbiy yoki buzuq URL
}
if (protokol !== "http:" && protokol !== "https:") return esc(matn);
return `<a href="${esc(url)}">${esc(matn)}</a>`;
}
console.log(havolaYasa("Sayt", "https://example.com/?a=1&b=2"));
console.log(havolaYasa("Profil", "javascript:alert(1)"));
console.log(havolaYasa("Profil", "JAVASCRIPT:alert(1)"));
console.log(havolaYasa("<Profil>", "/profil"));Konsolda:
<a href="https://example.com/?a=1&b=2">Sayt</a>
Profil
Profil
<Profil>4-mashq: API qadami
vazifalar-api ga shablon qo'shmang. Uning o'rniga loyiha README ga bitta qator yozing: "HTML — frontend'da; API faqat JSON". Kelajakdagi email uchun 2-mashqdagi kabi toza funksiya yetadimi yoki EJS kerakmi — o'z fikringizni bir gap bilan qo'shing. Commit qilmang.
Yechim
Namuna: "Shablon yo'q: API faqat JSON qaytaradi, HTML'ni vazifalar frontend'i yasaydi. Email (28-qism) uchun 1–2 ta oddiy xat bo'lsa — escape qiluvchi toza funksiya, ko'p va murakkab bo'lsa — EJS (1 paket, 0.2 MB)." Muhimi — qarorni o'lchov va ehtiyojga bog'lash.
10. Real ishda
- Express + EJS — eski va kichik loyihalarda, admin panellarda tez-tez uchraydi. Ko'p o'zbek kompaniyalarining ichki tizimlari shunday yozilgan.
- Email — har backend'da bor: ro'yxatdan o'tish, parol tiklash, chek. Bu yerda escape xatosi foydalanuvchining pochtasiga XSS emas, lekin phishing havola olib kirishi mumkin.
- Xavfsizlik auditi —
<%-,{{{,!=vadangerouslySetInnerHTMLni grep qilish — birinchi qadam. - Intervyu: "Stored va reflected XSS farqi?", "Escape va sanitizatsiya farqi?"
Xulosa
- Template engine — qolip + ma'lumot = HTML; Express'da
view enginevares.render. - EJS
<%= %>, Pug=, Handlebars{{ }}— escape qiladi;<%- %>,!=,{{{ }}}— xom, XSS manbai. - Escape faqat matn va qo'shtirnoqli atributda yetadi; URL — oq ro'yxat,
<script>/on*— taqiq. - EJS 1 paket va 0.2 MB, Pug 51 paket va 6.9 MB; render tezligidagi farq real sahifada sezilmaydi.
vazifalar-api— JSON API, shablon kirmadi; HTML'ni frontend yasaydi.
Keyingi dars: Express + TypeScript — Request generiklari, req.sorovId uchun declaration merging va holat 17 ning commiti.
Manbalar
- Express — template engine'lar bilan ishlash: https://expressjs.com/en/guide/using-template-engines.html (2026-10-24)
- EJS hujjati (teglar,
include): https://ejs.co (npm 7.0.1, 2026-10-24); Pug: https://pugjs.org (3.0.4); Handlebars: https://handlebarsjs.com (4.7.10) - OWASP Cross Site Scripting Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- Kanon
t16(uch engine: escape, render/s, hajm). EJS +res.render+ partial chiqishi — biz ishga tushirdik (express 5.3.0, ejs 7.0.1, Node 24.21.0, Windows 11).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!