Программа CONTROLS
Более сложная программа CGI называется CONTROLS и выполняет обработку данных, полученных из формы, которая была показана на рис. 7.2. Исходный текст документа HTML с этой формой представлен в листинге 7.1.
Программа CONTROLS отображает в динамически формируемом документе HTML метод, использованный для передачи (POST или GET), размер и тип данных, поступающих от формы. Принятые данные показываются как в исходном виде, так и после перекодировки. Кроме того, в документе HTML располагается список значений всех полей, определенных в форме (рис. 7.7).
Рис. 7.7. Фрагмент документа, сформированного динамически программой CONTROLS
Из рисунка видно, что навигатор прислал серверу WWW 135 байт информации. Так как при этом был использован метод POST, данные были направлены в стандартный поток ввода INPUT. Данные закодированы в кодировке URL, так как содержимое переменой среды CONTENT_TYPE равно application/x-www-form-urlencoded.
Обратите внимание на текстовое поле с именем text1. Все пробелы в соответствующей строке в кодировке URL заменены на символ “+”. Что же касается символов “&” и “,”, то они пришли в виде %26 и %2C. Функция перекодирования возвращает строку в исходный вид - “Sample of text1 &,.”.
Форма, показанная на рис. 7.2, имеет две кнопки, предназначенные для передачи данных серверу WWW. Это обычная кнопка и кнопка в виде графического изображения. Мы нажали графическую кнопку, поэтому от формы пришла информация о координатах курсора мыши в виде переменных с именами x и y.
Рассмотрим исходный текст программы CONTROLS (листинг 7.6).
Листинг 7.6. Файл chap7\controls\controls.c
// ===============================================
// Программа CGI controls.c
// Демонстрирует методы получения и обработки
// данных от форм, расположенных в документах HTML
//
// (C) Фролов А.В., 1997
// E-mail: frolov@glas.apc.org
// WWW: http://www.glasnet.ru/~frolov
// или
// http://www.dials.ccas.ru/frolov
// ===============================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Прототипы функций перекодировки
void DecodeStr(char *szString);
char DecodeHex(char *str);
// ------------------------------------------------
// Функция main
// Точка входа программы CGI
// ------------------------------------------------
void main(int argc, char *argv[])
{
int lSize;
FILE * fileReceived;
char * szMethod;
char * szQueryString;
char szBuf[8196];
char szSrcBuf[8196];
char * szPtr;
char * szParam;
// Вывод заголовка HTTP и разделительной строки
printf("Content-type: text/html\n\n");
// Вывод начального форагмента документа HTML,
// формируемого динамически
printf("<!DOCTYPE HTML PUBLIC"
" \"-//W3C//DTD HTML 3.2//EN\">");
printf("<HTML><HEAD><TITLE>XYZ Incorporation"
"</TITLE></HEAD><BODY BGCOLOR=#FFFFFF>");
// Определяем метод передачи данных
szMethod = getenv("REQUEST_METHOD");
// Обработка метода POST
if(!strcmp(szMethod, "POST"))
{
// Определяем размер данных, полученных от навигатора
// при передаче данных из полей формы
lSize = atoi(getenv("CONTENT_LENGTH"));
// Читаем эти данные в буфер szBuf из
// стандартного потока ввода STDIN
fread(szBuf, lSize, 1, stdin);
// Создаем файл, в который будут записаны
// принятые данные
fileReceived = fopen("received.dat", "w");
// Выполняем запись принятых данных
fwrite(szBuf, lSize, 1, fileReceived);
// Закрываем файл принятых данных
fclose(fileReceived);
// Отображаем значения некоторых переменных среды
printf("<H2>Переменные среды</H2>");
// Метод доступа
printf("REQUEST_METHOD = %s", getenv("REQUEST_METHOD"));
// Размер полученных данных в байтах
printf("<BR>CONTENT_LENGTH = %ld", lSize);
// Тип полученных данных
printf("<BR>CONTENT_TYPE = %s", getenv("CONTENT_TYPE"));
// Закрываем буфер данных двоичным нулем,
// превращая его таким образом в строку
szBuf[lSize] = '\0';
// Делаем копию принятых данных в буфер szSrcBuf
strcpy(szSrcBuf, szBuf);
// Отображаем принятые данные без обработки
printf("<H2>Принятые данные</H2>");
printf("<P>%s", szSrcBuf);
// Выполняем перекодировку принятых данных
DecodeStr(szSrcBuf);
// Отображаем результат перекодировки
printf("<H2>Данные после перекодировки</H2>");
printf("<P>%s", szSrcBuf);
// Выводим список значений полей формы
printf("<H2>Список значений полей</H2>");
// Дописываем в конец буфера принятых данных
// символ "&", который используется в качестве
// разделителя значений полей
szBuf[lSize] = '&';
szBuf[lSize + 1] = '\0';
// Цикл по полям формы
for(szParam = szBuf;;)
{
// Ищем очередной разделитель
szPtr = strchr(szParam, '&');
// Если он найден, раскодируем строку параметров
if(szPtr != NULL)
{
*szPtr = '\0';
DecodeStr(szParam);
// Выводим в документ значение параметра
printf("%s<BR>", szParam);
// Переходим к следующему параметру
szParam = szPtr + 1;
// Если достигнут конец буфера, завершаем цикл
if(szParam >= (szBuf + lSize))
break;
}
else
break;
}
// Выводим завершающий фрагмент документа HTML
printf("</BODY></HTML>");
return;
}
// Обработка метода GET
else if(!strcmp(szMethod, "GET"))
{
// Получаем данные, полученные от формы.
// При использовании метода GET эти данные
// передаются в переменной среды QUERY_STRING
szQueryString = getenv("QUERY_STRING");
// Записываем эти данные в выходной файл
fileReceived = fopen("received.dat", "w");
fwrite(szQueryString, strlen(szQueryString) + 1,
1, fileReceived);
fclose(fileReceived);
// Выводим в динамически формируемый документ HTML
// значения некоторых переменных среды
printf("<H2>Переменные среды</H2>");
// Метод передачи данных
printf("REQUEST_METHOD = %s", getenv("REQUEST_METHOD"));
// Полученные данные
printf("<BR>QUERY_STRING = %s", szQueryString);
// Копируем принятые данные в буфер szSrcBuf
strcpy(szSrcBuf, szQueryString);
// Отображаем принятые данные
printf("<H2>Принятые данные</H2>");
printf("<P>%s", szSrcBuf);
// Перекодируем данные и отображаем результат
// перекодировки
DecodeStr(szSrcBuf);
printf("<H2>Данные после перекодировки</H2>");
printf("<P>%s", szSrcBuf);
// Выводим в документ список значений полей формы
strcpy(szBuf, szQueryString);
printf("<H2>Список значений полей</H2>");
szBuf[strlen(szBuf)] = '&';
szBuf[strlen(szBuf) + 1] = '\0';
for(szParam = szBuf;;)
{
szPtr = strchr(szParam, '&');
if(szPtr != NULL)
{
*szPtr = '\0';
DecodeStr(szParam);
printf("%s<BR>", szParam);
szParam = szPtr + 1;
if(szParam >= (szBuf + lSize))
break;
}
else
break;
}
printf("</BODY></HTML>");
}
}
// ------------------------------------------------
// Функция DecodeStr
// Раскодирование строки из кодировки URL
// ------------------------------------------------
void DecodeStr(char *szString)
{
int src;
int dst;
char ch;
// Цикл по строке
for(src=0, dst=0; szString[src]; src++, dst++)
{
// Получаем очередной символ перекодируемой строки
ch = szString[src];
// Заменяем символ "+" на пробел
ch = (ch == '+') ? ' ' : ch;
// Сохраняем результат
szString[dst] = ch;
// Обработка шестнадцатеричных кодов вида "%xx"
if(ch == '%')
{
// Выполняем преобразование строки "%xx"
// в код символа
szString[dst] = DecodeHex(&szString[src + 1]);
src += 2;
}
}
// Закрываем строку двоичным нулем
szString[dst] = '\0';
}
// ------------------------------------------------
// Функция DecodeHex
// Раскодирование строки "%xx"
// ------------------------------------------------
char DecodeHex(char *str)
{
char ch;
// Обрабатываем старший разряд
if(str[0] >= 'A')
ch = ((str[0] & 0xdf) - 'A') + 10;
else
ch = str[0] - '0';
// Сдвигаем его влево на 4 бита
ch <<= 4;
// Обрабатываем младший разряд и складываем
// его со старшим
if(str[1] >= 'A')
ch += ((str[1] & 0xdf) - 'A') + 10;
else
ch += str[1] - '0';
// Возвращаем результат перекодировки
return ch;
}
Функция main программы CONTROLS вначале выводит в стандартный поток вывода STDOUT заголовок HTTP и начальные строки динамически формируемого документа HTML. Для вывода мы использовали функцию printf.
Далее функция main определяет использованный метод передачи данных, анализируя содержимое переменной среды REQUEST_METHOD. Это необходимо, так как при разных методах передачи необходимо использовать различные методы получения входных данных. Значение переменной среды программа получает при помощи функции getenv.
Если данные передаются методом POST, программа будет считывать их из стандартного потока ввода STDIN. Размер данных находится в переменной среды CONTENT_LENGTH. Соответствующая текстовая строка получается функцией getenv и преобразуется в численное значение функцией atoi.
Чтение данных из входного потока выполняется за один вызов функции fread:
fread(szBuf, lSize, 1, stdin);
Этой функции мы передаем адрес буфера для записи принятых данных, размер данных, количество буферов, которые нужно считать, и входной поток.
Программа CGI может сохранить принятые данные в файле для дальнейшей обработки. Наша программа создает в текущем каталоге файл с названием received.dat:
fileReceived = fopen("received.dat", "w");
fwrite(szBuf, lSize, 1, fileReceived);
fclose(fileReceived);
Текущим каталогом при запуске этой программы в среде сервера Microsoft Information Server будет каталог с загрузочным модулем программы CONTROLS. Заметим, что для того чтобы программа CGI могла создать файл в каталоге, необходимо соответствующим образом настроить права доступа. В последней главе нашей книги вы найдете информацию о том, как это можно сделать для сервера Microsoft Information Server.
После сохранения принятых данных в файле программа CONTROLS выводит в стандартный поток вывода содержимое некоторых переменных среды: REQUEST_METHOD, CONTENT_LENGTH и CONTENT_TYPE.
Далее наша программа выполняет перекодировку полученных данных из кодировки URL. Перед этим принятые данные копируются в буфер, где они будут обновляться по месту.
Для копирования мы закрываем буфер двоичным нулем, после чего копирование можно выполнить функцией копирования строки strcpy.
Перекодировка выполняется функцией DecodeStr,, определенной в нашем приложении. Эту функцию мы рассмотрим позже.
После перекодирования результат будет находиться в буфере szSrcBuf, откуда он и берется для отображения.
На завершающем этапе обработки данных, полученных от формы, программа CONTROLS записывает в выходной документ HTML значения отдельных полей. Напомним, что эти значения имеют формат &<Имя_поля>=<Значение>, при этом символ “&” используется как разделитель.
Наша программа закрывает исходный буфер с принятыми данными дополнительным символом “&” (для простоты сканирования), после чего запускает цикл по полям формы.
В этом цикле во входной строке с помощью функции strchr ищется символ разделитель. Если этот символ найден, он заменяется на двоичный нуль, после чего полученная текстовая строка значения параметра перекодируется функцией DecodeStr и выводится в выходной поток STDOUT.
Цикл завершается тогда, когда в процессе сканирования указатель текущей позиции выходит за границы буфера данных.
В конце программа CONTROLS закрывает документ HTML, записывая в него команды </BODY> и </HTML>.
Если данные передаются в программу CONTROLS методом GET, входные данные находятся в переменной среды QUERY_STRING, которую мы получаем следующим образом:
szQueryString = getenv("QUERY_STRING");
Обработка принятых данных выполняется аналогично тому, как это делается при методе POST. Разница заключается лишь в том, что в документе мы отображаем содержимое только переменных REQUEST_METHOD и QUERY_STRING.
Теперь займемся перекодировкой принятых данных, которая выполняется функцией DecodeStr.
Эта функция сканирует входную строку, заменяя символы “+” на пробелы. Если в перекодируемой строке встречается комбинация символов вида "%xx", она заменяются на однобайтовый код соответствующего символа с помощью функции DecodeHex.
Функция DecodeHex комбинирует значение кода символа из старшего и младшего разряда преобразуемой комбинации символов.