Category Archives: terttu

Salasanan salakirjoitus tertussa

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002. Ressu satunnaislukuja löytyy osoitteesta: moijari.com:5001. Ikuinen kalenteri osoitteesta: moijari.com:5003.

Salasana on salakirjoitettuna muodossa:

sha256:ee84f5affcf47d:51567b8c4c20db2a00ea24eaae125a26f5c7c87fdbe5b90a02225fc97008b49a (oheinen salasana on muuten "SalaSana1234") ja vielä
unsigned char *peppper="l7IKrgcLMgsl_4Wv";

Alussa salatun salasanan tallennusformaatti, kaksoispisteen jälkeen alkaa salt:ti ja seuraavan kaksoispisteen jälkeen alkaa kryptattu salasana.

Tämä ensimmäinen rutiini salakirjoittaa password kentässä olevan salasanan ja muodostaa encpasswd kentän joka sisältää salasanan salakirjoitetussa muodossa.

int encryptpasswd(unsigned char encpasswd[ENCPASSWD_SIZE],unsigned char *password)
{
  int c,d;

  HashCtx ctx;

  unsigned char salt[7];
  unsigned char salthex[15];
  unsigned char twodigit[3];
  unsigned char temp[HashLen];
  unsigned char temphex[HashLen*2+1];

  memset(encpasswd,0,ENCPASSWD_SIZE);

  ressu_genbytes2(sizeof(salt),salt);

  ...

Ohjelman alussa varataan erilaisia ohjelman tarvitsemia muistialueita. Salt kentän tarkoituksena on erilaistaa sama salasana eri käyttäjillä ja vaikeuttaa kryptattujen salasanojen hakemiston muodostamista. Salt on salakirjoittamattomana salakirjoitetussa salasanassa , ja kopioidaan sellaisenaan salasanan tarkastukseen. Temp kenttää taas käytetään kryptatun salasanan tallettamiseen..

Seuraavat ohjelmarivit muuttavat salt:in heksamuotoon:

  ...
  for(c=0;c<sizeof(salt);c++) {
    sprintf(twodigit,"%02x",salt[c]);
    strcat(salthex,twodigit);
  }
  ...

Seuraava kappale tekee varsinaisen salakirjoituksen, Salakirjoitus tehdään sha256 tiivisterutiinilla. HASHROUNDS on tällä hetkellä sata tuhatta, ja siihen on valittu mahdollisimman suuri luku, joka silti pitää logon-vaiheen keston kohtuullisena. Pepper on aloitusarvo rutiinille.

  ...
  memset(temp,0,sizeof(temp));
  strcpy(temp,pepper);
  for(c=0;c<HASHROUNDS;c++) {
    HashInit(&ctx);
    HashUpdate(&ctx,temp,sizeof(temp));
    HashUpdate(&ctx,salt,sizeof(salt));
    HashUpdate(&ctx,password,strlen(password)+1);
    HashFinal(temp,&ctx);
  }
  ...

Seuraavassa edellisen koodikappaleen tulos temp muuttujassa laitetaan heksamuotoon:

  ...
  temphex[0]='\0';
  for(c=0;c<sizeof(temp);c++) {
    sprintf(twodigit,"%02x",temp[c]);
    strcat(temphex,twodigit);
  }
  ...

Kootaan salakirjoitettu salasana:

  ...
  strcpy(encpasswd,"sha256:");
  strcat(encpasswd,salthex);
  strcat(encpasswd,":");
  strcat(encpasswd,temphex);
  ...

Ja tyhjennetään vielä välikentät, jottei niitä vuoda ulos aliohjelmasta: ja funktion lopussa vielä return lause (0=ok).

  ...
  memset(salt,0,sizeof(salt));
  memset(salthex,0,sizeof(salthex));
  memset(temp,0,sizeof(temp));
  memset(temphex,0,sizeof(temphex));

  return(0);
}

Salasanan tarkistus funktio tekee oikeastaan samat asiat, hieman muuteltuna: koodikappaleen loppu tarkistaa salakirjoitetun salasanan tallennusformaatin ja  hakee salt:in heksamuodossa. Salt haetaan parametrina annetusta salakirjoitetusta salasanasta.

int checkpasswd(unsigned char encpasswd[ENCPASSWD_SIZE],unsigned char *password)
{
  int c,d,e,byte,ok;
  unsigned char *p,*s;
  HashCtx ctx;

  unsigned char salt[7];
  unsigned char salthex[15];
  unsigned char twodigit[3];
  unsigned char temp[HashLen];
  unsigned char temphex[HashLen*2+1];
  unsigned char encpasswd2[128];

  p=encpasswd;

  if(strncmp(p,"sha256:",7))
    return(1);

  p+=7;
  s=salthex;
  while(*p!=':') {
    *s++=*p++;
  }
  *s='\0';
  ...

Salasanan tarkistusrutiini hakee saltin satunnaisbittigeneraattorin sijasta annetusta salakirjoitetusta salasanasta.

Seuraava siirtää heksana olevan saltin binäärimuotoon:

  ...
  c=0;
  s=salthex;
  for(;;) {
    if((d=gethexdigit(*s++))==-1) break;
    if((e=gethexdigit(*s++))==-1) break;
    byte=d*16+e;
    salt[c++]=byte;
  }
  ...

Varsinainen salasanan kryptausosa on samanlainen kun edellisessä crypt rutiinissa:

  ...
  memset(temp,0,sizeof(temp));
  strcpy(temp,pepper);
  for(c=0;c<HASHROUNDS;c++) {
    HashInit(&ctx);
    HashUpdate(&ctx,temp,sizeof(temp));
    HashUpdate(&ctx,salt,sizeof(salt));
    HashUpdate(&ctx,password,strlen(password)+1);
    HashFinal(temp,&ctx);
  }
  ...

Muokanaan salakirjoitettu salasana temp kentästä temphex kenttään heksamuotoon:

  ...
  temphex[0]='\0';
  for(c=0;c<sizeof(temp);c++) {
    sprintf(twodigit,"%02x",temp[c]);
    strcat(temphex,twodigit);
  }
  ...

Muodostetaan varsinainen salasana salakirjoitetussa muodossa:

  ...
  strcpy(encpasswd2,"sha256:");
  strcat(encpasswd2,salthex);
  strcat(encpasswd2,":");
  strcat(encpasswd2,temphex);
  ...

Vertaillaan salakirjoitettuna annettua salasanaa ja selväkielisestä salasanasta muodostettua salakirjoitettua salasanaa keskenään, tyhjennellään kenttiä ja palautetaan totuusarvo:

  ...
  if(!strcmp(encpasswd,encpasswd2)) {
    fprintf(stdout,"\n*************ok");
    ok=0;
  } else {
    fprintf(stdout,"\n*************error");
    ok=1;
  }

  memset(encpasswd2,0,ENCPASSWD_SIZE);
  memset(salt,0,sizeof(salt));
  memset(salthex,0,sizeof(salthex));
  memset(temp,0,sizeof(temp));
  memset(temphex,0,sizeof(temphex));

  return(ok);
}

Tässä vielä gethexdigit:

int gethexdigit(char h)
{
  int byte;

  if(h>='0' && h<='9')
    byte=h-'0';
  else if(h>='a' && h<='f')
    byte=h-'a'+10;
  else if(h>='A' && h<='F')
    byte=h-'A'+10;
  else
    byte=-1;

  return(byte);
}

 

Hash:in käyttö tertun kyselyn käsittelyssä

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Tällä hetkellä ajattelen että terttu jakautuu kahteen osaan, sovellusosaan ja tietokantaosaan. Sovellusosa sisältää kaikki sovelluskohtaiset asiat, kuten tertussa olevat logon-rutiini, sovellus-rutiini, prosessien jatkaminen, talletus-rutiini ja nuo sarakkeiden siirtelyt prosessien yhteydessä.

Tietokantaosa sisältää toiminnot fetch ja save, jotka ovat yksinkertaisia toimintoja. Fetchillä luetaan rivejä ja save:lla talletetaan niitä.

Fetch lyhyesti

(polveilevaa jaarittelua) Fetch koostuu vaiheista, Ensimmäinen vaihe muodostaa asiakkaan antamasta kyselystä vakiomuotoiset versiot. Tässä yksi versio kyselyn normalisointirutiinista, voit itse kokeilla tehdä muut versiot voi olla myös että minä lisään koodia postin loppuun..

void skk_querystring1(char *query, unsigned char *newquery)
{
  int value;
  unsigned char *p,*q;
  HashCtx ctx;

  p=query;
  q=newquery;

  skk_skipwhite(&p);
  while(*p!='\0') {
    if(*p=='\'') {
      *q++=*p++;
      while(*p!='\'' && *p!='\0')
        *q++=*p++;
      *q++='\'';
      if(*p=='\'')
        p++;
    } else if(*p=='=') {
      p++;
      *q++=' ';
      *q++='=';
      *q++=' ';
    } else if(*p=='\"') {
      p++;
      value=0;
      while(*p!='\"' &&*p!='\0') {
        p++;
        value=1;
      }
      if(*p=='\"')
        p++;
        if(value==1)
          *q++='?';
    } else if(*p==',') {
      p++;
      *q++=',';
      *q++=' ';
    } else if(*p==' ' || *p=='\t') {        
      skk_skipwhite(&p);
      *q++=' ';
    } else
      p++;
  }
  *q='\0';
}

Huomaathan että tämä normalisointi normalisoi myös ‘määrä’=’isotilaus’. Ja että toisaalta se ei toimi kyselylle ‘määrä'<‘saldo’. Tämä on vielä hiukan hakusessa.

Seuraavasta kyselystä (‘asiakasnumero’ = “1000” ,  ‘asiakkaan nimi’) tulostetaan ainakin seuraavat versiot: versioissa vakiotoiminta on siten, että vaihdetaan useamman välilyönti-tabi yhdistelmät yhteen välilyöntiin. Yhtäsuuruusmerkki vaihdetaan välilyönti yhtäsuuruusmerkki-välilyöntiin. Hipsulla ‘\” annetut kentän nimet tallennetaan siten että ne joko alkavat rivin alusta, tai erotetaan toisista sanoista välilyönnein. Lainausmerkillä ‘”‘ annetut kentän arvot siirretään samalla tavalla kuin hipsu kentät. Samoin kyselyn kentät lajitellaan aakkosjärjestykseen. Mallina olevassa kyselyssä olevat kentät ovat tietenkin jo aakkosjärjestyksessä.

