Übergabe von Parametern über das CGI

von Thomas Salvador.

In diesem Artikel werde ich die Übergabe über das CGI darlegen. Anschließend werden wir uns eine eine allgemeine Routine dafür in Perl bauen. Diese Programmiersprache eignet sich aufgrund ihrer sehr mächtigen Zeichenkettenfunktionen meiner Meinung nach sehr gut zur CGI-Programmierung. Ich muss allerdings zugeben, dass ich Perl ganz allgemein gerne verwende.

Übergabearten

Prinzipiell gibt es beim Common Gateway Interface (CGI) zwei Übergabearten get und post.

get

Bei get werden die Daten als Zeichenkette in der Umgebungsvariablen QUERY_STRING geliefert. Den Namen können Sie sich gut daran merken, dass z.b. eine ?xxx-Anfrage a la

https://www.irgendwas.erde/cgi-bin/test.cgi?ok

eine Übertragung per QUERY_STRING (Query=Anfrage) ist und via get übertragen wird.

Die Länge eines solchen Strings ist beschränkt, was sein Einsatzgebiet stark einschränkt. Insbesondere hängt es dowohl vom Browser als auch vom Server ab, welche L&aauml;nge unterstützt ist.

So unterstüzen manche 2048 Zeichen, andere 8192 Zeichen, oder 65536 Zeichen, oder begrenzen gar nicht, wie es jüngere RFC eigentlich vorsehen.

Es eignet sich so nur für kürzere Daten, etwa wenige Parameter.

post

Bei post gibt es diese Beschränkung nicht. Daten werden hier nicht per Umgebungsvariablen geliefert, sondern als Datenstrom über die sog. Standardeingabe STDIN. Seine Länge wird in der in der Umgebungsvariablen CONTENT_LENGTH geliefert.

Sie können mit post also beliebig lange Daten übergeben.

Lesen der Eingabe

Aus Perl-Sicht ist das in beiden Fällen ein String, er wird nur unterschiedlich eingelesen. Die Art der Übergabe kann man in der Umgebungsvariablen REQUEST_METHOD erfragen. In einem ersten Schritt lesen wir also erstmal die Eingabe in einen Perl-String.

PseudoCode:

wenn (Methode get) {
  hole Eingabe aus umgebungsvariable
} ansonsten {
  hole Eingabe entsprechender Länge von der Standardeingabe
}

Perl:

if ($ENV{'REQUEST_METHOD'} eq 'GET') {
  $EINGABE = $ENV{'QUERY_STRING'};
} else {
  read(STDIN, $EINGABE, $ENV{'CONTENT_LENGTH'});
}

Struktur der Eingabe

Die Eingabe haben wir. Wie ist sie aufgebaut? Generell kann man natürlich alles an ein Programm senden, wir wollen aber davon ausgehen, dass es von einem Formular oder etwas Formular-ähnlichem stammt.

Ein Formular besteht aus einem oder mehreren Datenfeldern. Jedes Feld hat einen Namen und einen Wert.Der String setzt sich so zusammen, dass Name=Wert-Paare aneinangehängt werden, jeweils durch ein "&" voneinander getrennt.

name1=wert1&name2=wert2&name3=wert3&...&nameN=wertN

In dem String gibt es keine Leerzeichen, diese wurden durch "+" ersetzt.

Man sieht sofort, dass man aus HTML bekanntes Problem hat: Zeichen mit besonderer Bedeutung. Das kaufmännische Und steht für die Trennung von Feldern, Plus steht für das Leerzeichen. Der ein oder andere wird nun vielleicht vermuten, dass man dafür Ersatzdarstellung übertragen wird.

Das ist auch richtig, aber nur der Nebeneffekt davon, dass man über das CGI nur Buchstaben und Zahlen direkt, das Leerzeichen durch Plus ersetzt und alles andere kodiert übermittelt. Dabei wird der hexadezimale ASCII-Wert xy des Zeichens in der Form %xy übertragen. z.B. %2e für ASCII 46, also den Punkt.

