Jatkoa tertulle DB2, DBS ja DBA 0.05

Kaikki oikeudet pidätetään. Olen jo jonkin aikaa ihmetellyt satunnaisbittejä ja terttu on jäänyt vähemmälle huomiolle. Jos haluat lukea valmiimmista ohjelmista, tässä fort (https://moijari.com/?p=964), tässä ressu1.7 (https://moijari.com/?p=798), ja tässä ressu1.8 (https://moijari.com/?p=842). Tässä postissa esitellään uusi yritys tertusta. Ohjelma koostuu tietokannasta (DB2) ja html-lomakeohjelmasta (DBS). db_ alkuiset rutiinit kuuluvat tietokantaan ja dbs_alkuiset rutiinit html-liittymään. Postin aluksi käydään läpi DB2 rutiinit ja sitten jatketaan DBS rutiineilla. Tässä postissa on listattuna kaikki rutiinit eli periaatteessa tästä voi copy-pastata toimivan version.

Edit: Julkaisun jälkeiset muutokset

Lisätty http-versio palvelinohjelmasta. Kirjoitettu server rutiinien (http_server ja https_server) htmlin taulukon luku yksinkertaisemmaksi (while htmlparams). Lisäksi in-taulukon kokoa kasvatetaan dynaamisesti, jos on tarvetta, pituus oli ennen kovakoodattu. Jaettu sovellusta (DBA) ja palvelin osuutta (DBS) kahteen osaan. Ongelmat ovat puuttuvien toiminnallisuuksien lisäksi dba_app:in ja palvelimien pituudet koodina. Kummankin palvelimen yhteiset funktiokutsut voisi tehdä aliohjelmiksi, varsinkin jos ja kun kutsuja tulee molemmista palvelimista. Pääohjelma on tehty uudessa versiossa siten, että komentokytkimillä (–http, –https ja –port) valitaan käytettävä palvelin ja portin numero.

Lyhennetty https_server ja http_server rutiineja siirtämällä niiden yhteiset osat funktioihin. Yhteiset osat ovat: getaddrinfo, socket, bind, listen ja close, ne ovat uudessa versiossa server_-alkuisissa funktioissa. Vastaavasti https_server ja http_server rutiinit kutsuvat näitä rutiineja.

Kirjoitettu uudestaan html_printf rutiini nimellä dbs_html_printf. Uudessa versiossa puskureita kasvatetaan tarvittaessa, edellisessä versiossahan niiden pituudet olivat konstantteja. Dba_logon rutiinissa on esimerkkejä uudesta kutsusta. Myös dbs_html_set ja dbs_html_clear rutiinit on muuttuneet.

Palasteltu dba_app funktiota pienempiin kokonaisuuksiin. dba_app_value() funktioon on eroteltu varsinainen data, ja dba_app_memberid funktioon on eroteltu sarakkeen nimen tulostus. Lisäksi lisättiin käännökset sarakkeen nimelle ja sovelluksen nimelle. Seuraavassa esimerkki ruotsinkielisistä asiakas ohjelman käännöksistä:

'app' "asiakas", 'language' "sv", 'apptext' "Kundhantering"
'memberid' "asiakasnumero", 'language' "sv", 'membertext' "Kundnummer"
'memberid' "asiakkaan nimi", 'language' "sv", 'membertext' "Kundens namn"
'memberid' "asiakkaan osoite", 'language' "sv", 'membertext' "Kundens adress"
'memberid' "asiakkaan postitoimipaikka", 'language' "sv", 'membertext' "Kundens postkontor"
'memberid' "asiakkaan postiosoite", 'language' "sv", 'membertext' "Kundens postadress"
'memberid' "asiakkaan maa", 'language' "sv", 'membertext' "Kundens land"
'memberid' "asiakkaan email", 'language' "sv", 'membertext' "Kundens e-post"

Käännös asiakashallinnasta venäjäksi: (google translate)

'app' "asiakas", 'language' "ru", 'apptext' "управление клиентами"
'memberid' "asiakasnumero", 'language' "ru", 'membertext' "номер клиента"
'memberid' "asiakkaan nimi", 'language' "ru", 'membertext' "Имя Клиента"
'memberid' "asiakkaan osoite", 'language' "ru", 'membertext' "адрес клиента"
'memberid' "asiakkaan postitoimipaikka", 'language' "ru", 'membertext' "почтовое отделение клиента"
'memberid' "asiakkaan postiosoite", 'language' "ru", 'membertext' "почтовый адрес покупателя"
'memberid' "asiakkaan maa", 'language' "ru", 'membertext' "страна заказчика"
'memberid' "asiakkaan email", 'language' "ru", 'membertext' "электронная почта клиента"

Vielä sama sovellus kiinaksi (google translate). Saan näistä (venäjä ja kiina) paljon markkinointimateriaalia kommentteihin:

'app' "asiakas", 'language' "ch", 'apptext' "用戶管理"
'memberid' "asiakasnumero", 'language' "ch", 'membertext' "顧客號碼"
'memberid' "asiakkaan nimi", 'language' "ch", 'membertext' "顧客姓名"
'memberid' "asiakkaan osoite", 'language' "ch", 'membertext' "客戶地址"
'memberid' "asiakkaan postitoimipaikka", 'language' "ch", 'membertext' "客戶郵局"
'memberid' "asiakkaan postiosoite", 'language' "ch", 'membertext' "客戶的郵政地址"
'memberid' "asiakkaan maa", 'language' "ch", 'membertext' "客戶所在國家"
'memberid' "asiakkaan email", 'language' "ch", 'membertext' "客戶電子郵件"

Jatkettu dba_app:in palastelua. Tällä kertaa siitä on otettu foreign keys kappale, joka on nimetty dba_app_get_foreign_keys. Se antaa listan sovelluksen sarakkeista, joihin tarvitaan tieto mahdollisista sovelluksen ulkopuolisista kentistä.

Edellisestä päivityksestä on sen verran aikaa, että päätin postata tämän listan puolivalmisteena. Tässä on uusi tietosisältö, uudessa versiossa ovat asiakkaan tuotteen ja tilauksen lisäksi toimitus ja laskutus määritykset. dba_app_foreign_keys, dba_app_foreign_query, ja puuttumaan jää dba_app_foreign_data. Foreign keys päättelee sovellusten väliset vierasavaimet, foreign query muodostaa avaimista kyselyn ja foreign data (joka vielä puuttuu) muodostaa merkkijonon tarvittavista kentistä (jotka siis tulevat tuolta vierassovellukselta. Merkkijono luetaan app_value rutiinissa.

Siirretty http_server ja https_server rutiineista yhteiset osat dba_main rutiiniin. Lisätty automaattinen sovellusten tekemien kyselyjen ja niiden palauttaman datan tulostaminen html-sivun loppuun. Tuloste on tilaus näytöllä tämän näköinen: App tekee aika monta kyselyä, mutta kysely suoritetaan vain jos sitä ei ole aiemmin tehty. Eli sovelluskohtaiset kyselyt suoritetaan vain ensimmäisellä kerralla ja seuraavalla kerralla palautetaan vain edellinen vastaus. Tätä kyselyjen tulostusta varten on uudelleenpaketoitu db_query, db_query_first_data() ja db_data_next_data() uusiin dba_query ja dba_data alkuisiin rutiineihin. samoin kyselyä varten on lisätty uusi html_printf puskuri2, jonka koodia on myös dba_main rutiinissa.

query: 'app' "tilaus", 'appname'
data: 'app' "tilaus", 'appname' "Tilaukset"
query: 'app' "tilaus", 'language' "en", 'apptext'
query: 'memberid'
data: 'memberid' "asiakasnumero"
data: 'memberid' "asiakkaan email"
data: 'memberid' "asiakkaan maa"
data: 'memberid' "asiakkaan nimi"
data: 'memberid' "asiakkaan osoite"
data: 'memberid' "asiakkaan postinumero"
data: 'memberid' "asiakkaan postitoimipaikka"
data: 'memberid' "laskun asiakasnumero"
data: 'memberid' "laskun asiakkaan nimi"
data: 'memberid' "laskun rivinumero"
data: 'memberid' "laskun tilausnumero"
data: 'memberid' "laskun toimitusnumero"
data: 'memberid' "laskun tuotenumero"
data: 'memberid' "laskun tuotteen hinta"
data: 'memberid' "laskun tuotteen nimi"
data: 'memberid' "laskunumero"
data: 'memberid' "tilattu määrä"
data: 'memberid' "tilauksen asiakasnumero"
data: 'memberid' "tilauksen asiakkaan nimi"
data: 'memberid' "tilauksen rivinumero"
data: 'memberid' "tilauksen tuotenumero"
data: 'memberid' "tilauksen tuotteen hinta"
data: 'memberid' "tilauksen tuotteen nimi"
data: 'memberid' "tilausnumero"
data: 'memberid' "toimitettu määrä"
data: 'memberid' "toimittajan email"
data: 'memberid' "toimittajan maa"
data: 'memberid' "toimittajan nimi"
data: 'memberid' "toimittajan osoite"
data: 'memberid' "toimittajan postinumero"
data: 'memberid' "toimittajan postitoimipaikka"
data: 'memberid' "toimittajanumero"
data: 'memberid' "toimituksen asiakasnumero"
data: 'memberid' "toimituksen asiakkaan nimi"
data: 'memberid' "toimituksen rivinumero"
data: 'memberid' "toimituksen tilattu määrä"
data: 'memberid' "toimituksen tilausnumero"
data: 'memberid' "toimituksen tuotenumero"
data: 'memberid' "toimituksen tuotteen hinta"
data: 'memberid' "toimituksen tuotteen nimi"
data: 'memberid' "toimitusnumero"
data: 'memberid' "tuotenumero"
data: 'memberid' "tuotteen hinta"
data: 'memberid' "tuotteen nimi"
query: 'app', 'fromapp', 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "asiakas", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "asiakas2", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "tuote", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "tuote2", 'toapp' "tilaus"
query: 'app' "asiakas", 'chapter', 'memberid'
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakasnumero"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan email"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan maa"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan nimi"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan osoite"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan postinumero"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan postitoimipaikka"
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "asiakas2", 'chapter', 'memberid'
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "tuote", 'chapter', 'memberid'
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotenumero"
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotteen hinta"
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotteen nimi"
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "tuote2", 'chapter', 'memberid'
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'memberid'
data: 'memberid' "asiakasnumero"
data: 'memberid' "asiakkaan email"
data: 'memberid' "asiakkaan maa"
data: 'memberid' "asiakkaan nimi"
data: 'memberid' "asiakkaan osoite"
data: 'memberid' "asiakkaan postinumero"
data: 'memberid' "asiakkaan postitoimipaikka"
data: 'memberid' "laskun asiakasnumero"
data: 'memberid' "laskun asiakkaan nimi"
data: 'memberid' "laskun rivinumero"
data: 'memberid' "laskun tilausnumero"
data: 'memberid' "laskun toimitusnumero"
data: 'memberid' "laskun tuotenumero"
data: 'memberid' "laskun tuotteen hinta"
data: 'memberid' "laskun tuotteen nimi"
data: 'memberid' "laskunumero"
data: 'memberid' "tilattu määrä"
data: 'memberid' "tilauksen asiakasnumero"
data: 'memberid' "tilauksen asiakkaan nimi"
data: 'memberid' "tilauksen rivinumero"
data: 'memberid' "tilauksen tuotenumero"
data: 'memberid' "tilauksen tuotteen hinta"
data: 'memberid' "tilauksen tuotteen nimi"
data: 'memberid' "tilausnumero"
data: 'memberid' "toimitettu määrä"
data: 'memberid' "toimittajan email"
data: 'memberid' "toimittajan maa"
data: 'memberid' "toimittajan nimi"
data: 'memberid' "toimittajan osoite"
data: 'memberid' "toimittajan postinumero"
data: 'memberid' "toimittajan postitoimipaikka"
data: 'memberid' "toimittajanumero"
data: 'memberid' "toimituksen asiakasnumero"
data: 'memberid' "toimituksen asiakkaan nimi"
data: 'memberid' "toimituksen rivinumero"
data: 'memberid' "toimituksen tilattu määrä"
data: 'memberid' "toimituksen tilausnumero"
data: 'memberid' "toimituksen tuotenumero"
data: 'memberid' "toimituksen tuotteen hinta"
data: 'memberid' "toimituksen tuotteen nimi"
data: 'memberid' "toimitusnumero"
data: 'memberid' "tuotenumero"
data: 'memberid' "tuotteen hinta"
data: 'memberid' "tuotteen nimi"
query: 'app', 'fromapp', 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "asiakas", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "asiakas2", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "tuote", 'toapp' "tilaus"
data: 'app' "tilaus", 'fromapp' "tuote2", 'toapp' "tilaus"
query: 'app' "asiakas", 'chapter', 'memberid'
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakasnumero"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan email"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan maa"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan nimi"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan osoite"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan postinumero"
data: 'app' "asiakas", 'chapter' "header", 'memberid' "asiakkaan postitoimipaikka"
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "asiakas2", 'chapter', 'memberid'
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "tuote", 'chapter', 'memberid'
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotenumero"
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotteen hinta"
data: 'app' "tuote", 'chapter' "header", 'memberid' "tuotteen nimi"
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "tuote2", 'chapter', 'memberid'
query: 'app' "tilaus", 'chapter', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilauksen asiakkaan nimi"
data: 'app' "tilaus", 'chapter' "header", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilattu määrä"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'memberid' "tilausnumero"
query: 'app' "tilaus", 'chapter'
data: 'app' "tilaus", 'chapter' "header"
data: 'app' "tilaus", 'chapter' "lines"
query: 'app' "tilaus", 'chapter' "header", 'sort', 'memberid'
data: 'app' "tilaus", 'chapter' "header", 'sort' "01", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "header", 'sort' "02", 'memberid' "tilauksen asiakasnumero"
data: 'app' "tilaus", 'chapter' "header", 'sort' "03", 'memberid' "tilauksen asiakkaan nimi"
query: 'tilausnumero', 'tilauksen asiakasnumero', 'tilauksen asiakkaan nimi'
data: 'tilausnumero' "3000", 'tilauksen asiakasnumero' "1000", 'tilauksen asiakkaan nimi' "Firma 1"
query: 'asiakasnumero', 'asiakkaan nimi'
data: 'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1"
query: 'memberid' "tilausnumero", 'language' "en", 'membertext'
query: 'word' "tilausnumero", 'language' "en", 'translation'
data: 'word' "tilausnumero", 'language' "en", 'translation' "order product name"
query: 'memberid' "tilausnumero", 'length'
query: 'memberid' "tilausnumero", 'mode'
query: 'memberid' "tilauksen asiakasnumero", 'language' "en", 'membertext'
query: 'word' "tilauksen asiakasnumero", 'language' "en", 'translation'
data: 'word' "tilauksen asiakasnumero", 'language' "en", 'translation' "order customer number"
query: 'memberid' "tilauksen asiakasnumero", 'length'
query: 'memberid' "tilauksen asiakasnumero", 'mode'
query: 'memberid' "tilauksen asiakkaan nimi", 'language' "en", 'membertext'
query: 'word' "tilauksen asiakkaan nimi", 'language' "en", 'translation'
data: 'word' "tilauksen asiakkaan nimi", 'language' "en", 'translation' "order customer name"
query: 'memberid' "tilauksen asiakkaan nimi", 'length'
data: 'memberid' "tilauksen asiakkaan nimi", 'length' "32"
query: 'memberid' "tilauksen asiakkaan nimi", 'mode'
query: 'app' "tilaus", 'chapter' "lines", 'sort', 'memberid'
data: 'app' "tilaus", 'chapter' "lines", 'sort' "04", 'memberid' "tilausnumero"
data: 'app' "tilaus", 'chapter' "lines", 'sort' "05", 'memberid' "tilauksen rivinumero"
data: 'app' "tilaus", 'chapter' "lines", 'sort' "06", 'memberid' "tilauksen tuotenumero"
data: 'app' "tilaus", 'chapter' "lines", 'sort' "07", 'memberid' "tilauksen tuotteen nimi"
data: 'app' "tilaus", 'chapter' "lines", 'sort' "08", 'memberid' "tilauksen tuotteen hinta"
data: 'app' "tilaus", 'chapter' "lines", 'sort' "09", 'memberid' "tilattu määrä"
query: 'tilausnumero', 'tilauksen rivinumero', 'tilauksen tuotenumero', 'tilauksen tuotteen nimi', 'tilauksen tuotteen hinta', 'tilattu määrä'
data: 'tilausnumero' "3000", 'tilauksen rivinumero' "10", 'tilauksen tuotenumero' "2000", 'tilauksen tuotteen nimi' "Tappi", 'tilauksen tuotteen hinta' "5,00", 'tilattu määrä' "1"
query: 'memberid' "tilausnumero", 'language' "en", 'membertext'
query: 'word' "tilausnumero", 'language' "en", 'translation'
data: 'word' "tilausnumero", 'language' "en", 'translation' "order number"
query: 'memberid' "tilauksen rivinumero", 'language' "en", 'membertext'
query: 'word' "tilauksen rivinumero", 'language' "en", 'translation'
data: 'word' "tilauksen rivinumero", 'language' "en", 'translation' "order line number"
query: 'memberid' "tilauksen tuotenumero", 'language' "en", 'membertext'
query: 'word' "tilauksen tuotenumero", 'language' "en", 'translation'
data: 'word' "tilauksen tuotenumero", 'language' "en", 'translation' "order product number"
query: 'memberid' "tilauksen tuotteen nimi", 'language' "en", 'membertext'
query: 'word' "tilauksen tuotteen nimi", 'language' "en", 'translation'
data: 'word' "tilauksen tuotteen nimi", 'language' "en", 'translation' "order product name"
query: 'memberid' "tilauksen tuotteen hinta", 'language' "en", 'membertext'
query: 'word' "tilauksen tuotteen hinta", 'language' "en", 'translation'
data: 'word' "tilauksen tuotteen hinta", 'language' "en", 'translation' "order product price"
query: 'memberid' "tilattu määrä", 'language' "en", 'membertext'
query: 'word' "tilattu määrä", 'language' "en", 'translation'
data: 'word' "tilattu määrä", 'language' "en", 'translation' "ordered quantity"
query: 'tuotenumero', 'tuotteen hinta', 'tuotteen nimi'
data: 'tuotenumero' "2000", 'tuotteen hinta' "5,00", 'tuotteen nimi' "Tappi"
query: 'memberid' "tilausnumero", 'length'
query: 'memberid' "tilausnumero", 'mode'
query: 'memberid' "tilauksen rivinumero", 'length'
query: 'memberid' "tilauksen rivinumero", 'mode'
query: 'memberid' "tilauksen tuotenumero", 'length'
query: 'memberid' "tilauksen tuotenumero", 'mode'
query: 'memberid' "tilauksen tuotteen nimi", 'length'
data: 'memberid' "tilauksen tuotteen nimi", 'length' "32"
query: 'memberid' "tilauksen tuotteen nimi", 'mode'
query: 'memberid' "tilauksen tuotteen hinta", 'length'
query: 'memberid' "tilauksen tuotteen hinta", 'mode'
query: 'memberid' "tilattu määrä", 'length'
query: 'memberid' "tilattu määrä", 'mode' 

Edellisessä muutoksessa http_server() ja https_server() rutiinin peräkkäisten write:ien ja SSL_write:ien määrä kasvoi neljään. Tässä muutoksessa se pudotetaan taas yhteen, jota suoritetaan neljä kertaa “for(c=0”-luupissa. Lisäksi muutama bugi yhtäsuuruus merkkejä ja vääriä avaimia korjattu tietosisällössä.

Lisätty uusi tapa tehdä dynaamisia (eli automaattisesti kasvavia) merkkijonoja, funktiot dba_string_set ja dba_string_add. Funktioita käytetään uudessa dba_foreign_data() rutiinissa, vierasavain viittaukset ovat vieläkin puolivalmiste, vielä tarvitaan app_value:n vierasavaimen hakeva osa.

Lisätty html merkkijonojen tulostukseen rutiini dbs_html_buf_printf(), jossa ensimmäiseksi parametriksi voi laittaa halutun puskurin numeron. Lisätty html-lomakkeeseen foreign key kappale, jossa mainitaan lomakkeen vierasavaimet. Tilaus-lomakkeelle tulostuu seuraavat rivit:

Foreign keys: 'tilauksen asiakasnumero' = "asiakasnumero", 'tilauksen asiakkaan nimi' = "asiakkaan nimi";; 'tilauksen tuotenumero' = "tuotenumero", 'tilauksen tuotteen hinta' = "tuotteen hinta", 'tilauksen tuotteen nimi' = "tuotteen nimi";;
line 1 query: 'asiakasnumero' "1000", 'asiakkaan nimi';
line 1 data: 'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1"
line 2 query: 'tuotenumero' "2000", 'tuotteen hinta', 'tuotteen nimi';
line 2 data: 'tuotenumero' "2000", 'tuotteen hinta' "5,00", 'tuotteen nimi' "Tappi"

Lisätty sarakenimien (memberid) kääntämiseen ohjelma, jolla voi kääntää nimet halutuille kielille. Ohjelmaa käytetään kutsumalla dba_app_translations() rutiinia. Rutiinin kutsu on lisätty dba_main funktioon. Rutiini käynnistetään “–translate” komentorivikytkimellä.

Ensimmäisessä luupissa dba_app_translations() tulostaa tertun kentistä seuraavanlaisen listan: Lista koostuu tertun numeroiduista sarakenimistä. Numerolla viitataan seuraavassa vaiheessa tämän sanan käännökseen, jolla on tietysti sama numero.

fi 0:asiakasnumero, 1:asiakkaan email, 2:asiakkaan maa, 3:asiakkaan nimi, 4:asiakkaan osoite, 5:asiakkaan postinumero, 6:asiakkaan postitoimipaikka, 7:laskun asiakasnumero, 8:laskun asiakkaan nimi, 9:laskun rivinumero, 10:laskun tilausnumero, 11:laskun toimitusnumero, 12:laskun tuotenumero, 13:laskun tuotteen hinta, 14:laskun tuotteen nimi, 15:laskunumero, 16:tilattu määrä, 17:tilauksen asiakasnumero, 18:tilauksen asiakkaan nimi, 19:tilauksen rivinumero, 20:tilauksen tuotenumero, 21:tilauksen tuotteen hinta, 22:tilauksen tuotteen nimi, 23:tilausnumero, 24:toimitettu määrä, 25:toimittajan email, 26:toimittajan maa, 27:toimittajan nimi, 28:toimittajan osoite, 29:toimittajan postinumero, 30:toimittajan postitoimipaikka, 31:toimittajanumero, 32:toimituksen asiakasnumero, 33:toimituksen asiakkaan nimi, 34:toimituksen rivinumero, 35:toimituksen tilattu määrä, 36:toimituksen tilausnumero, 37:toimituksen tuotenumero, 38:toimituksen tuotteen hinta, 39:toimituksen tuotteen nimi, 40:toimitusnumero, 41:tuotenumero, 42:tuotteen hinta, 43:tuotteen nimi

Tämä ensimmäinen rivi voidaan kääntää esimerkiksi googlella. jolloin se saadaan seuraavaan muotoon: Tämä on siis yksi rivi, ja sen alkuun on lisätty tässä ‘en’ joka viittaa kielen lyhenteeseem, tässä englanti. Ensimmäinen rivi (tässä edellinen fi alkuinen rivi) ja käännösrivi lisätään translations.dat tiedostoon, jolloin seuraava –translate ajo tekee (word, language, translation) rivit, jotka voidaan lisätä tietokantaan.

en 0: customer number, 1: customer email, 2: customer country, 3: customer name, 4: customer address, 5: customer postal code, 6: customer post office, 7: invoice customer number, 8: invoice customer name, 9: invoice line number, 10: invoice order number, 11: invoice delivery number, 12: invoice product number, 13: invoice product price, 14: invoice product name, 15: invoice number, 16: ordered quantity, 17: order customer
 number, 18: order customer name, 19: order line number, 20: order product number, 21: order product price, 22: order product name, 23: order product name, 24: delivered quantity, 25: supplier email, 26: supplier country, 27: supplier name, 28: supplier address, 29: supplier's postal code, 30: supplier's post office, 31: supplier's number, 32: delivery's customer number, 33: delivery's customer's name, 34: delivery's line 
number, 35: delivery's ordered quantity, 36: delivery's order number, 37: delivery's product number, 38: delivery's product price, 39: delivery product name, 40: delivery number, 41: product number, 42: product price, 43: product name

Tässä alkua edellisten kahden rivin –translate tulosteesta: word niminen kenttä tulee ensimmäisestä, fi-tietueesta nollan kohdalta (asiakasnumero), ja translation tulee en rivin 0-kohdasta (customer number). Asiakkaan email on sana 1 ja asiakkaan maa on sana 3 jne.

translations.dat tiedoston ensimmäinen rivi on aina lähde kieli, ja sen jälkeen tulee yksi tai useampia kohdekieliä omilla riveillään. Muista lisätä aina rivin alkuun kielikoodi. Kielikoodilla täytetään seuraavan raportin language kenttä.

'word' "asiakasnumero", 'language' "en", 'translation' "customer number"
'word' "asiakkaan email", 'language' "en", 'translation' "customer email"
'word' "asiakkaan maa", 'language' "en", 'translation' "customer country"
'word' "asiakkaan nimi", 'language' "en", 'translation' "customer name"
'word' "asiakkaan osoite", 'language' "en", 'translation' "customer address"
'word' "asiakkaan postinumero", 'language' "en", 'translation' "customer postal code"
'word' "asiakkaan postitoimipaikka", 'language' "en", 'translation' "customer post office"
'word' "laskun asiakasnumero", 'language' "en", 'translation' "invoice customer number"
'word' "laskun asiakkaan nimi", 'language' "en", 'translation' "invoice customer name"

Varsinainen nimien käännös sovelluksessa tapahtuu dba_app_memberid() rutiinissa.

Tämä muutos kuuluisi fort ohjelmaan, jota ei vielä tässä postissa ole ja seuraavaan fort postiin on vielä matkaa joten liitän sen tähän.

Ensimmäinen funktio fort_hash_http_page() lukee satunnaisuutta web sivulta hash:aamalla koko sivun sisällön ja palauttamalla hash:in tuloksen muuttujassa hash. Ohjelmalle tarvitaan lisäksi muuttujat host = “moijari.com”, port = “5001” ja page=”/”, joka on ressulla satunnaisuusbittejä tekevä sivuni https://moijari.com:5001 (siis http:). En suosittele muuttamaan parametreja muiksi, eli hakemaan satunnaisuutta jonkun muun omistamasta sivusta. Tässä satunnaisuutta haetaan yhden kerran ohjelman ajon alussa.

Toisessa osassa listaa on paikka jossa tätä rutiinia kutsutaan fort_init():issä. Listassa on ensin jo aiemmin käytetty rutiini satunnaislukuja /dev/random:sta ja sen jälkeen lisätään tämä uusi FORT_USE_MOIJARICOM5001 rutiini. Seuraavassa alun definessä tämä muutos on pois päältä (a).

#define aFORT_USE_MOIJARICOM5001 2

#ifdef FORT_USE_MOIJARICOM5001

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

int fort_connect(unsigned char *host, unsigned char *port)
{
  struct addrinfo hints, *res, *resp;
  int s, status;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  if((status = getaddrinfo(host, port, &hints, &res)) != 0) {
    fprintf(stderr,"\n%s: getaddrinfo", procname);
    fprintf(stderr,", status %d", status);
    fprintf(stderr,", gai_strerror(): %s", gai_strerror(status));
    fprintf(stderr,", errno %d\n", errno);
    fflush(stderr);
  }

  for(resp=res; resp!=NULL; resp = resp->ai_next) {
    if((s = socket(resp->ai_family, resp->ai_socktype, resp->ai_protocol))<0)
      continue;
    if(connect(s, resp->ai_addr, resp->ai_addrlen) == 0)
      break;
    close(s);
  }

  freeaddrinfo(res);

  return(s);
}

#include <stdarg.h>

void dbs_printf(unsigned char **buf, int *buf_length, const char *format, ...)
{
  int count;
  va_list args;

  va_start(args, format);
  count = snprintf(*buf, *buf_length, format, args) + 1;
  va_end(args);
  if(*buf_length < count) {
    *buf_length = count;
    *buf = realloc(*buf, *buf_length);
    va_start(args, format);
    count = snprintf(*buf, *buf_length, format, args) + 1;
    va_end(args);
  }
}

void fort_hash_http_page(unsigned char *host,unsigned char *port, unsigned char *page, unsigned char *hash)
{
  int s,status,bytes,total;

  if((s = fort_connect(host, port))<0) {
    fprintf(stderr,"\n%s: cannot fort_connect()", procname);
    fprintf(stderr,", status: %d", s);
    fprintf(stderr,", errno: %d" , errno);
    perror("fort_connect");
    fflush(stderr);
  }

  unsigned char *format =
    "GET %s HTTP/1.0\r\n"
    "Host: %s\r\n";
  static unsigned char *msg = NULL;
  static int msg_length=0;

  dbs_printf(&msg, &msg_length, format, page, host);

  if((status=write(s, msg, strlen(msg)))<0) {
    fprintf(stderr, "\n%s: write(), error: %d\n", procname, errno);
    perror("write");
    fflush(stderr);
  }

  HashCtx ctx;
  char buffer[1024];

  HashInit(&ctx);
  total=0;
  while((bytes = read(s, buffer, sizeof(buffer)))>0) {
    //write(1,buffer,bytes);                                                                                                                                                                                                                                                                                                                                                                                                                
    HashUpdate(&ctx, buffer, bytes);
    total+=bytes;
  }

  HashFinal(hash, &ctx);

  fprintf(stdout,"fort_hash_http_page: %d bytes read", total);
  fprintf(stdout,", sha256: ");
  for(int c = 0;c < HashLen; c++) {
    fprintf(stdout,"%02x", hash[c]);
  }
  fprintf(stdout,"\n");
  fflush(stdout);

  close(s);
}

#endif

Seuraavassa ovat siis fort_init():iin tehtävät muutokset: eli FORT_USE_RANDOM kappaleen jälkeen tuleva uusi FORT_USE_MOIJARICOM5001 kappale.

void fort_init()
{
  int c, pooltemp;

  fprintf(stdout,"Fort v0.49");

  . . .

#ifdef FORT_USE_RANDOM
  memset(temp, 0, sizeof(temp));
  fort_readfile_xor(sizeof(temp), temp,
      "/dev/random");
  fort_reseed(sizeof(temp), temp);

  dump_pools("Randomness from random");
#endif

#ifdef FORT_USE_MOIJARICOM5001
  unsigned char hash[HashLen];

  // do not change these parameters unless
  // you own the target site.
  fort_hash_http_page("moijari.com", "5001", "/", hash);
  fort_reseed(sizeof(hash), hash);
  dump_pools("Rand. from moijari.com:5001");
#endif

  . . .
}

Edellinen lisää fort:in alun satunnaisuuteen yhden lähteen lisää.

Lisätty https-versio satunnaislukusivun luvusta: sille minulla ei vielä ole valmista luettavaa satunnaislukusivua, mutta sellainen tulee varmasti. Https funktiota ei ajeta vielä fort_init():ssä, mutta lisään sen kun satunnaisbitit löytyvät verkosta (moijari.com:5005). Lisäksi palasteltu http ja https versioiden yhteiset osat fort_connect():iin ja dbs_printf():ään.

#ifdef FORT_USE_MOIJARICOM5005

void fort_hash_https_page(unsigned char *host,unsigned char *port, unsigned char *page, unsigned char *hash)
{
  int s, status, bytes, total;
  struct addrinfo hints, *res, *resp;

  SSL_METHOD *method=NULL;
  SSL_CTX *ctx=NULL;
  SSL *ssl;

  SSL_library_init();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  OpenSSL_add_ssl_algorithms();
  SSL_load_error_strings();

  if((method = (SSL_METHOD *)
      SSLv23_client_method()) == NULL) {
    fprintf(stderr,"\n%s: cannot SSLv23_client_method()", procname);
    fflush(stderr);
  }

  if((ctx=SSL_CTX_new(method)) == NULL) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_new()", procname);
    fflush(stderr);
  }

  if((ssl=SSL_new(ctx)) == NULL) {
    fprintf(stderr,"\n%s: cannot SSL_new()", procname);
    fflush(stderr);
  }

  if((s = fort_connect(host, port))<0) {
    fprintf(stderr,"\n%s: cannot fort_connect()", procname);
    fprintf(stderr,", status: %d", s);
    fprintf(stderr,", errno: %d" , errno);
    perror("fort_connect");
    fflush(stderr);
  }

  SSL_set_fd(ssl,s);

  if((status=SSL_connect(ssl))<=0) {
    fprintf(stderr,"\n%s: cannot SSL_connect()", procname);
    fprintf(stderr,", status: %d", status);
    fprintf(stderr,", errno: %d" , errno);
    fprintf(stderr,", SSL_get_error(): %d\n", SSL_get_error(ssl,status));
    perror("SSL_connect");
    fflush(stderr);
  }

  unsigned char *format =
    "GET %s HTTP/1.0\r\n"
    "Host: %s\r\n";
  static unsigned char *msg = NULL;
  static int msg_length=0;

  dbs_printf(&msg,&msg_length, format, page, host);

  if((status=SSL_write(ssl, msg, strlen(msg)))<0) {
    fprintf(stderr,"\n%s: SSL_write()", procname);
    fprintf(stderr,", status: %d", status);
    fprintf(stderr,", errno: %d", errno);
    fprintf(stderr,", SSL_get_error(): %d", SSL_get_error(ssl,status));
    perror("SSL_write");
    fflush(stderr);
  }
  fflush(stdout);

  HashCtx hashctx;
  char buffer[1024];

  HashInit(&hashctx);
  total=0;
  while((bytes=SSL_read(ssl, buffer, sizeof(buffer)))>0) {
    //write(1,buffer,bytes);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    HashUpdate(&hashctx, buffer, bytes);
    total+=bytes;
  }
  fflush(stdout);
  if(bytes<0) {
    fprintf(stderr, "\n%s: SSL_read()", procname);
    fprintf(stderr, ", status: %d", bytes);
    fprintf(stderr, ", errno: %d", errno);
    fprintf(stderr,", SSL_get_error(): %d", SSL_get_error(ssl,status));
    perror("SSL_read");
    fflush(stderr);
  }

  fflush(stdout);

  HashFinal(hash, &hashctx);

  fprintf(stdout,"dbs_hash_https_page: %d bytes read", total);
  fprintf(stdout,", sha256: ");
  for(int c = 0;c < HashLen; c++) {
    fprintf(stdout,"%02x", hash[c]);
  }
  fprintf(stdout,"\n");
  fflush(stdout);

  SSL_shutdown(ssl);
  SSL_free(ssl);
  SSL_CTX_free(ctx);
  close(s);
}

#endif

Jos olet kiinnostjunut, seuraavassa tulosteessa osia html version lukemasta moijari.com:5001 sivusta:

HTTP/1.0 200 OK
Location: 
Server: ressuhttps5 v0.96
Date: Wed, 25 Nov 2020 15:41:59 GMT
Content&#8209;Length: 107442


<!DOCTYPE html>
<html lang="fi">
	<head>
		<meta charset="UTF-8">
		<title>Ressu random numbers</title>
		<meta name="author" content="Jari Kuivaniemi">
		<meta name="format-detection" content="telephone=no">
	</head>
	<body>
		<h1>Ressu random numbers</h1>
		<code>
JPQMWWlplNTnZD&#8209;B U2GGaP_7TknMYUEv 9o8T9ERDp4jXPXRm aQpNmg6_ZP5l2BRy TbFBZ4I&#8209;9bmRrLzH CTa&#8209;LuF2sug_E0k
J QMuBmGq88Rj&#8209;Vcoa pkdcXcvhXKQX&#8209;1xS jSyPFA_3mtQQDLXZ &#8209;TphlonN1qZDApye 2jqNVF9u&#8209;CWU&#8209;cCp lsDPdDoZ_fp8gHyj kncA36CcbhMK0PPU ug431N3LhHceFqXi iZI7tlVBB&#8209;hHuk1C Y7Sg0rQlAp1zQGE
E UVe7I6iukg8PlmI6 tbPU8giSZfYz2YMT mgpXrHLL7XBasYT5 436mqfv&#8209;96ImgSNc

...

1_usGw7zVI_1ZAIY RnlkjWgphyTdawhF 7BPvno7f1oaAWovV Nda8PwrJfV&#8209;jsUV2 x2oKjdMM7yydkzVS QidzMmXcneH1LG6A tH15FVbfE_BVR_Nn 0z48e1nliq2Wammg U7kkdBbLKkN9aonk efVA67Q3DoeSlG1W pBcssNPWh&#8209;67343f
		<code>
		<h1>Statistics</h1>
		monobit: ones: 262454(50.062183%) zeroes: 261802(49.937817%), total: 524256<br>
		bitwise monobit data: 1(0: 32793, 1: 32739) 2(0: 32885, 1: 32647) 4(0: 32698, 1: 32834) 8(0: 32844, 1: 32688) 16(0: 32829, 1: 
32703) 32(0: 32732, 1: 32800) 64(0: 32848, 1: 32684) 128(0: 32825, 1: 32707)<br>
		bitwise monobit total: 524256, lowest: 32647(6.227301%), highest: 32885(6.272699%)<br>
		poker2: data:  0:65793 1:65493 2:65375 3:65467<br>
		poker2: total: 262128, lowest: 65375(24.940106%), highest: 65793(25.099570%)<br>
		poker4: data:  0:8252 1:8281 2:8145 3:8257 4:8167 5:8184 6:8306 7:8077 8:8257 9:7947 10:8186 11:8221 12:8182 13:8347 14:8127 1
5:8128<br>
		poker4: total: 131064, lowest: 7947(6.063450%), highest: 8347(6.368644%)<br>
		poker8: data:  0:262 1:263 2:256 3:241 4:221 5:262 6:255 7:251 8:264 9:267 10:241 11:273 12:273 13:284 14:280 15:225 16:249 17

...

average: total 8341993, count: 65532, average: 127.296478<br>
		entropy: 7.997073<br>
		<h1>Program version (powered by Ressu 1.1)</h1>
		program version: ressuhttps5 v0.96 sha256(e198d61ce5811a6d694775c4ecd3b1791f77a0e1ff71779c31c291c9f4017ba4)<br>
		8 bit data sha256(a0440b769795c03c1a131c65c4e736fa07fad97a6ae10236a828208f8d8e5494)<br>
		6 bit data sha256(947780866763677d5aecec7fe78268d3c8ff9e00a995e50c769738fd1b43d7d4)<br>
		original url: <a href="https://moijari.com:5001">https://moijari.com:5001</a>
		 or <a href="https://moijari.com:5001">https://moijari.com:5001</a><br>
		<br><br>
	</body>
</html>

Edit: End of Julkaisun jälkeiset muutokset

Tietosisältö on nyt seuraavankaltaisessa tiedostossa:

;
;
;       Asiakas
;
;
'app' "asiakas", 'appname' "Asiakkaiden hallinta"
'memberid' "asiakasnumero", 'text', "Asiakasnumero" 'key' "key"
'memberid' "asiakkaan nimi", 'text', "Asiakkaan nimi", 'length' "32"
'memberid' "asiakkaan osoite", 'text', "Asiakkaan osoite", 'length' "32", 'mode' "Optional"
'memberid' "asiakkaan postinumero", 'text', "Asiakkaan postinumero", 'length' "10", 'mode' "Optional"
'memberid' "asiakkaan postitoimipaikka", 'text', "Asiakkaan postitoimipaikka", 'length' "32", 'mode' "Optional"
'memberid' "asiakkaan maa", 'text', "Asiakkaan maa", 'length' "32", 'mode' "Optional"
'memberid' "asiakkaan email", 'text', "Asiakkaan email", 'length' "32", 'mode' "Required"
'app' "asiakas", 'chapter' "header", 'sort' "01", 'memberid' "asiakasnumero"
'app' "asiakas", 'chapter' "header", 'sort' "02", 'memberid' "asiakkaan nimi"
'app' "asiakas", 'chapter' "header", 'sort' "03", 'memberid' "asiakkaan osoite"
'app' "asiakas", 'chapter' "header", 'sort' "04", 'memberid' "asiakkaan postinumero"
'app' "asiakas", 'chapter' "header", 'sort' "05", 'memberid' "asiakkaan postitoimipaikka"
'app' "asiakas", 'chapter' "header", 'sort' "06", 'memberid' "asiakkaan maa"
'app' "asiakas", 'chapter' "header", 'sort' "07", 'memberid' "asiakkaan email"
'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1", 'asiakkaan osoite' "Venekatu 1", 'asiakkaan postinumero' "00500", 'asiakkaan postitoimipaikka' "111111", 'asiakkaan maa' "Finland", 'asiakkaan email' "jarik@example.com"
'app' "asiakas", 'language' "fi", 'apptext' "Asiakkaiden hallinta"
'memberid' "asiakasnumero", 'language' "fi", 'membertext' "Asiakasnumero"
'memberid' "asiakkaan nimi", 'language' "fi", 'membertext' "Asiakkaan nimi"
'memberid' "asiakkaan osoite", 'language' "fi", 'membertext' "Asiakkaan osoite"
'memberid' "asiakkaan postinumero", 'language' "fi", 'membertext' "Asiakkaan postinumero"
'memberid' "asiakkaan postitoimipaikka", 'language' "fi", 'membertext' "Asiakkaan postitoimipaikka"
'memberid' "asiakkaan maa", 'language' "fi", 'membertext' "Asiakkaan maa"
'memberid' "asiakkaan email", 'language' "fi", 'membertext' "Asiakkaan sähköposti"
'app' "asiakas", 'language' "sv", 'apptext' "Kundhantering"
'memberid' "asiakasnumero", 'language' "sv", 'membertext' "Kundnummer"
'memberid' "asiakkaan nimi", 'language' "sv", 'membertext' "Kundens namn"
'memberid' "asiakkaan osoite", 'language' "sv", 'membertext' "Kundens adress"
'memberid' "asiakkaan postinumero", 'language' "sv", 'membertext' "Kundens postnummer"
'memberid' "asiakkaan postitoimipaikka", 'language' "sv", 'membertext' "Kundens postkontor"
'memberid' "asiakkaan maa", 'language' "sv", 'membertext' "Kundens land"
'memberid' "asiakkaan email", 'language' "sv", 'membertext' "Kundens e-post"
'app' "asiakas", 'language' "ru", 'apptext' "управление клиентами"
'app' "asiakas", 'language' "ch", 'apptext' "用戶管理"
;
;
;       Tuote
;
;
'app' "tuote", 'appname' "Tuotteet"
'memberid' "tuotenumero" 'key' "key"
'memberid' "tuotteen nimi", 'text', "Tuotteen nimi", 'length' "32"
'app' "tuote", 'chapter' "header", 'sort' "01", 'memberid' "tuotenumero"
'app' "tuote", 'chapter' "header", 'sort' "02", 'memberid' "tuotteen nimi"
'app' "tuote", 'chapter' "header", 'sort' "03", 'memberid' "tuotteen hinta"
'tuotenumero' "2000", 'tuotteen nimi' "Tappi", 'tuotteen hinta' "5,00"
;
;
;       Tilaus
;
;
'app' "tilaus", 'appname' "Tilaukset"
'app' "tilaus", 'fromapp' "asiakas", 'toapp' "tilaus"
'app' "tilaus", 'fromapp' "tuote", 'toapp' "tilaus"
'app' "tilaus", 'fromapp' "asiakas2", 'toapp' "tilaus"
'app' "tilaus", 'fromapp' "tuote2", 'toapp' "tilaus"
'memberid' "tilausnumero" 'key' "key"
'memberid' "tilauksen asiakkaan nimi", 'text', "Tilauksen asiakkaan nimi", 'length' "32"
'memberid' "tilauksen rivinumero" 'key' "key"
'memberid' "tilauksen tuotteen nimi", 'text', "Tilauksen tuotteen nimi", 'length' "32"
'app' "tilaus", 'chapter' "header", 'sort' "01", 'memberid' "tilausnumero"
'app' "tilaus", 'chapter' "header", 'sort' "02", 'memberid' "tilauksen asiakasnumero"
'app' "tilaus", 'chapter' "header", 'sort' "03", 'memberid' "tilauksen asiakkaan nimi"
'app' "tilaus", 'chapter' "lines", 'sort' "04", 'memberid' "tilausnumero"
'app' "tilaus", 'chapter' "lines", 'sort' "05", 'memberid' "tilauksen rivinumero"
'app' "tilaus", 'chapter' "lines", 'sort' "06", 'memberid' "tilauksen tuotenumero"
'app' "tilaus", 'chapter' "lines", 'sort' "07", 'memberid' "tilauksen tuotteen nimi"
'app' "tilaus", 'chapter' "lines", 'sort' "08", 'memberid' "tilauksen tuotteen hinta"
'app' "tilaus", 'chapter' "lines", 'sort' "09", 'memberid' "tilattu määrä"
'tilausnumero' "3000", 'tilauksen asiakasnumero' "1000", 'tilauksen asiakkaan nimi' "Firma 1"
'tilausnumero' "3000", 'tilauksen rivinumero' "10", 'tilauksen tuotenumero' "2000", 'tilauksen tuotteen nimi' "Tappi", 'tilauksen tuotteen hinta' "5,00", 'tilattu määrä' "1"
'app' "tilausrivit", 'appname' "Tilausrivit"
'app' "tilausrivit", 'chapter' "header", 'sort' "01", 'memberid' "tilausnumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "02", 'memberid' "tilauksen asiakasnumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "03", 'memberid' "tilauksen asiakkaan nimi"
'app' "tilausrivit", 'chapter' "header", 'sort' "04", 'memberid' "tilauksen rivinumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "05", 'memberid' "tilauksen tuotenumero"
'app' "tilausrivit", 'chapter' "header", 'sort' "06", 'memberid' "tilauksen tuotteen nimi"
'app' "tilausrivit", 'chapter' "header", 'sort' "07", 'memberid' "tilauksen tuotteen hinta"
'app' "tilausrivit", 'chapter' "header", 'sort' "08", 'memberid' "tilattu määrä"
;
;
;      Toimitus
;
;
'app' "toimitus", 'appname' "Toimitukset"
'app' "toimitus", 'fromapp' "tilaus", 'toapp' "toimitus"
'app' "toimitus", 'fromapp' "asiakas", 'toapp' "toimitus"
'app' "toimitus", 'fromapp' "tuote", 'toapp' "toimitus"
'app' "toimitus", 'chapter' "header", 'sort' "01", 'memberid' "toimitusnumero"
'app' "toimitus", 'chapter' "header", 'sort' "02", 'memberid' "toimituksen asiakasnumero"
'app' "toimitus", 'chapter' "header", 'sort' "03", 'memberid' "toimituksen asiakkaan nimi"
'app' "toimitus", 'chapter' "header", 'sort' "03", 'memberid' "toimituksen tilausnumero"
'app' "toimitus", 'chapter' "lines", 'sort' "04", 'memberid' "toimitusnumero"
'app' "toimitus", 'chapter' "lines", 'sort' "05", 'memberid' "toimituksen rivinumero"
'app' "toimitus", 'chapter' "lines", 'sort' "06", 'memberid' "toimituksen tuotenumero"
'app' "toimitus", 'chapter' "lines", 'sort' "07", 'memberid' "toimituksen tuotteen nimi"
'app' "toimitus", 'chapter' "lines", 'sort' "08", 'memberid' "toimituksen tuotteen hinta"
'app' "toimitus", 'chapter' "lines", 'sort' "09", 'memberid' "toimituksen tilattu määrä"
'app' "toimitus", 'chapter' "lines", 'sort' "09", 'memberid' "toimitettu määrä"
'toimitusnumero' "4000", 'toimituksen asiakasnumero' "1000", 'toimituksen asiakkaan nimi' "Firma 1", 'toimituksen tilausnumero' "3000"
'toimitusnumero' "4000", 'toimituksen rivinumero' "10", 'toimituksen tuotenumero' "2000", 'toimituksen tuotteen nimi' "Tappi", 'toimituksen tuotteen hinta' "5,00", 'toimituksen tilattu määrä' "1", 'toimitettu määrä' "1"
;
;
;       Laskutus
;

'app' "lasku", 'appname' "Laskut"
'app' "lasku", 'fromapp' "toimitus", 'toapp' "lasku"
'app' "lasku", 'fromapp' "asiakas", 'toapp' "lasku"
'app' "lasku", 'fromapp' "tuote", 'toapp' "lasku"
'app' "lasku", 'chapter' "header", 'sort' "01", 'memberid' "laskunumero"
'app' "lasku", 'chapter' "header", 'sort' "02", 'memberid' "laskun asiakasnumero"
'app' "lasku", 'chapter' "header", 'sort' "03", 'memberid' "laskun asiakkaan nimi"
'app' "lasku", 'chapter' "header", 'sort' "04", 'memberid' "laskun tilausnumero"
'app' "lasku", 'chapter' "header", 'sort' "05", 'memberid' "laskun toimitusnumero"
'app' "lasku", 'chapter' "lines", 'sort' "10", 'memberid' "laskunumero"
'app' "lasku", 'chapter' "lines", 'sort' "11", 'memberid' "laskun rivinumero"
'app' "lasku", 'chapter' "lines", 'sort' "12", 'memberid' "laskun tuotenumero"
'app' "lasku", 'chapter' "lines", 'sort' "13", 'memberid' "laskun tuotteen nimi"
'app' "lasku", 'chapter' "lines", 'sort' "14", 'memberid' "laskun tuotteen hinta"
'app' "lasku", 'chapter' "lines", 'sort' "15", 'memberid' "toimituksen tilattu määrä"
'app' "lasku", 'chapter' "lines", 'sort' "16", 'memberid' "toimitettu määrä"
'laskunumero' "5000", 'laskun asiakasnumero' "1000", 'laskun asiakkaan nimi' "Firma 1", 'laskun tilausnumero' "3000", 'laskun toimitusnumero' "4000"
'laskunumero' "5000", 'laskun rivinumero' "10", 'laskun tuotenumero' "2000", 'laskun tuotteen nimi' "Tappi", 'laskun tuotteen hinta' "5,00", 'toimituksen tilattu määrä' "1", 'toimitettu määrä' "1"
;
;
;       Toimittaja
;
;
'app' "toimittaja", 'appname' "Toimittajat"
'memberid' "toimittajanumero" 'key' "key"
'memberid' "toimittajan nimi", 'text', "Toimittajan nimi", 'length' "32"
'memberid' "toimittajan osoite", 'text', "Toimittajan osoite", 'length' "32"
'memberid' "toimittajan postinumero", 'text', "Toimittajan postinumero", 'length' "10"
'memberid' "toimittajan postitoimipaikka", 'text', "Toimittajan postitoimipaikka", 'length' "32"
'memberid' "toimittajan maa", 'text', "Toimittajan maa", 'length' "32"
'app' "toimittaja", 'chapter' "header", 'sort' "01", 'memberid' "toimittajanumero"
'app' "toimittaja", 'chapter' "header", 'sort' "02", 'memberid' "toimittajan nimi"
'app' "toimittaja", 'chapter' "header", 'sort' "03", 'memberid' "toimittajan osoite"
'app' "toimittaja", 'chapter' "header", 'sort' "04", 'memberid' "toimittajan postinumero"
'app' "toimittaja", 'chapter' "header", 'sort' "05", 'memberid' "toimittajan postitoimipaikka"
'app' "toimittaja", 'chapter' "header", 'sort' "06", 'memberid' "toimittajan maa"
'toimittajanumero' "2000", 'toimittajan nimi' "Firma 1", 'toimittajan osoite' "Venekatu 1", 'toimittajan postinumero' "00500", 'toimittajan postitoimipaikka' "Helsinki", 'toimittajan maa' "Finland"
'memberid' "toimittajanumero", 'language' "fi", 'membertext' "Toimittajanumero"
'memberid' "toimittajan nimi", 'language' "fi", 'membertext' "Toimittajan nimi"
'memberid' "toimittajan osoite", 'language' "fi", 'membertext' "Toimittajan osoite"
'memberid' "toimittajan postinumero", 'language' "fi", 'membertext' "Toimittajan postinumero"
'memberid' "toimittajan postitoimipaikka", 'language' "fi", 'membertext' "Toimittajan postitoimipaikka"
'memberid' "toimittajan maa", 'language' "fi", 'membertext' "Toimittajan maa"
'memberid' "toimittajan email", 'language' "fi", 'membertext' "Toimittajan email"
;
;
;       Translations (google)
;
;
'word' "asiakasnumero", 'language' "en", 'translation' "customer number"
'word' "asiakkaan email", 'language' "en", 'translation' "customer email"
'word' "asiakkaan maa", 'language' "en", 'translation' "customer country"
'word' "asiakkaan nimi", 'language' "en", 'translation' "customer name"
'word' "asiakkaan osoite", 'language' "en", 'translation' "customer address"
'word' "asiakkaan postinumero", 'language' "en", 'translation' "customer postal code"
'word' "asiakkaan postitoimipaikka", 'language' "en", 'translation' "customer post office"
'word' "laskun asiakasnumero", 'language' "en", 'translation' "invoice customer number"
'word' "laskun asiakkaan nimi", 'language' "en", 'translation' "invoice customer name"
'word' "laskun rivinumero", 'language' "en", 'translation' "invoice line number"
'word' "laskun tilausnumero", 'language' "en", 'translation' "invoice order number"
'word' "laskun toimitusnumero", 'language' "en", 'translation' "invoice delivery number"
'word' "laskun tuotenumero", 'language' "en", 'translation' "invoice product number"
'word' "laskun tuotteen hinta", 'language' "en", 'translation' "invoice product price"
'word' "laskun tuotteen nimi", 'language' "en", 'translation' "invoice product name"
'word' "laskunumero", 'language' "en", 'translation' "invoice number"
'word' "tilattu määrä", 'language' "en", 'translation' "ordered quantity"
'word' "tilauksen asiakasnumero", 'language' "en", 'translation' "order customer number"
'word' "tilauksen asiakkaan nimi", 'language' "en", 'translation' "order customer name"
'word' "tilauksen rivinumero", 'language' "en", 'translation' "order line number"
'word' "tilauksen tuotenumero", 'language' "en", 'translation' "order product number"
'word' "tilauksen tuotteen hinta", 'language' "en", 'translation' "order product price"
'word' "tilauksen tuotteen nimi", 'language' "en", 'translation' "order product name"
'word' "tilausnumero", 'language' "en", 'translation' "order product name"
'word' "toimitettu määrä", 'language' "en", 'translation' "delivered quantity"
'word' "toimittajan email", 'language' "en", 'translation' "supplier email"
'word' "toimittajan maa", 'language' "en", 'translation' "supplier country"
'word' "toimittajan nimi", 'language' "en", 'translation' "supplier name"
'word' "toimittajan osoite", 'language' "en", 'translation' "supplier address"
'word' "toimittajan postinumero", 'language' "en", 'translation' "supplier's postal code"
'word' "toimittajan postitoimipaikka", 'language' "en", 'translation' "supplier's post office"
'word' "toimittajanumero", 'language' "en", 'translation' "supplier's number"
'word' "toimituksen asiakasnumero", 'language' "en", 'translation' "delivery's customer number"
'word' "toimituksen asiakkaan nimi", 'language' "en", 'translation' "delivery's customer's name"
'word' "toimituksen rivinumero", 'language' "en", 'translation' "delivery's line number"
'word' "toimituksen tilattu määrä", 'language' "en", 'translation' "delivery's ordered quantity"
'word' "toimituksen tilausnumero", 'language' "en", 'translation' "delivery's order number"
'word' "toimituksen tuotenumero", 'language' "en", 'translation' "delivery's product number"
'word' "toimituksen tuotteen hinta", 'language' "en", 'translation' "delivery's product price"
'word' "toimituksen tuotteen nimi", 'language' "en", 'translation' "delivery product name"
'word' "toimitusnumero", 'language' "en", 'translation' "delivery number"
'word' "tuotenumero", 'language' "en", 'translation' "product number"
'word' "tuotteen hinta", 'language' "en", 'translation' "product price"
'word' "tuotteen nimi", 'language' "en", 'translation' "product name"
'word' "asiakasnumero", 'language' "sv", 'translation' "kundnummer"
'word' "asiakkaan email", 'language' "sv", 'translation' "kundens e-post"
'word' "asiakkaan maa", 'language' "sv", 'translation' "kundland"
'word' "asiakkaan nimi", 'language' "sv", 'translation' "kundnamn"
'word' "asiakkaan osoite", 'language' "sv", 'translation' "kundadress"
'word' "asiakkaan postinumero", 'language' "sv", 'translation' "kundens postnummer"
'word' "asiakkaan postitoimipaikka", 'language' "sv", 'translation' "kundpost"
'word' "laskun asiakasnumero", 'language' "sv", 'translation' "fakturakundnummer"
'word' "laskun asiakkaan nimi", 'language' "sv", 'translation' "fakturakundnamn"
'word' "laskun rivinumero", 'language' "sv", 'translation' "fakturaradnummer"
'word' "laskun tilausnumero", 'language' "sv", 'translation' "fakturanummer"
'word' "laskun toimitusnumero", 'language' "sv", 'translation' "fakturans leveransnummer"
'word' "laskun tuotenumero", 'language' "sv", 'translation' "fakturans produktnummer"
'word' "laskun tuotteen hinta", 'language' "sv", 'translation' "fakturans produktpris"
'word' "laskun tuotteen nimi", 'language' "sv", 'translation' "fakturans produktnamn"
'word' "laskunumero", 'language' "sv", 'translation' "fakturanumret"
'word' "tilattu määrä", 'language' "sv", 'translation' "beställt kvantitet"
'word' "tilauksen asiakasnumero", 'language' "sv", 'translation' "beställ kundens nummer"
'word' "tilauksen asiakkaan nimi", 'language' "sv", 'translation' "beställ kundens namn"
'word' "tilauksen rivinumero", 'language' "sv", 'translation' "order radnummer"
'word' "tilauksen tuotenumero", 'language' "sv", 'translation' "beställ produktnummer"
'word' "tilauksen tuotteen hinta", 'language' "sv", 'translation' "beställ produktpris"
'word' "tilauksen tuotteen nimi", 'language' "sv", 'translation' "beställ produktnamn"
'word' "tilausnumero", 'language' "sv", 'translation' "beställ produktnamn"
'word' "toimitettu määrä", 'language' "sv", 'translation' "levererad kvantitet"
'word' "toimittajan email", 'language' "sv", 'translation' "leverantörs-e-post"
'word' "toimittajan maa", 'language' "sv", 'translation' "leverantörsland"
'word' "toimittajan nimi", 'language' "sv", 'translation' "leverantörsnamn"
'word' "toimittajan osoite", 'language' "sv", 'translation' "leverantörsadress"
'word' "toimittajan postinumero", 'language' "sv", 'translation' "leverantörens postnummer"
'word' "toimittajan postitoimipaikka", 'language' "sv", 'translation' "leverantörens postkontor"
'word' "toimittajanumero", 'language' "sv", 'translation' "leverantörens nummer"
'word' "toimituksen asiakasnumero", 'language' "sv", 'translation' "leveransens kundnummer"
'word' "toimituksen asiakkaan nimi", 'language' "sv", 'translation' "leverans kundens namn"
'word' "toimituksen rivinumero", 'language' "sv", 'translation' "leveransens radnummer"
'word' "toimituksen tilattu määrä", 'language' "sv", 'translation' "leveransens beställda antal"
'word' "toimituksen tilausnumero", 'language' "sv", 'translation' "leveransens beställningsnummer"
'word' "toimituksen tuotenumero", 'language' "sv", 'translation' "leveransens produktnummer"
'word' "toimituksen tuotteen hinta", 'language' "sv", 'translation' "leveransens produktpris"
'word' "toimituksen tuotteen nimi", 'language' "sv", 'translation' "leveransproduktnamn"
'word' "toimitusnumero", 'language' "sv", 'translation' "leveransnummer"
'word' "tuotenumero", 'language' "sv", 'translation' "produktnummer"
'word' "tuotteen hinta", 'language' "sv", 'translation' "produktpris"
'word' "tuotteen nimi", 'language' "sv", 'translation' "produktnamn"
'word' "asiakasnumero", 'language' "ru", 'translation' "номер клиента"
'word' "asiakkaan email", 'language' "ru", 'translation' "адрес электронной почты клиента"
'word' "asiakkaan maa", 'language' "ru", 'translation' "страна клиента"
'word' "asiakkaan nimi", 'language' "ru", 'translation' "имя клиента"
'word' "asiakkaan osoite", 'language' "ru", 'translation' "адрес клиента"
'word' "asiakkaan postinumero", 'language' "ru", 'translation' "почтовый индекс клиента"
'word' "asiakkaan postitoimipaikka", 'language' "ru", 'translation' "почтовое отделение клиента"
'word' "laskun asiakasnumero", 'language' "ru", 'translation' "номер клиента счета-фактуры"
'word' "laskun asiakkaan nimi", 'language' "ru", 'translation' "имя клиента счета-фактуры"
'word' "laskun rivinumero", 'language' "ru", 'translation' "номер строки счета-фактуры"
'word' "laskun tilausnumero", 'language' "ru", 'translation' "номер заказа счета-фактуры"
'word' "laskun toimitusnumero", 'language' "ru", 'translation' "номер доставки счета-фактуры"
'word' "laskun tuotenumero", 'language' "ru", 'translation' "номер продукта счета-фактуры"
'word' "laskun tuotteen hinta", 'language' "ru", 'translation' "цена продукта счета-фактуры"
'word' "laskun tuotteen nimi", 'language' "ru", 'translation' "название продукта счета-фактуры"
'word' "laskunumero", 'language' "ru", 'translation' "номер счета-фактуры"
'word' "tilattu määrä", 'language' "ru", 'translation' "заказанное количество"
'word' "tilauksen asiakasnumero", 'language' "ru", 'translation' "номер клиента заказа"
'word' "tilauksen asiakkaan nimi", 'language' "ru", 'translation' "имя заказчика заказа"
'word' "tilauksen rivinumero", 'language' "ru", 'translation' "заказ номер строки"
'word' "tilauksen tuotenumero", 'language' "ru", 'translation' "номер продукта заказа"
'word' "tilauksen tuotteen hinta", 'language' "ru", 'translation' "цена продукта заказа"
'word' "tilauksen tuotteen nimi", 'language' "ru", 'translation' "название продукта заказа"
'word' "tilausnumero", 'language' "ru", 'translation' "имя продукта заказа"
'word' "toimitettu määrä", 'language' "ru", 'translation' "количество доставки"
'word' "toimittajan email", 'language' "ru", 'translation' "адрес электронной почты поставщика"
'word' "toimittajan maa", 'language' "ru", 'translation' "страна поставщика"
'word' "toimittajan nimi", 'language' "ru", 'translation' "имя поставщика"
'word' "toimittajan osoite", 'language' "ru", 'translation' "адрес поставщика"
'word' "toimittajan postinumero", 'language' "ru", 'translation' "почтовый индекс поставщика"
'word' "toimittajan postitoimipaikka", 'language' "ru", 'translation' "почтовое отделение поставщика"
'word' "toimittajanumero", 'language' "ru", 'translation' "номер поставщика"
'word' "toimituksen asiakasnumero", 'language' "ru", 'translation' "номер клиента доставки"
'word' "toimituksen asiakkaan nimi", 'language' "ru", 'translation' "имя клиента доставки"
'word' "toimituksen rivinumero", 'language' "ru", 'translation' "номер строки доставки"
'word' "toimituksen tilattu määrä", 'language' "ru", 'translation' "заказанное количество доставки"
'word' "toimituksen tilausnumero", 'language' "ru", 'translation' "номер заказа доставки"
'word' "toimituksen tuotenumero", 'language' "ru", 'translation' "номер продукта доставки"
'word' "toimituksen tuotteen hinta", 'language' "ru", 'translation' "цена продукта доставки"
'word' "toimituksen tuotteen nimi", 'language' "ru", 'translation' "наименование поставки"
'word' "toimitusnumero", 'language' "ru", 'translation' "номер поставки"
'word' "tuotenumero", 'language' "ru", 'translation' "номер продукта"
'word' "tuotteen hinta", 'language' "ru", 'translation' "цена продукта"
'word' "tuotteen nimi", 'language' "ru", 'translation' "название продукта"
'word' "asiakasnumero", 'language' "sp", 'translation' "número de cliente"
'word' "asiakkaan email", 'language' "sp", 'translation' "correo electrónico del cliente"
'word' "asiakkaan maa", 'language' "sp", 'translation' "país del cliente"
'word' "asiakkaan nimi", 'language' "sp", 'translation' "nombre del cliente"
'word' "asiakkaan osoite", 'language' "sp", 'translation' "dirección del cliente"
'word' "asiakkaan postinumero", 'language' "sp", 'translation' "código postal del cliente"
'word' "asiakkaan postitoimipaikka", 'language' "sp", 'translation' "oficina postal del cliente"
'word' "laskun asiakasnumero", 'language' "sp", 'translation' "número de cliente de la factura"
'word' "laskun asiakkaan nimi", 'language' "sp", 'translation' "nombre del cliente de la factura"
'word' "laskun rivinumero", 'language' "sp", 'translation' "número de línea de la factura"
'word' "laskun tilausnumero", 'language' "sp", 'translation' "número de pedido de la factura"
'word' "laskun toimitusnumero", 'language' "sp", 'translation' "número de entrega de la factura"
'word' "laskun tuotenumero", 'language' "sp", 'translation' "número de producto de la factura"
'word' "laskun tuotteen hinta", 'language' "sp", 'translation' "precio del producto de la factura"
'word' "laskun tuotteen nimi", 'language' "sp", 'translation' "nombre del producto de la factura"
'word' "laskunumero", 'language' "sp", 'translation' "número de factura"
'word' "tilattu määrä", 'language' "sp", 'translation' "cantidad pedida"
'word' "tilauksen asiakasnumero", 'language' "sp", 'translation' "número de cliente del pedido"
'word' "tilauksen asiakkaan nimi", 'language' "sp", 'translation' "nombre del cliente del pedido"
'word' "tilauksen rivinumero", 'language' "sp", 'translation' "pedido número de línea"
'word' "tilauksen tuotenumero", 'language' "sp", 'translation' "número de producto del pedido"
'word' "tilauksen tuotteen hinta", 'language' "sp", 'translation' "precio del producto del pedido"
'word' "tilauksen tuotteen nimi", 'language' "sp", 'translation' "nombre del producto del pedido"
'word' "tilausnumero", 'language' "sp", 'translation' "nombre del producto del pedido"
'word' "toimitettu määrä", 'language' "sp", 'translation' "cantidad entregada"
'word' "toimittajan email", 'language' "sp", 'translation' "correo electrónico del proveedor"
'word' "toimittajan maa", 'language' "sp", 'translation' "país del proveedor"
'word' "toimittajan nimi", 'language' "sp", 'translation' "nombre del proveedor"
'word' "toimittajan osoite", 'language' "sp", 'translation' "dirección del proveedor"
'word' "toimittajan postinumero", 'language' "sp", 'translation' "código postal del proveedor"
'word' "toimittajan postitoimipaikka", 'language' "sp", 'translation' "oficina postal del proveedor"
'word' "toimittajanumero", 'language' "sp", 'translation' "número del proveedor"
'word' "toimituksen asiakasnumero", 'language' "sp", 'translation' "número de cliente de la entrega"
'word' "toimituksen asiakkaan nimi", 'language' "sp", 'translation' "nombre del cliente de la entrega"
'word' "toimituksen rivinumero", 'language' "sp", 'translation' "número de línea de entrega"
'word' "toimituksen tilattu määrä", 'language' "sp", 'translation' "cantidad pedida de la entrega"
'word' "toimituksen tilausnumero", 'language' "sp", 'translation' "número de orden de la entrega"
'word' "toimituksen tuotenumero", 'language' "sp", 'translation' "número de producto de la entrega"
'word' "toimituksen tuotteen hinta", 'language' "sp", 'translation' "precio del producto de la entrega"
'word' "toimituksen tuotteen nimi", 'language' "sp", 'translation' "nombre del producto de entrega"
'word' "toimitusnumero", 'language' "sp", 'translation' "número de entrega"
'word' "tuotenumero", 'language' "sp", 'translation' "número de producto"
'word' "tuotteen hinta", 'language' "sp", 'translation' "precio del producto"
'word' "tuotteen nimi", 'language' "sp", 'translation' "nombre del producto"
'word' "asiakasnumero", 'language' "ch", 'translation' "客户编号"
'word' "asiakkaan email", 'language' "ch", 'translation' "客户电子邮件"
'word' "asiakkaan maa", 'language' "ch", 'translation' "客户国家/地区"
'word' "asiakkaan nimi", 'language' "ch", 'translation' "客户名称"
'word' "asiakkaan osoite", 'language' "ch", 'translation' "客户地址"
'word' "asiakkaan postinumero", 'language' "ch", 'translation' "客户邮政编码"
'word' "asiakkaan postitoimipaikka", 'language' "ch", 'translation' "客户邮局"
'word' "laskun asiakasnumero", 'language' "ch", 'translation' "发票客户编号"
'word' "laskun asiakkaan nimi", 'language' "ch", 'translation' "发票客户名称"
'word' "laskun rivinumero", 'language' "ch", 'translation' "发票行编号"
'word' "laskun tilausnumero", 'language' "ch", 'translation' "发票订单号"
'word' "laskun toimitusnumero", 'language' "ch", 'translation' "发票交货号"
'word' "laskun tuotenumero", 'language' "ch", 'translation' "发票产品号"
'word' "laskun tuotteen hinta", 'language' "ch", 'translation' "发票产品价格"
'word' "laskun tuotteen nimi", 'language' "ch", 'translation' "发票产品名称"
'word' "laskunumero", 'language' "ch", 'translation' "发票编号"
'word' "tilattu määrä", 'language' "ch", 'translation' "订购数量"
'word' "tilauksen asiakasnumero", 'language' "ch", 'translation' "订单客户编号"
'word' "tilauksen asiakkaan nimi", 'language' "ch", 'translation' "订单客户名称"
'word' "tilauksen rivinumero", 'language' "ch", 'translation' "订单行号"
'word' "tilauksen tuotenumero", 'language' "ch", 'translation' "订单产品编号"
'word' "tilauksen tuotteen hinta", 'language' "ch", 'translation' "订单产品价格"
'word' "tilauksen tuotteen nimi", 'language' "ch", 'translation' "订单产品名称"
'word' "tilausnumero", 'language' "ch", 'translation' "订单产品名称"
'word' "toimitettu määrä", 'language' "ch", 'translation' "交付数量"
'word' "toimittajan email", 'language' "ch", 'translation' "供应商电子邮件"
'word' "toimittajan maa", 'language' "ch", 'translation' "供应商国家/地区"
'word' "toimittajan nimi", 'language' "ch", 'translation' "供应商名称"
'word' "toimittajan osoite", 'language' "ch", 'translation' "供应商地址"
'word' "toimittajan postinumero", 'language' "ch", 'translation' "供应商邮政编码"
'word' "toimittajan postitoimipaikka", 'language' "ch", 'translation' "供应商邮局"
'word' "toimittajanumero", 'language' "ch", 'translation' "供应商编号"
'word' "toimituksen asiakasnumero", 'language' "ch", 'translation' "交付客户编号"
'word' "toimituksen asiakkaan nimi", 'language' "ch", 'translation' "交付客户编号"
'word' "toimituksen rivinumero", 'language' "ch", 'translation' "交付行编号"
'word' "toimituksen tilattu määrä", 'language' "ch", 'translation' "交付订单数量"
'word' "toimituksen tilausnumero", 'language' "ch", 'translation' "交付订单编号"
'word' "toimituksen tuotenumero", 'language' "ch", 'translation' "交付产品编号"
'word' "toimituksen tuotteen hinta", 'language' "ch", 'translation' "交付产品价格"
'word' "toimituksen tuotteen nimi", 'language' "ch", 'translation' "交货产品名称"
'word' "toimitusnumero", 'language' "ch", 'translation' "交货编号"
'word' "tuotenumero", 'language' "ch", 'translation' "产品编号"
'word' "tuotteen hinta", 'language' "ch", 'translation' "产品价格"
'word' "tuotteen nimi", 'language' "ch", 'translation' "产品名称"

Db2:sen muuttujia:

struct query *first_query=NULL;

#define MAXNAME 33
#define MAXVALUE 1024

Query rakenteen query osoite osoittaa kyselyyn ja data rakenteen data osoite osoittaa vastausriviin.

DB2 puolen pääfunktiot:

void db_open(char *filename) — tällä avataan tietokanta

void db_query(char *query) — Tällä määritellään kysely ja haetaan vastaus

struct data *db_query_get_first(struct query *q) — Tällä saadaan kyselyn ensimmäinen vastausrivi.

struct data *db_data_get_next(struct data *d) — Tällä saadaan kyselyn seuraava vastausrivi

int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2) — luetaan kenttä tietueesta

void db_save(struct data *d) — Talletetaan tietue

Db2 funktiot:

static void db_skipwhite(unsigned char **p2);
static unsigned char *save_string(unsigned char *string);
static struct data *db_create_data(unsigned char *data);
static struct query *db_create_query(unsigned char *query);
static struct query *db_fetch_query(char *query);
void dump_queries();
static struct data *db_add_data(struct data **data, unsigned char *buf);
static struct data *db_query_add_data(struct query *q,unsigned char *buf);
static int db_parsename(int namelen, unsigned char *name, unsigned char **p2);
static int db_parsevalue(int valuelen, unsigned char *value, unsigned char **p2);
static int db_parse_nameandvalue(int namelen, unsigned char *name, int valuelen, unsigned char *value, unsigned char **p2);
static int db_compare(unsigned char *query, unsigned char *string);
static void db_get_elements(int lenstring2, unsigned char *string2, unsigned char *query, unsigned char *string);
struct query *db_query(char *query);
struct data *db_query_get_first(struct query *q);
struct data *db_data_get_next(struct data *d);
int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2);
static void db_parse_header(int headersize,unsigned char *header, unsigned char *string);
void db_open(char *filename);