(‘asiakasnumero’, ‘asiakkaan nimi’)

tällä versiolla voidaan tarkistaa, onko tietueet talletettu siten, että yhdessä hash koodilla hajasijoitetussa tietoryhmässä on kaikki tietueet. Esimerkkinå voisi olla:

(‘kuukauden numero’=”1″, ‘kuukauden nimi’=”Tammikuu”)
(‘kuukauden numero’=”2″, ‘kuukauden nimi’=”Helmikuu”)

Tietueet jatkuvat tietysti samalla tavalla loppuun asti. Kun tiedosto avataan, sieltä valitaan kyselyyn mätsäävät tiedot.

Toinen vakioitu versio kyselystä on seuraavana:

(‘asiakasnumero’ = ?, ‘asiakkaan nimi’)

Tällä hash koodilla löydetään tieto siitä, onko tämä tieto talletettu tuossa muodossa, eli siis kannattaako (‘asiakasnumero’= “1000” “asiakkaan nimi”) tyylistä kyselyä hakea. Ilmeisesti kaikilla tietojen perusavaimilla voi tai pitää olla tämä versio. Huomaa pienet tiedostot ja ensimmäinen kyselyversio.

Seuraava versio kyselystä on malli, jolla hajasijoitetusta “tietokannasta” haetaan tieto:

(‘asiakasnumero’ = “1000” ,  ‘asiakkaan nimi’)

Ja tässäkin vakiomuotoisesta kyselystä muodostetaan hash koodi, josta tehdään x syvyyksinen hakemistorakenne ja jos tiedosto löytyy, sieltä pitäisi löytyä kyselyn tulos. Eli kyselyn vastaukseen tarvitaan n 4 tiedoston avausta, hash koodin laskentaa ja kyselymerkkijonon normalisointia.

Toisessa vaiheessa tarkistetaan löytyykö kyselyn perusteella avain muistista, muistissa olevat kyselyt talletetaan muodossa:

struct query {
    unsigned char *query;
    struct set *sets;
    struct query *next;
}:

Haetaan vain muistilista läpi “tarkalla” kyselyversiolla, jos kysely löytyy listasta, sillä on tässä rakenteessa sets kentässä ratkaisu. Jatkossa lisätään vielä viimeiset tapahtumat eli muutokset lokista. (katso perusavain määrittely jatkossa)

Muistissa olevat kyselyt pitää järjestää siten että usein käytetyt kyselyt ovat ensimmäisenä listassa, silloin voidaan poistaa harvoin käytetyt kyselyt siten, että poistetaan viimeiset kysely vastaus parit.

Kolmannessa vaiheessa haetaan pelkät kenttien nimet sisältävällä versiolla olisiko kysymys pienestä taulusta (tai miksei suuremmastakin, jos kyselyn suoritusnopeudella ei ole väliä). Jos tiedosto löytyi, me saamme siitä vastauksen. Vastaukseen lisätään vielä viimeisten lokitapahtumien muutokset.

Neljännessä vaiheessa haetaan kysäri(?)versiolla onko meillä ratkaisua tälle kyselymuodolle, jos on käytetään kenttien arvot sisältävää versiota, taas saadaan tulos, johon lisätään lokitapahtumat ja palautetaan tiedot asiakkaalle (asiakasohjelmalle).

Viidennessä versiossa kyselyn vastaus haetaan lokista (vrt perusavain mallit).

Kaikissa kyselymalleissa palautettava vastaus lisätään muistissa olevien kyselyiden joukkoon.

Save

Save toiminto lisää talletuskutsussa olevat tietueet lokin loppuun ja kirjoittaa lokin levylle. Ilmeisesti jossain kohtaa kirjoitetaan nämä lokientrit eri talletustavoilla käytettyihin tiedostoihin (muisti, pieni taulu, taulu avaimella jne.)

Hakemistorakenne

Hash koodista muodostetaan hakemistorakenne seuraavasti:

Hash koodilla: abcdefghijklmnopqrstuvwxyz hakemistorakenne voi olla: kanta/ab/cd/ef/gh/ij/kl/mn/op/qr/st/uv/wx/yz tai kanta/ab/cdefghijklmnopqrstuvwxyz tai jotakin siltä väliltä.

Perusavaimista

Edit: Perusavaimen käsittelyyn on mielessäni kaksi mallia, ensimmäinen on yksinkertainen malli, jossa tietueen avaim(ena/ina) ovat aina saman nimiset kentät, muilla tietueen kentillä ei ole merkitystä, eli selvitään yksinkertaisella määrittelyllä:

(‘memberid’=”asiakasnumero”, ‘key’=”1″)(‘memberid’=”toimittajanumero”, ‘key’=”1″)
(‘memberid’=”tuotenumero”, ‘key’=”1″)
(‘memberid’=”tilausnumero”, ‘key’=”1″)
(‘memberid’=”tilausrivin numero”, ‘key’=”1″)
(‘memberid’=”toimitusnumero”, ‘key’=”1″)
(‘memberid’=”toimitusrivin numero”, ‘key’=”1″)
(‘memberid’=”laskunumero”, ‘key’=”1″)
(‘memberid’=”laskurivin numero”, ‘key’=”1″)
(‘memberid’=”tapahtumanumero”, ‘key’=”1″)
(‘memberid’=”tapahtumarivin numero”, ‘key’=”1″)

Edit: Olen yleensä pitäytynyt tiukasti näissä kolmessa neljässä taulussa, mutta annetaan nyt hiukan ajatuksen lentää:

(‘memberid’=”bussinumero”, ‘key’=”1″)
(‘memberid’=”autonumero”, ‘key’=”1″)
(‘memberid’=”toimenpidenumero”, ‘key’=”1″).

Edit: Voit keksiä itse oman alasi tietueiden avaimet. Toisessa mallissa kaikki tietueen kentät määrittelevät tietuetyypin, ja näistä kentistä jotkut ovat avaimia, esimerkiksi:

(‘memberid’=”asiakasnumero”, ‘key’=”1″)
(‘memberid’=”asiakkaan nimi”)
(‘memberid’=”asiakkaan osoite”)
(‘memberid’=”asiakkaan postitoimipaikka”)
(‘memberid’=”asiakkaan postiosoite”)
(‘memberid’=”asiakkaan maa”)

Edit: Tottakai tietueella voi olla ‘setid’ kenttä, joka nimeää nämä kenttä yhdistelmät. Esimerkiksi edellisessä set id voisi olla ‘asiakas’, Tertussa tähän asti on ollut vain sovelluksia, joilla voi olla otsakkeita ja rivejä.

Edit: (out there) Toki näistä voi keksiä vielä monimutkaisempia vaihtoehtoja, joissa esimerkiksi jonkun kentän sisältö määrittää tietueella olevat kentät:

(‘memberid’=”toimenpidenumero”)
(‘memberid’=”toimenpidetyyppi”)
(‘toimenpidetyyppi=”öljyn vaihto”, ‘memberid’=”tuotenumero”)
(‘toimenpidetyyppi=”öljyn vaihto”, ‘memberid’=”huomiot”)(‘toimenpidetyyppi=”renkaan vaihto”, ‘memberid’=”uusi paine”)(‘toimenpidetyyppi=”renkaan vaihto”, ‘memberid’=”huomiot”)

Edit: Nämä tuovat monimutkaisuutta enemmän kuin on tarpeen. Tämän tyyppiset ratkaisut kuitenkin ratkeavat automaattisella navigaatiolla, tässä vain tarvitaan tietuetta tai ainakin tuota toimenpidetyyppiä mukaan hakuun, jossa haetaan tietueen kenttiä.

Levytilan vähennystä

Jos kyselyyn vastataan hakemalla kyselyyn perustuvalla hash koodilla relevantit tietueet, tiedosto voi olla perinteisen terttu (tai skk1) formaatin mukaan:

(‘asiakasnumero’=”1000″, ‘asiakkaan nimi’=”Asiakas 1″)

