File Coverage

blib/lib/Apache2/SSI.pm
Criterion Covered Total %
statement 918 2164 42.4
branch 332 1196 27.7
condition 100 402 24.8
subroutine 113 202 55.9
pod 79 86 91.8
total 1542 4050 38.0


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI.pm
3             ## Version v0.2.3
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2020/12/17
7             ## Modified 2021/03/19
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package Apache2::SSI;
14             BEGIN
15             {
16 14     14   2612494 use strict;
  14         584  
  14         874  
17 14     14   124 use warnings;
  14         57  
  14         732  
18 14     14   117 use warnings::register;
  14         63  
  14         3552  
19 14     14   656 use parent qw( Module::Generic );
  14         389  
  14         231  
20 14     14   43 our( $MOD_PERL, $MOD_PERL_VERSION, $SERVER_VERSION );
21 14 50 33     92 if( exists( $ENV{MOD_PERL} )
22             &&
23             ( $MOD_PERL = $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ ) )
24             {
25 0         0 $MOD_PERL_VERSION = $1;
26 0         0 select( ( select( STDOUT ), $| = 1 )[ 0 ] );
27             ## For exec cmd to check the user has permission to execute commands
28 0         0 require Apache2::Access;
29 0         0 require Apache2::Const;
30 0         0 Apache2::Const->import( compile => qw( :common :http OK DECLINED CONN_KEEPALIVE ) );
31 0         0 require Apache2::Filter;
32 0         0 require Apache2::Connection;
33 0         0 require Apache2::RequestRec;
34             ## For exec commands
35 0         0 require Apache2::SubProcess;
36 0         0 require Apache2::SubRequest;
37 0         0 require Apache2::RequestIO;
38 0         0 require Apache2::Log;
39 0         0 require Apache2::ServerUtil;
40 0         0 require Apache2::RequestUtil;
41 0         0 require APR::Brigade;
42 0         0 require APR::Bucket;
43 0         0 require APR::Table;
44 0         0 require APR::Base64;
45 0         0 require APR::Request;
46 0         0 require APR::SockAddr;
47 0         0 require APR::Finfo;
48 0         0 require APR::Const;
49 0         0 APR::Const->import( -compile => qw( FINFO_NORM ) );
50             }
51 14     14   245821815 use Apache2::Expression;
  14         66  
  14         331  
52 14     14   14680 use Apache2::SSI::File;
  14         55  
  14         300  
53 14     14   6500 use Apache2::SSI::Finfo;
  14         27  
  14         522  
54 14     14   8135 use Apache2::SSI::Notes;
  14         52  
  14         293  
55 14     14   16589 use Apache2::SSI::URI;
  14         117  
  14         315  
56 14     14   6118 use Config;
  14         32  
  14         726  
57 14     14   88 use Cwd ();
  14         31  
  14         240  
58 14     14   74 use DateTime;
  14         31  
  14         339  
59 14     14   76 use DateTime::Format::Strptime;
  14         30  
  14         185  
60             # XXX Remove after debugging
61             # use Devel::Confess;
62 14     14   750 use Digest::MD5 ();
  14         29  
  14         226  
63 14     14   9618 use Digest::SHA ();
  14         33265  
  14         420  
64 14     14   119 use Encode ();
  14         32  
  14         214  
65 14     14   6910 use File::Which ();
  14         15712  
  14         345  
66 14     14   7025 use HTML::Entities ();
  14         76677  
  14         694  
67 14     14   7491 use IO::Select;
  14         19765  
  14         845  
68 14     14   6942 use MIME::Base64 ();
  14         8863  
  14         385  
69 14     14   6832 use Net::Subnet ();
  14         77554  
  14         388  
70 14     14   129 use Nice::Try;
  14         35  
  14         152  
71 14     14   84050702 use Regexp::Common qw( net Apache2 );
  14         41  
  14         258  
72 14     14   4480 use Scalar::Util ();
  14         32  
  14         306  
73 14     14   80 use URI;
  14         28  
  14         1349  
74             ## Will use XS version automatically
75 14     14   9006 use URL::Encode ();
  14         21728  
  14         396  
76 14     14   6822 use URI::Escape::XS ();
  14         33874  
  14         571  
77 14     14   6629 use version;
  14         26128  
  14         92  
78 14         31 our $VERSION = 'v0.2.3';
79 14     14   2227 use constant PERLIO_IS_ENABLED => $Config{useperlio};
  14         41  
  14         2545  
80             ## As of Apache 2.4.41 and mod perl 2.0.11 Apache2::SubProcess::spawn_proc_prog() is not working
81 14     14   107 use constant MOD_PERL_SPAWN_PROC_PROG_WORKING => 0;
  14         27  
  14         2026  
82 14         88184 our $HAS_SSI_RE = qr{<!--#(?:comment|config|echo|elif|else|endif|exec|flastmod|fsize|if|include|perl|printenv|set).*?-->}is;
83             };
84              
85             {
86             ## Compile it beforehand and keep it there
87             our $ATTRIBUTES_RE = qr/
88             (
89             (?<tag_attr>
90             (?:
91             [[:blank:]\h]*
92             (?<attr_name>[\w\-]+)
93             [[:blank:]\h]*
94             =
95             [[:blank:]\h]*
96             ## (?<!\\)(?<attr_val>[^\"\'[:blank:]\h]+)
97             ## (?:(?<!\")|(?<!\'))(?<attr_val>[^[:blank:]\h]+)
98             (?!["'])(?<attr_val>[^[:blank:]\h]+)
99             [[:blank:]\h]*
100             )
101             |
102             (?:
103             [[:blank:]\h]*
104             (?<attr_name>[\w\-]+)
105             [[:blank:]\h]*
106             =
107             [[:blank:]\h]*
108             (?<quote>(?<quote_double>\")|(?<quote_single>\'))
109             (?(<quote_double>)
110             (?<attr_val>(?>\\"|[^"])*+)
111             |
112             (?<attr_val>(?>\\'|[^'])*+)
113             )
114             ## (?>\\["']|[^"'])*+
115             \g{quote}
116             [[:blank:]\h]*
117             )
118             )
119             )
120             /xsm;
121            
122             our $EXPR_RE = qr/
123             (?<tag_attr>
124             \b(?<attr_name>expr)
125             [[:blank:]\h]*\=
126             (?:
127             (?:
128             (?!["'])(?<attr_val>[^[:blank:]\h]+)
129             [[:blank:]\h]*
130             )
131             |
132             (?:
133             [[:blank:]\h]*
134             (?<quote>(?<quote_double>\")|(?<quote_single>\'))
135             (?(<quote_double>)
136             (?<attr_val>(?>\\"|[^"])*+)
137             |
138             (?<attr_val>(?>\\'|[^'])*+)
139             )
140             \g{quote}
141             [[:blank:]\h]*
142             )
143             )
144             )
145             /xsmi;
146            
147             our $SUPPORTED_FUNCTIONS = qr/(base64|env|escape|http|ldap|md5|note|osenv|replace|req|reqenv|req_novary|resp|sha1|tolower|toupper|unbase64|unescape)/i;
148             our $FUNCTION_PARAMETERS_RE = qr/
149             [[:blank:]\h]* # Some possible leading blanks
150             (?:
151             (?:
152             (?<func_quote>(?<func_quote_2>\")|(?<func_quote_1>\')) # quotes used to enclose function parameters
153             (?(<func_quote_2>)
154             (?<func_params>(?>\\"|[^"])*+)
155             |
156             (?<func_params>(?>\\'|[^'])*+)
157             )
158             \g{func_quote}
159             )
160             |
161             (?<func_params>(?>\\\)|[^\)\}])*+) # parameters not surounded by quotes
162             )
163             [[:blank:]\h]* # Some possible trailing blanks
164             /xsm;
165            
166             our $IS_UTF8 = qr/
167             ^(
168             ([\0-\x7F])
169             |
170             ([\xC2-\xDF][\x80-\xBF])
171             |
172             (
173             (
174             ([\xE0][\xA0-\xBF])
175             |
176             ([\xE1-\xEC\xEE-\xEF][\x80-\xBF])
177             |
178             ([\xED][\x80-\x9F])
179             )
180             [\x80-\xBF]
181             )
182             |
183             (
184             (
185             ([\xF0][\x90-\xBF])
186             |
187             ([\xF1-\xF3][\x80-\xBF])
188             |
189             ([\xF4][\x80-\x8F])
190             )
191             [\x80-\xBF][\x80-\xBF]
192             )
193             )*$
194             /x;
195             }
196              
197             ## PerlResponseHandler
198             sub handler : method
199             {
200 0 0 0 0 1 0 if( Scalar::Util::blessed( $_[1] ) && $_[1]->isa( 'Apache2::Filter' ) )
201             {
202 0         0 return( &apache_filter_handler( @_ ) );
203             }
204             else
205             {
206 0         0 return( &apache_response_handler( @_ ) );
207             }
208             }
209              
210             sub ap2perl_expr
211             {
212 245     245 1 489 my $self = shift( @_ );
213 245         350 my $ref = shift( @_ );
214 245         375 my $buf = shift( @_ );
215 245 50       637 return( [] ) if( ref( $ref ) ne 'HASH' );
216 245         365 my $opts = {};
217 245 100       532 if( @_ )
218             {
219 71 0       276 $opts = ref( $_[0] ) eq 'HASH'
    50          
220             ? shift( @_ )
221             : !( @_ % 2 )
222             ? { @_ }
223             : {};
224             }
225 245 50       757 $opts->{skip} = [] if( !exists( $opts->{skip} ) );
226 245 100       655 $opts->{top} = 0 if( !exists( $opts->{top} ) );
227 245 100       570 $opts->{embedded} = 0 if( !exists( $opts->{embedded} ) );
228 245         477 my $type = $ref->{type};
229 245         337 my $stype = '';
230 245 100       624 $stype = $ref->{subtype} if( defined( $ref->{subtype} ) );
231 245         359 my $elems = $ref->{elements};
232 245     0   1947 $self->message( 3, "Processing expression breakdown for type '$type' with subtype '$stype', raw data '$ref->{raw}' and hash: ", sub{ $self->dump( $ref ) });
  0         0  
233              
234 245         4706 my $prev_regexp_capture = $self->{_regexp_capture};
235 245         633 my $r = $self->apache_request;
236 245         4348 my $env = $self->env;
237              
238 245         1320 my $map_binary =
239             {
240             '=' => 'eq',
241             '==' => 'eq',
242             '!=' => 'ne',
243             '<' => 'lt',
244             '<=' => 'le',
245             '>' => 'gt',
246             '>=' => 'ge',
247             };
248             ## In perl, this is inverted, operators used for integers are used for strings and vice versa
249 245         1157 my $map_integer =
250             {
251             'eq' => '==',
252             'ne' => '!=',
253             'lt' => '<',
254             'le' => '<=',
255             'gt' => '>',
256             'ge' => '>=',
257             };
258            
259             ## String and integer comparison are dealt with separately below
260 245 100 66     2528 if( $type eq 'comp' )
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    50          
261             {
262 24         67 my $op = '';
263 24 100       110 $op = $ref->{op} if( defined( $ref->{op} ) );
264 24         187 $self->message( 3, "Processing type '$type' with operator '$op' and raw data '$ref->{raw}'." );
265             ## ==, =, !=, <, <=, >, >=, -ipmatch, -strmatch, -strcmatch, -fnmatch
266 24 100       576 if( $stype eq 'binary' )
    100          
    50          
    100          
    50          
267             {
268 4         28 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
269 4         23 my $this2 = $self->ap2perl_expr( $ref->{wordb_def}->[0], [] );
270 4 100       23 push( @$buf, '!' ) if( $ref->{is_negative} );
271             ## "IP address matches address/netmask"
272 4 100 33     63 if( $op eq 'ipmatch' )
    50          
    50          
273             {
274 2         16 push( @$buf, $self->_ipmatch( $this2->[0], $this1->[0] ) );
275             }
276             ## "left string matches pattern given by right string (containing wildcards *, ?, [])"
277             elsif( $op eq 'strmatch' || $op eq 'fnmatch' )
278             {
279 0         0 push( @$buf, @$this1, qq{=~ /$this2->[0]/} );
280             }
281             ## "same as -strmatch, but case insensitive"
282             elsif( $op eq 'strcmatch' )
283             {
284 0         0 push( @$buf, @$this1, qq{=~ /$this2->[0]/i} );
285             }
286             else
287             {
288 2         17 push( @$buf, @$this1, $map_binary->{ $op }, @$this2 );
289             }
290             }
291             ## 192.168.1.10 in split( /\,/, $ip_list )
292             elsif( $stype eq 'function' )
293             {
294 2         11 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
295 2         7 my $func = $ref->{function_def}->[0];
296 2         7 my $func_name = $func->{name};
297 2         11 my $argv = $self->parse_expr_args( $func->{args_def} );
298 2         24 push( @$buf, sprintf( "scalar( grep( %s eq \$_, ${func_name}\(${argv}\) ) )", $this1->[0] ) );
299             }
300             ## e.g.: %{SOME_VALUE} in {"John", "Peter", "Paul"}
301             elsif( $stype eq 'list' )
302             {
303 0         0 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
304 0         0 my $list = $self->parse_expr_args( $ref->{list_def} );
305 0         0 push( @$buf, sprintf( "scalar( grep( %s eq \$_, (%s) ) )", $this1->[0], $list ) );
306             }
307             elsif( $stype eq 'regexp' )
308             {
309 6         34 $self->message( 3, "Got here in regexp with operator '$op'." );
310 6         1274 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
311 6         29 my $this2 = $self->ap2perl_expr( $ref->{regexp_def}->[0], [] );
312 6         39 my $map =
313             {
314             '=' => '=~',
315             '==' => '=~',
316             '!=' => '!~',
317             };
318 6         23 push( @$buf, @$this1 );
319 6 50       35 push( @$buf, exists( $map->{ $ref->{op} } ) ? $map->{ $ref->{op} } : $ref->{op} );
320 6         23 push( @$buf, @$this2 );
321             }
322             elsif( $stype eq 'unary' )
323             {
324 12         101 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
325 12     0   92 $self->message( 3, "\$ref returned contains: ", sub{ $self->dump( $ref ) });
  0         0  
326 12         224 my $word = join( '', @$this );
327             ## check if the uri is accessible to all
328 12 100 66     409 if( $op eq 'A' || $op eq 'U' )
    50 33        
    50 33        
    50 33        
    100 33        
    100 100        
    50          
329             {
330 2         4 my $url = $word;
331             ## Because we cannot do variable length lookbehind
332 2         13 $self->message( 3, "Checking accessibility of uri '$url'." );
333 2         30 my $res;
334 2         14 my $req = $self->lookup_uri( $url );
335 2 50       9 $self->message( 3, "\$req is not defined: ", $self->error ) if( !defined( $req ) );
336             ## A lookup will give us a code 200, so we need to run it to check if file exists
337             # $self->message( 3, "Returned code is '$rc', \$req->code is ", $req->code, "' and file name '", $req->filename, "'. Is it ok ? (", Apache2::Const::HTTP_OK, ") => ", ( ( $rc == Apache2::Const::HTTP_OK || $rc == Apache2::Const::OK ) ? 'yes' : 'no' ), "." );
338 2         10 my $file = $req->filename;
339 2         9 $self->message( 3, "Checking looked up file name '$file'." );
340 2 100 33     45 if( $req->code != 200 )
    50 33        
341             {
342 1         3 $res = 0;
343             }
344             elsif( -e( "$file" ) && ( ( -f( "$file" ) && -r( "$file" ) ) || ( -d( "$file" ) && -x( "$file" ) ) ) )
345             {
346 1         87 $res = 1;
347             }
348             else
349             {
350 0         0 $res = 0;
351             }
352 2         58 push( @$buf, $res );
353             }
354             ## Those are the same as in perl so we pass through
355             elsif( $op eq 'd' || $op eq 'e' || $op eq 'f' || $op eq 's' )
356             {
357 0         0 push( @$buf, "-${op} ${word}" );
358 0         0 my $file = $req->filename;
359 0         0 $self->message( 3, "Checking looked up file name '$file'." );
360 0         0 my $res = 1;
361 0 0       0 if( $req->code != 200 )
362             {
363 0         0 $res = 0;
364             }
365 0         0 push( @$buf, $res );
366             }
367             elsif( $op eq 'h' || $op eq 'L' )
368             {
369 0         0 push( @$buf, "-l( $word )" );
370             }
371             elsif( $op eq 'F' )
372             {
373 0         0 $self->message( 3, "Checking accessibility of file '$word'." );
374 0         0 my $req = $self->lookup_file( $word );
375             }
376             elsif( $op eq 'n' || $op eq 'z' )
377             {
378             ## Because we cannot do variable length lookbehind
379 5 100       47 push( @$buf, ( $op eq 'z' ? '!' : '' ) . "length( ${word} )" );
380             }
381             ## <!--#if expr='-R "134.28.200"' -->
382             elsif( $op eq 'R' )
383             {
384 2         13 my $ip = $self->remote_ip;
385 2         6 my $subnet = $word;
386             ## We need to be careful because the subnet provided may ver well be
387             ## a function or something else, and we would not want to surround
388             ## it with double quotes.
389 2 50       10 if( $self->_is_ip( $subnet ) )
390             {
391 0         0 $subnet = qq{"$subnet"};
392             }
393              
394 2         1568 $self->message( 3, "Checking ip '$ip' against subnet '$subnet'." );
395 2         56 push( @$buf, qq{\$self->_ipmatch( $subnet, "$ip" )} );
396             }
397             elsif( $op eq 'T' )
398             {
399 3         21 $self->message( 3, "Checking if word '$word' is true." );
400             ## Because we cannot do variable length lookbehind
401 3 50       56 my $val = length( $word )
402             ? $word
403             : '';
404 3 50       28 $val = $self->parse_eval_expr( $val ) if( length( $val ) );
405 3         19 $self->message( 3, "word is now, after being eval'ed: '$val'." );
406 3         49 $val = lc( $val );
407 3         6 my $res;
408 3 100 66     43 if( $val eq '' || $val eq '0' || $val eq 'off' || $val eq 'false' || $val eq 'no' )
      100        
      66        
      66        
409             {
410 2         5 $res = 0;
411             }
412             else
413             {
414 1         3 $res = 1;
415             }
416 3         15 push( @$buf, $res );
417             }
418             }
419             }
420             elsif( $type eq 'cond' )
421             {
422 48 50       379 if( $stype eq 'and' )
    100          
    100          
    100          
    100          
    50          
    0          
423             {
424 0         0 my $this1 = $self->ap2perl_expr( $ref->{and_def_expr2}->[0], [] );
425 0         0 my $this2 = $self->ap2perl_expr( $ref->{and_def_expr2}->[0], [] );
426 0         0 push( @$buf, @$this1, '&&', @$this2 );
427             }
428             elsif( $stype eq 'boolean' )
429             {
430 9 100       32 push( @$buf, $ref->{booltype} eq 'true' ? 1 : 0 );
431             }
432             elsif( $stype eq 'or' )
433             {
434 1         6 my $this1 = $self->ap2perl_expr( $ref->{or_def_expr1}->[0], [] );
435 1         6 my $this2 = $self->ap2perl_expr( $ref->{or_def_expr2}->[0], [] );
436 1         6 push( @$buf, @$this1, '||', @$this2 );
437             }
438             elsif( $stype eq 'comp' )
439             {
440 30         207 my $this = $self->ap2perl_expr( $ref->{elements}->[0], [] );
441 30         120 push( @$buf, @$this );
442             }
443             elsif( $stype eq 'negative' )
444             {
445 7         77 my $this = $self->ap2perl_expr( $ref->{negative_def}->[0], [] );
446 7         50 push( @$buf, '!(', @$this, ')' );
447             }
448             elsif( $stype eq 'parenthesis' )
449             {
450 1         48 my $this = $self->ap2perl_expr( $ref->{parenthesis_def}->[0], [] );
451 1         6 push( @$buf, '(', @$this, ')' );
452             }
453             elsif( $stype eq 'variable' )
454             {
455 0         0 my $this = $self->ap2perl_expr( $ref->{variable_def}->[0], [] );
456 0         0 push( @$buf, @$this );
457             }
458             }
459             elsif( $type eq 'function' )
460             {
461 13         51 my $func = $ref->{name};
462 13 50       53 warn( "\$func is not defined! Hash refernece \$ref contains: ", $self->dump( $ref ), "\n" ) if( !defined( $func ) );
463             ## parse_expr_args returns a string of comma separated arguments
464 13         120 my $argv = $self->parse_expr_args( $ref->{args_def} );
465             ## https://httpd.apache.org/docs/current/expr.html
466             ## Functions
467             ## Example:
468             ## base64('Tous les êtres humains naissent libres (et égaux) en dignité et en droits.')
469             ## base64("Tous les êtres humains naissent libres et égaux en dignité et en droits.")
470             ## base64( $QUERY_STRING )
471             ## %{base64:'Tous les êtres humains naissent libres et égaux en dignité et en droits.'}
472             ## %{base64:"Tous les êtres humains naissent libres (et égaux) en dignité et en droits."}
473             ## Is this a standard Apache2 function ?
474 13 50       518 if( $func =~ /^$SUPPORTED_FUNCTIONS$/i )
475             {
476 13         103 $self->message( 3, "Calling function 'parse_func_${func}' with arguments '$argv'." );
477 13         297 push( @$buf, "\$self->parse_func_${func}( ${argv} )" );
478             }
479             else
480             {
481 0         0 push( @$buf, "${func}( ${argv} )" );
482             }
483             }
484             elsif( $type eq 'integercomp' )
485             {
486 3         25 my $op = $ref->{op};
487 3         13 my $op_actual = '';
488 3 50       18 if( !exists( $map_integer->{ $op } ) )
489             {
490 0         0 warn( "Unknown operator \"${op}\" for integer comparison in \"$ref->{raw}\".\n" );
491 0         0 $op_actual = $op;
492             }
493             else
494             {
495 3         8 $op_actual = $map_integer->{ $op };
496             }
497 3         15 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
498 3         13 my $this2 = $self->ap2perl_expr( $ref->{wordb_def}->[0], [] );
499 3         12 push( @$buf, @$this1, $op_actual, @$this2 );
500             }
501             elsif( $type eq 'join' )
502             {
503 0         0 my $argv = $self->parse_expr_args( $ref->{list_def} );
504 0 0       0 if( $ref->{word_def} )
505             {
506 0         0 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
507 0         0 push( @$buf, 'join(', @$this1, ',', $argv, ')' );
508             }
509             else
510             {
511 0         0 push( @$buf, q{join('', }, $argv, ')' );
512             }
513             }
514             elsif( $type eq 'listfunc' )
515             {
516 0         0 my $func = $ref->{name};
517 0         0 my $args = $ref->{args_def};
518 0         0 my $argv = $self->parse_expr_args( $args );
519 0 0       0 if( $func =~ /^$SUPPORTED_FUNCTIONS$/i )
520             {
521 0         0 $self->message( 3, "Calling function 'parse_func_${func}' with arguments '$argv'." );
522 0         0 push( @$buf, "\$self->parse_func_${func}( ${argv} )" );
523             }
524             else
525             {
526 0         0 push( @$buf, "${func}( ${argv} )" );
527             }
528             }
529             elsif( $type eq 'regany' )
530             {
531             ## Apache2 regular expressions work asis in perl, because they are PCRE
532 0         0 push( @$buf, $ref->{raw} );
533             }
534             elsif( $type eq 'regex' )
535             {
536             ## Apache2 regular expressions work asis in perl, because they are PCRE
537 8         26 push( @$buf, $ref->{raw} );
538             }
539             elsif( $type eq 'regsub' )
540             {
541 0         0 push( @$buf, $ref->{raw} );
542             }
543             elsif( $type eq 'split' )
544             {
545 0         0 my $regex = $ref->{regex};
546 0         0 my $this;
547 0 0       0 if( $ref->{word_def} )
    0          
548             {
549 0         0 $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
550             }
551             elsif( $ref->{list_def} )
552             {
553 0         0 $this = $self->ap2perl_expr( $ref->{list_def}->[0], [] );
554             }
555 0         0 push( @$buf, 'split(', $regex, ',', @$this, ')' );
556             }
557             elsif( $type eq 'string' && $opts->{skip} ne 'string' )
558             {
559             ## Search string for embedded variables
560 10         31 my $this = $ref->{raw};
561             ## my $reType = $self->legacy ? 'Legacy' : $self->trunk ? 'Trunk' : '';
562 10 50       55 my $reType = $self->trunk ? 'Trunk' : 'Legacy';
563             # $self->message( 3, qq[Using regex of type '$reType' for embedded variable finding: $RE{Apache2}{"${reType}Variable"}] );
564 10         505 $self->message( 3, qq[Using regex of type '$reType' for embedded variable] );
565 10         224 $this =~ s
566             {
567             $RE{Apache2}{"${reType}Variable"}
568             }
569 3         2924 {
570 3         35 my $var = $+{variable};
571 3         90 $self->message( 3, "Parsing variable $+{variable} embedded into string." );
572 3   50     13 my $res = $self->parse_expr( $var, { embedded => 1 } );
573 3         37 $res //= '';
574             $res;
575 10 100       11421 }gexis;
576             if( $opts->{top} )
577 8         58 {
578             push( @$buf, 'qq{' . $this . '}' );
579             }
580             else
581 2         14 {
582             push( @$buf, $this );
583             }
584             }
585             elsif( $type eq 'stringcomp' )
586 13         47 {
587 13         36 my $op = $ref->{op};
588 13 50       58 my $op_actual = '';
589             if( !exists( $map_binary->{ $op } ) )
590 0         0 {
591 0         0 warn( "Unknown operator \"${op}\" for integer comparison in \"$ref->{raw}\".\n" );
592             $op_actual = $op;
593             }
594             else
595 13         38 {
596             $op_actual = $map_binary->{ $op };
597 13         103 }
598 13         64 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
599 13         49 my $this2 = $self->ap2perl_expr( $ref->{wordb_def}->[0], [] );
600             push( @$buf, @$this1, $op_actual, @$this2 );
601             }
602             elsif( $type eq 'sub' )
603 0         0 {
604 0         0 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
605             push( @$buf, @$this, '=~', $ref->{regsub} );
606             }
607             elsif( $type eq 'variable' )
608 36         96 {
609 36 100       179 my $var_name = $ref->{name};
    100          
    50          
610             if( $stype eq 'function' )
611 1         8 {
612             $self->message( 3, "Got here for function name '$ref->{name}'." );
613 1         18 # push( @$buf, $ref->{name} . '(' . $self->parse_expr_args( $ref->{args_def} ) . ')' );
614 1         6 $ref->{type} = 'function';
615 1         4 my $this = $self->ap2perl_expr( $ref, [] );
616             push( @$buf, @$this );
617             }
618             elsif( $stype eq 'rebackref' )
619 1         8 {
620 1         19 $self->message( 3, "Got here for back reference value '$ref->{value}'." );
621 1         6 my $val = $prev_regexp_capture->[ int( $ref->{value} ) - 1 ];
622 1 50       19 $self->message( 3, "Found regex back reference value '$val'." );
623             push( @$buf, $self->_is_number( $val ) ? $val : "q{" . $val . "}" );
624             # push( @$buf, $val );
625             }
626             elsif( $stype eq 'variable' )
627 34         259 {
628 34         616 $self->message( 3, "\${}: Is there environment variable '$var_name'? '", $env->{ $var_name }, "'." );
629 34 100 66     234 my $try = '';
630             if( !length( $try ) && length( $env->{ $var_name } ) )
631 13         38 {
632             $try = $env->{ $var_name };
633 34 50 66     129 }
  21         205  
634             if( !length( $try ) && defined( ${ "main\::${var_name}" } ) )
635 0         0 {
  0         0  
636             $try = ${ "main\::${var_name}" };
637             }
638 34 100       108 ## Last resort
639             if( !length( $try ) )
640 21         120 {
641             $try = $self->parse_echo({ var => $var_name });
642 34 100       113 }
643             if( !length( $try ) )
644 20         46 {
645             $try = '${' . $var_name . '}';
646             }
647             else
648 14 100 100     139 {
649             $try = 'q{' . $try . '}' unless( $self->_is_number( $try ) || $opts->{embedded} );
650 34         5129 }
651             push( @$buf, $try );
652             }
653             else
654 0         0 {
655             warn( "Unknown subtype '$stype' in variable with Apache2::Expression data being: ", $self->dump( $ref ), "\n" );
656             }
657             }
658             elsif( $type eq 'word' )
659 46         231 {
660 46 100       1014 $self->message( 3, "Got here with type '$type' and sub type '$stype'" );
    100          
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
661             if( $stype eq 'digits' )
662 2         7 {
663             push( @$buf, $ref->{value} );
664             }
665             elsif( $stype eq 'ip' )
666 5         20 {
667             push( @$buf, "'" . $ref->{value} . "'" );
668             }
669             elsif( $stype eq 'dotted' )
670 0         0 {
671 0         0 $self->message( 3, "Adding '", 'q{' . $ref->{word} . '}', "." );
672             push( @$buf, 'q{' . $ref->{word} . '}' );
673             }
674             elsif( $stype eq 'function' )
675 0         0 {
676 0         0 my $def = $ref->{function_def}->[0];
677             push( @$buf, $def->{name} . '(' . $self->parse_expr_args( $def ) . ')' );
678             }
679             elsif( $stype eq 'join' )
680 0         0 {
681 0         0 my $this = $self->ap2perl_expr( $ref->{join_def}->[0], [] );
682             push( @$buf, @$this );
683             }
684             elsif( $stype eq 'parens' )
685 0         0 {
686 0         0 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
687             push( @$buf, '(' . $this->[0] . ')' );
688             }
689             elsif( $stype eq 'quote' )
690 39         179 {
691             push( @$buf, $ref->{quote} . $ref->{word} . $ref->{quote} );
692             }
693             elsif( $stype eq 'rebackref' )
694 0         0 {
695             push( @$buf, $prev_regexp_capture->[ int( $ref->{value} ) - 1 ] );
696             }
697             elsif( $stype eq 'regex' )
698             {
699 0         0 ## Apache2 regular expressions are PCRE so we use them asis
700             push( @$buf, $ref->{regex} );
701             }
702             elsif( $stype eq 'sub' )
703 0         0 {
704 0         0 my $this = $self->ap2perl_expr( $ref->{sub_def}->[0], [] );
705             push( @$buf, @$this );
706             }
707             elsif( $stype eq 'variable' )
708 0         0 {
709 0         0 my $this = $self->ap2perl_expr( $ref->{variable_def}->[0], [] );
710             push( @$buf, @$this );
711             }
712             }
713             elsif( $type eq 'words' )
714 44 100       159 {
715             if( length( $ref->{list} ) )
716             {
717 1         4 # my $this2 = $self->ap2perl_expr( $ref->{list_def}->[0], [] );
718             my $tmp = [];
719 1         13 ## We go through each element of the list which can be composed of string, function or other
720 1 50       13 my $all_string = 1;
721             if( ref( $ref->{words_def} ) )
722 1         2 {
  1         9  
723             foreach my $that ( @{$ref->{words_def}} )
724 3 50 33     27 {
      33        
725 3         11 $all_string = 0 unless( $that->{type} eq 'string' || $that->{type} eq 'word' || $that->{type} eq 'variable' );
726 3         9 my $this = $self->ap2perl_expr( $that, [] );
727             push( @$tmp, @$this );
728 1 50       7 }
729             push( @$buf, $all_string ? 'q{' . $ref->{list} . '}' : join( ',', @$tmp ) );
730             }
731             else
732 0         0 {
733 0         0 my $this = $self->ap2perl_expr( $ref->{list_def}->[0], [] );
734             push( @$buf, @$this );
735             }
736             }
737             else
738 43         1043 {
739 43         145 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
740             push( @$buf, @$this );
741             }
742 245     0   1668 }
  0         0  
743 245         5151 $self->message( 3, "Returning ", scalar( @$buf ), " items in array ref: ", sub{ $self->dump( $buf ) } );
744             return( $buf );
745             }
746              
747             sub apache_response_handler
748 0     0 1 0 {
749 0         0 my( $class, $r ) = @_;
750 0 0       0 my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
751 0 0       0 $r->log->debug( "${class} [PerlResponseHandler]: Received request for uri '", $r->uri, "' with path info '", $r->path_info, "' and file name '", $r->filename, "', content type is '", $r->content_type, "' and arguments: '", join( "', '", @_ ), "'." ) if( $debug > 0 );
752 0         0 return( Apache2::Const::DECLINED ) unless( $r->content_type eq 'text/html' );
753 0 0       0 $r->status( Apache2::Const::HTTP_OK );
754             $r->no_cache(1) if( ( $r->dir_config( 'Apache2_SSI_NO_CACHE' ) ) eq 'on' );
755             # $r->sendfile( $r->filename );
756             # return( Apache2::Const::OK );
757 0         0  
758             my $params =
759             {
760             apache_filter => $r->output_filters,
761             apache_request => $r,
762             debug => 3,
763 0         0 };
764 0         0 my $val;
765             my $map =
766             {
767             DEBUG => 'debug',
768             Echomsg => 'echomsg',
769             Errmsg => 'errmsg',
770             Sizefmt => 'sizefmt',
771             Timefmt => 'timefmt',
772 0         0 };
773             foreach my $key ( keys( %$map ) )
774 0 0       0 {
775             if( length( $val = $r->dir_config( "Apache2_SSI_${key}" ) ) )
776 0         0 {
777             $params->{ $map->{ $key } } = $val;
778             }
779 0 0       0 }
    0          
780             if( $r->dir_config( 'Apache2_SSI_Expression' ) eq 'legacy' )
781 0         0 {
782             $params->{legacy} = 1;
783             }
784             elsif( $r->dir_config( 'Apache2_SSI_Expression' ) eq 'trunk' )
785 0         0 {
786             $params->{trunk} = 1;
787             }
788             ## new(9 will automatically set the value for uri() based on the Apache2::RequestRec->unparsed_uri
789 0   0     0 my $self = $class->new( $params ) || do
790             {
791             $r->log->error( "Error instantiating ${class}: ", $class->error );
792             return( Apache2::Const::DECLINED );
793             };
794            
795 0   0     0 my $u = $self->uri || do
796             {
797             $r->log->error( "No URI set. This should not happen." );
798             $r->status( Apache2::Const::HTTP_INTERNAL_SERVER_ERROR );
799             return( Apache2::Const::OK );
800 0 0       0 };
801             unless( $u->code == Apache2::Const::HTTP_OK )
802 0         0 {
803 0         0 $r->log->error( "Cannot server uri \"$u\". http code is \"", $u->code, "\"." );
804 0         0 $r->status( $u->code );
805             return( Apache2::Const::DECLINED );
806 0         0 }
807 0   0     0 my $file = $u->filename;
808 0 0       0 my $max_length = int( $r->dir_config( 'Apache2_SSI_Max_Length' ) ) || 0;
809             if( -s( $file ) >= $max_length )
810 0         0 {
811 0         0 $r->log->error( "HTML data exceeds our size threshold of $max_length. Rejecting the request." );
812 0         0 $r->status( Apache2::Const::HTTP_REQUEST_ENTITY_TOO_LARGE );
813             return( Apache2::Const::OK );
814 0         0 }
815 0 0       0 my $html = $u->slurp_utf8;
816             if( !length( $html ) )
817 0         0 {
818 0         0 $r->status( Apache2::Const::HTTP_NO_CONTENT );
819             return( Apache2::Const::OK );
820             }
821            
822             ## my $addr = $r->useragent_addr;
823 0         0 ## $self->message( 3, "Remote addr is: '$addr' (", $$addr, ")." );
824 0     0   0 $self->message( 3, "\$ENV{MOD_PERL} value is '$ENV{MOD_PERL}' and Apache2::Const::HTTP_OK value is '", Apache2::Const::HTTP_OK, "'" );
  0         0  
825 0         0 $self->message( 3, "Remote connection from '", sub{ $self->remote_ip }, "' for uri '", $r->uri, "'." );
826 0 0       0 my $res = $self->parse( $html );
827             if( !defined( $res ) )
828 0         0 {
829 0         0 $r->log->error( "${class} is unable to process data: ", $self->error );
830             return( Apache2::Const::DECLINED );
831             }
832             else
833 0         0 {
834 0     0   0 try
835 0         0 {
836             $res = Encode::encode( 'utf8', $res, Encode::FB_CROAK );
837 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
838 0     0   0 catch( $e )
839 0         0 {
840 0         0 $r->log->error( "${class} encountered an error while trying to encode data into utf8: $e" );
841 0 0 0     0 return( Apache2::Const::DECLINED );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
842             }
843 0         0
844 0         0 my $len = length( $res );
845 0         0 $self->message( 3, "Returning ${len} bytes of html data: '$res'" );
846 0     0   0 try
847 0         0 {
848 0         0 $r->headers_out->set( 'Content-Length' => $len );
849 0         0 my $sent = $r->print( $res );
850             $self->message( 3, "${sent} bytes sent out." );
851 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
852 0     0   0 catch( $e )
853 0         0 {
854 0 0 0     0 $r->log->error( "${class} encountered an error while sending resulting data via Apache2::Filter->print: $e" );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
855 0         0 }
856             return( Apache2::Const::OK );
857             }
858             }
859              
860             ## https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_
861             ## sub handler : FilterRequestHandler
862             ## sub handler : method
863             sub apache_filter_handler
864             {
865 0     0 1 0 ## my( $class, $f, $brigade, $mode, $type, $len ) = @_;
866 0         0 my( $class, $f, $bb ) = @_;
867             my $r = $f->r;
868 0 0       0 ## my $class = __PACKAGE__;
869 0 0 0     0 my $main = $r->is_initial_req ? $r : $r->main;
870 0         0 return( Apache2::Const::DECLINED ) unless( $r->is_initial_req && $main->content_type eq 'text/html' );
871 0 0       0 my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
872 0 0       0 $main->no_cache(1) if( ( $r->dir_config( 'Apache2_SSI_NO_CACHE' ) ) eq 'on' );
873             $r->log->debug( "${class} [PerlOutputFilterHandler]: Received request for uri '", $r->uri, "' with path info '", $r->path_info, "'." ) if( $debug > 0 );
874 0         0
875 0 0       0 my $ctx = $f->ctx;
876             unless( $ctx->{invoked} )
877 0 0       0 {
878 0         0 $r->log->debug( "${class} [PerlOutputFilterHandler]: First time invoked, removing Content-Length header currently set to '", $r->headers_out->get( 'Content-Length' ), "'." ) if( $debug > 0 );
879             $r->headers_out->unset( 'Content-Length' );
880             }
881            
882             ## Then, we might get called multiple time, since there may be multiple brigade buckets
883 0 0       0 ## Here, we retrieve the last buffer we put in $f->ctx->{data} if any
884 0 0       0 my $html = exists( $ctx->{data} ) ? $ctx->{data} : '';
885 0         0 $r->log->debug( "${class} [PerlOutputFilterHandler]: HTML data buffer set to '$html'." ) if( $debug > 0 );
886 0         0 $ctx->{invoked}++;
887 0   0     0 my $seen_eos = 0;
888 0 0       0 my $max_length = int( $r->dir_config( 'Apache2_SSI_Max_Length' ) ) || 0;
889             $r->log->debug( "${class} [PerlOutputFilterHandler]: Maximum length set to '$max_length'." ) if( $debug > 0 );
890 0         0 ## Get all the brigade buckets data
891             for( my $b = $bb->first; $b; $b = $bb->next( $b ) )
892 0 0       0 {
893 0         0 $seen_eos++, last if( $b->is_eos );
894 0         0 $b->read( my $bdata );
895 0 0 0     0 $html .= $bdata;
896             return( Apache2::Const::DECLINED ) if( $max_length && length( $html ) >= $max_length );
897             }
898            
899 0 0       0 ## If we have not reached the special End-of-String bucket, we store our buffer in $f->ctx->{data} and return OK
900             if( !$seen_eos )
901             {
902 0 0       0 ## store context for all but the last invocation
903 0         0 $r->log->debug( "${class} [PerlOutputFilterHandler]: Not reached the EOS bucket. Storing html to data buffer." ) if( $debug > 0 );
904 0         0 $ctx->{data} = $html;
905 0         0 $f->ctx( $ctx );
906             return( Apache2::Const::OK );
907             }
908            
909 0 0       0 ## Let's behave well as per the doc
910             if( $f->c->keepalive == Apache2::Const::CONN_KEEPALIVE )
911 0 0       0 {
912 0         0 $r->log->debug( "${class} [PerlOutputFilterHandler]: KeepAlive count (", $f->c->keepalive, ") reached the threshold of '", Apache2::Const::CONN_KEEPALIVE, "'." ) if( $debug > 0 );
913 0         0 $ctx->{data} = '';
914             $f->ctx( $ctx );
915             }
916 0         0
917 0         0 my $size = length( $html );
918 0         0 $ctx->{data} = '';
919 0         0 $ctx->{invoked} = 0;
920 0 0       0 $f->ctx( $ctx );
921             if( $size == 0 )
922 0         0 {
923 0         0 $r->log->debug( "${class} [PerlOutputFilterHandler]: Data received is empty. Nothing to do." );
924             return( Apache2::Const::OK );
925 0         0 }
926 0     0   0 try
927 0         0 {
928             $html = Encode::decode( 'utf8', $html, Encode::FB_CROAK );
929 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
930 0     0   0 catch( $e )
931 0         0 {
932 0         0 $r->log->error( "${class} [PerlOutputFilterHandler]: Failed to decode data from utf8: $e" );
933 0 0 0     0 return( Apache2::Const::DECLINED );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
934             }
935            
936 0 0       0 #W We just add that the charset is utf-8
937             $main->content_type( 'text/html; charset=utf-8' ) unless( $main->content_type =~ /\bcharset\n/i );
938 0         0
939             my $params =
940             {
941             apache_filter => $f,
942             apache_request => $r,
943             debug => 3,
944 0         0 };
945 0         0 my $val;
946             my $map =
947             {
948             DEBUG => 'debug',
949             Echomsg => 'echomsg',
950             Errmsg => 'errmsg',
951             Sizefmt => 'sizefmt',
952             Timefmt => 'timefmt',
953 0         0 };
954             foreach my $key ( keys( %$map ) )
955 0 0       0 {
956             if( length( $val = $r->dir_config( "Apache2_SSI_${key}" ) ) )
957 0         0 {
958             $params->{ $map->{ $key } } = $val;
959             }
960 0 0       0 }
    0          
961             if( $r->dir_config( 'Apache2_SSI_Expression' ) eq 'legacy' )
962 0         0 {
963             $params->{legacy} = 1;
964             }
965             elsif( $r->dir_config( 'Apache2_SSI_Expression' ) eq 'trunk' )
966 0         0 {
967             $params->{trunk} = 1;
968 0 0       0 }
969             $r->log->debug( "${class} [PerlOutputFilterHandler]: Creating a ${class} object." ) if( $debug > 0 );
970 0   0     0 my $self = $class->new( $params ) || do
971             {
972             $r->log->error( "Error instantiating ${class}: ", $class->error );
973             return( Apache2::Const::DECLINED );
974             };
975             ## my $addr = $r->useragent_addr;
976 0         0 ## $self->message( 3, "Remote addr is: '$addr' (", $$addr, ")." );
977 0     0   0 $self->message( 3, "\$ENV{MOD_PERL} value is '$ENV{MOD_PERL}' and Apache2::Const::HTTP_OK value is '", Apache2::Const::HTTP_OK, "'" );
  0         0  
978 0         0 $self->message( 3, "Remote connection from '", sub{ $self->remote_ip }, "' for uri '", $r->uri, "'." );
979 0 0       0 my $res = $self->parse( $html );
980             if( !defined( $res ) )
981 0         0 {
982 0         0 $r->log->error( "${class} [PerlOutputFilterHandler]: is unable to process data: ", $self->error );
983             return( Apache2::Const::DECLINED );
984             }
985             else
986 0         0 {
987 0     0   0 try
988 0         0 {
989             $res = Encode::encode( 'utf8', $res, Encode::FB_CROAK );
990 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
991 0     0   0 catch( $e )
992 0         0 {
993 0         0 $r->log->error( "${class} [PerlOutputFilterHandler]: encountered an error while trying to encode data into utf8: $e" );
994 0 0 0     0 return( Apache2::Const::DECLINED );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
995             }
996            
997 0         0 # $r->headers_out->unset( 'Content-Length' );
998 0         0 my $len = length( $res );
999 0         0 $self->message( 3, "Returning ${len} bytes of html data: '$res'" );
1000 0     0   0 try
1001 0         0 {
1002 0         0 $r->headers_out->set( 'Content-Length' => $len );
1003 0 0       0 my $sent = $f->print( "$res" );
1004             $r->log->debug( "${class} [PerlOutputFilterHandler]: ${sent} bytes sent out." ) if( $debug > 0 );
1005 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1006 0     0   0 catch( $e )
1007             {
1008 0         0 ## $self->message( 3, "An error has occured print data to web client: $e" );
1009 0 0 0     0 $r->log->error( "${class} encountered an error while sending resulting data via Apache2::Filter->print: $e" );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1010             }
1011             ## This will cause a segfault
1012 0         0 ## $r->rflush;
1013             return( Apache2::Const::OK );
1014             }
1015             }
1016              
1017             sub init
1018 62     62 1 16819 {
1019 62         189 my $self = shift( @_ );
1020 62         184 my $class = ref( $self );
1021 62 50       269 my $args = {};
1022             if( scalar( @_ ) )
1023 14     14   209 {
  14         33  
  14         52221  
1024 62 50       836 no warnings 'uninitialized';
    50          
1025             $args = Scalar::Util::reftype( $_[0] ) eq 'HASH'
1026             ? shift( @_ )
1027             : !( scalar( @_ ) % 2 )
1028             ? { @_ }
1029             : {};
1030 62   50     304 }
1031 62         1080 my $uri = delete( $args->{document_uri} ) // '';
1032 62         217 $self->{html} = '';
1033 62         199 $self->{apache_filter} = '';
1034 62         178 $self->{apache_request} = '';
1035             $self->{document_root} = '';
1036 62         196 ## e.g.: [Value Undefined]
1037 62         187 $self->{echomsg} = '';
1038 62         172 $self->{errmsg} = '[an error occurred while processing this directive]';
1039 62         241 $self->{filename} = '';
1040 62         157 $self->{legacy} = 0;
1041 62         191 $self->{trunk} = 0;
1042 62         162 $self->{remote_ip} = '';
1043 62         145 $self->{sizefmt} = 'abbrev';
1044 62         159 $self->{timefmt} = undef;
1045 62         299 $self->{_init_strict_use_sub} = 1;
1046 62 50       467 $self->{_init_params_order} = [qw( apache_filter apache_request document_root document_uri )];
1047 62         2441 $self->SUPER::init( %$args ) || return;
1048 62         206 $self->{_env} = '';
1049             $self->{_path_info_processed} = 0;
1050             ## Used to hold regular expression matches during eval in _eval_vars()
1051 62         183 ## and make them available for the next evaluation
1052 62         154 $self->{_regexp_capture}= [];
1053             $self->{_uri_reset} = 0;
1054             ## A stack reflecting the current state of if/else parser.
1055             ## Each entry is 1 when we've seen a true condition in this if-chain,
1056             ## 0 when we haven't. Initially it's as if we're in a big true
1057 62         185 ## if-block with no else.
1058 62         195 $self->{if_state} = [1];
1059 62         193 $self->{notes} = '';
1060             $self->{suspend} = [0];
1061 62 50       473 ## undef means the current locale's default
1062 62         2572 $self->mod_perl( defined( $MOD_PERL ) ? length( $MOD_PERL ) > 0 : 0 );
1063 62 50 33     1198 my $r = $self->apache_request;
1064             if( $MOD_PERL && !$r )
1065             {
1066 0 0       0 # XXX Must check if GlobalRequest is set
1067             if( !( $r = $self->apache_request ) )
1068 0         0 {
1069 0 0       0 $r = Apache2::RequestUtil->request;
1070             if( $r )
1071 0         0 {
1072 0         0 $self->apache_request( $r );
1073             $self->apache_filter( $r->input_filters );
1074             }
1075             else
1076 0         0 {
1077             print( STDERR "${class} seems to be running under modperl version '$MOD_PERL', but could not get the Apache2::RequestRec object via Apache2::RequestUtil->request(). You need to enable GlobalRequest in your VirtualHost with: PerlOptions +GlobalRequest\n" );
1078             }
1079             }
1080 62         137 }
1081             my $p = {};
1082 62 50       252 ## $self->message( 3, "Apache request object is: '$r' and \$uri is '$uri'. Args is: ", sub{ $self->dump( $args ) } );
    0          
    0          
1083             if( length( "$uri" ) )
1084 62         227 {
1085             $p->{document_uri} = "$uri";
1086             }
1087             elsif( $r )
1088 0         0 {
1089             $p->{document_uri} = $r->unparsed_uri;
1090             }
1091             elsif( length( $self->env( 'DOCUMENT_URI' ) ) )
1092 0         0 {
1093             $p->{document_uri} = $self->env( 'DOCUMENT_URI' );
1094             }
1095             else
1096 0         0 {
1097             $p->{document_uri} = $self->env( 'REQUEST_URI' );
1098             }
1099            
1100 62 50       234 ## $self->message( 3, "Document root is ($self->{document_root}) and Apache document root is '", ( $r ? $r->document_root : '' ), "'." );
    0          
1101             if( length( $self->{document_root} ) )
1102 62         153 {
1103             $p->{document_root} = $self->{document_root};
1104             }
1105             elsif( $r )
1106 0         0 {
1107             $p->{document_root} = $r->document_root;
1108             }
1109             else
1110 0         0 {
1111             $self->env( 'DOCUMENT_ROOT' );
1112             }
1113 62         149
1114 62 50       196 $p->{debug} = $self->{debug};
1115 62 50 33     528 $p->{apache_request} = $r if( $r );
    0          
    0          
1116             if( length( "$p->{document_uri}" ) && length( "$p->{document_root}" ) )
1117 62   50     531 {
1118             my $u = Apache2::SSI::URI->new( $p ) ||
1119 62         268 return( $self->error( "Unable to instantiate an Apache2::SSI::URI object with document uri \"$p->{document_uri}\" and document root \"$p->{document_root}\": ", Apache2::SSI::URI->error ) );
1120             $self->{uri} = $u;
1121             }
1122             elsif( !length( "$p->{document_root}" ) )
1123 0         0 {
1124             return( $self->error( "No document root ($p->{document_root}) value was provided." ) );
1125             }
1126             elsif( !length( "$p->{document_uri}" ) )
1127 0         0 {
1128             return( $self->error( "No document uri ($p->{document_uri}) value was provided." ) );
1129             }
1130             else
1131 0         0 {
1132             return( $self->error( "No document uri ($p->{document_uri}) nor document root ($p->{document_root}) value were provided." ) );
1133 62         162 }
1134 62 50       770 my $notes;
1135 62         209 $notes = Apache2::SSI::Notes->new( debug => $self->{debug} ) if( Apache2::SSI::Notes->supported );
1136 62         633 $self->{notes} = $notes;
1137             return( $self );
1138             }
1139 4     4 1 19  
1140             sub apache_filter { return( shift->_set_get_object_without_init( 'apache_filter', 'Apache2::Filter', @_ ) ); }
1141 952     952 1 2594  
1142             sub apache_request { return( shift->_set_get_object_without_init( 'apache_request', 'Apache2::RequestRec', @_ ) ); }
1143              
1144             sub clone
1145 4     4 1 11 {
1146 4   33     17 my $self = shift( @_ );
1147 4         23 my $class = ref( $self ) || $self;
1148 4         10 my @copy = qw( debug echomsg errmsg remote_ip sizefmt timefmt );
1149 4         38 my $params = {};
1150 4 50       21 @$params{ @copy } = @$self{ @copy };
1151 4 50       89 $params->{apache_filter} = $self->apache_filter if( $self->apache_filter );
1152 4         84 $params->{apache_request} = $self->apache_request if( $self->apache_request );
1153 4         18 $params->{document_uri} = $self->uri->document_uri;
1154 4         14 $params->{document_root} = $self->document_root;
1155 4   50     106 $self->message( 3, "Current document root is '", $self->document_root, "' ($self->{document_root})" );
1156 4         38 my $new = $class->new( %$params ) || return( $self->error( "Unable to create a clone of our object: ", $class->error ) );
1157             return( $new );
1158             }
1159              
1160             sub decode_base64
1161 1     1 1 3 {
1162 1         3 my $self = shift( @_ );
1163 1     1   2 try
1164 1         4 {
1165 1         7 my $v = join( '', @_ );
1166 1 50       24 $self->message( 3, "Decoding: '$v'." );
1167             if( $self->mod_perl )
1168 0         0 {
1169             $v = APR::Base64::decode( $v );
1170             }
1171             else
1172 1         53 {
1173             $v = MIME::Base64::decode( $v );
1174 1 50       6 }
1175 1 50       64 $v = Encode::decode( 'utf8', $v ) if( $self->_has_utf8( $v ) );
1176 1         21 $self->message( 3, "Returning: '", $v, "'. Does data contain utf8? ", ( $self->_has_utf8( $v ) ? 'yes' : 'no' ) );
1177             return( $v );
1178 1 50       8 }
  0 50       0  
  1 50       4  
  1 0       3  
  1 50       4  
  1         2  
  1         3  
  1         3  
  1         7  
  0         0  
  1         3  
  0         0  
  1         5  
  1         3  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
1179 0     0   0 catch( $e )
1180 0         0 {
1181 0 0 33     0 return( $self->error( "Error while decoding base64 data: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  1         12  
  1         24  
1182             }
1183             }
1184              
1185             sub decode_entities
1186 0     0 1 0 {
1187 0         0 my $self = shift( @_ );
1188 0     0   0 try
1189 0         0 {
1190             return( HTML::Entities::decode_entities( @_ ) );
1191 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1192 0     0   0 catch( $e )
1193 0         0 {
1194 0 0 0     0 return( $self->error( "Error while decoding html entities data: $e" ) );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1195             }
1196             }
1197              
1198             sub decode_uri
1199 1     1 1 3 {
1200 1         2 my $self = shift( @_ );
1201 1     1   1 try
1202 1         7 {
1203             return( URI::Escape::uri_unescape( @_ ) );
1204 1 50       6 }
  0 50       0  
  1 50       3  
  1 0       2  
  1 50       4  
  1         1  
  1         3  
  1         2  
  1         6  
  0         0  
  1         3  
  0         0  
  1         34  
  1         3  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
1205 0     0   0 catch( $e )
1206 0         0 {
1207 0 0 33     0 return( $self->error( "Error while decoding uri: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  1         12  
  1         17  
1208             }
1209             }
1210              
1211             sub decode_url
1212 0     0 1 0 {
1213 0         0 my $self = shift( @_ );
1214 0     0   0 try
1215 0 0       0 {
1216             if( $self->mod_perl )
1217 0         0 {
1218             return( Encode::decode( 'utf8', APR::Request::decode( @_ ), Encode::FB_CROAK ) );
1219             }
1220             else
1221 0         0 {
1222             return( URL::Encode::url_decode_utf8( @_ ) );
1223             }
1224 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1225 0     0   0 catch( $e )
1226 0         0 {
1227 0 0 0     0 return( $self->error( "Error while url decoding data: $e" ) );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1228             }
1229             }
1230 0     0 1 0  
1231             sub document_filename { return( shift->uri->filename( @_ ) ); }
1232 0     0 1 0  
1233             sub document_path { return( shift->uri->document_path( @_ ) ); }
1234              
1235             sub document_root
1236 84     84 1 11239 {
1237 84         300 my $self = shift( @_ );
1238 84 50       1547 my $r = $self->apache_request;
1239             if( $r )
1240 0 0       0 {
1241 0         0 $r->document_root( @_ ) if( @_ );
1242             return( $r->document_root );
1243             }
1244             else
1245 84 100       297 {
1246             if( @_ )
1247 63         186 {
1248 63         373 $self->{document_root} = shift( @_ );
1249             $self->_set_env( DOCUMENT_ROOT => $self->{document_root} );
1250 84   33     400 }
1251             return( $self->{document_root} || $self->env( 'DOCUMENT_ROOT' ) );
1252             }
1253             }
1254              
1255 0     0 1 0 ## A document uri is an absolute uri possibly with some path info and query string.
1256             sub document_uri { return( shift->uri->document_uri( @_ ) ); }
1257 29     29 1 253  
1258             sub echomsg { return( shift->_set_get_scalar( 'echomsg', @_ ) ); }
1259              
1260             sub encode_base64
1261 2     2 1 385 {
1262 2         4 my $self = shift( @_ );
1263 2     2   4 try
1264 2         6 {
1265             my $v = join( '', @_ );
1266 2 50       47 # $self->message( 3, "Does data has utf8 flag on? ", ( Encode::is_utf8( $v ) ? 'yes' : 'no' ) );
1267 2 50       108 $v = Encode::encode( 'utf8', $v, Encode::FB_CROAK ) if( Encode::is_utf8( $v ) );
1268             if( $self->mod_perl )
1269 0         0 {
1270             return( APR::Base64::encode( $v ) );
1271             }
1272             else
1273 2         105 {
1274             return( MIME::Base64::encode( $v, '' ) );
1275             }
1276 2 50       23 }
  0 50       0  
  2 50       7  
  2 0       11  
  2 50       5  
  2         3  
  2         3  
  2         5  
  2         11  
  0         0  
  2         4  
  0         0  
  2         14  
  2         6  
  2         7  
  2         7  
  0         0  
  0         0  
  0         0  
  0         0  
1277 0     0   0 catch( $e )
1278 0         0 {
1279 0 0 33     0 return( $self->error( "Error while encoding data into base64: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  2         48  
  2         50  
1280             }
1281             }
1282              
1283             sub encode_entities
1284 1     1 1 484 {
1285 1         2 my $self = shift( @_ );
1286 1     1   2 try
1287 1         19 {
1288             return( HTML::Entities::encode_entities( join( '', @_ ) ) );
1289 1 50       21 }
  0 50       0  
  1 50       4  
  1 0       2  
  1 50       3  
  1         1  
  1         2  
  1         2  
  1         5  
  0         0  
  1         2  
  0         0  
  1         92  
  1         3  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
1290 0     0   0 catch( $e )
1291 0         0 {
1292 0 0 33     0 return( $self->error( "Error while encoding data into html entities: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  1         24  
  1         9  
1293             }
1294             }
1295              
1296             sub encode_md5
1297 1     1 0 4 {
1298 1         2 my $self = shift( @_ );
1299 1     1   1 try
1300 1         5 {
1301             my $v = join( '', @_ );
1302 1 50       20 ## $self->message( 3, "Does data has utf8 flag on? ", ( Encode::is_utf8( $v ) ? 'yes' : 'no' ) );
1303 1         47 $v = Encode::encode( 'utf8', $v, Encode::FB_CROAK ) if( Encode::is_utf8( $v ) );
1304             return( Digest::MD5::md5_hex( $v ) );
1305 1 50       8 }
  0 50       0  
  1 50       5  
  1 0       2  
  1 50       3  
  1         3  
  1         3  
  1         3  
  1         6  
  0         0  
  1         5  
  0         0  
  1         5  
  1         4  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
1306 0     0   0 catch( $e )
1307 0         0 {
1308 0 0 33     0 return( $self->error( "Error while encoding data into md5 hex: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  1         15  
  1         20  
1309             }
1310             }
1311              
1312             sub encode_uri
1313 2     2 1 383 {
1314 2         5 my $self = shift( @_ );
1315 2     2   5 try
1316 2         14 {
1317             return( URI::Escape::uri_escape_utf8( join( '', @_ ) ) );
1318 2 50       21 }
  0 50       0  
  2 50       9  
  2 0       5  
  2 50       5  
  2         5  
  2         3  
  2         5  
  2         10  
  0         0  
  2         6  
  0         0  
  2         171  
  2         6  
  2         6  
  2         12  
  0         0  
  0         0  
  0         0  
  0         0  
1319 0     0   0 catch( $e )
1320 0         0 {
1321 0 0 33     0 return( $self->error( "Error while encoding uri: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  2         35  
  2         36  
1322             }
1323             }
1324              
1325             sub encode_url
1326 1     1 1 1176 {
1327 1         172 my $self = shift( @_ );
1328 1     1   139 try
1329 1 50       7 {
1330             if( $self->mod_perl )
1331 0         0 {
1332 0         0 my $v = Encode::encode( 'utf8', join( '', @_ ), Encode::FB_CROAK );
1333             return( APR::Request::encode( $v ) );
1334             }
1335             else
1336 1         46 {
1337             return( URL::Encode::url_encode_utf8( join( '', @_ ) ) );
1338             }
1339 1 50       22 }
  0 50       0  
  1 50       4  
  1 0       2  
  1 50       3  
  1         2  
  1         2  
  1         2  
  1         4  
  0         0  
  1         3  
  0         0  
  1         4  
  1         3  
  1         9  
  1         10  
  0         0  
  0         0  
  0         0  
  0         0  
1340 0     0   0 catch( $e )
1341 0         0 {
1342 0 0 33     0 return( $self->error( "Error while url encoding data: $e" ) );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  1         52  
  1         14  
1343             }
1344             }
1345              
1346             sub env
1347 389     389 1 633 {
1348             my $self = shift( @_ );
1349 389 50       908 ## The user wants the entire hash reference
1350             unless( @_ )
1351 389         635 {
1352 389 50       5769 my $r = $self->apache_request;
1353             if( $r )
1354 0 0       0 {
    0          
1355 0         0 $r = $r->is_initial_req ? $r : $r->main ? $r->main : $r;
1356             return( $r->subprocess_env )
1357             }
1358             else
1359 389 100       970 {
1360             unless( ref( $self->{_env} ) )
1361 46         2095 {
1362             $self->{_env} = {%ENV};
1363 389         948 }
1364             return( $self->{_env} );
1365             }
1366 0         0 }
1367 0 0       0 my $name = shift( @_ );
1368 0         0 return( $self->error( "No environment variable name was provided." ) ) if( !length( $name ) );
1369 0 0       0 my $opts = {};
1370             if( scalar( @_ ) )
1371 14     14   190 {
  14         47  
  14         23943  
1372 0 0 0     0 no warnings 'uninitialized';
1373             $opts = pop( @_ ) if( defined( $_[-1] ) && Scalar::Util::reftype( $_[-1] ) eq 'HASH' );
1374             }
1375 0   0     0 ## return( $self->error( "Environment variable value provided is a reference data (", overload::StrVal( $val ), ")." ) ) if( ref( $val ) && ( !overload::Overloaded( $val ) || ( overload::Overloaded( $val ) && !overload::Method( $val, '""' ) ) ) );
1376 0 0       0 my $r = $opts->{apache_request} || $self->apache_request;
1377             if( $r )
1378 0 0       0 {
    0          
1379 0 0       0 $r = $r->is_initial_req ? $r : $r->main ? $r->main : $r;
1380             if( @_ )
1381 0         0 {
1382 0         0 my $val = shift( @_ );
1383 0         0 $r->subprocess_env( $name => $val );
1384             $ENV{ $name } = $val;
1385 0         0 }
1386 0         0 my $v = $r->subprocess_env( $name );
1387             return( $v );
1388             }
1389             else
1390 0         0 {
1391 0 0       0 my $env = {};
1392             unless( ref( $self->{_env} ) )
1393             {
1394 0         0 ## Make a copy of the environment variables
1395             $self->{_env} = {%ENV};
1396 0         0 }
1397 0 0       0 $env = $self->{_env};
1398             if( @_ )
1399 0         0 {
1400 0         0 $env->{ $name } = $ENV{ $name } = shift( @_ );
1401 0 0       0 my $meth = lc( $name );
1402             if( $self->can( $meth ) )
1403 0         0 {
1404             $self->$meth( $env->{ $name } );
1405             }
1406 0         0 }
1407             return( $env->{ $name } );
1408             }
1409             }
1410 6     6 1 132  
1411             sub errmsg { return( shift->_set_get_scalar( 'errmsg', @_ ) ); }
1412              
1413 0     0 1 0 ## This is set by document_uri
1414             sub filename { return( shift->uri->filename( @_ ) ); }
1415              
1416             sub find_file
1417 7     7 1 18 {
1418 7         26 my( $self, $args ) = @_;
1419 7         137 my $r = $self->apache_request;
1420 7 100       38 my $req = '';
    50          
    0          
1421             if( exists( $args->{file} ) )
1422 3         17 {
1423 3         19 $self->_interp_vars( $args->{file} );
1424             $req = $self->lookup_file( $args->{file} );
1425             }
1426             elsif( exists( $args->{virtual} ) )
1427 4         24 {
1428 4         19 $self->_interp_vars( $args->{virtual} );
1429             $req = $self->lookup_uri( $args->{virtual} );
1430             }
1431             elsif( $r )
1432 0         0 {
1433             $req = Apache2::SSI::File->new( $r->filename, apache_request => $r );
1434 7         23 }
1435             return( $req );
1436             }
1437              
1438             sub finfo
1439 0     0 1 0 {
1440 0         0 my $self = shift( @_ );
1441 0         0 my $r = $self->apache_request;
1442 0 0       0 my $newfile;
    0          
1443             if( @_ )
1444 0         0 {
1445 0 0 0     0 $newfile = shift( @_ );
1446             return( $self->error( "New file path specified but is an empty string." ) ) if( !defined( $newfile ) || !length( $newfile ) );
1447             }
1448             elsif( !$self->{finfo} )
1449 0         0 {
1450 0 0       0 $newfile = $self->filename;
1451             return( $self->error( "No file path set. This should not happen." ) ) if( !$newfile );
1452             }
1453 0 0       0
1454             if( defined( $newfile ) )
1455 0 0       0 {
1456             $self->{finfo} = Apache2::SSI::Finfo->new( $newfile, ( $r ? ( apache_request => $r ) : () ) );
1457 0         0 }
1458             return( $self->{finfo} );
1459             }
1460 0     0 1 0  
1461             sub html { return( shift->_set_get_scalar( 'html', @_ ) ); }
1462 56     56 1 2701  
1463             sub legacy { return( shift->_set_get_boolean( 'legacy', @_ ) ); }
1464              
1465             sub lookup_file
1466 3     3 1 8 {
1467 3   50     12 my $self = shift( @_ );
1468 3         20 my $file = shift( @_ ) || return( $self->error( "No file provided to look up." ) );
1469 3         54 $self->message( 3, "Looking up file \"$file\"." );
1470 3   50     66 my $r = $self->apache_request;
1471             my $f = Apache2::SSI::File->new(
1472             $file,
1473             ( $r ? ( apache_request => $r ) : () ),
1474             base_file => $self->uri->filename,
1475             debug => $self->debug
1476 3 50       24 ) || return( $self->error( "Unable to instantiate an Apache2::SSI::File object: ", Apache2::SSI::File->error ) );
1477             if( $f->code == 404 )
1478             {
1479 0         0 ## Mimic the Apache error when the file does not exist
1480             $self->error( "unable to lookup information about \"$file\" in parsed file \"", $self->uri, "\"." );
1481 3         11 }
1482             return( $f );
1483             }
1484              
1485             sub lookup_uri
1486 6     6 1 15 {
1487 6         16 my $self = shift( @_ );
1488 6         19 my $uri = shift( @_ );
1489 6   50     128 my $r = $self->apache_request;
1490             my $u = Apache2::SSI::URI->new(
1491             ( $r ? ( apache_request => $r ) : () ),
1492             base_uri => $self->uri,
1493             document_uri => $uri,
1494             document_root => ( $r ? $r->document_root : $self->document_root ),
1495             debug => $self->debug
1496 6 100       26 ) || return( $self->error( "Unable to instantiate an Apache2::SSI::URI object: ", Apache2::SSI::URI->error ) );
1497             if( $u->code == 404 )
1498             {
1499 1         10 ## Mimic the Apache error when the file does not exist
1500             $self->error( "unable to get information about uri \"$uri\" in parsed file ", $self->uri );
1501 6         2315 }
1502 6         110 $self->message( 3, "Resolved uri \"$uri\" to filename \"", $u->filename, "\"." );
1503             return( $u );
1504             }
1505 68     68 1 752  
1506             sub mod_perl { return( shift->_set_get_boolean( 'mod_perl', @_ ) ); }
1507              
1508             sub new_uri
1509 3     3 0 6 {
1510 3         6 my $self = shift( @_ );
1511 3 50 33     30 my $uri = shift( @_ );
1512 3         10 return( $self->error( "No uri provided to create an Apache2::SSI::URI object." ) ) if( !defined( $uri ) || !length( $uri ) );
1513             my $p =
1514             {
1515             document_uri => $uri,
1516             document_root => $self->document_root,
1517             base_uri => $self->uri,
1518             debug => $self->debug,
1519 3 50       104 };
1520 3   50     52 $p->{apache_request} = $self->apache_request if( $self->apache_request );
1521             my $o = Apache2::SSI::URI->new( $p ) ||
1522 3         18 return( $self->error( "Unable to create an Apache2::SSI::URI: ", Apache2::SSI::URI->error ) );
1523             return( $o );
1524             }
1525              
1526             ## This makes use of Apache2::SSI::Notes which guarantees that notes are shared in and out of Apache framework
1527             ## Notes are cleaned up at server shutdown with an handler set in startup.pl
1528             ## See scripts/startup.pl and conf/extra.conf.in as an example
1529             sub notes
1530 0     0 0 0 {
1531 0         0 my $self = shift( @_ );
1532 0         0 my $notes = $self->{notes};
1533 0 0       0 my $r = $self->apache_request;
1534             unless( scalar( @_ ) )
1535 0 0       0 {
    0          
1536             if( $r )
1537 0         0 {
1538             return( $r->pnotes );
1539             }
1540             elsif( $notes )
1541 0         0 {
1542             return( $notes->get );
1543             }
1544             ## We just return an empty hash to avoid error
1545             else
1546 0         0 {
1547             return( {} );
1548             }
1549 0         0 }
1550 0         0 my $var = shift( @_ );
1551 0         0 my $new;
1552 0 0       0 my $new_value_set = 0;
1553             if( @_ )
1554 0         0 {
1555 0         0 $new = shift( @_ );
1556 0 0       0 $new_value_set++;
1557             if( $notes )
1558 0         0 {
1559             $notes->set( $var => $new );
1560             }
1561             }
1562 0 0       0
1563             if( $r )
1564 0         0 {
1565 0     0   0 try
1566 0 0       0 {
1567 0         0 $r->pnotes( $var => $new ) if( $new_value_set );
1568 0         0 $self->message( 3, "Retrieving note '$var' => '", $r->pnotes( $var ), "'" );
1569 0 0       0 my $val = $r->pnotes( $var );
1570 0 0 0     0 $self->message( 3, "Is pnotes value defined? ", defined( $val ) ? 'yes' : 'no' );
1571 0         0 $val //= $notes->get( $var ) if( $notes );
1572             return( $val );
1573 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1574 0     0   0 catch( $e )
1575 0 0       0 {
    0          
1576 0 0       0 $self->message( 3, "An error occurred trying to ", (defined( $new ) ? 'set/' : ''), " get the note value for variable \"${var}\"", (defined( $new ) ? " with value '${new}" : ''), ": $e" );
    0          
1577 0 0 0     0 return( $self->error( "An error occurred trying to ", (defined( $new ) ? 'set/' : ''), " get the note value for variable \"${var}\"", (defined( $new ) ? " with value '${new}" : ''), ": $e" ) );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1578             }
1579 0 0       0 }
1580 0         0 return( $notes->get( $var ) ) if( $notes );
1581             return( '' );
1582             }
1583              
1584             sub parse
1585 54     54 1 2701 {
1586 54 50       240 my $self = shift( @_ );
1587 54         429 my $html = @_ ? shift( @_ ) : $self->{html};
1588 54 50       1262 $self->message( 3, "Parsing html:\n'$html'" );
1589 54         2220 return( $self->error( "No html data was provided to parse ssi." ) ) if( !length( $html ) );
1590             my @parts = split( m/($HAS_SSI_RE)/s, $html );
1591             ## Nothing to do
1592 54         185 # return( Apache2::Const::DECLINED ) if( scalar( @parts ) <= 1 );
1593 54         105 my $out = '';
1594 54         199 my $ssi;
1595             while( @parts )
1596 212         1524 {
1597 212 100       599 $out .= ( '', shift( @parts ) )[ 1 - $self->{suspend}->[0] ];
1598 158         285 last unless( @parts );
1599             $ssi = shift( @parts );
1600 158 50       1036 ## There's some weird 'uninitialized' warning on the next line, but I can't find it.
1601             if( $ssi =~ m/^<!--#(.*)-->$/s )
1602 158         623 {
1603 158 100       929 my $res = $self->parse_ssi( $1 );
1604             $out .= "$res" if( defined( $res ) );
1605             }
1606             else
1607 0         0 {
1608             return( $self->error( 'Parse error' ) );
1609             }
1610 54         337 }
1611 54         1445 $self->message( 3, "Returning:\n'$out'" );
1612             return( $out );
1613             }
1614              
1615             ## <!--#comment Blah Blah Blah -->
1616             sub parse_comment
1617 0     0 0 0 {
1618 0         0 my $self = shift( @_ );
1619             my $comment = shift( @_ );
1620 0         0 ## comments are removed
1621             return( '' );
1622             }
1623              
1624             sub parse_config
1625 6     6 1 19 {
1626 6     0   46 my( $self, $args ) = @_;
  0         0  
1627 6 50       109 $self->message( 3, "Setting config values for arguments: ", sub{ $self->dump( $args ) });
1628 6 100       21 $self->{echomsg} = $args->{echomsg} if( exists( $args->{echomsg} ) );
1629 6 100       23 $self->{errmsg} = $args->{errmsg} if( exists( $args->{errmsg} ) );
1630 6 100       16 $self->{sizefmt} = lc( $args->{sizefmt} ) if( exists( $args->{sizefmt} ) );
1631 6         30 $self->{timefmt} = $args->{timefmt} if( exists( $args->{timefmt} ) );
1632             return( '' );
1633             }
1634              
1635             sub parse_echo
1636 38     38 1 110 {
1637 38         96 my( $self, $args ) = @_;
1638             my $var = $args->{var};
1639 38         97 ## $self->_interp_vars( $var );
1640 38         676 my $r = $self->apache_request;
1641 38         63 my $env = $self->env;
1642 14     14   226 my $value;
  14         28  
  14         19613  
1643 38         185 no strict( 'refs' );
1644             $self->message( 3, "Checking value for variable '$var'." );
1645 38 50 33     1353
    100 33        
    100 66        
      33        
1646             if( defined( $var ) && $r && defined( $value = $r->subprocess_env( $var ) ) )
1647             {
1648             ## Ok then
1649             }
1650             elsif( defined( $var ) && $self->can( my $method = "parse_echo_\L$var\E" ) )
1651 8         32 {
1652             $value = $self->$method( $r );
1653             }
1654             elsif( defined( $var ) && exists( $env->{ $var } ) )
1655 5         115 {
1656 5         89 $self->message( 3, "Returning variable \"$var\" with value \"$env->{$var}\"." );
1657             $value = $env->{ $var };
1658             }
1659             else
1660 25         116 {
1661             $value = $self->echomsg;
1662 38         567 }
1663             $self->message( 3, "Value found is '$value'" );
1664 38 50 33     1057
1665             if( $args->{decoding} && lc( $args->{decoding} ) ne 'none' )
1666 0         0 {
1667 0         0 $args->{decoding} = lc( $args->{decoding} );
1668 0     0   0 try
1669 0 0       0 {
    0          
    0          
    0          
1670             if( $args->{decoding} eq 'url' )
1671 0         0 {
1672             $value = $self->decode_uri( $value );
1673             }
1674             elsif( $args->{decoding} eq 'urlencoded' )
1675 0         0 {
1676             $value = $self->decode_url( $value );
1677             }
1678             elsif( $args->{decoding} eq 'base64' )
1679 0         0 {
1680             $value = $self->decode_base64( $value );
1681             }
1682             elsif( $args->{decoding} eq 'entity' )
1683 0         0 {
1684             $value = $self->decode_entities( $value );
1685             }
1686 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1687 0     0   0 catch( $e )
1688 0         0 {
1689 0         0 $self->error( "Decoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
1690 0 0 0     0 return( $self->errmsg );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1691             }
1692             }
1693 38 50 33     171
1694             if( $args->{encoding} && lc( $args->{encoding} ) ne 'none' )
1695 0         0 {
1696 0         0 $args->{encoding} = lc( $args->{encoding} );
1697 0     0   0 try
1698 0 0       0 {
    0          
    0          
    0          
1699             if( $args->{encoding} eq 'url' )
1700 0         0 {
1701             $value = $self->encode_uri( $value );
1702             }
1703             elsif( $args->{encoding} eq 'urlencoded' )
1704 0         0 {
1705             $value = $self->encode_url( $value );
1706             }
1707             elsif( $args->{encoding} eq 'base64' )
1708 0         0 {
1709             $value = $self->encode_base64( $value );
1710             }
1711             elsif( $args->{encoding} eq 'entity' )
1712 0         0 {
1713             $value = $self->encode_entities( $value );
1714             }
1715 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
1716 0     0   0 catch( $e )
1717 0         0 {
1718 0         0 $self->error( "Enecoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
1719 0 0 0     0 return( $self->errmsg );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
1720             }
1721 38         129 }
1722             return( $value );
1723             }
1724 0     0 1 0  
1725             sub parse_echo_date_gmt { return( shift->_format_time( time(), undef, 'GMT' ) ); }
1726 1     1 1 5  
1727             sub parse_echo_date_local { return( shift->_format_time( time() ) ); }
1728              
1729             sub parse_echo_document_name
1730 2     2 1 6 {
1731 2         6 my $self = shift( @_ );
1732 2         8 my $r = shift( @_ );
1733 2 50       58 my $uri = $self->uri;
1734             if( $r )
1735 0 0       0 {
    0          
1736 0   0     0 $r = $r->is_initial_req ? $r : $r->main ? $r->main : $r;
1737 0         0 my $v = $r->subprocess_env( 'DOCUMENT_NAME' ) || $uri->finfo->name;
1738             $self->message( 3, "Found value of '$v' and finfo name is '", $uri->finfo->name, "' for uri '$uri'." );
1739 0         0 ## return( $self->_set_var( $r, 'DOCUMENT_NAME', basename $r->filename ) );
1740             return( $v );
1741             }
1742             else
1743 2         6 {
1744 2   33     33 my $env = $self->env;
1745 2         12 my $v = $env->{DOCUMENT_NAME} || $uri->finfo->name;
1746             return( $v );
1747             }
1748             }
1749 0     0 1 0  
1750             sub parse_echo_document_uri { return( shift->document_uri ); }
1751              
1752             sub parse_echo_last_modified
1753 1     1 1 2 {
1754 1         2 my $self = shift( @_ );
1755 1         3 my $r = shift( @_ );
1756 1 50       20 my $uri = $self->uri;
1757             if( $r )
1758 0 0       0 {
    0          
1759 0   0     0 $r = $r->is_initial_req ? $r : $r->main ? $r->main : $r;
1760             my $v = $r->subprocess_env( 'LAST_MODIFIED' ) || $self->_lastmod( $r->filename );
1761             }
1762             else
1763 1         4 {
1764 1         6 my $env = $self->env;
1765 1   33     21 $self->message( 3, "LAST_MODIFIED value '", $env->{LAST_MODIFIED}, "' and document_filename mtime is '", $uri->finfo->mtime, "' and document file is '", $uri->document_filename, "'." );
1766             return( $env->{LAST_MODIFIED} || $self->_format_time( $uri->finfo->mtime ) );
1767             }
1768             }
1769              
1770             sub parse_echo_query_string
1771 4     4 0 6 {
1772 4         11 my $self = shift( @_ );
1773 4         84 my $uri = $self->uri;
1774             return( $uri->query_string );
1775             }
1776              
1777             sub parse_elif
1778 4     4 1 15 {
1779             my( $self, $args ) = @_;
1780 4 50       19 ## Make sure we're in an 'if' chain
  4         19  
1781 4 50       12 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
1782 4         13 return( '' ) if( $self->{suspend}->[1] );
1783             return( $self->_handle_ifs( $self->parse_eval_expr( $args->{expr} ) ) );
1784             }
1785              
1786             sub parse_else
1787 38     38 1 109 {
1788             my $self = shift( @_ );
1789 38 50       65 ## Make sure we're in an 'if' chain
  38         158  
1790 38 100       146 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
1791 37         115 return( '' ) if( $self->{suspend}->[1] );
1792             return( $self->_handle_ifs(1) );
1793             }
1794              
1795             sub parse_endif
1796 39     39 1 102 {
1797             my $self = shift( @_ );
1798 39 50       73 ## Make sure we're in an 'if' chain
  39         169  
1799 39         66 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
  39         106  
1800 39         68 shift( @{$self->{if_state}} );
  39         83  
1801 39         152 shift( @{$self->{suspend}} );
1802             return( '' );
1803             }
1804              
1805             sub parse_eval_expr
1806 53     53 1 120 {
1807 53         124 my $self = shift( @_ );
1808 53 50       205 my $text = shift( @_ );
1809 53 50       149 $self->message( 3, "No expression to eval was provided." ) if( !length( $text ) );
1810             return( '' ) if( !length( $text ) );
1811 53         224
1812 53         294 my $perl = $self->parse_expr( $text );
1813 53         1017 $self->message( 3, "Position after parsing is: ", pos( $text ) );
1814 53         796 $self->message( 3, "Evaluating text '$perl'" );
1815             my $result;
1816 53         92 do
1817             {
1818 53     0   467 ## Silence some warnings about bare words such as strings being eval'ed
1819             local $SIG{__WARN__} = sub{};
1820 14     14   127 # package main;
  14         34  
  14         49646  
1821             no warnings 'uninitialized';
1822             ## Only to test if this was a regular expression. If it was the array will contain successful match, other it will be empty
1823 53         213 ## @rv will contain the regexp matches or the result of the eval
1824 53         134 local @matches = ();
1825 53         184 local @rv = ();
1826             my $eval = <<EOT;
1827             \@rv = ($perl);
1828 53 100       289 EOT
1829             $eval .= <<EOT if( $perl =~ /[\=\!]\~/ );
1830             \@matches = \@-;
1831 53         275 EOT
1832 53         7359 $self->message( 3, "Evaluating text:\n$eval" );
1833 53         238 eval( $eval );
1834 53     0   442 $result = $rv[0];
  0         0  
1835             $self->message( 3, "\@- is: ", sub{ $self->dump( \@matches ) } );
1836 53 100       1353 ## Make any regular expression capture available for the next evaluation
1837 53     0   286 $self->{_regexp_capture} = \@rv if( scalar( @matches ) );
  0         0  
1838             $self->message( 3, "Potential regular expression matches found: ", sub{ $self->dump( $self->{_regexp_capture} ) } );
1839 53   100     1148 };
1840 53 50       168 $result //= '';
1841 53 50       166 $self->message( 3, "Eval error found: $@" ) if( $@ );
1842 53 100       311 return( $self->error( "Eval error for expression '$text' translated to '$perl': $@" ) ) if( $@ );
1843 53         3294 $self->message( 3, "Got an error: ", $self->error->message ) if( $self->error );
1844 53         993 $self->message( 3, "Returning result: '$result'" );
1845             return( $result );
1846             }
1847              
1848             sub parse_exec
1849 4     4 1 10 {
1850             my( $self, $args ) = @_;
1851 4         11 ## XXX did we check enough?
1852 4         68 my $r = $self->apache_request;
1853 4         105 my $uri = $self->uri;
1854 4 50       11 my $filename;
1855             if( $r )
1856 0         0 {
1857             $filename = $r->filename;
1858 0 0       0  
1859             if( $r->allow_options & Apache2::Const::OPT_INCNOEXEC )
1860 0         0 {
1861 0         0 $self->error( "httpd: exec used but not allowed in $filename" );
1862             return( $self->errmsg );
1863             }
1864             }
1865 4 100       12 # XXX Need to improve on this
1866             if( exists( $args->{cmd} ) )
1867 1         9 {
1868             $self->message( 3, "Executing command '$args->{cmd}'." );
1869             ## https://metacpan.org/pod/Apache2::SubProcess
1870             ## Fails to work: <https://rt.cpan.org/Public/Bug/Display.html?id=54153>
1871 1 50 50     32 ## <https://rt.cpan.org/Public/Dist/Display.html?Status=Active;Name=mod_perl>
1872             if( $r && MOD_PERL_SPAWN_PROC_PROG_WORKING )
1873 0         0 {
1874 0         0 my $data;
1875 0         0 my $fh = $r->spawn_proc_prog( $args->{cmd} );
1876             if( PERLIO_IS_ENABLED || IO::Select->new( $fh )->can_read(10) )
1877 0         0 {
1878             $data = <$fh>;
1879 0 0       0 }
1880             return( defined( $data ) ? $data : '' );
1881             }
1882             else
1883 1         16 {
1884 1         156 my $env = $self->env;
1885             local %ENV = %$env;
1886 1         7614 ## What a shame to fork exec. Too bad spawn_proc_prog() does not work.
1887             return( scalar( qx( $args->{cmd} ) ) );
1888             }
1889             }
1890 3 50       8
1891             unless( exists( $args->{cgi} ) )
1892 0         0 {
1893 0         0 $self->error( "No 'cmd' or 'cgi' argument given to #exec" );
1894             return( $self->errmsg );
1895             }
1896            
1897             ## Get a new Apache2::SSI::URI object
1898 3   33     10 my $cgi = $self->new_uri( $args->{cgi} ) || do
1899             {
1900             $self->message( 3, "Unable to get a new Apache2::SSI::URI for cgi '$args->{cgi}': ", Apache2::SSI::URI->error );
1901             return( $self->errmsg );
1902 3         10 };
1903             $self->message( 3, "CGI path to execute is: '$cgi'." );
1904 3   33     66 my $doc_root = $self->document_root || do
1905             {
1906             $self->error( "No document root set." );
1907             return( $self->errmsg );
1908             };
1909 3 100       7
1910             if( $cgi->code != 200 )
1911 2         24 {
1912 2         46 $self->message( 3, "CGI file code is not 200 (", $cgi->code, ")." );
1913 2         3564 $self->error( "Error including cgi: subrequest returned status '" . $cgi->code . "', not 200" );
1914             return( $self->errmsg );
1915             }
1916 1         4
1917 1         4 my $finfo = $cgi->finfo;
1918 1 50       18 $self->message( 3, "Checking permission for file \"", $cgi->filename, "\"." );
    50          
1919             if( !$finfo->exists )
1920 0         0 {
1921 0         0 $self->message( 3, "CGI file does not exist." );
1922 0         0 $cgi->code( 404 );
1923 0         0 $self->error( "Error including cgi \"$args->{cgi}\". File not found. CGI resolved to \"", $cgi->filename, "\"" );
1924             return( $self->errmsg );
1925             }
1926             elsif( !$finfo->can_exec )
1927 0         0 {
1928             $self->message( 3, "CGI file is not executable." );
1929 0         0 ## return( $self->error( "Error including cgi \"$args->{cgi}\". File is not executable by Apache user." ) );
1930 0         0 $self->error( "Error including cgi \"$args->{cgi}\". File is not executable by Apache user." );
1931 0         0 $cgi->code( 401 );
1932             return( $self->errmsg );
1933 1         46 }
1934             $self->message( 3, "Ok, file \"$cgi\" exists (code = '", $cgi->code, "')" );
1935            
1936 1 50       17
1937             if( $r )
1938 0         0 {
1939             my $rr = $cgi->apache_request;
1940             # my $u = URI->new( $rr->uri . ( length( $cgi->path_info ) ? $cgi->path_info : length( $uri->path_info ) ? $uri->path_info : '' ) );
1941 0         0 # $u->query( $uri->query_string ) if( !length( $cgi->query_string ) && length( $uri->query_string ) );
1942 0 0 0     0 $self->message( 3, "Setting path info to '", $uri->path_info, "' and query string to '", $uri->query_string, "'." );
1943 0 0 0     0 $cgi->path_info( $uri->path_info ) if( !length( $cgi->path_info ) && length( $uri->path_info ) );
1944 0         0 $cgi->query_string( $uri->query_string ) if( !length( $cgi->query_string ) && length( $uri->query_string ) );
1945 0         0 $self->message( 3, "Running cgi \"$cgi\" (", $cgi->filename, ")." );
1946 0         0 $rr->content_type( 'application/x-httpd-cgi' );
1947 0         0 $cgi->env( GATEWAY_INTERFACE => 'CGI/1.1' );
1948 0         0 $cgi->env( DOCUMENT_URI => "$cgi" );
1949 0     0   0 my( $content, $headers ) = $rr->fetch_uri( "$cgi" );
  0         0  
1950 0         0 $self->message( 3, "Content found is:\n'$content'\nand headers are: ", sub{ $self->dump( $headers ) });
1951             return( $content );
1952             }
1953             else
1954 1         2 {
1955             my $buf;
1956 1         2 {
  1         3  
1957 1         12 local $ENV{DOCUMENT_URI} = $cgi->document_uri;
1958 1 50       3 local $ENV{PATH_INFO} = $uri->path_info;
1959 1         4 local $ENV{PATH_INFO} = $cgi->path_info if( length( $cgi->path_info ) );
1960 1 50       4 local $ENV{QUERY_STRING} = $uri->query_string;
1961 1         5 local $ENV{QUERY_STRING} = $cgi->query_string if( length( $cgi->query_string ) );
1962 1         4 local $ENV{REMOTE_ADDR} = $self->remote_ip;
1963 1         4 local $ENV{REQUEST_METHOD} = 'GET';
1964 1         7 local $ENV{REQUEST_URI} = $cgi->document_uri;
1965 1         3 my $file = $cgi->filename;
1966             $buf = qx( "$file" );
1967             };
1968 1 50       11970 ## Failed to execute
1969             if( $? == -1 )
1970 0         0 {
1971 0         0 $self->message( 3, "CGI exite value was not 0 but '$?'." );
1972 0         0 $cgi->code( 500 );
1973             return( $self->errmsg );
1974 1         13 }
1975 1         27 my( $key, $val );
1976 1         53 my $headers = {};
1977             while( $buf =~ s/([^\012]*)\012// )
1978 3         28 {
1979             my $line = $1;
1980 3         18 ## if we need to restore as content when illegal headers are found.
1981             my $save = "$line\012";
1982 3         10
1983 3 100       34 $line =~ s/\015$//;
1984             last unless( length( $line ) );
1985 2 50 0     29
    0          
1986             if( $line =~ /^([a-zA-Z0-9_\-.]+)\s*:\s*(.*)/ )
1987             {
1988 2 100       29 ## $response->push_header( $key, $val ) if( $key );
1989 2         34 $headers->{ $key } = $val if( $key );
1990             ( $key, $val ) = ( $1, $2 );
1991             }
1992             elsif( $line =~ /^\s+(.*)/ && $key )
1993 0         0 {
1994             $val .= " $1";
1995             }
1996             else
1997             {
1998 0         0 ## $response->push_header( "Client-Bad-Header-Line" => $line );
1999             $headers->{ 'Client-Bad-Header-Line' } = $line;
2000             }
2001             }
2002 1 50       23 ## $response->push_header( $key, $val ) if( $key );
2003 1     0   64 $headers->{ $key } = $val if( $key );
  0         0  
2004 1         552 $self->message( 3, "Headers found are: ", sub{ $self->dump( $headers ) } );
2005             return( $buf );
2006             }
2007             }
2008              
2009             sub parse_expr
2010 71     71 1 6297 {
2011 71         137 my $self = shift( @_ );
2012 71         208 my $text = shift( @_ );
2013 71 100       234 my $opts = {};
2014             if( @_ )
2015 3 0       15 {
    50          
2016             $opts = ref( $_[0] ) eq 'HASH'
2017             ? shift( @_ )
2018             : !( @_ % 2 )
2019             ? { @_ }
2020             : {};
2021 71 100       315 }
2022 71         220 $opts->{embedded} = 0 if( !exists( $opts->{embedded} ) );
2023 71         1404 my $r = $self->apache_request;
2024 71         396 my $env = $self->env;
2025 71         1257 $self->message( 3, "Processing text '$text'." );
2026 71 100       223 my $prev_regexp_capture = $self->{_regexp_capture};
2027             unless( $self->{_exp} )
2028 38         147 {
2029             $self->{_exp} = Apache2::Expression->new( legacy => 1, debug => $self->debug );
2030             }
2031 71         148
2032 71         142 my $exp = $self->{_exp};
2033 71         114 my $hash = {};
2034 71     71   97 try
2035 71         1692 {
  0         0  
2036 71         662 local $SIG{ALRM} = sub{ die( "Timeout!\n" ) };
2037 71         463 alarm( 90 );
2038 71         2522 $hash = $exp->parse( $text );
2039             alarm( 0 );
2040 71 50       491 }
  71 50       427  
  71 50       197  
  71 0       119  
  71 50       169  
  71         114  
  71         112  
  71         187  
  71         249  
  0         0  
  71         126  
  0         0  
  71         301  
  71         184  
  71         227  
  71         263  
  0         0  
  0         0  
  0         0  
  0         0  
2041 0     0   0 catch( $e )
2042 0         0 {
2043 0 0 33     0 return( $self->error( "Error parsing expression '$text': $e" ) );
  0 0 33     0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  71         1908  
  0         0  
2044 71         212 }
2045 71         223 my $res = [];
2046 71         109 $opts->{top} = 1;
  71         255  
2047             foreach my $this ( @{$hash->{elements}} )
2048 71         434 {
2049 71         302 my $res2 = $self->ap2perl_expr( $this, [], $opts );
2050             push( @$res, @$res2 );
2051 71         417 }
2052 71         2237 $self->message( 3, "Returning '", join( ' ', @$res ), "'." );
2053             return( join( ' ', @$res ) );
2054             }
2055              
2056             sub parse_flastmod
2057 1     1 1 3 {
2058 1         5 my( $self, $args ) = @_;
2059 1 50       3 my $p = $self->find_file( $args );
2060             unless( $p->code == 200 )
2061 0         0 {
2062             return( $self->errmsg );
2063 1   33     10 }
2064             return( $self->_lastmod( $p, $args->{timefmt} || $self->{timefmt} ) );
2065             }
2066              
2067             sub parse_fsize
2068 2     2 1 12 {
2069             my( $self, $args ) = @_;
2070 2         15 ## $self->message( 3, "Got here with args: ", sub{ $self->dump( $args ) });
2071 2 50       7 my $f = $self->find_file( $args );
2072             unless( $f->code == 200 )
2073 0         0 {
2074 0         0 $self->message( "Requested file \"", $f->filename, "\" not found." );
2075             return( $self->errmsg );
2076 2         9 }
2077 2         10 my $finfo = $f->finfo;
2078 2         20 my $size = $finfo->size;
2079 2         53 $self->message( 3, "File \"$f\" size is: '$size'" );
2080 2 100       96744 my $n = Module::Generic::Number->new( $size );
    50          
2081             if( $self->{sizefmt} eq 'bytes' )
2082             {
2083             ## Not everyone is using a comma as thousand separator
2084             ## 1 while( $size =~ s/^(\d+)(\d{3})/$1,$2/g );
2085 1         23 ## return( $size );
2086 1         329 my $str = $n->format( 0 )->scalar;
2087 1         49 $self->message( 3, "Returning \"$str\" (", overload::StrVal( $str ), ")." );
2088 1 50       5 undef( $n );
2089 1         36 return( '' ) if( !defined( $str ) );
2090             return( $str );
2091             }
2092             elsif( $self->{sizefmt} eq 'abbrev' )
2093 1 50       57 {
2094 0         0 return( $size ) if( $size < 1024 );
2095 0         0 my $n = Module::Generic::Number->new( $size );
2096 0         0 my $str = $n->format_bytes->scalar;
2097 0 0       0 undef( $n );
2098 0         0 return( '' ) if( !defined( $str ) );
2099             return( $str );
2100             }
2101             else
2102 0         0 {
2103 0         0 $self->error( "Unrecognized size format '$self->{sizefmt}'" );
2104             return( $self->errmsg );
2105             }
2106             }
2107              
2108             ## Functions
2109             ## See https://httpd.apache.org/docs/trunk/en/expr.html#page-header
2110 1     1 1 9 # base64|env|escape|http|ldap|md5|note|osenv|replace|req|reqenv|req_novary|resp|sha1|tolower|toupper|unbase64|unescape
2111             sub parse_func_base64 { return( shift->encode_base64( join( '', @_ ) ) ); }
2112              
2113             ## Return first match of note, reqenv, osenv
2114             sub parse_func_env
2115 1     1 1 4 {
2116 1         3 my $self = shift( @_ );
2117 1         6 my $var = shift( @_ );
2118 1         23 my $r = $self->apache_request;
2119 1         8 my $env = $self->env;
2120 1 50       21 $self->message( 3, "Getting environment value for variable '${var}'." );
2121             if( $r )
2122 0         0 {
2123 0     0   0 try
2124 0   0     0 {
2125             return( $r->subprocess_env( $var ) || $env->{ $var } || $self->notes( $var ) );
2126 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2127 0     0   0 catch( $e )
2128 0         0 {
2129 0         0 $self->message( 3, "An error occurred trying to get the environment value for variable \"${var}\": $e" );
2130 0 0 0     0 return( $self->error( "An error occurred trying to get the environment value for variable \"${var}\": $e" ) );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
2131             }
2132             }
2133             else
2134 1   33     17 {
2135             return( $env->{ $var } || $self->notes( $var ) );
2136             }
2137             }
2138 1     1 1 10  
2139             sub parse_func_escape { return( shift->encode_uri( join( '', @_ ) ) ); }
2140              
2141             sub parse_func_http
2142 0     0 1 0 {
2143 0         0 my $self = shift( @_ );
2144 0         0 my $header_name = shift( @_ );
2145 0 0       0 my $r = $self->apache_request;
2146             if( $r )
2147 0         0 {
2148 0         0 my $headers = $r->headers_in;
2149             return( $headers->{ $header_name } );
2150             }
2151             ## No http header outside of Apache
2152             else
2153 0         0 {
2154 0 0       0 my $env = $self->env;
2155 0         0 return( $env->{ $header_name } ) if( length( $env->{ $header_name } ) );
2156 0 0       0 my $name = $header_name =~ tr/-/_/;
2157 0 0       0 return( $env->{"HTTP_\U${name}\E"} ) if( length( $env->{"HTTP_\U${name}\E"} ) );
2158 0         0 return( $env->{ uc( $name ) } ) if( length( $env->{ uc( $name ) } ) );
2159             return( '' );
2160             }
2161             }
2162              
2163             ## Apache documentation: "Escape characters as required by LDAP distinguished name escaping (RFC4514) and LDAP filter escaping (RFC4515)"
2164             ## Taken from Net::LDAP::Util
2165             sub parse_func_ldap
2166 1     1 1 3 {
2167 1         5 my $self = shift( @_ );
2168 1         10 my $val = join( '', @_ );
  5         25  
2169 1         13 $val =~ s/([\x00-\x1F\*\(\)\\])/'\\' . unpack( 'H2', $1 )/oge;
2170             return( $val );
2171             }
2172 1     1 1 9  
2173             sub parse_func_md5 { return( shift->encode_md5( @_ ) ); }
2174              
2175             ## Notes are stored in the ENV global hash so they can be shared across processes
2176             sub parse_func_note
2177 0     0 1 0 {
2178 0         0 my $self = shift( @_ );
2179 0         0 my $var = shift( @_ );
2180             return( $self->notes( $var ) );
2181             }
2182              
2183             ## Essentially same as parse_func_note
2184             sub parse_func_osenv
2185 0     0 1 0 {
2186 0         0 my $self = shift( @_ );
2187 0         0 my $var = shift( @_ );
2188             return( $ENV{ $var } );
2189             }
2190              
2191             sub parse_func_replace
2192 1     1 1 3 {
2193 1         5 my $self = shift( @_ );
2194 1         23 my( $str, $what, $with ) = @_;
2195 1         14 $str =~ s/$what/$with/g;
2196             return( $str );
2197             }
2198 0     0 1 0  
2199             sub parse_func_req { return( shift->parse_func_http( @_ ) ); }
2200              
2201             sub parse_func_reqenv
2202 0     0 1 0 {
2203 0         0 my $self = shift( @_ );
2204 0         0 my $var = shift( @_ );
2205 0 0       0 my $r = $self->apache_request;
2206             if( $r )
2207 0         0 {
2208             return( $r->subprocess_env( $var ) );
2209             }
2210             else
2211 0         0 {
2212 0         0 my $env = $self->env;
2213             return( $env->{ $var } );
2214             }
2215             }
2216 0     0 1 0  
2217             sub parse_func_req_novary { return( shift->parse_func_http( @_ ) ); }
2218              
2219             sub parse_func_resp
2220 0     0 1 0 {
2221 0         0 my $self = shift( @_ );
2222 0         0 my $header_name = shift( @_ );
2223 0 0       0 my $r = $self->apache_request;
2224             if( $r )
2225 0         0 {
2226 0     0   0 my $headers = $r->headers_out;
  0         0  
2227 0         0 $self->message( 3, "Checking http header '$header_name' => '", $headers->{ $header_name }, "'. Existing headers are: ", sub{ $self->dump( {%$headers} ) } );
2228             return( $headers->{ $header_name } );
2229             }
2230             ## No http header outside of Apache
2231             else
2232 0         0 {
2233             return( '' );
2234             }
2235             }
2236              
2237             sub parse_func_sha1
2238 1     1 1 4 {
2239 1         4 my $self = shift( @_ );
2240 1         25 my $val = join( '', @_ );
2241             return( Digest::SHA::sha1_hex( $val ) );
2242             }
2243              
2244             sub parse_func_tolower
2245 2     2 1 6 {
2246 2         33 my $self = shift( @_ );
2247             return( lc( join( '', @_ ) ) );
2248             }
2249              
2250             sub parse_func_toupper
2251 1     1 1 4 {
2252 1         21 my $self = shift( @_ );
2253             return( uc( join( '', @_ ) ) );
2254             }
2255 1     1 1 10  
2256             sub parse_func_unbase64 { return( shift->decode_base64( join( '', @_ ) ) ); }
2257 1     1 1 8  
2258             sub parse_func_unescape { return( shift->decode_uri( join( '', @_ ) ) ); }
2259              
2260             sub parse_if
2261 39     39 1 121 {
2262 39         67 my( $self, $args ) = @_;
  39         144  
2263 39         73 unshift( @{$self->{if_state}}, 0 );
  39         138  
2264 39 100       141 unshift( @{$self->{suspend}}, $self->{suspend}->[0] );
2265 38         189 return( '' ) if( $self->{suspend}->[0] );
2266             return( $self->_handle_ifs( $self->parse_eval_expr( $args->{expr} ) ) );
2267             }
2268              
2269             sub parse_include
2270 4     4 1 15 {
2271 4 50 66     28 my( $self, $args ) = @_;
2272             unless( exists( $args->{file} ) or exists( $args->{virtual} ) )
2273 0         0 {
2274             return( $self->error( "No 'file' or 'virtual' attribute found in SSI 'include' tag" ) );
2275 4         22 }
2276 4 50       15 my $f = $self->find_file( $args );
2277             unless( $f->code == 200 )
2278 0         0 {
2279 0         0 $self->message( "File to include \"", $f->filename, "\" could not be found." );
2280             return( $self->errmsg );
2281 4         13 }
2282 4 50       11 my $filename = $f->filename;
2283             if( !-e( "$filename" ) )
2284 0         0 {
2285 0         0 $self->message( 3, "File to include \"$filename\" does not exists." );
2286             return( $self->errmsg );
2287             }
2288            
2289             # XXX This needs to be improved, as we should not assume the file encoding is utf8
2290             ## It could be binary or some other text encoding like iso-2022-jp
2291             ## So we should slurp it, parse the meta tags if this is an html and decode if the charset attribute is set or default to utf8
2292 4         129 ## But this complicates things quite a bit, so for now, it is just utf8 simply
2293 4 50       18 my $html = $f->slurp_utf8;
2294             if( !defined( $html ) )
2295 0         0 {
2296 0         0 $self->error( "Unable to get html data of included file \"", $f->filename, "\": ", $f->error );
2297             return( $self->errmsg );
2298             }
2299 4   33     22 my $clone = $self->clone || do
2300             {
2301             warn( $self->error );
2302             return( $self->errmsg );
2303             };
2304             ## share our environment variables with our clone so we pass it to included files.
2305 4         21 ## If we are running under mod_perl, we'll use subprocess_env
2306 4         11 my $env = $self->env;
2307 4         26 $clone->{_env} = $env;
2308             return( $clone->parse( $html ) );
2309             }
2310              
2311             # XXX Legacy
2312             # http://perl.apache.org/docs/1.0/guide/snippets.html#Passing_Arguments_to_a_SSI_script
2313             sub parse_perl
2314 0     0 1 0 {
2315 0         0 my( $self, $args, $margs ) = @_;
2316             my $r = $self->apache_request;
2317 0         0  
2318             my( $pass_r, @arg1, @arg2, $sub ) = (1);
2319 0         0 {
  0         0  
2320 0         0 my @a;
2321             while( @a = splice( @$margs, 0, 2 ) )
2322 0         0 {
2323 0 0       0 $a[1] =~ s/\\(.)/$1/gs;
    0          
    0          
    0          
    0          
2324             if( lc( $a[0] ) eq 'sub' )
2325 0         0 {
2326             $sub = $a[1];
2327             }
2328             elsif( lc( $a[0] ) eq 'arg' )
2329 0         0 {
2330             push( @arg1, $a[1] );
2331             }
2332             elsif( lc( $a[0] ) eq 'args' )
2333 0         0 {
2334             push( @arg1, split( /,/, $a[1] ) );
2335             }
2336             elsif( lc( $a[0] ) eq 'pass_request' )
2337 0 0       0 {
2338             $pass_r = 0 if( lc( $a[1] ) eq 'no' );
2339             }
2340             elsif( $a[0] =~ s/^-// )
2341 0         0 {
2342             push( @arg2, @a );
2343             }
2344             ## Any unknown get passed as key-value pairs
2345             else
2346 0         0 {
2347             push( @arg2, @a );
2348             }
2349             }
2350             }
2351 0         0  
2352 0         0 $self->message( "sub is $sub, args are @arg1 & @arg2" );
2353             my $subref;
2354 0 0       0 ## for <!--#perl sub="sub {print ++$Access::Cnt }" -->
2355             if( $sub =~ /^[[:blank:]\h]*sub[[:blank:]\h]/ )
2356 0         0 {
2357 0 0       0 $subref = eval( $sub );
2358             if( $@ )
2359 0         0 {
2360             $self->error( "Perl eval of '$sub' failed: $@" )
2361             }
2362 0 0       0 ## return( $self->error( "sub=\"sub ...\" didn't return a reference" ) ) unless( ref( $subref ) );
2363             unless( ref( $subref ) )
2364 0         0 {
2365 0         0 $self->error( "sub=\"sub ...\" didn't return a reference" );
2366             return( $self->errmsg );
2367             }
2368             }
2369             ## for <!--#perl sub="package::subr" -->
2370             else
2371 14     14   188 {
  14         25  
  14         72230  
2372 0         0 no strict( 'refs' );
2373 0         0 $subref = ( defined( &{$sub} )
2374 0         0 ? \&{$sub}
2375 0         0 : defined( &{"${sub}::handler"} )
2376 0 0       0 ? \&{"${sub}::handler"}
  0 0       0  
2377             : \&{"main::$sub"});
2378             }
2379 0 0       0
2380             if( $r )
2381 0 0 0     0 {
2382 0 0       0 $pass_r = 0 if( $r and lc( $r->dir_config( 'SSIPerlPass_Request' ) ) eq 'no' );
2383             unshift( @arg1, $r ) if( $pass_r );
2384 0         0 }
2385 0         0 $self->message( 3, "sub is $subref, args are @arg1 & @arg2" );
2386             return( scalar( $subref->( @arg1, @arg2 ) ) );
2387             }
2388              
2389             sub parse_printenv
2390 0     0 1 0 {
2391 0         0 my $self = shift( @_ );
2392 0         0 my $env = $self->env;
  0         0  
2393             return( join( '', map( {"$_: $env->{$_}<br />\n"} sort( keys( %$env ) ) ) ) );
2394             }
2395              
2396             sub parse_set
2397 8     8 1 30 {
2398 8         28 my( $self, $args ) = @_;
2399 8         154 my $r = $self->apache_request;
2400 8         85 my $env = $self->env;
2401             $self->message( 3, "Setting variable \"$args->{var}\" to value \"$args->{value}\"." );
2402            
2403             ## $self->_interp_vars( $args->{value} );
2404             ## Do we need to decode and encode it?
2405 8 50 33     159 ## Possible values are: none, url, urlencoded, base64 or entity
2406             if( $args->{decoding} && lc( $args->{decoding} ) ne 'none' )
2407 0         0 {
2408 0         0 $args->{decoding} = lc( $args->{decoding} );
2409 0     0   0 try
2410 0 0       0 {
    0          
    0          
    0          
2411             if( $args->{decoding} eq 'url' )
2412 0         0 {
2413             $args->{value} = $self->decode_uri( $args->{value} );
2414             }
2415             elsif( $args->{decoding} eq 'urlencoded' )
2416 0         0 {
2417             $args->{value} = $self->decode_url( $args->{value} );
2418             }
2419             elsif( $args->{decoding} eq 'base64' )
2420 0         0 {
2421             $args->{value} = $self->decode_base64( $args->{value} );
2422             }
2423             elsif( $args->{decoding} eq 'entity' )
2424 0         0 {
2425             $args->{value} = $self->decode_entities( $args->{value} );
2426             }
2427 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2428 0     0   0 catch( $e )
2429 0         0 {
2430 0         0 $self->error( "Decoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
2431 0 0 0     0 return( $self->errmsg );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
2432             }
2433             }
2434 8         36
2435             $args->{value} = $self->parse_eval_expr( $args->{value} );
2436 8 50 33     38
2437             if( $args->{encoding} && lc( $args->{encoding} ) ne 'none' )
2438 0         0 {
2439 0         0 $args->{encoding} = lc( $args->{encoding} );
2440 0     0   0 try
2441 0 0       0 {
    0          
    0          
    0          
2442             if( $args->{encoding} eq 'url' )
2443 0         0 {
2444             $args->{value} = $self->encode_uri( $args->{value} );
2445             }
2446             elsif( $args->{encoding} eq 'urlencoded' )
2447 0         0 {
2448             $args->{value} = $self->encode_url( $args->{value} );
2449             }
2450             elsif( $args->{encoding} eq 'base64' )
2451 0         0 {
2452             $args->{value} = $self->encode_base64( $args->{value} );
2453             }
2454             elsif( $args->{encoding} eq 'entity' )
2455 0         0 {
2456             $args->{value} = $self->encode_entities( $args->{value} );
2457             }
2458 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2459 0     0   0 catch( $e )
2460 0         0 {
2461 0         0 $self->error( "Enecoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
2462 0 0 0     0 return( $self->errmsg );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
2463             }
2464             }
2465 8 50       28
2466             if( $r )
2467 0         0 {
2468 0         0 $r->subprocess_env( $args->{var}, $args->{value} );
2469             $env->{ $args->{var} } = $args->{value};
2470             }
2471             else
2472 8         41 {
2473             $env->{ $args->{var} } = $args->{value};
2474 8         67 }
2475             return( '' );
2476             }
2477              
2478             sub parse_ssi
2479 158     158 1 1309 {
2480             my( $self, $html ) = @_;
2481            
2482 158         293 ## For error reporting
2483 158 50       1070 my $orig = $html;
2484             if( $html =~ s/^(\w+)[[:blank:]\h]*// )
2485 158         354 {
2486 158 100 100     822 my $tag = $1;
2487 157         574 return if( $self->{suspend}->[0] and !( $tag =~ /^(if|elif|else|endif)/ ) );
2488 157   50     919 my $method = lc( "parse_${tag}" );
2489             my $code = $self->can( $method ) ||
2490             return( $self->error( "ssi function $tag is unsupported. No method $method found in package \"", ref( $self ), "\"." ) );
2491              
2492 157 50       502 ## Special case for comment directive because there is no key-value pair, but just text
2493 157         846 return( $self->$method( $html ) ) if( lc( $tag ) eq 'comment' );
2494 157         2907 $self->message( 3, "Parsing directive parameters for tag '$tag' and text '$html'" );
2495 157         1008 my $args = {};
2496 157 100       684 pos( $html ) = 0;
2497             if( $html =~ /^expr[[:blank:]\h]*\=/ )
2498 43 50       1632 {
2499             if( $html =~ /^$EXPR_RE$/ )
2500 43         825 {
2501 43         1080 $self->message( 3, "Found expression name '$+{attr_name}' and value '$+{attr_val}'." );
2502             $args->{ $+{attr_name} } = $+{attr_val};
2503             }
2504             else
2505 0         0 {
2506             warn( "Expression '$orig' is malformed\n" );
2507             }
2508             }
2509             else
2510 114         3202 {
2511             while( $html =~ /\G($ATTRIBUTES_RE)/gmcs )
2512 45         1005 {
2513             $args->{ $+{attr_name} } = $+{attr_val};
2514             }
2515 157     0   1430 }
  0         0  
2516             $self->message( 3, "Calling method \"$method\" with args: ", sub{ $self->dump( $args ) } );
2517 157         3255 # return( $self->$method( {@$args}, $args ) );
2518             return( $self->$method( $args ) );
2519 0         0 }
2520             return( '' );
2521             }
2522 0     0 1 0  
2523             sub path_info { return( shift->uri->path_info( @_ ) ); }
2524 0     0 1 0  
2525             sub query_string { return( shift->uri->query_string( @_ ) ); }
2526              
2527             ## http://httpd.apache.org/docs/2.4/developer/new_api_2_4.html
2528             ## https://github.com/eprints/eprints/issues/214
2529             sub remote_ip
2530 12     12 1 173 {
2531 12         42 my $self = shift( @_ );
2532 12         210 my $r = $self->apache_request;
2533 12 100       60 my $new = '';
2534 12         26 $new = shift( @_ ) if( @_ );
2535 12 50       37 my $ip;
2536             if( $r )
2537             {
2538 0         0 ## In Apache v2.4 or higher, client_ip is used instead of remote_ip
2539 0   0     0 my $c = $r->connection;
2540 0         0 my $coderef = $c->can( 'client_ip' ) // $c->can( 'remote_ip' );
2541 0     0   0 try
2542 0 0       0 {
2543 0         0 $coderef->( $c, $new ) if( $new );
2544             $ip = $coderef->( $c );
2545 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2546 0     0   0 catch( $e )
2547 0 0       0 {
2548 0 0 0     0 $self->error( "Unable to get the remote ip with the method Apache2::Connection->", ( $c->can( 'client_ip' ) ? 'client_ip' : 'remote_ip' ), ": $e" );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
2549 0 0       0 }
2550             $ip = $self->parse_echo({ var => 'REMOTE_ADDR' }) if( !CORE::length( $ip ) );
2551             }
2552             else
2553 12 100       43 {
2554 12         33 $self->{remote_ip} = $new if( $new );
2555 12 100       85 $ip = $self->{remote_ip};
2556             $ip = $self->parse_echo({ var => 'REMOTE_ADDR' }) if( !CORE::length( $ip ) );
2557 12 100       51 }
2558 5         21 return( $ip ) if( CORE::length( $ip ) );
2559             return( '' );
2560             }
2561              
2562 0     0 1 0 ## Same as document_uri
2563             sub request_uri { return( shift->uri->document_uri( @_ ) ); }
2564              
2565             sub server_version
2566 0     0 1 0 {
2567 0 0 0     0 my $self = shift( @_ );
2568 0 0       0 $self->{server_version} = $SERVER_VERSION if( !CORE::length( $self->{server_version} ) && CORE::length( $SERVER_VERSION ) );
2569 0 0       0 $self->{server_version} = shift( @_ ) if( @_ );
2570 0         0 return( $self->{server_version} ) if( $self->{server_version} );
2571 0 0       0 my $vers = '';
2572             if( $self->mod_perl )
2573 0         0 {
2574 0     0   0 try
2575 0         0 {
2576 0         0 my $desc = Apache2::ServerUtil::get_server_description();
2577 0 0       0 $self->message( 3, "Apache description is: '$desc'" );
2578             if( $desc =~ /\bApache\/([\d\.]+)/ )
2579 0         0 {
2580             $vers = $1;
2581             }
2582 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2583 0     0   0 catch( $e )
2584 0         0 {
2585 0 0 0     0 $self->message( 3, "Failed getting version from Apache2::ServerUtil::get_server_description()" );
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
2586 0         0 }
2587             $self->message( 3, "Found Apache version '$vers' from its description" );
2588             }
2589            
2590 0 0 0     0 ## XXX to test our alternative approach
2591             if( !$vers && ( my $apxs = File::Which::which( 'apxs' ) ) )
2592 0         0 {
2593 0         0 $vers = qx( $apxs -q -v HTTPD_VERSION );
2594 0 0       0 chomp( $vers );
2595             $vers = '' unless( $vers =~ /^[\d\.]+$/ );
2596             }
2597 0 0       0 ## Try apache2
2598             if( !$vers )
2599 0         0 {
2600             foreach my $bin ( qw( apache2 httpd ) )
2601 0 0       0 {
2602             if( ( my $apache2 = File::Which::which( $bin ) ) )
2603 0         0 {
2604 0 0       0 my $v_str = qx( $apache2 -v );
2605             if( ( split( /\r?\n/, $v_str ) )[0] =~ /\bApache\/([\d\.]+)/ )
2606 0         0 {
2607 0         0 $vers = $1;
2608 0         0 chomp( $vers );
2609             last;
2610             }
2611             }
2612             }
2613 0         0 }
2614 0 0       0 $self->message( 3, "Returning version '$vers'." );
2615             if( $vers )
2616 0         0 {
2617 0         0 $self->{server_version} = $SERVER_VERSION = version->parse( $vers );
2618             return( $self->{server_version} );
2619 0         0 }
2620             return( '' );
2621             }
2622 4     4 1 114  
2623             sub sizefmt { return( shift->_set_get_scalar( 'sizefmt', @_ ) ); }
2624 4     4 1 96  
2625             sub timefmt { return( shift->_set_get_scalar( 'timefmt', @_ ) ); }
2626 65     65 1 2515  
2627             sub trunk { return( shift->_set_get_boolean( 'trunk', @_ ) ); }
2628 28     28 0 217  
2629             sub uri { return( shift->_set_get_object( 'uri', 'Apache2::SSI::URI', @_ ) ); }
2630              
2631             sub parse_expr_args
2632 15     15 0 52 {
2633 15         35 my $self = shift( @_ );
2634 15 50       106 my $args = shift( @_ );
2635 15         126 return( $self->error( "I was expecting an array reference, but instead got '$args'." ) ) if( !$self->_is_array( $args ) );
2636 15         38 my $buff = [];
2637 15         51 my $prev_regexp_capture = $self->{_regexp_capture};
2638 15         264 my $r = $self->apache_request;
2639 15         66 my $env = $self->env;
2640             foreach my $this ( @$args )
2641 21   50     223 {
      100        
2642 21         399 $self->message( 3, "Processing argument of type '", ( $this->{type} // '' ), "' and sub type '", ( $this->{subtype} // '' ), "'." );
2643 21 50       122 my $res = $self->ap2perl_expr( $this, [] );
2644             push( @$buff, @$res ) if( $res );
2645 15         103 }
2646             return( join( ', ', @$buff ) );
2647             }
2648              
2649             sub _ipmatch
2650 3     3   8 {
2651 3   50     15 my $self = shift( @_ );
2652 3   33     15 my $subnet = shift( @_ ) || return( $self->error( "No subnet provided" ) );
2653 3         5 my $ip = shift( @_ ) || $self->remote_ip;
2654 3     3   6 try
2655 3         29 {
2656 3         27 local $SIG{__WARN__} = sub{};
2657 3         178342 my $net = Net::Subnet::subnet_matcher( $subnet );
2658 3 100       108433 my $res = $net->( $ip );
2659             return( $res ? 1 : 0 );
2660 3 100       17 }
  0 50       0  
  3 50       11  
  3 0       6  
  3 50       9  
  3         5  
  3         6  
  3         8  
  3         12  
  2         6  
  1         4  
  0         0  
  3         24  
  3         15  
  3         14  
  3         16  
  0         0  
  0         0  
  0         0  
  0         0  
2661 0     0   0 catch( $e )
2662 0         0 {
2663 0         0 $self->error( "Error while calling Net::Subnet: $e" );
2664 0 0 33     0 return( 0 );
  0 0 33     0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
  3         69  
  3         84  
2665             }
2666             }
2667              
2668             sub _is_ip
2669 2     2   5 {
2670 2         5 my $self = shift( @_ );
2671 2 50       9 my $ip = shift( @_ );
2672             return( 0 ) if( !length( $ip ) );
2673 2 50       17 ## We need to return either 1 or 0. By default, perl return undef for false
2674             return( $ip =~ /^(?:$RE{net}{IPv4}|$RE{net}{IPv6})$/ ? 1 : 0 );
2675             }
2676              
2677             sub _is_number
2678 15     15   46 {
2679 15         45 my $self = shift( @_ );
2680 15 50       81 my $word = shift( @_ );
2681 15 100       132 return( 0 ) if( !length( $word ) );
2682             return( $word =~ /^(?:$RE{num}{int}|$RE{num}{real})$/ ? 1 : 0 );
2683             }
2684              
2685             sub _format_time
2686 3     3   36 {
2687 3         9 my( $self, $time, $format, $tzone ) = @_;
2688 3   66     12 my $env = $self->env;
2689 3         93 $format ||= $self->{timefmt};
2690             $self->message( 3, "Time provided is ", scalar( localtime( $time ) ) );
2691 3         308 ## Quotes are important as they are used to stringify overloaded $time
2692 3   50     192 my $params = { epoch => "$time" };
2693 3 50       10 $params->{time_zone} = ( $tzone || 'local' );
2694 3         5 $params->{locale} = $env->{lang} if( length( $env->{lang} ) );
2695 3     3   5 try
2696 3         18 {
2697 3 50       3439 my $dt = DateTime->from_epoch( %$params );
2698             if( length( $format ) )
2699             {
2700             my $fmt = DateTime::Format::Strptime->new(
2701 3   50     27 pattern => $format,
2702             time_zone => ( $params->{time_zone} || 'local' ),
2703             locale => $dt->locale->code,
2704 3         13786 );
2705 3         170 $dt->set_formatter( $fmt );
2706             return( $dt );
2707             }
2708             else
2709 0         0 {
2710             return( $dt->format_cldr( $dt->locale->date_format_full ) );
2711             }
2712 3 50       17 }
  0 50       0  
  3 50       6  
  3 0       5  
  3 50       6  
  3         3  
  3         4  
  3         5  
  3         9  
  0         0  
  3         6  
  0         0  
  3         11  
  3         3  
  3         8  
  3         9  
  0         0  
  0         0  
  0         0  
  0         0  
2713 0     0   0 catch( $e )
2714 0         0 {
2715 0         0 $self->message( 3, "An error occurred getting a DateTime object for time \"$time\" with format \"$format\": $e" );
2716 0         0 $self->error( "An error occurred getting a DateTime object for time \"$time\" with format \"$format\": $e" );
2717 0 0 33     0 return( $self->errmsg );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  3         51  
  3         50  
2718             }
2719             }
2720              
2721             sub _handle_ifs
2722 79     79   139 {
2723 79         139 my $self = shift( @_ );
2724             my $cond = shift( @_ );
2725 79 100       265
2726             if( $self->{if_state}->[0] )
2727 32         78 {
2728             $self->{suspend}->[0] = 1;
2729             }
2730             else
2731 47         242 {
2732             $self->{suspend}->[0] = !( $self->{if_state}->[0] = !!$cond );
2733 79         484 }
2734             return( '' );
2735             }
2736              
2737             sub _has_utf8
2738 2     2   4 {
2739 2         63 my $self = shift( @_ );
2740             return( $_[0] =~ /$IS_UTF8/ );
2741             }
2742              
2743             sub _interp_vars
2744             {
2745 7     7   17 ## Find all $var and ${var} expressions in the string and fill them in.
2746             my $self = shift( @_ );
2747 7         34 ## Because ssi_echo may change $1, $2, ...
2748 7         42 my( $a, $b, $c );
  0         0  
2749 0 0       0 $_[0] =~ s{ (^|[^\\]) (\\\\)* \$(\{)?(\w+)(\})? }
2750             { ($a,$b,$c) = ($1,$2,$4);
2751             $a . ( length( $b ) ? substr( $b, length( $b ) / 2 ) : '' ) . $self->parse_echo({ var => $c }) }exg;
2752             }
2753              
2754 1     1   4 sub _lastmod
2755 1         5 {
2756 1         23 my( $self, $file, $format ) = @_;
2757             $self->message( 3, "Formatting time for file \"$file\" with format '$format'." );
2758             return( $self->_format_time( ( stat( "$file" ) )[9], $format ) );
2759             }
2760              
2761             ## This is different from the env() method. This one is obviously private
2762             ## whereas the env() one has triggers that could otherwise create an infinite loop.
2763 63     63   197 sub _set_env
2764 63         148 {
2765 63 50       215 my $self = shift( @_ );
2766 63 50       295 my $name = shift( @_ );
2767 63         174 return( $self->error( "No environment variable name provided." ) ) if( !length( $name ) );
2768 63         205 $self->{_env} = {} if( !ref( $self->{_env} ) );
2769 63         140 my $env = $self->{_env};
2770             $env->{ $name } = shift( @_ );
2771             return( $self );
2772             }
2773              
2774 0     0     sub _set_var
2775 0           {
2776 0 0         my $self = shift( @_ );
2777             my $r = shift( @_ );
2778 0           if( $r )
2779             {
2780             $r->subprocess_env( $_[0], $_[1] );
2781             }
2782 0           else
2783 0           {
2784             my $env = $self->env;
2785 0           $env->{ $_[0] } = $_[1];
2786             }
2787             return( $_[1] );
2788             }
2789              
2790             sub _time_args
2791 0     0     {
2792 0 0 0       ## This routine must respect the caller's wantarray() context.
2793             my( $self, $time, $zone ) = @_;
2794             return( ( $zone && $zone =~ /GMT/ ) ? gmtime( $time ) : localtime( $time ) );
2795             }
2796              
2797             ## Credits: Torsten Förtsch
2798             {
2799             package
2800             Apache2::SSI::Filter;
2801              
2802             if( exists( $ENV{MOD_PERL} ) &&
2803             $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ )
2804             {
2805             require Apache2::Filter;
2806             require Apache2::RequestUtil;
2807             require APR::Brigade;
2808             require APR::Bucket;
2809             require parent;
2810             parent->import( qw( Apache2::Filter ) );
2811             require Apache2::Const;
2812             Apache2::Const->import( -compile => qw( OK DECLINED HTTP_OK ) );
2813             eval( "sub fetch_content_filter : FilterRequestHandler { return( &apache_filter_handler ); }" );
2814             }
2815              
2816 0     0     sub read_bb
2817 0           {
2818 0           my( $bb, $buffer ) = @_;
2819             my $r = Apache2::RequestUtil->request;
2820 0           my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
2821              
2822             my $eos = 0;
2823             ## Cycling through APR::Bucket
2824             # while( my $b = $bb->first )
2825             # {
2826             # $eos++ if( $b->is_eos );
2827             # $r->log->debug( __PACKAGE__ . ": ", $b->length, " bytes of data received." );
2828             # ## $b->read( my $bdata );
2829             # my $len = $b->read( my $bdata );
2830             # $r->log->debug( __PACKAGE__ . ": data read is '$bdata' ($len byts read)" );
2831             # push( @$buffer, $bdata ) if( $buffer and length( $bdata ) );
2832 0 0         # $b->delete;
2833 0           # }
2834             $r->log->debug( __PACKAGE__, ": cycling through all the Brigade buckets." ) if( $debug > 0 );
2835 0 0         for( my $b = $bb->first; $b; $b = $bb->next( $b ) )
2836 0           {
2837 0 0         $r->log->debug( __PACKAGE__ . ": ", $b->length, " bytes of data received." ) if( $debug > 0 );
2838 0 0 0       my $len = $b->read( my $bdata );
2839 0           $r->log->debug( __PACKAGE__ . ": data read is '$bdata' ($len byts read)" ) if( $debug > 0 );
2840 0 0         push( @$buffer, $bdata ) if( $buffer and length( $bdata ) );
2841             $b->delete;
2842 0           $eos++, last if( $b->is_eos );
2843             }
2844             return( $eos );
2845             }
2846            
2847             ## We cannot declare it now. Instead we eval it so that it works under Apache and gets discarded outside
2848             ## sub fetch_content_filter : FilterRequestHandler
2849 0     0     sub apache_filter_handler
2850 0           {
2851 0 0         my( $f, $bb ) = @_;
2852             my $r = $f->r;
2853 0 0 0       unless( $f->ctx )
2854             {
2855             unless( $r->status == Apache2::Const::HTTP_OK or
2856 0           $r->pnotes->{force_fetch_content} )
2857 0           {
2858             $f->remove;
2859 0           return( Apache2::Const::DECLINED );
2860             }
2861             $f->ctx(1);
2862 0           }
2863            
2864 0           my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
2865 0 0          
2866 0 0         my $out = $f->r->pnotes->{out};
    0          
2867             $r->log->debug( __PACKAGE__ . ": reading data using '$out'." ) if( $debug > 0 );
2868 0           if( ref( $out ) eq 'ARRAY' )
2869 0 0         {
2870             read_bb( $bb, $out );
2871             $r->log->debug( __PACKAGE__ . ": data read is: ", join( '', @$out ) ) if( $debug > 0 );
2872             }
2873 0           elsif( ref( $out ) eq 'CODE' )
2874 0           {
2875             read_bb( $bb, my $buf = [] );
2876             $out->( $f->r, @$buf );
2877             }
2878 0 0         else
2879 0           {
2880 0           $r->log->debug( __PACKAGE__ . ": request is declined because \$out is neither an array or code." ) if( $debug > 0 );
2881             $f->remove;
2882 0           return( Apache2::Const::DECLINED );
2883             }
2884             return( Apache2::Const::OK );
2885             }
2886             }
2887              
2888             {
2889             package
2890             Apache2::RequestRec;
2891              
2892             if( exists( $ENV{MOD_PERL} ) &&
2893             $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ )
2894             {
2895             require Apache2::RequestRec;
2896             require Apache2::SubRequest;
2897             require APR::Table;
2898             require APR::Finfo;
2899             require APR::Const;
2900             APR::Const->import( -compile => qw( FILETYPE_REG ) );
2901             require Apache2::Const;
2902             Apache2::Const->import( -compile => qw( HTTP_OK OK HTTP_NOT_FOUND ) );
2903             require Apache2::Filter;
2904             require Apache2::FilterRec;
2905             require Apache2::Module;
2906             require ModPerl::Util;
2907             }
2908              
2909 0     0     sub headers_sent
2910             {
2911             my( $I ) = @_;
2912             # Check if any output has already been sent. If so the HTTP_HEADER
2913             # filter is missing in the output chain. If it is still present we
2914 0           # can send a normal error message or modify headers, see ap_die()
2915             # in httpd-2.2.x/modules/http/http_request.c.
2916 0 0         for( my $n = $I->output_filters; $n; $n = $n->next )
2917             {
2918             return if( $n->frec->name eq 'http_header' );
2919 0           }
2920             # http_header filter missing -- that means headers are sent
2921             return( 1 );
2922             }
2923              
2924 0     0     sub fetch_uri
2925 0 0 0       {
2926             my( $I, $url, $headers, $outfn ) = @_;
2927 0           if( @_ == 3 and ref( $headers ) eq 'CODE' )
2928 0           {
2929             $outfn = $headers;
2930             undef( $headers );
2931 0           }
2932 0            
2933 0           my $output = [];
2934 0 0         my $proxy = $url =~ m!^\w+?://!;
2935             my $subr;
2936 0 0         if( $proxy )
2937 0           {
2938             return unless( Apache2::Module::loaded( 'mod_proxy.c' ) );
2939             $subr = $I->lookup_uri( '/' );
2940             }
2941 0           else
2942             {
2943 0 0 0       $subr = $I->lookup_uri( $url );
      0        
2944             }
2945             if( $subr->status == Apache2::Const::HTTP_OK and
2946             ( length( $subr->handler ) ||
2947 0   0       $subr->finfo->filetype == APR::Const::FILETYPE_REG ) )
  0            
2948 0           {
2949 0 0         @{$subr->pnotes}{qw( out force_fetch_content )} = ( $outfn || $output, 1 );
2950             $subr->add_output_filter( \&Apache2::SSI::Filter::apache_filter_handler );
2951 0           if( $proxy )
2952 0           {
2953 0           $subr->proxyreq(2);
2954             $subr->filename( "proxy:" . $url );
2955 0           $subr->handler( 'proxy_server' );
2956 0 0         }
2957             $subr->headers_in->clear;
2958 0           if( $headers )
2959             {
2960 0           for( my $i = 0; $i < @$headers; $i += 2 )
2961             {
2962             $subr->headers_in->add( @$headers[ $i, $i + 1 ] );
2963             }
2964 0 0         }
2965             $subr->headers_in->add( 'User-Agent' => "Apache2::SSI/$VERSION" )
2966 0 0 0       unless( exists( $subr->headers_in->{'User-Agent'} ) );
2967 0           $_ = $I->headers_in->{Host} and $subr->headers_in->add( 'Host' => $_ )
2968 0 0         unless( exists( $subr->headers_in->{'Host'} ) );
2969             $subr->run;
2970 0           if( wantarray )
2971 0           {
2972 0           my( %hout );
2973             $hout{STATUS} = $subr->status;
2974             $hout{STATUSLINE} = $subr->status_line;
2975 0     0     $subr->headers_out->do(sub
2976 0           {
2977 0           $hout{ lc( $_[0] ) } = $_[1];
2978 0           1;
2979             });
2980             return( ( join( '', @$output ), \%hout ) );
2981             }
2982 0           else
2983             {
2984             return( join( '', @$output ) );
2985 0 0         }
2986             }
2987 0           if( wantarray )
2988 0           {
2989             my( %hout );
2990 0 0         $hout{STATUS} = $subr->status;
2991             $hout{STATUS} = Apache2::Const::HTTP_NOT_FOUND
2992             if( $hout{STATUS} == Apache2::Const::HTTP_OK );
2993 0     0     $subr->headers_out->do(sub
2994 0           {
2995 0           $hout{ lc( $_[0] ) } = $_[1];
2996 0           1;
2997             });
2998             return( ( undef, \%hout ) );
2999             }
3000 0           else
3001             {
3002 0           return;
3003             }
3004             return;
3005             }
3006             }
3007              
3008             1;
3009              
3010             __END__
3011              
3012             =encoding utf-8
3013              
3014             =head1 NAME
3015              
3016             Apache2::SSI - Apache2 Server Side Include
3017              
3018             =head1 SYNOPSIS
3019              
3020             Outside of Apache:
3021              
3022             use Apache2::SSI;
3023             my $ssi = Apache2::SSI->new(
3024             ## If running outside of Apache
3025             document_root => '/path/to/base/directory'
3026             ## Default error message to display when ssi failed to parse
3027             ## Default to [an error occurred while processing this directive]
3028             errmsg => '[Oops]'
3029             );
3030             my $fh = IO::File->new( "</some/file.html" ) || die( "$!\n" );
3031             $fh->binmode( ':utf8' );
3032             my $size = -s( $fh );
3033             my $html;
3034             $fh->read( $html, $size );
3035             $fh->close;
3036             if( !defined( my $result = $ssi->parse( $html ) ) )
3037             {
3038             $ssi->throw;
3039             };
3040             print( $result );
3041              
3042             Inside Apache, in the VirtualHost configuration, for example:
3043              
3044             PerlModule Apache2::SSI
3045             PerlOptions +GlobalRequest
3046             PerlSetupEnv On
3047             <Directory "/home/joe/www">
3048             Options All +Includes +ExecCGI -Indexes -MultiViews
3049             AllowOverride All
3050             SetHandler modperl
3051             # You can choose to set this as a response handler or a output filter, whichever works.
3052             # PerlResponseHandler Apache2::SSI
3053             PerlOutputFilterHandler Apache2::SSI
3054             # If you do not set this to On, path info will not work, example:
3055             # /path/to/file.html/path/info
3056             # See: <https://httpd.apache.org/docs/current/en/mod/core.html#acceptpathinfo>
3057             AcceptPathInfo On
3058             # To enable no-caching (see no_cache() in Apache2::RequestUtil:
3059             PerlSetVar Apache2_SSI_NO_CACHE On
3060             # This is required for exec cgi to work:
3061             # <https://httpd.apache.org/docs/current/en/mod/mod_include.html#element.exec>
3062             <Files ~ "\.pl$">
3063             SetHandler perl-script
3064             AcceptPathInfo On
3065             PerlResponseHandler ModPerl::PerlRun
3066             ## Even better for stable cgi scripts:
3067             ## PerlResponseHandler ModPerl::Registry
3068             ## Change this in mod_perl1 PerlSendHeader On to the following:
3069             ## <https://perl.apache.org/docs/2.0/user/porting/compat.html#C_PerlSendHeader_>
3070             PerlOptions +ParseHeaders
3071             </Files>
3072             <Files ~ "\.cgi$">
3073             SetHandler cgi-script
3074             AcceptPathInfo On
3075             </Files>
3076             # To enable debugging output in the Apache error log
3077             # PerlSetVar Apache2_SSI_DEBUG 3
3078             # To set the default echo message
3079             # PerlSetVar Apache2_SSI_Echomsg
3080             # To Set the default error message
3081             # PerlSetVar Apache2_SSI_Errmsg "Oops, something went wrong"
3082             # To Set the default size format: bytes or abbrev
3083             # PerlSetVar Apache2_SSI_Sizefmt "bytes"
3084             # To Set the default date time format
3085             # PerlSetVar Apache2_SSI_Timefmt ""
3086             # To enable legacy mode:
3087             # PerlSetVar Apache2_SSI_Expression "legacy"
3088             # To enable trunk mode:
3089             # PerlSetVar Apache2_SSI_Expression "trunk"
3090             </Directory>
3091              
3092             =head1 VERSION
3093              
3094             v0.2.0
3095              
3096             =head1 DESCRIPTION
3097              
3098             L<Apache2::SSI> implements L<Apache Server Side Include|https://httpd.apache.org/docs/current/en/howto/ssi.html>, a.k.a. SSI, within and outside of Apache2/mod_perl2 framework.
3099              
3100             L<Apache2::SSI> is inspired from the original work of L<Apache::SSI> with the main difference that L<Apache2::SSI> works well when called from within Apache mod_perl2 as well as when called outside of Apache if you want to simulate L<SSI|https://httpd.apache.org/docs/current/en/howto/ssi.html>.
3101              
3102             L<Apache2::SSI> also implements all of Apache SSI features, including functions, encoding and decoding and old style variables such as C<${QUERY_STRING}> as well as modern style such as C<v('QUERY_STRING')> and variants such as C<%{REQUEST_URI}>.
3103              
3104             See below details in this documentation and in the section on L</"SSI Directives">
3105              
3106             Under Apache mod_perl, you would implement it like this in your C<apache2.conf> or C<httpd.conf>
3107              
3108             <Files *.phtml>
3109             SetHandler modperl
3110             PerlOutputFilterHandler Apache2::SSI
3111             </Files>
3112              
3113             This would enable L<Apache2::SSI> for files whose extension is C<.phtml>. You can also limit this by location, such as:
3114              
3115             <Location /some/web/path>
3116             <Files *.html>
3117             SetHandler modperl
3118             PerlOutputFilterHandler Apache2::SSI
3119             </Files>
3120             </Location>
3121              
3122             In the example above, we enable it in files with extensions C<.phtml>, but you can, of course, enable it for all html by setting extension C<.html> or whatever extension you use for your html files.
3123              
3124             As pointed out by Ken Williams, the original author of L<Apache::SSI>, the benefit for using L<Apache2::SSI> is:
3125              
3126             =over 4
3127              
3128             =item 1. You want to subclass L<Apache2::SSI> and have granular control on how to render ssi
3129              
3130             =item 2. You want to "parse the output of other mod_perl handlers, or send the SSI output through another handler"
3131              
3132             =item 3. You want to imitate SSI without activating them or without using Apache (such as in command line) or within your perl/cgi script
3133              
3134             =back
3135              
3136             =head2 INSTALLATION
3137              
3138             perl Makefile.PL
3139             make
3140             make test
3141             sudo make install
3142              
3143             This will detect if you have Apache installed and run the Apache mod_perl2 tests by starting a separate instance of Apache on a non-standard port like 8123 under your username just for the purpose of testing. This is all handled automatically by L<Apache::Test>
3144              
3145             If you do not have Apache or mod_perl installed, it will still install, but obviously not start an instance of Apache/mod_perl, nor perform any of the Apache mod_perl tests.
3146              
3147             It tries hard to find the Apache configuration file. You can help it by providing command line modifiers, such as:
3148              
3149             perl Makefile.PL -apxs /usr/bin/apxs
3150              
3151             or, even specify the Apache configuration file:
3152              
3153             perl Makefile.PL -apxs /usr/bin/apxs -httpd_conf /home/john/etc/apache2/apache2.conf
3154              
3155             To run only some tests, for example:
3156              
3157             make test TEST_FILES="./t/31.file.t"
3158              
3159             If you are on a Linux type system, you can install C<apxs> by issuing on the command line:
3160              
3161             apt install apache2-dev
3162              
3163             You can check if you have it installed with the following command:
3164              
3165             dpkg -l | grep apache
3166              
3167             See L<ExtUtils::MakeMaker> for more information.
3168              
3169             =head1 METHODS
3170              
3171             =head2 new
3172              
3173             This instantiate an object that is used to access other key methods. It takes the following parameters:
3174              
3175             =over 4
3176              
3177             =item I<apache_filter>
3178              
3179             This is the L<Apache2::Filter> object object that is provided if running under mod_perl.
3180              
3181             =item I<apache_request>
3182              
3183             This is the L<Apache2::RequestRec> object that is provided if running under mod_perl.
3184              
3185             it can be retrieved from L<Apache2::RequestUtil/request> or via L<Apache2::Filter/r>
3186              
3187             You can get this L<Apache2::RequestRec> object by requiring L<Apache2::RequestUtil> and calling its class method L<Apache2::RequestUtil/request> such as Apache2::RequestUtil->request and assuming you have set C<PerlOptions +GlobalRequest> in your Apache Virtual Host configuration.
3188              
3189             Note that there is a main request object and subprocess request object, so to find out which one you are dealing with, use L<Apache2::RequestUtil/is_initial_req>, such as:
3190              
3191             use Apache2::RequestUtil (); # extends Apache2::RequestRec objects
3192             my $r = $r->is_initial_req ? $r : $r->main;
3193              
3194             =item I<debug>
3195              
3196             Sets the debug level. Starting from 3, this will output on the STDERR or in Apache error log a lot of debugging output.
3197              
3198             =item I<document_root>
3199              
3200             This is only necessary to be provided if this is not running under Apache mod_perl. Without this value, L<Apache2::SSI> has no way to guess the document root and will not be able to function properly and will return an L</error>.
3201              
3202             =item I<document_uri>
3203              
3204             This is only necessary to be provided if this is not running under Apache mod_perl. This must be the uri of the document being served, such as C</my/path/index.html>. So, if you are using this outside of the rim of Apache mod_perl and your file resides, for example, at C</home/john/www/my/path/index.html> and your document root is C</home/john/www>, then the document uri would be C</my/path/index.html>
3205              
3206             =item I<errmsg>
3207              
3208             The error message to be returned when a ssi directive fails. By default, it is C<[an error occurred while processing this directive]>
3209              
3210             =item I<html>
3211              
3212             The html data to be parsed. You do not have to provide that value now. You can provide it to L</parse> as its first argument when you call it.
3213              
3214             =item I<legacy>
3215              
3216             Takes a boolean value suchas C<1> or C<0> to indicate whether the Apache2 expression supported accepts legacy style.
3217              
3218             Legacy Apache expression typically allows for perl style variable C<${REQUEST_URI}> versus the modern style of C<%{REQUEST_URI}> and just an equal sign to imply a regular expression such as:
3219              
3220             $HTTP_COOKIES = /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/
3221              
3222             Modern expression equivalent would be:
3223              
3224             %{HTTP_COOKIES} =~ /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/
3225              
3226             See L<Regexp::Common::Apache2> for more information.
3227              
3228             See also the property I<trunk> to enable experimental expressions.
3229              
3230             =item I<remote_ip>
3231              
3232             This is used when you want to artificially set the remote ip address, i.e. the address of the visitor accessing the page. This is used essentially by the SSI directive:
3233              
3234             my $ssi = Apache2::SSI->new( remote_ip => '192.168.2.10' ) ||
3235             die( Apache2::SSI->error );
3236              
3237             <!--#if expr="-R '192.168.2.0/24' || -R '127.0.0.1/24'" -->
3238             Remote ip is part of my private network
3239             <!--#else -->
3240             Go away!
3241             <!--#endif -->
3242              
3243             =item I<sizefmt>
3244              
3245             The default way to format a file size. By default, this is C<abbrev>, which means a human readable format such as C<2.5M> for 2.5 megabytes. Other possible value is C<bytes> which would have the C<fsize> ssi directive return the size in bytes.
3246              
3247             See L<Apache2 documentation|https://httpd.apache.org/docs/current/en/howto/ssi.html> for more information on this.
3248              
3249             =item I<timefmt>
3250              
3251             The default way to format a date time. By default, this uses the display according to your locale, such as C<ja_JP> (for Japan) or C<en_GB> for the United Kingdoms. The time zone can be specified in the format, or it will be set to the local time zone, whatever it is.
3252              
3253             See L<Apache2 documentation|https://httpd.apache.org/docs/current/en/howto/ssi.html> for more information on this.
3254              
3255             =item I<trunk>
3256              
3257             This takes a boolean value such as C<0> or C<1> and when enabled this allows the support for Apache2 experimental expressions.
3258              
3259             See L<Regexp::Common::Apache2> for more information.
3260              
3261             Also, see the property I<legacy> to enable legacy Apache2 expressions.
3262              
3263             =back
3264              
3265             =head2 handler
3266              
3267             This is a key method expected by mod_perl. Depending on how this module is used, it will redirect either to L</apache_filter_handler> or to L</apache_response_handler>
3268              
3269             =head2 ap2perl_expr
3270              
3271             This method is used to convert Apache2 expressions into perl equivalents to be then eval'ed.
3272              
3273             It takes an hash reference provided by L<Apache2::Expression/parse>, an array reference to store the output recursively and an optional hash reference of parameters.
3274              
3275             It parse recursively the structure provided in the hash reference to provide the perl equivalent for each Apache2 expression component.
3276              
3277             It returns the array reference provided used as the content buffer. This array is used by L</parse_expr> and then joined using a single space to form a string of perl expression to be eval'ed.
3278              
3279             =head2 apache_filter
3280              
3281             Set or get the L<Apache2::Filter> object.
3282              
3283             When running under Apache mod_perl this is set automatically from the special L</handler> method.
3284              
3285             =head2 apache_filter_handler
3286              
3287             This method is called from L</handler> to handle the Apache response when this module L<Apache2::SSI> is used as a filter handler.
3288              
3289             See also L</apache_response_handler>
3290              
3291             =head2 apache_request
3292              
3293             Sets or gets the L<Apache2::RequestRec> object. As explained in the L</new> method, you can get this Apache object by requiring the package L<Apache2::RequestUtil> and calling L<Apache2::RequestUtil/request> such as C<Apache2::RequestUtil->request> assuming you have set C<PerlOptions +GlobalRequest> in your Apache Virtual Host configuration.
3294              
3295             When running under Apache mod_perl this is set automatically from the special L</handler> method, such as:
3296              
3297             my $r = $f->r; # $f is the Apache2::Filter object provided by Apache
3298              
3299             =head2 apache_response_handler
3300              
3301             This method is called from L</handler> to handle the Apache response when this module L<Apache2::SSI> is used as a response handler.
3302              
3303             See also L</apache_filter_handler>
3304              
3305             =head2 clone
3306              
3307             Create a clone of the object and return it.
3308              
3309             =head2 decode_base64
3310              
3311             Decode base64 data provided. When running under Apache mod_perl, this uses L<APR::Base64/decode> module, otherwise it uses L<MIME::Base64/decode>
3312              
3313             If the decoded data contain utf8 data, this will decoded the utf8 data using L<Encode/decode>
3314              
3315             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3316              
3317             =head2 decode_entities
3318              
3319             Decode html data containing entities. This uses L<HTML::Entities/decode_entities>
3320              
3321             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3322              
3323             Example:
3324              
3325             $ssi->decode_entities( 'Tous les &Atilde;&ordf;tres humains naissent libres et &Atilde;&copy;gaux en dignit&Atilde;&copy; et en droits.' );
3326             # Tous les êtres humains naissent libres et égaux en dignité et en droits.
3327              
3328             =head2 decode_uri
3329              
3330             Decode uri encoded data. This uses L<URI::Escape/uri_unescape>.
3331              
3332             Not to be confused with x-www-form-urlencoded data. For that see L</decode_url>
3333              
3334             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3335              
3336             Example:
3337              
3338             $ssi->decode_uri( 'https%3A%2F%2Fwww.example.com%2F' );
3339             # https://www.example.com/
3340              
3341             =head2 decode_url
3342              
3343             Decode x-www-form-urlencoded encoded data. When using Apache mod_perl, this uses L<APR::Request/decode> and L<Encode/decode>, otherwise it uses L<URL::Encode/url_decode_utf8> (its XS version) to achieve the same result.
3344              
3345             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3346              
3347             Example:
3348              
3349             $ssi->decode_url( 'Tous+les+%C3%83%C2%AAtres+humains+naissent+libres+et+%C3%83%C2%A9gaux+en+dignit%C3%83%C2%A9+et+en+droits.' );
3350             # Tous les êtres humains naissent libres et égaux en dignité et en droits.
3351              
3352             =head2 document_filename
3353              
3354             This is an alias for L<Apache2::SSI::URI/filename>
3355              
3356             =head2 document_directory
3357              
3358             Returns an L<Apache2::SSI::URI> object of the current directory of the L</document_uri> provided.
3359              
3360             =head2 document_path
3361              
3362             Sets or gets the uri path to the document. This is the same as L</document_uri>, except it is striped from L</query_string> and L</path_info>.
3363              
3364             =head2 document_root
3365              
3366             Sets or gets the document root.
3367              
3368             Wen running under Apache mod_perl, this value will be available automatically, using L<Apache2::RequestRec/document_root> method.
3369              
3370             If it runs outside of Apache, this will use the value provided upon instantiating the object and passing the I<document_root> parameter. If this is not set, it will return the value of the environment variable C<DOCUMENT_ROOT>.
3371              
3372             =head2 document_uri
3373              
3374             Sets or gets the document uri, which is the uri of the document being processed.
3375              
3376             For example:
3377              
3378             /index.html
3379              
3380             Under Apache, this will get the environment variable C<DOCUMENT_URI> or calls the L<Apache2::RequestRec/uri> method.
3381              
3382             Outside of Apache, this will rely on a value being provided upon instantiating an object, or the environment variable C<DOCUMENT_URI> be present.
3383              
3384             The value should be an absolute uri.
3385              
3386             =head2 echomsg
3387              
3388             The default message to be returned for the C<echo> command when the variable called is not defined.
3389              
3390             Example:
3391              
3392             $ssi->echomsg( '[Value Undefined]' );
3393             ## or in the document itself
3394             <!--#config echomsg="[Value Undefined]" -->
3395             <!--#echo var="NON_EXISTING" encoding="none" -->
3396              
3397             would produce:
3398              
3399             [Value Undefined]
3400              
3401             =head2 encode_base64
3402              
3403             Encode data provided into base64. When running under Apache mod_perl, this uses L<APR::Base64/encode> module, otherwise it uses L<MIME::Base64/encode>
3404              
3405             If the data have the perl internal utf8 flag on as checked with L<Encode/is_utf8>, this will encode the data into utf8 using L<Encode/encode> before encoding it into base64.
3406              
3407             Please note that the base64 encoded resulting data is all on one line, similar to what Apache would do. The data is B<NOT> broken into lines of 76 characters.
3408              
3409             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3410              
3411             =head2 encode_entities
3412              
3413             Encode data into html entities. This uses L<HTML::Entities/encode_entities>
3414              
3415             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3416              
3417             Example:
3418              
3419             $ssi->encode_entities( 'Tous les êtres humains naissent libres et égaux en dignité et en droits.' );
3420             # Tous les &Atilde;&ordf;tres humains naissent libres et &Atilde;&copy;gaux en dignit&Atilde;&copy; et en droits.
3421              
3422             =head2 encode_uri
3423              
3424             Encode uri data. This uses L<URI::Escape/uri_escape_utf8>.
3425              
3426             Not to be confused with x-www-form-urlencoded data. For that see L</encode_url>
3427              
3428             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3429              
3430             Example:
3431              
3432             $ssi->encode_uri( 'https://www.example.com/' );
3433             # https%3A%2F%2Fwww.example.com%2F
3434              
3435             =head2 encode_url
3436              
3437             Encode data provided into an x-www-form-urlencoded string. When using Apache mod_perl, this uses L<APR::Request/encode>, otherwise it uses L<URL::Encode/url_encode_utf8> (its XS version)
3438              
3439             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3440              
3441             Example:
3442              
3443             $ssi->encode_url( 'Tous les êtres humains naissent libres et égaux en dignité et en droits.' );
3444             # Tous+les+%C3%83%C2%AAtres+humains+naissent+libres+et+%C3%83%C2%A9gaux+en+dignit%C3%83%C2%A9+et+en+droits.
3445              
3446             =head2 env
3447              
3448             Sets or gets the value for an environment variable. Or, if no environment variable name is provided, it returns the entire hash reference. This method is intended to be used by users of this module, not by developers wanting to inherit from it.
3449              
3450             Note that the environment variable hash is unique for each new object, so it works like L<Apache2::RequestRec/subprocess_env>, meaning each process has its set of environment variable.
3451              
3452             When a value is set for an environment variable that has an equivalent name, it will call the method as well with the new value provided. This is done to ensure data consistency and also additional processing if necessary.
3453              
3454             For example, let assume you set the environment variable C<REQUEST_URI> or C<DOCUMENT_URI> like this:
3455              
3456             $ssi->env( REQUEST_URI => '/some/path/to/file.html?q=something&l=ja_JP' );
3457              
3458             This will, in turn, call L</request_uri>, which is an alias for L<document_uri> and this method will get the uri, path info and query string from the value provided and set those values accordingly, so they can be available when parsing.
3459              
3460             =head2 errmsg
3461              
3462             Sets or gets the error message to be displayed in lieu of a faulty ssi directive. This is the same behaviour as in Apache.
3463              
3464             =head2 error
3465              
3466             Retrieve the error object set. This is a L<Module::Generic::Error> object.
3467              
3468             This module does not die nor "croak", but instead returns undef when an error occurs and set the error object.
3469              
3470             It is up to you to check the return value of the method calls. If you do not, you will miss important information. If you really want your script to die, it is up to you to interrupt it:
3471              
3472             if( !defined( $ssi->parse( $some_html_data ) ) )
3473             {
3474             die( $ssi->error );
3475             }
3476              
3477             or maybe more simply, when you are sure you will not get a false, but defined value:
3478              
3479             $ssi->parse( $some_html_data ) || die( $ssi->error );
3480              
3481             This example is dangerous, because L</parse> might return an empty string which will be construed as a false value and will trigger the die statement, even though no error had occurred.
3482              
3483             =head2 filename
3484              
3485             This is an alias for L<Apache2::SSI::URI/filename>
3486              
3487             =head2 find_file
3488              
3489             Provided with a file path, and this will resolve any variable used and attempt to look it up as a file if the argument I<file> is provided with a file path as a value, or as a URI if the argument C<virtual> is provided as an argument.
3490              
3491             This will call L</lookup_file> or L</lookup_uri> depending on whether it is dealing with a file or an uri.
3492              
3493             It returns a L<Apache2::SSI::URI> object which is stringifyable and contain the file path.
3494              
3495             =head2 finfo
3496              
3497             Returns a L<Apache2::SSI::Finfo> object. This provides access to L<perlfunc/stat> information as method, taking advantage of L<APR::Finfo> when running under Apache, and L<File::stat>-like interface otherwise. See L<Apache2::SSI::Finfo> for more information.
3498              
3499             =head2 html
3500              
3501             Sets or gets the html data to be processed.
3502              
3503             =head2 lookup_file
3504              
3505             Provided with a file path and this will look up the file.
3506              
3507             When using Apache, this will call L<Apache2::SubRequest/lookup_file>. Outside of Apache, this will mimick Apache's lookup_file method by searching the file relative to the directory of the current document being served, i.e. the L</document_uri>.
3508              
3509             As per Apache SSI documentation, you cannot specify a path starting with C</> or C<../>
3510              
3511             It returns a L<Apache2::SSI::File> object.
3512              
3513             =head2 lookup_uri
3514              
3515             Provided with an uri, and this will loo it up and return a L<Apache2::SSI::URI> object.
3516              
3517             Under Apache mod_perl, this uses L<Apache2::SubRequest/lookup_uri> to achieve that. Outside of Apache it will attempt to lookup the uri relative to the document root if it is an absolute uri or to the current document uri.
3518              
3519             It returns a L<Apache2::SSI::URI> object.
3520              
3521             =head2 mod_perl
3522              
3523             Returns true when running under mod_perl, false otherwise.
3524              
3525             =head2 parse
3526              
3527             Provided with html data and if none is provided will use the data specified with the method L</html>, this method will parse the html and process the ssi directives.
3528              
3529             It returns the html string with the ssi result.
3530              
3531             =head2 parse_config
3532              
3533             Provided with an hash reference of parameters and this sets three of the object parameters that can also be set during object instantiation:
3534              
3535             =over 4
3536              
3537             =item I<echomsg>
3538              
3539             The value is a message that is sent back to the client if the echo element attempts to echo an undefined variable.
3540              
3541             This overrides any default value set for the parameter I<echomsg> upon object instantiation.
3542              
3543             =item I<errmsg>
3544              
3545             This is the default error message to be used as the result for a faulty ssi directive.
3546              
3547             See the L</echomsg> method.
3548              
3549             =item I<sizefmt>
3550              
3551             This is the format to be used to format the files size. Value can be either C<bytes> or C<abbrev>
3552              
3553             See also the L</sizefmt> method.
3554              
3555             =item I<timefmt>
3556              
3557             This is the format to be used to format the dates and times. The value is a date formatting based on L<POSIX/strftime>
3558              
3559             See also the L</timefmt> method.
3560              
3561             =back
3562              
3563             =head2 parse_echo
3564              
3565             Provided with an hash reference of parameter and this process the C<echo> ssi directive and returns its output as a string.
3566              
3567             For example:
3568              
3569             Query string passed: <!--#echo var="QUERY_STRING" -->
3570              
3571             There are a number of standard environment variable accessible under SSI on top of other environment variables set. See L<SSI Directives> section below.
3572              
3573             =head2 parse_echo_date_gmt
3574              
3575             Returns the current date with time zone set to gmt and based on the provided format or the format available for the current locale such as C<ja_JP> or C<en_GB>.
3576              
3577             =head2 parse_echo_date_local
3578              
3579             Returns the current date with time zone set to the local time zone whatever that may be and on the provided format or the format available for the current locale such as C<ja_JP> or C<en_GB>.
3580              
3581             Example:
3582              
3583             <!--#echo var="DATE_LOCAL" -->
3584              
3585             =head2 parse_echo_document_name
3586              
3587             Returns the document name. Under Apache, this returns the environment variable C<DOCUMENT_NAME>, if set, or the base name of the value returned by L<Apache2::RequestRec/filename>
3588              
3589             Outside of Apache, this returns the environment variable C<DOCUMENT_NAME>, if set, or the base name of the value for L</document_uri>
3590              
3591             Example:
3592              
3593             <!--#echo var="DOCUMENT_NAME" -->
3594              
3595             If the uri were C</some/where/file.html>, this would return only C<file.html>
3596              
3597             =head2 parse_echo_document_uri
3598              
3599             Returns the value of L</document_uri>
3600              
3601             Example:
3602              
3603             <!--#echo var="DOCUMENT_URI" -->
3604              
3605             The document uri would include, if any, any path info and query string.
3606              
3607             =head2 parse_echo_last_modified
3608              
3609             This returns document last modified date. Under Apache, there is a standard environment variable called C<LAST_MODIFIED> (see the section on L</SSI Directives>), and if somehow absent, it will return instead the formatted last modification datetime for the file returned with L<Apache2::RequestRec/filename>. The formatting of that date follows whatever format provided with L</timefmt> or by default the datetime format for the current locale (e.g. C<ja_JP>).
3610              
3611             Outside of Apache, the similar result is achieved by returning the value of the environment variable C<LAST_MODIFIED> if available, or the formatted datetime of the document uri as set with L</document_uri>
3612              
3613             Example:
3614              
3615             <!--#echo var="LAST_MODIFIED" -->
3616              
3617             =head2 parse_eval_expr
3618              
3619             Provided with a string representing an Apache2 expression and this will parse it, transform it into a perl equivalent and return its value.
3620              
3621             It does the parsing using L<Apache2::Expression/parse> called from L</parse_expr>
3622              
3623             If the expression contains regular expression with capture groups, the value of capture groups will be stored and will be usable in later expressions, such as:
3624              
3625             <!--#config errmsg="[Include error]" -->
3626             <!--#if expr="%{HTTP_COOKIE} =~ /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/"-->
3627             <!--#set var="CONTENT_LANGUAGE" value="%{tolower:$1}"-->
3628             <!--#elif expr="-z %{CONTENT_LANGUAGE}"-->
3629             <!--#set var="CONTENT_LANGUAGE" value="en"-->
3630             <!--#endif-->
3631             <!DOCTYPE html>
3632             <html lang="<!--#echo encoding="none" var="CONTENT_LANGUAGE" -->">
3633              
3634             =head2 parse_exec
3635              
3636             Provided with an hash reference of parameters and this process the C<exec> ssi directives.
3637              
3638             Example:
3639              
3640             <!--#exec cgi="/uri/path/to/progr.cgi" -->
3641              
3642             or
3643              
3644             <!--#exec cmd="/some/system/file/path.sh" -->
3645              
3646             =head2 parse_expr
3647              
3648             It takes a string representing an Apache2 expression and calls L<Apache2::Expression/parse> to break it down, and then calls L</ap2perl_expr> to transform it into a perl expression that is then eval'ed by L</parse_eval_expr>.
3649              
3650             It returns the perl representation of the Apache2 expression.
3651              
3652             To make this work, certain Apache2 standard functions used such as C<base64> or C<md5> are converted to use this package function equivalents. See the C<parse_func_*> methods for more information.
3653              
3654             =head2 parse_elif
3655              
3656             Parse the C<elif> condition.
3657              
3658             Example:
3659              
3660             <!--#if expr=1 -->
3661             Hi, should print
3662             <!--#elif expr=1 -->
3663             Shouldn't print
3664             <!--#else -->
3665             Shouldn't print
3666             <!--#endif -->
3667              
3668             =head2 parse_else
3669              
3670             Parse the C<else> condition.
3671              
3672             See L</parse_elif> above for example.
3673              
3674             =head2 parse_endif
3675              
3676             Parse the C<endif> condition.
3677              
3678             See L</parse_elif> above for example.
3679              
3680             =head2 parse_flastmod
3681              
3682             Process the ssi directive C<flastmod>
3683              
3684             Provided with an hash reference of parameters and this will return the formatted date time of the file last modification time.
3685              
3686             =head2 parse_fsize
3687              
3688             Provided with an hash reference of parameters and this will return the formatted file size.
3689              
3690             The output is affected by the value of L</sizefmt>. If its value is C<bytes>, it will return the raw size in bytes, and if its value is C<abbrev>, it will return its value formated in kilo, mega or giga units.
3691              
3692             Example
3693              
3694             <!--#config sizefmt="abbrev" -->
3695             This file size is <!--#fsize file="/some/filesystem/path/to/archive.tar.gz" -->
3696              
3697             would return:
3698              
3699             This file size is 12.7M
3700              
3701             Or:
3702              
3703             <!--#config sizefmt="bytes" -->
3704             This file size is <!--#fsize virtual="/some/filesystem/path/to/archive.tar.gz" -->
3705              
3706             would return:
3707              
3708             This file size is 13,316,917 bytes
3709              
3710             The size value before formatting is a L<Module::Generic::Number> and the output is formatted using L<Number::Format> by calling L<Module::Generic::Number/format>
3711              
3712             =head2 parse_func_base64
3713              
3714             Returns the arguments provided into a base64 string.
3715              
3716             If the arguments are utf8 data with perl internal flag on, as checked with L<Encode/is_utf8>, this will encode the data into utf8 with L<Encode/encode> before encoding it into base64.
3717              
3718             Example:
3719              
3720             <!--#set var="payload" value='{"sub":"1234567890","name":"John Doe","iat":1609047546}' encoding="base64" -->
3721             <!--#if expr="$payload == 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNjA5MDQ3NTQ2fQo='" -->
3722             Payload matches
3723             <!--#else -->
3724             Sorry, this failed
3725             <!--#endif -->
3726              
3727             =head2 parse_func_env
3728              
3729             Return first match of L<note>, L<reqenv>, and L<osenv>
3730              
3731             Example:
3732              
3733             <!--#if expr="env( $QUERY_STRING ) == /\bl=ja_JP/" -->
3734             Showing Japanese data
3735             <!--#else -->
3736             Defaulting to English
3737             <!--#endif -->
3738              
3739             =head2 parse_func_escape
3740              
3741             Escape special characters in %hex encoding.
3742              
3743             Example:
3744              
3745             <!--#set var="website" value="https://www.example.com/" -->
3746             Please go to <a href="<!--#echo var='website' encoding='escape' -->"><!--#echo var="website" --></a>
3747              
3748             =head2 parse_func_http
3749              
3750             Get HTTP request header; header names may be added to the Vary header.
3751              
3752             Example:
3753              
3754             <!--#if expr="http('X-API-ID') == 1234567" -->
3755             You're good to go.
3756             <!--#endif -->
3757              
3758             However, outside of an Apache environment this will return the value of the environment variable in the following order:
3759              
3760             =over 4
3761              
3762             =item X-API-ID (i.e. the name as-is)
3763              
3764             =item HTTP_X_API_ID (i.e. adding C<HTTP_> and replace C<-> for C<_>)
3765              
3766             =item X_API_ID (i.e. same as above, but without the C<HTTP_> prefix)
3767              
3768             =back
3769              
3770             If none is found, it returns an empty string.
3771              
3772             For an equivalent function for response headers, see L</parse_func_resp>
3773              
3774             =head2 parse_func_ldap
3775              
3776             Escape characters as required by LDAP distinguished name escaping (RFC4514) and LDAP filter escaping (RFC4515).
3777              
3778             See L<Apache documentation|https://httpd.apache.org/docs/trunk/en/expr.html#page-header> for more information
3779              
3780             Example:
3781              
3782             <!--#set var="phrase" value="%{ldap:'Tous les êtres humains naissent libres (et égaux) en dignité et\ en\ droits.\n'}" -->
3783             # Tous les êtres humains naissent libres \28et égaux\29 en dignité et\5c en\5c droits.\5cn
3784              
3785             =head2 parse_func_md5
3786              
3787             Hash the string using MD5, then encode the hash with hexadecimal encoding.
3788              
3789             If the arguments are utf8 data with perl internal flag on, as checked with L<Encode/is_utf8>, this will encode the data into utf8 with L<Encode/encode> before encoding it with md5.
3790              
3791             Example:
3792              
3793             <!--#if expr="md5( $hash_data ) == '2f50e645b6ef04b5cfb76aed6de343eb'" -->
3794             You're good to go.
3795             <!--#endif -->
3796              
3797             =head2 parse_func_note
3798              
3799             Lookup request note
3800              
3801             <!--#set var="CUSTOMER_ID" value="1234567" -->
3802             <!--#if expr="note('CUSTOMER_ID') == 1234567" -->
3803             Showing special message
3804             <!--#endif -->
3805              
3806             This uses L<Apache2::SSI::Notes> to enable notes to be shared on and off Apache2/mod_perl2 environment. Thus, you could set a note from a command-line perl script, and then access it under Apache2/mod_perl2 or just your regular script running under a web server.
3807              
3808             For example:
3809              
3810             In your perl script outside of Apache:
3811              
3812             # Basic parameters to make Apache2::SSI happy
3813             my $ssi = Apache2::SSI->new( document_root => '/home/john/www', document_uri => '/' ) ||
3814             die( Apache2::SSI->error );
3815             $ssi->notes( API_VERSION => 2 );
3816              
3817             Then, in your perl script running under the web server, be it Apache2/mod_perl2 or not:
3818              
3819             my $ssi = Apache2::SSI->new || die( Apache2::SSI->error );
3820             my $api_version = $ssi->notes( 'API_VERSION' );
3821              
3822             To enable shareability of notes on and off Apache, this makes uses of shared memory segments. See L<Apache2::SSI::Notes> for more information on the notes api and L<perlipc> for more information on shared memory segments.
3823              
3824             Just keep in mind that the notes are B<never> removed even when Apache shuts down, so it is your responsibility to remove them if you do not want them anymore. For example:
3825              
3826             use Apache2::SSI::Notes;
3827             my $notes = Apache2::SSI::Notes->new;
3828             $notes->remove;
3829              
3830             be aware that shared notes might note be available for your platform. Check L<Apache2::SSI::Notes> for more information and also L<perlport> on shared memory segments.
3831              
3832             =head2 parse_func_osenv
3833              
3834             Lookup operating system environment variable
3835              
3836             <!--#if expr="env('LANG') =~ /en(_(GB|US))/" -->
3837             Showing English language
3838             <!--#endif -->
3839              
3840             =head2 parse_func_replace
3841              
3842             replace(string, "from", "to") replaces all occurrences of "from" in the string with "to".
3843              
3844             Example:
3845              
3846             <!--#if expr="replace( 'John is in Tokyo', 'John', 'Jack' ) == 'Jack is in Tokyo'" -->
3847             This worked!
3848             <!--#else -->
3849             Nope, it failed.
3850             <!--#endif -->
3851              
3852             =head2 parse_func_req
3853              
3854             See L</parse_func_http>
3855              
3856             =head2 parse_func_reqenv
3857              
3858             Lookup request environment variable (as a shortcut, v can also be used to access variables).
3859              
3860             This is only different from L</parse_func_env> under Apache.
3861              
3862             See L</parse_func_env>
3863              
3864             Example:
3865              
3866             <!--#if expr="reqenv('ProcessId') == '$$'" -->
3867             This worked!
3868             <!--#else -->
3869             Nope, it failed.
3870             <!--#endif -->
3871              
3872             Or using the Apache SSI C<v> shortcut:
3873              
3874             <!--#if expr="v('ProcessId') == '$$'" -->
3875              
3876             =head2 parse_func_req_novary
3877              
3878             Same as L</parse_func_req>, but header names will not be added to the Vary header.
3879              
3880             =head2 parse_func_resp
3881              
3882             Get HTTP response header.
3883              
3884             Example:
3885              
3886             <!--#if expr="resp('X-ProcessId') == '$$'" -->
3887             This worked!
3888             <!--#else -->
3889             Nope, it failed.
3890             <!--#endif -->
3891              
3892             An important note here:
3893              
3894             First, there is obviously no response header available for perl scripts running outside of Apache2/mod_perl2 framework.
3895              
3896             If the script runs under mod_perl, not all response header will be available depending on whether you are using L<Apache2::SSI> in your Apache configuration as an output filter handler (C<PerlOutputFilterHandler>) or a response handler (C<PerlResponseHandler>).
3897              
3898             If it is running as an output filter handler, then some headers, such as C<Content-Type> will not be available, unless they have been set by a script in a previous phase. Only basic headers will be available. For more information, check the Apache/mod_perl2 documentation on each phase.
3899              
3900             =head2 parse_func_sha1
3901              
3902             Hash the string using SHA1, then encode the hash with hexadecimal encoding.
3903              
3904             Example:
3905              
3906             <!--#if expr="sha1('Tous les êtres humains naissent libres et égaux en dignité et en droits.') == '8c244078c64a51e8924ecf646df968094a818d59'" -->
3907             This worked!
3908             <!--#else -->
3909             Nope, it failed.
3910             <!--#endif -->
3911              
3912             =head2 parse_func_tolower
3913              
3914             Convert string to lower case.
3915              
3916             Example:
3917              
3918             <!--#if expr="tolower('Tous les êtres humains naissent libres et égaux en dignité et en droits.') == 'tous les êtres humains naissent libres et égaux en dignité et en droits.'" -->
3919             This worked!
3920             <!--#else -->
3921             Nope, it failed.
3922             <!--#endif -->
3923              
3924             =head2 parse_func_toupper
3925              
3926             Convert string to upper case.
3927              
3928             Example:
3929              
3930             <!--#if expr="toupper('Tous les êtres humains naissent libres et égaux en dignité et en droits.') == 'TOUS LES ÊTRES HUMAINS NAISSENT LIBRES ET ÉGAUX EN DIGNITÉ ET EN DROITS.'" -->
3931             This worked!
3932             <!--#else -->
3933             Nope, it failed.
3934             <!--#endif -->
3935              
3936             =head2 parse_func_unbase64
3937              
3938             Decode base64 encoded string, return truncated string if 0x00 is found.
3939              
3940             Example:
3941              
3942             <!--#if expr="unbase64('VG91cyBsZXMgw6p0cmVzIGh1bWFpbnMgbmFpc3NlbnQgbGlicmVzIGV0IMOpZ2F1eCBlbiBkaWduaXTDqSBldCBlbiBkcm9pdHMu') == 'Tous les êtres humains naissent libres et égaux en dignité et en droits.'" -->
3943             This worked!
3944             <!--#else -->
3945             Nope, it failed.
3946             <!--#endif -->
3947              
3948             =head2 parse_func_unescape
3949              
3950             Unescape %hex encoded string, leaving encoded slashes alone; return empty string if %00 is found.
3951              
3952             Example:
3953              
3954             <!--#if expr="unescape('https%3A%2F%2Fwww.example.com%2F') == 'https://www.example.com/'" -->
3955             This worked!
3956             <!--#else -->
3957             Nope, it failed.
3958             <!--#endif -->
3959              
3960             =head2 parse_if
3961              
3962             Parse the C<if> condition.
3963              
3964             See L</parse_elif> above for example.
3965              
3966             =head2 parse_include
3967              
3968             Provided with an hash reference of parameters and this process the ssi directive C<include>, which is arguably the most used.
3969              
3970             It will try to resolve the file to include by calling L</find_file> with the same arguments this is called with.
3971              
3972             Under Apache, if the previous look up succeeded, it calls L<Apache2::SubRequest/run>
3973              
3974             Outside of Apache, it reads the entire file, utf8 decode it and return it.
3975              
3976             =head2 parse_perl
3977              
3978             Provided with an hash reference of parameters and this parse some perl command and returns the output as a string.
3979              
3980             Example:
3981              
3982             <!--#perl sub="sub{ print 'Hello!' }" -->
3983              
3984             or
3985              
3986             <!--#perl sub="package::subroutine" -->
3987              
3988             =head2 parse_printenv
3989              
3990             This returns a list of environment variables sorted and their values.
3991              
3992             =head2 parse_set
3993              
3994             Provided with an hash reference of parameters and this process the ssi directive C<set>.
3995              
3996             Possible parameters are:
3997              
3998             =over 4
3999              
4000             =item I<decoding>
4001              
4002             The decoding of the variable before it is set. This can be C<none>, C<url>, C<urlencoded>, C<base64> or C<entity>
4003              
4004             =item I<encoding>
4005              
4006             This instruct to encode the variable value before display. It can the same possible value as for decoding.
4007              
4008             =item I<value>
4009              
4010             The string value for the variable to be set.
4011              
4012             =item I<var>
4013              
4014             The variable name
4015              
4016             =back
4017              
4018             Example:
4019              
4020             <!--#set var="debug" value="2" -->
4021             <!--#set decoding="entity" var="HUMAN_RIGHT" value="Tous les &Atilde;&ordf;tres humains naissent libres et &Atilde;&copy;gaux en dignit&Atilde;&copy; et en droits." encoding="urlencoded" -->
4022              
4023             See the L<Apache SSI documentation|https://httpd.apache.org/docs/current/en/mod/mod_include.html> for more information.
4024              
4025             =head2 parse_ssi
4026              
4027             Provided with the html data as a string and this will parse its embedded ssi directives and return its output as a string.
4028              
4029             If it fails, it sets an L</error> and returns an empty string.
4030              
4031             =head2 path_info
4032              
4033             Sets or gets the path info for the current uri.
4034              
4035             Example:
4036              
4037             my $string = $ssi->path_info;
4038             $ssi->path_info( '/my/path/info' );
4039              
4040             The path info value is also set automatically when L</document_uri> is called, such as:
4041              
4042             $ssi->document_uri( '/some/path/to/file.html/my/path/info?q=something&l=ja_JP' );
4043              
4044             This will also set automatically the C<PATH_INFO> environment variable.
4045              
4046             =head2 query_string
4047              
4048             Set or gets the query string for the current uri.
4049              
4050             Example:
4051              
4052             my $string = $ssi->query_string;
4053             $ssi->query_string( 'q=something&l=ja_JP' );
4054              
4055             or, using the L<URI> module:
4056              
4057             $ssi->query_string( $uri->query );
4058              
4059             The query string value is set automatically when you provide an L<document_uri> upon instantiation or after:
4060              
4061             $ssi->document_uri( '/some/path/to/file.html?q=something&l=ja_JP' );
4062              
4063             This will also set automatically the C<QUERY_STRING> environment variable.
4064              
4065             =head2 remote_ip
4066              
4067             Sets or gets the remote ip address of the visitor.
4068              
4069             Under Apache mod_perl, this will call L<Apache2::Connection/remote_ip> for version 2.2 or lower and will call L<Apache2::Connection/useragent_ip> for version above 2.2, and otherwise this will get the value from the environment variable C<REMOTE_ADDR>
4070              
4071             This value can also be overriden by being provided during object instantiation.
4072              
4073             # Pretend the ssi directives are accessed from this ip
4074             $ssi->remote_ip( '192.168.2.20' );
4075              
4076             This is useful when one wants to check how the rendering will be when accessed from certain ip addresses.
4077              
4078             This is used primarily when there is an expression such as
4079              
4080             <!--#if expr="-R '192.168.1.0/24' -->
4081             Visitor is part of my private network
4082             <!--#endif -->
4083              
4084             or
4085              
4086             <!--#if expr="v('REMOTE_ADDR') -R '192.168.1.0/24' -->
4087             <!--#include file="/home/john/special_hidden_login_feature.html" -->
4088             <!--#endif -->
4089              
4090             L<Apache2::Connection> also has a L<Apache2::Connection/remote_addr> method, but this returns a L<APR::SockAddr> object that is used to get the binary version of the ip. However you can also get the string version like this:
4091              
4092             use APR::SockAddr ();
4093             my $ip = $r->connection->remote_addr->ip_get();
4094              
4095             Versions above 2.2 make a distinction between ip from direct connection, or the real ip behind a proxy, i.e. L<Apache2::Connection/useragent_ip>
4096              
4097             =head2 request_uri
4098              
4099             This is an alias for L</document_uri>
4100              
4101             =head2 server_version
4102              
4103             Returns the server version as a L<version> object can caches that value.
4104              
4105             Under mod_perl2, it uses L<Apache2::ServerUtil/get_server_description> and outside of mod_perl, it tries to find C<apxs> using L<File::Which> and in last resort, tries to find the C<apache2> or C<httpd> binary to get its version information.
4106              
4107             =head2 sizefmt
4108              
4109             Sets or gets the formatting for file sizes. Value can be either C<bytes> or C<abbrev>
4110              
4111             =head2 timefmt
4112              
4113             Sets or gets the formatting for date and time values. The format takes the same values as L<POSIX/strftime>
4114              
4115             =head1 Encoding
4116              
4117             At present time, the html data are treated as utf8 data and decoded and encoded back as such.
4118              
4119             If there is a need to broaden support for other charsets, let me know.
4120              
4121             =head1 SSI Directives
4122              
4123             This is taken from Apache documentation and summarised here for convenience and clarity to the perl community.
4124              
4125             =head2 config
4126              
4127             <!--#config errmsg="Error occurred" sizefmt="abbrev" timefmt="%B %Y" -->
4128             <!--#config errmsg="Oopsie" -->
4129             <!--#config sizefmt="bytes" -->
4130             # Thursday 24 December 2020
4131             <!--#config timefmt="%A $d %B %Y" -->
4132              
4133             =head2 echo
4134              
4135             <!--#set var="HTMl_TITLE" value="Un sujet intéressant" -->
4136             <!--#echo var="HTMl_TITLE" encoding="entity" -->
4137              
4138             Encoding can be either C<entity>, C<url> or C<none>
4139              
4140             =head2 exec
4141              
4142             # pwd is "print working directory" in shell
4143             <!--#exec cmd="pwd" -->
4144             <!--#exec cgi="/uri/path/to/prog.cgi" -->
4145              
4146             =head2 include
4147              
4148             # Filesystem file path
4149             <!--#include file="/home/john/var/quote_of_the_day.txt" -->
4150             # Relative to the document root
4151             <!--#include virtual="/footer.html" -->
4152              
4153             =head2 flastmod
4154              
4155             <!--#flastmod file="/home/john/var/quote_of_the_day.txt" -->
4156             <!--#flastmod virtual="/copyright.html" -->
4157              
4158             =head2 fsize
4159              
4160             <!--#fsize file="/download/software-v1.2.tgz" -->
4161             <!--#fsize virtual="/images/logo.jpg" -->
4162              
4163             =head2 printenv
4164              
4165             <!--#printenv -->
4166              
4167             =head2 set
4168              
4169             <!--#set var="debug" value="2" -->
4170              
4171             =head2 if, elif, endif and else
4172              
4173             <!--#if expr="$debug > 1" -->
4174             I will print a lot of debugging
4175             <!--#else -->
4176             Debugging output will be reasonable
4177             <!--#endif -->
4178              
4179             or with new version of Apache SSI:
4180              
4181             No such file or directory.
4182             <!--#if expr="v('HTTP_REFERER') != ''" -->
4183             Please let the admin of the <a href="<!--#echo encoding="url" var="HTTP_REFERER" -->"referring site</a> know about their dead link.
4184             <!--#endif -->
4185              
4186             =head2 functions
4187              
4188             Apache SSI supports the following functions, as of Apache version 2.4.
4189              
4190             See L<Apache documentation|https://httpd.apache.org/docs/current/en/expr.html#page-header> for detailed description of what they do.
4191              
4192             You can also refer to the methods C<parse_func_*> documented above, which implement those Apache functions.
4193              
4194             =over 4
4195              
4196             =item I<base64>
4197              
4198             =item I<env>
4199              
4200             =item I<escape>
4201              
4202             =item I<http>
4203              
4204             =item I<ldap>
4205              
4206             =item I<md5>
4207              
4208             =item I<note>
4209              
4210             =item I<osenv>
4211              
4212             =item I<replace>
4213              
4214             =item I<req>
4215              
4216             =item I<reqenv>
4217              
4218             =item I<req_novary>
4219              
4220             =item I<resp>
4221              
4222             =item I<sha1>
4223              
4224             =item I<tolower>
4225              
4226             =item I<toupper>
4227              
4228             =item I<unbase64>
4229              
4230             =item I<unescape>
4231              
4232             =back
4233              
4234             =head2 variables
4235              
4236             On top of all environment variables available, Apache makes the following ones also accessible:
4237              
4238             =over 4
4239              
4240             =item DATE_GMT
4241              
4242             =item DATE_LOCAL
4243              
4244             =item DOCUMENT_ARGS
4245              
4246             =item DOCUMENT_NAME
4247              
4248             =item DOCUMENT_PATH_INFO
4249              
4250             =item DOCUMENT_URI
4251              
4252             =item LAST_MODIFIED
4253              
4254             =item QUERY_STRING_UNESCAPED
4255              
4256             =item USER_NAME
4257              
4258             =back
4259              
4260             See L<Apache documentation|https://httpd.apache.org/docs/current/en/mod/mod_include.html#page-header> and L<this page too|https://httpd.apache.org/docs/current/en/expr.html#page-header> for more information.
4261              
4262             =head2 expressions
4263              
4264             There is reasonable, but limited support for Apache expressions. For example, the followings are supported
4265              
4266             In the examples below, we use the variable C<QUERY_STRING>, but you can use any other variable of course.
4267              
4268             The regular expression are the ones L<PCRE|http://www.pcre.org/> compliant, so your perl regular expressions should work.
4269              
4270             <!--#if expr="$QUERY_STRING = 'something'" -->
4271             <!--#if expr="v('QUERY_STRING') = 'something'" -->
4272             <!--#if expr="%{QUERY_STRING} = 'something'" -->
4273             <!--#if expr="$QUERY_STRING = /^something/" -->
4274             <!--#if expr="$QUERY_STRING == /^something/" -->
4275             # works also with eq, ne, lt, le, gt and ge
4276             <!--#if expr="9 gt 3" -->
4277             <!--#if expr="9 -gt 3" -->
4278             # Other operators work too, namely == != < <= > >= =~ !~
4279             <!--#if expr="9 > 3" -->
4280             <!--#if expr="9 !> 3" -->
4281             <!--#if expr="9 !gt 3" -->
4282             # Checks the remote ip is part of this subnet
4283             <!--#if expr="-R 192.168.2.0/24" -->
4284             <!--#if expr="192.168.2.10 -R 192.168.2.0/24" -->
4285             <!--#if expr="192.168.2.10 -ipmatch 192.168.2.0/24" -->
4286             # Checks if variable is non-empty
4287             <!--#if expr="-n $some_variable" -->
4288             # Checks if variable is empty
4289             <!--#if expr="-z $some_variable" -->
4290             # Checks if the visitor can access the uri /restricted/uri
4291             <!--#if expr="-A /restricted/uri" -->
4292              
4293             For subnet checks, this uses L<Net::Subnet>
4294              
4295             Expressions that would not work outside of Apache, i.e. it will return an empty string:
4296              
4297             <!--#expr="%{HTTP:X-example-header} in { 'foo', 'bar', 'baz' }" -->
4298              
4299             See L<Apache documentation|http://httpd.apache.org/docs/2.4/en/expr.html> for more information.
4300              
4301             =head1 CREDITS
4302              
4303             Credits to Ken Williams for his implementation of L<Apache::SSI> from which I borrowed some code.
4304              
4305             =head1 AUTHOR
4306              
4307             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
4308              
4309             CPAN ID: jdeguest
4310              
4311             L<https://git.deguest.jp/jack/Apache2-SSI>
4312              
4313             =head1 SEE ALSO
4314              
4315             L<Apache2::SSI::File>, L<Apache2::SSI::Finfo>, L<Apache2::SSI::Notes>, L<Apache2::SSI::URI>, L<Apache2::SSI::SharedMem> and L<Apache2::SSI::SemStat>
4316              
4317             mod_include, mod_perl(3), L<Apache::SSI>,
4318             L<https://httpd.apache.org/docs/current/en/mod/mod_include.html>,
4319             L<https://httpd.apache.org/docs/current/en/howto/ssi.html>,
4320             L<https://httpd.apache.org/docs/current/en/expr.html>
4321             L<https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_>
4322              
4323             =head1 COPYRIGHT & LICENSE
4324              
4325             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
4326              
4327             You can use, copy, modify and redistribute this package and associated
4328             files under the same terms as Perl itself.
4329              
4330             =cut