Seuraava kappale on fort:in satunnaisuuden keräämistä varten:

#define DB2_EVENTS 2
static unsigned int db2_events = 1;
static unsigned int db2_event_mode = 6;

#ifdef DB2_EVENTS
#define DB2_EVENTS_START(source) \
  IUTIME micros; \
  static int \
    pool=0, pool2=0; \
  if(db2_events) { \
    fort_add_random_event_time(&pool, \
    source, db2_event_mode); \
    fort_add_random_event_timer_start(&micros); \
  }
#else
#define DB2_EVENTS_START(source)
#endif

#ifdef DB2_EVENTS
#define DB2_EVENTS_END(source) \
  if(db2_events) \
    fort_add_random_event_timer_do(&pool2, \
        source, db2_event_mode, &micros);
#else
#define DB2_EVENTS_END(source)
#endif

Tämä rutiini tulostaa muistissa olevat kyselyt:

void dump_queries()
{
  struct query *q=first_query;
  struct data *d;

  while(q!=NULL) {
    fprintf(stdout,"Header: (addr:%p",q);
    fprintf(stdout,", text=\"%s\"",q->query);
    fprintf(stdout,", keys=\"%s\"",q->keys);
    fprintf(stdout,", count=%ld",q->count);
    fprintf(stdout,", first_data=%p",q->first_data);
    fprintf(stdout,", next=%p",q->next_query);
    fprintf(stdout,")\n");
    fflush(stdout);

    d=q->first_data;
    while(d!=NULL) {
      fprintf(stdout,"Data:   (addr:%p",d);
      fprintf(stdout,", data=\"%s\"",d->data);
      fprintf(stdout,", next_data=%p",d->next_data);
      fprintf(stdout,")\n");
      fflush(stdout);
      d=d->next_data;
    }
    q=q->next_query;
  }
}
Rutiini tallettaa merkkijonon ja palauttaa osoitteen siihen:
static unsigned char *save_string(unsigned char *string)
{
  unsigned char *temp=malloc(strlen(string)+1);
  strcpy(temp, string);
  return(temp);
}