tai tiivistää sitä hiukan kirjoittamalla kenttien nimet vain otsakkeeseen: (kyselystähän kenttien nimiä ei voi katsoa, koska ne ovat lajiteltuna aakkosjärjestykseen, eli alkuperäisen kyselyn nimillä ei ole merkitystä.

‘asiakasnumero’, ‘asiakkaan nimi’
“1000”, “Asiakas 1”

Tietueiden “splittaus”

Edit: Tämä ajatus liittyy myös tiedon talletustilan vähentämiseen. Jos teemme kyselyn, Esimerkiksi:

’tilausnumero’, ’tilauksen asiakasnumero’, ’tilauksen asiakkaan nimi’, ’tilauksen tuotenumero’, ’tilauksen tuotteen nimi’,

kysely muodostuu otsakkeiden tiedoista ja tilausrivien tiedoista, eli kyselyn tulos on:

’tilausnumero’=”4000″, ’tilauksen asiakasnumero’=”1000″, ’tilauksen asiakkaan nimi’=”Asiakas 1″, ’tilauksen tuotenumero’=”3000″, ’tilauksen tuotteen nimi’=”Tuote 1″
’tilausnumero’=”4000″, ’tilauksen asiakasnumero’=”1000″, ’tilauksen asiakkaan nimi’=”Asiakas 1″, ’tilauksen tuotenumero’=”3000″, ’tilauksen tuotteen nimi’=”Tuote 1″
’tilausnumero’=”4000″, ’tilauksen asiakasnumero’=”1000″, ’tilauksen asiakkaan nimi’=”Asiakas 1″, ’tilauksen tuotenumero’=”3000″, ’tilauksen tuotteen nimi’=”Tuote 1″

Ja siinä on otsakkeen osalta aika paljon toistoa, koska kaikki otsakekentät ovat samat samassa tilauksessa. Tässä tapauksessa voitaisiin jakaa sovellus kaikkiin eri rivimalleihin, eli otsake olisi oma tietuemallinsa, rivi olisi oma tietuemallinsa, ja ne vain yhdisteltäisiin vasta luvun yhteydessä,

 

HTTP ja HTTPS palvelimen pääohjelma

Kirjoitin uuden version terttu palvelimen pääohjelmasta. Nyt se pystyy vastaamaan myös https viesteihin. Kokeiluversiossa ohjelma antaa satunnaislukuja, ja sitä voi ajaa näistä linkeistä http://moijari.com:5001 ja https://moijari.com:5001.

En vielä ostanut certifikaattia moijari.com:ille, joten moijari.com nettiosoitteelle pitää luoda poikkeus, jos haluaa sitä käyttää https:llä.

Ohjelman alussa ovat c tyyliset includelauseet, joilla luodaan erilaisia muuttujia, rakenteita ja funktioiden mallikutsuja:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <openssl/ssl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>

/* See: http://h41379.www4.hpe.com/doc/83final/ba554_90007/ch04s03.html */

#include <netdb.h>

Aiemmissa tärkein on ehkä tuo <openssl/ssl.h>, joka käytännössä kuvaa nämä tämän ohjelman SSL alkuiset rutiinit. See kappaleessa on itseasiassa nettisivu, jota käytin apuna rakennuksessa.

#define DEFAULT_PORT "5001"
#define backlog 5

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

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

int usehttps=1;
int usehttpthruhttps=0;

Tässä tärkeimmät ehkä porttinumero, tiedostot, joista https avaimet ja sertifikaatti luetaan ja puskurit asiakkaan viestin lukuun ja kirjoitukseen (in=luku, 1 ja 2 kirjoitus).

Pääohjelman alussa määritellään pääohjelman tarvitsemia muuttujia, ja kutsutaan https liittymän “alustamiseen” liittyviä asioita.

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

  char buffer[32768],*p;
  callid=0;

  signal(SIGPIPE,SIG_IGN);

  procname=argv[0];
#ifdef OLD1
  if(usehttps) {
    myport=HTTPS_PORT;
  } else {
    myport=DEFAULT_PORT;
  }
#else
  myport=DEFAULT_PORT;

Alun kokonaislukumuuttujat (int) ovat perinteistä liittymää varten. Perinteisessä liittymässä http tiedon siirto tehdään socket, bind, listen, accept, read- ja write kutsujen avulla. https tiedon siirto tehdään SSL alkuisten kutsujen avulla. edelleen struct komennolla määritellyt muistialueet ovat perinteisiä funktioita varten. signal() funktiolla ohitetaan ilmeisesti writen katketessa lähetetty PIPe signaali. Procnameen talletetaan ohjelman nimi komentorivin ensimmäisestä sanasta. Ohjelman nimeä käytetään virheilmoituksissa. Oletusportti on aina DEFAULT_PORT, jonka arvo on tällä hetkellä 5001. Järjestelmäfunktioista saa kuvauksen komennolla $ man [komento], esimerkiksi $ man SSL_read tai $ man read.

Seuraavassa pätkässä ovat ensimmäiset SSL-rutiinit, näitä ajetaan vain yhden kerran ohjelman suorituksen alussa. usehttps flagillä määritellään käytetäänkö ohjelmassa HTTPS:ää. Eli tässä tapauksessa jos https on käytössä kutsutaan SSL-funktiot SSL_library_init(),  OpenSSL_add_ssl_algorithms(), SSL_load_error_strings(), SSLv23_server_method(), SSL_CTX_new(), SSL_CTX_use_certificate_file(), SSL_CTX_use_PrivateKey_file(), SSL_CTX_load_verify_locations.

   if(usehttps) {

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

Tössö ensimmäiset perinteiset kutsut: näitä kutsutaan kanssa vain kerran: getaddrinfo, socket(),  bind(),  listen().

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

Seuraava on ns pääluuppi, joka suoritetaan aina kun veppilomakkeelle kirjoitetaan tietoja ja painetaan nappulaa. Täytetyt tiedot ovat read (tai SSL_read kutsulla luetussa htmlin muuttujassa ja lähetettävä tieto kootaan htmlin:in perusteella html1 ja html 2 tauluihin. Lähetettävää tietoa varten on kaksi taulua, koska taulu 1 sisältää rivin, jossa on taulun 2 merkkien lukumäärä.

Jokaisella weppitapahtumalla käydään läpi accept(), SSL_new(), SSL_set_fd, SSL_accept(),

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

    fprintf(stdout,"\nConnection from %x, port %d",
            sa_cli.sin_addr.s_addr,
            sa_cli.sin_port);
    if(usehttps) {
      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) {
        if(status==-1 && SSL_get_error(ssl,status)==1) {
          fprintf(stdout,"\nUsing http thru https");
          usehttpthruhttps=1;
        } else {
          fprintf(stderr,"\n%s: cannot SSL_accept(), status: %d, SSL error: %d",  procname, status, SSL_get_error(ssl,status));
          fflush(stderr);
        }
      }
    }
    if(usehttps && usehttpthruhttps==0) {

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

Tässä on ajateltu, että jos SSL_accept kaatuu virheeseen 1, yhteys ei ole HTTPS yhteys vaan http yhteys. HTTP yhteyden merkiksi laitetaan muuttujaan usehttpthruhttps 1.

Sitten onkin readin vuoro.

   memset(htmlin,0,sizeof(htmlin));

   if(usehttps && usehttpthruhttps==0) {
     if((status=SSL_read(ssl, htmlin,
       sizeof(htmlin)))<1) {
       fprintf(stderr,"\n%s: cannot SSL_read(), status: %d, SSL_error: %d", pr\
ocname, status, SSL_get_error(ssl,status));
       fflush(stderr);
     }
     htmlin[bytes]='\0';
     fprintf(stdout,"\nreceived %d chars, \"%s\"\n", status, htmlin);
   } else {
    if((bytes=read(news, htmlin, sizeof(htmlin)
      ) )==-1) {
      fprintf(stderr,"\n%s: cannot read()\n",procname);
      perror("bind");
      fflush(stderr);
    }
    htmlin[bytes]='\0';
    fprintf(stdout,"\nreceived %d chars,\"%s\"\n", bytes, htmlin);
   }

Eli käytetään SSL_read:id tai read:ia sen mukaan, onko kyseessä https vai http yhteys. htmlin kenttään tulee asiakkaan kenttämuutokset ja nappula ja write:en tai SSL write:en muodostetaan html tai bootstrap tai vastaava lauseita.

Ja vielä lopuksi sivunmuodostus (ja write lauseet):

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

    html_printf("HTTP/1.0 200 OK\r\n");
    html_printf("Location: \r\n");
    html_printf("Server: %s\r\n", programname);
    now = time(NULL);
    strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

    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("</head>");

    html_printf("<body>");

    html_printf("<h1>Hello, world!</h1>");

    html_printf("</body>");

    html_printf("</html>");

    len=strlen(html2);
    html=html1;
    html_printf("Content-Length: %d",len
    html_printf("\r\n\r\n");

Eli puskuriin 1 laitetaan protokolla, palvelin, kellonaika ja päivämäärä. Kakkospuskuriin laitetaan html lauseita sisältävä sivu (html_printf() on muuten aiemmassa postissa). Ykköspuskuriin laitetaan kakkospuskurin pituuden sisältävä rivi ja seuraavassa write lauseet ykkös ja kakkospuskurille.

   if(usehttps && usehttpthruhttps==0) {
      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);
      }
    } else {
      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);
      }
    }

Kirjoitetaan ykkös ja kakkospuskurit joko SSL_write:llä tai writellä().

Ja lopetetaan tämä input output askel vapauttamalla tuo ssl ctx alue ja suljetaan kierroksen alussa acceptilta saatu news tiedosto.

    if(usehttps && usehttpthruhttps==0) {
      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++);
    fflush(stdout);
  } /* takaisin for(;;) luupin alkuun */

Jos meillä on tästä for(;;) luupista lopetusmahdollisuus, lopetuksessa vielä vapautetaan SSL_CTX_free komennolla ctx muuttuja.

SSL_CTX_free(ctx);

