CGI's (Common Gateway Interface) ist einfach Ausgedrückt eine Schnittstelle, womit man Anwendungen für das Internet schreiben kann. Diese CGI-Anwendung laufen dabei auf einem (Web)Server (wie Beispielsweise dem Apache) und wird von einer HTML-Webseite angesteuert. Die Daten erhält diese CGI-Anwendung entweder von der HTML-Seite selbst (POST-Verfahren) oder direkt über die URL (GET-Verfahren). C C++ C/C++ CGI mit C CGI Skripte in C CGI mit C- CGI Skripte in C Kapitel 27: CGI in C

27.12. Ein Gästebuch            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Um alle Funktionen jetzt zu demonstrieren, soll ein einfaches Gästebuch erstellt werden. Ich denke so etwas kennt jeder. Folgende Dateien benötigen Sie dafür:

In diesem Beispiel wurde auch gleich die HTML-Datei, welche die Einträge anzeigt, zur Datenspeicherung verwendet, um das Programm ein wenig kürzer zu halten. Bei einem umfangreichen Gästebuch würden sich dafür eine extra Datei oder eine Datenbank anbieten.

27.12.1 Das HTML-Formular (guestbook.html)

<html>
<head>
<title>Eintrag ins Gästebuch</title>
</head>
<body text="#000000" bgcolor="#FFFFFF" link="#FF0000" alink="#FF0000" vlink="#FF0000">
<h3>Formular zum Eintragen ins Gästebuch</h3>

<pre>
<form action="http://localhost/cgi-bin/auswert.cgi" method=post>
<b>Name   : </b>
<input value="IhrName" name="Name" size="20">
<b>E-Mail : </b>
<input value="Mailadresse@mail" name="E-Mail" size="20">

<b>Bewertung dieser Webseite : </b>
<select name="Bewertung" size="3">
<option>Note 1</option><option>Note 2</option>
<option selected>Note 3</option><option>Note 4</option>
<option>Note 5</option><option>Note 6</option>
</select><br>

<b>Ihr Eintrag :</b>
<textarea name="textform" cols="32" rows="6">Textinhalt
</textarea>

<b>Ihre Programmierkenntnis : </b>
<input type="checkbox" name="programmieren" value="C/C++">C/C++
<input type="checkbox" name="programmieren" value="Perl">Perl
<input type="checkbox" name="programmieren" value="Visual Basic">
Visual Basic

<input type="reset"><input type=submit value="Abschicken">
</form>
</pre>
</body>
</html>

Abbildung 27.xx: Das HTML-Formular
Abbildung 27.xx: Das HTML-Formular

Der Ort, wo Sie diese Datei speichern, ist in der Regel auf dem lokalen System nicht so wichtig. Wichtiger ist, dass die Angaben zum Aufrufen der CGI-Anwendung stimmen:

<form action="http://localhost/cgi-bin/auswert.cgi" method=post>

27.12.2 Das CGI-Programm (auswert.cgi)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_PAARE 255

void print_location(char *);
char *getdata();
char *Strdup(const char *);
void hex2ascii(char *);
char convert(char *);
struct CGI_DATEN *erstellen(char *);
void printf_error(char *);

struct CGI_DATEN
{
   char *variable;
   char *wert;
   struct CGI_DATEN *next;
};

struct CGI_DATEN *ende = NULL;

/* Weiterleitung zu einer URL
 * url ist die URL, wohin Sie den User weiterleiten
 */
void print_location(char *url)
{
   printf("Location: %s\n", url);
   /* Für den Fall, dass ein alter Browser keine
      automatische Weiterleitung unterstützt */
   printf("Content-Type: text/html\n\n");
   printf("<html><head>\n");
   printf("<title>Weiterleitung zu %s</title>\n",url);
   printf("</head><body>\n");
   printf("Weiter gehts <a href=\"%s\">hier</a>",url);
   printf("</body></html>\n");
}