Tällä allokoidaan tila yhdelle data lohkolle:

static struct data *db_create_data(unsigned char *data)
{
  struct data *temp;

  if((temp = malloc(sizeof(struct data))) != NULL) {
    temp->data = save_string(data);
    temp->changes = 0;
    temp->next_data = NULL;
    return(temp);
  }
  return(NULL);
}

Tällä taas saadaan tila yhdelle query-rakenteelle:

static struct query *db_create_query(unsigned char *query)
{
  struct query *temp;

  if((temp = malloc(sizeof(struct query))) != NULL) {
    temp->query = save_string(query);
    temp->keys = NULL;
    temp->count = 0;
    temp->first_data = NULL;
    temp->next_query = NULL;
  }
  return(temp);
}

Seuraava rutiini hakee halutun kyselyn ja palauttaa osoitteen siihen: aluksi while(*ppquery) luuppi etsii kyselyä, joka on samanlainen parametrinä annetun kyselyn kanssa. Jos sellainen löytyy, se poistetaan query ketjusta ja lisätään takaisin ketjun alkuun (if found). Näin useimmin käytetyt kyselyt ovat ketjussa ensimmäisinä. Jos taas ketjua ei löydyy, kysely ketjun alkuun lisätään uusi kysely (else). Rutiini palauttaa osoitteen query-rakenteeseen.

