Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 3. CGI изнутри

51

Передача параметров методом POST

В отличие от метода GET, здесь параметры передаются сценарию не через переменные окружения, а через стандартный поток ввода (в Си он называется stdin). То есть программа должна работать так, будто никакого сервера не существует, а она читает данные, которые вводит пользователь с клавиатуры. (Конечно, на самом деле никакой клавиатуры нет и быть не может, а заправляет всем сервер, который "изображает из себя" клавиатуру.)

Следует заметить очень важную деталь: то, что был использован метод POST, вовсе не означает, что не был применен также и метод GET. Иными словами, метод POST подразумевает также возможность передачи данных через URL- строку. Эти данные будут, как обычно, помещены в переменную окружения

QUERY_STRING.

Но как же узнать, сколько именно данных переслал пользователь методом POST? До каких пор нам читать входной поток? Для этого служит переменная окружения CONTENT_LENGTH, в которой хранится строка с десятичным представлением числа переданных байтов данных (разумеется, перед использованием ее надо перевести в обычное число).

Модифицируем предыдущий пример так, чтобы он принимал POST-данные, а также выводил и GET-информацию, если она задана:

Листинг 3.3. Получение данных POST

#include <stdio.h> #include <stdlib.h>

void main(void) {

//извлекаем значения переменных окружения char *RemoteAddr = getenv("REMOTE_ADDR");

char *ContentLength = getenv("CONTENT_LENGTH"); char *QueryString = getenv("QUERY_STRING");

//вычисляем длину данных — переводим строку в число int NumBytes = atoi(ContentLength);

//выделяем в свободной памяти буфер нужного размера char *Data = (char *)malloc(NumBytes + 1);

//читаем данные из стандартного потока ввода fread(Data, 1, NumBytes, stdin);

//добавляем нулевой код в конец строки

//(в Си нулевой код сигнализирует о конце строки) Data[NumBytes] = 0;

52

Часть I. Основы Web-программирования

// выводим заголовок printf("Content-type: text/html\n\n");

// выводим документ printf("<html><body>");

printf("<h1>Здравствуйте. Мы знаем о вас все!</h1>"); printf("Ваш IP-адрес: %s<br>",RemoteAddr); printf("Количество байтов данных: %d<br>",NumBytes); printf("Вот параметры, которые Вы указали: %s<br>",Data); printf("А вот то, что мы получили через URL: %s",

QueryString);

printf("</body></html>");

}

Странслируем этот сценарий и запишем то, что получилось, под именем script.cgi в каталог, видимый извне как /cgi-bin/. Откроем в браузере следующий HTMLфайл с формой:

Листинг 3.4. POST-форма

<html><body>

<form action=/cgi-bin/script.cgi?param=value method=post> Name1: <input type=text name="name1"><br>

Name2: <input type=text name="name2"><br> <input type=submit value="Запустить сценарий!"> </form>

</body></html>

Теперь, если набрать в полях ввода какой-нибудь текст и нажать кнопку, получим HTML-страницу, сгенерированную сценарием, например, следующего содержания:

Здравствуйте. Мы знаем о вас все!

Ваш IP-адрес: 136.234.54.2

Количество байтов данных: 23

Вот параметры, которые Вы указали: name1=Vasya&name2=Petya А вот то, что мы получили через URL: param=value

Как можно заметить, обработка метода POST устроена сложнее, чем GET. Тем не менее, метод POST используется чаще, особенно если нужно передавать большие объемы данных или "закачивать" файл на сервер (эта возможность также поддерживается протоколом HTTP и HTML).

Глава 3. CGI изнутри

53

Расшифровка URL-кодированных данных

Если бы в предыдущем примере мы ввели параметры, содержащие, например, буквы кириллицы, то сценарию они бы поступили не в "нормальном" виде, а в URLзакодированном. Пожалуй, ни один сценарий не обходится без функции расшифровки URL-кодированных данных. И это совсем не удивительно. Радует только то, что такую функцию нужно написать один раз, а дальше можно пользоваться ей по мере необходимости.

