File Coverage

blib/lib/CGI/WebIn.pm
Criterion Covered Total %
statement 30 43 69.7
branch 8 20 40.0
condition 1 8 12.5
subroutine 7 9 77.7
pod 0 3 0.0
total 46 83 55.4


line stmt bran cond sub pod time code
1             # Для разрешения upload-а нужно в директорию со скриптом поместить файл с
2             # именем ".can_upload". В противном случае upload запрещаеся.
3             # Если же этот файл задан, то делается попытка прочитать из него параметры:
4             # dir=имя_директории для закачки
5             # maxsize=максимальный_размер закачиваемого файла
6             # В любом случае, по окончание работы скрипта закачанные файлы удаляются
7             # (если только они не были перемещены скриптом в другое место).
8              
9             # TODO:
10             # 3. Попробовать решить проблему экспорта
11             # 4. Имена временных файлов должны быть совместимы с Маком.
12             # 5. Поддержка undef в Serialize + предупреждения.
13              
14             package CGI::WebIn;
15 1     1   6640 use strict;
  1         3  
  1         473  
16             our $VERSION = '2.03';
17             our @EXPORT=qw(
18             %IN
19             %GET
20             %POST
21             %COOKIES
22             SetCookie
23             DropCookie
24             );
25              
26              
27             ####################### Константы, управляющие работой #####################
28             our $CANUPL_FILE = ".can_upload"; # имя файла, разрешающего закачку
29             our $MULTICHUNK_SIZE = 20000; # длина блока считывания STDIN-а
30             our $MAX_ARRAY_IDX = 10000; # максимально возможный индекс N в a[N]
31             our $uniq_tempnam = 0; # temp files counter
32             our @TempFiles = (); # all temp files (to delete after end)
33             our @Errors = (); # all query parsing errors
34              
35             # Настройки сериализации.
36             # Некоторые внутренние настроечные переменные. Фактически, они используются
37             # в качестве констант. Лучше всего их никогда не трогать. Эти константы
38             # должны состоять из одного символа!
39             our $Div1 = "."; # ALWAYS should be one nondigit!!!
40             our $Div2 = "."; # may be the same as $Div1
41              
42              
43             ####################### Преременные с данными браузера #####################
44             our %IN = (); # Данные формы
45             our %GET = (); # Данные GET
46             our %POST = (); # Данные POST
47             our %COOKIES = (); # Все пришедшие Cookies
48             our %IMPORT_MOD = (); # Модули, затребовавшие импорт переменных (ключи)
49              
50              
51             # void _reparseAll()
52             # Parses all the input data.
53             sub _reparseAll {
54 1 50   1   5 if($ENV{QUERY_STRING}) {
55 0         0 _parseURLEnc($ENV{QUERY_STRING},"get");
56             }
57 1 50       5 if(uc($ENV{REQUEST_METHOD}) eq "POST") {
58 0 0 0     0 if(exists($ENV{'CONTENT_TYPE'}) && $ENV{'CONTENT_TYPE'}=~m|^\s*multipart/form-data|i) {
59 0         0 _parseMultipart();
60             } else {
61 0         0 read(STDIN,my $data,$ENV{CONTENT_LENGTH});
62 0         0 _parseURLEnc($data,"post");
63             }
64             }
65 1 50 33     8 if($ENV{HTTP_COOKIE} || $ENV{COOKIE}) {
66 0         0 _parseCookies();
67             }
68             # use Data::Dumper; print "
".Dumper(\%IN)."
";
69             }
70              
71              
72             # void import(...)
73             # Called on 'use'.
74             sub import
75 1     1   7 { my ($pkg, $opt)=@_;
76 1         3 my $caller = caller();
77 1 50       3 export_vars($opt, $caller) if $opt;
78 1     1   7 no strict;
  1         2  
  1         748  
79 1         3 foreach (@EXPORT) {
80 6         25 my ($type, $name) = /^([%@\$]?)(.*)$/s;
81 6 100       19 if ($type eq '%') {
    50          
82 4         4 *{$caller."::".$name} = \%{$name};
  4         17  
  4         9  
83             } elsif ($type eq '') {
84 2         1 *{$caller."::".$name} = \&{$name};
  2         1793  
  2         8  
85             }
86             }
87             }
88              
89              
90             # Deletes temporary files if present.
91             sub END
92 1 50   1   185 { map { unlink($_) } @TempFiles if @TempFiles;
  0         0  
93             }
94              
95             # list of string GetErrors()
96             # Returns all errors collected while parsing the form input data
97             # (for example, too large autoarray index).
98             sub GetErrors {
99 0     0 0 0 return @Errors;
100             }
101              
102              
103             # Encoding and decoding.
104 1     1 0 309 sub URLEncode { my ($s)=@_; $s=~s{([^-_A-Za-z0-9./])}{sprintf("%%%02X", ord $1)}sge; return $s }
  1         11  
  233         750  
  1         5  
