13. díl - Kniha návštěv - Ukládání textů do databáze

PHP Databáze pro začátečníky Kniha návštěv - Ukládání textů do databáze

Vítejte u dalšího dílu seriálu o tvorbě dynamických doplňků na webové stránky (začátek je tady). Dnes si probereme ukládání textů do databáze, dokončíme zabezpečení SQL, nakousneme obranu proti spamovacím robotům a vytvoříme si svoji první návštěvní knihu.

Návštěvní kniha (anglicky guestbook) je stránka, na kterou můžou návštěvníci připisovat vzkazy. Provedení se může lišit od jednoduchého malého okénka (shoutboard) po plnohodnotné jednovláknové fórum s možností odpovídání, citací, přihlašování a podobně. V tomhle článku zůstaneme spíš na tom jednodušším konci spektra. Ale dost teorie, jde se na věc.

Datové struktury

Na ukládání vzkazů od návštěvníků nám postačí jedna tabulka, ve které bude každý řádek odpovídat jednomu vzkazu. Ukládat budeme:

  • Vlastní text vzkazu.
  • Identifikační kód, podle kterého budeme vzkazy řadit, hledat, mazat apod.
  • Jméno autora.
  • Datum (případně i čas) odeslání.
  • Případné další věci jako třeba IP adresu odesilatele, mail, odkaz a podobně zatím pro přehlednost vynecháme.

Na text vzkazu nám asi nebude stačit TINYTEXT (255 znaků), ale TEXT postačí určitě - 64 KB nemá ani celý tenhle článek. Stejně by posloužil jakýkoli dostatečně dlouhý VARCHAR.

Na identifikační kód použijeme dostatečně velké přirozené číslo s automaticky nastavovanou hodnotou, jako obvykle.

Jméno autora bude pravděpodobně celkem krátké, dlouhé traktáty tam píšou snad jen hloupí roboti. Dejme tomu, že 30 znaků by mohlo stačit. Teoreticky by se autoři mohli podepisovat přímo v textu vzkazu a oddělené políčko pro jméno by tedy nebylo nutné, ale nebylo by to moc praktické (půlka lidí by na to zapomínala).

Datum a případně i čas odeslání jsou docela užitečné údaje. Zároveň slouží i pro orientaci návštěvníka: podle nich bezpečně pozná, jakým směrem jsou příspěvky v knize řazeny (tedy pokud mu to nenaznačíme nějakým jiným způsobem, což doporučuji). Jako datový typ můžeme použít DATE nebo DATETIME, v našem příkladu si vystačíme s datem.

Tabulka by tedy mohla vypadat nějak takto:

CREATE TABLE nkniha
(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
datumvlozeni DATE,
autor VARCHAR(30),
obsah TEXT
)

Pozn.: čistě teoreticky by se kniha dala vytvořit i bez databáze, pouze zápisem do souboru. Ale to má několik háčků: zaprvé obtížné řazení od nejnovějšího příspěvku, zadruhé obtížné stránkování a zatřetí problémy, pokud přijde víc příspěvků současně. První dva háčky se dají obejít vytvořením samostatného souboru pro každý příspěvek (třeba s číselným jménem), se třetím nic nenaděláme.

Odesílání vzkazů

První základní funkce knihy. Potřebujeme na to odesílací formulář, do kterého nám návštěvníci budou svoje vzkazy psát. Stačit nám budou dvě textová políčka, datum a kód si doplníme sami:

<form action="nkniha.php" method="post">
Vaše jméno:<br>
<input type="text" name="jmeno" maxlength="30" size="30"><br>
Váš vzkaz:<br>
<textarea name="vzkaz" rows="7" cols="50" wrap="soft"></textarea><br>
<input type="submit" value="Ulož do knihy">
</form>

Po odeslání formuláře se zadaný vzkaz předá skriptu nkniha.php. Asi nejpraktičtější bude, když dáme všechno - formulář, zpracování i zobrazení - do jednoho skriptu (pak je atribut action zbytečný), ale teoreticky by to šlo zařídit i odděleně.

Skript nkniha.php tedy dostal návštěvníkův vzkaz a měl by ho uložit do databáze. Tady máte první nástřel - schválně zkuste přijít na to, co je na něm špatně:

mysql_query("INSERT INTO nkniha VALUES (NULL, CURDATE(), '".$_POST['jmeno']."', '".$_POST['vzkaz']."')",$spojeni));

Příkaz INSERT, čili vložení řádku do tabulky, už dávno známe. Hodnota NULL na místě identifikačního kódu je v pořádku - nic jiného by tam ani nešlo, dosazení správného čísla zajistí auto_increment. Funkce SQL CURDATE() je novinka: dá nám aktuální datum přesně v takovém tvaru, jaký vyžaduje datový typ DATE. Zadané jméno a vzkaz, které se uloží do sloupců autor a obsah, jsou syntakticky správně. Parametr $spojeni je jako obvykle výstup z mysql_connect, na tom není co zkazit. Takže?

Jasně, bezpečnost. Kdo dával minule pozor, ví, co to je SQL injekce:

Vaše jméno:

Váš vzkaz:

Po dosazení zadaných textů do výše uvedeného příkazu by databáze dostala tohle:

INSERT INTO nkniha VALUES (NULL, CURDATE(), 'vtipálek', 'nic'); DROP TABLE nkniha; --')

Ano, totéž jako známý vtip z xkcd. Apostrof za slovem "nic" ukončil textový parametr, závorka a středník ukončují příkaz INSERT, následuje nový příkaz a nakonec dvě pomlčky zakomentují případný zbytek původního příkazu, aby ho SQL nebralo jako syntaktickou chybu. Jak prosté, milý Watsone...