Prosessit jatkoa 2

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Edellisestä terttu postista on kulunut jo sen verran paljon aikaa (tuo aiempi satunnaislukugeneraattori+=opinnäyte) että nyt tuli tarve jatkaa. Tässä postissa ei aiemmasta poiketen ole mukana koodia, tämä on enemmänkin pohdiskelua tai jaarittelua (25000 sivuhakua muuten). Olen aiemmin kirjoittanut vierasavainten käsittelyn syöttölomaketta varten (http://moijari.com/?p=260)(http://moijari.com/?p=209). Seuraavaksi ohjelmassa on save nappulan tarvitsema prosessien hallinta. Nämä ovat oikeastaan saman toiminnon toistoa. Tällä hetkellä ajatuksena on, että “vierasavainkäsittelyn” lisäksi tarvitaan ainakin:

-ehtolause (esimerkiksi tilaus-toimitus tietovirrassa toimituspäivä<=kuluvapäivä (toimitetaan vasta toimituspäivänä), ja toimitus-laskutus tietovirrassa toimitettu==”1″ (laskutetaan vasta toimituksen jälkeen), varastosaldon laskennassa vapaasaldo>tilattumäärä) (sallitaan tilaus vain kun varastossa tilattu määrä tuotteita)
-perusavainten täyttäminen (kun luodaan tilaus, tai  automaattisesti uusi toimitus tai lasku).
-laskutoimitus (tilauksen summa=rivin summa, varastosaldo=varastosaldo+määrä, jne)
-summaus (tilauksen laskutettu määrä, kun tilauksessa on useampia laskuja)
-sijoitus (toimitettu=”1″, laskutettu=”1″ jne.)

Seuraavassa tilaus-sovelluksen määrittelyt:

('tilausnumero', 'tilauksen asiakasnumero', 'tilauksen asiakkaan nimi', 'tilauspäivä', 'tilauksen summa')
('tilausnumero', 'tilausrivin numero', 'tilauksen tuotenumero', 
 'tilauksen tuotteen nimi', 'tilauksen tuotteen hinta', 'tilattu määrä', 'rivin summa'

Ja seuraavassa tilauksen vierasavaimen päässä olevat tiedot asiakas ja tuote-sovellukset:

('asiakasnumero', 'asiakkaan nimi', 'asiakkaan osoite', 'asiakkaan postinumero',
 'asiakkaan postitmp')
('tuotenumero', 'tuotteen nimi', 'tuotteen hinta')

Ja seuraavassa noiden väliset suhteet:

('sovellus'="tilaus", 'fromsovellus'="asiakas", 'tosovellus'="tilaus")
('sovellus'="tilaus", 'fromsovellus'="tuote", 'tosovellus'="tilaus")

Eli kun täytetään tilaus tietueita, samanlaiset sarakkeet haetaan asiakas sovelluksesta. Tilauksen otsakkeessa on kentät tilauksen asiakasnumero ja tilauksen asiakkaan nimi, joita vastaavat kentät ovat asiakasnumero ja asiakkaan nimi. Tilauksen riveillä taas on tilauksen tuotteen numero, tilauksen tuotteen nimi ja tilauksen tuotteen hinta. Jos tilaustietueille on määritelty yksilöivä tieto jommasta kummasta, loput kentät täytetään automaattisesti. Mikä tahansa tilauksella oleva asiakkaan (tai tuotteen) tieto voidaan täyttää, jos se on yksilöivä, muut kentät täytetään automaattisesti. Jos esimerkiksi täytetään asiakkaalle numero 1000, nimi haetaan automaattisesti, jos tuo asiakas numero 1000 on olemassa(lähde tietovirta). Jos tilauksen tuotteen hintaan täytetään 10€, tuotteen muut tiedot haetaan tuotteelta, jonka hinta on ainoana tuotteena 10€.

Seuraavassa laajennetaan tätä koko tilaus-toimitus-laskutus tietovirtaan:

('tilausnumero', 'tilauksen asiakasnumero', 'tilauksen asiakkaan nimi', 'tilauspäivä',
 'tilauksen summa')
('tilausnumero', 'tilausrivin numero', 'tilauksen tuotenumero',
 'tilauksen tuotteen nimi', 'tilauksen tuotteen hinta', 'tilattu määrä',
 'tilauksen toimitettu määrä', 'tilauksen laskutettu määrä', 'tilausrivin summa')
('toimitusnumero', 'toimituksen asiakasnumero', 'toimituksen asiakkaan nimi',
 'toimituksen tilauspäivä', 'toimituspäivä', 'toimituksen summa')
('toimitusnumero', 'toimitusrivin numero', 'toimituksen tuotenumero',
 'toimituksen tuotteen nimi', 'toimituksen tuotteen hinta', 'toimituksen tilattu määrä', 'toimitettu määrä', 'rivin summa')
('laskunumero', 'laskun asiakasnumero', 'laskun asiakkaan nimi', 'laskun päivä',
 'tilauspäivä', 'toimituspäivä', 'eräpäivä', 'laskun summa')
('laskunumero', 'laskurivin numero', 'tuotenumero', 'tuotteen nimi', 'tuotteen hinta', 
 'laskurivin tilattu määrä', 'laskurivin toimitettu määrä', 'laskutettu määrä', 'rivin summa')
('asiakasnumero', 'asiakkaan nimi', 'asiakkaan osoite', 'asiakkaan postinumero',
 'asiakkaan postitmp')
('tuotenumero', 'tuotteen nimi', 'tuotteen hinta', 'tuotteen varastosaldo')

Huomaathan että tässä ei ole esimerkiksi osoitteita, ne lisätään tarvittaessa. Ajatus on löytää tekniset toiminnot, joilla ne saadaan toimimaan. Seuraavassa tietovirtamääritykset koko prosessista:

('sovellus'="tilaus", 'fromsovellus'="asiakas", 'tosovellus'="tilaus")
('sovellus'="tilaus", 'fromsovellus'="tuote", 'tosovellus'="tilaus")
('sovellus'="tilaus", 'fromsovellus'="tilaus", 'tosovellus'="toimitus")
('sovellus'="toimitus", 'fromsovellus'="toimitus", 'tosovellus'="laskutus")

Lisäsin edelliseen tilaussovellukseen kentät ’tilauksen toimitettu määrä’ ja ’tilauksen laskutettu määrä’ havainnollistaakseni prosessin kulkua takaisinpäin. Ensimmäisessä tilausriville saadaan tieto, kuinka paljon tilausriviä on toimitettu, ja se kopioituu yksiselitteisesti toimituksen toimitettu määrä kentästä. Jos tilausriviä on toimitettu useammassa toimituksessa, sen pitäisi olla tietysti summa näistä riveistä.

Laskussa on taas ‘laskun tilattu määrä’ kenttä, joka tulee yksiselitteisesti tilauksesta. Kenttä toimii esimerkkinä, jossa kenttä ei tule prosessin edellisestä vaiheesta vaan hyppää taaksepäin. Ilmeinen vaihtoehto olisi lisätä:

('sovellus'="laskutus", 'fromsovellus'="tilaus", 'tosovellus'="laskutus")

mutta tässä on muitakin kenttiä (asiakkaan tiedot, tuotetiedot), jotka kopioituisivat. Tässä taas laskulla voi jossakin tapauksessa olla useampia tilauksia, jolloin laskun tilattu määrä kenttään tulisi niiden summa. Hmmmm.

Samoin lisäsin tuotetietoihin ‘tuotteen vapaasaldo’-kentän. Se havainnollistaa ulkopuolista kenttää, jota ei talleteta käsiteltävään sovellukseen, mutta sillä kuitenkin tehdään käsittelyä tai laskentaa ja se talletetaan sovelluksen ulkopuolelle. Tässä oletetetaan että meillä on vain yksi varasto ja toimitukset lähtevät samana päivänä (vapaasaldo tietysti vaihtelee toimituspäivästä(-ajasta) johtuen), oikeasti ‘vapaasaldo’ olisi esimerkiksi (‘varasto’, ‘varastopaikka’, ‘tuotenumero’, ‘toimituspäivä’, ‘vapaasaldo’) tai vastaavalla tietueella. Edellisistä ‘varasto’ olisi valinnaisena tietona tilauksessa tai asiakkaalle määriteltäisiin lähin varasto, tai se voitaisiin päätellä yksiselitteisesti toimituksessa.

Laskussa on myös ‘laskun toimitettu määrä’ joka tulee yksiselitteisesti toimituksesta/toimituksista.

Jos järjestelmässä olisi asiakaskohtainen hinta, esimerkiksi (‘tuotenumero’, ‘asiakasnumero’, ‘tuotteen hinta’), vierasavainhaku palauttaisi kaksi tietuetta, tuotekohtaisen tuotteen hinnan ja asiakaskohtaisen hinnan (jos molemmat olisivat syötettynä). Yksinkertaisessa versiossa tilauksen tekijä valitsisi useammasta vaihtoehdosta, se toimisi myös muissa valinnoissa (jos esimerkiksi toimittajan valinta tilauksella palauttaisi kaksi toimittajaa). Hmmm.

Tässä on versio tilauksesta, ja toimituksesta jossa varasto on tilauksen riveillä ja toimituksessa toimituksen otsakkeella. Tässä tapauksessa jos tilatut tavarat tulevat varastoista 1 ja 2 tilaus jakaantuu kahdeksi toimitukseksi (esimerkiksi tuotevarastolle ja tarvikevarastolle).

('tilausnumero', 'tilauksen asiakasnumero', 'tilauksen asiakkaan nimi', 'tilauspäivä',
 'tilauksen summa')
('tilausnumero', 'tilausrivin numero', 'tilauksen tuotenumero',
 'tilauksen tuotteen nimi', 'tilauksen tuotteen hinta', 'tilattu määrä',
 'varasto', 'tilauksen toimitettu määrä', 'tilauksen laskutettu määrä', 'tilausrivin summa')
('toimitusnumero', 'toimittava varasto', 'toimituksen asiakasnumero', 'toimituksen asiakkaan nimi',
 'toimituksen tilauspäivä', 'toimituspäivä', 'toimituksen summa')
('toimitusnumero', 'toimitusrivin numero', 'toimituksen tuotenumero',
 'toimituksen tuotteen nimi', 'toimituksen tuotteen hinta', 'toimituksen tilattu määrä',
 'toimitettu määrä', 'rivin summa')

Prosessit jatkoa

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Olen lisännyt koodia save rutiiniin. Save rutiinin tehtävä on tallettaa lomakkeella tehdyt muutokset. Lisäksi save hoitaa prosessien jatkamisen, eli esimerkiksi lelusovelluksessa tilauksen toimitukseen ja laskutukseen. Save antaa myös talletettaville tietueille avaintiedot, ja määrittelee vierasavainten perusteella haettavat kentät. Save jakaa myös tarvittaessa yhden tiedon kahdeksi (esimerkiksi tilauksessa tilataan tuotteita kahdesta varastosta, ja varastoille tehdään erilliset toimitukset(eli varasto on toimituksen otsaketiedoissa)).

Voit katsoa lelusovelluksessa saven lokkaantumalla sisään, ja menemällä tilausten käsittelynäytölle, painamalla change ja syöttämällä tilauksen kenttiin tietoja (esimerkiksi asiakasnumero 1000, tuotenumetot 3000,3001,3002). Näytön loppuun tulostuu changes if saved kappale, joka listaa muutokset, jotka lomake tällä hetkellä tekee.

Vielä puuttuvat ainakin

  • Avainten arvojen haku
  • Tämän hetkisten tietojen haku
  • Summausta
  • Kaavat kuten ‘rivin summa’=’tilattu määrä’*’tilauksen tuotteen hinta’
  • Tietojen varsinainen lisääminen terttuun
  • Tietojen kenttien oikeellisuuden tarkistus
  • Ehto prosessissa eteenpäin siirtymiseen (esimerkiksi ’tilauksen toimituspäivä’=’kuluvapäivä’ jne.)
  • Tällä hetkellä rivit on yhdistelty siten, että otsake ja rivit on yhdistelty samalle riville, näiden jakaminen otsake ja rivi tietueiksi.

Avainten arvojen haku tarkoittaa esimerkiksi lelusovelluksessa tilausnumeron, toimitusnumeron haku. Tilausnumert on lelusovelluksessa 4000sta lähtien, toimitusnumerot 5000:sta alkaen.

Tämän hetkisten tietojen haku tarkoittaa kaikkien tätä tietoa koskevien tietojen hakua, aiemmat tiedot vaikuttavat tietysti tietueen tekemiin muutoksiin. Esimerkiksi tilaus on voinut aiheuttaa toimituksen, ja määrien muutos vaikuttaa toimituksen määriin, ei välttämättä lisää uutta toimitusta, esimerkiksi.

Summauksesta esimerkkinä voisi olla tilauksen laskutettu määrä kenttä, johon pitää summata kaikki tilausriviä koskevien laskujen laskutettu määrä kentät.

Muistutukseksi vielä ohjelman pitäisi toimia millä tahansa tietueilla, voit keksiä sopivat prosessit muilta toimialoilta, tai tietotarpeista.

Lisäsin loppuun pari koodin pätkää. 2set elements muistuttaa paljon foreign key rutiinia.

Koodailen tätä juuri, koodiin ja postiin saattaa tulla muutoksia.

sovellus_get_elements(char **bones2,char *bones)
{
 int comp;
 char temps[1024],memberid[64];
 struct set *setmembers, *setm;

 setmembers=NULL;
 skk_fetch_sets(&setmembers,bones,skk->first);
 setm=setmembers;
 while(setm!=NULL) {
 set_get_element("memberid",&comp,memberid,setm->data);
 set_add_element(bones2,memberid);
 setm=setm->next;
 }
}

sovellus_get_2set_elements(char **movebones, char *tosovellus, char *fromsovellus)
{
 int c,comp,longest,rest;
 char temps[1024],allname[64],fromname[64],toname[64],savefromname[64],savetoname[64],value[64];
 char *allbones,*pab,
 *frombones,*pfb,
 *tobones,*ptb;

 sprintf(temps,"'memberid'");
 allbones=NULL;
 sovellus_get_elements(&allbones,temps);
 fprintf(stdout,"\nAB: %s",allbones);

 sprintf(temps,"'sovellus'=\"%s\", 'memberid'",fromsovellus);
 frombones=NULL;
 sovellus_get_elements(&frombones,temps);
 fprintf(stdout,"\nFB: %s",frombones);

 sprintf(temps,"'sovellus'=\"%s\", 'memberid'",tosovellus);
 tobones=NULL;
 sovellus_get_elements(&tobones,temps);
 fprintf(stdout,"\nTB: %s",tobones);
  fprintf(stdout,"\nCB: ",tobones);

  *movebones=NULL;
  rest=0;
  pfb=frombones;
  while(*pfb!='\0') {
    set_get_next_element(fromname,&comp,value,&pfb);
    ptb=tobones;
    longest=0;
    while(*ptb!='\0') {
      set_get_next_element(toname,&comp,value,&ptb);
      pab=allbones;
      while(*pab!='\0') {
        set_get_next_element(allname,&comp,value,&pab);
        if( (strstr(fromname,allname)!=NULL) &&
            (strstr(toname,allname)!=NULL) ) {
          if((c=strlen(allname))>longest) {
            longest=c;
            strcpy(savefromname,fromname);
            strcpy(savetoname,toname);
          }
        }
      }
    }
    if(longest>0) {
      sprintf(temps,"'%s' = '%s'",savetoname,savefromname);
      set_add_element_noquotes(movebones,temps);
    }
  }
  fprintf(stdout,"\nCB2: %s",*movebones);
}

Muutama pikku muutos ja yksi suurempi

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Terttu-valikossa on uutena valintana yhteenveto, jolla saa koko terttu kannan kuvan. Siitä on helpompi hahmottaa uutta fetch versiota (lukee lokeja). edit: Ja toisaalta sen avulla on helpompi hahmottaa save toimintoa, joka ilmeisesti tekee kaikki tapahtuman tarvitsemat muutokset kantaan (eli nuo tietovirrat, summattujen tietojen päivitykset jne) (vrt. transaktiot, commit). endedit. Esimerkki kannassa se näyttää tälläisen raportin:

Lisäksi poistin submit nappulan login näytöstä, se on nyt muotoa:

html_printf(“<input type=\”submit\” value=\”Submit\” style=\”visibility:hidden;\”>”);

Lisäksi muutos moodi näyttää vain yhden tyhjän rivin entisen 10 sijasta.

Tämä on varmaan lyhin posti, jonka olen kirjoittanut.

 'memberid', 'memberlength', 'membertype'
 'userid', 'username', 'password'
 'sovellus', 'sovelluksen nimi', 'link'
 'sovellus', 'chapter', 'memberid'
 'asiakasnumero', 'asiakkaan nimi', 'asiakkaan osoite', 'asiakkaan postinumero', 'asiakkaan postitmp'
 'toimittajanumero', 'toimittajan nimi', 'toimittajan osoite', 'toimittajan postinumero', 'toimittajan postitmp'
 'tuotenumero', 'tuotteen nimi', 'tuotteen hinta'
 'sovellus', 'fromsovellus', 'tosovellus'
 'tilausnumero', 'tilauksen asiakasnumero', 'tilauksen asiakkaan nimi', 'tilauspäivä', 'tilauksen summa'
 'tilausnumero', 'tilausrivin numero', 'tilauksen tuotenumero', 'tilauksen tuotteen nimi', 'tilauksen tuotteen hinta', 'tilattu määrä', 'rivin summa'
 'tilausnumero', 'asiakasnumero', 'tilauspäivä', 'tilauksen summa'
 'toimitusnumero', 'toimituksen asiakasnumero', 'toimituksen tilauspäivä', 'toimituspäivä', 'toimituksen summa'
 'toimitusnumero', 'toimitusrivin numero', 'toimituksen tuotenumero', 'toimituksen tuotteen nimi', 'toimituksen tuotteen hinta', 'tilattu määrä', 'toimitettu määrä', 'rivin summa'
 'laskunumero', 'asiakasnumero', 'laskun päivä', 'tilauspäivä', 'toimituspäivä', 'eräpäivä', 'laskun summa'
 'laskunumero', 'laskurivin numero', 'tuotenumero', 'tuotteen nimi', 'tuotteen hinta', 'tilattu määrä', 'toimitettu määrä', 'laskutettu määrä', 'rivin summa'
 'tapahtumanumero', 'kirjauspäivä', 'kirjauskuukausi', 'kirjausvuosi', 'laskunumero', 'asiakasnumero', 'tilausnumero', 'toimitusnumero', 'tapahtuman summa'
 'tapahtumanumero', 'tapahtumarivin numero', 'tilinumero', 'tapahtumarivin summa'
 'sovellus', 'chapter', 'set'
 'set', 'memberid'
 'tilinumero', 'tilin nimi'
 'sovellus', 'sovelluksen nimi', 'link', ''
 'asiakasnumero', 'vuosi', 'myynti'
 'asiakasnumero', 'myynti'
 'asiakasnumero', 'tuotenumero', 'myynti'
 'asiakasnumero', 'vuosi', 'tuotenumero', 'myynti'
; Tämä suoritetaan aina save nappulalla, näin saadaan kannasta
; ylläpidettyä ajankohtainen kuva. Huomasin muuten taas pikku optimoinnin,
; etsipä se..

skk_save_overview(struct set *set) /* JariK 20151025 */
{
  int comp,found,add,counts,counto;
  char *p,name[64],value[64],name2[64],value2[64];
  char *bones,temps[64];
  struct set *seto;

  p=set->data;
  bones=NULL;
  while(*p!='\0') {
    set_get_next_element(name,&comp,value,&p);
    sprintf(temps,"'%s'",name);
    set_add_element_noquotes(&bones,temps);
  }
  ; Selataan edellinen overview läpi tietue tietueelta
  ; Jos kenttään add jää tosi, tietue on uusi.
  add=1;
  seto=setoverview;
  while(seto!=NULL) {

    ; Lasketaan overview rivin kenttien lukumäärä
    counto=0;
    p=seto->data;
    while(*p!='\0') {
      set_get_next_element(name,&comp,value,&p);
      counto++;
    }

    ; Lasketaan uuden tietueen kenttien lukumäärä

    counts=0;
    p=set->data;
    while(*p!='\0') {
      set_get_next_element(name,&comp,value,&p);
      counts++;
    }

    ; Jos kenttiä yhtä monta, verrataan kenttä kentält
    if(counto==counts) {

      ; Jos found kenttään jää yksi, löysimme edellisestä overviewistä
      ; tämän tietueen.
      found=1;
      p=seto->data;
      while(*p!='\0') {
        set_get_next_element(name,&comp,value,&p);
        if(set_get_element(name,&comp,value2,bones)==0) {
          found=0;
        }
      }

      ; Jos overview:stä löytyi jo samanmuotoinen tietue,
      ; ei tarvitse lisätä tätä tietuetta sinne.
      if(found==1) {
        add=0;
        break;
      }
    }
    ; Tarkistetaan seuraava overview tietuemalli.
    seto=seto->next;
  }
  ; Jos uusi, lisätään
  ;
  if(add==1) {
    set_add_set_end(&setoverview,bones);
  }
}

Vierasavaimet ja sovellukset näyttö

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Lisäsin puuttuvan koodin vierasavainten käsittelyyn. Sitä voi kokeilla lokkaantumalla lelusovellukseen (testi/testaus ks. etusivu) ja valitsemalla terttu sanan alta tilaukset sovelluksen ja painamalla change nappulaa. Voit kokeilla täyttelemällä asiakasnumero, asiakasnimi, tuotenumero, tuotteen nimi kenttiä. Asiakas ja tuotetietoja voit katsella asiakas ja tuote sovelluksissa.

Nyt löytyy myös sovellukset sovellus, jossa voi katsella sovellusten määrityksiä.

Lokkausnäytölle lisäsin terttu palvelimen versionumeron ja ohjelman sha256 tiivisteen.

Vielä puuttuu ainakin:

  • Lokin kirjoitus
  • Fetch lokista
  • Näytön tietojen tarkistus
  • Tietovirtojen hallinta (lelusovelluksessa tilaus, toimitus, laskutus, kirjanpito)
  • Kenttien järjestys ruudulla (nyt kentän nimen mukaan aakkosjärjestyksessä)
  • Summaus esimerkiksi myyntiraporteille (tai tapahtumien automaattinen lisäys), summaus varastosaldo/-tapahtumatyyliin, (myynnit miinusta, ostot plussaa)
  • ja varmaankin paljon muuta, ainakin muiden toimintojen tarpeita (ostotilaus, varastointi, jne.).

HTML asiakas

Sain valmiiksi uuden version käyttöliittymästä. Rutiini on aika pitkä, joten ajattelin käydä sen läpi vähän yksityiskohtaisemmin kuin edelliset koodinpätkät.

Kaikki oikeudet tietenkin pidätetään. Viimeinen versio ohjelmasta löytyy seuraavasta linkistä: moijari.com:5002

Linkissä on muuten tämä uusi versio, toivotaan, ettei se kaatuile… Linkissä on edellisen postin sisään lokkaantuminen. Anna käyttäjätunnukseksi testi ja salasanaksi testaus.

Edit: Lisätty save nappula jatkon tekstiin ja koodiin. Lisätty loppuun otsakkeen ja rivien tulostusrutiinit.

Rutiini käy läpi seuraavat vaiheet

  • nappuloiden käsittely
  • HTML otsakkeen tulostus (HTTP/1.0, Location, Server, Date jne.)
  • terttu sanan valikon tulostus
  • ylärivin nappuloiden tulostus
  • vierasavainten käsittely (aiemmassa postissa)
  • Save nappulan rutiini
  • otsakeiden kenttien luku tertusta
  • rivien kenttien tietojen luku tertusta
  • Fetch nappulan käsittely ensimmäinen käsittely
  • Prev ja Next nappuloiden endimmäinen käsittely
  • Submit, Display, Change ja Save nappuloiden käsittely
  • Fetch, Prev ja Next nappuloiden toinen yhteinen osa
  • tulosta otsake
  • tulosta rivit
  • vapauta käytetyt muistialueet.

Koodissa on vähän enemmän kommentteja kun tavallisesti. Ohjelman viimeinen versio linkki toimii taas. Vierasavainten käsittelypostissa mainitut muutokset löytyy kappaleessa eivät ole vielä paikallaan.

void html_sovellus(char *sovellus)
{
  int comp;
  char temps[512];
  unsigned char *criteria;

  unsigned char *bonesheaderdata,*bonesheaderdatacriteria;
  unsigned char *boneslinesdata,*boneslinesdatacriteria;
  unsigned char *memberid[64];
  unsigned char timebuf[128];
  unsigned char *ucp;
  time_t now;

  struct set *setheaderfields, *sethf;
  struct set *setlinesfields, *setlf;
  struct set *setheaderdata, *sethd;
  struct set *setscreendata, *setsd;
  struct set *setlinesdata, *setld;
  struct set *setprev,*setnext;

  bonesheaderdatacriteria=NULL;
  boneslinesdatacriteria=NULL;
  setheaderfields=NULL, sethf=NULL;
  setlinesfields=NULL, setlf=NULL;
  setheaderdata=NULL, sethd=NULL;
  setscreendata=NULL, setsd=NULL,
  setlinesdata=NULL, setld=NULL;
  setprev=NULL,setnext=NULL;

  *func='\0';

  ; luetaan parametreistä func parametri
  set_get_element("func",&comp,func,htmlparams);

  if(*func=='\0') {
    strcpy(func,"");
  }

  if(!strcmp(func,"Terttu"))
    strcpy(func,"");
  ; Nappuloiden käsittely

  if(!exists_session_var("mode"))
    strcpy(mode,"Display");
  else {
    ucp=NULL;
    get_session_var("mode",&ucp);
    strcpy(mode,ucp);
  }

  ; Func kentässä on ylärivin nappulan toiminnon nimi
  ; (Fetch, Prev, Next, Display, Change, Save jne.)
  ; Mode kentän mukaan otsakkeen ja rivien tulostuksessa
  ; tulostetaan vain katselu(Display) tai muuttelu(Change)
  ; kenttiä. Lisäksi mode vaikuttaa siihen tulostetaanko
  ; rivit lohkoon ylimääräisiä rivejä rivien lisäystä
  ; varten.
  ;
  ; Suomeksi:
  ;
  ; Fetch, Prev ja Next nappulat antavat katselunäytön,
  ; muuttaaksesi tietoja voit painaa Change nappulaa.
  ;
  ; Submit, Reset ja Save eivät vaikuta näyttömuotoon,
  ; eli jos olit ennen nappulaa muuttamassa, näyttö
  ; säilyy muutosmoodissa, jos olit display moodissa,
  ; näyttö säilyy muuttelumoodissa.
  ;
  ; Display ja change nappulat vaikuttavat vain
  ; näyttötilaan (katselu, muutos).
  ;
  ; Fetch, Prev ja Next nappulat lukevat tietoja
  ; tertusta. Ja muuttavat näyttötilaksi katselutilan
  ;
  ; Save nappula kutsuu skk_save rutiinia, joka lisää
  ; nappulan lokin loppuun(nyt). Muut rutiinin
  ; tehtävät on vielä auki 
  ;
  if(!strcmp(func,"Fetch")) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Prev") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Next") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Submit")) {
    /* ei vaikuta modeen */
  } else if(!strcmp(func,"Reset")) {
  } else if(!strcmp(func,"Display") ) {
    strcpy(mode,"Display");
  } else if(!strcmp(func,"Change")) {
    strcpy(mode,"Change");
  } else if(!strcmp(func,"Save")) {
    /* Save ei vaikuta näyttöön */     
  }

  if(mode==NULL) {
    strcpy(mode,"Display");
    set_session_var("mode",mode);
  } else 
    set_session_var("mode",mode);

  ; Tulostetaan html otsake. Html otsakkeessa
  ; ensimmäisessä osassa on html:n rivien lukumäärä.
  ; Tämän takia ensimmäinen ja toinen osa tulostetaan
  ; erikseen (html1 ja html2 muuttujat). html_printf
  ; löytyy aiemmasta postista.

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

  html_printf("HTTP/1.0 200 OK\r\n");
  html_printf("Location: \r\n");
  html_printf("Server: %s\r\n",programname);
  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

  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>Terttu</title>",clientname);
  html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
  html_printf("<link href=\"terttu.css\" rel=\"stylesheet\" type=\"text/css\">");
#ifdef OLD1
  html_printf("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=\"UTF-8\">");
#endif
  html_printf("</head>");
  html_printf("<body>");
  html_printf("<form action=\"%s\" method=\"post\">",sovellus);
  html_printf("<input type=\"hidden\" name=\"sessionid\" value=\"%s\">",sessionid);

  html_printf("<nav>");
  html_printf("<ul>");
  html_printf("<li>Terttu");
  html_printf("<ul>");

  ; Tulostaa terttu sanan valikon.

  html_sovellus_menu();

  html_printf("</ul></li>");

  ; Tulostaa ylärivin nappulat

  html_print_buttons();

  html_printf("</ul>");
  html_printf("</nav>");

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

  ; Selvittää foreign key yhteydet ja täyttää kentät.
  
  set_find_foreigns(sovellus,setscreen); /* JariK 20150920 */

  ; Save nappula suorittaa skk_save rutiinin. Lisäksi
  ; tarvitaan varmaankin tarkistuksia tiedoille (katso
  ; set_find_foreigns / tietovirrat.
  ;
  ; Aluksi Save nappi kirjoittaa tietueet lokiin, jossa
  ; sama tieto voi olla ajallisesti eri aikoina useampaan
  ; kertaan. skk_fetch:stä ilmeisesti tulee uusi versio,
  ; joka lukee lokia. Ilmeisesti viimeisin versio valitaan
  ; näytölle. Jatkossa poimintaehdosta (esim
  ; asiakasnumero=1000) voisi muodostaa tiiviste-
  ; merkkijonon, jonka nimiseen tiedostoon kaikki
  ; asiakasta tai muuta objektia koskevat tiedot
  ; laitetaan. Asiakasluettelo (kaikki tietueet,
  ; joissa on asiakasnumero kenttä) voisi olla
  ; saatavilla pelkästä 'asiakasnumero' tiivistettynä
  ; sanasta tehdystä tiedostosta.
  ;

  if(!strcmp(func,"Save")) {                                                                                                                    
    skk_save(setscreen);                                                                                                                        
  }
  ; Haetaan otsakkeen kenttien nimet ja muodostetaan
  ; otsakkeen hakuun käytettävä lause (bonesheaderdata)

  rowid=0;

  sprintf(temps,"'sovellus'=\"%s\", 'chapter'=\"header\", 'memberid', 'memberlength'",sovellus); /* Added memberlength. JariK 20140209 */

  skk_fetch2(&setheaderfields,temps,skk);

  bonesheaderdata=NULL;

  sethf=setheaderfields;
  while(sethf!=NULL) {
    set_get_element("memberid",&comp,memberid,sethf->data);
    set_add_element(&bonesheaderdata,memberid); /* Collect fieldnames to fieldsh */
    sethf=sethf->next;
  }

  ; Haetaan rivikenttien nimet ja muodostetaan rividatan
  ; hakuun tarvittava lause.
  sprintf(temps,"'sovellus'=\"%s\", 'chapter'=\"lines\", 'memberid', 'memberlength'",sovellus); /* JariK 20140127 */

  skk_fetch3(&setlinesfields,temps,skk); /* Changed to version 2. JariK 20140214 to version 3. JariK 20140303 */

  boneslinesdata=NULL;

  setlf=setlinesfields; /* sovellus, chapter="header", memberid */
  while(setlf!=NULL) {
    set_get_element("memberid",&comp,memberid,setlf->data);
    set_add_element(&boneslinesdata,memberid); /* Collect fieldnames to fieldsh */
    setlf=setlf->next;
  }

  ; Tämä on fetch nappulan ensimmäinen vaihe. Fetch
  ; nappula lukee näytön otsakkeen kenttien arvot.
  ;
  ; Arvoja käytetään fetch, prev ja next nappuloiden
  ; valikoimien otsaketietueiden valinnassa.
  ; Fetch tallettaa kenttien arvot istuntomuuttujan
  ; findmatch arvoksi.
  ;
  ; Lopuksi Fetchissä luetaan tuo otsakeketju ja
  ; palautetaan ketjun ensimmäinen otsikko sethd
  ; muuttujassa.
  if(!strcmp(func,"Fetch")) { /* JariK 20140220 */

    setscreendata=NULL;

    /* skkuser contains fields filled by user (see parameter reading)
     * fieldsh contains names of header fields.
     */
    skk_fetch_sets(&setscreendata,bonesheaderdata,setscreen);

    setsd=setscreendata;
    criteria=NULL;
    while(setsd!=NULL) {
      set_copy_filled(&criteria,setsd->data);
      setsd=setsd->next;
    }

    if(setscreendata!=NULL) /* Free also. JariK 20140221 */
      set_free(setscreendata);
    setscreendata=NULL;

    bonesheaderdatacriteria=NULL;
    set_copy_all(&bonesheaderdatacriteria,criteria);
    set_copy_all(&bonesheaderdatacriteria,bonesheaderdata);
     
    set_session_var("findmatch",bonesheaderdatacriteria);

    if(criteria!=NULL)
      free(criteria);
    criteria=NULL;

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdatacriteria,skk->first);
    
    sethd=setheaderdata;
  }

  ; Prev ja next nappulat lukevat näytöltä tämän hetkisen
  ; otsake tietueen kentät. Seuraavaksi hartaan Fetchin
  ; saamat valintakriteerit findmatch ympäristö-
  ; muuttujasta. Sitten luetaan valintakriteerien
  ; mukaiset tiedot tertusta. Seuraavaksi 

  if((!strcmp(func,"Prev") || /* JariK 20140223 */
     !strcmp(func,"Next")) &&
     exists_session_var("findmatch")) { /* JariK 20140223 */

    skk_fetch_sets(&setscreendata,bonesheaderdata,setscreen); /* Ennen set_fetch. JariK 20150920 */

    setsd=setscreendata;
    criteria=NULL;
    while(setsd!=NULL) {
      set_copy_filled(&criteria,setsd->data);
      setsd=setsd->next;
    }

    if(setsd!=NULL)
      set_free(setsd);
    setsd=NULL;

    /* Luetaan fetchin tallettama kenttä, joka sisältää
     * otsakkeen kentät fetchin painamisen ajalta.
     * Esim selaa asiakkaan tilauksia jos asiakasnumero
     * on täytetty.
     */
    get_session_var("findmatch",&bonesheaderdatacriteria);

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdatacriteria,skk->first);
    
    setnext=setheaderdata; /* has next row. JariK 20140226 */
    sethd=NULL; /* has new current row. JariK 20140226 */
    setprev=NULL; /* has previous row. JariK 20140226 */
    
    while(setnext!=NULL) {
      ; Kun arvot mätsäävät ruudulla prev tai next
      ; nappulan painamisen aikana olleisiin tietoihin,
      ; näytetään seuraavaksi joko tätä tietuetta edeltävä
      ; tai seuraava otsikko.
      if(!set_match2(setnext->data,criteria)) {
        if(!strcmp(func,"Prev")) {
          if(setprev!=NULL) {
            sethd=setprev;
          } else {
            sethd=setnext;
          }
          break;
        } else if(!strcmp(func,"Next")) {
          if(setnext->next!=NULL) {
            sethd=setnext->next;
          } else {
            sethd=setnext;
          }
          break;
        }
      }
      setprev=setnext;
      setnext=setnext->next;
    }
    /* Ei mätsäävää tietuetta, otetaan eka (esim fetch, reset, next) 20150929 JariK */
    if(sethd==NULL) 
      sethd=setheaderdata;
  }

  ; Submit, Change, Display ja Save nappuloilla luetaan
  ; vain ruudulla olevat tiedot uudestaan
  ; tulostettavaksi.
  if(!strcmp(func,"Submit") ||
     !strcmp(func,"Change") ||
     !strcmp(func,"Display") ||
     !strcmp(func,"Save") ) {

    setheaderdata=NULL;
    skk_fetch_sets(&setheaderdata,bonesheaderdata,setscreen);

    setlinesdata=NULL;
    skk_fetch_sets(&setlinesdata,boneslinesdata,setscreen);

    setld=setlinesdata;
  }

  if(!strcmp(func,"Fetch") ||
     !strcmp(func,"Prev") ||
     !strcmp(func,"Next")) {
    boneslinesdatacriteria=NULL;
    if(sethd!=NULL)
      set_copy_all(&boneslinesdatacriteria,sethd->data);
    set_copy_all(&boneslinesdatacriteria,boneslinesdata);

    setlinesdata=NULL;
    skk_fetch_sets(&setlinesdata,boneslinesdatacriteria,skk->first);

    setld=setlinesdata;
  }

  sethf=setheaderfields; /* sovellus, chapter="header", memberid */

  if(setheaderfields!=NULL)
    html_print_header(sethd,setheaderfields,mode); /* Ennen sethd->data */

  setlf=setlinesfields; /* sovellus, chapter="lines", memberid */
  setld=setlinesdata;

  html_print_lines(setld, setlinesfields,mode);

  html_printf("</td>");
  html_printf("</tr>");
  html_printf("</table>");

  set_debug_foreigns(sovellus,setscreen);

  html_printf("</body>");
  html_printf("</form>");
  html_printf("</html>");
  html_printf("\n\n");

  ; Vapautetaan käytössä olleet muistialueet

  if(bonesheaderdata!=NULL)
    free(bonesheaderdata);
  bonesheaderdata=NULL;

  if(bonesheaderdatacriteria!=NULL)
    free(bonesheaderdatacriteria);
  bonesheaderdatacriteria=NULL;

  if(setheaderfields!=NULL)
    set_free(setheaderfields);
  setheaderfields=NULL;

  if(setlinesfields!=NULL)
    set_free(setlinesfields);
  setlinesfields=NULL;

  if(setheaderdata!=NULL)
    set_free(setheaderdata);
  setheaderdata=NULL;

  if(setlinesdata!=NULL)
    set_free(setlinesdata);
  setlinesdata=NULL;
}

