Ders 04 / 17
Şablon ve İfade Enjeksiyonu
Sunucu tarafı değerlendirme riskinin yüzey olarak ölçülmesi: değerlendirme ortamında çözülebilen ad sayısı ortamın kendisinden üretilen sondalarla sayılır, on iki şablonlu bir envanterde kaçının kullanıcı verisini yalnız değer olarak, kaçının ifade olarak aldığı ayrılır ve gövdesi dışarıdan gelen şablonların toplam değerlendirme yüzeyi üç kurulumda karşılaştırılır.
İçindekiler
Önceki ders sorgu metnini sabit kabul etti: iskeleti geliştirici yazar, değer dışarıdan gelir. Bildirim metinleri, rapor başlıkları ve saha etiketleri bu varsayımı taşımıyor. Bunların gövdesi bir şablondur ve şablonların bir kısmı çalışma zamanında bir yapılandırma kaydından ya da bir kullanıcı alanından okunur.
Bu ders şablonları sayar. Ölçülen büyüklük metnin bozulup bozulmadığı değil, değerlendirme yüzeyidir: şablon işlenirken kaç ad çözülebiliyor. Bir şablon kullanıcı verisini yalnız değer olarak alıyorsa yüzey bildirilen alanlarla sınırlıdır; veriyi ifade olarak alıyorsa yüzey ortamın tamamıdır.
İki Motor, Tek Ortam
Model iki motor içerir. Değer motoru yalnız bildirilen adları yerine koyar ve sonucu bir daha taramaz. İfade motoru noktalı bir yolu ortamda çözer; bildirim diye bir şeyi yoktur. Ortam, şablon işlenirken erişilebilen nesne kümesidir.
GC9 (varsayım): ortam ve motorlar modeldir. Yüzey, ortamda noktalı yolla çözülebilen yaprak ad sayısıdır; adın taşıdığı değerin ne kadar duyarlı olduğu ölçülmez.
// motor.mjs — iki sablon motoru ve degerlendirme ortami (kurgu model) // Deger motoru: yalniz bildirilen adlari yerine koyar, sonuc bir daha taranmaz. export const degerMotoru = (sablon, ortam, bildirilen) => sablon.replace(/\{([\w.]+)\}/g, (t, yol) => bildirilen.includes(yol) ? String(yol.split(".").reduce((o, k) => o?.[k], ortam)) : t); // Ifade motoru: noktali yolu ortamda cozer; bildirim diye bir sey yoktur. export const ifadeMotoru = (sablon, ortam) => sablon.replace(/\{([\w.]+)\}/g, (t, yol) => { const v = yol.split(".").reduce((o, k) => o?.[k], ortam); return v === undefined || typeof v === "object" ? t : String(v); }); // Erisilebilir ad: ortamda noktali yolla cozulebilen yaprak adlarin listesi. export const erisilebilir = (o, onek = "") => o === null || typeof o !== "object" ? [onek] : Object.entries(o).flatMap(([k, v]) => erisilebilir(v, onek ? `${onek}.${k}` : k)); export const GENIS = { abone: { ad: "A. Yilmaz", no: "A-100432", tarife: "mesken" }, fatura: { donem: "2026-03", tutar: 412, sonGun: "2026-04-15" }, ayar: { bolge: "kuzey", surum: "3", gunlukSeviye: "bilgi", sunucuAdi: "olcum-1" }, baglanti: { depoAdi: "olcum", rolAdi: "rapor_okuyucu", zamanAsimi: 30 }, sistem: { calismaDizini: "/srv/olcum", islemNo: 4120 }, }; export const DAR = { abone: GENIS.abone, fatura: GENIS.fatura }; export const BILDIRILEN = ["abone.ad", "abone.no", "fatura.donem", "fatura.tutar", "fatura.sonGun"];
Geniş ortam bir uygulamanın şablon işleyicisine elinin altındaki her şeyi vermesini temsil eder: abone ve fatura verisinin yanında ayarlar, bağlantı bilgileri ve süreç bilgisi. Dar ortam yalnız bildirimin gerektirdiği iki nesneyi taşır.
İfade motorunun varlığı bir kusur değil, bir istektir. Bildirim metnini yöneticinin düzenleyebilmesi, rapor başlığına bölge adının girebilmesi ve saha etiketinin şablondan üretilmesi işin gerçek gereksinimleridir. Değerlendirme yüzeyi bu gereksinimin sayısal karşılığıdır: metni dışarıdan düzenlenebilir yapmak, o metni işleyen ortamı da dışarıdan sorgulanabilir yapar. Ölçülmesi gereken şey bu takasın büyüklüğüdür.
Değerlendirme Yüzeyi
Yüzey sayılırken sonda kümesi elle yazılmaz; ortamın kendisinden üretilir. Her erişilebilir ad için tek bir yerleştirme denenir ve kaçının çözüldüğü sayılır. Böylece ölçüm, ortam değiştiğinde kendiliğinden güncellenir.
// yuzey.mjs — degerlendirme yuzeyi: ortamda kac ad cozulebiliyor import { degerMotoru, ifadeMotoru, erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs"; // Sonda kumesi ortamin kendisinden uretilir: her erisilebilir ad icin tek yerlestirme. const sondalar = (ortam) => erisilebilir(ortam).map((yol) => [yol, `{${yol}}`]); console.log(`bildirilen ad: ${BILDIRILEN.length}\n`); console.log(`${"ortam / motor".padEnd(22)}${"erisilebilir ad".padStart(16)}${"cozulen".padStart(9)}${"bildirim disi".padStart(15)}`); for (const [adi, ortam, motor] of [["genis / ifade", GENIS, "ifade"], ["genis / deger", GENIS, "deger"], ["dar / ifade", DAR, "ifade"], ["dar / deger", DAR, "deger"]]) { const s = sondalar(ortam); const uygula = (x) => (motor === "ifade" ? ifadeMotoru(x, ortam) : degerMotoru(x, ortam, BILDIRILEN)); const cozulen = s.filter(([, x]) => uygula(x) !== x); const disi = cozulen.filter(([yol]) => !BILDIRILEN.includes(yol)).length; console.log(adi.padEnd(22) + String(s.length).padStart(16) + String(cozulen.length).padStart(9) + String(disi).padStart(15)); } const dis = erisilebilir(GENIS).filter((y) => !BILDIRILEN.includes(y)); console.log(`\ngenis ortamda bildirim disi kalan ad: ${dis.length}`); console.log(`ust duzey ad kumesi: genis ${Object.keys(GENIS).join(" ")} / dar ${Object.keys(DAR).join(" ")}`);
bildirilen ad: 5 ortam / motor erisilebilir ad cozulen bildirim disi genis / ifade 15 15 10 genis / deger 15 5 0 dar / ifade 6 6 1 dar / deger 6 5 0 genis ortamda bildirim disi kalan ad: 10 ust duzey ad kumesi: genis abone fatura ayar baglanti sistem / dar abone fatura
Dört satır iki bağımsız savunmayı ayırıyor. Ortamı daraltmak erişilebilir adı on beşten altıya indiriyor; ifade motoru hâlâ hepsini çözüyor, ama çözülecek ad kalmıyor. Motoru değere sabitlemek ortamdan bağımsız olarak çözülen adı bildirilen kümeye indiriyor: geniş ortamda bile bildirim dışı çözülen ad sıfır.
Son satır ikisinin birlikte anlamını veriyor. Dar ortamda değer motoru beş ad çözüyor,
ifade motoru altı — aradaki tek ad abone.tarife, bildirimde geçmeyen bir alan. Yüzey
sıfırlanmıyor; daralıyor ve sayılabilir kalıyor.
Şablon Envanteri
Yüzey şablon başına ölçülür ve tek bir şey belirler: şablonun gövdesi nereden geliyor. Gövde sabitse çözülecek adları geliştirici yazmıştır ve dışarıdan gelen bir ad yoktur. Gövde bir yapılandırma kaydından ya da kullanıcı alanından okunuyorsa çözülebilen her ad o şablonun yüzeyidir.
GC10 (varsayım): gövdesi sabit olan şablonun yüzeyi sıfır sayılır. Ölçülen şey şablonun kim tarafından yazıldığı değil, nereden okunduğudur.
// envanter.mjs — sablon envanteri (kurgu model) ve toplam degerlendirme yuzeyi import { erisilebilir, GENIS, DAR, BILDIRILEN } from "./motor.mjs"; const ORTAM = { genis: GENIS, dar: DAR }; // [servis, sablon, govde kaynagi, motor, ortam] const SABLON = [ ["bildirim", "faturaKesildi", "sabit", "deger", "dar"], ["bildirim", "sonGunYaklasti", "sabit", "deger", "dar"], ["bildirim", "okumaEksik", "sabit", "deger", "dar"], ["fatura", "dokumBasligi", "sabit", "deger", "dar"], ["fatura", "aciklamaSatiri", "sabit", "deger", "dar"], ["is-emri", "gorevMetni", "sabit", "deger", "dar"], ["is-emri", "kapanisNotu", "sabit", "deger", "dar"], ["bildirim", "kampanyaMetni", "yapilandirma", "ifade", "genis"], ["rapor", "ustBilgi", "yapilandirma", "ifade", "genis"], ["rapor", "altBilgi", "yapilandirma", "deger", "dar"], ["rapor", "ozetBasligi", "kullanici", "ifade", "genis"], ["saha", "etiketMetni", "kullanici", "ifade", "genis"], ]; const YENI = [["rapor", "bolgeBasligi", "yapilandirma"], ["bildirim", "hatirlatma", "yapilandirma"], ["saha", "formBasligi", "yapilandirma"]]; // Yuzey: govde sabitse cozulecek ad kumesi zaten sablonun icindedir. const yuzey = ([, , kaynak, motor, o]) => kaynak === "sabit" ? 0 : motor === "ifade" ? erisilebilir(ORTAM[o]).length : BILDIRILEN.length; const topla = (l) => l.reduce((t, s) => t + yuzey(s), 0); const say = (l, i, v) => l.filter((s) => s[i] === v).length; console.log(`${SABLON.length} sablon, hepsi kullanici verisi aliyor`); console.log(` motor: deger ${say(SABLON, 3, "deger")}, ifade ${say(SABLON, 3, "ifade")}`); console.log(` govde kaynagi: sabit ${say(SABLON, 2, "sabit")}, yapilandirma ` + `${say(SABLON, 2, "yapilandirma")}, kullanici ${say(SABLON, 2, "kullanici")}\n`); const KURULUM = [ ["mevcut durum", (s) => s], ["kalip: deger motoru + dar ortam", ([a, b, k]) => [a, b, k, "deger", "dar"]], ["kalip: govde de sabit", ([a, b]) => [a, b, "sabit", "deger", "dar"]], ]; console.log(`${"kurulum".padEnd(34)}${"sablon".padStart(8)}${"disaridan govde".padStart(17)}${"toplam yuzey".padStart(14)}`); for (const [adi, d] of KURULUM) { const l = SABLON.map(d), y = YENI.map(([a, b, k]) => d([a, b, k, "ifade", "genis"])); console.log(adi.padEnd(34) + String(l.length).padStart(8) + String(l.length - say(l, 2, "sabit")).padStart(17) + String(topla(l)).padStart(14)); console.log(` uc yeni sablon eklendikten sonra: ${l.length + y.length} sablon, yuzey ${topla([...l, ...y])}`); }
12 sablon, hepsi kullanici verisi aliyor motor: deger 8, ifade 4 govde kaynagi: sabit 7, yapilandirma 3, kullanici 2 kurulum sablon disaridan govde toplam yuzey mevcut durum 12 5 65 uc yeni sablon eklendikten sonra: 15 sablon, yuzey 110 kalip: deger motoru + dar ortam 12 5 25 uc yeni sablon eklendikten sonra: 15 sablon, yuzey 40 kalip: govde de sabit 12 0 0 uc yeni sablon eklendikten sonra: 15 sablon, yuzey 0
On iki şablonun hepsi kullanıcı verisi alıyor; bu tek başına bir sayı taşımıyor. Ayrım sekizinin veriyi yalnız değer olarak, dördünün ifade olarak almasındadır. Ve asıl ayrım gövde kaynağındadır: yedi şablonun gövdesi sabit, üçü yapılandırmadan, ikisi doğrudan kullanıcı alanından okunuyor.
Toplam yüzey altmış beş. Bu sayının tamamı beş şablondan geliyor; kalan yedisi hiç katkı yapmıyor. Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon sayısıyla artıyor.
Beş şablonun ikisi ayrı bir sınıftır. Rapor özet başlığı ve saha etiket metni gövdelerini bir yapılandırma kaydından değil, doğrudan bir kullanıcı alanından alıyor: kullanıcının yazdığı başlık, şablonun gövdesi olarak işleniyor. Yapılandırmadan gelen gövdeyi düzenleyen kişi sayısı sınırlıdır ve o kayıt bir onay akışından geçebilir; kullanıcı alanından gelen gövde için böyle bir sınır yoktur. İkisinin yüzeyi aynı — on beşer ad — ama o yüzeye ulaşabilen kişi sayısı aynı değildir.
İkinci kurulum kalıbın kazancını veriyor: bütün şablonlar değer motoruna ve dar ortama alındığında yüzey altmış beşten yirmi beşe iniyor. Sıfıra inmiyor, çünkü gövde hâlâ dışarıdan okunuyor ve bildirilen beş ad her birinde çözülebiliyor. Üçüncü kurulumda gövde de sabitleniyor ve yüzey sıfırlanıyor — ama bu kurulumda yönetici bildirim metnini artık düzenleyemez. Bedel budur ve yazılması gerekir.
Alt satırlar üçüncü sütunu veriyor. Üç yeni şablon eklendiğinde mevcut durumda yüzey altmış beşten yüz ona çıkıyor: her yeni şablon on beş ad ekliyor. Kalıp altında aynı üç şablon yüzeyi yirmi beşten kırka çıkarıyor — beşer ad. Gövdesi sabit kalıpta üç yeni şablon yüzeyi hiç değiştirmiyor.
Üç kurulumun karşılaştırılması bir şeyi daha söylüyor. İkinci kurulumda yüzey yirmi beşe inerken şablonların hiçbiri işlevini yitirmiyor: bildirim metni hâlâ yönetici tarafından düzenlenebiliyor, yalnız düzenlenen metnin çözebileceği ad kümesi daralıyor. Üçüncü kurulumda kazanılan yirmi beş birim, kaybedilen bir yetenekle ödeniyor. Yüzeyi sıfırlayan tek yol, metni dışarıdan düzenlenebilir olmaktan çıkarmaktır ve bu bir güvenlik kararı değil, bir ürün kararıdır.
Gövde Denetimi
Gövdesi sabitlenemeyen şablonlar için üçüncü bir savunma kalır: gövdenin kendisini bir izin listesinden geçirmek. Bu, gövdeyi okuyup içinde geçen adları çıkarmak ve listede olmayan bir ad varsa şablonu reddetmek demektir. Kazancı ve bedeli aynı sayıdan okunur.
// govde-denetimi.mjs — disaridan gelen sablon govdesinin izin listesinden gecirilmesi import { erisilebilir, GENIS, BILDIRILEN } from "./motor.mjs"; // Yapilandirmadan okunan sablon govdeleri (kurgu): hepsi yoneticinin yazdigi mesru metinler. const GOVDE = [ "Sayin {abone.ad}, {fatura.donem} donemi faturaniz hazir.", "Son odeme gunu {fatura.sonGun}.", "{abone.no} numarali abonelik icin {fatura.tutar} birim tahakkuk etti.", "Bolge {ayar.bolge} icin aylik dokum.", "Sunucu {ayar.sunucuAdi} uzerinden uretildi, surum {ayar.surum}.", ]; const adlar = (s) => [...s.matchAll(/\{([\w.]+)\}/g)].map((m) => m[1]); const gecer = (s, izin) => adlar(s).every((a) => izin.includes(a)); const IZIN = [["bildirilen ad kumesi", BILDIRILEN], ["bildirilen + ayar.bolge", [...BILDIRILEN, "ayar.bolge"]], ["govde denetimi yok", erisilebilir(GENIS)]]; console.log(`${GOVDE.length} sablon govdesi, ${new Set(GOVDE.flatMap(adlar)).size} ayri ad kullaniliyor\n`); console.log(`${"izin listesi".padEnd(24)}${"uzunluk".padStart(8)}${"gecen govde".padStart(12)}${"duran mesru govde".padStart(19)}${"yuzey".padStart(7)}`); for (const [adi, izin] of IZIN) { const gecen = GOVDE.filter((s) => gecer(s, izin)).length; console.log(adi.padEnd(24) + String(izin.length).padStart(8) + String(gecen).padStart(12) + String(GOVDE.length - gecen).padStart(19) + String(izin.length).padStart(7)); }
5 sablon govdesi, 8 ayri ad kullaniliyor izin listesi uzunluk gecen govde duran mesru govde yuzey bildirilen ad kumesi 5 3 2 5 bildirilen + ayar.bolge 6 4 1 6 govde denetimi yok 15 5 0 15
Üç satır tek bir eğriyi veriyor: izin listesi her uzadığında bir meşru gövde kurtuluyor ve yüzey bir artıyor. Beş adlık listede üç gövde geçiyor, iki meşru gövde duruyor. Listeye bölge adı eklendiğinde dördü geçiyor ve yüzey altıya çıkıyor. Denetim kaldırıldığında beşi de geçiyor ve yüzey on beş oluyor.
Denetimin kapsamadığı bir durum kalıyor. Gövdesi doğrudan kullanıcı alanından okunan iki şablonda süslü parantez taşıyan bir metin meşru olabilir — bir rapor başlığında ölçü birimi ya da bir saha etiketinde alan kodu yazılabilir. Orada izin listesi meşru başlığı reddeder ve bedel görünür olur; gövdenin bir kullanıcı alanından okunması kararı, izin listesinin çözemeyeceği bir karardır.
Bu, ilk dersteki izin listesinin şablon gövdesine uygulanmış hâlidir ve aynı özelliği taşır: listede olmayan meşru bir ad reddedilir. Farkı ölçünün ne olduğudur — orada dışarıda kalan nokta sayılıyordu, burada çözülebilen ad sayısı. İzin listesinin uzunluğu doğrudan yüzeyin kendisidir; bu yüzden her yeni ad talebi bir yüzey kararıdır ve bir yerde yazılı olması gerekir.
Özet
- Ölçülen büyüklük değerlendirme yüzeyidir: şablon işlenirken ortamda çözülebilen ad sayısı. Sonda kümesi elle yazılmaz, ortamın kendisinden üretilir.
- Geniş ortamda ifade motoru 15 adın 15’ini çözdü ve bunların 10’u bildirim dışıydı. Ortamı daraltmak erişilebilir adı 6’ya indirdi; motoru değere sabitlemek bildirim dışı çözülen adı geniş ortamda bile 0 yaptı. İki savunma bağımsızdır.
- On iki şablonun hepsi kullanıcı verisi alıyor; 8’i yalnız değer olarak, 4’ü ifade olarak. Gövde kaynağı ayrımı belirleyici: 7 sabit, 3 yapılandırmadan, 2 kullanıcı alanından.
- Toplam yüzey 65 ve tamamı gövdesi dışarıdan gelen 5 şablondan geliyor. Değer motoru ve dar ortam yüzeyi 25’e indiriyor; gövdenin de sabitlenmesi 0’a. Üçüncüsünün bedeli, bildirim metninin yönetici tarafından düzenlenememesidir.
- Gövdesi sabitlenemeyen şablonda üçüncü savunma gövdenin izin listesinden geçirilmesidir: 5 adlık listede 5 gövdenin 3’ü geçti ve 2 meşru gövde durdu; liste 6’ya çıkınca 4’ü geçti ve yüzey 6 oldu. İzin listesinin uzunluğu doğrudan yüzeyin kendisidir.
- Üç yeni şablonda yüzey mevcut durumda 65’ten 110’a, kalıp altında 25’ten 40’a çıktı, gövdesi sabit kurulumda 0’da kaldı. Yüzey şablon sayısıyla değil, gövdesi dışarıdan gelen şablon sayısıyla artar.
Sonraki Adım
Şablon gövdesi bir metindi ve dışarıdan gelmesi yüzeyi büyütüyordu. Aynı soru metin olmayan girdiler için de sorulur. Saha uygulaması iş emri kapanışında fotoğraf ve ölçüm dosyası yüklüyor; abone portalı sayaç okuma dökümü için dosya alıyor. Bu girdinin bir sözleşmesi yoktur — bir bayt dizisidir ve hakkında söylenen her şey istemcinin beyanıdır. Sonraki ders yükleme akışını denetim katmanı olarak sayar: tür, boyut, ad ve saklama yeri denetimlerinden kaçı var, hangi denetim tek başına yetmiyor ve saklama yerinin sunulması kaç nokta açıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.