CGI

Common Gateway Interface je protokol, který popisuje komunikaci mezi programem a webovým serverem. Je to standard, který podporuje mnoho serverů. V této kapitole popíši, jak rozchodit CGI se serverem Apache a jak napsat takový CGI program, se kterým si bude Apache rozumět.

O CGI

CGI byl původně jediný způsob, jak vytvářet stránky s dynamicky se měnícím obsahem. Při každém požadavku na stránku se programem obsah stránky generuje znovu, většinou trochu jiný (jinak by nemělo smysl jej generovat dynamicky).

Později bylo CGI vytlačeno moduly skriptovacích jazyků, jako je třeba PHP, Python, servlet kontainery v Javě, jazykem ASP.NET atd.

Dnes už píše webové stránky v C/C++ opravdu málo kdo. Když už je potřeba napsat něco, co funguje fakt rychle, tak se napíše jen kritická část v C/C++ jako modul do daného skriptovacího jazyka.

Jediný důvod, proč dneska psát CGI programy je ten, že je to zábava :-). A taky se při tom naučíte něco o fungování HTTP.

CGI můžete psát v jakémkoliv jazyce, třeba i v tom PHP, ale PHP jako modul funguje mnohem rychleji, než PHP jako CGI program.

Jak tedy CGI funguje? Pro vás, jako vývojáře CGI programů, až překvapivě jednoduše. Program získává data od serveru ze standardního vstup a z proměnných prostředí. Zpět serveru (tedy klientovi, který požádal server o spuštění CGI programu) posílá CGI program odpověď standardním výstupem.
Na první pohled tak nerozeznáte CGI program od jakéhokoliv jiného programu pracujícího se standardním vstupem a výstupem.

Pokud byste chtěli vyvíjet server podporující CGI, museli byste se toho o CGI naučit více.

Konfigurace Apache

Abyste si mohli CGI programy vyzkoušet, nejdříve si musíte nainstalovat server Apache a nakonfigurovat ho, aby CGI programy spouštěl. Tedy, můžete si nainstalovat a nakonfigurovat jakýkoliv server podporující CGI, ale já budu popisovat Apache (verzi 2).

Apache2 budete mít zajisté v balíčcích své distribuce, tak jej nainstalujte z nich.

Popíšu, jak zprovoznit CGI skripty v domácím adresáři každého uživatele. K tomu musíte mít zapnutý modul mod_userdir. Pro zprovoznění CGI zase musíte mít nainstalovaný modul mod_cgi.

Tyto moduly mohou být nainstalované současně z balíčkem Apache, nebo je můžete mít jako extra balíčky. Záleží na vaší distribuci.

Když máte balíčky nainstalované, musíte je taky zapnout. V OpenSuSE najdete v souboru /etc/sysconfig/apache2 řádek s APACHE_MODULES="…", kde se uvádějí moduly, které se zapnou při spuštění apache. Přidejte mezi ně cgi a userdir a restartujte Apache. Nebo použijte program a2enmod, který modul zapne (a pak restartujte Apache).

$ sudo /usr/sbin/a2enmod userdir
$ sudo /usr/sbin/a2enmod cgi
$ sudo systemctl restart apache2

Modul můžete vypnout pomocí a2dismod

V Debianu můžete použít také a2enmod. Nebo se v Debianu zapínají moduly tak, že se udělají symbolické odkazy v /etc/apache2/mods-enabled/ (podívejte se do tohoto adresáře a uvidíte). V jiných distribucích to může být zase jinak :(.
Server v Debianu restartujete pomocí příkazu /etc/init.d/apache2 restart.

Dalším krokem je konfigurace userdir tak, abyste mohli spouštět cgi. Modul userdir se obvykle nainstaluje s předpřipravenou konfigurací v souboru mod_userdir.conf, který najdete někde v /etc/apache2/. Obsah tohoto souboru by měl vypadat nějak takto:

<IfModule mod_userdir.c>
    # not every user's directory should be visible:
    UserDir disabled root
    <Directory /home/*/public_html>
        AllowOverride FileInfo AuthConfig Limit Indexes
        Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec ExecCGI
        AddHandler cgi-script .cgi

        <Limit GET POST OPTIONS PROPFIND>
                Order allow,deny
                Allow from all
                Require all granted
        </Limit>
        <LimitExcept GET POST OPTIONS PROPFIND>
                Order deny,allow
                Deny from all
        </LimitExcept>
    </Directory>
</IfModule>

Podobnou sekci (<Directory /home/*/public_html> ...) jsem našel i v souboru /etc/apache2/vhosts.d/default-vhost.conf. Stačilo ji smazat …

