Problémy starých skriptů v novém PHP

V poslední době se stále častěji objevují v různých konferencích dotazy, které se týkají nefunkčnosti „starých“ PHP skriptů v posledních verzích PHP. Proč tomu tak je?
Dotazů, které se týkají nefunkčnosti starších PHP skriptů v novějších verzích PHP, astronomicky přibývá. Pokud navštěvujete některou konferenci či board věnující se PHP, tak mi jistě dáte za pravdu. Přitom problém není vůbec tak horký, jak se může na první pohled zdát. Pojďme se tedy podívat na to, proč tomu tak je a jaké změny musíme udělat pro bezproblémovou funkčnost skriptů i v nových verzích PHP.

Pokud vlastníte nějakou publikaci věnující se výkladu PHP, tak dříve či později narazíte na kapitolu věnující se zpracování zadaných údajů ve formulářích. Zde se jistě dočtete, že veškeré údaje zadané ve formuláři jsou k dispozici na následující stránce (po odeslání formuláře) v podobě globálních proměnných. A zde již nastávají první problémy.

Jistě všichni víte, že na konfiguraci PHP se využívá konfigurační soubor php.ini. V tomto souboru se nachází řádek, který začíná register_globals a je mu přiřazena hodnota off či on. Až do verze 4.1.x byla implicitně nastavena hodnota on, což znamenalo, že veškeré údaje ve formulářích byli na následující stránce k dispozici v podobě globálních proměnných. Toto se týkalo i předávání proměnných v URL, session… apod (viz. níže).

V PHP 4.2.x je však implicitně nastavena hodnota off, čímž se předávání proměnných tímto způsobem zakazuje. Nyní si můžete jednoduše říci, že to přenastavíte opět na on a vše bude v pořádku. Ano bude, ovšem zdánlivě. Vypnutí této vlastnosti má totiž své opodstatnění a to bezpečnost. Prohlédněte si následující kód.

<form action="druhy.php" method="post">
<input type="text" name="jmeno" size="20" maxlength="20" />
<input type="submit" name="odeslat" value="Odeslat" />
</form>

Na stránce druhy.php by jste při nastavení register_globals=on měli k dispozici globální proměnnou $jmeno, která by obsahovala zadaný text (v tomto případě nejspíš něčí jméno). Nyní se ale na chvilku vžijme do role uživatele záškodníka, který je nepřející a provede přímo zavolání scriptu druhy.php a to následovně:

druhy.php?jmeno=nejakejmeno

Ve scriptu bude následně k dispozici taktéž globální proměnná $jmeno. A jakou tedy můžeme mít jistotu, že byla proměnná předána právě z předchozího formuláře? Odpověď je stručná: žádnou. Můžete mi namítnout, že je vlastně jedno, odkud se proměnná vzala. V tomto případě ano, ale když se nad tím vším zamyslíte, tak zjistíte, že v mnohých případech tomu tak být nemusí a může to po sobě zanechat nepěknou paseku. A to ani nemluvím o dalších možných vedlejších účincích. Proto by se ze zásady nemělo důvěřovat žádnému uživatelskému vstupu. Bohužel většina skriptů příliš důvěřuje zadaným údajům.

Začněme tedy pracovat s tím, že bude register_globals nastaveno na off (implicitní nastavení PHP 4.2.x). Když v tomto případě užijeme výše uvedených příkladů, tak neuspějeme. Žádná globální proměnná $jmeno se nevytvoří (ani v jednom případě). Jak tedy na to?

Ať je register_globals nastaveno na on či off, tak se na začátku zpracování skriptu automaticky vytvářejí pole, která obsahují prvky, které jsou názvem i hodnotou shodné s vytvářenými globálními proměnnými. Těch polí je několik a obsahují vždy prvky příslušné do dané skupiny. Přehled těch důležitých naleznete v následující tabulce:

Název pole Popis
$_SERVER Obsahuje proměnné, které jsou nějakým způsobem spjaté s prostředím www serveru. Toto pole obsahuje stejné prvky jako pole $HTTP_SERVER_VARS.
$_GET Proměnné, které byly scriptem „dostány“ metodou GET. Toto pole obsahuje stejné prvky jako pole $HTTP_GET_VARS.
$_POST Proměnné, které byly scriptem „dostány“ metodou POST. Toto pole obsahuje stejné prvky jako pole $HTTP_POST _VARS.
$_COOKIE Proměnné, které byly scriptem „dostány“ skrz COOKIES. Toto pole obsahuje stejné prvky jako pole $HTTP_COOKIE _VARS.
$_FILES Proměnné, které byli scriptem „dostány“ uploadem souborů. Toto pole obsahuje stejné prvky jako pole $HTTP_POST_FILES.
$_SESSION Proměnné, které jsou spjaty s aktuální relací. Toto pole obsahuje stejné prvky jako pole $HTTP_SESSION _VARS.

