Common Gateway Interface


Riku Nykänen

Ohjelmistotekniikan seminaari
12.12.1997

Jyväskylän Yliopisto
Matematiikan laitos




Sisältö


1. Johdanto

Tämä seminaariesitelmä käsittelee CGI-ohjelmoinnin perusteita ja pyrkii opastamaan CGI-ohjelmien kirjoittamista aloittavia. Se ei kuitenkaan käsittele yhtä CGI-ohjelmoinnin tärkeää osa-aluetta, tietoturvaa, koska siihen syventyminen edellyttäisi parempia pohjatietoja muilta alueilta. Tekstissä tarkastellaan myös SSI:a, jotka eivät varsinaisesti ole CGI-ohjelmia, mutta jotka usein yhdistetään CGI-ohjelmointiin ja auttavat välttymään turhien ohjelmien kirjoittamiselta.
Common Gateway Interface on suomennettu yleinen yhdyskäytävä liitäntä ("Tietotekniikan tietosanakirja", Suomen ATK-kustannus 1997, 2. uudistettu laitos). Koska englanninkielinen termi on kuitenkin yleisesti käytössä ja tunnettu, käytän koko ajan alkuperäistä nimitystä.
Tekstissä keskitytään CGI-ohjelmien toteutukseen Unix-pohjaisille palvelimille, koska suurin osa käytössä olevista WWW-palvelimista on Unix/Linux-palvelimia. NT-palvelimet sivutetaan lähes täysin, mutta liitteiden WWW-osoitteista löytyy tietoa CGI-ohjelmoinnista Windowsille ja MacOs:lle.

2. Yleistä

Common Gateway Interface (CGI) on rajapinta, jolla voidaan luoda interaktiivisia sovelluksia www-sivuille. CGI-rajapintaa käyttäviä ohjelmia kutsutaan usein CGI-ohjelmiksi, niin myös tässä tekstissä. Ohjelmat voivat ottaa vastaan käyttäjän antamaa tietoa ja lähettää sitten vastauksensa tiedot lähettäneelle selaimelle. Eli CGI on se palvelimen osa, jolla saadaan palvelimella olevat ohjelmat käyttöön. Sen avulla palvelin voi kutsua ulkoisia ohjelmia ja välittää niille käyttäjän antamaa tietoa. Ohjelman ei tarvitse välttämättä lähettää vakiomuotoista tulostetta, vaan se voi generoida tulosteen käyttäjän syötteen mukaan. Esimerkkejä CGI-ohjelmista ovat mm. hakurobotit, vieraskirjat ja laskurit. Verkkokaupankäynnin lisääntyessä, lisääntyy myös myyntisovellusten tarve. CGI:tä käyttämällä voidaan helposti toteuttaa näitä sovelluksia.

2.1. Kielet

WWW-palvelin ei välitä kielestä, jolla CGI-ohjelma on kirjoitettu. Yleisimmin Unix-palvelimissa käytetty kieli on Perl. Se on tulkattava kieli, joka vaatii palvelimeen oman Perl-tulkin. Perl yhdistetäänkin usein CGI-ohjelmointiin ja CGI-ohjelmointi Perliin. Muita Unixissa CGI-ohjelmointiin käytettyjä kieliä ovat C, C++, Tcl ja Python. NT-palvelimissa käytetään yleensä Visual Basicia, Delphiä, Perlia C:tä ja C++:aa. Macintoshissa käytetään C:tä, C++:aa ja AppleScriptiä. Yleisesti CGI-ohjelmien kirjoittamiseen riittää se, että ohjelmointikieli osaa ottaa vastaan tietoa palvelimelta ja lähettää sitä takaisin.
Vaikka Perl on ollutkin käytetyin ohjelmointikieli CGI-ohjelmoinnissa, on C++:n käyttö koko ajan lisääntynyt. Perlin etu ja haitta on se, että se on tulkattava kieli. Suuremmissa sovelluksissa se on hidas ja vie paljon muistia, mutta Perl-tulkkeja on saatavissa lähes jokaiseen käyttöjärjestelmään. Suosionsa ansiosta Perlille on olemassa paljon valmiita aliohjelmakirjastoja, jotka helpottavat huomattavasti ohjelmien kirjoittamista. Myös CGI:ssä tärkeät merkkijonojen käsittely operaatiot ovat Perlissä huomattavasti C++:a paremmin toteutettu. C++:n etuina Perliin verrattuna on nopeus ja käännettyjen ohjelmien pieni koko. Myös C++:lle on tullut valmiita aliohjelmakirjastoja syötteiden käsittelyyn. C++:n vahvuutena ovat myös Perliä paremmat olio-ominaisuudet. Kielen valinta on kuitenkin pitkälle tapauskohtaista, johon vaikuttavat myös ohjelmoijan taidot. On luonnollista käyttää kieltä, jonka hallitsee hyvin kuin lähteä opettelemaan uutta kieltä ohjelman kirjoittamisen yhteydessä.