V našem případě to naštěstí není tak horké, funkce mysql_query totiž dokáže zpracovat pouze jeden příkaz. Když jich dostane víc (i když jsou syntakticky v pořádku a oddělené středníkem), neprovede ani jeden a rovnou vrátí false. Ale byla by chyba se na to spoléhat - co kdyby to nějaká verze dělala jinak, že jo.

Uživatelské texty vkládané do příkazů proto musíme vždy prohnat nějakou escapovací funkcí, která zajistí, že se z textu stane opravdu jenom neškodný text, který SQL za žádných okolností nevyhodnotí jako příkaz. První možností je standardní phpčkovská funkce addslashes, která ale používá neměnný algoritmus a nebere v úvahu případné syntaktické odlišnosti konkrétní databáze. Nejjistější je mysql_real_es­cape_string, která je stavěná databázím SQL na míru:

$zabezpeceny_text=mysql_real_escape_string($puvodni_text,$spojeni);

Funkce před každý apostrof a další řídicí znaky vloží znak "\". Interpret si po vyhodnocení příkazu lomítka vyhází, takže v databázi už bude text uložený bez nich, přesně tak, jak ho uživatel napsal.

Mysql_real_es­cape_string je strašně dlouhé slovo, z hlediska pohodlí se vyplatí zabalit si ho do nějaké funkce s kratším jménem. Já například používám tohle:

function zabezpec($retezec)
{
global $spojeni;
return mysql_real_escape_string($retezec,$spojeni);
}

Novinkou je pro nás slovíčko global, které říká, že proměnná $spojeni je globální. To je nutné, protože v PHP se všechny proměnné definované uvnitř funkcí berou jako lokální. Kdyby tam ten řádek nebyl, mysql_real_es­cape_string by skončila chybou, protože by v omylem vytvořené lokální proměnné $spojeni pochopitelně nenašla platné spojení s databází.

Takže tedy ukládání příspěvků do knihy, znovu a lépe:

mysql_query("INSERT INTO nkniha VALUES (NULL, CURDATE(), '"
            .zabezpec($_POST['jmeno'])."', '"
            .zabezpec($_POST['vzkaz'])."')",$spojeni));

Teď už je to v pořádku, tohle nám nikdo nehackne.

Kdy zabezpečení provádět, to už je na vás. Buď až těsně před použitím jako v tomto příkladě, nebo o něco dříve, nebo si třeba můžeme hned na začátku pro jistotu zabezpečit rovnou celý POST a máme po starostech:

foreach($_POST as $klic=>$hodnota)
 $zpost[$klic]=mysql_real_escape_string($hodnota,$spojeni);

Pole $zpost možná budu používat i v dalším textu. Vždycky bude představovat zabezpečenou kopii $_POST a nebudu zbytečně opakovat, jak jsme se k ní dostali. Obdobný význam bude mít pole $zget pro $_GET.

Dobrá, proti SQL injekci jsme obrnění. Zbývá ještě nepodstatná drobnost zvaná doubleposty, čili duplicitní příspěvky. Ty vznikají, když někdo po odeslání vzkazu během netrpělivého čekání na odezvu serveru zmáčkne F5 - tím se data z formuláře odešlou znovu (některé prohlížeče se ptají, některé odesílají rovnou). Zabráníme tomu tak, že ihned po uložení příspěvku skript restartujeme, ale tentokrát bez formulářových dat. Jak? Skokem pomocí funkce header:

header('Location: nkniha.php');

Nebo kratší a blbuvzdornější forma, která říká "skoč na ten skript, ve kterém zrovna jsi" :

header('Location: .');

To samozřejmě předpokládá, že zpracování nového příspěvku je to první, co náš skript dělá, a nic během toho nevypisuje na stránku, jinak by header nefungoval. Ale to už znáte. Další samozřejmá věc je, že header voláme z vnitřku podmínky "pokud byl zadán nějaký text", jinak by se nám skript zacyklil donekonečna.

Vzkazy tedy umíme ukládat, teď ještě by to chtělo nějak je zobrazit. Pokračujte prosím tudy...


 

  Aktivity (1)

Článek pro vás napsal Mircosoft
Avatar
Autor je amatérský pascalista, assemblerista a bastlíř. Profesionálně psal nebo píše v HLASM, Rexxu, Cobolu, ST, LAD, FBD, PHP, SQL, JS, Basicu a pár dalších jazycích, které kupodivu stále existují a používají se :-).

Jak se ti líbí článek?
Celkem (5 hlasů) :
3.43.43.4 3.43.4


 



 

 

Komentáře

Avatar
Tomík
Člen
Avatar
Tomík:

Rád bych se zeptal, jak přesně funguje: mysql_real_es­cape_string?

 
Odpovědět 17.11.2015 16:46
Avatar
Mircosoft
Redaktor
Avatar
Mircosoft:

Přidá zpětná lomítka () před všechny speciální znaky, které by se daly vyhodnotit jako konec příkazu nebo syntaktická chyba. Tedy především apostrofy ('), ale i některé další. Částečně to závisí na znakové sadě a konkrétním databázovém enginu, proto se funkci předává i odkaz na databázi (existují i funkce jako addslashes, které jsou na databázi nezávislé, ale právě z toho důvodu nejsou tak spolehlivé).

 
Odpovědět 17.11.2015 21:28
Avatar
Mircosoft
Redaktor
Avatar
Mircosoft:

Aha, tak zpětné lomítko se tady v komentářích nezobrazuje. Admine, bug :-P .

 
Odpovědět 17.11.2015 21:29
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 3 zpráv z 3.