static struct query *db_fetch_query(char *query)
{
  int found;
  struct query **ppquery, *thisq;

  ppquery = &first_query;
  found = 0;
  while(*ppquery != NULL) {
    if(!strcmp(query, (*ppquery)->query)) {
      found = 1;
      break;
    }
    ppquery = &((*ppquery)->next_query);
  }
  if(found) {
    // remove from list                                                                                                                                  
    thisq = *ppquery;
    *ppquery = thisq->next_query;
    // add back to 1st of list                                                                                                                           
    thisq->next_query = first_query;
    first_query = thisq;
  } else {
    // create new                                                                                                                                        
    thisq = db_create_query(query);
    thisq->next_query = first_query;
    first_query = thisq;
  }

  return(thisq);
}

Seuraava rutiini lisää yhden kyselyn vastaustietueen data ketjuun. Jos tietue on jo ketjussa, sitä ei lisätä, ja jos tietuetta ei ole tietue luodaan (if (!found)). Ketjua ylläpidetään siten että data ketju on aakkosjärjestyksessä.

static struct data *db_add_data(struct data **data, unsigned char *buf) {
  int found;
  struct data **pdata = data;
  struct data *pnew;

  DB2_EVENTS_START(202)

  found=0;
  while(*pdata!=NULL) {
    if(!strcmp(buf,(*pdata)->data)) {
      found = 1;
      break;
    }
    if(strcmp(buf,(*pdata)->data)<0) {
      break;
    }
    pdata=&((*pdata)->next_data);
  }
  if(!found) {
    pnew = db_create_data(buf);
    pnew -> next_data = *pdata;
    *pdata = pnew;
  }

  DB2_EVENTS_END(203)

  return(pnew);
}

static struct data *db_query_add_data(struct query *q, unsigned char *buf)
{
  if(db_add_data(&q->first_data,buf) !=
      NULL)
    q->count++;
}

Sitten funktio, jolla ohitetaan merkkijonosta välilyönnit ja tabit:

static void db_skipwhite(unsigned char **p2)
{
  unsigned char *p;

  p=*p2;
  while(*p==' ' || *p=='\t')
    p++;
  *p2=p;
}

Tällä luetaan nimi merkkijonosta. Nimi koostuu heittomerkeistä ja niiden välissä olevista merkeistä. Merkkejä ei aakkostarkasteta, joten nimessä voi olla mitä tahansa merkistöä tahansa. Myöskään nimiä (ja arvoja) ei vielä unicode tarkasteta, pitkissä nimissä (tai arvoissa) ei ole merkkikatkoa. Unicodessa:han yksi merkki muodostuu useammista fyysisistä merkeistä, ja jos kirjainmerkki katkaistaan kesken fyysisten merkkien voi tulla ongelmia.

Näissä funktioissa paikka merkkijonossa kerrotaan p2 osoitteella. Sen sisältö osoittaa seuraavaan parsittavaan merkkiin.

static int db_parsename(int namelen, unsigned char *name, unsigned char **p2)
{
  int stat, ncount;
  unsigned char *p, *n;

  p=*p2;
  n=name;
  ncount=0;
  stat=0;

  db_skipwhite(&p);
  if(*p == '\'') { // name with '                                                                                                                          
    p++;
    while(*p != '\'' && *p != '\0') {
      if(++ncount < namelen)
        *n++ = *p;
      p++;
      stat = 1;
    }
    if(*p == '\'')
      p++;
  }
  *n = '\0';
  *p2 = p;

  return(stat);
}

Arvon lukuun tarkoitettu rutiini: kuten kenttien nimissä tämäkin voi koostua mistä tahansa merkistöistä. Arvo on aina tuplahipsujen (“) välissä. P2 muuttuja osoittaa merkkiin jota käsitellään ja sinne päivitetään rutiinin lopussa seuraava merkki.

static int db_parsevalue(int valuelen, unsigned char *value, unsigned char **p2)
{
  int stat, vcount;
  unsigned char *p, *v;

  p = *p2;
  v = value;
  vcount = 0;
  stat = 0;

  db_skipwhite(&p);
  if(*p == '"') { // value with " -- skip                                                                                                                  
    p++;
    while(*p != '\"' && *p != '\0') {
      if(++vcount < valuelen)
         *v++ = *p;
      p++;
      stat = 2;
    }
    if(*p == '\"')
      p++;
  }
  *v = '\0';
  *p2 = p;

  return(stat);
}

Tällä parsitaan nimi arvo pari. Tässä nimen ja arvon välissä voi olla ‘=’.

static int db_parse_nameandvalue(int namelen, unsigned char *name, int valuelen, unsigned char *value, unsigned char **p2)
{
  int stat;
  unsigned char *p,*n,*v;

  DB2_EVENTS_START(204)

  p = *p2;
  n = name;
  *n = '\0';
  v = value;
  *v = '\0';
  stat = 0;

  db_skipwhite(&p);
  stat=db_parsename(namelen,name,&p);
  db_skipwhite(&p);
  if(*p == '=') {
    p++;
  }
  db_skipwhite(&p);
  if(*p == '\"') {
    stat=db_parsevalue(valuelen,value,&p);
  }
  if(*p == ',') {
    p++;
  }
  *p2 = p;

  DB2_EVENTS_END(205)

  return(stat);
}

Tällä verrataan tietuetta kyselyyn ja palautetaan 1 jos tietue mätsää 0 jos ei.

static int db_compare_all(unsigned char *query, unsigned char *string)
{
  int stat,stat2,ok,ok2;
  unsigned char *q, *s;
  unsigned char name[MAXNAME], value[MAXVALUE],
      name2[MAXNAME], value2[MAXVALUE];

  DB2_EVENTS_START(206)

  ok = 1;
  q = query;
  db_skipwhite(&q);
  while((stat = db_parse_nameandvalue(
      sizeof(name), name,
      sizeof(value), value, &q))>0) {
    ok2 = 0;
    s = string;
    db_skipwhite(&s);
    while((stat2 = db_parse_nameandvalue(
        sizeof(name2), name2,
        sizeof(value2), value2, &s))>0) {
      ok2=0;
      if(!strcmp(name, name2)) {
        if(stat == 2 && stat2 == 2) {
          if(!strcmp(value, value2))
            ok2 = 1;
        } else if(stat==1 && stat2==2) {
          ok2 = 1;
        }
      }
      if(ok2)
        break;
    }
    if(!ok2)
      ok = 0;
  }

  DB2_EVENTS_END(207)

  return(ok);
}

Tämä poimii kyselyn sisältämät elementit merkkijonosta:

static void db_get_elements(int lenstring2, unsigned char *string2, unsigned char *query, unsigned char *string)
{
  int first,stat,stat2;
  unsigned char name[MAXNAME],
      value[MAXVALUE], name2[MAXNAME],
      value2[MAXVALUE];
  unsigned char *q,*s;

  *string2='\0';
  first=1;
  q=query;
  db_skipwhite(&q);
  while((stat = db_parse_nameandvalue(
      sizeof(name), name,
      sizeof(value), value,&q))>0) {
    s = string;
    db_skipwhite(&s);
    while((stat2 = db_parse_nameandvalue(
        sizeof(name2), name2,
        sizeof(value2), value2, &s))>0) {
      if(!strcmp(name, name2)) {
        if((strlen(string2) + strlen(name2) +
            strlen(value2) +5)<lenstring2) {
          if(!first)
            strcat(string2,", ");
          strcat(string2,"\'");
          strcat(string2,name2);
          strcat(string2,"\'");
          strcat(string2," ");
          strcat(string2,"\"");
          strcat(string2,value2);
          strcat(string2,"\"");
          first=0;
        }
      }
    }
  }
}

