Moderator |
|
Je kent het wel: (forum)functionaliteit -zoals hier ook gebruikt wordt- waarbij je een url tussen [ url ] tags zet (maar dan zonder spaties), met optioneel een omschrijving. Deze functionaliteit zet deze informatie om naar een hyperlink in HTML.
Natuurlijk moet je wel enigszins voorzichtig omgaan met deze functionaliteit want je kunt er wel enige rottigheid mee uithalen. De URL zal op correctheid gecontroleerd moeten worden (en bij het afdrukken zal alles ge-escaped moeten worden uiteraard).
Maar hoe valideer ik mijn URL? Er zijn een aantal mogelijkheden.
regexp from hell (RFH)
Je zou een reguliere expressie kunnen opstellen die (zo goed mogelijk) de RFC's volgt, maar soms heb je dan nog steeds valse positieven en valse negatieven.
filter_var in combinatie met FILTER_VALIDATE_URL
Maar deze werkt niet met multibyte karakters zoals hier en ook op PHP.net wordt opgemerkt. De auteur uit het eerder gelinkte artikel haalt wel input filtering en output escaping door elkaar...
andere opties?
...
En dan heb je nog "inline detectie" van URL's (vaak met dezelfde RFH). Maar die neemt mogelijk punten van de eindes van zinnen mee, of kapt deze mogelijk op een raar punt af doordat er een of andere lijpe word-cut-wrap bewerkking in de rest van de UBB-functionaliteit zit .
Dit brengt mij tot de volgende gedachtengang/strategie:
* Ik weet niet hoe URL's van gebruikers er uitzien, niet (op voorhand) waar deze beginnen of eindigen. Als het de intentie is van een gebruiker om een link te plaatsen, dan moet deze tussen [ url ] tags staan.
* De enige harde eis die ik heb is dat deze begint met http of https.
* Het lijkt mij de verantwoordelijkheid van de gebruiker zelf om een kloppende URL in te vullen (derp).
En de volgende (vereenvoudigde) implementatie (aanname: karakterset = UTF-8):
<?php
header('Content-Type: text/html; charset=UTF-8');
function escape($in) {
return htmlspecialchars($in, ENT_QUOTES, 'UTF-8');
}
function callback($matches) {
if (empty($matches[2])) {
$url = $matches[3];
$description = $matches[3];
} else {
$url = $matches[2];
$description = $matches[3];
}
// do not use /s switch - we do not allow newlines
if (preg_match('#^https?://(.*)$#', $url) == 1) {
return '<a href="'.$url.'">'.$description.'</a>';
} else {
return $matches[0];
}
}
$input = 'your (unclean) input';
echo nl2br(preg_replace_callback('#\[url(=([^]]+))?](.*)\[/url]#siU', 'callback', escape($input)));
?>
<?php header('Content-Type: text/html; charset=UTF-8'); function escape($in) { } function callback($matches) { if (empty($matches[2])) { $url = $matches[3]; $description = $matches[3]; } else { $url = $matches[2]; $description = $matches[3]; } // do not use /s switch - we do not allow newlines return '<a href="'.$url.'">'.$description.'</a>'; } else { return $matches[0]; } } $input = 'your (unclean) input'; ?>
Uiteraard heeft dit ook nadelen (je kunt altijd rotzooi in een URL stoppen) maar is bij mijn weten wel veilig ($input wordt geescaped in de preg_replace_callback aanroep, je zou er ook voor kunnen kiezen om op een andere plaats output te escapen en/of bij te houden of dit is gebeurd).
Ideeën / suggesties?
|