Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

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.

Komentáře

  1. Ondřej Mirtes http://ondrej.mirtes.cz/ #1

    avatar

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

    před 7 lety

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