Aplikační data čistá jako lilie
V článku o Cross-site
scriptingu jsem se dotkl myšlenky, že není vhodné na začátku skriptu
prohánět vstupní parametry funkcí htmlspecialchars()
.
Doporučoval jsem místo toho aplikovat toto ošetření až bezprostředně
při odesílání dat na výstup. Rád bych nyní téma blíže rozvedl. Proč
je paušální použití zmíněné funkce na začátku aplikace špatné?
Už zase to MVC
Pro pochopení, v čem je chyba, si představte klasickou MVC architekturu. Na stejné systémové rozpory nakonec narazíte snad i při jakékoliv jiné architektuře, ale na MVC je to vidět snad nejlépe.
V architektuře MVC se aplikace sestává ze tří částí – Modelu, View a Controlleru. Zjednodušeně řečeno Controller zajišťuje interakci vůči požadavkům uživatelů a jejich primární zpracování. Model poskytuje business logiku a veškerou manipulaci s aplikačními daty. View pak výstup na zobrazovací zařízení uživatele.
Základní myšlenkou MVC je předpoklad, že když kteroukoliv z těchto částí vyměníte či nahradíte, nemělo by to nijak ovlivnit zbývající dvě části. Většina webových aplikací tak má View zajišťující HTML výstup do prohlížeče. Pokud jej ale nahradím jiným, které poskytuje třeba výstup do WML, odpověď přes SOAP nebo export do PDF, mělo by to být možné, aniž bych musel cokoliv měnit v Modelu nebo Controlleru.
Kontaminujeme aplikační data
Celý problém je už asi zjevný. Funkce htmlspecialchars()
je
totiž úzce svázána jen a pouze s výstupem do HTML.
Patří tedy ze své podstaty do View generujícího webové stránky. Nikde
jinde v aplikaci nemá co dělat.
Když ji použijeme na vstupní data hned na začátku běhu aplikace (tedy
v Controlleru či Modelu), kontaminujeme si veškerá aplikační data
balastem, který je svázán jen s HTML formátem výstupu.
Například z názvu firmy Pepa & syn
budeme mít v databázi
uložené Pepa & syn
.
Budu-li chtít později vygenerovat třeba PDF soubor, mám zásadní
problém. Musím předtím nějak všechny &
,
"
a jim podobné textové entity pracně nesystémově
nahrazovat zpět za původní znaky.
Vše má své místo
Správné řešení je jen jedno – systémově aplikovat všechna ošetření právě na těch místech, kam patří. Pokud je cokoliv svázáno s konkrétním typem výstupu, je nutné to aplikovat právě až při generování daného výstupu.
Do Modelu a tím potažmo i do databáze patří pouze čistá aplikační data, nezatížená a nekontaminovaná jakýmkoliv konkrétním typem View. Jenom tak budou připravena pro výstup do jakéhokoliv dnešního i budoucího formátu.
Proklatě magické uvozovky
Ze podobného soudku je fungování direktivy
magic_quotes_gpc
. Měla původně chránit aplikace před
SQL injection tím, že ve vstupních datech opatří všechny citlivé znaky
zpětným lomítkem, čímž z hlediska databáze zruší jejich speciální
význam. To má ale smysl dělat až bezprostředně při vkládání vstupních
dat do SQL dotazu:
mysql_query('SELECT *
FROM uzivatele
WHERE jmeno = "' . mysql_real_escape_string($_GET['jmeno']) . '"');
Tím, že mechanizmus magic_quotes_gpc
ošetřuje paušálně
všechna vstupní data, a navíc ještě před začátkem provádění
samotného skriptu, zanáší do aplikace nesystémovost popisovanou v tomto
článku. V důsledku toho pak máme v aplikaci proměnné plné
zpětných lomítek, která tam vůbec nemají co
dělat.
echo $_GET['id'];
Pokud uvedený skript zavolám s parametrem ?id=joe's garage
,
aplikace mi při zapnuté direktivě vypíše joe\'s garage
.
Kdybych chtěl získat to, co uživatel opravdu zadal, tedy joe's
garage
, musím celý parametr explicitně prohnat funkcí
stripslashes()
. To je ale samozřejmě úplně postavené na hlavu
jakožto důsledek celé nesystémovosti takového řešení.
HTML do databáze nepatří
Dalším obdobným příkladem nevhodného postupu je ukládání vlastně jakéhokoliv HTML kódu do databáze. Ani ten tam logicky nepatří, protože je opět spojen s jedním typem výstupu. A při generování nějakého e-mailu nebo PDF bych musel opět řešit, jak se vypořádat s uloženými HTML tagy apod. Je ovšem pravda, že zrovna tady praktická stránka věci bohužel většinou vítězí nad snahou o systémovou čistotu a třeba při ukládání článků v CMS sypou HTML do databáze skoro všichni.
Radši ale upozorním na jednu věc. Pokud říkám, že HTML kód nepatří do databáze, myslím tím databázi v rámci Modelu, neboli v rámci základních aplikačních dat.
Jsou ale případy, kdy je ukládání HTML kódu nebo dat prohnaných skrz
htmlspecialchars()
zcela v pořádku. Mám na mysli typicky
databázové cachování částí vygenerovaného kódu nebo
i celých výstupních stránek. Tady je ovšem nutné si uvědomit, že tato
část databáze nepatří do modelu, ale je jakoby součástí
View.
Ošetření až na místě, kde je to potřeba je také fajn z důvodu toho, že hned na první pohled vidíme, že to prostě ošetřeno je :)
Tak já jen proč aplikační data? Mě to spíše sedí na výstupní data.
Díky za vysvětlení, teď už je mi ten rozdíl jasný :)
Nezbývá mi nic jiného než souhlasit. Ale nahodil si téma, že renderování html nebo pdf ve view by nemělo nijak ovlivnit model nebo controller. No :) Přece jenom někde v kontroleru se musím rozhodnout, jestli budu chci výstup v html nebo pdf. Generování pdf obsahuje přece jenom nějakou aplikační logiku.
Když píšete, že HTML do databáze nepatří, jak si představujete uložení strukturovaných textů se správným sémantickým označením textových prvků, jako jsou nadpisy, odstavce, seznamy atd.
Díky za info :)
[4] Neměl jsem zde na mysli nabízení více různých paralelních verzí View (tam samozřejmě musíš většinou skrze Controller nějak určit, které z nich požaduješ), ale to, že vezmeš v aplikaci jedno stávající View, dáš ho pryč a místo něj tam dosadíš jiné. To by mělo jít bez toho, aniž bys musel cokoliv měnit v Modelu nebo Controlleru. V reálné praxi tohle asi člověk použije málokdy, ale je to výborný příklad pro pochopení MVC, pro ilustraci, jak vlastně funguje, a pro jasné vymezení odpovědností a pravomocí jednotlivých částí aplikace.
[5] Právě proto jsem v článku taky nechal tu výtku, že takovéhle praktické aspekty nakonec často převažují. Ideálně si to představuji tak, že by strukturovaný text měl být vyznačen nějakým metajazykem nezávislým na konkrétním formátu, který půjde snadno transformovat na libovolný výstup. Mým představám takovému metajazyku nejvíce odpovídá asi Texy!, ale může jím být samozřejmě například nějaké vlastní XML.
Anebo, a tím se vracíme zase na začátek, třebas i HTML. Ale pak je důležité vidět a cítit ten rozdíl mezi HTML jako metajazykem v databázi a mezi HTML jako jedním z mnoha možných výstupních formátů. To pak totiž ovlivní celý návrh aplikace s struktury jejích View.
[5] parádním příkladem je ukládání ukázek zdrojových kódů s obarvenou syntaxí. Pokud je ukládáme v HTML už s barvičkama, kód je pak těžko editovatelný. Lépe je ukládat v textové podobě s metaoznačením „tohle je kus PHP co má být obarven“. Metainformace může být zapsaná i v HTML (třeba pomocí třídy u bloku PRE) – tím se z HTML udělá metajazyk.
Vlastně i značka ve WordPressu
<!-- more -->
patří do stejné kategorie.Příkladem XML metajazyku je například DocBook. Z něj se v pohodě vygeneruje HTML nebo PDF podle potřeby.
[7] Já ten „aspekt“ nevidím v rozdílu mezi tím, který jazyk je víc nebo míň „meta“. Jediným pravidlem by IMHO mělo být ukládání textu v jeho zdrojové podobě. To, v jakém formátu je tento zdroj pořízen je z hlediska MVC přece jedno. Zdrojová data obsahují vždy nejsémantičtější možnou informaci, jakoukoliv transformací do jiného formátu se může jenom tratit. Takže pokud uživatel vkládá v syntaxi Texy, ukládá se Texy, pokud vkládá HTML pomocí WYSIWYG editoru, ukládá se přirozeně HTML.
http://www.joelonsoftware.com/articles/Wrong.html
100% souhlasím, že „escapovaná“ data do DB nepatří. Escapovat HTML* by se mělo těsně před výstupem, už jen proto, že se nikdy nemůžeme spolehnout na to, že nějaký zlovolný uživatel nevloží ručně do DB svůj HTML kód.
Trochu mě zarazil ten příklad se SQL – copak v PHP nemáte parametrizované dotazy (jako třeba my v Javě)?
Texy bych do DB necpal** – to je lepší to HTML, ve smyslu „metajazyka“, tedy nějaký formát založený na XML se značkami které používáme (klidně shodné s HTML značkami), se kterým při výstupu nebudeme pracovat jako s textem (načíst z DB a bezmyšlenkovitě plácnout do výstupu), ale jako s XML dokumentem, který ještě těsně před výstupem zvalidujeme, aby obsahoval jen povolené značky.
*) resp. filtrovat jen povolené značky.
**) ani jako rozhraní s uživatelem mi nepřijde nejvhodnější.
Podle mého parametrizované dotazy nejsou záležitostí PHP nebo Javy, ale záležitostí příslušného SŘBD.
Například já v PHP používám parametrizované dotazy typu
INSERT INTO tabulka(sloupec1,sloupec2) VALUES (:parametr1, :parametr2)
nad SŘBD ORACLE naprosto běžně.
Přiznám se, že s MySQL nemám moc zkušeností, takže nevím, zda i tento SŘBD podporuje parametrizované dotazy.