int html_print_header(struct set *set, struct set *setheaderfields,char *mode)
{
  struct set *sethf;

  html_printf("<div id=\"header\">");
  html_printf("<table border=\"0\">");
  sethf=setheaderfields;

  ; Tulostetaan kentän nimi arvopareja kaikille
  ; otsakkeen kentille.

  while(sethf!=NULL) {
    html_printf("<tr>");
    html_printf("<td>");
    html_print_fieldname(set,sethf->data,mode);
    html_printf("</td><td>");
    html_print_fieldvalue(set,sethf->data,mode);
    html_printf("</td>");
    sethf=sethf->next;
    html_printf("</tr>");
  }
  html_printf("</table>");
  html_printf("</div>");
}

int html_print_lines(struct set *setlinesdata, struct set *setlinesfields,char *mode)
{
  int comp,lines;
  char memberid[64],memberlength[10],value[128];
  struct set *setlf;
  struct set *setld;

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

  ; Tulostetaan kentän nimet taulukon yläriville.

  setlf=setlinesfields;
  while(setlf!=NULL) {
    set_get_element("memberid",&comp,memberid,setlf->data);

    html_printf("<td>");
    html_printf("%s",memberid); /* Little bugfix JariK 31012014 */
    html_printf("</td>");
    setlf=setlf->next;
  }
  html_printf("</tr>");

  rowid++;

  lines=0;

  ; Selataan rividata läpi, ja kirjoitetaan yksi rivi
  ; joka tietueelle.

  setld=setlinesdata;
  while(setld!=NULL) {
    html_printf("<tr>");

    ; Tulostetaan riville kaikki kentät.

    setlf=setlinesfields;
    while(setlf!=NULL) {
      set_get_element("memberid",&comp,memberid,setlf->data); /* Get fieldname */
      set_get_element("memberlength",&comp,memberlength,setlf->data);
      set_get_element(memberid,&comp,value,setld->data); /* Get data */
      html_printf("<td>");
      html_printf("<input type=\"text\" value=\"%s\" name=\"%s%c%d\" size=\"%s\"  id=\"programbuttonlines\"",
                  value,memberid,rowidchar,rowid,memberlength);
      if(mode !=NULL && !strcmp(mode,"Display"))
        html_printf(" readonly");
      html_printf(">",
                  value,memberid,rowidchar,rowid,memberlength);
      html_printf("</td>");
      setlf=setlf->next;
    }
    html_printf("</tr>");

    rowid++;
    lines++;
    setld=setld->next;
  }

  ; Muuttelu tilassa tulostetaan tyhjiä rivejä, niin
  ; että kokonaisrivien määrä on 10 (esim 1 täytetty
  ; ja 9 tyhjää. Yli kymmenen rivin tapahtumissa oli
  ; ajatus että tulostetaan vain yksi ylimääräinen
  ; rivi. Kun se täytetään enter tuo uuden tyhjän rivin.
  ; sitä ei ole tässä.

  if(mode!=NULL && !strcmp(mode,"Change")) {
    while(lines<10) {
      html_printf("<tr>");
      setlf=setlinesfields;
      while(setlf!=NULL) {
        set_get_element("memberid",&comp,memberid,setlf->data); /* Get fieldname */
        set_get_element("memberlength",&comp,memberlength,setlf->data);
        html_printf("<td>");
        html_printf("<input type=\"text\" value=\"\" name=\"%s%c%d\" size=\"%s\"  id=\"programbuttonlines\">",
                    memberid,rowidchar,rowid,memberlength);
        html_printf("</td>");
        setlf=setlf->next;
      }
      html_printf("</tr>");

      rowid++;
      lines++;
    }
  }
}

int html_print_fieldname(struct set *set, char *sethf,char *mode)
{
  int comp;
  char memberid[64];

  ; Tulostetaan kentän nimi.

  set_get_element("memberid",&comp,memberid,sethf);
  html_printf("%s",memberid);
}

int html_print_fieldvalue(struct set *set, char *setf,char *mode)
{
  int comp;
  char memberid[64],memberlength[10],value[128];

  set_get_element("memberid",&comp,memberid,setf);
  set_get_element("memberlength",&comp,memberlength,setf);

  ; Tulostetaan kentän arvo. Tässä tehdään input lause
  ; syöttöluukkua varten. Lisäksi iffi näyttökentälle.

  *value='\0';
  if(set!=NULL && set->data!=NULL)
    set_get_element(memberid,&comp,value,set->data); /* Added data to header. JariK 20140216 */
  html_printf("<input type=\"text\" value=\"%s\" name=\"%s%c%d\" size=\"%s\" id=\"programbuttonheader\"",
      value,memberid,rowidchar,rowid,memberlength);
  if(mode !=NULL && !strcmp(mode,"Display"))
    html_printf(" readonly");
  html_printf("><br>");
}



Login

Haukkasin liian suuren palan edellisessä postissa, vaikka suurin osa koodista olikin jo olemassa. Aloitan uudestaan login palalla. Tästä puuttuu vielä kenttien pituuksien rajoituiksia, tämä on vasta ensimmäinen versio. Salasanat ovat vielä selväkielisiä.

Tätä samaa rutiinia käytetään käyttäjän tietojen tarkistuksessa, session id:n määrittelyssä ja login näytön tulostuksessa.

Lisäsin loppuun vielä html-parametrien käsittelyn. Se on pala, joka muuttaa html-näytön parametrit terttu muotoon. (katso userid ja password kentät logon näytöllä)

Kaikki oikeudet tietenkin pidätetään.

int get_session(char *sessionid)
{
 int c,ok,comp;
 char userid2[32],password2[32],password3[32],bones[64];
 struct set *sets,*setp,*setusers;
 time_t now;
 unsigned char timebuf[128];

 ok=0;
 *sessionid='\0';
 *userid2='\0';
 *password2='\0';
 sets=NULL;
 setusers=NULL;

 /* luetaan html datasta sessionid */
 set_get_element("sessionid",&comp,sessionid,htmlparams);

 /* Jos id löytyi, tarkistetaan istunnon olemassaolo */
 if(*sessionid!='\0') {
  if(exists_session_var("SESSION"))
    ok=1;
  else
    sessionid[0]='\0';
 }

 if(*sessionid=='\0') {
  /* luetaan näytöltä käyttäjätunnus ja käyttäjän syöttämä salasana */
  skk_fetch_sets(&sets,"'userid', 'password'",setscreen); /* ennen skk_fetch2() JariK 20150917 */

  /* Näitä pareja löytyy vain yksi (katso lopun näytön tulostus */
  if(skk_count_sets(sets)==1) {
   set_get_element("userid",&comp,userid2,sets->data);
   set_get_element("password",&comp,password2,sets->data);
  }

  if(*userid2!='\0' || password2!='\0') {
   /* Haetaan mätsäävä tunnus tertusta */
   sprintf(bones,"'userid'=\"%s\", 'password'",userid2);
   skk_fetch_sets(&setusers,bones,skk->first);

   if(skk_count_sets(setusers)==1) {
    /* Jos löytyi vaan yksi */
    set_get_element("password",&comp,password3,setusers->data);

    /* Verrataan salasanoja, jos samat,luodaan sessionid,
       talletetaan se ja userid */
    if(!strcmp(password2,password3)) {
     get_session_id(sessionid);
     set_session_var("session",sessionid);
     set_session_var("userid",userid);
     ok=1;
    }
   }
  }
 }

 /* Jos sessionidtä ei löytynyt/saatu aikaiseksi,
  * tulostetaan login näyttö käyttäjälle:
  */
 if(*sessionid=='\0') {
  html=html1;
  html[0]='\0';

  html_printf("HTTP/1.0 200 OK\r\n");
  html_printf("Location: \r\n");
  html_printf("Server: %s\r\n",programname);
  now = time(NULL);
  strftime(timebuf, sizeof(timebuf), HTMLTIMEFORMAT, gmtime(&now));

  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>Terttu</title>");
  html_printf("<meta name=\"author\" content=\"Jari Kuivaniemi\">");
  html_printf("<link href=\"terttu.css\" rel=\"stylesheet\" type=\"text/css\">");
  html_printf("</head>");
  html_printf("<body>");

  html_printf("<form action=\"logon\" method=\"post\">");
  html_printf("<input type=\"hidden\" name=\"sessionid\" value=\"%s\">",sessionid);
  html_printf("<table border=\"0\">");
  html_printf("<tr>");
  html_printf("Testiohjelmaan lokkaantuminen Userid=testi, password=testaus");
  html_printf("</tr>");
  html_printf("<tr>");
  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\">",userid2);
  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\">",password2);
  html_printf("</td>");

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

  html_printf("</table>");
  html_printf("</form>");

  html_printf("<br>");
  html_printf("Tämä on kesken olevan projektin \"lelusovellus\", eikä se ole tarkoitettu tuotteiden tilaamiseen.<br>");
  html_printf("<br>");
  html_printf("Saat listan toiminnoista leijumalla Terttu -sanan päällä.<br>");
  html_printf("<br>");
  html_printf("Raportti sovelluksen rakentamisesta löytyy osoitteesta moijari.com.<br>");
  html_printf("<br>");
  html_printf("<br>");
  html_printf("</body>");
 }
 return(ok);
}

