Введение
Нет, нет и еще раз нет! Изобретение "велосипедов" не преследуется по закону, но и не особо приветствуется. Просто иногда хочется понять механизм работы некоторых элементов, к которым давно привык, и не обращаешь на них внимание. Для обработки данных, получаемых из формы, существует много модулей: CGI, CGI::Simple, CGI::Lite, CGI::WebIn, это из тех, которые знаю я. Наверняка их еще больше. А что я вижу в скриптах "неизвестного производства"?
$buffer = $ENV{'QUERY_STRING'};
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'})
}
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ s/<!--(.|\n)*-->/<br>/g;
$value =~ s/</</g;
$value =~ s/>/>/g;
$value =~ s/\cM/<br>/g;
$value =~ s/\n/ /g;
$value =~ s/\|/ /g;
$value =~ s/\|/ /g;
$value =~ s/<([^>]|\n)*>/<br>/g;
$FORM{$name} = $value;
}
После чего, начинающие специалисты копируют этот код в свои скрипты и начинают "флудить" на форумах (каюсь: сам таким был и так делал). Но это не самое интересное, проблемы начинаются после того, как потребуется "фильтровать" данные, но не все и не так; потом, иногда форма отправляет несколько значений для одного параметра, а мы получаем только одно; про upload вообще помолчу. В итоге, этот код начинает "обрастать" дополнительными "фишками". А требований все больше и больше:
...выносим этот код в отдельную внешнюю процедуру, так как при доработках постоянно при ходится править кучу скриптов...
...старые доработки и фильтры удалять нельзя, из используют некоторые скрипты, приходится делать дополнительные...
...upload - черт с ним, цепляем модуль CGI, но не везде...
...некоторые параметры надо получить в виде массива, заносим эти параметры отдельно...
и так далее... в общем полный улет... А когда это все надоедает, начинаем писать use CGI в скриптах, и не морочим себе голову.
Но, с использованием, CGI и альтернативных модулей, начинаешь "лениться" и про механизм обработки полученных данных - забываешь. В данной статье мы рассмотрим принцип обработки данных и в процессе напишем небольшой модуль, "без претензий" на первенство.
1. Какие данные мы получаем
В основном (если не всегда), данные передаются только двумя методами:
* GET;
* POST;
Но во время отправки данных методом POST мы можем передать дополнительные данные в URI.
А так же, данные предаются практически всегда (если не всегда) двумя типами данных:
* text/plain (text/html);
* multipart/form-data (только для метода POST);
Формат передаваемых данных можно посмотреть здесь. Но особо не вдаваясь в подробности можно сказать так:
если тип данных text/... данные передаются в виде:
param1=value1¶m2=value2
Где "&" - разделитель параметров, а "=" - разделитель между параметром и значением. При этом можно не волноваться по поводу того, что в имени или значении параметра могут быть эти символы, так как браузер автоматически конвертирует эти символы (и некоторые другие) в шестнадцатеричный формат.
если тип данных multipart/form-data:
-----------------------------7d513a1b160308
Content-Disposition: form-data; name="param1"
value1
-----------------------------7d513a1b160308
Content-Disposition: form-data; name="file"; filename="D:\param2.txt"
Content-Type: text/plain
blablablablablablablablabla
blablablablablablablabla
blablablablablablabla
blablablablablabla
blablablablabla
blablablabla;
blablabla?
blabla;
bla
blabla
bla
blablabla
blablablablablabla
-----------------------------7d513a1b160308--
При этом мы видим, что предварительного ковертирования символов - нет, то есть данные передаются "как есть". Отправной точкой для нас является только уникальный разделитель, в нашем случае - "-----------------------------7d513a1b160308" (естественно, что он каждый раз новый).
Какие данные передает нам Cookies:
Данные передаются в переменной окружения $ENV{'HTTP_COOKIE'} ($ENV{'COOKIE'}), формат:
param1=value1; param2=value2; param3 = value3;
В общем, ничего сложного, итак:
2. Начало модуля и объявление объекта:
Ничего нового, все как по учебнику:
package My::CGI;
# Без него никак нельзя :)
use strict;
# С помощью этого модуля, будем определять FILEHANDLE
# Модуль выбран первый попавшийся, если кому нравится другой - пожалуйста
use IO::File;
# Версия, что бы потом не запутаться
our $VERSION = '1.0.0';
# Процедура объявления объекта
sub new {
# %common - дополнительные сведения, в нашем случае, максимальный объем принимаемых данных
my ($self, %common) = @_;
$self = {
max_upload => 262144, # Default 256 Kb
# Здесь будем хранить имена и значения параметров
data => {},
# Здесь будем хранить куки
cookies => {},
# Здесь будем хранить ссылки на временные файлы которые загрузили из формы
tmp => {},
};
# Определяем максимальный объем передаваемых данных, если надо
$self->{'max_upload'} = $common{'MAX_UPLOAD'} if $common{'MAX_UPLOAD'};
# Запускаем процедуру разбора полученных данных
$self = &_parse_common_data($self);
# "Благославляем" наш объект
bless $self;
# ... и возвращаем
return $self;
}
Сама собой выплыла следующая процедура (_parse_common_data) - разбор полученных данных.
3. Разбор полученных данных
В этой процедуре мы должны обработать три вида данных, точнее не обработать а указать последовательность обработки следующих данных:
* данные переданные методом GET или в URI (QUERY_STRING);
* данные переданные методом POST (CONTENT_LENGTH + STDIN) при этом определить какого они типа;
* данные Cookies (HTTP_COOKIE);
Код:
sub _parse_common_data {
my $self = shift;
# Проверяем наличие QUERY_STRING, при этом не имеет значение метод передачи
# данных, так при методе GET у нас в этой переменной передаются значения формы,
# при методе POST, дополнительные даные в URI, а может просто быть запрос с
# какими-либо параметрами
if ($ENV{'QUERY_STRING'}) {
# и если у нас есть значение, то обрабатываем данные, при этом отдельно указывая
# метод, так для метода POST - 'POST', остальные - GET;
$self = &_parse_QUERY_STRING($self, 'GET')
}
# Проверяем метод передачи данных, для обработки POST
if (uc($ENV{'REQUEST_METHOD'}) eq 'POST') {
# Если тип данных multipart, то передаем обработку в соответсвующую процедуру
if (exists($ENV{'CONTENT_TYPE'}) && $ENV{'CONTENT_TYPE'} =~m |^\s*multipart/form-data|i) {
$self = &_parse_MultiPart($self)
# иначе стандартная обработка, с указанием, что обрабатываются данные метода POST
} else {
$self = &_parse_QUERY_STRING($self, 'POST')
}
}
# Проверяем наличие переданных Cookies
if ($ENV{'HTTP_COOKIE'} || $ENV{'COOKIE'}) {
# Если есть, то обрабатываем
$self = &_parse_COOKIES($self)
}
# Возвращаем заполненый данными массив
return $self
}
Процедура небольшая, и несложная, пора переходить к самому интересному:
4. Разбор данных типа text
Что нам нужно, собственно алгоритм:
* разобрать по отдельности все параметры;
* разобрать имя параметра и его значение;
* обработать эти данные (так как некоторые символы при отправке конвертируются в шестнадцатеричный код);
* положить результат в наш хеш (который в последствии станет объектом), но мы должны учесть, что параметр может быть один, а значений несколько;
Код:
sub _parse_QUERY_STRING {
my ($self, $type) = @_;
my $data;
# Выбираем данные в соответсвии с методом передачи данных
if ($type && $type eq 'POST') {
read(STDIN, $data, $ENV{'CONTENT_LENGTH'})
} else {
$data = $ENV{'QUERY_STRING'}
}
# Разделяем отдельно параметры. В общем, по сути, достаточно было бы и одного
# символа "&", в качестве разделителя, но иногда проявляется символ "?", а в
# модуле CGI еще используется символ ";", но впрочем, хуже не будет если мы укажем
# все символы, тем более как сказано выше, боятся того, что в имени параметра или
# его значении может проскочить этот символ - не стоит, так что:
my @pairs = split(/[\?\&\;]/,$data);
foreach (@pairs) {
# Отделяем имя параметра от его значения, цифра 2 говорит о том, что переменная $_
# разбивается только на 2 части, хотя это лишнее, но тоже не помешает
my ($param, $value) = split('=', $_, 2);
# Если какого-либо значения нет, то данный параметр пропускаем
next unless $param && $value;
# Декодируем полученные значения из шестнадцатеричного формата отдельной
# Хотя, отдельно процедуру выносить не обязательно, только для удобства
$param = &URLDecode($param);
$value = &URLDecode($value);
# Внедряем в наш хеш полученные данные, так как данная функция пригодится нам и
# при обработке данных типа multipart, то выносим её отдельно
$self = &_include_data($self, $param, $value);
}
return $self
}
Быстро обрисуем процедуру URLDecode, она взята как есть у Дмитрия Котерова (CGI::WebIn), и сложного в ней ничего нет:
sub URLDecode {my $s = shift; $s =~tr /+/ /; $s =~s /%([0-9A-Fa-f]{2})/chr(hex($1))/esg; return $s}
Information
- Posted on 27.04.2013 15:27
- Просмотры: 1156