Как уже упоминалось, кодирование заключается в том, что некоторые неалфавитноцифровые символы (в том числе и "русские" буквы, которые тоже считаются неалфавитными) преобразуются в форму %XX, где XX — код символа в шестнадцатеричной системе счисления. Далее представлена функция на Си, которая умеет декодировать подобные данные и приводить их к нормальному представлению.

Мы не можем сначала все данные (например, полученные из стандартного по- тока ввода) декодировать, а уж потом работать с ними (в частности, разбивать по месту вхождения символов & и =). Действительно, вдруг после перекоди- ровки появятся символы & и =, которые могут быть введены пользователем? Как мы тогда узнаем, разделяют ли они параметры или просто набраны с кла- виатуры? Очевидно, никак. Поэтому такой способ нам не подходит, и придется работать с каждым значением отдельно, уже после разделения строки на час- ти.

Итак, приходим к следующему алгоритму: сначала разбиваем строку параметров на блоки (параметр=значение), затем из каждого блока выделяем имя параметра и его значение (обособленные символом =), а уж потом для них вызываем функцию перекодировки, приведенную ниже:

Листинг 3.5. Функция URL-декодирования

//Функция преобразует строку данных st в нормальное представление.

//Результат помещается в ту же строку, что была передана в параметрах. void UrlDecode(char *st) {

char *p=st; // указывает на текущий символ строки char hex[3]; // временный буфер для хранения %XX

int code;

// преобразованный код

//запускаем цикл, пока не кончится строка (то есть, пока не

//появится символ с кодом 0, см. ниже)

do {

// Если это %-код ...

if(*st == '%') { // тогда копируем его во временный буфер hex[0]=*(++st); hex[1]=*(++st); hex[2]=0;

54

Часть I. Основы Web-программирования

//переводим его в число sscanf(hex,"%X",&code);

//и записываем обратно в строку

*p++=(char)code;

//указатель p всегда отмечает то место в строке, в которое

//будет помещен очередной декодированный символ

}

//иначе, если это "+", то заменяем его на " " else if(*st=='+') *p++=' ';

//а если не то, ни другое — оставляем как есть else *p++=*st;

}while(*st++!=0); // пока не найдем нулевой код

}

Функция основана на том свойстве, что длина декодированных данных всегда меньше, чем кодированных, а значит, всегда можно поместить результат в ту же строку, не опасаясь ее переполнения. Конечно, примененный алгоритм далеко не оптимален, т. к. использует довольно медлительную функцию sscanf() для перекодирования каждого символа. Тем не менее, это самое простое, что можно придумать, вот почему я так и сделал.

Итак, теперь мы можем слегка модифицировать предыдущий пример сценария, заставив его перед выводом данных декодировать их. Попробуем написать это так:

Листинг 3.6. Получение POST-данных с URL-декодированием

#include <stdio.h> #include <stdlib.h>

void main(void) {

//получаем значения переменных окружения char *RemoteAddr = getenv("REMOTE_ADDR");

char *ContentLength = getenv("CONTENT_LENGTH");

//выделяем память для буфера QUERY_STRING

char *QueryString = malloc(strlen(getenv("QUERY_STRING")) + 1);

//копируем QUERY_STRING в созданный буфер strcpy(QueryString, getenv("QUERY_STRING"));

//декодируем QUERY_STRING UrlDecode(QueryString);

//вычисляем количество байтов данных — переводим строку в число int NumBytes = atoi(ContentLength);

Глава 3. CGI изнутри

55

//выделяем в свободной памяти буфер нужного размера char *Data = (char*)malloc(NumBytes + 1);

//читаем данные из стандартного потока ввода fread(Data, 1, NumBytes, stdin);

//добавляем нулевой код в конец строки

//(в Си нулевой код сигнализирует о конце строки) Data[NumBytes] = 0;

//декодируем данные (хоть это и не совсем осмысленно, но выполняем

//сразу для всех POST-данных, не разбивая их на параметры) UrlDecode(Data);

//выводим заголовок

printf("Content-type: text/html\n\n"); // выводим документ

printf("<html><body>");

printf("<h1>Здравствуйте. Мы знаем о Вас все!</h1>"); printf("Ваш IP-адрес: %s<br>",RemoteAddr); printf("Количество байтов данных: %d<br>", NumBytes); printf("Вот параметры, которые Вы указали: %s<br>",Data); printf("А вот то, что мы получили через URL: %s",

QueryString);

printf("</body></html>");

}