/*
 *  Funktion liest Daten in der POST- oder GET-Methode ein.
 *  Rückgabewert: String puffer mit den Daten
 *  bei Fehler  : NULL
*/
char *getdata()
{
   unsigned long size;
   char *puffer = NULL;
   char *request = getenv("REQUEST_METHOD");
   char *cont_len;
   char *cgi_string;

   /* Zuerst die Request-Methode überprüfen */
   if(  NULL == request )
      return NULL;
   else if( strcmp(request, "GET") == 0 )
      {
         /* Die Methode GET -> Query String abholen */
         cgi_string = getenv("QUERY_STRING");
         if( NULL == cgi_string )
            return NULL;
         else
            {
               puffer =(char *) Strdup(cgi_string);
               return puffer; /* Rückgabewert an den Aufrufer */
            }
      }
   else if( strcmp(request, "POST") == 0 )
      {
         /* Die Methode POST -> Länge des Strings
            ermitteln (CONTENT_LENGTH) */
         cont_len = getenv("CONTENT_LENGTH");
         if( NULL == cont_len)
            return NULL;
         else
            { /* String CONTENT_LENGTH in
                 unsigned long umwandeln */
              size = (unsigned long) atoi(cont_len);
              if(size <= 0)
                 return NULL; /* Keine Eingabe!?!? */
            }
         /* Jetzt lesen wir die Daten von stdin ein */
         puffer =(char *) malloc(size+1);
         if( NULL == puffer )
            return NULL;
         else
            {
               if( NULL == fgets(puffer, size+1, stdin) )
                  {
                     free(puffer);
                     return NULL;
                  }
               else  /* Rückgabewerte an dem Ausrufer */
                  return puffer;
            }
      }
   /* Weder GET-Methode noch die POST-Methode wurden verwendet */
   else
        return NULL;
}

/*
 *  Da die Funktion strdup() in der Headerdatei <string.h> keine
 *  ANSI C-Funktion ist, schreiben wir eine eigene
 */
char *Strdup(const char *str)
{
   char *p;
   if(NULL == str)
      return NULL;
   else
      {
         p = (char *)malloc(strlen(str)+1);
        if(NULL == p)
           return NULL;
        else
           strcpy(p, str);
      }
   return p;
}

/* Wandelt einzelne Hexzeichen (%xx) in ASCII-Zeichen
   und kodierte Leerzeichen (+) in echte Leerzeichen um */
void hex2ascii(char *str)
{
   int x,y;
   for(x=0,y=0; str[y] != '\0'; ++x,++y)
      {
         str[x] = str[y];
         /* Ein hexadezimales Zeichen? */
         if(str[x] == '%')
            {
               str[x] = convert(&str[y+1]);
               y+=2;
            }
         /* Ein Leerzeichen? */
         else if( str[x] == '+')
            str[x]=' ';
      }
   /* Geparsten String sauber terminieren */
   str[x] = '\0';
}

/* Funktion konvertiert einen String von zwei hexadezimalen
 * Zeichen und gibt das einzelne dafür stehende Zeichen zurück
 */
char convert(char *hex)
{
   char ascii;
   /* erster Hexawert */
   ascii =
   (hex[0] >= 'A' ? ((hex[0] & 0xdf) - 'A')+10 : (hex[0] - '0'));
   ascii <<= 4; /* Bitverschiebung schneller als ascii*=16 */
   /* zweiter Hexawert */
   ascii +=
   (hex[1] >= 'A' ? ((hex[1] & 0xdf) - 'A')+10 : (hex[1] - '0'));
   return ascii;
}

/* Liste aus Variable/Wert-Paaren erstellen
 * Rückgabewert: Anfangsadresse der Liste
 * Bei Fehler: NULL
 */
struct CGI_DATEN *erstellen(char *str)
{
   char* s;
   char* res;
   /* Irgendwo gibt es auch eine Grenze, hier sind
      MAX_PAARE erlaubt */
   char *paare[MAX_PAARE];
   struct CGI_DATEN *ptr_daten = NULL;
   struct CGI_DATEN *ptr_anfang = NULL;
   int i=0, j=0;
   /* Zuerst werden die Variablen/Werte-Paare anhand des Zeichens
      '&' getrennt, sofern es mehrere sind  */
    s=str;
    res=strtok(s,"&");
    while( res != NULL && i < MAX_PAARE)
       {
          /* Wert von res dynamisch in char **pair speichern */
          paare[i] = (char *)malloc(strlen(res)+1);
          if(paare[i] == NULL)
             return NULL;
          paare[i] = res;
          res=strtok(NULL,"&");
           i++;
       }
   /* Jetzt werden die Variablen von den Werten getrennt und
      an die Struktur CGI_DATEN übergeben  */
   while ( i > j )
      {/* Das erste Element? */
         if(ptr_anfang == NULL)
            {
               ptr_anfang =(struct CGI_DATEN *)
                             malloc(sizeof (struct CGI_DATEN *));
               if( ptr_anfang == NULL )
                  return NULL;
               res = strtok( paare[j], "=");
               if(res == NULL)
                  return NULL;
               ptr_anfang->variable = (char *)
                                     malloc(strlen(res)+1);
               if( ptr_anfang->variable == NULL )
                  return NULL;
               ptr_anfang->variable = res;
               res = strtok(NULL, "\0");
               if(res == NULL)
                  return NULL;
               ptr_anfang->wert =(char *) malloc(strlen(res)+1);
               if( ptr_anfang->wert == NULL )
                  return NULL;
               ptr_anfang->wert = res;
               //printf("%s %s<br>",ptr_daten->variable,
                                 // ptr_daten->wert);
               ptr_anfang->next =(struct CGI_DATEN *)
                             malloc(sizeof (struct CGI_DATEN *));
               if(ptr_anfang->next == NULL)
                  return NULL;
               ptr_daten = ptr_anfang->next;
               j++;
            }
         else
            {/* Die restlichen Elemente */
               res = strtok( paare[j], "=");
               if(res == NULL)
                  return NULL;
               ptr_daten->variable =(char *)
                                         malloc(strlen(res)+1);
               if(ptr_daten->variable == NULL)
                  return NULL;
               ptr_daten->variable = res;
               res = strtok(NULL, "\0");
               if(res == NULL)
                  return NULL;
               ptr_daten->wert =(char *) malloc(strlen(res)+1);
               if(ptr_daten->wert == NULL)
                  return NULL;
               ptr_daten->wert = res;
               //printf("%s %s<br>",ptr_daten->variable,
                                  // ptr_daten->wert);
               ptr_daten->next = (struct CGI_DATEN *)
                           malloc(sizeof (struct CGI_DATEN *));
               if( ptr_daten->next == NULL )
                  return NULL;
               ptr_daten = ptr_daten->next;
               j++;
            }
      }
   ende = ptr_daten;
   /* Anfangsadresse der Liste struct CGI_DATEN zurückgeben */
   return ptr_anfang;
}