Konfigurace v podstatě popisuje, jaké konfigurační volby může uživatel měnit přes .htaccess a co se bude spouštět v adresáři public_html adresáře každého uživatele (vyjma uživatele root). Nejdůležitější je pro nás mít v Options ExecCGI a řádka AddHandler cgi-script .cgi, která způsobí, že každý spustitelný soubor s koncovkou .cgi bude spuštěn jako cgi program.

Po změně konfigurace je potřeba apache restartovat.

$ sudo systemctl restart apache2

Do uživatelova adresáře se přes webový prohlížeč podíváte na adrese http://localhost/~username/. Tedy za localhost (nebo můžete použít IP adresu či doménu vašeho stroje) je vlnovka a uživatelské jméno.

Pokud se chci podívat na soubor /home/petr/public_html/index.html, zadám do prohlížeče http://localhost/~petr/index.html. Soubor index.html může vypadat třeba jako tady.

Pokud jde o cgi, důležité je mít v <Directory> části AddHandler cgi-script .cgi. Tím je řečeno, že se za CGI program bude považovat každý spustitelný soubor (s právem spuštění), který má příponu .cgi. Pokud chcete používat více přípon, třeba .py pro python, přidejte je oddělené mezerou.
Druhá bezpodmínečná záležitost je mít v řádku s Options volbu ExecCGI, která povoluje spouštění CGI programů.

Zbytek nastavení se týká fungování modulu userdir nebo nějakých jiných modulů a nastavení Apache. To můžete nechat tak, jak to máte přednastaveno po instalaci.

Kdybyste měli konfigurací nějaké problémy, oficiální dokumentaci k CGI najdete na httpd.apache.org/docs/2.2/howto/cgi.html.

Příprava příkladů

V této kapitole uvidíte 4 příklady, které sdílejí následující zdrojový kód.

  1. /*------------------------------------------------*/
  2. /* 45cgi/html.h                                   */
  3. #include <stdio.h>
  4. void htmlStart(char *title);
  5. void htmlEnd(void);
  6. void htmlForm(char *method);
  7. void htmlFormWithFile();
  8. /*------------------------------------------------*/

V html.h jsou deklarované funkce, které vypisují HTML. Funkce htmlStart() začíná vypsáním hlavičky a nezbytného prázdného řádku za hlavičkou a pak vypíše značky pro začátek HTML s titulkem a nadpisem. Funkce htmlEnd() vypíše značky pro konec HTML dokumentu.

Funkce htmlForm() a htmlFormWithFile() vypíšou HTML značky pro formuláře s dvěma inputy (pro jméno a příjmení). Druhá funkce obashuje navíc input pro odeslání souboru.

