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 (http://moijari.com/?p=964), tässä ressu1.7 (http://moijari.com/?p=798), ja tässä ressu1.8 (http://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.

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 postitoimipaikka", 'text', "Asiakkaan postitoimipaikka", 'length'="10", 'mode' "Optional"
'memberid' "asiakkaan postiosoite", 'text', "Asiakkaan postiosoite", '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 postitoimipaikka"
'app' "asiakas", 'chapter' "header", 'sort' "05", 'memberid' "asiakkaan postiosoite"
'app' "asiakas", 'chapter' "header", 'sort' "06", 'memberid' "asiakkaan maa"
'app' "asiakas", 'chapter' "header", 'sort' "07", 'memberid' "asiakkaan email"
'app' "asiakas2", 'chapter' "header", 'sort' "01", 'memberid' "asiakasnumero"
'app' "asiakas2", 'chapter' "header", 'sort' "02", 'memberid' "asiakkaan nimi"
'app' "asiakas2", 'chapter' "header", 'sort' "03", 'memberid' "asiakkaan osoite"
'app' "asiakas2", 'chapter' "header", 'sort' "04", 'memberid' "asiakkaan postitoimipaikka"
'app' "asiakas2", 'chapter' "header", 'sort' "05", 'memberid' "asiakkaan postiosoite"
'app' "asiakas2", 'chapter' "header", 'sort' "06", 'memberid' "asiakkaan maa"
'app' "asiakas2", 'chapter' "header", 'sort' "07", 'memberid' "asiakkaan email"
'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1", 'asiakkaan osoite' "Venekatu 1", 'asiakkaan postitoimipaikka' "00500", 'asiakkaan postiosoite' "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 postitoimipaikka", 'language' "fi", 'membertext' "Asiakkaan postitoimipaikka"
'memberid' "asiakkaan postiosoite", 'language' "fi", 'membertext' "Asiakkaan postiosoite"
'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 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"
'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' "электронная почта клиента"
'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' "客戶電子郵件"
;
;
;       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"
'app' "tuote2", 'chapter' "header", 'sort' "01", 'memberid' "tuotenumero"
'app' "tuote2", 'chapter' "header", 'sort' "02", 'memberid' "tuotteen nimi"
'app' "tuote2", '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"
'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' = "1000", 'toimituksen tuotteen nimi' = "Tappi", 'toimituksen tuotteen hinta' = "5,00", 'toimituksen tilattu määrä' = "5,00", '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ä' = "5,00", 'toimitettu määrä' = "1

Sisäinen rakenne on seuraavanlainen: Ylimpänä on lista kyselyitä, joiden sisälle rakentuu kyselyn vastaus.

struct data {
  unsigned char *data;
  char changes;
  struct data *next_data;
};

struct query {
  unsigned char *query;
  unsigned char *keys;
  unsigned long count;
  struct data *first_data;
  struct query *next_query;
};

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 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 käytetään 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 dbs_renew_cookie()
{
  dbs_html_set(0);
  html_printf("Set-Cookie: sessionid=%s; Max-Age=%d\r\n",
              htmlsessionid, 3600*DBS_RENEW_SESSION_HOURS);
  html_printf("Set-Cookie: mode=%s; Max-Age=%d\r\n",
              htmlmode, 3600*DBS_RENEW_SESSION_HOURS);
  dbs_html_printf("Set-Cookie: language=%s; Max-Age=%d\r\n",
              htmllanguage, 3600*DBS_RENEW_SESSION_HOURS);
  dbs_html_set(1);
}

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 };
int dbs_html_size[] =
    { 0, 0, 0 };

void 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]);
  }
}

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(const char *format, ...)
{
  int count;
  va_list args;
  static unsigned char *dbs_printbuf=NULL;
  static int dbs_printbuf_len=0;

  va_start(args, format);
  count=vsnprintf(dbs_printbuf,
      dbs_printbuf_len,
      format, args) + 1;
  va_end(args);
  if(count > dbs_printbuf_len) {
    dbs_printbuf_len = count;
    dbs_printbuf=realloc(dbs_printbuf,
        dbs_printbuf_len);
    va_start(args, format);
    count=vsnprintf(dbs_printbuf,
        dbs_printbuf_len,
        format, args) + 1;
    va_end(args);
  }

  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);
}

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)
{
  DBS_EVENTS_START(300)

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

  DBS_EVENTS_END(301)
}

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

  DBS_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;
    }
  }

  DBS_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")) {
      dba_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 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 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 char *dba_app_foreign_keys=NULL;
