htmlspecialchars($text) converte &, <, >, " e ' in &, <, >, " e '. Chiamala su ogni pezzo di input dell'utente che stampi in una pagina, e l'input viene mostrato come testo invece di essere letto come HTML.
Il blocco stampa lo stesso commento due volte, una volta grezzo e una volta con l'escape. Eseguilo e confronta le due righe nella scheda Page, poi scrivi del tuo HTML nel form, per esempio <h1>big</h1> o <img src=x>, e premi Show.
Nella riga grezza il browser obbedisce ai tag: la parola in grassetto è in grassetto, e un visitatore che scrive <script> fa eseguire il suo script nel browser di ogni altro lettore. Questo attacco si chiama cross-site scripting (XSS). Nella riga con l'escape gli stessi caratteri arrivano come <b> e il browser li disegna come testo. Passa alla scheda Output per vedere le entità che PHP ha davvero stampato.
Cosa converte htmlspecialchars
Cinque caratteri, nient'altro. Lettere, accenti ed emoji passano invariati.
& è nell'elenco perché inizia ogni entità: se venisse lasciato com'è, un commento che cita < verrebbe mostrato come <.
Applicare l'escape agli attributi, non solo al testo
Anche l'input dell'utente dentro un attributo ha bisogno dell'escape. Senza, una virgoletta nel valore chiude l'attributo e il resto dell'input diventa nuovi attributi. Qui il "nome" introduce di nascosto un attributo style; eseguilo e guarda le due caselle.
Nella casella non sicura il browser vede value="Ada" seguito da un nuovo attributo style, quindi la casella diventa rossa e mostra solo Ada. Un attaccante scriverebbe lì onfocus="..." invece di style, e il suo codice verrebbe eseguito. Nella casella sicura ogni " è diventato ", quindi l'intera stringa resta dentro value e viene mostrata come è stata scritta.
Da PHP 8.1 i flag di default sono ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, quindi vengono convertiti anche gli apici singoli e gli attributi scritti con '...' sono sicuri. Il codice più vecchio passa spesso ENT_QUOTES a mano, e su PHP 7 e precedenti era necessario:
Una piccola funzione di supporto per i template
Scrivere htmlspecialchars($x, ENT_QUOTES, 'UTF-8') decine di volte in un template è pesante, quindi la maggior parte dei progetti lo racchiude in una funzione di una lettera. I motori di template come Twig e Blade fanno lo stesso automaticamente per ogni {{ $var }}.
Il tipo ?string e ?? '' contano: passare null a htmlspecialchars() è deprecato da PHP 8.1, e il tuo PHP stamperebbe un avviso di deprecazione per ogni utente senza biografia.
Doppia codifica e htmlspecialchars_decode
Se a un valore viene applicato l'escape due volte, chi legge vede le entità: & diventa & la prima volta e &amp; la seconda, che il browser mostra come &. Di solito significa che l'escape è stato applicato al salvataggio e di nuovo alla stampa. Passa double_encode: false per lasciare intatte le entità esistenti, e usa htmlspecialchars_decode() per tornare indietro.
La vera soluzione è salvare il testo grezzo e applicare l'escape solo in uscita. double_encode: false serve per i testi che contengono già entità che non hai creato tu, come un feed importato.
htmlspecialchars vs htmlentities vs strip_tags
Queste tre si confondono spesso. Il blocco le esegue tutte sullo stesso input e mostra, per ciascuna, cosa stampa PHP e cosa ne fa il browser:
htmlspecialchars()applica l'escape ai cinque caratteri HTML. Usala per qualsiasi testo che stampi nell'HTML.htmlentities()trasforma ancheéiné. Era utile quando le pagine non erano UTF-8; oggi rende solo il sorgente più difficile da leggere.strip_tags()elimina i tag e ne mantiene il testo. Serve a trasformare l'HTML in testo semplice (l'anteprima di un'email, una meta description), non alla sicurezza: l'ultima riga mostra che un<b>consentito mantiene il suoonclick, e il testo messo dentro un attributo non viene toccato affatto.
Dove htmlspecialchars non basta
htmlspecialchars() è l'escape giusto per il testo HTML e gli attributi tra virgolette. Altri punti di una pagina hanno altre regole:
- In un URL,
http_build_query()ourlencode()codifica il valore;htmlspecialchars()rende poi valido nell'HTML il&tra i parametri. - In JavaScript,
json_encode()produce un valore JS valido, eJSON_HEX_TAGtrasforma<e>in\u003Ce\u003E, così un</script>nei dati non può chiudere il tag script. - Non stampare mai l'input dell'utente in un
hrefsenza verificarne lo schema:htmlspecialchars('javascript:alert(1)')resta invariato e viene comunque eseguito al clic. Accetta solo URLhttpehttps, come mostrato nella pagina su filter_var.
Per la gestione dei form che mette insieme tutto questo, vedi i form in PHP.
Domande frequenti
Cosa fa htmlspecialchars in PHP?
Sostituisce &, <, >, " e ' con &, <, >, " e '. Il browser mostra allora quei caratteri invece di leggerli come HTML, quindi un <script> scritto in un form viene mostrato come testo e non viene mai eseguito.
Qual è la differenza tra htmlspecialchars e htmlentities?
htmlspecialchars() converte solo i cinque caratteri speciali dell'HTML. htmlentities() converte anche ogni carattere che ha un'entità con nome, quindi café diventa café. Con pagine UTF-8 sono ugualmente sicure, e htmlspecialchars() mantiene leggibile l'output, quindi è la scelta abituale.
In PHP 8 serve ancora ENT_QUOTES?
Non per la sicurezza: da PHP 8.1 i flag di default sono ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, quindi vengono convertiti anche gli apici singoli. Molti progetti passano ancora esplicitamente ENT_QUOTES, 'UTF-8' così la chiamata si comporta allo stesso modo nelle versioni più vecchie ed è evidente a chi legge.
Devo usare htmlspecialchars sull'input o sull'output?
Sull'output. Salva e valida il valore grezzo, e applica l'escape nel momento in cui lo stampi nell'HTML. Applicarlo all'input salva < nel database, falsa lunghezze e ricerche e porta a doppi escape come &lt;.
strip_tags basta per prevenire XSS?
No. strip_tags() rimuove i tag ma il suo parametro dei tag consentiti ne mantiene gli attributi, quindi <b onclick="..."> sopravvive, e non fa nulla per il testo messo dentro un attributo. Usa htmlspecialchars() quando stampi l'input dell'utente.