HTML van een bezoeker: escapen in Python
In les 1 las de browser <b>vet</b> als HTML, omdat de f-string
het letterlijk in de pagina zette. De oplossing heet escapen: van elke <
een < maken, van elke > een >, en zo verder. Die tekens toont de
browser als tekst.
escape
Python heeft er een functie voor, escape uit de module html:
from html import escape
print(escape("<b>vet</b>"))
Uitvoer:
<b>vet</b>
In de browser staat daar weer <b>vet</b>, met de haakjes erbij. Niets wordt
vet.
Op beide plekken in je server
Je gastenboek zet op twee plekken tekst van een bezoeker in HTML: in het
antwoord na het versturen, en in de lijst. Op beide komt escape om de tekst
heen:
@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():
regels = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
regels += f"<li>{escape(bericht['naam'])}: {escape(bericht['bericht'])}</li>"
return HTMLResponse(f"<ul>{regels}</ul>")
- Regel 4:
Het bericht gaat de database in zoals het getypt is. Escapen doe je pas bij het tonen.
- Regel 6:
De naam in het antwoord na het versturen.
- Regel 13:
De naam en het bericht in de lijst, elk apart.
Zet from html import escape bovenaan main.py, bij de andere imports.
Doe de test opnieuw
Gooi gastenboek.db weg, start de server opnieuw, en vul als Sara weer
<b>vet</b> in. In de lijst staat nu <b>vet</b> met de haakjes erbij.
lijst.py uit les 1 laat zien waarom:
<ul><li>Sara: <b>vet</b></li></ul>
Waar de controle zit
Het bericht mag de database in zoals het getypt is. De controle zit op de plek waar het de HTML in gaat.
Browser: Een bezoeker typt HTML. Bijvoorbeeld <b>vet</b> in zijn bericht. · De zwakheid
Server: Het bericht gaat de database in. Zoals het getypt is. Dat is prima: het gevaar zit pas bij het tonen.
Browser: Een ander vraagt de lijst op. Met hx-get of door de pagina te openen.
Hier zit de controleServer: De lijst wordt HTML. Een .html-template of escape() maakt van < een <. · Escapen in Python
Browser: De browser toont tekst. Je ziet <b>vet</b> met de haakjes erbij; niets van de bezoeker wordt HTML.
Er gaat iets mis
Noor vult als naam <i>Noor</i> in. Onder het formulier staat haar naam als
tekst, maar in de lijst is hij schuin. lijst.py geeft:
<ul><li><i>Noor</i>: <b>vet</b></li></ul>
Oorzaak: in de lijst staat escape om het bericht, maar niet om de naam.
Eén vergeten plek is genoeg.
Oplossing: elke waarde van een bezoeker krijgt zijn eigen escape, ook de
naam. Omdat je zo makkelijk een plek vergeet, doet een template het in
les 3 voor je.
Opdrachten
Opdracht 1: Run - Beide plekken
Zet escape op beide plekken. Vul als naam <i>Noor</i> in en als bericht
<b>vet</b>. Wat zie je onder het formulier, en wat in de lijst?
Antwoord
Onder het formulier: Bedankt, <i>Noor</i>. Je bericht staat in het gastenboek.
In de lijst: <i>Noor</i>: <b>vet</b>. Alles staat er als tekst, niets is
schuin of vet.
Opdracht 2: Predict - escape twee keer
Wat print dit?
from html import escape
print(escape(escape("<b>")))
Tip
De eerste escape maakt van < een <. Welk teken in < escapet de
tweede?
Antwoord
&lt;b&gt;
De tweede keer wordt de & van < zelf ge-escapet, tot &. In de
browser zie je dan <b> staan in plaats van <b>. Escape daarom één
keer, op de plek waar de tekst de HTML in gaat, en niet ook al bij het
opslaan.
Zo ziet je main.py er nu uit
import time
from html import escape
from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from sqlitedict import SqliteDict
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@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():
regels = ""
with SqliteDict("gastenboek.db") as db:
for bericht in db.values():
regels += f"<li>{escape(bericht['naam'])}: {escape(bericht['bericht'])}</li>"
return HTMLResponse(f"<ul>{regels}</ul>")
Beide plekken escapen nu. In de volgende les gaat de lijst naar een template.
Door naar les 3: templates escapen vanzelf.