Všechny funkce odesílají výstup do standardního výstupního proudu, což v případě CGI bude znamenat do prohlížeče klienta.

  1. /*------------------------------------------------*/
  2. /* 45cgi/html.c                                   */
  3. #include "html.h"
  4.  
  5. void htmlStart(char *title)
  6. {
  7.     /* hlavicky */
  8.     printf("Content-Type: text/html;charset=UTF-8\r\n");
  9.     printf("\r\n");
  10.  
  11.     /* html */
  12.     printf("<html>\n\t<head>\n\t\t<title>%s</title>\n\t</head>\n", title);
  13.     printf("\t<body>\n");
  14.     printf("\t\t<h1>%s</h1>\n", title);
  15. }
  16.  
  17. void htmlEnd(void)
  18. {
  19.     printf("\t</body>\n");
  20.     printf("</html>\n");
  21. }
  22.  
  23. void htmlForm(char *method)
  24. {
  25.     printf("\t\t<form method='%s' action='#'>\n", method);
  26.     printf("\t\t\t<fieldset>\n");
  27.     printf("\t\t\t\tJméno: <input type='text' name='jmeno' value='' /><br />\n");
  28.     printf("\t\t\t\tPříjmení: <input type='text' name='prijmeni' value='' /><br />\n");
  29.     printf("\t\t\t\t<input type='submit' />\n");
  30.     printf("\t\t\t</fieldset>\n");
  31.     printf("\t\t</form>\n");
  32. }
  33.  
  34. void htmlFormWithFile()
  35. {
  36.     printf("\t\t<form method='post' enctype='multipart/form-data' action='#'>\n");
  37.     printf("\t\t\t<fieldset>\n");
  38.     printf("\t\t\t\tJméno: <input type='text' name='jmeno' value='' /><br />\n");
  39.     printf("\t\t\t\tPříjmení: <input type='text' name='prijmeni' value='' /><br />\n");
  40.     printf("\t\t\t\tSoubor: <input type='file' name='soubor' value='' /><br />\n");
  41.     printf("\t\t\t\t<input type='submit' />\n");
  42.     printf("\t\t\t</fieldset>\n");
  43.     printf("\t\t</form>\n");
  44. }
  45. /*------------------------------------------------*/

Druhá sada pomocných funkcí je deklarována v common.h.

  1. /*------------------------------------------------*/
  2. /* 45cgi/common.h                                 */
  3. #include <stdio.h>
  4.  
  5. void usage();
  6. void printEnvironmentVals();
  7. void printAllEnvironmentVals(char **env);
  8. void printPostData();
  9. /*------------------------------------------------*/

Funkce printEnvironmentVals() vypíše dvě nejzajímavější proměnné prostředí: QUERY_STRING a REQUEST_METHOD. Tyto proměnné prostředí definuje server pro každý spuštěný CGI program. Pokud funkce nenajde QUERY_STRING, pak zřejmě není program spouštěn jako CGI. Vypíše se varování a program se ukončí.

Funkce printAllEnvironmentVals() se bude používat pro výpis všech proměnných prostředí.

Funkce printPostData() vypíše do elementu <pre> vše, co přečte ze standardního vstupu. Internetové prohlížeče obvykle ignorují nové řádky v HTML dokumentech, ale v elementu <pre> je neignorují a text podle nich zalamují.
Do standardního vstupu dostane CGI program vše, co je posláno metodou POST. O POST ještě bude řeč.

  1. /*------------------------------------------------*/
  2. /* 45cgi/common.c                                 */
  3. #include <stdlib.h>
  4. #include "common.h"
  5.  
  6. void usage()
  7. {
  8.     printf("Tento program by měl být spouštěn jako CGI.\n");
  9. }
  10.  
  11. void printEnvironmentVals() {
  12.     char *qstring, *method;
  13.     qstring = getenv("QUERY_STRING");
  14.     if (!qstring) {
  15.         usage();
  16.         exit(EXIT_FAILURE);
  17.     }
  18.  
  19.     method = getenv("REQUEST_METHOD");
  20.     if (!method)
  21.         method = "-";
  22.  
  23.     printf("\t\t<p>QUERY_STRING: %s</p>\n", qstring);
  24.     printf("\t\t<p>REQUEST_METHOD: %s</p>\n", method);
  25. }
  26.  
  27.  
  28. void printAllEnvironmentVals(char **env) {
  29.     int i = 0;
  30.     while (env[i++]) {
  31.         printf("\t\t%s<br />\n", env[i - 1]);
  32.     }
  33. }
  34.  
  35. void printPostData() {
  36.     char buff[1024];
  37.     printf("\t\t<pre>\n");
  38.     while (fgets(buff, sizeof(buff), stdin) != NULL) {
  39.         printf("%s", buff);
  40.     }
  41.     printf("\t\t</pre>\n");
  42. }
  43. /*------------------------------------------------*/

Proměnné prostředí