Tällä tarkistetaan sisältääkö merkkijono halutun elementin. Tätä käytetään kun päätellään tietueen avaimet.

static int db_contains_element(char *name2, char *string)
{
  unsigned char *s;
  unsigned char name[MAXNAME],
      value[MAXVALUE];

  s = string;
  db_skipwhite(&s);
  while(db_parse_nameandvalue(
      sizeof(name), name,
      sizeof(value), value, &s)>0) {
    if(!strcmp(name, name2))
      return(1);
  }
  return(0);
}

Tämä rutiini suorittaa kyselyn. Tällä hetkellä se on vain simppeli tiedoston luku, siihen on joukko muutoksia jonossa. (muut kyselyt pohjana, vierasavainten päässä olevien kenttien “join”, suurista tiedostoista vain osa muistissa, jne.)

static struct query *db_query2(char *query)
{
  struct query *q;
  unsigned char
      string[8192],
      string2[8192],
      header[8192];

  FILE *fp1;

  DB2_EVENTS_START(208)

  q = db_fetch_query(query);

  if((fp1 = fopen(dbfilename, "r")) != NULL) {
    while(fgets(string, sizeof(string), fp1) != NULL) {
      string[strlen(string)-1] = '\0';
      if(string[0] != ';') {
        if(db_compare_all(query, string)) {
          db_get_elements(sizeof(string2), string2,
              query, string);
          db_query_add_data(q, string2);
        }
      }
    }
    fclose(fp1);
  }

  DB2_EVENTS_END(209)

  return(q);
}

Tämä funktio palauttaa tietueesta jonkin tietueen kentän arvon:

int db_data_get_field(struct data *d, unsigned char *name2, int value2len, unsigned char *value2)
{
  int stat;
  unsigned char name[MAXNAME], value[MAXVALUE];
  unsigned char *p;

  p = d->data;
  db_skipwhite(&p);
  while((stat = db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &p))>0) {
    if(!strncmp(name, name2,
        strlen(name2))) {
      strncpy(value2, value, value2len);
      if(value2len <= strlen(value))
        value2[value2len-1] = '\0';
      return(1);
    }
  }
  return(0);
}

Seuraava lisää kyselyyn listan kyselyn avaimista. Tätä tarvitaan esimerkiksi save-toiminnossa.

void db_update_keys(struct query *q)
{
  int found = 0;
  unsigned char keys[1024];
  struct query *q2;
  struct data *d2;

  keys[0] = '\0';
  q2=db_query2("'memberid', 'key' \"key\"");
  d2=db_query_get_first(q2);
  while(d2 != NULL) {
    int first = 1;
    unsigned char memberid[MAXNAME];

    if(db_data_get_field(d2, "memberid", sizeof(memberid),
        memberid)) {
      if(db_contains_element(memberid, q->query)) {
        if(!first)
          strcat(keys, ", ");
        strcat(keys, "'");
        strcat(keys, memberid);
	strcat(keys, "'");
        found = 1;
        first = 0;
      }
    }
    d2 = db_data_get_next(d2);
  }
  if(found)
    q->keys = save_string(keys);
  else
    q->keys = save_string("none");
}

Tätä rutiinia kutsutaan kyselyssä, ja se vain kutsuu kysely kakkosta. Tämä se vuoksi että kyselyn tulkinnassa voidaan tehdä lisäkyselyjä.

struct query *db_query(char *query)
{
  struct query *q,*q2;
  struct data *d2;

  q = db_query2(query);

  if(q->keys == NULL) {
    db_update_keys(q);
  }

  return(q);
}

Seuraavalla saadaan kyselyn palauttamasta query-tietueesta ensimmäinen kyselyn vastausrivi:

struct data *db_query_get_first(struct query *q)
{
  return(q->first_data);
}

Seuraavalla saadaan kyselyn data-tietueesta seuraava data tietue:

struct data *db_data_get_next(struct data *d)
{
  return(d->next_data);
}

Funktio palauttaa tietueesta tietueen kenttien nimet, eli kyselyn, jonka vastaus tietue on.

static void db_parse_header(int headersize,unsigned char *header, unsigned char *string)
{
  int first;
  unsigned char *p, name[MAXNAME], value[MAXVALUE];

  *header = '\0';
  first = 1;
  p = string;
  db_skipwhite(&p);
  while(db_parse_nameandvalue(sizeof(name), name,
      sizeof(value), value, &p)>0) {
    if((strlen(header) + strlen(name) + 2) <
        headersize) {
      if(!first)
        strcat(header, ", ");
      strcat(header, "'");
      strcat(header, name);
      strcat(header, "'");
      first = 0;
    }
  }
}

Seuraavalla funktiolla avataan tietokanta:

void db_open(char *filename)
{
  int stringlen = 8192, headerlen = 8192;
  unsigned char *string, *header;
  unsigned char buffer[1024];
  FILE *fp1;

  string = malloc(stringlen);
  header = malloc(headerlen);

  strcpy(dbfilename, filename);

  if((fp1 = fopen(dbfilename, "r"))
      != NULL) {
    while(fgets(string, stringlen, fp1)
        != NULL) {
      while(string[strlen(string)-1] != '\n') {
        string = realloc(string,
            stringlen*2);
        fgets(string+stringlen-1,
            stringlen+1, fp1);
        stringlen *= 2;
      }
      string[strlen(string)-1] = '\0';
      db_parse_header(sizeof(header),
          header, string);
      db_query(header);
    }
    fclose(fp1);
  }

  free(string);
  free(header);

  fprintf(stdout,"\n");
}

Seuraavaksi siirrytään DBS alkuisiin rutiineihin. Aluksi muutamia muuttujia:

char *procname;
char *programname = "DBS version 0.6 ©";

unsigned char *htmlin;
int htmlinlen=2048;

#define HTMLTIMEFORMAT "%a, %d %b %Y %H:%M:%S GMT"

unsigned char htmlmethod[10];
unsigned char htmlpath[128];
unsigned char htmlversion[32];
unsigned char htmlfilename[64];
unsigned char htmlsessionid[33];
unsigned char htmltime[32];
unsigned char htmluserid[24];
unsigned char htmlpassword[24];
unsigned char *htmlparams=NULL;
unsigned char htmldigest[HashLen*2+1];
unsigned char htmlmode[10];
unsigned char htmlip[32];
unsigned char htmlport[10];
unsigned char htmllanguage[10];
#define TIMEFORMAT "%Z%Y%m%d%H%M%S"

Näillä ensimmäisillä rutiineilla muodostetaan käyttäjälle istuntoavain. Satunnaisbittien tekemiseen voidaan käyttää tässä sekä fort:ia ja ressua. Jos haluat kokeilla sovellusta käytännössä, mukaan on lisätty kevytversio ressusta, näin fort:ia ja ressua (+sha256) ei tarvitse kopioida.

Session id annetaan istunnolle kun käyttäjä syöttää käyttäjätunnuksen ja salasanan onnistuneesti (dbs_logon). Session id:stä tehdään kookie selaimelle.

Ensimmäinen merkki session id:ssä on pieni tai iso kirjain ja loput merkit ovat numeroita, pieniä tai isoja kirjaimia. Pieniä ja isoja kirjaimia on 26+26 on 52 ja siihen numerot, erilaisia merkkejä on 62, eli yksi merkki on vajaat 6 bittiä. Erilaisia istunto id:itä on 52*62^31=1.90e57.

#define aFORT 2
#define aRESSU 2

unsigned char dbs_clockbyte() /* JariK 2013 */
{
  struct timeval tv;

  gettimeofday(&tv,NULL);

  return(tv.tv_usec & 0xff);
}

void dbs_ressu_genbytes_fast(int size, unsigned char *buffer) /* JariK 09/2020 */
{
  int c, d, e, f, byte;

  f=0;

  for(c=0; c<8 || c%8!=0 || c<16 ; c++) {
    for(d=0; d<size; d++) {
      e = buffer[d];
      e = ((e&0x80)>>7) | ((e&0x7f)<<1);
      byte = dbs_clockbyte();
      buffer[d] = e^byte;
    }
    for(d=0; d<size; d++) {
      f = (f+buffer[d])%size;
      e = buffer[d];
      buffer[d] = buffer[f];
      buffer[f] = e;
    }
  }
}

#define RANDOMCNT 256
#define aDBS_RANDOM_CLEAR 2

static unsigned char dbs_random_bytes[RANDOMCNT];
static int dbs_random_byte = 999999999;
static int dbs_random_cnt = RANDOMCNT;

int dbs_random_genbyte()
{
  if(dbs_random_byte>=dbs_random_cnt) {
#ifdef DBS_RANDOM_CLEAR
    memset(dbs_random_bytes, 0,
        dbs_random_cnt);
#else
    if(dbs_random_byte == 999999999)
      memset(dbs_random_bytes, 0,
          dbs_random_cnt);
#endif
#ifdef FORT
    fort_random_data(dbs_random_cnt,
        dbs_random_bytes);
#endif
#ifdef RESSU
    ressu_genbytes(dbs_random_cnt,
        dbs_random_bytes);
#endif
    dbs_ressu_genbytes_fast(dbs_random_cnt,
        dbs_random_bytes);
    dbs_random_byte = 0;
  }
  return(dbs_random_bytes[
      dbs_random_byte++]);
}

int dbs_random_genbyte_limit(int limit)
{
  int c;

  while((c = dbs_random_genbyte()) >=
      (256/limit)*limit);
  /* while((c = fort_random_data_byte())>                                                                                                                                                                                                                                                                                                                                      
      (256/limit)*limit); little bug */
  return(c % limit);
}

void dbs_random_clear()
{
  fort_clear();
  ressu_clear();
  memset(dbs_random_bytes, 0,
      dbs_random_cnt);
  dbs_random_byte = 999999998;
}

void dbs_random_genbuffer(int size, unsigned char *buffer)
{
  int c;

  for(c = 0;c < size;c++)
    buffer[c] ^= dbs_random_genbyte();
}

void dbs_gensessionid(int size, unsigned char *buffer)
{
  int len,byte,first;
  unsigned char chars[] =
    "0123456789" \
    "abcdefghijklmnopqrstuvwxyz" \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  unsigned char *n;


  dbs_random_clear();
  n=buffer;
  len=0;
  first=1;
  while(++len<size) {
    if(first)
      byte=dbs_random_genbyte_limit(
          sizeof(chars)-11)+10;
    else
      byte=dbs_random_genbyte_limit(
          sizeof(chars)-1);
    *n++=chars[byte];
    first=0;
  }
  *n='\0';
  dbs_random_clear();
}

Tulostetaan kysely debukkausta varten:

void dbs_dump_query(struct query *q)
{
  struct data *d;

  html_printf("query: %s<br>",q->query);
  d = db_query_get_first(q);
  while(d != NULL) {
    html_printf("data: %s<br>",d->data);
    d = db_data_get_next(d);
  }
}

Seuraavalla rutiinilla lisätään ohjelman kookie selaimen muistiin: kookiena talletetaan session id ja htmlmoodi. Max-Age kertoo kuinka kauan sekunteina kookie on käyttökelpoinen, tässä 24 tuntia. Kookie päivitetään aina html lähetyksen yhteydessä, joten kookie on käyttökelpoinen 24 tuntia viimeisen html-lomakkeen vastaanotosta. Kookieta ei tarkasteta vielä alkuperäiseen kookieen.

#define DBS_RENEW_SESSION_HOURS 24

void dba_renew_cookie()
{
  dbs_html_buf_printf(HTML_HEADER_BUFFER,
    "Set-Cookie: sessionid=%s; Max-Age=%d\r\n",
    htmlsessionid, 3600*DBS_RENEW_SESSION_HOURS);
  dbs_html_buf_printf(HTML_HEADER_BUFFER,
    "Set-Cookie: mode=%s; Max-Age=%d\r\n",
    htmlmode, 3600*DBS_RENEW_SESSION_HOURS);
  dbs_html_buf_printf(HTML_HEADER_BUFFER,
    "Set-Cookie: language=%s; Max-Age=%d\r\n",
    htmllanguage, 3600*DBS_RENEW_SESSION_HOURS);
}

Seuraavana uudelleen paketoidut tietokantarutiinit: rutiinit on paketoitu uudelleen sitä varten että saadaan html-raportti tehdyistä kyselyistä ja niiden sisällöstä: raportti kirjoitetaan dbs_html rutiinin kakkospuskuriin. Puskuria käsitetään seuraavan rutiinin lisäksi dba_main() rutiinissa ja tietenkin dbs_html rutiineissa.

struct query *dba_query(unsigned char *query)
{
  int save_html;
  struct query *q;
  struct data *d;

  // query statements and data                                                                                                                
  dbs_html_buf_printf(HTML_QUERY_BUFFER,
      "query: %s\n<br>", query);
  q=db_query(query);
  d=db_query_get_first(q);
  while(d!=NULL) {
    dbs_html_buf_printf(HTML_QUERY_BUFFER,
        "data: %s\n<br>",d->data);
    d = db_data_get_next(d);
  }
  return(q);
}

struct data *dba_query_get_first(struct query *q)
{
  return(db_query_get_first(q));
}

struct data *dba_data_get_next(struct data *d)
{
  return(db_data_get_next(d));
}

get_seconds rutiini on jo vanha tuttu:

unsigned long dbs_getseconds()
{
  struct timeval tv;

  gettimeofday(&tv, NULL);

  return((IUTIME)tv.tv_sec);
}

Seuraava aliohjelma tulostaa printf tyyppisellä määrityksellä tehdyn merkkijonon html-puskuriin. Dbs_html_set funktiolla valitaan aktiivinen puskuri eli mihin puskuriin html “rivi” tulostetaan, dbs_html_clear rutiinilla tämänhetkinen puskuri tyhjennetään ja dbs_html_printf (ennen html_printf) rutiini tulostaa html merkkijonon ja lisää sen tämän hetkiseen puskuriin. html_printf rutiinin ensimmäinen kappale tulostaa html merkkijonon kasvattaen tulostusaluetta (dbs_printbuf ja dbs_printbuf_len) jos se on tarpeen. Toinen kappale kasvattaa varsinaista puskuria, jos kasvatus on tarpeen. Kolmas kappale eli tuo strcpy kopioi html lauseen tarvittaessa kasvatettuun puskuriin.

#include <stdarg.h>

int dbs_html_now = 0;
unsigned char *dbs_html[] =
    { NULL, NULL, NULL, NULL, NULL };
int dbs_html_size[] =
    { 0, 0, 0, 0, 0 };
#define HTML_BUFFERS 5
#define HTML_HEADER_BUFFER 0
#define HTML_PAYLOAD_BUFFER 1
#define HTML_FOREIGN_BUFFER 2
#define HTML_QUERY_BUFFER 3
#define HTML_LAST_BUFFER 4

void dbs_html_set(int html)
{
  dbs_html_now=html;
  if(dbs_html[html]==NULL) {
    dbs_html_size[html] = 128;
    dbs_html[html]=malloc(
        dbs_html_size[html]);
  }
}

int dbs_html_get()
{
  return(dbs_html_now);
}

void dbs_html_clear()
{
  dbs_html[dbs_html_now][0]='\0';;
}

// old name works also
#define html_printf dbs_html_printf

void dbs_html_printf_valist(const char *format, va_list ap)
{
  int count;
  static char *dbs_printbuf=NULL;
  static int dbs_printbuf_len=0;

  va_list ap2;
  va_copy(ap2,ap);

  count=vsnprintf(dbs_printbuf, dbs_printbuf_len, format, ap) + 1;
  if(dbs_printbuf_len < count) {
    dbs_printbuf_len = count;
    dbs_printbuf=realloc(dbs_printbuf, dbs_printbuf_len);
    count=vsnprintf(dbs_printbuf, dbs_printbuf_len,
        format, ap2) + 1;
  }

  if(dbs_html_size[dbs_html_now] <
     strlen(dbs_html[dbs_html_now]) +
     strlen(dbs_printbuf) + 1) {
    dbs_html_size[dbs_html_now] =
     strlen(dbs_html[dbs_html_now]) +
     strlen(dbs_printbuf) + 1;
    dbs_html[dbs_html_now] = realloc(dbs_html[dbs_html_now],
     dbs_html_size[dbs_html_now]);
  }

  strcpy(dbs_html[dbs_html_now]+
      strlen(dbs_html[dbs_html_now]), dbs_printbuf);
}


void dbs_html_printf(const char *format, ...)
{
  va_list args;

  va_start(args, format);
  dbs_html_printf_valist(format, args);
  va_end(args);
}

void dbs_html_buf_printf(int bufno, const char *format, ...)
{
  int save_html;
  va_list args;

  save_html=dbs_html_get();
  dbs_html_set(bufno);
  va_start(args, format);
  dbs_html_printf_valist(format, args);
  va_end(args);
  dbs_html_set(save_html);
}

Tällä luetaan html parametri:

int dbs_parse_hex1(unsigned char **str) /* 20131003 JariK */
{
  char *p;
  int digit;

  p=*str;
  digit = 0;
  if(*p>='a' && *p<='f')
    digit=*p-'a'+10, p++;
  else if(*p>='A' && *p<='F')
    digit=*p-'A'+10, p++;
  else if(*p>='0' && *p<='9')
    digit=*p-'0', p++;
  else digit=-1;

  *str = p;
  return(digit);
}

int dbs_parse_html_string(int stringlen, unsigned char *string, unsigned char **html)
{
  int c,d,count,ok;
  unsigned char *s=string;
  unsigned char *h=*html;

  ok=0;
  count=0;

  while(isalnum(*h)||*h=='%'||*h=='+'||
      *h=='-'||*h=='_' || *h=='.' ||
      *h=='*') {
    ok=1;
    if(*h=='%') {
      h++;
      if((c=dbs_parse_hex1(&h)) >= 0) {
        if((d=dbs_parse_hex1(&h)) >= 0) {
          c = c*16+d;
        } else
          c = c;
      } else
        c = 64;
      if(++count < stringlen)
        *s++ = c;
    } else if(*h=='+') {
      if(++count < stringlen)
        *s++ = ' ';
      h++;
    } else {
      if(++count < stringlen)
        *s++ = *h;
      h++;
    }
  }
  *s = '\0';
  *html = h;

  return(ok);
}

int dbs_parse_html_parameter(int namelen, unsigned char *name, int valuelen, unsigned char *value, unsigned char **s)
{
  DB2_EVENTS_START(300)

  dbs_parse_html_string(namelen, name, s);
  if((**s)=='=')
    (*s)++;
  dbs_parse_html_string(valuelen, value, s);
  if(**s=='&')
    (*s)++;

  DB2_EVENTS_END(301)
}

int dbs_get_parameter(char *name, int valuelen, unsigned char *value)
{
  int ok;
  unsigned char name2[32];
  unsigned char *h = htmlparams;

  DB2_EVENTS_START(302)

  value[0] = '\0';
  ok = 0;

  while(*h!='\0') {
    dbs_parse_html_parameter(sizeof(name2),
        name2, valuelen, value, &h);
    if(!strcmp(name,name2)) {
      ok = 1;
      break;
    }
  }

  DB2_EVENTS_END(303)

  return(ok);
}

Tässä dbs:n logon näyttö: Näyttö antaa käyttäjälle käyttäjä ja salasana syöttöikkunat ja tarkastaa annetut vastaukset ja tekee istuntoavaimen (sessionid).

void dba_logon()
{
  unsigned char htmltext[128];
  unsigned char userid[32];
  unsigned char password[32];

  if(dbs_get_parameter("userid-0",
      sizeof(userid), userid)==1 &&
     dbs_get_parameter("password-0",
      sizeof(password), password)==1) {
    if(!strcmp(userid,"testi") &&
        !strcmp(password,"testaus")) {
      dbs_gensessionid(
          sizeof(htmlsessionid),
          htmlsessionid);
      dbs_get_parameter("language-0",
          sizeof(htmllanguage),
          htmllanguage);
      dba_renew_cookie();

      fprintf(stdout,"Userid=\"%s\"",
          userid);
      fprintf(stdout,", password=\"%s\"",
          password);
      fprintf(stdout,", language=\"%s\"",
          htmllanguage);
      fprintf(stdout,", sessionid=\"%s\"",
          htmlsessionid);
      fprintf(stdout,"\n");

      FILE *fp1;

      if((fp1 = fopen("dbssessions.deb", "a")) != NULL) {
        fprintf(fp1,"sessionid=%s",
            htmlsessionid);
        fprintf(fp1,", user=%s",
            userid);
        fprintf(fp1,", language=%s",
            htmllanguage);
        fprintf(fp1,", logontime=%s",
            htmltime);
        fprintf(fp1,", ip=%s",
            htmlip);
        fprintf(fp1,"\n");
        fclose(fp1);
      }
    }
  }

  if(*htmlsessionid=='\0') {

    sprintf(htmltext,"<form action=\"logon\" method=\"POST\">");
    dbs_html_printf(htmltext);

    dbs_html_printf("<table border=\"0\">");
    dbs_html_printf("<tr>");
    dbs_html_printf("Testiohjelmaan lokkaantuminen"
        " Userid=testi, password=testaus");
    dbs_html_printf("</tr>");
    dbs_html_printf("<td>");
    dbs_html_printf("Userid");
    dbs_html_printf("</td>");
    dbs_html_printf("<td>");

    dbs_html_printf("<input type=\"char\""
        " name=\"userid-0\" value=\"%s\">",
        htmluserid);
    dbs_html_printf("</td>");
    dbs_html_printf("</tr>");

    dbs_html_printf("<tr>");
    dbs_html_printf("<td>");
    dbs_html_printf("Password");
    dbs_html_printf("</td>");
    dbs_html_printf("<td>");
    dbs_html_printf("<input type=\"password\""
        " name=\"password-0\" value=\"%s\">",
        htmlpassword);
    dbs_html_printf("</td>");
    dbs_html_printf("</tr>");

    dbs_html_printf("<tr>");
    dbs_html_printf("<td>");
    dbs_html_printf("Language");
    dbs_html_printf("</td>");
    dbs_html_printf("<td>");
    dbs_html_printf("<input type=\"char\""
        " name=\"language-0\" value=\"%s\""
        " size=\"3\">",htmllanguage);
    dbs_html_printf("</td>");
    dbs_html_printf("</tr>");

    dbs_html_printf("</table>");

    dbs_html_printf("<input type=\"submit\" value=\"Submit\">");

    dbs_html_printf("</form>");
  }
}