static int dba_app_foreign_query_length = 0;
static char *dba_app_foreign_query = NULL;
static int dba_app_foreign_data_length = 0;
static char *dba_app_foreign_data = NULL;

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 tilausjonoista.

'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. 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,"'app', 'fromapp', 'toapp' \"%s\"",app);
  qfromapp=db_query(query);
  dfromapp = db_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 = db_query(query);

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

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

      dfrommemberid = db_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 = db_query_get_first(qtomemberid);
        while(dtomemberid != NULL) {
          db_data_get_field(dtomemberid, "memberid",
              sizeof(tomemberid), tomemberid);
          dallmemberid=db_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 = db_data_get_next(dallmemberid);
          } // dallmemberid != NULL) {                                                                                                                                                                                                                                                                                                                                                                                                                                     
          dtomemberid = db_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 = db_data_get_next(dfrommemberid);
      } // while(dfrommemberid != NULL                                                                                                                                                                                                                                                                                                                                                                                                                                     
      FOREIGN_KEYS_ADD_STRING(";");
      separatorneeded=0;
    } // if(!strcmp(toapp2                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    dfromapp = db_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 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ä 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);
}

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);
    fprintf(stdout,"query: %s",query);
    fprintf(stdout,", apptext: %s",
        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=db_query(query);
  if((dlanguage = db_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(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)) {
      fprintf(stdout,"query: %s", query);
      fprintf(stdout,", length: %s\n",
          lengths);
      fflush(stdout);
      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)) {
      fprintf(stdout,"query: %s", query);
      fprintf(stdout,", mode: %s\n", mode);
      fflush(stdout);
      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                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  dbs_html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Submit\">", 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);
  dbs_html_printf("<br>", app);

  fprintf(stdout,"htmlparams:\"%s\"\n",
      htmlparams);
  fprintf(stdout,"app: %s\n", app);

  static int dba_app_foreign_keys_length = 0;
  static char *dba_app_foreign_keys = NULL;
  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\n",
      dba_app_foreign_keys);

  fprintf(stdout,"query: %s\n", query);

  sprintf(query,"'app' \"%s\","
      " 'appname'", app);
  fprintf(stdout,"query: %s\n", query);
  qappname=db_query(query);
  dappname=db_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");
  }

  formerrors = 0;

  sprintf(query,"'app' \"%s\","
      " 'chapter'", app);
  fprintf(stdout,"query: %s\n", query);
  qchapter=db_query(query);
  dchapter=db_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);
    fprintf(stdout,"query: %s\n", query);
    qmemberid = db_query(query);
    dmemberid = db_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 = db_data_get_next(
          dmemberid);
    }

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

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

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

      ddata=db_query_get_first(qdata);

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

      dmemberid =
          db_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 = db_data_get_next(
            dmemberid);
      }
      dbs_html_printf("</table>");

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

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

      dmemberid = db_query_get_first(
          qmemberid);

      // save for dump
      qlinesmemberid = qmemberid;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
      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=db_data_get_next(dmemberid);
      }
      dbs_html_printf("</tr>");
      ddata = db_query_get_first(qdata);

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

        fprintf(stdout,"%s\n",
            ddata->data);
        dbs_html_printf("<tr>");
        dmemberid = db_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 = db_data_get_next(
              dmemberid);
        }
        dbs_html_printf("</tr>");
        ddata=db_data_get_next(ddata);
      }
      dbs_html_printf("</table>");
    } // end of if(!strcmp(chapter,"lines"))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
    dchapter=db_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);
  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>");
  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: Ensiksiksi 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