2.2. CGI vai JavaScript?

CGI:n ja JavaScriptin vastakkain asettelu on viime aikoina lisääntynyt. Sinänsä ne eivät taistele toisiaan vastaan CGI:n ollessa palvelimen käyttämä rajapinta ja JavaScript asiakkaan selaimen tulkkaama ohjelmointikieli. JavaScript sopii CGI:tä paremmin WWW-sivuille tehtäviin ohjelmiin ja CGI paremmin käsittelemään sivuilta saatavaa tietoa. Eräs ratkaiseva tekijä valintaa suoritettaessa on se, että CGI-ohjelmat pystyvät tallettamaan tietoa palvelimelle toisin kuten JavaScript-ohjelmat.

2.3. Palvelin

Ohjelmat sijoitetaan yleensä palvelimella cgi-bin-nimiseen hakemistoon. Tällöin tietoturvaan liittyviä ongelmia voidaan helpommin valvoa. Yleensä cgi-bin-hakemistolle annetaan suuremmat oikeudet kuin html-hakemistoille. Yleensä cgi-bin-hakemistosta ei saada tiedostostolistausta, mikäli tiedosto index.html puuttuu. Unixissa pitää lisäksi varmistaa, että ohjelmatiedostoon on annettu kaikille luku- ja suoritusoikeus. Myös mahdollisiin talletustiedostoihin on annettava kirjoitusoikeus. Turvallisuuden parantamiseksi tämä voidaan myös antaa ohjelmassa ennen talletusta ja ottaa pois, kun talletus on suoritettu.

3. Parametrien välitys ohjelmalle

CGI-ohjelmalle parametrit välitetään joko URLin avulla lähetystapaa GET käyttäen tai lomakkeen lähettämän pyynnön tekstirungossa lähetystapaa POST käyttäen. POST-tapa on yleisempi, koska se ei aseta yhtä suuria rajoituksia syötteen koolle kuin GET. Lähetystavan ohjaa <form>-komennon method-attribuutti.
Esimerkiksi

3.1. URL:n avulla

Lähetystavalla GET vastaanottava ohjelma saa lomakkeen sisällön yhtenä merkkijonona parametrikseen. Merkkijono on luettavissa ympäristömuuttujasta QUERY_STRING. Merkkijonossa kaikki parit nimi=arvo on eroteltu toisistaan &-merkillä. Koska parametrit lähetetään osana URL-osoitetta, ei mukana saa olla välilyöntejä eikä muita URL:n käyttämiä erikoismerkkejä. Tämän vuoksi "skandit" ja monet erikoismerkit esitetään heksakoodattuina ja välilyönnit esitetään +-merkkeinä.
Esimerkiksi kutsuttaessa vieraskirjaa ylläpitävää CGI-ohjelmaa saattaisi URL näyttää seuraavalta:

Tällöin etunimet-kentässä välitetään Arto Juhani, sukunimi-kentässä Mäkinen ja syntpv-kentässä 24/12/70.
Kutsu suoritettaessa palvelin sijoittaisi URLin parametri osan ympäristömuuttujaan QUERY_STRING. Eli sen sisältö esimerkki tapauksessa olisi
Jonojen purkamiseen on useita valmiita aliohjelmakirjastoja, jotka muuttavat nimi-arvo-parit selkeämmin luettavaan muotoon. Näistä kirjastoista lisää myöhemmin.
Lähetystavalla GET lähetettäessä on huomattava, että koko lomakkeen sisältö näkyy URL:ssä. Myös salasanakenttien sisältö näkyy tällöin URL:ssä salaamattomana. Tämän vuoksi salasanoja ja paljon tietoja sisältävien lomakkeiden lähetyksessä on parempi käyttää POST-lähetystapaa.

