Na navigaci | Klávesové zkratky

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ů:

  1. kompilace regulárního výrazu
  2. 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.

před 15 lety v rubrice PHP | blog píše David Grudl | nahoru

Mohlo by vás zajímat

Komentáře

  1. Ondřej Mirtes #1

    avatar

    Jak by řekl klasik – „peklíčko!“

    před 15 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.