void loeschen(struct CGI_DATEN *daten)
{
   struct CGI_DATEN *next = NULL;
   while(daten != ende)
      {
         next = daten->next;
         if(daten->variable != NULL)
            free(daten);
         daten=next;
      }
}

void printf_error(char *str)
{
   printf("Content-Type: text/html\n\n");
   printf("<html><head>\n");
   printf("<title>CGI-Fehlermeldung</title>\n");
   printf("</head><body>\n");
   printf("%s",str);
   printf("</body></html>\n");
}


int main()
{
   char *str;
   struct CGI_DATEN *cgi;
   struct CGI_DATEN *free_cgi;
   FILE *f;
   /* Eingabe einlesen */
   str = getdata();
   if(str == NULL)
      {
         printf_error("Fehler beim Einlesen von der "
                      "Formulareingabe");
         return 0;
      }
   /* Hexzeichen in ASCII-Zeichen konvertieren und aus '+'
      Leerzeichen machen */
   hex2ascii(str);
   /* Liste der Formualardaten erstellen */
   cgi = erstellen(str);
   free_cgi = cgi;
   if (cgi == NULL)
      {
        printf_error("Fehler beim Erstellen der "
                     "Variablen/Werte-Liste\n");
        return 0;
      }

   /* Datei zum Schreiben öffnen */
   /* Bitte den Pfad anpassen: bsp. unter SuSE Linux:
    * f = fopen("/srv/www/htdocs/gaeste.html", "r+");
    * und WICHTIG: Schreibrechte auf diese Datei vergeben
    */
   f = fopen("gaeste.html", "r+");
   if(f == NULL)
      {
         printf_error("Konnte Datei gaeste.html nicht zum "
                      "schreiben öffnen\n");
         return 0;
      }
   else
      {
         /*Stream vor </body></html> */
         fseek(f, -14, SEEK_END);
         fprintf(f, "<hr><br>"); /* Eine horizontale Linie */
         /* Name */
         if(cgi->wert != NULL)
            fprintf(f, "Name: %s E-Mail: ",cgi->wert);
         cgi = cgi->next;
         /* Mailadresse */
         if(cgi->wert != NULL)
            fprintf(f, "<a href=\"mailto:%s\">%s</a> ",
                                           cgi->wert,cgi->wert);
         cgi = cgi->next;
         /* Bewertung */
         if(cgi->wert != NULL)
            fprintf(f, "Bewertung : %s",cgi->wert);
         cgi = cgi->next;
         /* Eintrag */
         if( cgi->wert != NULL)
            {
               fprintf(f, "<p><b>Der Eintrag : </b>");
               fprintf(f, "%s",cgi->wert);
            }
         cgi = cgi->next;
         /* Programmierkenntnis(se) */
         if(cgi->wert != NULL)
            {
               fprintf(f, "<br><br>Programmierkenntnisse : ");
               while(cgi->wert != NULL  &&
                     strcmp(cgi->variable,"programmieren") == 0 )
                  {
                     fprintf(f, "%s ",cgi->wert);
                     cgi = cgi->next;
                  }
            }
         fprintf(f, "</p></body></html>");
         fclose(f);
      }

   /* Speicher wieder freigeben */
   loeschen(free_cgi);
   /* Auch hier müssen Sie die Pfadangabe ggf. anpassen */
   print_location("http://localhost/cgi-bin/gaeste.html");
   return 0;
}