3.2. POST-metodilla

POST-tapaa käytettäessä siirtyvät parametrit HTTP-pyynnön runko-osassa. Tällöin palvelin ohjaa nämä syöttövirtaan (STDIN). Yhtenäisyyden vuoksi myös POST-metodilla lähetetyt tiedot ovat samassa muodossa kuin GET-metodilla lähetetyt. On huomattava, että jos lähetystapaa ei erityisesti määritellä niin oletusarvona on GET.

3.3. Aliohjelmakirjastoja parametrien purkamiseen ympäristömuuttujista

Perlille on saatavana mm. seuraavat aliohjelmakirjastot
Cgi-lib.pl on helppokäyttöinen aliohjelmakirjasto Perlille. Se on saatavana WWW-osoitteesta http://cgi-lib.stanford.edu/cgi-lib/.
CGI.pm on oliopohjainen moduli Perlille. Hyvin dokumentoitu moduli on löytyy URLista http://www-genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html.
C:lle ja C++:lle löytyy myös useita vastaavia kirjastoja.
Un-CGI muuttaa POST-metodilla lähetetyn lomakkeen tiedot ympäristömuuttujiksi WWW_kentännimi, jossa ympäristömuuttuja sisältää kentän arvon valmiiksi dekoodattuna. Se on saatavilla WWW-osoitteesta http://www.midwinter.com/~koreth/uncgi.html.
LibCGI++ on oliopohjainen luokkakirjasto, joka valmiiksi dekoodaa myös kenttien arvot. Olio-ominaisuuksiensa vuoksi toimii se ainoastaan C++-kääntäjissä. LibCGI++:aan voi tutustua tarkemmin osoitteessa http://www.ncsa.uiuc.edu/People/daman/cgi++/.

4. Tulostus ja virtuaalidokumentit

CGI-ohjelma voi tulostuksessa osoittaa selaimelle jonkun olemassa olevan dokumentin tai luoda itse virtuaalisen dokumentin. UNIXissa ohjelmat tulostavat kaksiosaisen tietolohkon. Lohkon ensimmäinen osa on HTTP-otsikko. Siinä kerrotaan sitä seuraavan datan muoto, joka muodostaa toisen lohkon.
Ohjelman tulee aloittaa tulostus HTTP-dataotsikon tulostuksella. Yleensä tulostus aloitetaan dataosan tyypin määrittelemisellä. Tyypin määrittelyssä käytetään sisältölajeja, jotka läheisesti muistuttvat MIME-tyyppejä. Yleisimmin käyttetyjä sisältölajeja ovat text (alalajeja mm. html ja plain) sekä image (alalajeja mm. jpeg ja gif). Kaikki tulostus tapahtuu tulostusvirran (STDOUT) kautta.
Ohjelma, joka sen URLia kutsuttaessa tulostaisi näytölle tekstin "Hello World", voidaan toteuttaa Unixissa komentotiedostona seuraavaalla tavalla:

Esimerkissä aluksi tulostettavan tiedoston muodoksi määritellään HTML-teksti. Tämän jälkeen tulostetaan tyhjä rivi, joka erottaa HTTP-otsikon dataosasta. Tämän jälkeen kaikki ohjelman tulosteet tulkitaan HTML-tekstiksi.
Äskeisen esimerkki ohjelman täydellinen HTTP-otsikko ja virtuaalinen HTML-dokumentti olisi seuraavanlainen:
Tulostettaessa harvinaisempia sisältölajeja, kannattaa tutkia mitä sisältölajeja asiakkaan selainohjelma tukee. Hyväksytyt sisältölajit löytyvät ympäristömuuttujasta HTTP_ACCEPT.
Tulostus voi olla myös tyyppiä Location, jolloin määritellään avattava dokumentti. Esimerkiksi tulostetaan samassa hakemistossa oleva tuloste.html.

Tulostettavan dokumentin osoite voi olla joko täydellinen tai suhteellinen.

5. Lomakkeet

Yleisin CGI-ohjelmien käyttömuoto laskureiden lisäksi on erilaisten lomakkeiden käsittely. Lomakkeet ovat HTML-kieltä, joten niiden syntaksiin ei tässä yhteydessä puututa.