Podívejte se na první příklad CGI programu. Nejdříve vypíše hlavičku Content-Type, pak začátek HTML. Druhá funkce vypíše proměnné prostředí, které v Linuxu můžete „nestandardně“ získat jako třetí argument funkce main(). Poslední volaná funkce HTML uzavře.

  1. /*------------------------------------------------*/
  2. /* 45cgi/example1.c                               */
  3. #include "common.h"
  4. #include "html.h"
  5.  
  6. int main(int arg, char *argv[], char *env[])
  7. {
  8.     htmlStart("Example 1");
  9.     printAllEnvironmentVals(env);
  10.     htmlEnd();
  11.  
  12.     return 0;
  13. }
  14. /*------------------------------------------------*/

Program přeložte a přesunte do adresáře /home/username/public_html/ pod jménem example1.cgi. Koncovka .cgi je nezbytná, protože takto koncovka byla nastavená v konfiguraci Apache, viz AddHandler.
Pak už můžete spustit CGI program na adrese http://localhost/~username/example1.cgi.

Výsledek by měl vypadat nějak takto:

Example 1

HTTP_HOST=localhost
HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HTTP_ACCEPT_LANGUAGE=cs,en-us;q=0.7,en;q=0.3
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_DNT=1
HTTP_CONNECTION=keep-alive
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SERVER_SIGNATURE=
Apache/2.4.6 (Linux/SUSE) Server at localhost Port 80

SERVER_SOFTWARE=Apache/2.4.6 (Linux/SUSE)
SERVER_NAME=localhost
SERVER_ADDR=::1
SERVER_PORT=80
REMOTE_HOST=localhost
REMOTE_ADDR=::1
DOCUMENT_ROOT=/srv/www/htdocs
REQUEST_SCHEME=http
CONTEXT_PREFIX=/~username
CONTEXT_DOCUMENT_ROOT=/home/username/public_html
SERVER_ADMIN=[no address given]
SCRIPT_FILENAME=/home/username/public_html/example1.cgi
REMOTE_PORT=33143
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/~username/example1.cgi
SCRIPT_NAME=/~username/example1.cgi

Pokud se vám místo html stránky stáhne .cgi program, pak mátě špatně nakonfigurovaný Apache.

Program si můžete spustit i z příkazové řádky. Uvidíte, že dostanete seznam úplně jiných proměnných prostředí (například SHELL, USER, TMPDIR, HOME atd.).

CGI program dostane od serveru jinou sadu proměnných prostředí. Nejzajímavější je například QUERY_STRING, ve kterém jsou parametry předávané v URL požadavku (vše za otazníkem, viz dále), nebo REQUEST_METHOD.

REQUEST_METHOD určuje typ požadavku. Mezi nejpoužívanější patří GET a POST. POST se používá pro odesílání formulářů (o tom budou další příklady).

REMOTE_ADDR obsahuje IP adresu uživatele, který požádal přes web o spuštění CGI programu. SERVER_ADDR je adresa serveru, na kterém CGI program běží. V příkladu výše se jedná o IPv6 adresy, ale pravděpodobněji uvidíte IPv4 adresy. Když třeba otevřete stránku http://127.0.0.1/~username/example1.cgi, bude IP adresa 127.0.0.1 (REMOTE_ADDR i SERVER_ADDR, protože se připojujete k serveru ze svého vlastního počítače).

Proměnná HTTP_ACCEPT_LANGUAGE obsahuje seznam jazyků, které by si prohlížeč přál. Jde o informace získané z hlaviček odeslaných prohlížečem. Význam dalších proměnných prostředí si už domyslíte.

Požadavek GET

V následujícím příkladu trochu přiblížím GET požadavek. Program zase nejdříve vypíše hlavičky a začátek HTML, pak dvě nejzajímavější proměnné prostředí, formulář s method="get" a nakonec vypíše všechny proměnné prostředí jako předchozí příklad.

/*------------------------------------------------*/
/* 45cgi/example2.c                               */
#include "common.h"
#include "html.h"

int main(int argc, char *argv[], char *env[])
{
    htmlStart("Example 2");
    printEnvironmentVals();
    htmlForm("get");
    printAllEnvironmentVals(env);
    htmlEnd();

    return 0;
}
/*------------------------------------------------*/

V prohlížeči, na adrese http://localhost/~username/example2.cgi, uvidíte něco takového:

Example 2

QUERY_STRING:

REQUEST_METHOD: GET

Jméno:
Příjmení:

… výstup zkrácen …