Dekodieren der Eingabe

Man legt ein Array in Perl an, in dem jedes Feld ein Feld des Strings aufnimmt. Nun muss man jedes Feld betrachten und Pluszeichen durch Leerzeichen ersetzen.

Die einzelnen Name/Wert-Paare können nun wieder zerlegt und in ein sogenanntes assoziatives Feld gelegt werden. Ein solches Feld ist ähnlich dem Array, nur dass es einen beliebigen Schlüsselwert vom Typ Zeichenkette haben kann und zu diesem einem Wert speichert. z.B.

  • "hell" → "sonne"
  • "rot" → "rose"
  • ...

Man könnte es auch Hash nennen.

Bei Letzterem ist zu beachten, ob es einen solchen Schlüssel im Hash schon gibt. Ist das der Fall, so heißt dies, dass mehrere Paare den Namen haben.

Es gibt genau einen Fall bei dem das auftritt (abgesehen von dem, wenn Programme noch Fehler haben und Schrott senden :) Formular, die eine Checkboxen-Gruppe haben, bei der der Besucher mehr als eine Alternative gewählt hat.

Dann wird für jede selektierte Alternative der Gruppenname als Name und der Name der Alternative als Wert gesendet:

gruppenid=idselect1&gruppenid=idselect2&...&gruppenid=idselectN&

Der Einfachheit halber hängen wir den neuen Wert durch den ASCII-Wert 0 getrennt einfach hinten an.

PseudoCode:

Erzeuge Array fields durch Spaltung am Zeichen &
Für jedes Feld tue {
  ersetze Pluszeichen durch Leerzeichen
  erzeuge durch Spaltung am Gleichheitszeichen ein Paar (k,v)
  ersetze alle Zeichenfolgen %xy durch ihre ASCII-Zeichen,
  xy ist hexadezimal.
  wenn (es schon einen Parameter k gibt) {
    hänge den aktuellen Wert v durch ein ASCII-0
    getrennt an den bestehenden an
  } ansonsten {
    erzeuge einen Eintrag k => v im Hash PARAMS
  }
}

Perl:

<tt>fields = split(/[&;]/,$EINGABE);

foreach $i (0 .. $#fields) {
  $fields[$i] =~ s/\+/ /g;
  ($k, $v) = split(/=/,$fields[$i],2);
  $v =~ s/%(..)/pack("c",hex($1))/ge;
  if (defined($PARAMS{$k})) {
    $PARAMS{$k} = $PARAMS{$k} . "\000" . $v
  } else {
    $PARAMS{$k} = $v;
  }
}

Damit sind wir durch.

Die fertige Routine

Als Ergebnis unserer Mühen erhalten wir die Lösung als Unterroutine parseinput():

sub parseinput() {
  ## Syntax:   %P = &parseinput(); # liefere Parameter nach P.
  ##
  ####################################################################
  ## parseinput is copyright 2000 Thomas Salvador, brauchbar.de
  ## Das Routine ist frei verwendbar, solange diese Meldung
  ## unveraendert erhalten bleibt.
  ## Dokumentation unter https://brauchbar.de/wd/artikel/82.html

  if ($ENV{'REQUEST_METHOD'} eq 'GET') {
    my $EINGABE = $ENV{'QUERY_STRING'};
  } else {
    read(STDIN, $EINGABE, $ENV{'CONTENT_LENGTH'});
  }

  my </tt>fields = split(/[&;]/,$EINGABE);
  my $v;
  my $k;
  my %P=();

  foreach $i (0 .. $#fields) {
    $fields[$i] =~ s/\+/ /g;
    ($k, $v) = split(/=/,$fields[$i],2);
    $v =~ s/%(..)/pack("c",hex($1))/ge;
    if (defined($P{$k})) {
      $P{$k} = $P{$k} . "\000" . $v;
    } else {
      $P{$k} = $v;
    }
  }
  return %P;
}

Fertig zum Einbau in beliebige Programme.