Lomakkeiden komponenteista yksinkertaisimpia syötteenä ovat normaalit teksti- ja salasanakentät. Näissä ainoastaan erikoismerkit tulevat QUERY_STRING:ssä heksakoodattuna tekstinä. Erikoismerkeiksi luokitellaan normaaleja aakkosia (ei sisällä "skandeja"), numeroita sekä pistettä (.), väliviivaa (-) ja alaviivaa (_) lukuunottamatta lähes kaikki merkit. Textarea-komponentti toimii muuten samalla tavalla, mutta rivin vaihto lisää merkkijonoon "CR"- ja "LF"-merkit (...%0D%0A...).
Radiopainikkeissa (radiobutton) pitää kaikilla saman ryhmän painikkeilla olla asetettuna jokin arvo value-attribuuttia käyttäen. Tällöin QUERY_STRING sisältää ryhmän nimen jälkeen valitun radiopainikkeen arvon. Samoin toimivat valintaruudut (checkbox). Koska jokainen valintaruutu on eri niminen, ei niissä välttämättä tarvitse käyttää value-attribuuttia. Mikäli valintaruutu on rastitettu, sisältää QUERY_STRING valintaruudun arvona "on", jos mitään arvoa ei ole erikseen asetettu. Jos ruutua ei ole valittu, ei arvoa lähetetä.
Click here for Picture
<select>-komento toimii joko kuten valintaruutu tai kuten radiopainike. Tämä voidaan valita multible-attribuuttia käyttämällä. Jos sallitaan usempi yhtäaikainen valinta, niin <select>-komento toimii kuten valintaruutu, muuten se toimii kuten radiopainike. Jos valintaruudun alkiolle ei ole määritelty erillistä arvoa value-attribuuttia käyttäen, lähetetään palvelimelle alkion arvona <option>-komennon sisältö.
Esimerkiksi seuraavan kaltaisesta lomakkeesta tehtäessä kuvan mukaiset valinnat, olisi QUERY_STRINGin sisältönä:

Lomakeen HTML-koodi on seuraavanlainen:

6. Ympäristömuuttujat

Unix-palvelimissa suurin osa CGI-ohjelmien tarvitsemasta tiedosta on saatavana ympäristömuuttujien kautta. CGI-ohjelmat hakevat tiedon ympäristömuuttujista aivan kuten muutkin ohjelmat. Seuraavassa on listattu yleisesti käytettävissä olevat ympäristömuuttujat. Näiden lisäksi joillakin palvelimilla (mm. Apache) on omia ympäristömuuttujiaan.

Esimerkiksi Jyväskylän yliopiston WWW-palvelin antoi seuraavanlaisen tulostuksen kutsuttaessa yksinkertaista CGI-ohjelmaa, joka tulostaa ympäristömuuttujien arvoja.
Click here for Picture

7. SSI

Server Side Includet (SSI) ovat HTML-dokumenttiin sijoitettavia komentoja, joilla voidaan suorittaa ohjelmia tai tulostaa muuttujien arvoja. Vaikka SSI:t eivät teknisesti ole CGI-ohjelmia, ne usein auttavat välttämään CGI-ohjelmien kirjoittamisen, kun halutaan CGI:n kaltaista tietoa tai tulostuksia. Aina ei ole järkevää generoida uudestaan koko dokumenttia, vaan ainoastaan tietyt kohdat. SSI:t sijoittavat dokumenttiin käskyjen tilalle niiden tulosteen. Kun selaimella katsotaan SSI-käskyn sisältävän ohjelman lähdekoodia, ei siinä näy alkuperäistä komentoa, vaan sen tuloste.
Kaikki palvelimet eivät kuitenkaan tue näitä käskyjä. Erityisesti CERNin palvelin ei tunne SSI-käskyjä ilman määritysten muuttamista. Komentojen käyttöön ottamisessakin on erilaisia tapoja. Palvelimeen saattaa esimerkiksi olla määritelty, että komentoja voidaan ajaa vain sivuilta, joiden tarkennin on shtml.
Järjestelmällä on myös haittoja. Asiakkaan pyytäessä dokumenttia SSI:tä kayttävältä palvelimelta, käy palvelin läpi koko dokumentin etsien SSI-komentoja. Dokumenttien tutkiminen ja komentojen suorittaminen kuormittavat palvelinta. Lisäksi SSI:n käyttö antaa mahdollisuuksia väärinkäytöksiin. Tietämätön käyttäjä saattaa kutsua SSI:llä järjestelmäkomentoja, jotka paljastavat luottamuksellista tietoa palvelimesta ja sen käyttäjistä. Näin ollen SSI saattaa olla kätevä, mutta sitä on käytettävä varovasti.