Tady formulář odeslat nemůžete, na mém webhostingu nejdou spouštět CGI programy :(

Pro zajímavost se můžete podívat na HTML pro formulář. Jestli vás zajímají podrobnosti o tom, jak se píšou HTML formuláře, budete si to muset nastudovat někde jinde. HTML tu popisovat nebudu.

<form method='get' action='#'>
    <fieldset>
        Jméno: <input type='text' name='jmeno' value='' /><br />
        Příjmení: <input type='text' name='prijmeni' value='' /><br />
        <input type='submit' />
    </fieldset>
</form>

Pokud do formuláře zadáte jméno Julius a příjmení Caesar a formulář odešlete, budete přesměrováni na adresu http://localhost/~username/example2.cgi?jmeno=Julius&prijmeni=Caesar

To, co je za otazníkem, se stane hodnotou proměnné prostředí QUERY_STRING. REQUEST_URI bude obsahovat "/~username/example2.cgi?jmeno=Julius&prijmeni=Caesar". Z těchto proměnných prostředí můžete vyčíst data, která uživetel formulářem odeslal.

Example 2

QUERY_STRING: jmeno=Julius&prijmeni=Caesar

REQUEST_METHOD: GET

Jméno:
Příjmení:

… výstup zkrácen …
REQUEST_METHOD=GET
QUERY_STRING=jmeno=Julius&prijmeni=Caesar
REQUEST_URI=/~username/example2.cgi?jmeno=Julius&prijmeni=Caesar
SCRIPT_NAME=/~username/example2.cgi

Při čtení těchto dat buďte opatrní. Uživatel/cracker může do adresního řádku napsat cokoliv, nemusí ani odesílat formulář.

Zkuste si do jména zadat Žluťoučký kůň a příjmení nechat prázdné. Query string pak bude vypadat tatko:

jmeno=%C5%BDlu%C5%A5ou%C4%8Dk%C3%BD+k%C5%AF%C5%88&prijmeni=

Co to? Co to? Místo mezery je plus, místo Ž je %C5%BD. Nějak se nám ty data zakódovali. Nj, nějak se musí udržet pořádek a odlišit jména proměnných od jejich hodnot. Každý ne-ASCII znak je zapsán pomocí procenta a hexadecimální reprezentace kódu. A mezera se zapisuje jako plus.

Není vám to %C5%BD povědomé? Vzpomeňte si na příklad s tabulkou UTF-8 znaků. Znak Ž se skládá právě ze dvou bajtů, C5 a BD. To nám to ale všechno do sebe hezky zapadá :-).

Hodnotu posílá prohlížeč na server zakódovanou v UTF-8, protože server, resp. náš CGI program, posílá hlavičku Content-Type: text/html;charset=UTF-8, kterou říká, že bude posílat HTML dokument v kódování UTF-8. Prohlížeč pak komunikuje ve stejném kódování.

Než se pustíte do psaní funkce na parsování query stringu, vyčkejte do kapitoly o FastCGI, kde vám předsavím knihovnu, která to udělá za vás.

Požadavek POST

Další příklad je stejný jako předchozí, pouze mění metodu formuláře z GET na POST. A navíc přibylo volání funkce, která načte a zobrazí data ze standardního vstupu stdin. Formulář vypadá na první pohled stejně, ale po odeslání dostanete něco trochu jiného.

  1. /*------------------------------------------------*/
  2. /* 45cgi/example3.c                               */
  3. #include "common.h"
  4. #include "html.h"
  5.  
  6. int main(int argc, char *argv[], char *env[])
  7. {
  8.     htmlStart("Example 3");
  9.     printEnvironmentVals();
  10.     printPostData();
  11.     htmlForm("post");
  12.     printAllEnvironmentVals(env);
  13.     htmlEnd();
  14.  
  15.     return 0;
  16. }
  17. /*------------------------------------------------*/

Tohle uvidíte po odeslání:

Example 3

QUERY_STRING:

REQUEST_METHOD: POST

jmeno=Julius&prijmeni=Caesar

Jméno:
Příjmení:

… výstup zkrácen …
REQUEST_METHOD=POST
QUERY_STRING=
REQUEST_URI=/~username/example3.cgi
SCRIPT_NAME=/~username/example3.cgi