html_get_screen_params(struct set **set)
{
 int id,first;
 char *p,*q;
 unsigned char name[64],value[64];
 struct set *setu;

 /* hae html parametrien sijaintiosoite */
 p=html_get_param2_addr();

 while(*p!='\0') {
  /* Hae parametrin nimi ja arvo */
  html_get_param2(&p,name,sizeof(name)-1,value,sizeof(value)-1);

  /* osoite parametrin nimen loppuun */
  q=name+strlen(name)-1;
  id=0;

  /* Haetaan parametrin nimen lopussa olevan numeron alku */
  if(isdigit(*q)) {
   while(isdigit(*(q-1))) {
   q--;
  }

  /* rivinumeron lisäksi parametrin nimen lopussa pitää olla '-'.
   * esimerkiksi 'userid-0'.
   */
  if(*(q-1)==rowidchar)
   *(q-1)='\0';
   /* rivin numero inttimuuttujaan */
   id=atoi(q);
  } else {
   id=-1;
  }

  if(id>=0) {
   /* Talletetaan kenttä oikealle riville. */
   set_add_element_id(set,id,name,value);
  }
  /* Ohitetaan parametrien välissä oleva '&' merkki. */
  if(*p=='&') {
   p++;
  }
 }
}

Html (asiakas) liittymä