7.1. SSI:n peruskomennot

SSI:n peruskomentoja ei ole paljon, mutta ne saattavat säästää yksinkertaisimpien CGI-ohjelmien kirjoittamiselta. Kaikki SSI-komennot ovat muotoa:

SSI:n peruskomennot ovat
lisää dokumenttiin SSI-ympäristömuuttujan tai muun ympäristömuuttujan arvon.
Esimerkiksi
lisää nimetyn dokumentin nykyiseen dokumenttiin. Argumenttia file käytettäessä pitää lisättävän tiedoston löytyä samasta hakemistosta kuin komentoa kutsuva dokumentti.
Esimerkiksi
tulostaa annetun tiedoston koon tavuina.
tulostaa tiedoston viimeisimmän muutospäivän. Päiväystä voidaan muotoilla config-komennon suorittaa ulkoisia ohjelmia ja lisää dokumenttiin niiden tulostuksen. Esimerkiksi
muokkaa SSI:n tulostuksia. Argumentti errmsg määrittää oletus virheilmoituksen. Sizefmt määritää tiedoston koon muodon. Vaihtoehdot ovat abbrev (pyöristettynä kilotavun tarkkuudelle) ja bytes (koko tavuina). Timefmt muotoilee ajan ja päivämäärän esitystä. Komennon käyttöä esitelty luvussa SSI:n tulostuksen aikaformaatti.

7.2. SSI:n ympäristömuuttujat

7.3. SSI:n tulostuksen aikaformaatti

SSI-komentojen tulostaessa aikaa ja päivämääriä voidaan tulostusformaattia muotoilla config-komennon timefmt-parametria käyttämällä. Taulukossa 1 esitellään formaatin määrittelyssä käytettävät koodit.

Koodi Tarkoitus

Esimerkki

%a Viikonpäivän lyhenne
Sun
%A Viikonpäivä Sunday
%b

Kuukauden lyhenne (myös %h)

Jan
%B Kuukausi January
%d Päivä 08
%D Päiväys muodossa "%m/%d%y" 08/11/97
%e Päivä 8 (ei 08)
%H Tunnit 0-23 14
%I Tunnit 1-12 03
%j Päivä vuodesta 325
%m Kuukausi numerona 11
%M Minuutit 07
%p AM/PM (aamupäivä/iltapäivä) AM
%r Aika muodossa "%I:%M:%S %p" 08.57.55 PM
%S Sekunnit 55
%T 24h aika muodossa "%H:%M:%S" 14.24.09
%U Viikon numero (myös %W) 38
%w Päivä viikosta (sunnuntai = 0) 2
%y Vuosi sadasta 97
%Y Vuosi 1997
%Z Aikavyöhyke GMT

Taulukko 1. timefmt-parametrin määrittelyssä käytettävät koodit.

Esimerkiksi lisättäessä HTML-koodiin

tulostuksena saadaan

8. Cookiet

Netscape esitteli cookiet vähentämään palvelimella tapahtuvaa käyttäjäkohtaisten tietojen varastointia. Cookie-järjestelmän avulla sivukohtainen tieto varastoidaan käyttäjän koneelle. Koska cookiet ovat asiakkaan kovalevyllä, niin ne säilyvät vaikka selain välillä suljettaisiinkin.
Cookien käyttö CGI-ohjelmalla tapahtuu seuraavasti:

8.1. Set-Cookie-kentän käyttö

Set-Cookie-otsikon syntaksi on seuraava:

Lähetettäessä ainoa pakollinen osa on muuttuja=arvo-parit. Niitä tulee olla vähintään yksi ja niiden tulee olla ensimmäisinä, muiden parametrien järjestyksellä ei ole väliä. Muuttuja ja arvo voivat olla mitä tahansa merkkijonoja, mutta ne eivät saa sisältää välilyöntejä, sarkain-merkkiä tai puolipistettä. Kyseiset merkit heksakoodataan, joten vastaanottavan ohjelman tulee ymmärtää purkaa koodaus.
Expires-attribuutti asettaa cookien voimassaoloajan. Päiväyksen jälkeen cookie ei ole enää voimassa, eikä selain lähetä sitä. Päiväyksen muoto on seuraava:
Ellei voimassaolo aikaa anneta, on cookie voimassa vain käynnissä olevan istunnon ajan.
Path-attribuutti rajaa palvelimen hakemistot, joissa kyseinen cookie on voimassa. Esimerkiksi, jos pathin arvo on /pub, lähetetään cookie joka kerta kun haetaan sivua palvelimen hakemistosta /pub tai jostain sen alihakemistosta. Polun arvo "/" tarkoittaa, että cookie on käytössä palvelimen kaikilla sivuilla. Jos path-attribuuttia ei anneta, on cookie käytössä ainoastaan sen antaneella sivulla.
Domain-attribuutti määrää niiden koneiden nettiosoitteen, joille cookie palautetaan. Osoitteessa tulee olla vähintään kaksi pistettä (..). Esimerkiksi .jyu.fi kattaisi koneet www.jyu.fi, www.math.jyu.fi ja kaikki muut .jyu.fi-päätteisen osoitteen omaavat palvelimet.
Secure-attribuutia käytettäessä cookie lähetetään ainoastaan salattua yhteyttä (SHTTP tai SSL) käytettäessä. Jos attribuuttia ei ole, lähetään cookie yhteyskäytännöstä riippumatta.
Jos kaksi cookieta ovat samannimisiä, palauttaa selain ensin tarkemmin määritellyn. Esimerkiksi, jos cookiet on asetettu:
niin selaimen pyytäessä dokumenttia /nerot-polulta, se palauttaa
Netscape Navigator 3.0 varastoi enintään 300:n cookien tiedot. Jokaisen cookien koko on rajoitettu 4 kilotavuun. Jokaista palvelinta tai toimialuetta kohti varastoidaan korkeintaan 20 cookieta. Jos määrät ylittyvät, pyyhitään yli vanhin cookie. Jos koko ylittää sallitun rajan, sovitetaan cookie maksimiin.

9. Yhteenveto

CGI on käytöllinen rajapinta WWW-sovellusten tekemiseen. Yleensä ohjelmia kirjoitettaessa vastaan tulevat ongelmat liittyvätkin muihin tekijöihin kuin varsinaisen rajapinnan toimimattomuuteen. CGI:n käyttöä tulee varmasti vähentämään IntraBuilderin kaltaisten sovellusten lisääntyminen. Toisaalta verkottumisen ja etenkin verkon kautta tapahtuvan kaupankäynnin lisääntyminen pitävät CGI:n vielä pitkään yleisenä rajapintana WWW-sovellusten ohjelmoinnissa.

Lähteet

Spainbour Stephen, Quercia Valerie, "Webmaster - Tehokäyttäjän opas", Suomen ATK-kustannus, 1997.
Niemi Juha, Kiuttu Petri, "Opeta itsellesi JavaScript", SuomenATK-kustannus, 1996.
MicroPC 11/97, "CGI:llä vuorovaikutusta www-sivuille".
NCSA, "NCSA CGI Documentation", "http://hoohoo.ncsa.uiuc.edu/cgi/", 1997.
Netscape, "HTTP Cookies", "http://home.netscape.com/newsref/std/cookie_spec.html", 1997.

Liitteet

Lisämateriaalia

Introduction to the Common Gateway Interface,
http://www.virtualville.com/library/cgi.html
CGI Securirty Tutorial,
http://www.csclub.uwaterloo.ca/u/mlvanbie/cgisec/
The Web Developer's Virtual Library,
http://WWW.Stars.com/
The CGI Resource Index,
http://www.cgi-resources.com/
Perl,
www.perl.com
Delphi 2.0 and Standard CGI,
http://www.okcom.net/~mlovell/delphiCGI.html
Microbrew CGI Tutorial (Macintosh),
http://www.networkmultimedia.com/TutPage/CHAP101.htm
NCSA HTTPd Tutorial: Server Side Includes,
http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html


(c) Riku Nykänen 1997