105 1     1 0 6 sub URLDecode { my ($s)=@_; $s=~tr/+/ /; $s=~s/%([0-9A-Fa-f]{2})/chr(hex($1))/esg; return $s }
  1         6  
  1         6  
  0         0  
  1         2  
106              
107              
108             my %CODE = (
109             'export_vars' => <<'END_OF_FUNC',
110             # void export_vars(sting $options, string $toPkg)
111             # Export EGPC-variables from %GET, %POST etc.
112             sub export_vars
113             { my ($opt, $to)=@_;
114             if(!scalar(@_)) {
115             # Вызов без параметров - обойти и экспортировать во все модули-клиенты
116             while(my ($mod,$opt)=each(%IMPORT_MOD)) {
117             export_vars($opt,$mod);
118             }
119             } else {
120             # Вызов с параметрами - экспорт переменных только в укакзанный модуль
121             return if !$opt;
122             $opt="gpces" if lc($opt) eq "a" || $opt eq "1";
123             # Сохраняем информацию о том, что модуль "хочет" экспортирования и
124             # в дальнейшем. Например, при вызове SetCookie() соответствующая
125             # переменная создастся не только в %COOKIES, но и во всех модулях.
126             $IMPORT_MOD{$to}=$opt;
127             # Экспортируем еще не существующие переменные
128             no strict;
129             my $Bad=\%{$to."::"};
130             foreach my $op (split //,$opt) {
131             $op=lc($op);
132             my $Hash =
133             $op eq "g" && \%GET ||
134             $op eq "p" && \%POST ||
135             $op eq "c" && \%COOKIES ||
136             $op eq "e" && \%ENV || next;
137             while(my ($k,$v)=each(%$Hash)) {
138             # не переписывать существующие переменные
139             next if exists $Bad->{$k};
140             ## BUGFIX 11.07.2002 v1.10:
141             ## разрешается применять только буквенно-цифровые имена,
142             ## раньше имена вида SomeModule::var приводили к дыре
143             ## в безопасности.
144             next if $k=~/[^\w\d_]/s;
145             *{$to."::".$k}=ref($v)? $Hash->{$k} : \$Hash->{$k};
146             }
147             }
148             }
149             }
150             END_OF_FUNC
151              
152              
153             '_processPar' => <<'END_OF_FUNC',
154             # void _processPar(string $key, string $value, string $type)
155             # Добавляет пару $key=>$value в хэш %IN (с разбором многоуровневых хэшей),
156             # а также в хэш %GET, %POST или %COOKIES, в зависимости от значения $type
157             # (get, post, cookies соответственно).
158             # Пустые скобки "{}" заменяются на значение "{$v}"!
159             sub _processPar
160             { my ($k,$v,$type)=@_;
161             return if !defined($k);
162             $type=uc($type||"IN");
163              
164             ## BUGFIX 12.07.2002 v1.10:
165             ## до этого было s/\r//sg, что неправильно работало на Маке.
166             $v=~s/\x0d\x0a?|\x0a\x0d?/\n/sg if defined $v && !ref $v;
167              
168             # Проверяем вид "a{10}{20}" и заодно получаем первый ключ
169             do { push @Errors, "$type: Unknown input field format '$k'"; return } if $k!~/^([^}{\[\]]+)/sg;
170              
171             ## Этап I: Получаем все индексы, обрамленные соотв. скобками.
172             my @Ind = ([$1, '']);
173             while(pos $k < length $k) {
174             my ($i,$t);
175             $k=~/\G
176             \{ (
177             (?:
178             [^}"']* |
179             ## BUGFIX 12.07.2002 v1.10:
180             ## нужно писать [^"\\], а не [^"].
181             ## after slash ALWAYS must be any character.
182             "(?:[^"\\]+|\\.)*" |
183             '(?:[^'\\]+|\\.)*'
184             )
185             ) \}
186             /sxgc and do { $t=$Ind[-1][1]='HASH'; push @Ind, $i=[$1, ''] }
187             or ####
188             $k=~/\G
189             \[ (
190             (?:
191             [^]"']* |
192             "(?:[^"\\]+|\\.)*" |
193             '(?:[^'\\]+|\\.)*'
194             )*
195             ) \]
196             /sxgc and do { $t=$Ind[-1][1]='ARRAY'; push @Ind, $i=[$1, ''] }
197             or ###
198             do { push @Errors, "$type: Corrupted parameter '$k'"; return };
199              
200             if($i->[0] eq "" && defined $v) {
201             # Заменяем пустой индекс БЕЗ КАВЫЧЕК на $v или -1
202             $i->[0] = $t eq 'HASH'? $v : '';
203             } else {
204             # Убираем слэши перед кавычками, но только если строка была заковычена.
205             $i->[0]=~s/^(['"])(.*)\1$/$2/sg
206             and
207             $i->[0]=~s/\\(['"\\])/$1/sg;
208             }
209             }
210             # [0] содержит очередной индекс.
211             # [1] содержит ТИП объекта, в котором этот индекс СОДЕРЖИТСЯ.
212              
213             # use Data::Dumper; print "
".Dumper(\%IN)."
";
214              
215             ## Этап II: заполняем хэши. К сожалению, приходится делать
216             ## цикл по всем хэшам и фактически делать одну работу несколько
217             ## раз, потому что где-то уже могут существовать полузаполненные
218             ## хэши.
219             my @Outs=(\%IN);
220             push @Outs, \%GET if lc($type) eq "get";
221             push @Outs, \%POST if lc($type) eq "post";
222             push @Outs, \%COOKIES if lc($type) eq "cookie";
223             foreach my $cur (@Outs) {
224             foreach my $idx (@Ind) {
225             # Текущий ключ и тип значения по этому ключу.
226             my ($i,$t) = @$idx;
227             # Получаем ссылку $r не то место, куда нужно записать значение.
228             my $r;
229             if(ref $cur eq 'HASH') {
230             # Работаем с $cur как с хэшем.
231             $r = \$cur->{$i};
232             } elsif(ref $cur eq 'ARRAY') {
233             # Работаем с $cur как с массивом.
234             # Индекс -1 означает "добавить в конец".
235             $i = @$cur if $i eq "";
236             # Не-цифровые и слишком большие индексы не допускаются.
237             do { push @Errors, "$type: Non-numeric index '$i' in '$k'"; return } if $i=~/[^\d]/s;
238             do { push @Errors, "$type: Too large index '$i' in '$k'"; return } if $i>$MAX_ARRAY_IDX;
239             $r = \$cur->[$i];
240             }
241             # Если такого ключа еще нет, то в $$r будет undef.
242             $$r = ($t eq 'HASH'? {} : $t eq 'ARRAY'? [] : '') if !defined $$r;
243             # Проверка соответствия типов.
244             if(ref($$r) ne $t) {
245             push @Errors, "$type: Mismatched parameter type: key '$i' in '$k' later defined as ".(ref($$r)||"SCALAR").", not ".(!$t? "SCALAR" : $t eq 'HASH'? 'HASH' : 'ARRAY');
246             return;
247             }
248             $$r = $v if !$t;
249             $cur = $$r;
250             }
251             }
252             }
253             END_OF_FUNC
254              
255              
256             '_parseURLEnc' => <<'END_OF_FUNC',
257             # void _parseURLEnc(string $input, string $type)
258             sub _parseURLEnc
259             { my ($tosplit,$type) = @_;
260             my (@pairs) = split(/[&?]/,$tosplit);
261             my ($param,$value);
262             foreach (@pairs) {
263             ($param,$value) = split('=',$_,2);
264             $param = URLDecode(defined($param)?$param:"");
265             $value = URLDecode(defined($value)?$value:"");
266             _processPar($param,$value,$type);
267             }
268             }
269             END_OF_FUNC
270              
271              
272             'tempnam' => <<'END_OF_FUNC',
273             # string tempnam([string $Dir])
274             # Возвращает уникальное (используется PID и таймер) имя файла в директории, указанной в
275             # параметрах. По умолчанию - в директории, указанной в переменной окружения TMP или TEMP,
276             # или, в крайнем случае, в текущей. В конце работы скрипта все файлы, имеющие
277             # имена, сгенерированные tempnam(), будут удалены!
278             # Всегда возвращает полный путь к временному файлу.
279             sub tempnam
280             { my ($dir)=@_;
281             foreach my $cd ($dir,$ENV{TMP},$ENV{TEMP},"/tmp",".") {
282             if(defined $cd && -d $cd && -w $cd) { $dir=$cd; last; }
283             }
284             my $nm=$dir."/".time()."-$$-".(++$uniq_tempnam).".tmp";
285             if($nm!~m{^[/\\]}) {
286             require Cwd;
287             $nm=Cwd::getcwd()."/".$nm;
288             }
289             push(@TempFiles,$nm);
290             return $nm;
291             }
292             END_OF_FUNC
293              
294              
295             '_parseMultipart' => <<'END_OF_FUNC',
296             # hash _readUplConf()
297             # Читает конфигурационный файл, разрешающий или запрещающий
298             # закачку в текущей директории.
299             sub _readUplConf {
300             open(local *F,"<$CANUPL_FILE") or return
301             my %cfg=();
302             while(my $st=) {
303             $st=~s/^\s+|\s+$|#.*$//gs;
304             next if $st eq "";
305             my ($k,$v)=split(/=/,$st,2);
306             $cfg{$k}=$v;
307             }
308             return %cfg;
309             }
310              
311             # Обработка Multipart-данных формы
312             # void _parseMultipart()
313             our ($InBuf, $InLength); # our для strict
314             sub _parseMultipart
315             { # Устанавливаем директорию и другие параметры для закачки (если разрешена)
316             my %cfg=_readUplConf(); # свойства закачки
317              
318             #------- Работа с STDIN с возможностью "запихивания" данных обратно в поток
319             $InBuf=""; $InLength=$ENV{CONTENT_LENGTH};
320             sub _readInput {
321             my $sz=shift||$InLength;
322             my $need=$MULTICHUNK_SIZE>$sz? $sz : $MULTICHUNK_SIZE;
323             my $nBuf=length($InBuf)<$need? length($InBuf) : $need;
324             my $out=substr($InBuf,0,$nBuf); $InBuf=substr($InBuf,$nBuf);
325             read(STDIN,$out,$need-$nBuf,$nBuf) if $need-$nBuf>0;
326             $InLength-=length($out);
327             return $out;
328             }
329             sub _putBack
330             { my ($data)=@_;
331             $InBuf=$data.$InBuf;
332             $InLength+=length($data);
333             }
334             sub _isEof { return !$InLength; }
335             #-------- Конец внутренних функций
336              
337             binmode(STDIN);
338             # Сначала читаем разделитель и финальные "\r\n"
339             my ($bound,$CRLF) = _readInput()=~/(^[^\r\n]*)([\r\n]*)(.*)/s; # Выделяем разделитель БЕЗ \n
340             _putBack($3);
341              
342             # Теперь читаем записи, завершенные разделителем
343             while((my $Data=_readInput()) ne "") {
344             if(substr($Data,0,2) eq "--") { last; } # Проверяем, не конец ли это
345             # Выделяем ВСЕ строки заголовка (до пустой строки).
346             $Data=~/^[$CRLF]*(.*?)$CRLF$CRLF(.*)/s
347             or do { push @Errors, "Malformed multipart header"; return };
348             _putBack($2); # Остаток запихиваем обратно
349            
350             # Получаем заголовки записи в %Headers
351             my @Lines=split(/$CRLF/,$1); # строки заголовка
352             my %Headers=();
353             foreach my $st (@Lines) {
354             my ($k,$v)=split(/: */,$st,2);
355             $Headers{lc($k)}=$v;
356             }
357             if(!%Headers) { push @Errors, "Malformed multipart POST (no header)"; return; }
358              
359             # Выделяем имя тэга и имя файла (если задано)
360             my ($name)=$Headers{'content-disposition'}=~/\bname="?([^\";]*)"?/;
361             my ($filename) = $Headers{'content-disposition'}=~/\bfilename="?([^\";]*)"?/;
362              
363             # Если это не закачка, то читаем данные и продолжаем
364             if(!defined $filename || $filename eq "") {
365             my ($body,$i);
366             $body = "";
367             for($body=""; ($i=index($body,$bound))<0 && !_isEof(); ) {
368             $body.=_readInput();
369             }
370             if($i<0) { push @Errors, "Malformed multipart POST (no boundary after body)"; return; }
371             _putBack(substr($body,$i+length($bound))); # запихиваем остаток назад
372             _processPar($name,substr($body,0,$i-length($CRLF)),"post");
373             next;
374             }
375              
376             # Иначе это закачка. Записываем временный файл.
377             my $temp=defined $cfg{dir}? tempnam($cfg{dir}):tempnam();
378             local *F; open(F,">$temp") or die("Cannot open temporary file $temp"); binmode(F);
379             my $written=0; # сколько байт в файле
380             my $stopWrite=0; # нужно ли записывать, или пропускать
381             while(1) {
382             # Файл слишком велик или же закачка запрещена?..
383             $stopWrite ||=
384             !%cfg && "File not found: $CANUPL_FILE"
385             || (defined $cfg{maxsize} && $written>$cfg{maxsize}) && "File exceeds limit of $cfg{maxsize} bytes";
386              
387             my $body1=_readInput();
388             my $body2=_readInput(128); # для проверки разделителя длиной <128 байт
389             my $body=$body1.$body2;
390              
391             # Нашли конец файла (разделитель)?
392             if((my $i=index($body,$bound))>=0) {
393             $written+=$i-length($CRLF);
394             print F substr($body,0,$i-length($CRLF)) if !$stopWrite;
395             _putBack(substr($body,$i+length($bound)));
396             last;
397             }
398             $written+=length($body1);
399             print F $body1 if !$stopWrite;
400             _putBack($body2);
401             }
402             close(F);
403              
404             # Формируем значение параметра.
405             ## BUGFIX 13.07.2002:
406             ## раньше имя вида f[] и f{} приводило к неправильному
407             ## созданию этого хэша, т.к. было насколько вызовов
408             ## _processPar с суффиксами {filename}, {file} и т.д.
409             my %hash=();
410             $hash{filename}=$filename;
411             # Файл слишком большой, либо upload запрещен?..
412             if($stopWrite) {
413             unlink($temp);
414             $hash{aborted}=$stopWrite;
415             } else {
416             # Иначе все в порядке
417             $hash{headers}=\%Headers;
418             $hash{file}=$temp;
419             $hash{size}=-s $temp;
420             $hash{type}=$Headers{'content-type'} if $Headers{'content-type'};
421             }
422             # Добавляем параметр.
423             _processPar($name,\%hash,"post");
424             }
425             }
426             END_OF_FUNC
427              
428              
429             '_parseCookies' => <<'END_OF_FUNC',
430             # Разбирает пришедшие cookies
431             sub _parseCookies
432             { my @Pairs = split("; *",$ENV{HTTP_COOKIE} || $ENV{COOKIE} || "");
433             foreach (@Pairs) {
434             my ($key,$value);
435             if(/^([^=]+)=(.*)/) { $key = $1; $value = $2; } else { $key = $_; $value = ''; }
436             $key=URLDecode($key);
437             $value=URLDecode($value);
438             my $v=Unserialize($value);
439             $value=defined($v)?$v:$value;
440             _processPar($key,$value,"cookie");
441             }
442             }
443             END_OF_FUNC
444              
445              
446             'ExpireCalc' => <<'END_OF_FUNC',
447             # int ExpireCalc(string $tm)
448             # This routine creates an expires time exactly some number of
449             # hours from the current time. It incorporates modifications from Mark Fisher.
450             # Format for time $tm can be in any of the forms...
451             # "now" -- expire immediately
452             # "+180s" -- in 180 seconds
453             # "+2m" -- in 2 minutes
454             # "+12h" -- in 12 hours
455             # "+1d" -- in 1 day
456             # "+3M" -- in 3 months
457             # "+2y" -- in 2 years
458             # "-3m" -- 3 minutes ago(!)
459             sub ExpireCalc
460             { my($time)=@_;
461             my(%mult)=('s'=>1, 'm'=>60, 'h'=>60*60, 'd'=>60*60*24, 'M'=>60*60*24*30, 'y'=>60*60*24*365);
462             my($offset);
463             if(lc($time) eq 'now') { $offset = 0; }
464             elsif($time=~/^([+-](?:\d+|\d*\.\d*))([mhdMy]?)/) { $offset = ($mult{$2} || 1)*$1; }
465             else { return $time; }
466             return (time+$offset);
467             }
468             END_OF_FUNC
469              
470              
471             'Expires' => <<'END_OF_FUNC',
472             # int Expires(int $time, string $format)
473             # This internal routine creates date strings suitable for use in
474             # cookies ($format="cookie") and HTTP headers ($format="http" or nothing).
475             # (They differ, unfortunately.) Thanks to Fisher Mark for this.
476             sub Expires
477             { my($time,$format) = @_; $format ||= 'http';
478             my(@MON)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
479             my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;
480             # pass through preformatted dates for the sake of expire_calc()
481             $time = ExpireCalc($time); return $time unless $time =~ /^\d+$/;
482             # cookies use '-' as date separator, HTTP uses ' '
483             my($sc) = ' '; $sc = '-' if $format eq "cookie";
484             my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($time);
485             return sprintf("%s, %02d$sc%s$sc%04d %02d:%02d:%02d GMT",
486             $WDAY[$wday],$mday,$MON[$mon],$year+1900,$hour,$min,$sec);
487             }
488             END_OF_FUNC
489              
490              
491             'SetCookie' => <<'END_OF_FUNC',
492             # void SetCookie(string $name, string $value [,int $expire][,$path][,$domain][bool $secure])
493             # Устанавливает cookie с именем $name и значение $value ($value может быть сложным объектом
494             # - в частности, ссылкой на массив или хэш).
495             # Если $value не задан (undef), cookie удаляется.
496             # Если $expire не задан, время жизни становится бесконечным. Если задан, но равен
497             # нулю - создается one-session cookie.
498             # Параметр $expire можно задавать в виде, который "понимает" функция ExpireCalc().
499             sub SetCookie
500             { my ($name,$value,$expires,$path,$domain,$secure)=@_;
501             my $NeedDel=0;
502              
503             # [12.03.2002] Можно и без этого.
504             # if(!defined $path) {
505             # $path=$ENV{SCRIPT_NAME};
506             # $path=~s{/[^/]*$}{}sg;
507             # $path.="/";
508             # }
509              
510             if(!defined $expires) { $expires="+20y"; }
511             if(!defined $value) { $value=""; $expires="-3y"; $NeedDel=1; }
512            
513             my @Param;
514             push(@Param,URLEncode($name)."=".URLEncode(Serialize($value)));
515             push(@Param,"domain=$domain") if defined $domain;
516             push(@Param,"path=$path") if defined $path;
517             push(@Param,"expires=".Expires($expires,"cookie")) if $expires;
518             push(@Param,'secure') if $secure;
519            
520             my $cook="Set-Cookie: ".join("; ",@Param);
521             eval {
522             local ($SIG{__WARN__},$SIG{__DIE__})=(sub {}, sub {});
523             require CGI::WebOut;
524             };
525             if($@) {
526             # Если не вышло загрузить CGI::WebOut, то просто печатаем.
527             print $cook . "\r\n";
528             } else {
529             CGI::WebOut::Header($cook);
530             }
531             if(!$NeedDel) { _processPar($name,$value,"cookie"); }
532             else { _processPar($name,undef,"cookie"); }
533             # Экспортируем Cookie во все нужные модули
534             export_vars();
535             }
536             END_OF_FUNC
537              
538              
539             'DropCookie' => <<'END_OF_FUNC',
540             # void DropCookie(string $name [,$path] [,$domain])
541             # Удаляет cookie с именем $name. Параметры $path и $domain
542             # должны точно совпадать с теми, которые были заданы при
543             # установке Cookie.
544             sub DropCookie
545             { my ($name,$path,$domain)=@_;
546             SetCookie($name,undef,undef,$path,$domain);
547             }
548             END_OF_FUNC
549              
550              
551             'Serialize' => <<'END_OF_FUNC',
552             # string Serialize(mixed @args)
553             # Упаковывает в строку любой (практически) объект. Так что не обязательно передавать
554             # этой функции ссылку - можно прямо объект целиком.
555             # (В этом случае он будет рассмотрен как список).
556             # Нельзя упаковывать объекты, содержащие ссылки на функции и дескрипторы файлов.
557             # В случае ошибки возвращает undef и выводит warning.
558             sub Serialize
559             { my $st="L".($#_+1).$Div2;
560             foreach my $Arg (@_) {
561             while((my $Ref=ref($Arg)) eq "REF") { $st.="r"; $Arg=$$Arg; }
562             if(ref($Arg) ne "") { $st.="r"; }
563             if(ref($Arg) eq "") { $st.=length($Arg).$Div1.$Arg; }
564             elsif(ref($Arg) eq "SCALAR") { $st.=length($$Arg).$Div1.$$Arg; }
565             elsif(ref($Arg) eq "ARRAY") { $st.=Serialize(@$Arg); }
566             elsif(ref($Arg) eq "HASH") { $st.="H".Serialize(%$Arg); }
567             else { warn("Serialize: invalid field type '".ref($Arg)."'"); return undef; }
568             }
569             return $st;
570             }
571             END_OF_FUNC
572              
573              
574             'Unserialize' => <<'END_OF_FUNC',
575             # mixed _Unserialize(string $st)
576             # Internal function.
577             sub _Unserialize
578             { my ($st,$TotalLen)=@_;
579             # Считаем число ссылок
580             my $RefCount;
581             for($RefCount=0; substr($st,$RefCount,1) eq "r"; $RefCount++) {;}
582             $$TotalLen+=$RefCount; $st=substr($st,$RefCount);
583             # Определяем тип
584             my $Type="S"; # Может быть еще: "HL" (да, 2 символа!!!) или "L"
585             if(substr($st,0,1) eq "H") { $Type="H"; $st=substr($st,2); $$TotalLen+=2; }
586             elsif(substr($st,0,1) eq "L") { $Type="L"; $$TotalLen++; $st=substr($st,1); }
587             # Выполняем действия в зваисимости от типа
588             my $PResult;
589             if($Type eq "S") {
590             # Это - обычная строка.
591             my $len=substr($st,0,my $p=index($st,$Div1)); # 0123.aaabbb
592             $st=substr($st,$p+1); $$TotalLen+=$p+1+$len; # ^ ^p
593             # Распаковываем исходную строку
594             my $s=substr($st,0,$len); $PResult=\$s;
595             } elsif($Type eq "L" || $Type eq "H") {
596             my @Unpack;
597             my $size=substr($st,0,my $p=index($st,$Div2));
598             $st=substr($st,$p+1); $$TotalLen+=$p+1;
599             foreach my $i (0..$size-1) {
600             my $len; push(@Unpack,_Unserialize($st,\$len));
601             $$TotalLen+=$len;
602             $st=substr($st,$len);
603             }
604             if($Type eq "L") { $PResult=\@Unpack; } else { my %Hash=@Unpack; $PResult=\%Hash; }
605             }
606             # We have the pointer to the object $PResult. Returning the (n-1)-th reference on it.
607             for(my $i=0; $i<$RefCount; $i++) { my $tmp=$PResult; $PResult=\$tmp; }
608             if(ref($PResult) eq "ARRAY") { return wantarray?@$PResult:@$PResult[0]; }
609             elsif(ref($PResult) eq "HASH") { return %$PResult; }
610             else { return $$PResult; }
611             }
612              
613              
614             # mixed _Unserialize(string $st)
615             # Распаковывает строку, созданную ранее при помощи Serialize(). Возвращает то, что
616             # было когда-то передано в параметрах Serialize.
617             # В случае ошибки возвращает undef и выдает warning.
618             sub Unserialize
619             { return undef if !defined $_[0];
620             my @Result=(); my $err=0;
621             local $SIG{__WARN__}=sub { $err=1; };
622             local $SIG{__DIE__}=sub { $err=1; };
623             eval { @Result=_Unserialize($_[0]); };
624             if($err||$@) { return undef; }
625             return wantarray?@Result:$Result[0];
626             }
627             END_OF_FUNC
628             );
629              
630             #eval join("", values %CODE);
631              
632             our $AUTOLOAD;
633             sub AUTOLOAD {
634 0 0   0     my ($pkg, $sub) = $AUTOLOAD =~ /^(.*)::(.*)$/s or return;
635 0 0         return if $pkg ne __PACKAGE__;
636 0   0       eval($CODE{$sub} or return);
637 0           goto &$sub;
638             }
639              
640             _reparseAll();
641              
642             return 1;
643             __END__