Luetaan html merkkijono:

int html_get_string(unsigned char *string,unsigned char **str,int len)
{
  int c,d,ok,count;
  unsigned char *p,*n;

  n=string;
  p=*str;
  ok=0;

  count=0;
  while(isalnum(*p)||*p=='%' ||*p=='+' ||
      *p=='-' || *p=='_' || *p=='=' ||
      *p=='&' || *p=='.' || *p=='*') {
    ok = 1;
    if(*p=='%') {
      p++;
      if((c=parse_hex1(&p))>=0) {
	if((d=parse_hex1(&p))>=0) {
          c=c*16+d;
        } else
          c=c;
      } else
	c=64;
      if(count++<len)
        *n++=c;
    } else if(*p=='+') {
      if(count++<len)
	*n++=' ';
      p++;
    } else {
      if(count++<len)
	*n++=*p++;
      else
        p++;
    }
  }
  *n='\0';
  *str=p;

  return(ok);
}

Seuraavassa translate ohjelma, jolla käännetään memberid kenttien arvot muille kielille:

int dba_app_parse_word(int *num, int wordlength, unsigned char *word, unsigned char **p2)
{
  int count,stat;
  unsigned char *p;
  unsigned char *w;

  char *colon = ":";
  char *semicolon = ";";
  char *comma = ",";

  stat=0;
  p = *p2;
  db_skipwhite(&p);
  *num = 0;
  while(*p>='0' && *p<='9') {
    *num=(*num)*10+(*p-'0');
    p++;
    stat=1;
  }
  db_skipwhite(&p);
  if(*p==':')
    p++;
  if(!strncmp(p,colon,strlen(colon)))
    p+=strlen(colon);

  db_skipwhite(&p);
  count=0;
  w=word;
  while(*p!='\0' && *p!=',' &&
        strncmp(p,comma,strlen(comma)) &&
        strncmp(p,semicolon,strlen(semicolon)) && *p!='\n') {
    if(++count < wordlength)
      *w++ = *p;
    p++;
  }
  if(*p==',')
    p++;
  if(!strncmp(p,semicolon,strlen(semicolon)))
    p+=strlen(semicolon);
  if(!strncmp(p,comma,strlen(comma)))
    p+=strlen(comma);
  db_skipwhite(&p);
  *w='\0';

  *p2 = p;

  return(stat);
}

Seuraavana varsinainen käännösrutiini:

void dba_app_translations()
{
  int first;
  unsigned char memberid[32];
  struct query *q;
  struct data *d;

  int c;

  first=1;
  c=0;
  fprintf(stdout,"fi ");
  q=dba_query("'memberid'");
  d=dba_query_get_first(q);
  while(d!=NULL) {
    if(db_data_get_field(d, "memberid",
        sizeof(memberid), memberid)) {
      if(!first)
        fprintf(stdout,", ");
      fprintf(stdout,"%d:%s",c,memberid);
      first=0;
    }
    c++;
    d=dba_data_get_next(d);
  }
  fprintf(stdout,"\n");

  FILE *fp1;
  unsigned char *f,*t,*l;
  unsigned char frombuffer[4096];
  unsigned char tobuffer[4096];
  unsigned char fromword[1024];
  unsigned char toword[1024];
  int fromid;
  int toid;
  int stat,stat2;

  if((fp1=fopen("translations.dat","r"))!=NULL) {
    fgets(frombuffer,sizeof(frombuffer),fp1);
    frombuffer[strlen(frombuffer)-1]='\0';
    while(fgets(tobuffer,sizeof(tobuffer),fp1) !=NULL) {
      tobuffer[strlen(tobuffer)-1]='\0';
      fprintf(stdout,"%s\n",tobuffer);
      t=tobuffer;
      db_skipwhite(&t);
      l=language;
      while(isalpha(*t))
        *l++=*t++;
      *l='\0';
      db_skipwhite(&t);

      while(*t!='\0') {
        if((stat=dba_app_parse_word(&toid, sizeof(toword), toword, &t))!=1)
          break;
        f=frombuffer;
        db_skipwhite(&f);
        while(isalpha(*f))
          f++;
        db_skipwhite(&f);
        while(*f!='\0') {
          if((stat2=dba_app_parse_word(&fromid, sizeof(fromword), fromword, &f))!=1)
            break;
          if(toid==fromid) {
            fprintf(stdout,"'word' \"%s\"",fromword);
            fprintf(stdout,", 'language' \"%s\"",language);
            fprintf(stdout,", 'translation' \"%s\"\n",toword);
            fflush(stdout);
            break;
          }
          db_skipwhite(&f);
        }
      }
    }
  }
}

Nappuloiden tulostus:

void dba_app_buttons()
{
  int lineno=0;
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Submit\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Fetch\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Reset\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Display\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Change\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Check\">", lineno);
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Save\">", lineno);
}

Seuraavassa sovelluskoodi syöttölomakkeille. Ensin käytetyt muuttujat:

static int lineno;
static int formerrors = 0;
static unsigned char htmlstring[1024];
static unsigned char htmltext[128];
static unsigned char query[128];
static unsigned char app[32];
static unsigned char appname[32];
static unsigned char chapter[32];
static unsigned char memberid[32];
static unsigned char value[128];
static unsigned char parameterid[32];
static unsigned char parametervalue[128];
static unsigned char func[10];
static struct query *qappname;
static struct data *dappname;
static struct query *qchapter;
static struct data *dchapter;
static struct query *qheadermemberid;
static struct query *qlinesmemberid;
static struct query *qmemberid;
static struct data *dmemberid;
static struct query *qheaderdata;
static struct query *qlinesdata;
static struct query *qdata;
static struct data *ddata;

static int dba_app_foreign_keys_length=0;
static unsigned char *dba_app_foreign_keys=NULL;
static int dba_app_foreign_query_length = 0;
static unsigned char *dba_app_foreign_query = NULL;
static int dba_app_foreign_data_length = 0;
static unsigned char *dba_app_foreign_data = NULL;
static int dba_app_foreign_data_query_length = 0;
static unsigned char *dba_app_foreign_data_query = NULL;

Seuraavassa koodi, joilla hallitaan vaihtelevan mittaisia, automaattisesti kasvatettavia merkkijonoja. dba_string_free() tyhjentää ja nollaa pituus ja merkkijonokentät. Dba_string_realloc on sisäinen rutiini kahdelle viimeiselle. Se kasvattaa merkkijonolle varattua tilaa jos tarpeen. dba_string_set antaa alkuarvon merkkijonolle, sitä käytetään yleensä merkkijonon alkutyhjennykseen. Alkuarvo otetaan kolmannesta parametristä. Dba_string_add lisää merkkijonon perään toisen kolmantena parametrina olevan merkkijonon. Ensimmäinen ja toinen parametri ovat talletuspaikat merkkijonon pituudelle ja merkkijonoon osoittavalle osoitteelle. Jätin vielä debukkailuun käytetyt fprintf:t.

void dba_string_free(int *length, unsigned char **string)
{
  if(*string!=NULL)
    free(*string);
  *string=NULL;
  *length=0;
}

#define aDEBUG23 2

void dba_string_realloc(int *length, unsigned char **string, unsigned char *string2)
{
  int totallength = ((*string==NULL)? 0: strlen(*string)) + strlen(string2) + 1;

  if(*length < totallength) {
    *string = realloc(*string, totallength);
    if(*length == 0)
      *string[0] = '\0';
    *length = totallength;
  }
}

void dba_string_set(int *length, unsigned char **string, unsigned char *string2)
{
  dba_string_realloc(length, string, string2);

  strcpy(*string, string2);

#ifdef DEBUG23
  fprintf(stdout,"length: %d", *length);
  fprintf(stdout,", data: %s(%ld)", *string, strlen(*string));
  fprintf(stdout,", string: %s(%ld)", string2, strlen(string2));
  fprintf(stdout,"\n");
  fflush(stdout);
#endif
}

void dba_string_add(int *length, unsigned char **string, unsigned char *string2)
{
  dba_string_realloc(length, string, string2);

  strcat(*string, string2);

#ifdef DEBUG23
  fprintf(stdout,"length: %d", *length);
  fprintf(stdout,", data: %s(%ld)", *string, strlen(*string));
  fprintf(stdout,", string: %s(%ld)", string2, strlen(string2));
  fprintf(stdout,"\n");
  fflush(stdout);
#endif
}

Seuraavassa aliohjelma, joka hakee sovelluksen vierasavaimet. Se hakee vierasavaimet merkkijonoon jonka perusteella voidaan siirrellä vierasavainkentät. Esimerkiksi tilausohjelmalla jono voi olla seuraavanlainen: Huomaa että eri sovellusten kentät on eroteltu puolipisteellä ja että merkkijono päättyy puolipisteeseen.

 'tilauksen asiakasnumero' = "asiakasnumero", 'tilauksen asiakkaan nimi' = "asiakkaan nimi"; 'tilauksen tuotenumero' = "tuotenumero", 'tilauksen tuotteen hinta' = "tuotteen hinta", 'tilauksen tuotteen nimi' = "tuotteen nimi";

Tässä tilaus tapauksessa sen vierassovellukset on määritetty seuraavasti: eli tilaukseen tulee tietoja asiakas jonoista ja tuotejonoista.

'app' "tilaus", 'fromapp' "asiakas", 'toapp' "tilaus"
'app' "tilaus", 'fromapp' "tuote", 'toapp' "tilaus"

Ohjelma toimii siten, että se hakee kaikki from sovelluksen ja to sovelluksen kentät, ja etsii sieltä kentät, joilla on pisin yhteinen merkkijono ja merkkijono on olemassaoleva kenttä. Eri sovellukset erotellaan puolipisteillä.

int dba_app_get_foreign_keys(int foreign_keys_length, unsigned char *foreign_keys, unsigned char *app)
{
  int count, first, separatorneeded;

  struct query *qfromapp;
  struct data *dfromapp;
  struct query *qfrommemberid;
  struct data *dfrommemberid;
  struct query *qtomemberid;
  struct data *dtomemberid;
  struct query *qallmemberid;
  struct data *dallmemberid;

  if(foreign_keys_length>0)
    foreign_keys[0]='\0';
  first=1;
  separatorneeded=0;
  count=0;

  sprintf(query, "'memberid'");
  qallmemberid = dba_query(query);

  sprintf(query,"'app', 'fromapp', 'toapp' \"%s\"",app);
  qfromapp=dba_query(query);
  dfromapp = dba_query_get_first(qfromapp);
  while(dfromapp != NULL) {
    unsigned char app2[32], fromapp2[32], toapp2[32];
    unsigned char frommemberid[32], tomemberid[32],
      allmemberid[32];
    unsigned char savefrommemberid[32], savetomemberid[32];
    db_data_get_field(dfromapp,"app",
        sizeof(app2), app2);
    db_data_get_field(dfromapp,"fromapp",
        sizeof(fromapp2),fromapp2);
    db_data_get_field(dfromapp, "toapp",
        sizeof(toapp2),toapp2);
    if(!strcmp(toapp2, app2)) {
      sprintf(query,"'app' \"%s\", 'chapter', 'memberid'",
          fromapp2);
      qfrommemberid = dba_query(query);

      sprintf(query,"'app' \"%s\", 'chapter', 'memberid'",
          toapp2);
      qtomemberid = dba_query(query);

      dfrommemberid = dba_query_get_first(qfrommemberid);
      while(dfrommemberid != NULL) {
        int longest;
        db_data_get_field(dfrommemberid, "memberid",
            sizeof(frommemberid), frommemberid);
        longest = 0;
        savetomemberid[0] = '\0';
        savefrommemberid[0] = '\0';

        dtomemberid = dba_query_get_first(qtomemberid);
        while(dtomemberid != NULL) {
          db_data_get_field(dtomemberid, "memberid",
              sizeof(tomemberid), tomemberid);
          dallmemberid=dba_query_get_first(qallmemberid);
          while(dallmemberid != NULL) {
            db_data_get_field(dallmemberid, "memberid",
                sizeof(allmemberid), allmemberid);
            if((strstr(frommemberid, allmemberid) != NULL) &&
               (strstr(tomemberid, allmemberid) != NULL)) {
              if(longest < strlen(allmemberid)) {
                longest = strlen(allmemberid);
                strcpy(savetomemberid, tomemberid);
                strcpy(savefrommemberid, frommemberid);
              } // if(longest <                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
            }
            dallmemberid = dba_data_get_next(dallmemberid);
          } // dallmemberid != NULL) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
          dtomemberid = dba_data_get_next(dtomemberid);
        } // while(dtomemberid                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
        if(longest > 0) {
#define FOREIGN_KEYS_ADD_STRING(string) \
          if(count + strlen(string) < foreign_keys_length) { \
            strcat(foreign_keys, string); \
          } \
          count+=strlen(string);

          if(!first) {
            if(separatorneeded)
              FOREIGN_KEYS_ADD_STRING(",");
            FOREIGN_KEYS_ADD_STRING(" ");
          }

          FOREIGN_KEYS_ADD_STRING("'");
          FOREIGN_KEYS_ADD_STRING(savetomemberid);
          FOREIGN_KEYS_ADD_STRING("'");
          FOREIGN_KEYS_ADD_STRING(" = ");
          FOREIGN_KEYS_ADD_STRING("\"");
          FOREIGN_KEYS_ADD_STRING(savefrommemberid);
          FOREIGN_KEYS_ADD_STRING("\"");
          first=0;
          separatorneeded=1;
        }
        dfrommemberid = dba_data_get_next(dfrommemberid);
      } // while(dfrommemberid != NULL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
      FOREIGN_KEYS_ADD_STRING(";");
      separatorneeded=0;
    } // if(!strcmp(toapp2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    dfromapp = dba_data_get_next(dfromapp);
  } // dfromapp != NULL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  fprintf(stdout,"foreign keys count:%d:\n",count);
  return(count);
}

Seuraava rutiini db_app_get_foreign_query muodostaa edellisen funktion foreign_keys tulosteesta ja lomakkeen tietueesta kyselyjä, joilla voidaan hakea vierassovelluksen tiedot tähän sovellukseen. Tässäkin kyselyjen (eli sovellusten) välillä on puolipiste.

Esimerkkinä tälläisestä keys-jonosta:

 'tilauksen asiakasnumero' = "asiakasnumero", 'tilauksen asiakkaan nimi' = "asiakkaan nimi"; 'tilauksen tuotenumero' = "tuotenumero", 'tilauksen tuotteen hinta' = "tuotteen hinta", 'tilauksen tuotteen nimi' = "tuotteen nimi";

Rutiini tekee tälläisen: tässä tilauksen otsikko

'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1"