Обратите внимание на строки, выделенные жирным шрифтом. Теперь мы используем промежуточный буфер для хранения QUERY_STRING. Зачем? Попробуем поставить все на место, т. е. не задействовать промежуточный буфер, а работать с переменной окружения напрямую, как это было в листинге 3.5. Тогда в одних операционных системах этот код будет работать прекрасно, а в других — генерировать ошибку общей защиты, что приведет к немедленному завершению работы сценария. В чем же дело? Очень просто: переменная QueryString ссылается на значение переменной окружения QUERY_STRING, которая расположена в системной области памяти, а значит, доступна только для чтения. В то же время функция UrlDecode(), как я уже замечал, помещает результат своей работы в ту же область памяти, где находится ее параметр, что и вызывает ошибку.

Чтобы избавиться от указанного недостатка, мы и копируем значение переменной окружения QUERY_STRING в область памяти, доступной сценарию для записи — например, в какой-нибудь буфер, а потом уже преобразовываем его. Что и было сделано в последнем сценарии.

56

Часть I. Основы Web-программирования

Несколько однообразно и запутанно, не так ли? Да, пожалуй. Но, как говорится, "это даже хорошо, что пока нам плохо" — тем больше будет причин предпочитать PHP другим языкам программирования (так как в PHP эти проблемы изжиты как класс).

Формы

До сих пор из всех полей формы мы рассматривали только текстовые поля и кнопки отправки (типа submit). Давайте теперь поглядим, в каком виде приходят данные и от других элементов формы (а их существует довольно много).

Все элементы формы по именам соответствующих им тэгов делятся на 3 категории:

r<input...>

r<textarea...>...</textarea>

r<select...><option...>...</option>...</select>

Каждый из этих тэгов, конечно, может иметь имя. Ранее уже упоминалось, что пары имя=значение перед тем, как отправятся сценарию, будут разделены в строке параметров символом &. Кроме того, следует учитывать, что для тех компонентов формы, у тэгов которых не задан параметр name, соответствующая строка имя=значение передана не будет. Это ограничение введено для того, чтобы можно было в форме определять служебные элементы, которые не будут посылаться сценарию. Например, в их число входят кнопки (подтверждения отправки или обычные, используемые при программировании на JavaScript) и т. д. Так, создадим форму:

<form action=script.cgi>

... какие-то поля ...

<input type=submit value="Go!"> </form>

Несмотря на то, что кнопка Go! формально является полем ввода, ее данные не будут переданы сценарию, поскольку у нее отсутствует параметр name.

Чаще все же бывает удобно давать имена таким кнопкам. Например, для того, чтобы определить, каким образом был запущен сценарий — путем нажатия на кнопку или как-то еще (например, просто набором его URL в браузере). Создадим следующую форму:

<form action=script.cgi>

<input type=submit name="submit" value="Go!"> </form>

После запуска такой формы и нажатия в ней кнопки Go! сценарию среди прочих параметров будет передана строка submit=Go!. Вернувшись к примеру из предыдущей главы, мы теперь легко сможем определить, был ли сценарий выполнен из формы

Глава 3. CGI изнутри

57

или же простым указанием его URL (для этого достаточно проанализировать командную строку сценария и определить, присутствует ли в ней атрибут submit).