Content-Type: application/x-www-form-urlencoded^M
Origin: https://192.168.1.14:5004^M
Accept-Encoding: br, gzip, deflate^M
Cookie: mode=Display;sessionid=ObVhVnVzVEhQ96gUfKKS7HxguJR99r9Z^M
Connection: keep-alive^M
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8^M
User-Agent: Mozilla/5.0 (iPad; CPU OS 12_4_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1^M
Referer: https://192.168.1.14:5004/logon^M
Content-Length: 230^M
Accept-Language: en-us^M
^M
app-0=tilaus&func-0=Submit&asiakasnumero-1=1000&asiakkaan+nimi-1=Firma+1&asiakkaan+osoite-1=Venekatu+1&asiakkaan+postitoimipaikka-1=00500&asiakkaan+postiosoite-1=111111&asiakkaan+maa-1=Finland&asiakkaan+\
email-1=jarik%40example.com"
, htmlparams: "app-0=tilaus&func-0=Submit&asiakasnumero-1=1000&asiakkaan+nimi-1=Firma+1&asiakkaan+osoite-1=Venekatu+1&asiakkaan+postitoimipaikka-1=00500&asiakkaan+postiosoite-1=111111&asiakkaan+maa-1=Fin\
land&asiakkaan+email-1=esimerkki%40example.com", htmlsessionid: "ObVhVnVzVEhQ96gUfKKS7HxguJR99r9Z", htmlmode: "Display"

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_main()
{
  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);

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

  if(htmlsessionid[0]!='\0') {
    dba_app();
  }
}

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 c, listenfd, status, bytes, addr_size,
    len;
  unsigned char timebuf[128];

  time_t now;

  struct sockaddr sa_serv;
  struct sockaddr_in sa_cli;
  struct addrinfo *res;

  char buffer[32768],*p;
  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);

  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();
    char timebuf[128];
    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;
    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=read(news, htmlin+totalbytes,
          htmlinlen-totalbytes))<0) {
        fprintf(stderr,"\n%s: cannot read()\n", procname);
        perror("read");
        fflush(stderr);
      }
      fprintf(stdout,"(%d bytes)", bytes);
      *(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);

    dbs_html_set(0);
    dbs_html_clear();

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

    dbs_html_set(1);
    dbs_html_clear();

    html_printf("\n<!DOCTYPE html>\r\n");
    html_printf("<html lang=\"fi\">");

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

    html_printf("<body>");

    dba_main();

    html_printf("</body>");

    html_printf("</html>");

    dbs_html_set(0);
    dbs_html_clear();

    now = time(NULL);
    strftime(timebuf, sizeof(timebuf),
        HTMLTIMEFORMAT, gmtime(&now));
    html_printf("Date: %s\r\n", timebuf);
    len=strlen(html2);
    html_printf("Content-Length: %d", len);
    html_printf("\r\n\r\n");
    if((bytes=write(news, html1, strlen(html1)))==-1) {
      fprintf(stderr,"\n%s: cannot write()\n", procname);
      perror("write");
      fflush(stderr);
    }
    if((bytes=write(news, html2, strlen(html2)))==-1) {
      fprintf(stderr,"\n%s: cannot write()\n", procname);
      perror("write");
      fflush(stderr);
    }
    server_close(news);
    fprintf(stdout,"\ncallid: %d\n", callid++);
    fflush(stdout);
  }
}

Https palvelinohjelma

void https_server()
{
  int listenfd, status, status2, addr_size,
      len;
  unsigned char timebuf[128];

  time_t now;

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

  struct sockaddr sa_serv;
  struct sockaddr_in sa_cli;
  struct addrinfo hints;
  struct addrinfo *res;

  unsigned char *p;

  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);

  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();
    char timebuf[128];
    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);
      *(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);

    dbs_html_set(0);
    dbs_html_clear();

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

    dbs_html_set(1);
    dbs_html_clear();

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

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

    html_printf("<body>");

    dba_main();

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

    html_printf("</body>");

    html_printf("</html>");

    dbs_html_set(0);
    dbs_html_clear();

    now = time(NULL);
    strftime(timebuf, sizeof(timebuf),
        HTMLTIMEFORMAT, gmtime(&now));
    html_printf("Date: %s\r\n", timebuf);
    len=strlen(html2);
    html_printf("Content-Length: %d", len);
    html_printf("\r\n\r\n");

    if((status=SSL_write(ssl, html1, strlen(html1)))<1) {
      fprintf(stderr,"\n%s: cannot SSL_write()"
          ", status: %d, SSL error: %d",
          procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }
    if((status=SSL_write(ssl, html2, strlen(html2)))<1) {
      fprintf(stderr,"\n%s: cannot SSL_write()"
          ", status: %d, SSL error: %d",
          procname, 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;
  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);
  fflush(stdout);
  if(https)
    fprintf(stdout,", https");
  else
    fprintf(stdout,", http");

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

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

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

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

  exit(0);
}

Leave a Reply