Category Archives: muuta

Uusi kokeilu DB2 ja DBS 0.03

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

Tietosisältö on nyt seuraavankaltaisessa tiedostossa:

;
; Asiakas
;
'app' "asiakas", 'appname' "Asiakasluettelo"
'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"
'asiakasnumero' "1000", 'asiakkaan nimi' "Firma 1", 'asiakkaan osoite' "Venekatu 1", 'asiakkaan postitoimipaikka' "00500", 'asiakkaan postiosoite' "111111", 'asiakkaan maa' "Finland", 'asiakkaan email' "jarik@example.com"
;
; Tuote
;
'app' "tuote", 'appname' "Tuoteluettelo"
'memberid' "tuotenumero" 'key'="key"
'memberid' "tuotteen nimi", 'text', "Tuotteen nimi", 'length'="32"
'app' "tuote", 'chapter' "header", 'sort' "01", 'memberid' "tuotenumero"
'app' "tuote", 'chapter' "header", 'sort' "02", 'memberid' "tuotteen nimi"
'app' "tuote", 'chapter' "header", 'sort' "03", 'memberid' "tuotteen hinta"
'tuotenumero' "1000", 'tuotteen nimi' "Tappi", 'tuotteen hinta' "5,00"
;
; Tilaus
;
'app' "tilaus", 'appname' "tilaus"
'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' "1000", 'tilauksen asiakasnumero' "1000", 'tilauksen asiakkaan nimi' = "Firma 1"
'tilausnumero' "1000", 'tilauksen rivinumero' "10", 'tilauksen tuotenumero' = "3000", '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ä"

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.03";

char htmlin[32768],htmlin2[32768],html1[32768], html2[32768], *html;

int usehttps=1;

#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];
#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()
{
  html=html1;
  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);
  html=html2;
}

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.

#include <stdarg.h>

void html_printf(const char *format, ...)
{
  int c, done;
  va_list args;
  unsigned char buffer[8192], *b, *h;

  va_start(args, format);
  vsnprintf(buffer, sizeof buffer, format, args);
  va_end(args);

  b=buffer;
  h=html + strlen(html);

  while(*b != '\0') {
    *h++ = *b++;
  }
  *h = '\0';
}

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ö:

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

  if(dbs_get_parameter("userid-0", sizeof(userid), userid)==1 &&
     dbs_get_parameter("password-0", sizeof(password),
         password)==1) {
    if(!strcmp(userid,"testi") && !strcmp(password,"testaus")) {
      dbs_gensessionid(sizeof(htmlsessionid), htmlsessionid);
      dbs_renew_cookie();
      fprintf(stdout,"Userid=\"%s\"", userid);
      fprintf(stdout,", password=\"%s\", password);
      fprintf(stdout,", sessionid=\"%s\"\n", htmlsessionid);

      FILE *fp1;

      if((fp1=fopen("dbssessions.deb","a"))!=NULL) {
        fprintf(fp1,"sessionid=%s",htmlsessionid);
        fprintf(fp1,", user=%s",userid);

        unsigned long seconds = dbs_getseconds();
        char timebuf[128];
        strftime(timebuf, sizeof(timebuf), TIMEFORMAT,
               localtime((time_t *)&seconds));

        fprintf(fp1,", logontime=%s",timebuf);
        fprintf(fp1,", ip=%s",htmlip);
        fprintf(fp1,"\n");
        fclose(fp1);
      }
    }
  }

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

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

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

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

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

    html_printf("<tr>");
    //html_printf("<input type=\"hidden\" name=\"sessionid-0\" value=\"%s\">",htmlsessionid);                                                                                                                                                                                                                     
    html_printf("</tr>");

    html_printf("</table>");

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

    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:

void dbs_app()
{
  int first, lineno;
  unsigned char *p;

  char htmlstring[1024];
  unsigned char htmltext[128];
  unsigned char query[128];
  unsigned char app[32];
  unsigned char appname[32];
  unsigned char chapter[32];
  unsigned char memberid[32];
  unsigned char value[128];
  struct query *qappname;
  struct data *dappname;
  struct query *qchapter;
  struct data *dchapter;
  struct query *qheadermemberid;
  struct query *qlinesmemberid;
  struct query *qmemberid;
  struct data *dmemberid;
  struct query *qheaderdata;
  struct query *qlinesdata;
  struct query *qdata;
  struct data *ddata;

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

  char func[10];
  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);

  dbs_renew_cookie();

  p = htmlparams;
  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\">");
  html_printf(htmltext);
  // app selection
  sprintf(htmltext,"<input type=\"text\" value=\"%s\" name=\"app-%d\" size=\"10\">", app, lineno);
  html_printf(htmltext);
  // print buttons
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Submit\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Reset\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Display\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Change\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Check\">", lineno);
  html_printf("<input type=\"submit\" name=\"func-%d\" value=\"Save\">", lineno);
  html_printf("<br>", app);

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

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

  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);
    fprintf(stdout,"2app=%s, fromapp=%s, toapp=%s\n", app2,
        fromapp2, 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);
              }
            }
            dallmemberid = db_data_get_next(dallmemberid);
          }
          dtomemberid = db_data_get_next(dtomemberid);
        }
        if(longest > 0) {
          fprintf(stdout, "savefrommemberid:%s savetomemberid=%s\n", savefrommemberid, savetomemberid);
        }
        dfrommemberid = db_data_get_next(dfrommemberid);
      }
    }
    dfromapp = db_data_get_next(dfromapp);
  }
  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)) {
    sprintf(htmltext, "<h1>%s</h1>", appname);
    html_printf(htmltext);
    fprintf(stdout,"app=%s", app);
    fprintf(stdout,"\n");
  }

  int 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++;

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

      ddata=db_query_get_first(qdata);

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

      dmemberid = db_query_get_first(qmemberid);
      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);
        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);
            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);
            length=atoi(lengths);
          }
        }

        int errors = 0;

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

        html_printf("<tr>");
        html_printf("<td>");
        sprintf(htmltext,"%s", memberid);
        html_printf(htmltext);
        html_printf("</td>");
        html_printf("<td>");
        // 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"))
          strcat(htmltext," style=\"border-color:red;\"");
        strcat(htmltext,"><br>");
        html_printf(htmltext);
        html_printf("</td>");
        html_printf("</tr>");
        dmemberid=db_data_get_next(dmemberid);
      }
      html_printf("</table>");

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

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

      dmemberid=db_query_get_first(qmemberid);

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

      while(dmemberid != NULL) {
        db_data_get_field(dmemberid, "memberid",
            sizeof(memberid), memberid);
        html_printf("<td>");
        html_printf("%s", memberid);
        html_printf("</td>");
        dmemberid=db_data_get_next(dmemberid);
      }
      html_printf("</tr>");
      ddata = db_query_get_first(qdata);

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

        fprintf(stdout,"%s\n", ddata->data);
        html_printf("<tr>");
        dmemberid=db_query_get_first(qmemberid);
        while(dmemberid != NULL) {
          db_data_get_field(dmemberid, "memberid",
              sizeof(memberid), memberid);
          struct query *qmemberiddata;
          struct data *dmemberiddata;
          unsigned char lengths[10];
          int length;

          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);
              length=atoi(lengths);
            }
          }
          if(!strcmp(func, "Reset"))
            value[0] = '\0';
          else {
            char tempmemberid[32];
            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);
              length = atoi(lengths);
            }
          }

          int errors = 0;

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

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

          html_printf(htmltext);
          html_printf("</td>");
          dmemberid=db_data_get_next(dmemberid);
        }
        html_printf("</tr>");
        ddata=db_data_get_next(ddata);
      }
      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) {

  }

  html_printf("<p>application: %s(%s)</p>", app, appname);
  html_printf("<p>htmlparams: %s</p>", htmlparams);
  html_printf("<p>htmlstring: %s</p>", htmlstring);

  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);
  html_printf("</code>");
  html_printf("</form>");
  html_printf("<br><br>%s sha256(%s<br><br>", programname,
      htmldigest);
}

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

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