Seuraavaksi pitäisi kirjoittaa käyttäjän html-liittymä uudestaan. Olen “haaveillut” tästä jo joulukuun puolesta välistä, koodin pitäisi syntyä, mutta katsotaan.

Ensimmäisessä istunnossa en vielä saanut aikaan kuin suurinpiirteisen vaiheluettelon. Jos et halua lukea puolivalmisteesta, voit odottaa perjantaihin. Toisaalta tämä koko projekti on luonteeltaan puolivalmiste, joten… Jatkossa vielä kesken oleva versio. jatkan joko tätä pohdintaa tai koodausta viimeistään perjantaina (olen allokoinut jatkon sinne).

Kaikki oikeudet tietenkin pidätetään.

Liittymä sisältää koko html toiminnon alusta loppuun, eikä se tallenna muuta kuin istuntomuuttujia (session variables) ja mahdollisesti save toiminnolla tallennettavan datan. Liittymä hakee tietoja tertusta. (ks. kyselykieli)

Liittymä voidaan jakaa seuraaviin toimintoihin (steps).

Tämä html-liittymä on siis ns yleisellä tasolla, eli kaikki asiakasohjelman kentät on määritelty terttu kantaan, eli ideassa asiakas määrittelee kentät itse. Samaa html liittymää toiveen mukaan käytetään kaikissa syöttöruuduissa.

