23.12 Ein Gästebuch 

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:
- das HTML-Formular, in das sich der Besucher einträgt
- die CGI-Anwendung, die diesen Eintrag einliest, auswertet und bearbeitet
- die HTML-Datei, mit der das Gästebuch angezeigt wird
In diesem Beispiel wurde auch gleich die HTML-Datei, die 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.
23.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>
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>
Abbildung 23.21 Das HTML-Formular
23.12.2 Das CGI-Programm (»auswert.cgi«) 

/* auswert.c */ #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(void) { 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 den Aufrufer */ return puffer; } } /* Weder die 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_anfang->variable, ptr_anfang->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(void) { 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 EXIT_FAILURE; } /* 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 EXIT_FAILURE; } /* Datei zum Schreiben öffnen */ /* Bitte den Pfad anpassen: beispielsweise 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 oeffnen\n"); return EXIT_FAILURE; } 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/gaeste.html"); return EXIT_SUCCESS; }
Wichtig in diesem Listing ist der Pfad zum Öffnen der Datei gaeste.html
f = fopen("gaeste.html", "r+");
den 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/gaeste.html");
Auch hier muss der Speicherort der Datei gaeste.html richtig angegeben werden. Im Beispiel bedeutet dies, 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.
23.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, das Sie mit chmod nachträglich vergeben können (chmod a+rwx gaeste.html).
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:
Jetzt sollte die HTML-Seite erscheinen, und zwar mit dem Formular aus Abbildung 23.22.
Abbildung 23.22 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 das CGI-Programm Sie zur Seite gaeste.html weiter, wo Sie sich den neuen Eintrag ansehen können.
Zugegeben, das Layout entspricht nicht mehr dem heutigen Standard, aber darum geht es hierbei nicht.
Abbildung 23.23 Einträge im Gästebuch
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.