Глава 19 Программирование CGI

Программирование CGI

Введение

Резкие изменения окружающей среды приводят к тому, что некоторые виды лучше других добывают пропитание или избегают хищников. Многие ученые полагают, что миллионы лет назад при столкновении кометы с Землей в атмосферу поднялось огромное облако пыли. За э
Подобно тому, как комета изменила среду обитания доисторических животных, развитие Web изменило ситуацию в современных языках программирования и открыло новые горизонты. Хотя некоторые языки так и не прижились в "новом мировом порядке", Perl выдержал

Архитектура

В основе Web лежит обычный текст. Web-серверы общаются с броузерами с помощью текстового протокола HTTP (Hypertext Transfer Protocol). Многие пересылаемые документы кодируются специальной разметкой, которая называется HTML (Hypertext Markup Language). Тек Web-страницы идентифицируются по так называемым URL (Universal Resource Locator). URL выглядят так:
http://www.perl.com/CPAN/ http://www.perl.com:8001/bad/mojo.html
ftp://gatekeeper.dec.com/pub/misc/netlib.tar.Z
ftp://anonymous@myplace:gatekeeper.dec.co7n/pub/misc/netlib.tar.Z file:///etc/motd
Первая часть (http, ftp, file) называется схемой и определяет способ получения файла. Вторая (://) означает, что далее следует имя хоста, интерпретация которого зависит от схемы. За именем хоста следует путь, идентифицирующий документ. Путь также назы

Web является системой "клиент/сервер". Клиентские броузеры (например, Netscape или Lynx) запрашивают документы (идентифицируемые по частичным URL) у Web-серверов - таких, как Apache. Диалог броузера с сервером определяется протоколом HTTP. В основ Сервер сообщает программе CGI, какая страница была затребована, какие значения были переданы в HTML-формах, откуда поступил запрос, какие данные использовались при аутентификации и многое другое. Ответ программы CGI состоит из двух частей: заголовка, гово
Правильно реализовать протокол CGI нелегко, а ошибиться проще простого, поэтому мы рекомендуем использовать превосходный модуль CGI.pm Линкольна Штейна (Lincoln Stein). Модуль содержит удобные функции для обработки информации, полученной от сервера, и
Некоторые Web-серверы содержат встроенный интерпретатор Perl, что позволяет генерировать документы на Perl без запуска нового процесса. Системные издержки на чтение неизменившейся страницы пренебрежимо малы для страниц с редкими обращениями (даже поря

За кулисами

Программы CGI вызываются каждый раз, когда Web-серверу требуется сгенерировать динамический документ. Необходимо понимать, что программа CGI не работает постоянно с обращениями к ее различным частям со стороны броу-зера. При каждом запросе частичного URL, соответствующего программе, запускается ее новая копня. Программа генерирует страницу для данного запроса и завершается.
Броузер может запросить документ несколькими способами, которые называются методами (не путайте методы HTTP с методами объектно-ориентированного программирования!). Чаще всего встречается метод GET, который обозначает простой запрос документа. Метод H
Значения полей форм также могут кодироваться в методах GET и POST. В методе GET значение кодируется прямо в URL, что приводит к появлению уродливых URL следующего вида:
http://mox.perl.com/cgi-bin/program?name=JohannG'born=1685

В методе POST значения находятся в другой части запроса HTTP - не той, которую броузер отправляет серверу. Если бы в приведенном выше URL значения нолей отсылались методом POST, то пользователь, сервер и сценарий CGI видели бы обычный URL:
http://mox.perl.com/cgi -bin/program

Методы GET и POST также отличаются свойством идемпотентности. Проще говоря, однократный или многократный запрос GET для некоторого URL должен давать одинаковые результаты. Это объясняется тем, что в соответствии со спецификацией протокола HTTP запрос
Большинство серверов регистрирует запросы к файлам (ведут журнал обращений) для их последующего анализа Web-мастером. Ошибки в программах CGI тоже по умолчанию не передаются броузеру. Вместо этого они регистрируются в файле (журнал ошибок), а броузер
Сообщения об ошибках полезны в процессе отладки любой программы, но особенно полезны они в сценариях CGI. Однако авторы программ CGI не всегда имеют доступ к журналу ошибок или не знают, где он находится. Перенаправление ошибок рассматривается в рецеп
В рецепте 19.9 показано, как узнать, что в действительности говорят друг другу броузер с сервером. К сожалению, некоторые броузеры не реализуют спецификацию HTTP в полной мере. Рецепт поможет выяснить, что является причиной возникших трудностей - прог

Безопасность

Сценарии CGI позволяют запускать программы на вашем компьютере кому угодно. Конечно, программу выбираете вы, но анонимный пользователь может передать ей неожиданные значения и обмануть ее, заставляя сделать нечто нехорошее. Безопасности в Web уделяется бо
Некоторые узлы решают проблему, попросту отказываясь от программ CGI. Там, где без силы и возможностей программ CGI не обойтись, приходится искать средства обезопасить их. В рецепте 19.4 приведен список рекомендаций по написанию безопасных сценариев C

HTML и формы

Теги HTML позволяют создавать экранные формы. В этих формах пользователь вводит значения, передаваемые серверу. Формы состоят из элементов (widgets) - например, текстовых полей и флажков. Программы CGI обычно возвращают HTML-код, поэтому в модуле CGI пред В дополнение к рецепту 19.7 в этой главе также имеется рецепт 19.11. В нем показано, как создать форму, сохраняющую свои значения между вызовами. В рецепте 19.12 продемонстрировано создание одного сценария CGI, который создает и обрабатывает целый набор с

Ресурсы Web

Разумеется, лучшую информацию о программировании Web можно найти непосредственно в Web. Безопасность Web
http://www.w3.org/Security/Faq/

Общие сведения о Web
http://www.boutell.com/faq/

CGI
http://www.webthing.com/tutorials/cgifaq.html

Спецификация HTTP
http://www.w3.org/pub/WWW/Protocols/HTTP/

Спецификация HTML
http://www.w3.org/TR/REC-html40/ http://www.w3.org/pub/WWW/MarkUp/

Спецификация CGI
http://www.w3.org/CGI/

Безопасность CGI
http://www.go2net.com/people/paulp/cgi-security/safe-cgi.txt

19.1. Написание сценария CGI

Проблема

Требуется написать сценарии CGI для обработки содержимого HTML-формы. В частности, вы хотите работать со значениями полей формы и выдавать нужные выходные данные.

Решение

Сценарий CGI представляет собой программу, работающую на сервере и запускаемую Web-сервером для построения динамического документа. Он получает кодированную информацию от удаленного клиента (пользовательского броузера) через STDIN и неременные окружения и
Пример 19.1. hiweb
#!/usr/bin/perl -w
# hiweb - загрузить модуль CGI для расшифровки # данных, полученных от Web-сервера use
strict;
use CGI qw(:standard escapeHTML);
# Получить параметр от формы my $value = param('PARAM_NAME');
# Вывести документ print header(), start_html("Howdy there!"),
p("You typed: ", #(escapeHTML($value))),
end_html();

Комментарий

CGI - всего лишь протокол, формальное соглашение между Web-сервером и отдельной программой. Сервер кодирует входные данные клиентской формы, а программа CGI декодирует форму и генерирует выходные данные. В специфшка-ции протокола ничего не сказано о языке
Полная спецификация CGI определяет, какие данные хранятся в тех или иных переменных окружения (например, входные параметры форм) и как они кодируются. Теоретически декодирование входных данных в соответствии с протоколом не должно вызывать никаких про
Сценарии CGI вызываются двумя основными способами, которые называются методами, - но не путайте методы HTTP с методами объектов Perl! Метод GET используется для получения документов в ситуациях, когда идентичные запросы должны давать идентичные резуль
За небольшими исключениями, связанными с правами доступа к файлам и режимами повышенной интерактивности, сценарии CGI делают практически все то же, что и любая другая программа. Они могут возвращать результаты во многих форматах: обычный текст, докуме
Модуль CGI поддерживает два интерфейса - процедурный для повседневного использования и объектно-ориентированный для компетентных пользователей с нетривиальными потребностями. Практически все сценарии CGI должны использовать процедурный интерфейс, но, Чтобы прочитать входные данные пользовательской формы, передайте функции param имя нужного поля. Если на форме имеется поле с именем "favorite", то вызов param( "favorite") вернет его значение. В некоторых элементах форм (на-, пример, в списках) пользоват
Например, следующий сценарий получает значения трех полей формы, последнее из которых может возвращать несколько значений:
use CGI qw(:standard);
$who = param("Name");
$phone = paramC'Number");
@picks = param("Choices");

При вызове без аргументов рагат возвращает список допустимых параметров формы в списковом контексте или количество параметров формы в скалярном контексте. Вот и все, что нужно знать о пользовательском вводе. Делайте с ним, что хотите, а потом генерируйте выходные данные в нужном формате. В этом тоже нет ничего сложного. Помните, что, в отличие от обычных программ, выходные данные сценария CGI должны формати
Как видно из решения, модуль CGI упрощает не только ввод, но и вывод данных. Он содержит функции для генерации заголовков HTTP и HTML-кода. Функция header строит текст заголовка. По умолчанию она генерирует заголовки для документов text/html, но вы мо print header( -TYPE => 'text/plain', -EXPIRES => -+3d' ); Модуль CGI.pm также применяется для генерации HTML-кода. Звучит тривиально, но модуль CGI проявляется во всем блеске при создании динамических форм с сохранением состояния (например, страниц, предназначенных для оформления заказов). В модуле CGI даже имею При выводе элементов формы символы &, <, > и " в выходных данных HTML автоматически заменяются своими эквивалентами. В пользовательских выходных данных этого не происходит. Именно поэтому в решении импортируется и используется функция escapeHTML - даже ес Полный список функций вместе с правилами вызова приведен в документации но модулю CGI.pm, хранящейся в формате POD внутри самого модуля.

> Смотри также --------------------------------
Документация по стандартному модулю CGI; http://www.w3.org/CGI/', рецепт 19.7.

19.2. Перенаправление сообщений об ошибках

Проблема

У вас возникли трудности с отслеживанием предупреждений и ошибок вашего сценария, или вывод в STDERR из сценария приводит сервер в замешательство.

Решение

Воспользуйтесь модулем CGI::Carp из стандартной поставки Perl, чтобы все сообщения, направляемые в STDERR, снабжались префиксом - именем приложения и текущей датой. При желании предупреждения и ошибки также можно сохранять в файле или передавать броузеру.

Комментарий

Задача отслеживания сообщений в сценариях CGI пользуется дурной славой. Даже если вам удалось найти на сервере журнал ошибок, вы все равно не сможете определить, когда и от какого сценария поступило то или иное сообщение. Некоторые недружелюбные Web-серве На сцене появляется модуль CGI::Carp. Он замещает warn и die, а также функции carp, croak и confess обычного модуля Carp более надежными и содержательными версиями. При этом данные по-прежнему отсылаются в журнал ошибок сервера.
use CGI::Сагр;
warn "This is a complaint";
die "But this one is serious";

В следующем примере использования CGI::Carp ошибки перенаправляются в файл по вашему выбору. Все это происходит в блоке BEGIN, что позволяет перехватывать предупреждения на стадии компиляции:
BEGIN {
use CGI::Carp qw(carpout);
open( LOG, ""/var/local/cgi-logs/mycgi-log")
or die "Unable to append to mycgi-log: $!\n";
carpout(*LOG);
}

Фатальные ошибки могут даже возвращаться клиентскому броузеру - это удобно при отладке, но может смутить рядового пользователя.
use CGI::Carp qw(fatalsToBrowser);
die "Bad error here";

Даже если ошибка произойдет до вывода заголовка HTTP, модуль попытается избежать ужасной ошибки 500 Server Error. Нормальные предупреждения по-прежнему направляются в журнал ошибок сервера (или туда, куда вы отправили их функцией carpout) с префиксом

> Смотри также -------------------------------
Документация по стандартному модулю CGI::Carp; описание BEGIN в рецепте 12.3.

19.3. Исправление ошибки 500 Server Error

Проблема

Сценарий CGI выдает ошибку 500 Server Error.

Решение

Воспользуйтесь приведенным ниже списком рекомендаций. Советы ориентированы на аудиторию UNIX, однако общие принципы относятся ко всем системам.

Комментарий

Убедитесь, что сценарий может выполняться Web-сервером. Проверьте владельца и права доступа командой Is -/. Чтобы сценарий мог выполняться сервером, для него должны быть установлены необходимые права чтения и исполнения. Сценарий должен быть доступен для
Проследите, чтобы сценарий идентифицировался Web-сервером как сценарий. Большинство Web-серверов имеет общий для всей системы каталог cgi-bin, и все файлы данного каталога считаются сценариями. Некоторые серверы идентифицируют сценарий CGI как файл с
Если вы работаете в UNIX, проверьте, правильно ли задан путь к исполняемому файлу Perl в строке #!. Она должна быть первой в сценарии, перед ней даже не разрешаются пустые строки. Некоторые операционные системы устанавливают смехотворно низкие огранич
Если вы работаете в Win32, посмотрите, правильно ли связаны свои сценарии с исполняемым файлом Perl.
Проверьте наличие необходимых прав у сценария Проверьте пользователя, с правами которого работает сценарий, с помощью простого фрагмента из примера 19.2.
Пример 19.2. webwhoami
#!/usr/bin/perl
# webwhoami - show web users id
print "Content-Type: text/plain\n\n";
print "Running as ", scalar getpwuid($>), "\n";

Сценарий выводит имя пользователя, с правами которого он работает. Определите ресурсы, к которым обращается сценарий. Составьте список файлов, сетевых соединений, системных функций и т. д., требующих особых привилегий. Затем убедитесь в их доступности
Для всех файлов, в которые сценарий выполняет запись, установите права доступа 0666, а еще лучше - 0644, если они принадлежат тому пользователю, с чьими правами выполняется сценарий. Если сценарий создает новые файлы или перемещает/удаляет старые, пот Не содержит ли сценарий ошибок Perl? Попытайтесь запустить его в командной строке. Модуль CGI.pm позволяет запускать и отлаживать сценарии в командной строке или из стандартного ввода. В следующем фрагменте "О - вводимый вами признак конца файла:
% perl -we cgi-script " Простая компиляция
% perl -w cgi-script # Параметры из stdin
(offline mode: enter name=value pairs on standard input)
nanie=joe
number=10
"D
% perl -w cgi-script name=_joe numbered # Запустить с входными
# данными формы % perl -d cgi-script name=joe number=10 # To же в отладчике
# Сценарий с методом POST в csh
% (setenv HTTP_METHOD POST; perl -w cgi-script name=joe number=10)
# Сценарий с методом POST в sh
% HTTP_METHOD=POST perl -w cgi-script name=joe number=10

Проверьте журнал ошибок сервера. Большинство Web-серверов перенаправляет поток STDERR для процессов CGI в файл. Найдите его (попробуйте/usr/local/ etc/httpd/logs/error_log,
/usr/local/voww/logs/error _log или спросите у администратора) и посмо Не устарела ли ваша версия Perl? Ответ даст команда perl -v. Если у вас не установлена версия 5.004 или выше, вам или вашему администратору следует подумать об обновлении, поскольку 5.003 и более ранние версии не были защищены от переполнения буфера, из-з
Не используете ли вы старые версии библиотек? Выполните команду grep -i version для библиотечного файла (вероятно, находящегося в /usr/hb/perl5/, /usr/
local/lib/perl5/,/usr/lib/perl5/site_perl или похожем каталоге). Для CGI.pm (а фактически - для
% perl -MCGI -le 'print CGI->VERSION' 2.40
Используете ли вы последнюю версию Web-сервера? Хотя такое происходит редко, но все же в Web-серверах иногда встречаются ошибки, мешающие работе сценариев. Используете ли вы флаг -w? С этим флагом Perl начинает жаловаться на неинициализированные переменные, чтение из манипулятора, предназначенного только для записи, и т. д.
Используете ли вы флаг -Т? Если Perl жалуется на небезопасные действия, возможно, вы допустили какие-то неверные предположения относительно входных данных и рабочей среды вашего сценария. Обеспечьте чистоту данных, и вы сможете спокойно спать по ночам
Используете ли вы директиву use st rict? Она заставляет объявлять переменные перед использованием и ограничивать кавычками строки, чтобы избежать возможной путаницы с подпрограммами, и при этом находит множество ошибок.
Проверяете ли вы возвращаемые значения всех системных функций? Многие люди наивно полагают, что любой вызов open, system, rename или unlink всегда проходит успешно. Они возвращают значение, по которому можно проверить результат их работы, - так провер Находит ли Perl используемые вами библиотеки? Напишите маленький сценарий, который просто выводит содержимое @INC (список каталогов, в которых ищутся модули и библиотеки). Проверьте права доступа к библиотекам (должно быть разрешено чтение для пользовател
Выдает ли Perl предупреждения или сообщения об ошибках? Попробуйте использовать CGI::Carp (см. рецепт 19.2), чтобы направить предупреждения и ошибки в броузер или доступный файл.
Соблюдает ли сценарий протокол CGI? Перед возвращаемым текстом или изображением должен находиться заголовок HTTP. He забывайте о пустой строке между заголовком и телом сообщения. Кроме того, STDOUT в отличие от STDERR не очищается автоматически. Если ваш сценарий направляет в STDERR предупр
$|=1;

Никогда не пытайтесь декодировать поступающие данные формы, самостоятельно анализируя окружение и стандартный ввод - возникает слишком много возможностей для ошибок. Воспользуйтесь модулем CGI и проводите время за творческим программированием или чтен
Справочная информация
Обратитесь к спискам FAQ и другим документам, перечисленным в конце введения этой главы. Возможно, вы допустили какую-нибудь распространенную ошибку для своей системы - прочитайте соответствующий FAQ, и вам не придется краснеть за вопросы тина: "Почем
Спросите других. Почти у каждого есть знакомый специалист, к которому можно обратиться за помощью. Вероятно, ответ найдется намного быстрее, чем при обращении в Сеть. Если ваш вопрос относится к сценариям CGI (модуль CGI, декодирование cookies, получение данных о пользователе и т. д.), пишите в сотр.in fosy stems. www.authoring, misc,

> Смотри также ------------------------------
Рецепт 19.2; сведения о буферизации во введении к главе 8 "Содержимое файлов"; CGI FAQno адресу http://www.webthing.com/tutorials/cgifacf.html.

19.4. Написание безопасных программ CGI

Проблема

Поскольку сценарий CGI позволяет внешнему пользователю запускать программы на недоступном для него компьютере, любая программа CGI представляет потенциальную угрозу для безопасности. Вам хотелось бы свести эту угрозу к минимуму.

Решение

Комментарий

Многие из этих рекомендаций подходят для любых программ - флаг -w и проверка значений, возвращаемых системными функциями, пригодятся и в тех ситуациях, когда безопасность не является первоочередной заботой. Флаг -w заставляет Perl выводить предупреждения
Самая распространенная угроза безопасности (не считая непредвиденных вызовов командного интерпретатора) кроется в передаче форм. Кто угодно может сохранить исходный текст формы, отредактировать HTML-код и передать измененную форму. Даже если вы уверен
Еще хуже, если сценарий CGI использует значение поля формы как основу для выбора открываемого файла или выполняемой команды. Ложные значения, переданные сценарию, заставят его открывать произвольные файлы. Именно из-за таких ситуаций в Perl появился р В этом режиме Perl настаивает на том, чтобы переменная пути задавалась заранее, даже если при запуске программы указывается полный путь. Дело в том, что нельзя быть уверенным, что выполняемая команда не вызовет другую программу по относительному имени. Кр Например, при выполнении в режиме пометки фрагмента:
#!/usr/bin/perl -Т
open(FH, oo> $ARGV[0]") or die;

Perl выдает следующее предупреждение:
Insecure dependency in open while running with -T switch at ...
Это объясняется тем, что значение $ARGV[0] (поступившее в программу извне) считается не заслуживающим доверия. Единственный способ снять пометку с ненадежных данных - использовать обратные ссылки в регулярных выражениях:
$file = $ARGV[0]; # $file помечена
unless ($file =~ mft"([\w.-]+)$") { # С $1 снята пометка
die "filename '$file' has invalid characters.\n";
} $file = $1; # С $file снята пометка

Помеченные данные могут поступать из любого источника, находящегося вне программы, - например, из аргументов или переменных окружения, из результатов чтения файловых или каталоговых манипуляторов, команды stat или данных о локальном контексте. К числу
Один из распространенных видов атаки связан с так называемой ситуацией перехвата (race condition). Ситуация перехвата возникает тогда, когда нападающий вмешивается между двумя вашими действиями и вносит какие-то изменения, нарушающие работу программу. Ситуации перехвата возникают даже во внешне безобидных местах. Допустим, у вас одновременно выполняется не одна, а сразу несколько копий следующего кода:
unless (-e $filename) { # НЕВЕРНО!
open(FH, "> $filename");
# .. .
}

Между проверкой существования файла и его открытием для записи возникает возможность перехвата. Что еще хуже, если файл заменится ссылкой на что-нибудь важное (например, на ваш личный конфигурационный файл), предыдущий фрагмент сотрет этот файл. Прави Setuid-сценарий CGI работает с другими правами, нежели Web-сервер. Так он получает возможность работать с ресурсами (файлами, скрытыми базами данных паролей и т. д.), которые иначе были бы для него недоступны. Это может быть удобно, но может быть и опасно Наконец, принимайте во внимание физический путь вашего сетевого графика (возможно, это самая трудная из всех рекомендаций). Передаете ли вы незашифрованные пароли? Не перемещаются ли они по ненадежной сети? Поле формы PASSWORD защищает лишь от тех, кто по

> Смотри также -----------------------------
Perlsec(l); спецификации CGI и HTTP, а также список FAQ, по безопасности CGI, упомянутые во введении этой главы; раздел "Avoiding Denial of Service Attacks" в стандартной документации по модулю CGI; рецепт 19.6.
© copyright 2000 Soft group

Используются технологии uCoz