Lue istuntoavain parametrista (sessionid)

Lue sovelluksen nimi html parametrista

Lue sovelluksen otsakekentät tertusta

Lue sovelluksen rivikentät tertusta

Lue edellinen syöttöruutu parametreista

Lue mode muuttujan tila istuntokohtaisista muuttujista

Tee vierasavain haut (tietovirta haut). ks tietovirta

Hae sovelluksen osasovellukset

Tulosta terttu valikko osasovelluksista

Tulosta nappulat

Ruudulla on tällä hetkellä nappulat: Submit, Reset, Fetch, Prev, Next, Save, Display, Change, Delete.

Submit

Submit nappula vastaa syöttölomakkeen perinteistä enter toimintoa, eli se lähettää lomakkeen tiedot palvelimelle, suorittaa kaikki nämä palvelimen toiminnot, eli tämän ketjun jota olemme tekemässä. Eli enterillä täytetään myös vierasavainkentät.

Reset

Fetch

Fetch toiminnolla haetaan otsakkeelle kirjoitetuilla hakutekijöillä ketju mätsääviä otsakkeita, ja valitaan ensimmäinen syöttökenttiin. Fetchin hakukriteerit (otsake) talletetaan session muuttujaan preville ja nextille.

Prev

Prev näppäimellä haetaan seuraava tietue Fetch toiminnon ketjusta, Ilmeisesti tuo fetch ketju haetaan uudestaan, ja haetaan seuraava hakemalla ensin tämä tietue, ja sitä edellinen valitaan.

Next

Nextillä taas haetaan seuraava tietue Fetch toiminnon ketjusta, Kuten edellisessä fetchin ketjusta haetaan tämä tietue ja valitaan uudeksi näytölle tulevaksi tietueeksi seuraava.

Tulosta html syöttöruutu