В принципе, все тэги, за исключением <select>, с точки зрения сценария выглядят одинаково — как один они генерируют строки вида имя=значение, где имя — то, что задано в атрибуте name, а значение — либо текст, введенный пользователем, либо содержимое атрибута value (например, так происходит у независимых и зависимых переключателей, которые мы вскоре рассмотрим).

Тэг <input> — различные поля ввода

Существует много разновидностей этого тэга, отличающихся параметром type. Перечислю наиболее употребительные из них. В квадратных скобках я буду указывать необязательные параметры, а также параметры, отсутствие которых иногда имеет смысл (будем считать, что параметр name является обязательным, хотя это и не так в силу вышеизложенных рассуждений). Ни в коем случае не набирайте эти квадратные скобки!

Для удобства я расположу каждый параметр тэга на отдельной строке. И хотя стандарт HTML это не запрещает, настоятельно рекомендую вам стараться в своих формах избегать такого синтаксиса. Не разбивайте тэги форм на несколько строк, это значительно снижает читабельность кода страницы.

Текстовое поле (text)

<input type=text name=имя [value=значение] [size=размер] [maxlen=число]

>

Создает поле ввода текста размером примерно в size знакомест и максимально допустимой длиной maxlen символов (то есть пользователь сможет ввести в нем не больше этого количества символов).

Не советую, тем не менее, в программе на Си полагаться, что придет не боль-

ше maxlen символов и выделять для их получения буфер фиксированного размера. Дело в том, что злоумышленник вполне может запустить ваш сцена- рий в обход стандартной формы (содержащей "правильный" тэг <input>) и задать большой объем данных, чтобы этот буфер переполнить известный прием взлома недобросовестно написанных программ.

Если задано значение атрибута value, то в текстовом поле будет изначально отображена указанная строка.

58

Часть I. Основы Web-программирования

Поле ввода пароля (password)

<input type=password name=имя [value=значение] [size=размер] [maxlen=число]

>

Полностью аналогичен тэгу <input type=text>, за исключением того, что символы, набираемые пользователем, не будут отображаться на экране. Это удобно, если нужно запросить какой-то пароль. Кстати, если в качестве маски задается значение параметра value, все будет в порядке, однако, посмотрев исходный HTML-текст страницы в браузере, можно увидеть, что он (браузер) это значение не показывает (непосредственно на странице). Сделано это, видимо, из соображений безопасности, хотя, конечно же, злоумышленник легко преодолеет такую защиту, если вы попытаетесь скрыть с ее помощью что-то важное.

Скрытое текстовое поле (hidden)

<input type=hidden name=имя value=значение

>

Создает неотображаемое (скрытое) поле. Такой объект нужен исключительно для того, чтобы передать сценарию какую-то служебную информацию, до которой пользователю нет дела, — например, параметры настройки.

Пусть, например, у нас имеется многоцелевой CGI-сценарий, который умеет принимать данные пользователя и отправлять их как почтовое сообщение. Поскольку мы бы не хотели фиксировать E-mail получателя жестко, но в то же время и не стремимся, чтобы пользователь мог его менять перед отправкой формы, оформим соответствующий тэг в виде скрытого поля:

<form action=/cgi/sendmail.cgi method=post>

<input type=hidden name=email value="admin.microsoft.com."> <h2>Пошлите сообщение администратору:</h2>

<input type=text name="text">

<input type=submit name=doSend value="Отослать"> </form>

Я подразумеваю, что сценарий анализирует свои входные параметры и посылает текст из параметра text по адресу email. А вот еще один пример использования этого сценария, но уже без скрытого поля. Сравните:

<form action=/cgi/sendmail.cgi method=post> <h2>Пошлите сообщение другу:</h2>

Глава 3. CGI изнутри

59

Его E-mail: <input type=text name=email><br> Текст: <input type=text name="text"><br>

<input type=submit name=doSend value="Отослать"> </form>

Итак, мы задействовали один и тот же сценарий для нескольких разных целей. Еще раз напоминаю, что для сценария безразлично, получает он данные из обычного текстового или же из скрытого поля — в любом случае данные выглядят одинаково.