Ja tilausriville merkkijono on: (Näissä esimerkeissä näytöllä ei ollut tyhjiä kenttiä:

'tuotenumero' "2000", 'tuotteen hinta' "5,00", 'tuotteen nimi' "Tappi"

Seuraavassa asiakkaan nimi, tuotteen hinta ja tuotteen nimi ovat tyhjiä: Lomakkeen kenttien täyttämiseksi tarvitaan dba_app_foreign_data() funktiota, jota ei tässä vielä ole.

'asiakasnumero' "1000", 'asiakkaan nimi';
'tuotenumero' "2000", 'tuotteen hinta', 'tuotteen nimi'
void dba_app_get_parameter_id(unsigned char *parameterid, unsigned char *name)
{
  snprintf(parameterid,32,"%s-%d", name, lineno);
}

int dba_app_get_foreign_query2(int foreign_query_length, unsigned char *foreign_query,unsigned char *foreign_keys, unsigned char *data)
{
  int first, count, semicolonneeded, semicolonprinted;
  unsigned char fromname[32], toname[32];
  unsigned char name[32], value[1024];
  unsigned char *f,*d;

  if(foreign_query_length>0)
    foreign_query[0]='\0';
  first=1;
  count=0;
  semicolonprinted=0;

  fprintf(stdout,"\nget_foreign_query: foreign_query_length:%d", foreign_query_length);
  fprintf(stdout,", foreign_keys: %s", foreign_keys);
  fprintf(stdout,", lineno: %d\n", lineno);
  fprintf(stdout,"htmlparams: %s\n", htmlparams);
  fprintf(stdout,"data: %s\n", data);

#define FOREIGN_QUERY_ADD_STRING(string) \
  fprintf(stdout,"foreign data2: %s count: %d, foreign_query_length: %d, strlen:%ld, string: \"%s\"\n", \
          foreign_query, count, foreign_query_length, (long int)count + strlen(string),string); \
  if(count + strlen(string) < foreign_query_length) { \
    strcat(foreign_query, string); \
  } \
  count += strlen(string);

  f=foreign_keys;
  while(*f != '\0') {
    db_skipwhite(&f);
    if(*f==';') {
      f++;
      if(semicolonneeded==1) {
        FOREIGN_QUERY_ADD_STRING(";")
        semicolonneeded=0;
        semicolonprinted=1;
      }
      continue;
    }
    db_parse_nameandvalue(sizeof(toname), toname, sizeof(fromname), fromname, &f);
    dba_app_get_parameter_id(parameterid,toname);
    int datastat=db_get_field(data,toname,sizeof(value),value);
    int parameterstat;
    if((parameterstat=dbs_get_parameter(parameterid, sizeof(parametervalue), parametervalue))!=1)
      parametervalue[0]='\0';
    if(datastat==1) {
      fprintf(stdout,"toname: %s", toname);
      fprintf(stdout,", fromname: %s", fromname);
      fprintf(stdout,", parameterid: %s", parameterid);
      fprintf(stdout,", parameter value: \"%s\"", parametervalue);
      fprintf(stdout,", datastat: %d", datastat);
      fprintf(stdout,", parameterstat2: %d", parameterstat);
      fprintf(stdout,", name: %s", toname);
      fprintf(stdout,", value: \"%s\"", value);
      fprintf(stdout,", lineno: %d", lineno);
      fprintf(stdout,"\n");

      //strcpy(fromname,"jk");                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
      //strcpy(parametervalue,"jk");                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
      if(!first) {
        if(!semicolonprinted) {
          FOREIGN_QUERY_ADD_STRING(",")
        }
        semicolonprinted=0;
        FOREIGN_QUERY_ADD_STRING(" ")
      }
      FOREIGN_QUERY_ADD_STRING("'")
      FOREIGN_QUERY_ADD_STRING(fromname)
      FOREIGN_QUERY_ADD_STRING("'")
      if(*parametervalue!='\0' || parameterstat != 1) {
        FOREIGN_QUERY_ADD_STRING(" ")
        FOREIGN_QUERY_ADD_STRING("\"")
        FOREIGN_QUERY_ADD_STRING(parametervalue)
        FOREIGN_QUERY_ADD_STRING("\"")
      }
      first=0;
      semicolonneeded=1;
      fprintf(stdout,"\n");
    } // if(datastat==1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  } // while(*f                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
  return(count);
}

int dba_app_get_foreign_query(unsigned char *foreign_keys, unsigned char *data)
{
  int count, first;

  fprintf(stdout,"foreign_query try\n");
  count=dba_app_get_foreign_query2(dba_app_foreign_query_length, dba_app_foreign_query, foreign_keys, data) + 1;
  fprintf(stdout,"foreign data: \"%s\", count=%d\n",dba_app_foreign_query, count);
  if(dba_app_foreign_query_length < count) {
    dba_app_foreign_query_length = count;
    dba_app_foreign_query=realloc(dba_app_foreign_query, dba_app_foreign_query_length);
    count=dba_app_get_foreign_query2(dba_app_foreign_query_length, dba_app_foreign_query, foreign_keys, data) + 1;
    fprintf(stdout,"foreign data: \"%s\", count=%d\n",dba_app_foreign_query, count);
  }
  fprintf(stdout,"<<< foreign query: \"%s\" >>>\n", dba_app_foreign_query);
  fflush(stdout);
}

Toiseksi viimeinen osa vierasavainten (foreign key) käsittelystä dba_foreign_data. Se lukee edellisen foreign_query – funktion merkkijonon, esimerkiksi: huomaa sovellusten välillä oleva puolipiste.

'tuotenumero' "2000", 'tuotteen hinta', 'tuotteen nimi'; 'tuotenumero' "2000", 'tuotteen hinta', 'tuotteen nimi';

Ja tekee siitä tälläistä: Tässä viimeisessä mallissa ei ole puolipisteitä.

'tuotenumero' "2000", 'tuotteen hinta' "5,00", 'tuotteen nimi' "Tappi", 'tuotenumero' "2000", 'tuotteen hinta' "5,00", 'tuotteen nimi' "Tappi"

Eli suorittaa _query listassa olevat kyselyt ja ynnää kyselyjen vastaukset foreign_data merkkijonoon. Sitten varsinaiseen koodiin: Tässä merkkijonot käydään läpi kenttä kentältä, sen voisi tehdä nopeamminkin, mutta se jää myöhemmäksi. Aliohjelma käyttää uusia dba_string_set ja dba_string_add funktioita.

int dba_app_get_foreign_data(int *foreign_data_length, unsigned char **foreign_data, unsigned char *foreign_query)
{
  int stat, first, first2;
  unsigned char name[32], value[1024];
  unsigned char *f, *g;

  fprintf(stdout,"Foreign data: %s", foreign_query);
  fprintf(stdout,"\n");
  f=foreign_query;

  dba_string_set(&dba_app_foreign_data_length, &dba_app_foreign_data, "");
  first2=1;

  while(*f != '\0') {
    first = 1;
    dba_string_set(&dba_app_foreign_data_query_length, &dba_app_foreign_data_query, "");
    while(*f!=';' && *f!='\0')  {
      db_skipwhite(&f);
      stat=db_parse_nameandvalue(sizeof(name), name,
          sizeof(value), value, &f);
      fprintf(stdout,"foreign data query: name: %s", name);
      fprintf(stdout,", value: %s", value);
      fprintf(stdout,", stat: %d\n", stat);
#define FOREIGN_DATA_ADD_QUERY(string) \
      dba_string_add(&dba_app_foreign_data_query_length, &dba_app_foreign_data_query, string);
      if(!first) {
        FOREIGN_DATA_ADD_QUERY(", ");
      }
      FOREIGN_DATA_ADD_QUERY("'");
      FOREIGN_DATA_ADD_QUERY(name);
      FOREIGN_DATA_ADD_QUERY("'");
      if(stat>1) {
        FOREIGN_DATA_ADD_QUERY(" ");
        FOREIGN_DATA_ADD_QUERY("\"");
        FOREIGN_DATA_ADD_QUERY(value);
        FOREIGN_DATA_ADD_QUERY("\"");
      }
      first=0;
    } // while(*f!=';'                                                                                                                                                                                                                                                                                                                                                         
    if(*f==';')
      f++;
    fprintf(stdout,"queryall: %s\n", dba_app_foreign_data_query);

    struct query *q;
    struct data *d;

    first = 1;
    q = dba_query(dba_app_foreign_data_query);
    d = dba_query_get_first(q);
    // Exactly one record
    if(d!=NULL && d->next_data==NULL) {                                                                                                                                                                                                                                                                                                                  
      g=d->data;
      while(*g!='\0') {
        db_skipwhite(&g);
        stat=db_parse_nameandvalue(sizeof(name), name,
            sizeof(value), value, &g);
        fprintf(stdout,"\nforeign data result: name: %s", name);
        fprintf(stdout,", value: %s", value);
        fprintf(stdout,", stat: %d\n", stat);
#define FOREIGN_DATA_ADD_DATA(string) \
        dba_string_add(&dba_app_foreign_data_length, &dba_app_foreign_data, string);
        if(!first2) {
          FOREIGN_DATA_ADD_DATA(", ");
        }
        FOREIGN_DATA_ADD_DATA("'");
        FOREIGN_DATA_ADD_DATA(name);
        FOREIGN_DATA_ADD_DATA("'");
        if(stat>1) {
          FOREIGN_DATA_ADD_DATA(" ");
          FOREIGN_DATA_ADD_DATA("\"");
          FOREIGN_DATA_ADD_DATA(value);
          FOREIGN_DATA_ADD_DATA("\"");
        }
        fprintf(stdout,"<<< foreign data result added: \"%s\" >>>\n", dba_app_foreign_data);

        first2=0;
      } // while(*g!='\0') {                                                                                                                                                                                                                                                                                                                                                   
    } // if(d!=NULL                                                                                                                                                                                                                                                                                                                                                            
  } // while(*f != '\0')                                                                                                                                                                                                                                                                                                                                                       
  fprintf(stdout,"<<< foreign data results: \"%s\" >>>\n", dba_app_foreign_data);
}

Sitten sovelluksen nimen käännös:

void dba_app_app()
{
  struct query *qlanguage;
  struct data *dlanguage;
  unsigned char apptext[64];

  sprintf(query,"'app' \"%s\", 'language'"
      " \"%s\", 'apptext'", app,
      htmllanguage);
  qlanguage=db_query(query);
  if((dlanguage = db_query_get_first(
      qlanguage))!=NULL) {
    db_data_get_field(dlanguage, "apptext",
        sizeof(apptext),apptext);
  } else {
    sprintf(apptext,"%s", appname);
  }
  fprintf(stdout,"\n");

  dbs_html_printf(apptext);
}
Sarakkeen nimen käännös:
void dba_app_memberid()
{
  struct query *qlanguage;
  struct data *dlanguage;
  unsigned char membertext[64];

  sprintf(query,"'memberid' \"%s\", 'language' \"%s\", 'membertext'", memberid, htmllanguage);
  qlanguage=dba_query(query);
  if((dlanguage = dba_query_get_first(qlanguage))!=NULL) {
    db_data_get_field(dlanguage, "membertext",
                      sizeof(membertext),membertext);
    fprintf(stdout,"query: %s",query);
    fprintf(stdout,", membertext: %s",membertext);
  } else {
    sprintf(query,"'word' \"%s\", 'language' \"%s\", 'translation'", memberid, htmllanguage);
    qlanguage=dba_query(query);
    if((dlanguage = dba_query_get_first(qlanguage))!=NULL) {
      db_data_get_field(dlanguage, "translation",
                        sizeof(membertext),membertext);
      fprintf(stdout,"query: %s",query);
      fprintf(stdout,", membertext: %s",membertext);
    } else {
      sprintf(membertext,"%s", memberid);
    }
  }
  fprintf(stdout,"\n");

  dbs_html_printf(membertext);
}
Tässä pidempi rutiini joka tekee kaiken varsinaiseen kentän arvoon liittyvän:
void dba_app_value()
{
  struct query *qmemberiddata;
  struct data *dmemberiddata;
  unsigned char lengths[10];
  int length;

  // Find length of field                                                                                                                                                                                                                                                                                         
  length = 10;
  sprintf(query,"'memberid' \"%s\",
      'length'", memberid);
  qmemberiddata = db_query(query);
  if((dmemberiddata =
      db_query_get_first(qmemberiddata))
     != NULL) {
    if(db_data_get_field(dmemberiddata,
        "length", sizeof(lengths),
          lengths)) {
      length=atoi(lengths);
    }
  }

  // Find mode of field                                                                                                                                                                                                                                                                                           
  if(!strcmp(func, "Reset"))
    value[0] = '\0';
  else {
    // get field value                                                                                                                                                                                                                                                                                            
    char tempmemberid[64];
    sprintf(tempmemberid, "%s-%d",
        memberid, lineno);

    if(dbs_get_parameter(tempmemberid,
        sizeof(value),
        value)==0) {
      db_data_get_field(ddata, memberid,
          sizeof(value), value);
    }
  }

  char mode[10];
  strcpy(mode, "Required");
  sprintf(query,"'memberid' \"%s\","
      " 'mode'", memberid);
  qmemberiddata = db_query(query);
  if((dmemberiddata = db_query_get_first(
      qmemberiddata))
     != NULL) {
    if(db_data_get_field(dmemberiddata,
        "mode", sizeof(mode), mode)) {
      length=atoi(lengths);
    }
  }

  int errors = 0;

  // Do field checks                                                                                                                                                                                                                                                                                              
  if(!strcmp(mode, "Required") &&
      value[0] == '\0') {
    errors++;
    formerrors++;
  }

  // print field input                                                                                                                                                                                                                                                                                            
  sprintf(htmltext,"<input type=\"text\""
      " value=\"%s\" name=\"%s-%d\""
      " size=\"%d\"", value, memberid,
      lineno, length);
  if(strcmp(htmlmode, "Change"))
    strcat(htmltext, " readonly");
  // red if errors                                                                                                                                                                                                                                                                                                
  if(errors != 0 && (
    !strcmp(func, "Check") ||
    !strcmp(func,"Save")))
      strcat(htmltext," style=\"border-color:red;\"");
  strcat(htmltext,"><br>");
  dbs_html_printf(htmltext);
}

Ja varsinainen sovelluspääohjelma:

void dba_app()
{
  int first;
  unsigned char *p;

  DB2_EVENTS_START(302)

  qheadermemberid = NULL; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
  qheaderdata = NULL;
  qlinesmemberid = NULL;
  qlinesdata = NULL;

  if(dbs_get_parameter("func-0", sizeof(func), func)==1)
    fprintf(stdout,"FUNC:\"%s\"\n",func);

  if(!strcmp(func, "Display") ||
     !strcmp(func, "Change"))
    strcpy(htmlmode, func);

  dba_renew_cookie();

  p = htmlparams;
  dbs_html_get_string(htmlstring, &p, 1024);
  fprintf(stdout,"htmlstring: \"%s\"", htmlstring);

  if(dbs_get_parameter("app-0", sizeof(app), app)==0)
    sprintf(app,"asiakas");
  else
    fprintf(stdout,"APP:\"%s\"\n", app);

  lineno = 0;

  sprintf(htmltext,"<form action=\"app\" method=\"post\">");
  dbs_html_printf(htmltext);
  // app selection                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  sprintf(htmltext,"<input type=\"text\" value=\"%s\" name=\"app-%d\" size=\"10\">", app, lineno);
  dbs_html_printf(htmltext);
  // print buttons                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  dba_app_buttons();
  dbs_html_printf("<br>");
  fprintf(stdout,"htmlparams:\"%s\"\n", htmlparams);
  fprintf(stdout,"app: %s\n", app);

  sprintf(query,"'app' \"%s\", 'appname'", app);
  qappname=dba_query(query);
  dappname=dba_query_get_first(qappname);
  fprintf(stdout,"dappname->data=%s\n", dappname->data);
  fflush(stdout);
  if(db_data_get_field(dappname,"appname",
      sizeof(appname), appname)) {
    dbs_html_printf("<h1>");
    dba_app_app();
    dbs_html_printf("</h1>");
    fprintf(stdout,"app=%s", app);
    fprintf(stdout,"\n");
  }

  int count;

  fprintf(stdout,"foreign_keys try\n");
  count=dba_app_get_foreign_keys(dba_app_foreign_keys_length, dba_app_foreign_keys, app) + 1;
  fprintf(stdout,"str: %s, count=%d\n",dba_app_foreign_keys, dba_app_foreign_keys_length);
  if(count>dba_app_foreign_keys_length) {
    dba_app_foreign_keys_length = count;
    dba_app_foreign_keys=realloc(dba_app_foreign_keys, dba_app_foreign_keys_length);
    count=dba_app_get_foreign_keys(dba_app_foreign_keys_length, dba_app_foreign_keys,app) + 1;
    fprintf(stdout,"str: %s, count=%d\n",dba_app_foreign_keys, dba_app_foreign_keys_length);
  }
  fprintf(stdout,"Foreign keys: %s", dba_app_foreign_keys);
  dbs_html_buf_printf(HTML_FOREIGN_BUFFER,
    "Foreign keys: %s<br>", dba_app_foreign_keys);
  formerrors=0;

  sprintf(query,"'app' \"%s\", 'chapter'", app);
  qchapter=dba_query(query);
  dchapter=dba_query_get_first(qchapter);

  while(dchapter != NULL) {
    if(db_data_get_field(dchapter, "chapter",
        sizeof(chapter),chapter)) {
      fprintf(stdout,"chapter=%s", chapter);
      fprintf(stdout,"\n");
    }
    sprintf(query,"'app' \"%s\", 'chapter' \"%s\", 'sort', 'memberid'", app, chapter);
    qmemberid=dba_query(query);
    dmemberid=dba_query_get_first(qmemberid);

    // generate query for data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    *query = '\0';
    first = 1;
    while(dmemberid != NULL) {
      db_data_get_field(dmemberid,"memberid",
          sizeof(memberid), memberid);
      if(!first)
        strcat(query,", ");
      strcat(query,"'");
      strcat(query,memberid);
      strcat(query,"'");
      first=0;

      dmemberid=dba_data_get_next(dmemberid);
    }

    qdata=dba_query(query);
    ddata=dba_query_get_first(qdata);
    while(ddata != NULL) {
      fprintf(stdout,"data: %s\n", ddata->data);
      ddata=dba_data_get_next(ddata);
    }

    if(!strcmp(chapter,"header")) {
      lineno++;

      dbs_html_printf("<table border=\"0\">");

      ddata=dba_query_get_first(qdata);

      //dba_app_foreign_keys[0]='\0';                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
      //if(ddata!=NULL && ddata->data!=NULL)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      dba_app_get_foreign_query(
        dba_app_foreign_keys, ddata->data);
      dbs_html_buf_printf(HTML_FOREIGN_BUFFER,
        "line %d query: %s<br>", lineno, dba_app_foreign_query);
      dba_app_get_foreign_data(&dba_app_foreign_data_length,
        &dba_app_foreign_data,dba_app_foreign_query);
      dbs_html_buf_printf(HTML_FOREIGN_BUFFER,
        "line %d data:  %s<br>", lineno, dba_app_foreign_data);

      qheadermemberid = qmemberid; // save for dump                                                                                           
      qheaderdata = qdata;

      qheadermemberid = qmemberid; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                        
      qheaderdata = qdata;

      dmemberid = dba_query_get_first(qmemberid);
      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);

        dbs_html_printf("<tr>");

        dbs_html_printf("<td>");
        dba_app_memberid();
        dbs_html_printf("</td>");

        dbs_html_printf("<td>");
        dba_app_value();
        dbs_html_printf("</td>");

        dbs_html_printf("</tr>");
        dmemberid=dba_data_get_next(dmemberid);
      }
      dbs_html_printf("</table>");

    } else if(!strcmp(chapter,"lines")) {

      dbs_html_printf("<table border=\"0\">");
      dbs_html_printf("<tr>");

      dmemberid=dba_query_get_first(qmemberid);

      qlinesmemberid = qmemberid; // save for dump                                                                                                                                                                                                                                                                                                                                                                                                                         
      qlinesdata = qdata;

      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);
        dbs_html_printf("<td>");
        dba_app_memberid();
        dbs_html_printf("</td>");
        dmemberid=dba_data_get_next(dmemberid);
      }
      dbs_html_printf("</tr>");
      ddata = dba_query_get_first(qdata);

      while(ddata != NULL) {
        lineno++;

	dba_app_get_foreign_query(dba_app_foreign_keys,
          ddata->data);
        dbs_html_buf_printf(HTML_FOREIGN_BUFFER,
          "line %d query: %s<br>", lineno,
          dba_app_foreign_query);
        dba_app_get_foreign_data(&dba_app_foreign_data_length,
          &dba_app_foreign_data, dba_app_foreign_query);
	dbs_html_buf_printf(HTML_FOREIGN_BUFFER,
          "line %d data:  %s<br>", lineno,
          dba_app_foreign_data);
        
        fprintf(stdout,"%s\n", ddata->data);
        dbs_html_printf("<tr>");
        dmemberid=dba_query_get_first(qmemberid);
        while(dmemberid != NULL) {
          db_data_get_field(dmemberid, "memberid",
              sizeof(memberid), memberid);
          dbs_html_printf("<td>");
          dba_app_value();
          dbs_html_printf("</td>");
          dmemberid=dba_data_get_next(dmemberid);
        }
        dbs_html_printf("</tr>");
        ddata=dba_data_get_next(ddata);
      }
      dbs_html_printf("</table>");
    } // end of if(!strcmp(chapter,"lines"))                                                                                                                                                                                                                                                                                                                                                                                                                               
    dchapter=dba_data_get_next(dchapter);
  } // end of while(dchapter!=NULL) {                                                                                                                                                                                                                                                                                                                                                                                                                                      

  if(!strcmp(func, "Save") && formerrors == 0) {
    dba_app_save();
  }

  dbs_html_printf("<p>application: %s(%s)</p>", app, appname);
  dbs_html_printf("<p>htmlparams: %s</p>", htmlparams);
  dbs_html_printf("<p>htmlstring: %s</p>", htmlstring);
  dbs_html_printf("<p>foreign keys: %s</p>", dba_app_foreign_keys);
#ifdef OLD
  dbs_html_printf("<code>");
  dbs_dump_query(qappname);
  dbs_dump_query(qchapter);
  if(qheadermemberid != NULL)
    dbs_dump_query(qheadermemberid);
  if(qlinesmemberid != NULL)
    dbs_dump_query(qlinesmemberid);
  if(qheaderdata != NULL)
    dbs_dump_query(qheaderdata);
  if(qlinesdata != NULL)
    dbs_dump_query(qlinesdata);
  dbs_html_printf("</code>");
#endif
  dbs_html_printf("</form>");
  dbs_html_printf("<br><br>%s,  sha256(%s)<br><br>", programname,
      htmldigest);

  DB2_EVENTS_END(303)
}

Seuraavassa html otsakkeen lukuun käytettyjä funktioita: Ensiksi kuitenkin malli html otsakkeista: ensimmäiseltä tietueelta luetaan html-method ja path. Cookie-riviltä luetaan mode ja session id. Parametrit riviltä luetaan html lomakkeen parametrit.

Metodirivi on tiedoston ensimmäinen rivi ja sillä on välilyönnein eroteltuna method, path ja html-type. Cookie rivi alkaa “Cookie:” tekstillä.

Parametririviä edeltää neljä merkkiä, “\r\n\r\n” merkkijono.

Content-Length kenttä sisältää “payload” kentän pituuden eli tässä tapauksessa parametrien pituuden. Palvelimen lähettäessä html-sivun Content-Length kenttä sisältää html tekstin pituuden.

Otsakkeessa rivien välissä on aina ‘\r\n’.

POST /app HTTP/1.1^M
Host: 192.168.1.14:5004^M
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0^M
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8^M
Accept-Language: en-US,en;q=0.5^M
Accept-Encoding: gzip, deflate, br^M
Content-Type: application/x-www-form-urlencoded^M
Content-Length: 286^M
Origin: https://192.168.1.14:5004^M
Connection: keep-alive^M
Referer: https://192.168.1.14:5004/app^M
Cookie: sessionid=WMozuNOpY6mpXVb8peyl4yvSKagCDqNN; mode=Change; language=fi^M
Upgrade-Insecure-Requests: 1^M
^M
app-0=tilaus&func-0=Submit&tilausnumero-1=3000&tilauksen+asiakasnumero-1=1000&tilauksen+asiakkaan+nimi-1=Firma+1&tilausnu\
mero-2=3000&tilauksen+rivinumero-2=10&tilauksen+tuotenumero-2=2000&tilauksen+tuotteen+nimi-2=Tappi&tilauksen+tuotteen+hin\
ta-2=5%2C00&tilattu+m%C3%A4%C3%A4r%C3%A4-2=1"

Dbs_html_get_request_line:lla haetaan otsakkeen rivi, jonka alku sisältää helutun merkkijonon.

unsigned char *dbs_html_get_request_line(unsigned char *name)
{
  unsigned char *p;

  p = htmlin;
  while(*p != '\0') {
    if(!strncmp(p, "\r\n", 2)) {
      p += 2;
      if(!strncmp(p, name, strlen(name))) {
        p += strlen(name);
        if(*p == ':') {
          p++;
          while(isblank(*p))
            p++;
          return(p);
        }
      }
    }
    p++;
  }
  return(NULL);
}

dbs_html_get_request_line_num on numeerinen versio edellisestä: tällä luetaan ainakin Content-Length kentän arvo.

void dbs_html_get_request_line_num(unsigned char *name, int lenbuffer, unsigned char *buffer)
{
  int len;
  unsigned char *n, *p;

  if((p = dbs_html_get_request_line(name)) == NULL)
    sprintf(buffer, "0");
  else {
    len = 0;
    n = buffer;
    while(isdigit(*p)) {
      if(++len < lenbuffer)
        *n++ = *p;
      p++;
    }
    *n = '\0';
  }
}

Dbs_get_cookie funktiolla luetaan cookiet, jotka löytyvät otsakkeen “Cookie:” merkkijonolla alkavalta riviltä.

void dbs_get_cookie(unsigned char *name, int valuelen, unsigned char *value)
{
  int len;
  unsigned char *p,*n;

  if((p = dbs_html_get_request_line("Cookie")) == NULL) {
    return;
  }

  while(*p!='\n' && *p!='\0') {
    while(isblank(*p))
      p++;
    if(!strncmp(p, name, strlen(name))) {
      p += strlen(name);
      while(isblank(*p))
        p++;
      if(*p == '=')
        p++;
      while(isblank(*p))
        p++;
      n = value;
      len = 0;
      while(*p != ';' && isprint(*p)) {
        if(++len < sizeof(htmlsessionid))
          *n++ = *p;
        p++;
      }
      *n = '\0';
      return;
    }
    while(*p != ';' &&
        *p != '\n' &&
        *p != '\0')
      p++;
    if(*p == ';')
      p++;
    while(isblank(*p))
      p++;
  }
}

Aliohjelma kopioi välilyönnillä/välilyönneillä erotetun merkkijonon buf merkkijonoon.

