Zrádné regulární výrazy v PHP
V PHP jsou k dispozici tři knihovny pro regulární výrazy: PCRE, Oniguruma a POSIX Regex. Druhá jmenovaná nemusí být vždy k dispozici a třetí je zavržená, proto byste měli používat výhradně šikovnější a rychlejší knihovnu PCRE. Bohužel implementace trpí docela nepříjemnými nedostatky, a to ve všech verzích PHP.
Činnost jednotlivých funkcí preg_*
lze rozdělit do
dvou kroků:
- kompilace regulárního výrazu
- exekuce (hledání, záměna, filtrování, …)
Sympatické je, že PHP zkompilovanou podobu regulárních výrazů udržuje v cache a tudíž se kompilují vždy jen jednou. Proto je vhodné používat statické regulární výrazy, tj. negenerovat je parametricky.
Teď k těm nepříjemným záležitostem. Pokud se během kompilace odhalí
chyba, PHP na ni upozorní chybou úrovně E_WARNING
, avšak
návratová hodnota funkce je nejednotná:
preg_filter
,preg_replace_callback
,preg_replace
vracínull
preg_grep
,preg_match_all
,preg_match
,preg_split
vracífalse
Dobré je vědět, že funkce vracející skrze referenci pole
$matches
(tj. preg_match_all
a
preg_match
) při kompilační chybě argument nevynulují, tudíž
testovat návratovou hodnotu má opodstatnění.
PHP od verze 5.2.0 disponuje funkcí preg_last_error vracející kód
poslední chyby. Avšak pozor, týká se to pouze chyb vzniklých během
exekuce! Pokud dojde k chybě během kompilace, hodnota
preg_last_error
se nevynuluje a vrací předchozí hodnotu. Pokud
tedy návratová hodnota preg_*
funkce není null
resp. false
(viz výše), rozhodně nepřihlížejte
k tomu, co preg_last_error
vrací.
K jakým chybám může dojít během exekuce? Nejčastějším případem
je překročení pcre.backtrack_limit
nebo nevalidní UTF-8 vstup
při použití modifikátoru u
. (Poznámka: neplatné
UTF-8 v samotném regulárním výrazu se odhalí již při kompilaci.)
Nicméně způsob, jak PHP s takovou chybou naloží, je naprosto
neadekvátní:
- nevygeneruje žádnou zprávu (silent error)
- návratová hodnota funkce může naznačovat, že je vše v pořádku
- chybu lze zjistit až následným zavoláním
preg_last_error
Zastavím se u té návratové hodnoty, což je asi největší zrada.
Proces se totiž vykonává do chvíle, než se chyba objeví a poté se vrátí
částečně zpracovaný výsledek. A to v naprosté tichosti. Jenže ani
tohle neplatí vždy, třeba trojice funkcí preg_filter
,
preg_replace_callback
, preg_replace
umí i při
exekutivních chybách vracet null
.
Zda došlo během exekuce k chybě lze zjistit jedině voláním
preg_last_error
. Ale jak už víte, tato funkce vrací nesmyslný
výsledek v případě, že došlo naopak k chybě kompilace, musíme tedy
obě situace rozlišit přihlédnutím k návratové hodnotě funkce, zda-li je
null
resp. false
. A jelikož funkce vracející
null
při chybě kompilace umí vracet null
i při
chybě exekuce, lze konstatovat asi jen tolik, že PHP je nadevší pochybnost
zkurvený jazyk.
Jak by vypadalo bezpečné použití PCRE funkcí? Například takto:
function safeReplaceCallback($pattern, $callback, $subject)
{
// callback musíme ověřit sami
if (!is_callable($callback)) {
throw new Exception('Neplatny callback.');
}
// testujeme výraz nad prázdným řetězcem
if (preg_match($pattern, '') === false) { // chyba kompilace?
$error = error_get_last();
throw new Exception($error['message']);
}
// zavoláme PCRE
$result = preg_replace_callback($pattern, $callback, $subject);
// chyba exekuce?
if ($result === null && preg_last_error()) {
throw new Exception('Chyba zpracovani regularniho vyrazu.', preg_last_error());
}
return $result;
}
Uvedený kód transformuje chyby do výjimek, nesnaží se však potlačit výpis varování.
Bezpečné zpracování regulárních výrazů je implementováno ve třídě Nette\Utils\Strings.
Komentáře
Ondřej Mirtes #1
Jak by řekl klasik – „peklíčko!“
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.