Wichtig in diesem Listing ist der Pfad zum Öffnen der Datei gaeste.html

f = fopen("gaeste.html", "r+");

welchen Sie gegebenenfalls anpassen müssen. Bei dieser Angabe wird davon ausgegangen, dass sich das HTML-Dokument im cgi-bin-Verzeichnis befindet. Das funktioniert aber nicht auf jedem System. Beispielsweise bei SuSE 8.2 sieht der Pfad auf meinem System wie folgt aus:

f = fopen("/srv/www/htdocs/gaeste.html","r+"); 

Vorausgesetzt, die Datei gaeste.html befindet sich auch im entsprechenden Verzeichnis. Denken Sie auch an die Angabe und Änderung des Pfades, wenn Sie die CGI-Anwendung ins Web stellen.

Der zweite wichtige Pfad in dieser Anwendung ist der folgende:

print_location("http://localhost/cgi-bin/gaeste.html");

Auch hier muss der Speicherort der Datei gaeste.html richtig angegeben werden. Unter SuSE Linux sieht diese Angabe bei mir so aus:

print_location("http://localhost/gaeste.html"); 

Und das bedeutet, dass sich die Datei im htdocs-Verzeichnis des Webservers befindet. Dies sollte hier erwähnt werden, da ich aus Erfahrung weiß, dass dies eine der häufigsten Ursachen dafür ist, warum eine CGI-Anwendung nicht auf Anhieb funktioniert.

27.12.3 Das HTML-Gästebuch (gaeste.html)

<html>
<head>
  <title>Gästebuch</title>
</head>
<body>
<center><h1> Einträge im Gästebuch</h1></center><br>
</body></html>

Dies ist die Datei, in der die CGI-Anwendung auswert.cgi neue Daten schreibt. Daher muss der Speicherort der Datei mit den Angaben der Funktionen fopen() und print_location() im Programm auswert.cgi übereinstimmen. Außerdem benötigen Sie das Schreibrecht auf diese Datei, welches Sie mit chmod nachträglich vergeben können (chmod a+rwx gaeste.html).

27.12.4 Das Beispiel ausführen
In diesem Beispiel wird davon ausgegangen, dass sich die Dateien auswert.cgi und gaeste.html im cgi-bin-Verzeichnis befinden. Bitte passen Sie die Pfade im Listing auswert.c an Ihre Bedürfnisse an. Starten Sie als Erstes wieder Ihren Lieblingswebbrowser und öffnen Sie die HTML-Datei guestbook.html. Befindet sich diese beispielsweise im htdocs-Verzeichnis des Webservers, können Sie diese Datei mit folgender URL aufrufen:

http://localhost/guestbook.html  

Jetzt sollte die HTML-Seite erscheinen, und zwar mit dem Formular:

Abbildung 27.xx: Eingabeformular des Gästebuchs

Abbildung 27.xx: Eingabeformular des Gästebuchs

Wenn Sie alle Daten eingetragen haben, klicken Sie auf den Abschicken-Button, und das CGI-Programm auswert.cgi verrichtet seine Arbeit. Gleich darauf leitet Sie das CGI-Programm zur Seite gaeste.html weiter, wo Sie sich den neuen Eintrag ansehen können.

Abbildung 27.xx: Einträge im Gästebuch

Abbildung 27.xx: Einträge im Gästebuch

Zugegeben, das Layout entspricht nicht mehr dem heutigen Standard, aber darum geht es hierbei nicht.

27.13. Ausblick            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Sie wissen jetzt, wie Sie CGI-Anwendungen in C erstellen und anwenden können. Sollten Sie beabsichtigen, eine eigene CGI-Bibliothek zu schreiben, wissen Sie ja nun, wie Sie dabei vorgehen können. Haben Sie keine Lust, eine Bibliothek zu schreiben, greifen Sie einfach auf eine bereits vorhandene Bibliothek zurück. Ich kann Ihnen die cgic-Bibliothek von Thomas Boutell empfehlen. Diese entspricht zum einen dem ANSI C-Standard und ist zum anderen vielfach erprobt. Die Bibliothek können Sie mitsamt einer guten und ausführlichen Dokumentation von der Webseite http://www.boutell.com/cgic/ herunterladen.

Sie werden im nächsten Kapitel mit MySQL nochmals auf die CGI-Schnittstelle zurückgreifen und die hier erstellten Funktionen verwenden.

Weiter mit: Kapitel 28 : MySQL und C          zum Inhaltsverzeichnis