Часто скрытое поле используют для индикации того, что сценарий запущен в результате нажатия кнопки в форме, а не простым набором его URL в строке адреса браузера. Тем не менее, это, как уже говорилось, довольно плохой способ — лучше применять именованные кнопки submit.

В некоторых случаях именованные кнопки submit не помогают, и приходится пользоваться скрытым полем для индикации запуска сценария из формы. Про- исходит это в случае, если форма очень проста и состоит, например, всего из двух элементов поля ввода текста и кнопки submit (пусть даже и имено- ванной). Практически все браузеры в такой ситуации позволяют пользователю просто нажать <Enter> для отправки формы, а не возиться с нажатием на submit-кнопку. При этом разумеется, данные кнопки не посылаются на сервер. Вот тогда-то нас и выручит hidden-поле, например, с именем submit: если его значение установлено, то сценарий понимает, что пользователь ввел какие-то данные, в противном случае сценарий был запущен впервые путем набора его URL или перехода по гиперссылке.

Независимый переключатель (checkbox)

<input type=checkbox name=имя value=значение

[checked]

>

Этот тэг генерирует независимый переключатель (или флажок), который может быть либо установлен, либо сброшен (квадратик с галочкой внутри или пустой соответственно). Если пользователь установил этот элемент, прежде чем нажать кнопку доставки, сценарию поступит строка имя=значение, в противном случае не придет ни- чего, будто нашего поля и не существует вовсе. Если задан атрибут checked, то переключатель будет изначально установленным, иначе — изначально сброшенным.

Зависимый переключатель (radio)

<input type=radio name=имя value=значение

60

Часть I. Основы Web-программирования

[checked]

>

Включение в форму этого тэга вызывает появление на ней зависимого переключателя (или радиокнопки). Зависимый переключатель — это элемент управления, который, подобно независимому переключателю, может находиться в одном из двух состояний. С тем отличием, что если флажки не связаны друг с другом, то только одна радиокнопка из группы может быть выбрана в текущий момент. Конечно, чаще всего определяются несколько групп радиокнопок, независимых друг от друга. Наша кнопка будет действовать сообща с другими, имеющими то же значение атрибута name — иными словами, то же имя. Отсюда вытекает, что, в отличие от всех других элементов формы, две радиокнопки довольно часто имеют одинаковые имена. Если пользователь установит какую-то кнопку, сценарию будет передана строка имя=значение, причем значение будет тем, которое указано в атрибуте value выбранной кнопки (а все остальные переключатели проигнорируются, как будто неустановленные флажки). Если указан параметр checked, кнопка будет изначально выбрана, в противном случае — нет.

Чувствую, вас уже мучает вопрос: почему эта штука называется радиокнопкой? При чем тут радио, спрашиваете? Все очень просто. Дело в том, что на старых радиоприемниках (как и на магнитофонах) была группа клавиш, одна из кото- рых могла "залипать", освобождая при этом другую клавишу из группы. На- пример, если радио могло ловить 3 станции, то у него было 3 клавиши, и в кон- кретный момент времени только одна из них могла быть нажата (попробуйте слушать сразу несколько станций!). Согласен, что терминология очень спор- на), но история есть история

Кнопка отправки формы (submit)

<input type=submit [name=имя] value=текст_кнопки

>

Создает кнопку подтверждения с именем name (если этот атрибут указан) и названием (текстом, выводимым поверх кнопки), присвоенным атрибуту value. Как уже говорилось, если задан параметр name, после нажатия кнопки отправки сценарию вместе с другими парами будет передана и пара имя=текст_кнопки (если нажата не эта кнопка, а другая, будет передана строка другой, нажатой, кнопки). Это особенно удобно, когда в форме должно быть несколько кнопок submit, определяющих различные действия (например, кнопки Сохранить и Удалить в сценарии работы с записью какой-то базы данных) — в таком случае чрезвычайно легко установить, какая же кнопка была нажата, и предпринять нужные действия.