Přeskočit na hlavní obsah

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.

Komentáře

  1. 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 :)

  2. Tak já jen proč aplikační data? Mě to spíše sedí na výstupní data.

  3. Díky za vysvětlení, teď už je mi ten rozdíl jasný :)

  4. 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.

  5. 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 :)

  6. [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.

  7. [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.

  8. [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.

  9. [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.

  10. http://www.joelonsoftware.com/articles/Wrong.html

  11. 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ší.

  12. 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,slou­pec2) 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.