Шаг 3 - Структура CGI программы.

Как и любая программа, программа CGI шлюза должна получить данные, обработать их и вывести результат работы в наглядной форме.

Как получать данные ?

Весь процесс получения данных от веб-сервера можно представить следуюшим образом:
  1. Получить все необходимые переменные окружения. Наиболее важной на данном этапе является REQUEST_METHOD.
  2. Получить данные от сервера в зависимости от метода передачи:
      Если (REQUEST_METHOD="GET") то
         Взять данные из переменной окружения QUERY_STRING
      Иначе
         Если (REQUEST_METHOD="POST") то
             Проанализировать переменную QUERY_STRING
             Получить длинну данных из CONTENT_LENGTH
             Если (CONTENT_LENGTH>0) то
                 Считать CONTENT_LENGTH байт из sdtin как данные.
         Иначе
             Выдать сообщение об ошибке и выйти.
    
  3. Декодировать все полученные данные и, если надо, разбить их на пары "имя=значение" в удобную для программы форму.
С анализом переменной REQUEST_METHOD думаю все ясно, а вот ,что значит в методе POST проанализировать QUERY_STRING, наверно, не все ясно. При передаче методом POST сервер посылает данные через стандартный поток ввода, но это не значит, что пользователь не пользовался URL'ом для передачи данных. Примером может служить многопользовательский шлюз, в котором для идентификации пользователя используется URL, а для передачи данных stdin:
  http://.../cgi-bin/guestbook.cgi?user=bob&rec=0
В этом случае шлюзу гостевой книги (guestbook.cgi) сообщается два параметра user и rec, с помощью которых она может узнать "куда записывать" или "как обрабатывать" данные поступающие через поток ввода.

Считывание данных через поток sdtin должно осуществляться в динамическую память, или же во временный файл, если размер памяти ограничен или данные слишком велики для полного размещения в ОЗУ. С чем это связано ? Это связано с тем, что при использовании статичестких буферов может произойти его переполнение.

Пример:

  char cgi_data[1000];
  ...
  long content_length=atol(getenv("CONTENT_LENGTH"));
  fread(cgi_data,content_length,1,stdin);
  ...
Надеюсь все сразу видно :-)). Если content_length>1000, то произойдет переполнение cgi_data. Переполнение буферов это излюбленный метод атаки хакеров. Вместо этого лучше выделять память динамически:
  char *cgi_data;
  ...
  long content_length=atol(getenv("CONTENT_LENGTH"));
  cgi_data=(char *)malloc(content_length);
  if (cgi_data!=NULL)
     fread(cgi_data,content_length,1,stdin);
  ...
После получения данных от сервера их надо еще декодировать. Можно это сделать сразу, а можно по мере надобности. Если Вы будете это делать сразу, то Вам также придется их разбить на куски, так как при декодировании могут появиться лишние знаки "&" и "=", которые больше не позволят вам отделять пары "имя=значение" друг от друга.

Вот пример процедуры, которая декодирует данные из буфера:

/* Возвращает верхний регистр символа*/
char upperchar(char ch)
{
  if ((ch>='a') && (ch<='z'))
  {
      ch='A'+(ch - 'a');
      return ch;
   }
  else return ch;
};

/* Переводит из Hex в Dec*/
char gethex(char ch)
{
  ch=upperchar(ch);
  if ((ch>='0')&&(ch<= '9')) return (ch-'0');
  if ((ch>='A')&&( ch<='F')) return (ch-'A'+10);
};

/* 
  Ищет и возвращает параметр с именем name, в buffer.
  Если параметр name не найден, возвращает NULL.
  
Пример : message = getparam(post_buffer,"message=");

Замечание : символ "=" после имени параметра не удаляется
  и входит в возвращаемый результат, поэтому рекомендуется
  искать параметр вместе с символом "=".
 */
 
char *getparam(char *buffer,char *name)
{
 if (buffer==NULL) return NULL;

 char *pos;
 long leng=512,i=0,j=0;
 char h1,h2,Hex;

 char *p=(char *)malloc(leng);
 pos=strstr(buffer,name);
 if (pos == NULL) return NULL;

 if ((pos!=buffer) && (*(pos-1)!='&')) return NULL; 

 pos+=strlen(name);
 
 while ( (*(pos+i)!='&')&&( *(pos+i)!='\0' ))
 {
  if ( *(pos+i)=='%' )
  {
    i++;
    h1=gethex(*(pos+i));
    i++;
    h2=gethex(*(pos+i));
    h1=h1<<4;
    *(p+j)=h1+h2;
  }
  else
  {
    if (*(pos+i)!='+') *(p+j)=*(pos+i);
     else *(p+j)=' ';
  };
  i++;
  j++;
  if (j >= leng) p=(char*)realloc(p,leng+20);
  leng+=20;
 };
 if (j < leng) p=(char*)realloc(p,j+1);
      
 *(p+j)='\0';
 return p;
};

Теперь используя функцию getparam Вы сможете в любое время получить какой-нибудь параметр. Конечно эта процедура еще далека от совершенства, так как использует стандартный realloc и не обрабатывает ошибку нехватки памяти, но всеже ее можно использовать для данных не особо больших размеров.

Кстати надо еще сказать, что нецелесообразно размещать поступающие данные размером >64 Кб. в памяти, если, например, скрипт предназначен для закачки файлов, то можно (а вернее нужно) напрямую организовать запись в файл, иначе файл размером в пару мегабайт ваш скрипт не обработает :-)).


Предыдущий Шаг | Следующий Шаг | Оглавление

By Dron.