Blog der Heimetli Software AG

CGI mit C++

Heutzutage werden die Daten von Formularen meist mit einem Framework geschrieben das die Decodierung der POST-Daten übernimmt.

Wer kann, nimmt natürlich eines der eingangs erwähnten Frameworks, weil die einem viele Probleme abnehmen. Aber es gibt halt immer noch kleine Controller die zu wenig Resourcen für so ein Framework haben.

Nur noch wenige Leute wissen, dass man diese Daten eigentlich mit jeder beliebigen Sprache decodieren kann. Und wenn man im Internet sucht, findet man vielleicht noch etwas in perl und uralten C-Code.

Mit C++ 11 ist es möglich, den alten Code durch eine einfachere Funktion mit einem vernünftigen Interface zu ersetzen.

Das Interface

Weil die Funktion nur HTTP POST Requests mit dem Encoding application/x-www-form-urlencoded bearbeitet, kann das Interface sehr einfach sein:

multimap<string,string> GetCGIParameters()

Der Returnwert ist eine Multimap weil es mehrere Formularfelder mit gleichem Namen geben kann. Durch das Move-Assignment in C++ 11 kann die Multimap effizient an eine Variable von diesem Typ übergeben werden.

Der Code

Als erstes wird die Environment-Variable CONTENT_LENGTH gelesen. In diese Variable schreibt der Webserver wie viele Bytes das Programm lesen darf.

Dann liest die Funktion die angegebene Anzahl Bytes und decodiert sie mit Hilfe einer State-Machine.

Die Sonderzeichen für die State-Machine sind:

+Code für ein Space
%Start einer Escape-Sequenz mit zwei Hex-Buchstaben
&Trennt zwei Variablen
=Trennt Name und Wert der Variablen
/******************************************************************************/
/*                                                                            */
/*                                                         FILE: cgiparam.cpp */
/*                                                                            */
/*     Decodes the variables of a HTTP POST request                           */
/*     ============================================                           */
/*                                                                            */
/*     V1.00   19-DEC-2014     Te                                             */
/*                                                                            */
/******************************************************************************/
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <map>

using namespace std ;

enum STATE { TEXT, ESCAPE1, ESCAPE2 } ;

/******************************************/
 multimap<string,string> GetCGIParameters()
/******************************************/

{
   multimap<string,string> result ;

   const char *length = getenv( "CONTENT_LENGTH" ) ;

   if( length != NULL )
   {
      STATE  state = TEXT ;
      string value ;
      string key ;
      char   chr ;
      int    ch ;

      string *ps = &key ;
      for( int len = atoi(length); (len > 0) && ((ch = cin.get()) != EOF); --len )
      {
         switch( ch )
         {
          case '+': *ps  += ' ' ;
                    state = TEXT ;
                    break ;

          case '%': state = ESCAPE1 ;
                    break ;

          case '&': if( key.size() > 0 )
                       result.insert( pair<string,string>(key,value) ) ;

                    key.clear() ;
                    value.clear() ;
                    ps    = &key ;

                    state = TEXT ;
                    break ;

          case '=': ps    = &value ;
                    state = TEXT ;
                    break ;

          default:  switch( state )
                    {
                     case TEXT:    *ps += (char)ch ;
                                   break ;

                     case ESCAPE1: state = ESCAPE2 ;
                                   if( (ch >= '0') && (ch <= '9') )
                                      chr = (ch - '0') ;
                                   else if( (ch >= 'A') && (ch <= 'F') )
                                      chr = (ch - 'A') + 10 ;
                                   else if( (ch >= 'a') && (ch <= 'f') )
                                      chr = (ch - 'a') + 10 ;
                                   else
                                      state = TEXT ;
                                   break ;

                     case ESCAPE2: chr <<= 4 ;
                                   if( (ch >= '0') && (ch <= '9') )
                                   {
                                      chr |= (ch - '0') ;
                                      *ps += chr ;
                                   }
                                   else if( (ch >= 'A') && (ch <= 'F') )
                                   {
                                      chr |= (ch - 'A') + 10 ;
                                      *ps += chr ;
                                   }
                                   else if( (ch >= 'a') && (ch <= 'f') )
                                   {
                                      chr |= (ch - 'a') + 10 ;
                                      *ps += chr ;
                                   }
                                   state = TEXT ;
                                   break ;
                    }
                    break ;
         }
      }

      if( key.size() > 0 )
         result.insert( pair<string,string>(key,value) ) ;
   }

   return result ;
}

Testprogramm

Um die Funktion zu testen, habe ich auch ein Testprogramm geschrieben und es zusammen mit dem Build-Script in ein ZIP gepackt.

Hier können Sie cgiparam.cpp und Testprogramm herunterladen. Der Code wurde auf einem Raspberry PI mit einem Apache-Server gestestet.