Filter a další novinky v PHP 5.2
Školení, která pořádám
Článek vyšel v rámci PHP okénka na serveru Root.cz.
PHP 5.2 slibuje podobně jako každá větší verze téměř jakéhokoliv produktu významné zrychlení, bezpečnostní záplaty, aktualizované knihovny a spoustu dalších oprav a vylepšení. Některé novinky jsou ale významnější a vyplatí se na ně podívat podrobněji.
Filter
Asi nejvýznamnější novinkou v PHP 5.2 je rozšíření Filter. Toto rozšíření není zcela nové, v minulosti bylo k dispozici v knihovně PECL, do PHP 5.2 ale bylo umístěno s výstižnějšími názvy funkcí a mírně upraveným API. Rozšíření Filter slouží pro jednotnou kontrolu a úpravu dat, především pro kontrolu dat zadaných uživatelem a pro použití v situacích, kdy mají určité znaky speciální význam (např. v HTML nebo SQL). Pro filtrování dat přišlých od uživatele se používá funkce filter_input nebo hromadná filter_input_array, pro filtrování vlastních proměnných se používají funkce filter_var a filter_var_array.
Filtrů je celá řada, rozdělit je můžeme do tří skupin:
- Validační filtry
- Kontrolují, jestli hodnota splňuje daná kritéria. Pokud ano, tak ji vrátí ve správném typu, jinak vrátí false. Patří sem např. filtry int, validate_url, validate_email nebo validate_ip. Pomocí filtru validate_regexp lze kontrolu provádět pomocí vlastního regulárního výrazu.
- Sanitizační filtry
- Odstraňují z hodnoty nežádoucí znaky nebo je nahrazují. Patří sem např. filtry number_int, stripped (odstranění značek), special_chars (zakódování znaků
<>&"'
), encoded (zakódování pro URL), magic_quotes (aplikování funkce addslashes), email nebo url.
- Vlastní filtry
- Jsou realizovány uživatelsky definovanými funkcemi, které se volají pomocí filtru callback.
Chování většiny filtrů se dá navíc ovlivnit dalšími volbami, jejichž přehled je v dokumentaci.
Kromě ručního filtrování hodnot lze pomocí direktivy filter.default zapnout i implicitní filtrování všech dat přišlých od uživatele. K původním hodnotám se potom dá dostat pouze přes rozšíření Filter po aplikování požadovaného filtru (pokud data nijak upravit nechceme, dá se použít speciální filtr unsafe_raw). V tom případě se automaticky filtrují pole $_GET, $_POST, $_COOKIE, $_SERVER a $_ENV, pole $_REQUEST a $_SESSION se zatím nefiltrují a nedají se použít ani ve funkci filter_input.
<?php
$search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
$search_url = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_ENCODED);
echo "Hledali jste <code>$search_html</code>.\n";
echo "<a href='?search=$search_url'>Hledejte znovu.</a>";
?>
Rozšíření JSON, DateTime a ZIP
Jednoduché rozšíření JSON umožňuje převádět data do formátu JavaScript Object Notation a opět je z něj načítat. Funkce json_encode zakóduje libovolnou hodnotu kromě typu resource, funkce json_decode ji zase rozkóduje. Vzhledem k tomu, že se neukládají metody objektů, lze funkci json_decode určit, zda se mají místo objektů vracet asociativní pole.
<?php
$a = array("a" => 5, 6);
print_r(json_encode($a)); // {"a":5,"0":6}
?>
V PHP 5.1 byla přepracována práce s datem, pro uložení konstant byla bohužel zvolena třída s nevhodně zvoleným názvem date. Tato třída se nyní vrací pod názvem DateTime a s novými metodami.
Rozšíření pro práci s archivy typu ZIP je v PHP k dispozici už dávno, dříve ale záviselo na knihovně třetí strany a archivy dovolovalo pouze číst. Nová verze tohoto rozšíření knihovnu nepotřebuje a podporuje i zápis.
Další novinky
- Z nových funkcí stojí za zmínku především array_fill_keys umožňující vytvořit pole s přednastavenými klíči a error_get_last vracející poslední chybu v asociativním poli. V souvislosti s touto funkcí je vhodné zmínit i novou úroveň chyb E_RECOVERABLE_ERROR, kterou je možné ošetřit funkcí určenou pomocí set_error_handler, nicméně pokud se tak nestane, je program ukončen. Touto chybou bylo nahrazeno vracení chyb E_ERROR v situacích, které neuvedou PHP do nestabilního stavu.
- Funkce setcookie, setrawcookie a session_set_cookie_params dostaly nový parametr httponly, kterým se dá v Internet Exploreru 6 a novějším (ve Firefoxu 2 spolu s rozšířením) zakázat čtení cookies JavaScriptem. Automatické nastavování tohoto parametru při práci se sessions zajišťuje direktiva session.cookie_httponly.
- PHP 5.2 také konečně umožňuje vytvářet rozšíření zobrazující průběh nahrávání souboru na server. Konečnou implementaci potom zajišťuje např. rozšíření UploadProgress.
- Volání funkce __toString bylo rozšířeno na všechna možná místa, takže už se programátoři nebudou divit, proč volání kódu
echo $obj . ""
vypíše na rozdíl od echo $obj
pouze Object id #1
.
- Protokol
data://
, který vešel ve známost především v souvislosti s testem ACID2, je nyní možné používat i v PHP.
Další, ne již však tak významné novinky jsou popsány v instrukcích pro přechod ze starších verzí.
Bezpečnost
Z konfigurační direktivy allow_url_fopen byla vyčleněna direktiva allow_url_include, která zakazuje použití protokolu http://
a podobných pouze ve funkcích pro vkládání souborů. Nicméně stejně jako direktiva allow_url_fopen ani tato nezakazuje použití protokolů php://
a nového data://
, takže se pro zabránění vzdáleného spuštění použít nedá. Pro tento problém nicméně již existuje patch, takže snad začne tato bezpečnostní pojistka svou funkci doopravdy plnit již brzy.
Za zmínku určitě stojí značné množství oprav týkajících se bezpečnosti. S aktualizací na novou verzi tedy není radno otálet.
Diskuse
Juraj:
Osobne pokladám za naj pridanie filtrov a zdokonalenie práce so *.ZIP, s ktorými často na nete pracujem. Uź lenkvôli tomu na budúci týždeň prechádzam doma na 5.2 - na veď načase :o).
Len nedávno som si robil jednu funkciu, zasmejte sa, ale nepohoršite
http://www.jurko.info/index_php.php
Přidání filtrů je největší zvěrstvo, jakého se vývojáři dopustili od zavedení magic quotes. To je magic quotes dovedené do absurdna. Je nutné rozlišovat mezi filtrováním vstupu a escapováním dat pro výstup. A ne z toho namíchat jeden guláš.
Rozšíření pro ZIP je fajn.
Asi tě nepřekvapí, že mě se rozšíření Filter naopak líbí. Jediné, co mi na něm (zatím) chybí, je filtr "blackhole", který všechno sežere. Na ten bych nastavil direktivu filter.default, takže by se k jakýmkoliv datům od uživatele dalo přistupovat pouze funkcí filter_input() s použitím odpovídajícího filtru.
Chápu, že ti vadí míchání filtrů pro ošetření vstupu s filtry pro ošetření výstupu, začátečníci v tom potom mohou mít zmatek a někdo jim musí vysvětlit, kdy co použít (pokud to nepochopí sami). Že musí psát:
<?php
$a = filter_input(INPUT_POST, 'a', FILTER_SANITIZE_MAGIC_QUOTES);
mysql_query("INSERT INTO tab (a) VALUES ('$a')");
$row = mysql_fetch_assoc(mysql_query("SELECT a FROM tab"));
echo filter_var($row["a"], FILTER_SANITIZE_SPECIAL_CHARS);
?>
Nebo ještě lépe:
<?php
$options = array('options' => array('regexp' => '~^[a-z0-9_]+$~'));
if (filter_input(INPUT_POST, 'a', FILTER_VALIDATE_REGEXP, $options)) {
$a = filter_input(INPUT_POST, 'a', FILTER_UNSAFE_RAW);
$pdo->query("INSERT INTO tab (a) VALUES (?)", array($a));
}
$row = mysql_fetch_assoc(mysql_query("SELECT a FROM tab"));
$tmpl->process($row);
?>
A ne třeba:
<?php
$a = filter_input(INPUT_POST, 'a', FILTER_SANITIZE_SPECIAL_CHARS);
mysql_query("INSERT INTO tab (a) VALUES ('$a')");
$row = mysql_fetch_assoc(mysql_query("SELECT a FROM tab"));
echo $row["a"];
?>
ATom:
No nevím, zda je lepší:
<?php
$a = filter_input(INPUT_POST, 'a', FILTER_SANITIZE_MAGIC_QUOTES);
mysql_query("INSERT INTO tab (a) VALUES ('$a')");
$row = mysql_fetch_assoc(mysql_query("SELECT a FROM tab"));
echo filter_var($row["a"], FILTER_SANITIZE_SPECIAL_CHARS);
?>
nebo
<?php
$sql->query("INSERT INTO tab (a) VALUES (?)",$_REQUEST['a']);
.... získání dat
echo htmlspecialchars($row["a"]);
?>
V tomhle s Davidem souhlasím, on se navíc v problémech objektového modelu orientuje mnohem líp než já. Když už objekty používám, tak spíše minimalisticky, takže mě ty problémy tolik netrápí.
ATom:
No, já mám ten problém že se pak sám nevznám v tom co jsem napsal, pokud to neudělám důsledně dokumentované a logický oddělené a objekty mi tu možnost dávají, takže i kdyř potřebuji jen obyčejnou funkci, tak ji raději udělám jako statickou metodu třídy do které logicky patří. Přeci jen píšu v PHP aplikaci, která má už 30 000 řádků zdrojového kódu a těch funkci by bylo trochu moc, kdyby měli být všechny na jedné hromadě, osobně obdivuji ty, co to vydí jinak.
A to že ve vývojovém týmu není nikdo, kdo by na tohle dával pozor je škoda. On je PHP už relativně starý jazyk a je jasné, že udělali spoustu chyb s kterými se dnes už musí žít, ono to jde úplně krásně vidět i na Adobe Flashi. Ostatně se stačí podívat i na Javu a .NET, který se poučil od chyb, kterých se Java dnes už jen těžko zbavuje.
"takže i kdyř potřebuji jen obyčejnou funkci, tak ji raději udělám jako statickou metodu třídy do které logicky patří"
To nevim podle jake logiky tam patri. Timto se akorat supluje nepritomnost jmeneho prostoru v PHP.
ATom:
Patří do skupiny obecných funkcí, funkci generující HTML výstup, funkcí pro podporu JS, funkcí pro přístup k portálovým informacím, funkce pro řízení kontextu (generující URL), atd. Většinu věcí pak ale řeším objekty.
Jmenné prostory to nahrazuje velice slabě, mě to teda nestačí, jmenné prostory řešeím tak, že před název třídy ještě přidávám kontext, takže třeba common_Layout je třída Layout v balíčku common. Má to také tu výhodu, že jakmile někde kdekoliv použij třídu common_Layout, tak se mi sama načte.
finc:
Podle mého, každá funkce je řazená do své určité kategorie. Ono v OOP je mít jaksi ve vzduchoprázdnu není nejlepší, řeším to také pomocí statických metod.
Jinak co se týče dokumentace, tak pokud programátor dodržuje určité standardy, tak je takový kod z velké části samodokumentovatelný (divné slovo:)).
Jmenné prostory, toto jsem také řešil a nakonec jsem přebral způsob ze ZEND Framework. Pokud mám třídu Table_Model_Stmt, vím, že je dřída umístěna Table/Model/Stmt.php. Je pravda, že při práci na větším projektu v PHP, začne vznikat chaos v uspořádání tříd, tak toto je alespoň malá berlička. Doufejme, že v PHP 6, se to změní.
Jinak co se týče toho Filteru, já pořád nevidím důvod proč by to mělo něčemu pomoci, navíc takovýto "Filter" je triviální věc na naprogramování pomocí vlastních tříd.
Techi:
Trocha radosti, trocha skepse, jako v každé nové verzi PHP :)
ATom:
A něvíte někdo, kde je podrobněji popsáno toto:
Hooks for tracking file upload progress were introduced.
?
V článku je to popsáno. Řeší to např. rozšíření UploadProgress nebo prý také APC.
ATom:
Díky. Jsem to přehlédl.
FOUS:
Když použiji
$pattern = "/^[a-zA-Z]*[ ]?[a-zA-Z]*$/";
$test = filter_var($hodnota, FILTER_VALIDATE_REGEXP, array('options' => array('regexp'=> $pattern)))
neprojde čeština. Nevíte co s tím?
Nastaveni setlocale(LC_ALL, 'cs') ve scriptu nepomohlo.
Jinak validace na FLOAT se dá ošetřit na desetinnou čárku celkem jednoduše $test = filter_var($hodnota, FILTER_VALIDATE_FLOAT, array('options' => array('decimal'=> ','))), ale ta čeština nahoře se mi na Windowsech nepodařila prostřelit.
Do výčtu znaků je potřeba přidat znaky s diakritikou. Kdyby se použilo kódování UTF-8, lze použít \pL (používá se PCRE).
FOUS:
Jé to bude hnusný. Teda na pohled. Asi tam dáme UTF-8. Děkuji.
FOUS:
mám UTF8 ale \pL mi neudělá co potřebuji - kontrola českého jména - tzn. jedno, max. dvě slova jenom z českých písmenek
\pL umožní např. ččě df$%^&*
Zápasím s tím už druhý den %(, nemáte někdo nakopnutí správným směrem?
Zápisíš zbytečně. Už včera ti Jakub psal, že máš do výčtu [...] přidat i znaky s českou diakritikou. Třeba jako \x{xxxx} sekvence.
FOUS:
Když já chtěl mít ten regulární výraz "hezký". :)
Ono to není tak šílené, stačí zvolit vhodný rozsah a dát [a-z\x{xxx}-\x{yyy}]. Výraz bude krásný :-)
Interval [a-z] skutečně znamená jen písmena 'a'..'z' anglické abecedy, jak jsou určeny ASCII tabulkou. Ty diakritické je třeba do intervalu doplnit.
Mě <?php filter_var("čau", FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '~^\\pL+$~u'))) ?> funguje v kódování UTF-8 bez problémů.
FOUS:
Už se to blíží/plíží k výsledku. Musí mi totiž projít:
MUDr.Břetislav Žluťoučký
MUDr. Břetislav Žluťoučký
MUDr.Žluťoučký
MUDr. Žluťoučký
Břetislav Žluťoučký
Žluťoučký
Pánům dgx a Vránovi tímto mockrát děkuji za pomoc.
FOUS:
Tak tohle dělá to co potřebuji nahoře:
'~^(\\pL)*(([.][\\pZs]*)|[.])?(\\pL)*([\\pZs]*)?(\\pL)*$~u'
pokud byste to někdo někdy potřeboval :)
Jak se je to vlastne s pouzitim filtru (FILTER_SANITIZE_STRING) na osetreni uzivatelskych vstupu jako ochrana pred XSS? funguje, nebo je to nepouzitelne podobne jako "strip_tags"?
Jinak moc diky za blog, spoustu uzitecnych veci, zajimave podanych :)
Je to stejně nepoužitelné. Při výpisu do HTML je lepší používat FILTER_SANITIZE_SPECIAL_CHARS.
Diskuse je zrušena z důvodu spamu.