Ošetření délky zadávaných řetězců

Školení, která pořádám

Pokud se hodnoty ze vstupních polí HTML formulářů ukládají do databáze do sloupce typu varchar, měla by být omezena délka textu, který může uživatel zadat. To se dá řešit více způsoby:

První způsob je ve většině případů nešťastný, protože uživatel se o zkrácení textu nijak nedozví. Druhý způsob mi přijde zbytečně krkolomný, mohl by to provádět např. tento kód:

<?php
if (strlen($_POST["nazev"]) > 20) {
    echo "<p>Název je příliš dlouhý, maximální délka je 20 znaků!</p>\n";
}
?>

Pokud se text rozhodnete zkrátit např. funkcí substr, je nezbytné to provést na řetězci neošetřeném direktivou magic_quotes_gpc. Kód substr($_POST["jmeno"], 0, 10) totiž s touto direktivou např. pro řetězec František' vrátí František\ a bezpečnostní problém je na světě.

Nejelegantnější je tedy třetí způsob, který se nejsnáze realizuje atributem maxlength. Pokud se uživatel pokusí zadat delší text, prohlížeč to nedovolí. Pokud máte obavu, že by to pro vaše uživatele mohlo být matoucí, doplňte do formuláře informaci o maximální délce textu, ve většině případů to je ale zbytečné. Pamatujte také na to, že maximální délka sloupce varchar by měla být dostatečná, je např. nesmysl pro e-mail vyhradit pouze 20 znaků. Nejedná se o místo, které bude vždy zabráno (jako u typu char), ale skutečně pouze o maximální možnou délku.

Na atribut maxlength se nedá zcela spoléhat, protože pokud někdo formulář obejde a řetězec pošle např. přímo v URL, tak délku může překročit, většinou to ale ničemu nevadí, protože databáze dlouhý řetězec bez řečí zkrátí.

Závěr zní: vytvářejte textové položky dostatečně dlouhé a ve formulářích nezapomínejte na atribut maxlength.

Jakub Vrána, Dobře míněné rady, 29.6.2005, diskuse: 12 (nové: 0)

Diskuse

ikona dgx:

> ... řetězec pošle např. přímo v URL, tak délku může překročit, většinou to ale ničemu nevadí.

Většinou ne, ale jsou případy kdy ano a dochází pak k chybám, které se velmi těžko dohledávají. Jeden příklad za všechny - uživatel zadá heslo, z něj se generuje hash a ukládá do DB. Pokud jednou pošlu ořezané heslo (u hvězdiček maxlength nepostřehnete) a jednou celé v URL (s oblibou používám v bookmarcích), server to chápe jako dvě různá hesla.

Llaik:

Coz mi pripomina jednu bezpecnostni diru, ktera se mi kdysi povedla.

Nejdrive jsem vstupni promennou addslashnul, abych ji mohl ulozit do databaze. Ovsem nasledne jsem ji jeste zkracoval na zadanou maximalni delku.

A ono ejhle: z test" vzniklo test\" a po zkraceni jen test\

tak pozor, dodrzujte spravne poradi akci :)

ikona spaze:

to samy plati pri ukladani UTF-8 do non-UTF-8-aware databazi. Jak vsichni vime, tak delka retezce != poctu bajtu v tomto pripade.

ikona dgx:

nebo textu s html tagy, viz minulý Jakubův příspěvek na ROOT.cz

Leo:

Omezeni v klientovi ma jeste jednu nectnost, a to je uzivatelsky komfort. Na jeden web zadavam sve IC(O) a to ma urcity pocet cislic, takze autori nastavili maxlength prave na nej. Protoze si ale IC(O) nepamatuju, tak ho kopiruju pres schranku ze zdroje, kde jsou v nem mezery a vysledek je evidentni - oriznuti cisel na konci... Leo

Pavel Zbytovský:

Taky se mi to stává, v tomto případě by se to mohlo skombinovat s Javascriptem.

halogan:

strlen() v tomto pripade nemusi fungovat korektne pri pouziti UTF a kvuli tomu muze dojit k chybe a zkraceni textu a nezadouci delku, proto radeji mb_strlen nebo iconv_strlen.

ikona Jakub Vrána OpenID:

Ano, už jsem o tom psal: http://php.vrana.cz/vyber-kodovani-znaku.php.

ikona llook:

Řetězce je většinou potřeba zkracovat na určitý počet bytů, ne znaků. Proto nelze použít mbstring ani iconv.

Direktivu mbstring.func_overload naštěstí nikdo nepoužívá, takže můžeme použít single-bytový substr() a případný otřep odstranit za pomoci regulárního výrazu:
<?php
$maxLength
= 20;
$nazev = substr($_POST['nazev'], 0, $maxLength);

/* Pokud teď nemáme validní UTF-8 */
if (!preg_match('//u', $nazev)) {
    /* Odstraníme poslední půlznak */
    $nazev = preg_replace('/[\xc0-\xfd][\x80-\xbf]*$/', '', $nazev);

    /* Pokud přesto řetězec není validní, pak je chyba jinde */
    if (!preg_match('//u', $nazev)) {
        die('Vstup není UTF-8 :(');
    }
}

echo
$nazev;
?>
preg_match('//u', $nazev) od verze PHP 4.3.5 zjišťuje, jestli řetězec odpovídá UTF-8. Reguláru /[\xc0-\xfd][\x80-\xbf]*$/ odpovídá vícebytový nebo špatně seříznutý vícebytový řetězec na konci testovaného řetězce.

Spousta věcí z knihoven iconv a mbstring lze řešit přes u modifikátor PCRE.

ikona Jakub Vrána OpenID:

Pokud je texty potřeba zkracovat na počet bajtů a ne znaků, je někde něco špatně. Těžko budu někomu vysvětlovat, že maximální délka příjmení je 20 znaků, ale pokud v něm má diakritiku, tak třeba jen 10.

Pokud se jedná o ukládání do databáze, je vhodné použít třeba MySQL 4.1, které pracuje na znakovém a nikoliv bajtovém přístupu. Jiné věci je také lepší upravit tak, aby pracovaly se znaky a ne s bajty.

Rat:

chtěl bych se zeptat zdali by někdo nevěděl jakou bych měl použít funkci na zkrácení řetezce v kódování UTF 8. To by jen tak mimochodem taky neuškodilo připsat do toho článku.. když už se tam autor zmiňuje o zkracování přes substr() tak nějakou alternativu pro utf 8 :)) substr() nemusi pracovat uplne korektne.. jak jsem tak zjistil.. Ondra

ikona Jakub Vrána OpenID:

iconv_substr() nebo mb_substr().

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.