void dbs_html_parse_string(int buflen, char *buf, unsigned char **p2)
{
  int len;
  unsigned char *p, *n;

  p = *p2;
  len = 0;
  while(!isblank(*p) && *p!='\n' && *p!='\r') {
    if(++len < buflen) {
      *buf++ = *p;
    }
    p++;
  }
  *buf = '\0';

  while(isblank(*p))
    p++;

  *p2 = p;
}

Seuraava rutiini kopioi tiedostonimen tiedostopolusta buf merkkijonomuuttujaan. Tiedostopolkuhan alkaa viimeisen kauttaviivan jälkeisestä merkistä.

void dbs_html_parse_filename(int buflen, char *buf, unsigned char *p)
{
  int len;
  unsigned char *n;

  len = 0;
  n = buf;

  while(*p != '\0') {
    if(*p == '/') {
      n = buf;
      len = 0;
    } else {
      if(++len < buflen)
        *n++ = *p;
    }
    p++;
  }
  *n = '\0';
}

Seuraava rutiini palauttaa osoitteen html-otsakkeen parameters kenttään. Se löytyy “\r\n\r\n” merkkijonon jälkeen.

unsigned char *dbs_html_get_params()
{
  unsigned char *p = htmlin;

  while(*p != '\0') {
    if(!strncmp(p, "\r\n\r\n", 4)) {
      p += 4;
      break;
    }
    p++;
  }
  return(p);
}

Tämä on pääohjelma, joka hakee arvot html-alkuisille muuttujilla ja kutsuu dba_logon rutiinia tai dba_app rutiinia.

void dba_loop()
{
  unsigned char *p;

  p=htmlin;
  dbs_html_parse_string(sizeof(htmlmethod), htmlmethod,&p);
  fprintf(stdout,"htmlmethod: \"%s\"", htmlmethod);
  fflush(stdout);

  dbs_html_parse_string(sizeof(htmlpath), htmlpath, &p);
  fprintf(stdout,", htmlpath: \"%s\"", htmlpath);
  fflush(stdout);

  dbs_html_parse_string(sizeof(htmlversion), htmlversion, &p);
  fprintf(stdout,", htmlversion: \"%s\"", htmlversion);
  fflush(stdout);

  dbs_html_parse_filename(sizeof(htmlfilename), htmlfilename, htmlpath);
  fprintf(stdout,", htmlfilename: \"%s\"", htmlfilename);
  fflush(stdout);

  htmlparams=dbs_html_get_params();
  fprintf(stdout,", htmlparamslen: \"%ld\"", strlen(htmlparams));

  strcpy(htmlmode,"Display");
  htmlsessionid[0]='\0';

  dbs_get_cookie("sessionid", sizeof(htmlsessionid), htmlsessionid);
  fprintf(stdout,", htmlsessionid: \"%s\"", htmlsessionid);
  fflush(stdout);

  dbs_get_cookie("mode", sizeof(htmlmode), htmlmode);
  fprintf(stdout,", htmlmode: \"%s\"\n", htmlmode);
  fflush(stdout);

  dbs_get_cookie("language", sizeof(htmllanguage), htmllanguage);
  fprintf(stdout,", htmllanguage: \"%s\"\n", htmllanguage);
  fflush(stdout);

  for(int c=0;c<HTML_BUFFERS;c++) {
    dbs_html_set(c);
    dbs_html_clear();
  }

  dbs_html_set(HTML_HEADER_BUFFER);

  dbs_html_printf("HTTP/1.0 200 OK\r\n");
  dbs_html_printf("Location: \r\n");
  dbs_html_printf("Server: %s\r\n", programname);

  // query statements and data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
  dbs_html_set(HTML_FOREIGN_BUFFER);
  dbs_html_printf("<h2>Foreign keys</h2>");
  dbs_html_printf("<code>");

  dbs_html_set(HTML_QUERY_BUFFER);
  dbs_html_printf("<h2>Queries</h2>");
  dbs_html_printf("<code>");

  dbs_html_set(HTML_PAYLOAD_BUFFER);

  dbs_html_printf("\n<!doctype html>\r\n");
  dbs_html_printf("<html lang=\"fi\">");

  dbs_html_printf("<head>");
  dbs_html_printf("<meta charset=\"utf-8\">");
  dbs_html_printf("<title>dbs</title>");
  dbs_html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
  dbs_html_printf("<meta name=\"copyright\" content=\"Jari Kuivaniemi\">");
  dbs_html_printf("</head>");

  dbs_html_printf("<body>");

  if(htmlsessionid[0]=='\0') {
    strcpy(htmllanguage, "fi");
    dba_logon();
  } else if(!strcmp(htmlfilename, "logon")) {
    htmlsessionid[0] = '\0';
    strcpy(htmllanguage, "fi");
    dba_logon();
  }

  if(htmlsessionid[0] != '\0') {
    dba_app();
  }
#ifdef TEST
  dbs_html_printf("<h1>Hello, world! åäö</h1>");
  dbs_html_printf("<form action=\"httpstest2\" method=\"post\">");
  dbs_html_printf("<input type=\"submit\" name=\"func\" value=\"Submit\">");
  dbs_html_printf("<input type=\"text\" value=\"1234åäö\" name=\"sovellusä\" size=\"10\">");
  dbs_html_printf("<p>htmlparams: %s</p>",htmlparams);
  dbs_html_printf("<p>htmlstring: %s</p>",htmlstring);
  dbs_html_printf("</form>");
#endif

  dbs_html_printf("<br><br>%s,  sha256(%s)<br><br>", programname,
      htmldigest);
  dbs_html_printf("</body>");

  //dbs_html_printf("</html>");                                                                                                                                                                                       

  // query statements and data                                                                                                                                                                                        
  dbs_html_set(HTML_FOREIGN_BUFFER);
  dbs_html_printf("</code>");

  dbs_html_set(HTML_QUERY_BUFFER);
  dbs_html_printf("</code>");

  dbs_html_set(HTML_LAST_BUFFER);
  dbs_html_clear();
  dbs_html_printf("</html>");

  dbs_html_set(HTML_HEADER_BUFFER);

  time_t now;
  unsigned char timebuf[128];

  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));
  dbs_html_printf("Date: %s\r\n", timebuf);

  int len;
  len=strlen(dbs_html[1]) + strlen(dbs_html[2]) +
      strlen(dbs_html[3]) + strlen(dbs_html[4]);
  dbs_html_printf("Content-Length: %d", len);
  dbs_html_printf("\r\n\r\n");

  fprintf(stdout,"Header_ %s\n",dbs_html[0]);
}

void dba_main(int argc, char *argv[])
{
  int c, translate;

  translate = 0;
  for(c=0; c<argc; c++) {
    if(!strcmp(argv[c], "--translate"))
      translate=1;
  }
  if(translate) {
    dba_app_translations();
    exit(0);
  }
}

Seuraavalla rutiinilla lasketaan suoritettavalle tiedostolle SHA256 tiiviste. Tiiviste löytyy kaikkien sivujen alalaidasta.

void dbs_file_digest(char *filename,unsigned char *hash)
{
  int c;
  unsigned char buffer[1024];
  FILE *fp1;
  HashCtx ctx;

  HashInit(&ctx);
  if((fp1 = fopen(filename, "rb")) != NULL) {
    while((c = fread(buffer, 1, sizeof(buffer), fp1)) > 0)
      HashUpdate(&ctx, buffer, c);
    fclose(fp1);
  }
  HashFinal(hash, &ctx);
}

Seuraavat kappaleet toteuttavat http-palvelinohjelman, https-palvelinohjelman ja pääohjelman. Pääohjelma kutsuu komentoriviparametrien perusteella jompaa kumpaa, jo(t)ka taas kutsuu dba_main rutiinia.

#include <errno.h>
#include <openssl/ssl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* See: http://h41379.www4.hpe.com/doc/83final/ba554_90007/ch04s03.html */

#include <netdb.h>

#define DEFAULT_PORT "5004"
#define backlog 5

int s,news;
char *cert_file = "cert.pem";
char *privatekey_file = "key.pem";

char *myport=NULL;

Seuraava koodikappale sisältää http_server rutiinin ja https_server rutiinin yhteiset osat:

int server_getaddrinfo(unsigned char *myport, struct addrinfo **res)
{
  int status;
  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;

  if ((status = getaddrinfo(NULL, myport, &hints, res)) != 0) {
    fprintf(stderr, "\n%s: getaddrinfo error: %s",
            procname, gai_strerror(status));
    fprintf(stderr, ", error code %d\n", status);
    fflush(stderr);
  }
  return(status);
}

int server_socket(struct addrinfo *res)
{
  int s;

  if((s = socket(res->ai_family, res->ai_socktype,
      res->ai_protocol))==-1) {
    fprintf(stderr, "%s: socket(), error: %d\n",
        procname, errno);
    perror("socket");
    fflush(stderr);
  }

  return(s);
}

void server_bind(int s, struct addrinfo *res)
{
  if(bind(s, res->ai_addr, res->ai_addrlen)==-1) {
    if(errno == 98) {
      fprintf(stdout,"%s cannot bind, waiting to bind",
          procname);
      fflush(stdout);
      while(errno == 98) {
        sleep(10);
        fprintf(stdout,".");
        fflush(stdout);
        bind(s, res->ai_addr, res->ai_addrlen);
      }
      fprintf(stdout,"bind done!\n");
      fflush(stdout);
    } else {
      fprintf(stderr,"\n%s: cannot bind(), error: %d\n",'
          procname, errno);
      perror("bind");
      fflush(stderr);
    }
  }
}

int server_listen(int s)
{
  int listenfd;

  if((listenfd=listen(s,backlog))==-1) {
    fprintf(stderr,"\n%s: cannot listen()\n", procname);
    perror("listen");
    fflush(stderr);
  }

  return(listenfd);
}

void server_close(int s)
{
  if(close(s)==-1) {
    fprintf(stderr,"\n%s: cannot close()\n", procname);
    perror("close");
    fflush(stderr);
  }
}

Sitten itse palvelin rutiinit. Http palvelinohjelma:

void http_server()
{
  int addr_size;
  int listenfd;

  struct sockaddr_in sa_cli;
  struct addrinfo *res;

  unsigned char buffer10[10];

  // plus space for '\0'                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
  htmlin = malloc(htmlinlen+1);

  signal(SIGPIPE, SIG_IGN);

  server_getaddrinfo(myport,&res);
  s = server_socket(res);
  server_bind(s,res);
  freeaddrinfo(res);
  listenfd=server_listen(s);

  dba_main();

  for(;;) {
    addr_size = sizeof(sa_cli);
    news = accept(s, (struct sockaddr *)&sa_cli, &addr_size);

    strcpy(htmlip, inet_ntoa(sa_cli.sin_addr));
    sprintf(htmlport,"%d", sa_cli.sin_port);

    unsigned long seconds = dbs_getseconds();
    strftime(htmltime, sizeof(htmltime), TIMEFORMAT,
             localtime((time_t *)&seconds));

    fprintf(stdout,"\nConnection(http) from %x", sa_cli.sin_addr.s_addr);
    fprintf(stdout,", ip %s", htmlip);
    fprintf(stdout,", port %s", htmlport);
    fprintf(stdout,", at %s\n", htmltime);

    int clen = 0;
    int reads = 0;
    int first = 1;
    int bytes, totalbytes=0;

    htmlparams = NULL;
    htmlin[0]='\0';

    while(htmlparams == NULL ||
          clen-strlen(htmlparams) > 0) {
      if(!first) {
        fprintf(stdout,", ");
      }
      fprintf(stdout,"read %d", reads);
      fflush(stdout);
      if((bytes=read(news, htmlin+totalbytes, htmlinlen-totalbytes))<0) {
        fprintf(stderr,"\n%s: cannot read()\n", procname);
        perror("read");
        fflush(stderr);
      }
      fprintf(stdout,"(%d bytes)", bytes);
      if(bytes==0)
        break;
      if(bytes>3 &&
         !isprint(htmlin[0]) &&
         !isprint(htmlin[1]) &&
         !isprint(htmlin[2])) {
        fprintf(stdout,"https packet?\n");
        htmlin[0]='\0';
        totalbytes=0;
        bytes=0;
        break;
      }
      *(htmlin+totalbytes+bytes) = '\0';
      fprintf(stdout,"%s\n",htmlin);
      totalbytes+=bytes;
      if(totalbytes>=htmlinlen) {
        // plus space for '\0'                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
        htmlin=realloc(htmlin, htmlinlen*2+1);
        htmlinlen*=2;
      }

      htmlparams = dbs_html_get_params();
      dbs_html_get_request_line_num("Content-Length", sizeof(buffer10), buffer10);
      clen = atoi(buffer10);
      reads++;
      first = 0;
    }
    if(totalbytes==0)
      continue;
    fprintf(stdout,"\n%d reads", reads);
    fprintf(stdout,", received %d chars", totalbytes);
    fprintf(stdout,", read %d total bytes", totalbytes);
    fprintf(stdout,", input buffer size %d chars", htmlinlen);
    fprintf(stdout,", data=\"%s\"\n", htmlin);

    dba_loop();

    for(int c=0;c<HTML_BUFFERS;c++) {
      if((bytes=write(news, dbs_html[c], strlen(dbs_html[c])))==-1) {
        fprintf(stderr,"\n%s: cannot write() dbs_html[%d]\n", procname, c);
        perror("write");
        fflush(stderr);
      }
    }

    server_close(news);
    fprintf(stdout,"\ncallid: %d\n", callid++);
    fflush(stdout);
  }
}

Https palvelinohjelma

void https_server()
{
  int listenfd, status, addr_size;

  SSL_METHOD *method=NULL;
  SSL_CTX *ctx=NULL;
  SSL *ssl;
  X509 *peer_cert;

  struct sockaddr_in sa_cli;
  struct addrinfo *res;

  FILE *fp1;
  unsigned char buffer10[10];

  // plus space for '\0'                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
  htmlin = malloc(htmlinlen+1);

  signal(SIGPIPE, SIG_IGN);

  SSL_library_init();

  OpenSSL_add_ssl_algorithms();

  SSL_load_error_strings();

  if((method = (SSL_METHOD *)
      SSLv23_server_method()) == NULL) {
    fprintf(stderr,"\n%s: cannot SSLv3_server_method()", procname);
    fflush(stderr);
  }

  if((ctx=SSL_CTX_new(method)) == NULL) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_new()", procname);
    fflush(stderr);
  }

  if(SSL_CTX_use_certificate_file(ctx,
    cert_file, SSL_FILETYPE_PEM) <= 0) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_use_certificate()", procname);
    fflush(stderr);
  }

  if(SSL_CTX_use_PrivateKey_file(ctx,
      privatekey_file, SSL_FILETYPE_PEM)<=0) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_use_certificate()", procname);
    fflush(stderr);
  }

  if(!SSL_CTX_load_verify_locations(ctx,
      cert_file, NULL)) {
    fprintf(stderr,"\n%s: cannot SSL_CTX_verify_locations()", procname);
    fflush(stderr);
  }

  server_getaddrinfo(myport, &res);
  s=server_socket(res);
  server_bind(s, res);
  freeaddrinfo(res);
  listenfd = server_listen(s);

  dba_main();

  for(;;) {
    addr_size = sizeof(sa_cli);
    news=accept(s, (struct sockaddr *)&sa_cli, &addr_size);

    strcpy(htmlip, inet_ntoa(sa_cli.sin_addr));
    sprintf(htmlport,"%d", sa_cli.sin_port);

    unsigned long seconds = dbs_getseconds();

    strftime(htmltime, sizeof(htmltime), TIMEFORMAT,
             localtime((time_t *)&seconds));

    fprintf(stdout,"\nConnection(https) from %x", sa_cli.sin_addr.s_addr);
    fprintf(stdout,", ip %s", htmlip);
    fprintf(stdout,", port %s", htmlport);
    fprintf(stdout,", at %s\n", htmltime);

    if((ssl=SSL_new(ctx)) == NULL) {
      fprintf(stderr,"\n%s: cannot SSL_new()", procname);
      fflush(stderr);
    }

    if(SSL_set_fd(ssl,news) != 1) {
      fprintf(stderr,"\n%s: cannot SSL_set_fd()", procname);
      fflush(stderr);
    }

    if((status=SSL_accept(ssl))<0) {
      // Try to fix SSL_accept error 5
      if(SSL_get_error(ssl,status) == 5) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
        fprintf(stdout,", access failed, error 5, retry");
        SSL_free(ssl);
        continue;
      }
      // Try to fix SSL_accept error 1
      if(SSL_get_error(ssl,status) == 1) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
        fprintf(stdout,", access failed, error 1, retry");
        SSL_free(ssl);
        continue;
      }
      fprintf(stderr,"\n%s: cannot SSL_accept(), status: %d, SSL error: %d",
          procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }

    peer_cert = SSL_get_peer_certificate(ssl);
    if(peer_cert == NULL) {
      fprintf(stdout,", No peer certificate");
      fflush(stdout);
    }
    fprintf(stdout,"\n");

    int clen = 0;
    int reads = 0;
    int first = 1;
    htmlparams = NULL;
    int bytes, totalbytes = 0;
    while(htmlparams==NULL || clen-strlen(htmlparams) > 0) {
      if(!first) {
        fprintf(stdout,", ");
      }
      fprintf(stdout,"read %d", reads);
      fflush(stdout);
      if((bytes = SSL_read(ssl, htmlin+totalbytes, htmlinlen-totalbytes))<0) {
        fprintf(stderr,"\n%s: cannot SSL_read()\n", procname);
        perror("SSL_read");
        fflush(stderr);
      }
      fprintf(stdout,"(%d bytes)", bytes);
      if(bytes==0)
        break;
      *(htmlin+totalbytes+bytes) = '\0';

      totalbytes+=bytes;
      if(totalbytes >= htmlinlen) {
        // plus space for '\0'                                                                                                                                                                                                                                                                                                   
        htmlin=realloc(htmlin, htmlinlen*2+1);
        htmlinlen *= 2;
      }

      htmlparams = dbs_html_get_params();
      dbs_html_get_request_line_num("Content-Length",
          sizeof(buffer10), buffer10);
      clen = atoi(buffer10);
      reads++;
      first = 0;
    }

    fprintf(stdout,"\n%d reads", reads);
    fprintf(stdout,", received %d chars", totalbytes);
    fprintf(stdout,", read %d total bytes", totalbytes);
    fprintf(stdout,", input buffer size %d chars", htmlinlen);
    fprintf(stdout,", data=\"%s\"\n", htmlin);

    dba_loop();

    for(int c=0;c<HTML_BUFFERS;c++) {
      if((status=SSL_write(ssl, dbs_html[c], strlen(dbs_html[c])))<1) {
        fprintf(stderr,"\n%s: cannot SSL_write(), buffer %d, status: %d, SSL error: %d",
                procname, c, status, SSL_get_error(ssl,status));
        fflush(stderr);
      }
    }
    fprintf(stdout,"\nSSL connection using %s", SSL_get_cipher(ssl));
    fflush(stderr);
    SSL_free(ssl);

    server_close(news);

    fprintf(stdout,"\ncallid: %d\n", callid++);
    if((fp1 = fopen("dbscallid.deb","w")) != NULL) {
      fprintf(fp1,"%d\n", callid);
      fclose(fp1);
    }

    fflush(stdout);
  } // for(;;)                                                                                                                                                                                                                                                                                                                   
  SSL_CTX_free(ctx);
}

Pääohjelma

int main(int argc,char *argv[])
{
  int c,https = 1;
  unsigned char filedigest[HashLen],buffer10[10];

  procname=argv[0];

  dbs_file_digest("/proc/self/exe", filedigest);
  htmldigest[0]='\0';
  for(int c = 0;c < HashLen; c++) {
    char twodigits[3];
    sprintf(twodigits,"%02x", filedigest[c]);
    strcat(htmldigest, twodigits);
  }

  callid = 0;

  FILE *fp1;

  if((fp1 = fopen("dbscallid.deb","r")) != NULL) {
    fgets(buffer10, sizeof(buffer10), fp1);
    callid = atoi(buffer10);
    fclose(fp1);
  }

  if((fp1 = fopen("dbspid.deb","w")) != NULL) {
    fprintf(fp1,"%d\n", getpid());
    fclose(fp1);
  }

  myport = DEFAULT_PORT;

  for(c = 1; c < argc; c++) {
    if(!strncmp(argv[c], "--https",7))
      https = 1;
    else if(!strncmp(argv[c], "--http",6))
      https = 0;
    else if(!strncmp(argv[c], "--port",6)) {
      if(argv[c][6] != '\0')
        myport = save_string(&argv[c][6]);
      else
        myport = save_string(argv[++c]);
    } else if(!strncmp(argv[c], "-p",2)) {
      if(argv[c][2] != '\0')
        myport = save_string(&argv[c][2]);
      else
        myport = save_string(argv[++c]);
    } else {
      fprintf(stderr,"%s: Invalid option %s\n",
          procname, argv[c]);
      fflush(stderr);
      exit(1);
    }

  }
  fprintf(stdout,"%s", programname);
  if(https)
    fprintf(stdout,", https");
  else
    fprintf(stdout,", http");

  fprintf(stdout,", port %s", myport);

  fprintf(stdout,", sha256(%s)\n", htmldigest);
  fprintf(stdout,"%s", copyright);
  fflush(stdout);

  dba_main(argc,argv);

#ifdef RESSU
  ressu_init();
#endif
#ifdef FORT
  fort_init();
#endif

  if(https)
    https_server();
  else
    http_server();

  exit(0);
}

Published
Categorized as muuta