void main(int argc,char *argv[])
{
  int c, listenfd, status, status2, addr_size,
      len, callid;
  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];

  unsigned char filedigest[HashLen];

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

  ressu_init();
  fort_init();

  callid = 0;

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

  signal(SIGPIPE, SIG_IGN);

  procname =argv[0];
  myport = DEFAULT_PORT;

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

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

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

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

  freeaddrinfo(res);

  if((listenfd=listen(s, backlog))==-1) {
    fprintf(stderr,"\n%s: cannot listen()\n", procname);
    perror("listen");
    fflush(stderr);
  }
  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);

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

    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,"SSL_access failed, retry");
        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);
    }
    memset(htmlin, 0, sizeof(htmlin));

    if((status = SSL_read(ssl, htmlin+strlen(htmlin), sizeof(htmlin)-strlen(htmlin))) < 1) {
      fprintf(stderr,"\n%s: cannot SSL_read(), status: %d, SSL_error: %d",
              procname, status, SSL_get_error(ssl,status));
      fflush(stderr);
    }
    fprintf(stdout,"\nreceived %d chars, \"%s\"\n", status, htmlin);

    unsigned char *p;
    unsigned char *n;

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

    int clen = 0;
    // Content-Length: 33                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    dbs_html_get_request_line_num("Content-Length",
        sizeof(buffer10), buffer10);
    clen=atoi(buffer10);
    fprintf(stdout,", Content-Length: \"%d\"",clen);

    if(clen - strlen(htmlparams) > 0) {
      if((status = SSL_read(ssl, htmlin+strlen(htmlin),
          sizeof(htmlin) - strlen(htmlin)))<1) {
        fprintf(stderr,"\n%s: cannot SSL_read(), status2: %d, SSL_error: %d",
                procname, status, SSL_get_error(ssl,status));
        fflush(stderr);
      }
      fprintf(stdout,"\nreceived %d chars, \"%s\"\n", status, htmlin);
    }
    fprintf(stdout,", htmlparams: \"%s\"", 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);

    html=html1;
    html[0]='\0';

    now = time(NULL);
    strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

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

    html=html2;
    html[0]='\0';

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

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

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

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

    len = strlen(html2);
    html = html1;
    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);

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

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

Contact information

Olen ohjelmoija, ja suurin osa tekemästäni työstä on ollut kaupan järjestelmien parissa. Asiakkaina ovat olleet muunmuassa Metsä Botnia, Vattenfall, Puolustusvoimat, Bombardier, Paulig, Tradeka ja Inex Partners.

Työkaluina olen käyttänyt Sapin MM, Fi/CO, BW ja APO, Hp:n Transact, Image ja VPLUS järjestelmiä. Opiskeluaikoina käytän tietysti linuxia.

Yhteystiedot

Jari Kuivaniemi (+358 50 3089 817)

mail: jarik(ät)moijari(piste)com