Tentokráte je proměnná prostředí QUERY_STRING prázdná. Formulář vás odešle na stránku http://localhost/~username/example3.cgi, tj. bez query stringu v URL.

Data z formuláře jsou CGI programu předány do standardního vstupu. POST má oproti GETu tu výhodu, že může obsahovat téměř libovolně dlouhá data. GET požadavek je omezen jen na cca 8KiB, ačkoliv některé (staré) prohlížeče omezují délku URL na pouhých 255 znaků. POST takové omezení nemá.

Nevýhoda POSTu je, že nemůžete zkopírovat URL a někomu ho poslat, protože query string není součástí URL. Je to ale i jeho výhoda, protože požadavky POST by se správně měli používat na akce, které něco mění (ukládají do databáze, přihlašují uživatele atp), kdežto požadavky GET by měli jen ovlivnit, co se uživateli zobrazí. To už ale zacházím příliš daleko do popisu fungování webu.

Požadavek se souborem

Poslední ukázka vychází zase z té předešlé. Pouze ve formuláři přibylo políčko na odeslání souboru. Taky se musí v elementu form nastavit tag enctype na multipart/form-data. Jinak (v jiném kódování) nelze soubor odeslat. Data budou programu CGI předána zase ve standardním vstupu, ale budou vypadat (budou zakodována) trochu jinak.

  1. /*------------------------------------------------*/
  2. /* 45cgi/example4.c                               */
  3. #include "common.h"
  4. #include "html.h"
  5.  
  6. int main(int argc, char *argv[], char *env[])
  7. {
  8.     htmlStart("Example 4");
  9.     printEnvironmentVals();
  10.     printPostData();
  11.     htmlFormWithFile();
  12.     printAllEnvironmentVals(env);
  13.     htmlEnd();
  14.  
  15.     return 0;
  16. }
  17. /*------------------------------------------------*/

Formulář vypadá takto:

Example 4

QUERY_STRING:

REQUEST_METHOD: GET

Jméno:
Příjmení:
Soubor:

… výstup zkrácen …
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/~username/example4.cgi
SCRIPT_NAME=/~username/example4.cgi

A po odeslání se souborem hello-world.txt, který obsahuje jen řádku Hello World!:

Example 4

QUERY_STRING:

REQUEST_METHOD: POST

-----------------------------3613503935902733491650184458
Content-Disposition: form-data; name="jmeno"

Julius
-----------------------------3613503935902733491650184458
Content-Disposition: form-data; name="prijmeni"

Caesar
-----------------------------3613503935902733491650184458
Content-Disposition: form-data; name="soubor"; filename="hello-world.txt"
Content-Type: text/plain

Hello World!

-----------------------------3613503935902733491650184458--

Jméno:
Příjmení:
Soubor:

… výstup zkrácen …
REQUEST_METHOD=POST
QUERY_STRING=
REQUEST_URI=/~username/example4.cgi
SCRIPT_NAME=/~username/example4.cgi

Při tomto způsobu kódování začíná každá proměnná řádkou s několika mínusy a pak náhodně vygenerovaným číslem. Pak následují hlavičky (Content-Disposition, Content-Type), prázdná řádka a hodnota proměnné nebo obsah souboru.
Za poslední proměnnou (v našem případě souborem) je tato řádka ještě doplněná o dva mínusy.

Pokud se pokusíte poslat binární soubor, vypíše vám example4.cgi všechna binární data (vyzkoušejte na vlastní nebezpečí :-).

V této kapitole jste se naučili psát CGI programy, jak posílat POST a GET požadavky, jak nastavit encoding ve formuláři abyste mohli posílat soubory a kde získat odeslaná data.

Jak asi tušíte, vyparsovat taková data nebude nic snadného. Sice už umíte regulární výrazy, ale to vám v případě binárních souborů nebude moc platné (protože binární soubor neobsahuje jen text a reg. výrazy by kvůli tomu selhaly). Naštěstí existují zdarma dostupné knihovny, které vám s tím pomůžou. O jedné z nich vám povím v kapitole o Fast CGI.

Komentář Hlášení chyby
Vytvořeno: 6.10.2014
Naposledy upraveno: 20.11.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..