(Poznámka: Někteří jistě namítnou, že výčet není kompletní. Jistě není, ale já jsem vybral pouze ta pole, která souvisí s daným tématem.)

Pokud tedy budeme mít formulář jehož data jsou posílána metodou POST, pak na následující stránce budeme mít k dispozici tato data v poli $_POST. Pokud jako příklad uvedu formulář uvedený na začátku tohoto článku, pak bude proměnná $jmeno k dispozici jako $_POST[`jmeno`]. Pokud by tato proměnná byla předána jako součást www adresy, pak by byla k dispozici v poli $_GET[`jmeno`] (přišla metodou GET). Stejným způsobem se pracuje i s ostatními poli.

V předchozí tabulke jste si jistě všimli, že pole, které zjednodušeně označím jako $_neco mají stejný obsah jako $HTTP_neco_VARS. Možná se nyní ptáte, proč tomu tak je? Proč není pouze jedno pole? Podívejme se na to tedy trochu blíže. Pole $HTTP_neco_VARS jsou k dispozici již od verze PHP 3.x, kdežto pole $_neco je k dispozici "až" od verze 4.1.0. To vše souvisí se změnou register_globals, ale je zde i jedna dost podstatná odlišnost. Ač se na první pohled tato pole zdají stejná (lišící se pouze v názvu), tak rozdíl zde přeci jen je. Zatímco pole $HTTP_neco_VARS jsou globální, tak pole $_neco jsou tzv. superglobální.

Co si pod pojmem superglobální představit? Nejlépe bude ukázat vše na příkladě.

<?

function zobraz_ip () {
    echo $_SERVER[`REMOTE_ADDR`]."<br />"; // zobrazi se IP klienta
    echo $HTTP_SERVER_VARS[`REMOTE_ADDR`]."<br />"; // nic se nezobrazi
}

zobraz_ip ();

?>

Na předchozím případě vidíte jednoduchou funkci, jejíž úkolem je zobrazit IP klienta. Zatímco globální pole $HTTP_SERVER_VARS není ve funkci vidět (IP se nezobrazi), tak tzv. superglobální pole $_SERVER je k dispozici a IP se zobrazí tj. není nutno předávat pole jako parametr funkce. U $HTTP_SERVER_VARS je to nutné (možnost je též užít deklarace global). Hlavní rozdíl tedy je ve viditelnosti polí. Obecně $_neco je viditelné všude, což se o $HTTP_neco_VARS říci nedá.

(Poznámka: Nechci podceňovat tvůrce, ale přesto raději připomenu, že u názvu proměnných záleží na velikostí písmen tj. $HTTP_SERVER_VARS není totéž jako $http_server_vars. Zvláště při přechodech z jiných jazyků se toto může dost často plést.)

Co tedy používat? $HTTP_neco_VARS či $_neco? Na to není úplně jednoznačná odpověď. Vynecháme nyní úvahy ohledně kompatibility s PHP3. PHP3 je přeci jen již zastaralé a udržovat kompatibilitu více méně nemá smysl (mnohdy je to i dost obtížné). Když se zaměříme čistě na PHP4, tak musíme vzít v úvahu tzv. upload bug. Jedná se o dost závažnou chybu, která se objevila ve všech verzích od PHP 3.x až po PHP 4.1.1. Jen nezodpovědný správce by dovolil provozovat takovou verzi (tedy těžko bude vytvořený script běžet na takto "postižené" verzi). Záplata existuje na verze 3.0, 4.0.6, 4.1.0 a 4.1.1. Když vyjdeme z tohoto předpokladu, tak by bylo "hazardem" provozovat starší verzi PHP4 než je verze 4.0.6. Pole $_neco je, jak jsem již uvedl, dostupné od verze 4.1.0.

Při těchto faktech by možná bylo vhodnější využít $_neco. Pokud máte ale rozsáhlý projekt, který nějaký čas vytváříte a v kterém využíváte $HTTP_neco_VARS, pak by bylo nerozumné předělávat znovu všechny scripty. Přeci jen $HTTP_neco_VARS ještě dlouho zůstane pro zachování zpětné kompatibility. Zde je zbytečné přidělávat si práci.

Diskuze (43) Další článek: PlayStation 3 nebude herní konzole

Témata článku: Software, PHP, Bezpečnost, Programování, Hazard, Problém, Problemy, Jednoznačná odpověď, Stejný prvek, Pole, Záškodník, Skript, Zavolání, Stejná metoda


Aktuální číslo časopisu Computer

Speciál o přechodu na DVB-T2

Velký test herních myší

Super fotky i z levného mobilu

Jak snadno upravit PDF