Ga naar hoofdinhoud

HTML van een bezoeker: |safe, de bescherming uitzetten

Soms wil je in een template wél HTML laten zien, bijvoorbeeld een welkomsttekst die je zelf hebt geschreven, met een vet woord erin. Jinja escapet die ook, dus dan zie je de haakjes. Met |safe zeg je tegen Jinja: deze tekst is al veilig, escape hem niet.

Je eigen HTML met |safe​

Zet in main.py een welkomsttekst, en geef hem mee aan het template:

WELKOM = "<b>Welkom</b> in mijn gastenboek"

@app.get("/berichten/lijst")
async def berichten_lijst(request: Request):
with SqliteDict("gastenboek.db") as db:
alle_berichten = list(db.values())
return templates.TemplateResponse(
request, "berichten_lijst.html", {"berichten": alle_berichten, "welkom": WELKOM}
)
  1. Regel 1:

    Deze tekst schreef jij, in je eigen code. Geen bezoeker kan hem veranderen.

  2. Regel 8:

    Het template krijgt nu twee dingen mee: de berichten en de welkomsttekst.

En bovenaan templates/berichten_lijst.html:

<p>{{ welkom|safe }}</p>

Sla beide op, gooi gastenboek.db weg, open je gastenboek opnieuw en vul als Sara weer <b>vet</b> in.

Het welkomstwoord is vet, want de <b> bleef HTML. Het bericht van Sara is nog steeds tekst, want daar staat geen |safe achter.

Controlevraag
Wat doet |safe met de tekst waar het achter staat?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Er gaat iets mis​

Bezoekers schrijven weer vet​

Je wilde dat bezoekers vet konden schrijven, en zette |safe achter het bericht:

<!-- FOUT -->
<li>{{ bericht.naam }}: {{ bericht.bericht|safe }}</li>

<!-- GOED -->
<li>{{ bericht.naam }}: {{ bericht.bericht }}</li>

Er komt geen foutmelding, en <b>vet</b> is weer vet.

Oorzaak: |safe zet het escapen uit. Dan is alles wat een bezoeker invult weer HTML, net als met de f-string in les 2.

Oplossing: gebruik |safe alleen voor tekst die je zelf in je code hebt geschreven, nooit voor iets wat van een bezoeker komt.

Opdrachten​

Opdracht 1: Predict - Een bericht in de welkomsttekst​

Je wilt het eerste bericht boven de lijst zetten, en maakt de welkomsttekst in berichten_lijst zelf, na het lezen van de database:

welkom = f"<b>Eerste bericht:</b> {alle_berichten[0]['bericht']}"

Het template krijgt "welkom": welkom mee, en houdt {{ welkom|safe }}. In de database staat alleen het bericht <b>vet</b> van Sara.

Vraag: hoe staat het bericht van Sara bovenaan, en hoe in de lijst eronder?

Tip

Wie typte welk stuk van welkom?

Antwoord

Bovenaan is het vet: "Eerste bericht: vet". In de lijst staat het als tekst, met de haakjes. lijst.py geeft:

<p><b>Eerste bericht:</b> <b>vet</b></p>
<ul>

<li>Sara: &lt;b&gt;vet&lt;/b&gt;</li>

</ul>

|safe geldt voor de hele tekst in welkom, en die komt nu voor een deel van Sara. Dat jij de f-string schreef, maakt haar stuk niet veilig. Zet je eigen <b> in het template, en geef het bericht apart mee, zonder |safe:

<p><b>Eerste bericht:</b> {{ eerste }}</p>

Geef bij "welkom" weer je eigen WELKOM mee, en zet "eerste": alle_berichten[0]["bericht"] erbij. Dan staat het bericht ook bovenaan als tekst.

Opdracht 2: Investigate - Mag hier |safe?​

Welke van deze drie mag je met |safe in een template zetten?

  1. WELKOM, een tekst die in je main.py staat.
  2. De naam van een bezoeker.
  3. Een bericht dat een bezoeker heeft geschreven, maar dat jij eerst hebt gelezen en goedgekeurd.
Tip

Vraag je bij elk af: wie heeft de tekst getypt?

Antwoord

Alleen de eerste. De tweede en de derde komen van een bezoeker; dat jij een bericht hebt gelezen, maakt het niet veilig, want HTML kan ook iets doen wat je bij het lezen niet ziet. Wil je dat bezoekers vet kunnen schrijven, dan heb je een aparte, beperkte opmaak nodig, zoals **vet**, die je server zelf naar HTML omzet.

Zo ziet je main.py er nu uit
import time
from html import escape

from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlitedict import SqliteDict

app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

WELKOM = "<b>Welkom</b> in mijn gastenboek"

@app.post("/gastenboek")
async def gastenboek_opslaan(naam: str = Form(...), bericht: str = Form(...)):
with SqliteDict("gastenboek.db") as db:
db[f"bericht_{time.time_ns()}"] = {"naam": naam, "bericht": bericht}
db.commit()
return HTMLResponse(f"Bedankt, {escape(naam)}. Je bericht staat in het gastenboek.")

@app.get("/berichten/lijst")
async def berichten_lijst(request: Request):
with SqliteDict("gastenboek.db") as db:
alle_berichten = list(db.values())
return templates.TemplateResponse(
request, "berichten_lijst.html", {"berichten": alle_berichten, "welkom": WELKOM}
)

@app.get("/zoek")
async def zoek(term: str = ""):
gevonden = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
if term.lower() in bericht["bericht"].lower():
gevonden += f"<li>{escape(bericht['naam'])}: {escape(bericht['bericht'])}</li>"
return HTMLResponse(f"<p>Je zocht naar: {escape(term)}</p><ul>{gevonden}</ul>")

|safe staat alleen achter welkom, de tekst die je zelf schreef.

Door naar les 7: in de browser met innerHTML.