File Coverage

blib/lib/Apache2/SSI.pm
Criterion Covered Total %
statement 921 2180 42.2
branch 334 1208 27.6
condition 101 410 24.6
subroutine 113 203 55.6
pod 79 86 91.8
total 1548 4087 37.8


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI.pm
3             ## Version v0.2.4
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2020/12/17
7             ## Modified 2021/03/29
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   2351439 use strict;
  14         501  
  14         635  
17 14     14   106 use warnings;
  14         44  
  14         713  
18 14     14   123 use warnings::register;
  14         46  
  14         3225  
19 14     14   544 use parent qw( Module::Generic );
  14         278  
  14         200  
20 14     14   46 our( $MOD_PERL, $MOD_PERL_VERSION, $SERVER_VERSION );
21 14 50 33     85 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   154023820 use Apache2::Expression;
  14         52  
  14         266  
52 14     14   13212 use Apache2::SSI::File;
  14         47  
  14         241  
53 14     14   5930 use Apache2::SSI::Finfo;
  14         27  
  14         484  
54 14     14   7174 use Apache2::SSI::Notes;
  14         54  
  14         298  
55 14     14   16205 use Apache2::SSI::URI;
  14         57  
  14         294  
56 14     14   5960 use Config;
  14         33  
  14         1177  
57 14     14   105 use Cwd ();
  14         25  
  14         281  
58 14     14   69 use DateTime;
  14         22  
  14         340  
59 14     14   87 use DateTime::Format::Strptime;
  14         20  
  14         189  
60             # XXX Remove after debugging
61             # use Devel::Confess;
62 14     14   804 use Digest::MD5 ();
  14         26  
  14         205  
63 14     14   8857 use Digest::SHA ();
  14         31450  
  14         396  
64 14     14   179 use Encode ();
  14         27  
  14         216  
65 14     14   6677 use File::Which ();
  14         15721  
  14         300  
66 14     14   6676 use HTML::Entities ();
  14         71504  
  14         638  
67 14     14   6975 use IO::Select;
  14         19003  
  14         780  
68 14     14   6472 use MIME::Base64 ();
  14         9025  
  14         371  
69 14     14   6087 use Net::Subnet ();
  14         72344  
  14         383  
70 14     14   131 use Nice::Try;
  14         29  
  14         138  
71 14     14   81622171 use Regexp::Common qw( net Apache2 );
  14         41  
  14         193  
72 14     14   3868 use Scalar::Util ();
  14         26  
  14         249  
73 14     14   75 use URI;
  14         25  
  14         392  
74             ## Will use XS version automatically
75 14     14   8465 use URL::Encode ();
  14         19910  
  14         356  
76 14     14   6291 use URI::Escape::XS ();
  14         31596  
  14         535  
77 14     14   5803 use version;
  14         23476  
  14         82  
78 14         35 our $VERSION = 'v0.2.4';
79 14     14   1465 use constant PERLIO_IS_ENABLED => $Config{useperlio};
  14         32  
  14         2143  
80             ## As of Apache 2.4.41 and mod perl 2.0.11 Apache2::SubProcess::spawn_proc_prog() is not working
81 14     14   94 use constant MOD_PERL_SPAWN_PROC_PROG_WORKING => 0;
  14         26  
  14         1811  
82 14         80064 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 386 my $self = shift( @_ );
213 245         314 my $ref = shift( @_ );
214 245         301 my $buf = shift( @_ );
215 245 50       565 return( [] ) if( ref( $ref ) ne 'HASH' );
216 245         321 my $opts = {};
217 245 100       439 if( @_ )
218             {
219 71 0       253 $opts = ref( $_[0] ) eq 'HASH'
    50          
220             ? shift( @_ )
221             : !( @_ % 2 )
222             ? { @_ }
223             : {};
224             }
225 245 50       646 $opts->{skip} = [] if( !exists( $opts->{skip} ) );
226 245 100       553 $opts->{top} = 0 if( !exists( $opts->{top} ) );
227 245 100       479 $opts->{embedded} = 0 if( !exists( $opts->{embedded} ) );
228 245         391 my $type = $ref->{type};
229 245         317 my $stype = '';
230 245 100       549 $stype = $ref->{subtype} if( defined( $ref->{subtype} ) );
231 245         378 my $elems = $ref->{elements};
232 245     0   1624 $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         4175 my $prev_regexp_capture = $self->{_regexp_capture};
235 245         514 my $r = $self->apache_request;
236 245         3765 my $env = $self->env;
237              
238 245         1248 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         968 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     2275 if( $type eq 'comp' )
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    50          
261             {
262 24         45 my $op = '';
263 24 100       78 $op = $ref->{op} if( defined( $ref->{op} ) );
264 24         185 $self->message( 3, "Processing type '$type' with operator '$op' and raw data '$ref->{raw}'." );
265             ## ==, =, !=, <, <=, >, >=, -ipmatch, -strmatch, -strcmatch, -fnmatch
266 24 100       443 if( $stype eq 'binary' )
    100          
    50          
    100          
    50          
267             {
268 4         27 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
269 4         67 my $this2 = $self->ap2perl_expr( $ref->{wordb_def}->[0], [] );
270 4 100       32 push( @$buf, '!' ) if( $ref->{is_negative} );
271             ## "IP address matches address/netmask"
272 4 100 33     64 if( $op eq 'ipmatch' )
    50          
    50          
273             {
274 2         13 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         13 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         18 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
295 2         7 my $func = $ref->{function_def}->[0];
296 2         6 my $func_name = $func->{name};
297 2         16 my $argv = $self->parse_expr_args( $func->{args_def} );
298 2         27 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         37 $self->message( 3, "Got here in regexp with operator '$op'." );
310 6         1200 my $this1 = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
311 6         28 my $this2 = $self->ap2perl_expr( $ref->{regexp_def}->[0], [] );
312 6         37 my $map =
313             {
314             '=' => '=~',
315             '==' => '=~',
316             '!=' => '!~',
317             };
318 6         19 push( @$buf, @$this1 );
319 6 50       32 push( @$buf, exists( $map->{ $ref->{op} } ) ? $map->{ $ref->{op} } : $ref->{op} );
320 6         17 push( @$buf, @$this2 );
321             }
322             elsif( $stype eq 'unary' )
323             {
324 12         87 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
325 12     0   70 $self->message( 3, "\$ref returned contains: ", sub{ $self->dump( $ref ) });
  0         0  
326 12         175 my $word = join( '', @$this );
327             ## check if the uri is accessible to all
328 12 100 66     286 if( $op eq 'A' || $op eq 'U' )
    50 33        
    50 33        
    50 33        
    100 33        
    100 100        
    50          
329             {
330 2         3 my $url = $word;
331             ## Because we cannot do variable length lookbehind
332 2         10 $self->message( 3, "Checking accessibility of uri '$url'." );
333 2         23 my $res;
334 2         8 my $req = $self->lookup_uri( $url );
335 2 50       5 $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         6 my $file = $req->filename;
339 2         10 $self->message( 3, "Checking looked up file name '$file'." );
340 2 100 33     24 if( $req->code != 200 )
    50 33        
341             {
342 1         2 $res = 0;
343             }
344             elsif( -e( "$file" ) && ( ( -f( "$file" ) && -r( "$file" ) ) || ( -d( "$file" ) && -x( "$file" ) ) ) )
345             {
346 1         3 $res = 1;
347             }
348             else
349             {
350 0         0 $res = 0;
351             }
352 2         25 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       48 push( @$buf, ( $op eq 'z' ? '!' : '' ) . "length( ${word} )" );
380             }
381             ## <!--#if expr='-R "134.28.200"' -->
382             elsif( $op eq 'R' )
383             {
384 2         8 my $ip = $self->remote_ip;
385 2         4 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       8 if( $self->_is_ip( $subnet ) )
390             {
391 0         0 $subnet = qq{"$subnet"};
392             }
393              
394 2         1181 $self->message( 3, "Checking ip '$ip' against subnet '$subnet'." );
395 2         39 push( @$buf, qq{\$self->_ipmatch( $subnet, "$ip" )} );
396             }
397             elsif( $op eq 'T' )
398             {
399 3         13 $self->message( 3, "Checking if word '$word' is true." );
400             ## Because we cannot do variable length lookbehind
401 3 50       43 my $val = length( $word )
402             ? $word
403             : '';
404 3 50       18 $val = $self->parse_eval_expr( $val ) if( length( $val ) );
405 3         13 $self->message( 3, "word is now, after being eval'ed: '$val'." );
406 3         39 $val = lc( $val );
407 3         3 my $res;
408 3 100 66     30 if( $val eq '' || $val eq '0' || $val eq 'off' || $val eq 'false' || $val eq 'no' )
      100        
      66        
      66        
409             {
410 2         4 $res = 0;
411             }
412             else
413             {
414 1         2 $res = 1;
415             }
416 3         9 push( @$buf, $res );
417             }
418             }
419             }
420             elsif( $type eq 'cond' )
421             {
422 48 50       293 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       33 push( @$buf, $ref->{booltype} eq 'true' ? 1 : 0 );
431             }
432             elsif( $stype eq 'or' )
433             {
434 1         5 my $this1 = $self->ap2perl_expr( $ref->{or_def_expr1}->[0], [] );
435 1         5 my $this2 = $self->ap2perl_expr( $ref->{or_def_expr2}->[0], [] );
436 1         4 push( @$buf, @$this1, '||', @$this2 );
437             }
438             elsif( $stype eq 'comp' )
439             {
440 30         525 my $this = $self->ap2perl_expr( $ref->{elements}->[0], [] );
441 30         87 push( @$buf, @$this );
442             }
443             elsif( $stype eq 'negative' )
444             {
445 7         61 my $this = $self->ap2perl_expr( $ref->{negative_def}->[0], [] );
446 7         38 push( @$buf, '!(', @$this, ')' );
447             }
448             elsif( $stype eq 'parenthesis' )
449             {
450 1         41 my $this = $self->ap2perl_expr( $ref->{parenthesis_def}->[0], [] );
451 1         4 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         59 my $func = $ref->{name};
462 13 50       47 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         93 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       499 if( $func =~ /^$SUPPORTED_FUNCTIONS$/i )
475             {
476 13         103 $self->message( 3, "Calling function 'parse_func_${func}' with arguments '$argv'." );
477 13         299 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         19 my $op = $ref->{op};
487 3         17 my $op_actual = '';
488 3 50       11 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         12 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
498 3         17 my $this2 = $self->ap2perl_expr( $ref->{wordb_def}->[0], [] );
499 3         15 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         36 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         30 my $this = $ref->{raw};
561             ## my $reType = $self->legacy ? 'Legacy' : $self->trunk ? 'Trunk' : '';
562 10 50       42 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         409 $self->message( 3, qq[Using regex of type '$reType' for embedded variable] );
565 10         202 $this =~ s
566             {
567             $RE{Apache2}{"${reType}Variable"}
568             }
569 3         2923 {
570 3         32 my $var = $+{variable};
571 3         92 $self->message( 3, "Parsing variable $+{variable} embedded into string." );
572 3   50     15 my $res = $self->parse_expr( $var, { embedded => 1 } );
573 3         28 $res //= '';
574             $res;
575 10 100       8201 }gexis;
576             if( $opts->{top} )
577 8         44 {
578             push( @$buf, 'qq{' . $this . '}' );
579             }
580             else
581 2         7 {
582             push( @$buf, $this );
583             }
584             }
585             elsif( $type eq 'stringcomp' )
586 13         40 {
587 13         33 my $op = $ref->{op};
588 13 50       51 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         40 {
596             $op_actual = $map_binary->{ $op };
597 13         94 }
598 13         81 my $this1 = $self->ap2perl_expr( $ref->{worda_def}->[0], [] );
599 13         87 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         85 {
609 36 100       129 my $var_name = $ref->{name};
    100          
    50          
610             if( $stype eq 'function' )
611 1         7 {
612             $self->message( 3, "Got here for function name '$ref->{name}'." );
613 1         13 # push( @$buf, $ref->{name} . '(' . $self->parse_expr_args( $ref->{args_def} ) . ')' );
614 1         4 $ref->{type} = 'function';
615 1         3 my $this = $self->ap2perl_expr( $ref, [] );
616             push( @$buf, @$this );
617             }
618             elsif( $stype eq 'rebackref' )
619 1         5 {
620 1         15 $self->message( 3, "Got here for back reference value '$ref->{value}'." );
621 1         17 my $val = $prev_regexp_capture->[ int( $ref->{value} ) - 1 ];
622 1 50       16 $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         199 {
628 34         508 $self->message( 3, "\${}: Is there environment variable '$var_name'? '", $env->{ $var_name }, "'." );
629 34 100 66     175 my $try = '';
630             if( !length( $try ) && length( $env->{ $var_name } ) )
631 13         25 {
632             $try = $env->{ $var_name };
633 34 50 66     143 }
  21         191  
634             if( !length( $try ) && defined( ${ "main\::${var_name}" } ) )
635 0         0 {
  0         0  
636             $try = ${ "main\::${var_name}" };
637             }
638 34 100       77 ## Last resort
639             if( !length( $try ) )
640 21         147 {
641             $try = $self->parse_echo({ var => $var_name });
642 34 100       105 }
643             if( !length( $try ) )
644 20         46 {
645             $try = '${' . $var_name . '}';
646             }
647             else
648 14 100 100     50 {
649             $try = 'q{' . $try . '}' unless( $self->_is_number( $try ) || $opts->{embedded} );
650 34         3763 }
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         263 {
660 46 100       962 $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         10 {
663             push( @$buf, $ref->{value} );
664             }
665             elsif( $stype eq 'ip' )
666 5         15 {
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         174 {
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       183 {
715             if( length( $ref->{list} ) )
716             {
717 1         2 # my $this2 = $self->ap2perl_expr( $ref->{list_def}->[0], [] );
718             my $tmp = [];
719 1         9 ## We go through each element of the list which can be composed of string, function or other
720 1 50       14 my $all_string = 1;
721             if( ref( $ref->{words_def} ) )
722 1         3 {
  1         10  
723             foreach my $that ( @{$ref->{words_def}} )
724 3 50 33     25 {
      33        
725 3         8 $all_string = 0 unless( $that->{type} eq 'string' || $that->{type} eq 'word' || $that->{type} eq 'variable' );
726 3         7 my $this = $self->ap2perl_expr( $that, [] );
727             push( @$tmp, @$this );
728 1 50       8 }
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         649 {
739 43         138 my $this = $self->ap2perl_expr( $ref->{word_def}->[0], [] );
740             push( @$buf, @$this );
741             }
742 245     0   1358 }
  0         0  
743 245         4519 $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 12468 {
1019 62         175 my $self = shift( @_ );
1020 62         163 my $class = ref( $self );
1021 62 50       206 my $args = {};
1022             if( scalar( @_ ) )
1023 14     14   156 {
  14         29  
  14         47616  
1024 62 50       715 no warnings 'uninitialized';
    50          
1025             $args = Scalar::Util::reftype( $_[0] ) eq 'HASH'
1026             ? shift( @_ )
1027             : !( scalar( @_ ) % 2 )
1028             ? { @_ }
1029             : {};
1030 62   50     310 }
1031 62         921 my $uri = delete( $args->{document_uri} ) // '';
1032 62         180 $self->{html} = '';
1033 62         253 $self->{apache_filter} = '';
1034 62         149 $self->{apache_request} = '';
1035             $self->{document_root} = '';
1036 62         191 ## e.g.: [Value Undefined]
1037 62         183 $self->{echomsg} = '';
1038 62         164 $self->{errmsg} = '[an error occurred while processing this directive]';
1039 62         208 $self->{filename} = '';
1040 62         141 $self->{legacy} = 0;
1041 62         157 $self->{trunk} = 0;
1042 62         138 $self->{remote_ip} = '';
1043 62         128 $self->{sizefmt} = 'abbrev';
1044 62         146 $self->{timefmt} = undef;
1045 62         247 $self->{_init_strict_use_sub} = 1;
1046 62 50       416 $self->{_init_params_order} = [qw( apache_filter apache_request document_root document_uri )];
1047 62         2182 $self->SUPER::init( %$args ) || return;
1048 62         186 $self->{_env} = '';
1049             $self->{_path_info_processed} = 0;
1050             ## Used to hold regular expression matches during eval in _eval_vars()
1051 62         163 ## and make them available for the next evaluation
1052 62         478 $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         194 ## if-block with no else.
1058 62         146 $self->{if_state} = [1];
1059 62         168 $self->{notes} = '';
1060             $self->{suspend} = [0];
1061 62 50       367 ## undef means the current locale's default
1062 62         2260 $self->mod_perl( defined( $MOD_PERL ) ? length( $MOD_PERL ) > 0 : 0 );
1063 62 50 33     1032 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         159 }
1081             my $p = {};
1082 62 50       225 ## $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         453 {
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       207 ## $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         146 {
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         126
1114 62 50       166 $p->{debug} = $self->{debug};
1115 62 50 33     470 $p->{apache_request} = $r if( $r );
    0          
    0          
1116             if( length( "$p->{document_uri}" ) && length( "$p->{document_root}" ) )
1117 62   50     474 {
1118             my $u = Apache2::SSI::URI->new( $p ) ||
1119 62         237 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         135 }
1134 62 50       625 my $notes;
1135 62         181 $notes = Apache2::SSI::Notes->new( debug => $self->{debug} ) if( Apache2::SSI::Notes->supported );
1136 62         456 $self->{notes} = $notes;
1137             return( $self );
1138             }
1139 4     4 1 17  
1140             sub apache_filter { return( shift->_set_get_object_without_init( 'apache_filter', 'Apache2::Filter', @_ ) ); }
1141 952     952 1 2132  
1142             sub apache_request { return( shift->_set_get_object_without_init( 'apache_request', 'Apache2::RequestRec', @_ ) ); }
1143              
1144             sub clone
1145 4     4 1 10 {
1146 4   33     15 my $self = shift( @_ );
1147 4         20 my $class = ref( $self ) || $self;
1148 4         8 my @copy = qw( debug echomsg errmsg remote_ip sizefmt timefmt );
1149 4         25 my $params = {};
1150 4 50       15 @$params{ @copy } = @$self{ @copy };
1151 4 50       81 $params->{apache_filter} = $self->apache_filter if( $self->apache_filter );
1152 4         66 $params->{apache_request} = $self->apache_request if( $self->apache_request );
1153 4         14 $params->{document_uri} = $self->uri->document_uri;
1154 4         13 $params->{document_root} = $self->document_root;
1155 4   50     84 $self->message( 3, "Current document root is '", $self->document_root, "' ($self->{document_root})" );
1156 4         29 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 4 {
1162 1         4 my $self = shift( @_ );
1163 1     1   2 try
1164 1         6 {
1165 1         9 my $v = join( '', @_ );
1166 1 50       34 $self->message( 3, "Decoding: '$v'." );
1167             if( $self->mod_perl )
1168 0         0 {
1169             $v = APR::Base64::decode( $v );
1170             }
1171             else
1172 1         63 {
1173             $v = MIME::Base64::decode( $v );
1174 1 50       9 }
1175 1 50       61 $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       3  
  1         4  
  1         5  
  1         2  
  1         10  
  0         0  
  1         3  
  0         0  
  1         5  
  1         3  
  1         4  
  1         5  
  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         15  
  1         22  
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 4 {
1200 1         1 my $self = shift( @_ );
1201 1     1   2 try
1202 1         9 {
1203             return( URI::Escape::uri_unescape( @_ ) );
1204 1 50       7 }
  0 50       0  
  1 50       4  
  1 0       4  
  1 50       3  
  1         3  
  1         2  
  1         3  
  1         7  
  0         0  
  1         5  
  0         0  
  1         39  
  1         4  
  1         6  
  1         5  
  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         15  
  1         19  
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 10039 {
1237 84         241 my $self = shift( @_ );
1238 84 50       1427 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       263 {
1246             if( @_ )
1247 63         153 {
1248 63         313 $self->{document_root} = shift( @_ );
1249             $self->_set_env( DOCUMENT_ROOT => $self->{document_root} );
1250 84   33     336 }
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 212  
1258             sub echomsg { return( shift->_set_get_scalar( 'echomsg', @_ ) ); }
1259              
1260             sub encode_base64
1261 2     2 1 450 {
1262 2         3 my $self = shift( @_ );
1263 2     2   5 try
1264 2         7 {
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       94 $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         111 {
1274             return( MIME::Base64::encode( $v, '' ) );
1275             }
1276 2 50       23 }
  0 50       0  
  2 50       7  
  2 0       3  
  2 50       7  
  2         4  
  2         3  
  2         5  
  2         10  
  0         0  
  2         7  
  0         0  
  2         14  
  2         7  
  2         9  
  2         9  
  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         40  
  2         47  
1280             }
1281             }
1282              
1283             sub encode_entities
1284 1     1 1 480 {
1285 1         2 my $self = shift( @_ );
1286 1     1   1 try
1287 1         18 {
1288             return( HTML::Entities::encode_entities( join( '', @_ ) ) );
1289 1 50       13 }
  0 50       0  
  1 50       4  
  1 0       1  
  1 50       3  
  1         1  
  1         2  
  1         2  
  1         14  
  0         0  
  1         3  
  0         0  
  1         86  
  1         3  
  1         2  
  1         7  
  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         31  
  1         16  
1293             }
1294             }
1295              
1296             sub encode_md5
1297 1     1 0 4 {
1298 1         2 my $self = shift( @_ );
1299 1     1   3 try
1300 1         4 {
1301             my $v = join( '', @_ );
1302 1 50       21 ## $self->message( 3, "Does data has utf8 flag on? ", ( Encode::is_utf8( $v ) ? 'yes' : 'no' ) );
1303 1         44 $v = Encode::encode( 'utf8', $v, Encode::FB_CROAK ) if( Encode::is_utf8( $v ) );
1304             return( Digest::MD5::md5_hex( $v ) );
1305 1 50       7 }
  0 50       0  
  1 50       4  
  1 0       3  
  1 50       2  
  1         2  
  1         2  
  1         4  
  1         7  
  0         0  
  1         4  
  0         0  
  1         5  
  1         3  
  1         3  
  1         5  
  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         14  
  1         19  
1309             }
1310             }
1311              
1312             sub encode_uri
1313 2     2 1 454 {
1314 2         6 my $self = shift( @_ );
1315 2     2   5 try
1316 2         16 {
1317             return( URI::Escape::uri_escape_utf8( join( '', @_ ) ) );
1318 2 50       18 }
  0 50       0  
  2 50       7  
  2 0       5  
  2 50       5  
  2         4  
  2         5  
  2         3  
  2         12  
  0         0  
  2         8  
  0         0  
  2         159  
  2         6  
  2         5  
  2         8  
  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         54  
  2         29  
1322             }
1323             }
1324              
1325             sub encode_url
1326 1     1 1 1302 {
1327 1         2 my $self = shift( @_ );
1328 1     1   1 try
1329 1 50       4 {
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         40 {
1337             return( URL::Encode::url_encode_utf8( join( '', @_ ) ) );
1338             }
1339 1 50       14 }
  0 50       0  
  1 50       5  
  1 0       1  
  1 50       3  
  1         2  
  1         1  
  1         2  
  1         4  
  0         0  
  1         3  
  0         0  
  1         11  
  1         3  
  1         3  
  1         4  
  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         65  
  1         32  
1343             }
1344             }
1345              
1346             sub env
1347 389     389 1 512 {
1348             my $self = shift( @_ );
1349 389 50       758 ## The user wants the entire hash reference
1350             unless( @_ )
1351 389         558 {
1352 389 50       5100 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       852 {
1360             unless( ref( $self->{_env} ) )
1361 46         1863 {
1362             $self->{_env} = {%ENV};
1363 389         834 }
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   155 {
  14         30  
  14         21674  
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 106  
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 22 {
1418 7         25 my( $self, $args ) = @_;
1419 7         166 my $r = $self->apache_request;
1420 7 100       42 my $req = '';
    50          
    0          
1421             if( exists( $args->{file} ) )
1422 3         22 {
1423 3         18 $self->_interp_vars( $args->{file} );
1424             $req = $self->lookup_file( $args->{file} );
1425             }
1426             elsif( exists( $args->{virtual} ) )
1427 4         20 {
1428 4         29 $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         27 }
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 2242  
1463             sub legacy { return( shift->_set_get_boolean( 'legacy', @_ ) ); }
1464              
1465             sub lookup_file
1466 3     3 1 22 {
1467 3   50     11 my $self = shift( @_ );
1468 3         21 my $file = shift( @_ ) || return( $self->error( "No file provided to look up." ) );
1469 3         52 $self->message( 3, "Looking up file \"$file\"." );
1470 3   50     68 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       19 ) || 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         8 }
1482             return( $f );
1483             }
1484              
1485             sub lookup_uri
1486 6     6 1 19 {
1487 6         19 my $self = shift( @_ );
1488 6         18 my $uri = shift( @_ );
1489 6   50     110 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       22 ) || return( $self->error( "Unable to instantiate an Apache2::SSI::URI object: ", Apache2::SSI::URI->error ) );
1497             if( $u->code == 404 )
1498             {
1499 1         5 ## 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         1742 }
1502 6         90 $self->message( 3, "Resolved uri \"$uri\" to filename \"", $u->filename, "\"." );
1503             return( $u );
1504             }
1505 68     68 1 729  
1506             sub mod_perl { return( shift->_set_get_boolean( 'mod_perl', @_ ) ); }
1507              
1508             sub new_uri
1509 3     3 0 9 {
1510 3         6 my $self = shift( @_ );
1511 3 50 33     30 my $uri = shift( @_ );
1512 3         11 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       130 };
1520 3   50     66 $p->{apache_request} = $self->apache_request if( $self->apache_request );
1521             my $o = Apache2::SSI::URI->new( $p ) ||
1522 3         21 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 2268 {
1586 54 50       203 my $self = shift( @_ );
1587 54         395 my $html = @_ ? shift( @_ ) : $self->{html};
1588 54 50       1065 $self->message( 3, "Parsing html:\n'$html'" );
1589 54         1708 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         167 # return( Apache2::Const::DECLINED ) if( scalar( @parts ) <= 1 );
1593 54         86 my $out = '';
1594 54         143 my $ssi;
1595             while( @parts )
1596 212         1218 {
1597 212 100       511 $out .= ( '', shift( @parts ) )[ 1 - $self->{suspend}->[0] ];
1598 158         236 last unless( @parts );
1599             $ssi = shift( @parts );
1600 158 50       811 ## There's some weird 'uninitialized' warning on the next line, but I can't find it.
1601             if( $ssi =~ m/^<!--#(.*)-->$/s )
1602 158         432 {
1603 158 100       972 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         330 }
1611 54         1242 $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 17 {
1626 6     0   39 my( $self, $args ) = @_;
  0         0  
1627 6 50       94 $self->message( 3, "Setting config values for arguments: ", sub{ $self->dump( $args ) });
1628 6 100       17 $self->{echomsg} = $args->{echomsg} if( exists( $args->{echomsg} ) );
1629 6 100       18 $self->{errmsg} = $args->{errmsg} if( exists( $args->{errmsg} ) );
1630 6 100       16 $self->{sizefmt} = lc( $args->{sizefmt} ) if( exists( $args->{sizefmt} ) );
1631 6         24 $self->{timefmt} = $args->{timefmt} if( exists( $args->{timefmt} ) );
1632             return( '' );
1633             }
1634              
1635             sub parse_echo
1636 38     38 1 105 {
1637 38         78 my( $self, $args ) = @_;
1638             my $var = $args->{var};
1639 38         85 ## $self->_interp_vars( $var );
1640 38         590 my $r = $self->apache_request;
1641 38         64 my $env = $self->env;
1642 14     14   121 my $value;
  14         27  
  14         18176  
1643 38         159 no strict( 'refs' );
1644             $self->message( 3, "Checking value for variable '$var'." );
1645 38 50 33     1200
    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         27 {
1652             $value = $self->$method( $r );
1653             }
1654             elsif( defined( $var ) && exists( $env->{ $var } ) )
1655 5         32 {
1656 5         68 $self->message( 3, "Returning variable \"$var\" with value \"$env->{$var}\"." );
1657             $value = $env->{ $var };
1658             }
1659             else
1660 25         101 {
1661             $value = $self->echomsg;
1662 38         503 }
1663             $self->message( 3, "Value found is '$value'" );
1664 38 50 33     915
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     108
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         108 }
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 3  
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         4 my $self = shift( @_ );
1732 2         9 my $r = shift( @_ );
1733 2 50       38 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     14 my $env = $self->env;
1745 2         10 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         4 my $r = shift( @_ );
1756 1 50       17 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         2 {
1764 1         6 my $env = $self->env;
1765 1   33     18 $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 9 {
1772 4         22 my $self = shift( @_ );
1773 4         74 my $uri = $self->uri;
1774             return( $uri->query_string );
1775             }
1776              
1777             sub parse_elif
1778 4     4 1 10 {
1779             my( $self, $args ) = @_;
1780 4 50       5 ## Make sure we're in an 'if' chain
  4         13  
1781 4 50       12 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
1782 4         11 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 86 {
1788             my $self = shift( @_ );
1789 38 50       53 ## Make sure we're in an 'if' chain
  38         142  
1790 38 100       111 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
1791 37         97 return( '' ) if( $self->{suspend}->[1] );
1792             return( $self->_handle_ifs(1) );
1793             }
1794              
1795             sub parse_endif
1796 39     39 1 83 {
1797             my $self = shift( @_ );
1798 39 50       56 ## Make sure we're in an 'if' chain
  39         132  
1799 39         76 return( $self->error( "Malformed if..endif SSI structure" ) ) unless( @{$self->{if_state}} > 1 );
  39         81  
1800 39         48 shift( @{$self->{if_state}} );
  39         74  
1801 39         109 shift( @{$self->{suspend}} );
1802             return( '' );
1803             }
1804              
1805             sub parse_eval_expr
1806 53     53 1 83 {
1807 53         86 my $self = shift( @_ );
1808 53 50       177 my $text = shift( @_ );
1809 53 50       133 $self->message( 3, "No expression to eval was provided." ) if( !length( $text ) );
1810             return( '' ) if( !length( $text ) );
1811 53         179
1812 53         224 my $perl = $self->parse_expr( $text );
1813 53         825 $self->message( 3, "Position after parsing is: ", pos( $text ) );
1814 53         678 $self->message( 3, "Evaluating text '$perl'" );
1815             my $result;
1816 53         76 do
1817             {
1818 53     0   379 ## Silence some warnings about bare words such as strings being eval'ed
1819             local $SIG{__WARN__} = sub{};
1820 14     14   122 # package main;
  14         31  
  14         48076  
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         143 ## @rv will contain the regexp matches or the result of the eval
1824 53         106 local @matches = ();
1825 53         150 local @rv = ();
1826             my $eval = <<EOT;
1827             \@rv = ($perl);
1828 53 100       230 EOT
1829             $eval .= <<EOT if( $perl =~ /[\=\!]\~/ );
1830             \@matches = \@-;
1831 53         201 EOT
1832 53         5699 $self->message( 3, "Evaluating text:\n$eval" );
1833 53         202 eval( $eval );
1834 53     0   360 $result = $rv[0];
  0         0  
1835             $self->message( 3, "\@- is: ", sub{ $self->dump( \@matches ) } );
1836 53 100       1123 ## Make any regular expression capture available for the next evaluation
1837 53     0   223 $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     974 };
1840 53 50       425 $result //= '';
1841 53 50       123 $self->message( 3, "Eval error found: $@" ) if( $@ );
1842 53 100       194 return( $self->error( "Eval error for expression '$text' translated to '$perl': $@" ) ) if( $@ );
1843 53         2627 $self->message( 3, "Got an error: ", $self->error->message ) if( $self->error );
1844 53         871 $self->message( 3, "Returning result: '$result'" );
1845             return( $result );
1846             }
1847              
1848             sub parse_exec
1849 4     4 1 12 {
1850             my( $self, $args ) = @_;
1851 4         24 ## XXX did we check enough?
1852 4         91 my $r = $self->apache_request;
1853 4         108 my $uri = $self->uri;
1854 4 50       16 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       14 # XXX Need to improve on this
1866             if( exists( $args->{cmd} ) )
1867 1         22 {
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     27 ## <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         14 {
1884 1         282 my $env = $self->env;
1885             local %ENV = %$env;
1886 1         7314 ## 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       11
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     19 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         20 };
1903             $self->message( 3, "CGI path to execute is: '$cgi'." );
1904 3   33     80 my $doc_root = $self->document_root || do
1905             {
1906             $self->error( "No document root set." );
1907             return( $self->errmsg );
1908             };
1909 3 100       12
1910             if( $cgi->code != 200 )
1911 2         22 {
1912 2         46 $self->message( 3, "CGI file code is not 200 (", $cgi->code, ")." );
1913 2         3920 $self->error( "Error including cgi: subrequest returned status '" . $cgi->code . "', not 200" );
1914             return( $self->errmsg );
1915             }
1916 1         5
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 0     0 {
1928             unless( $^O =~ /^(dos|mswin32|NetWare|symbian|win32)$/i && -T( "$finfo" ) )
1929 0         0 {
1930             $self->message( 3, "CGI file is not executable." );
1931 0         0 ## return( $self->error( "Error including cgi \"$args->{cgi}\". File is not executable by Apache user." ) );
1932 0         0 $self->error( "Error including cgi \"$args->{cgi}\". File is not executable by Apache user." );
1933 0         0 $cgi->code( 401 );
1934             return( $self->errmsg );
1935             }
1936 1         47 }
1937             $self->message( 3, "Ok, file \"$cgi\" exists (code = '", $cgi->code, "')" );
1938            
1939 1 50       17
1940             if( $r )
1941 0         0 {
1942             my $rr = $cgi->apache_request;
1943             # my $u = URI->new( $rr->uri . ( length( $cgi->path_info ) ? $cgi->path_info : length( $uri->path_info ) ? $uri->path_info : '' ) );
1944 0         0 # $u->query( $uri->query_string ) if( !length( $cgi->query_string ) && length( $uri->query_string ) );
1945 0 0 0     0 $self->message( 3, "Setting path info to '", $uri->path_info, "' and query string to '", $uri->query_string, "'." );
1946 0 0 0     0 $cgi->path_info( $uri->path_info ) if( !length( $cgi->path_info ) && length( $uri->path_info ) );
1947 0         0 $cgi->query_string( $uri->query_string ) if( !length( $cgi->query_string ) && length( $uri->query_string ) );
1948 0         0 $self->message( 3, "Running cgi \"$cgi\" (", $cgi->filename, ")." );
1949 0         0 $rr->content_type( 'application/x-httpd-cgi' );
1950 0         0 $cgi->env( GATEWAY_INTERFACE => 'CGI/1.1' );
1951 0         0 $cgi->env( DOCUMENT_URI => "$cgi" );
1952 0     0   0 my( $content, $headers ) = $rr->fetch_uri( "$cgi" );
  0         0  
1953 0         0 $self->message( 3, "Content found is:\n'$content'\nand headers are: ", sub{ $self->dump( $headers ) });
1954             return( $content );
1955             }
1956             else
1957 1         2 {
1958             my $buf;
1959 1         2 {
  1         3  
1960 1         11 local $ENV{DOCUMENT_URI} = $cgi->document_uri;
1961 1 50       4 local $ENV{PATH_INFO} = $uri->path_info;
1962 1         4 local $ENV{PATH_INFO} = $cgi->path_info if( length( $cgi->path_info ) );
1963 1 50       4 local $ENV{QUERY_STRING} = $uri->query_string;
1964 1         5 local $ENV{QUERY_STRING} = $cgi->query_string if( length( $cgi->query_string ) );
1965 1         5 local $ENV{REMOTE_ADDR} = $self->remote_ip;
1966 1         4 local $ENV{REQUEST_METHOD} = 'GET';
1967 1         9 local $ENV{REQUEST_URI} = $cgi->document_uri;
1968 1         5 my $file = $cgi->filename;
1969 1 50       14 my $mime = $finfo->mime_type;
1970 1 50 33     63 $self->message( 3, "Mime type for file '$file' is '$mime', OS is '$^O' an is it a plain text file ? ", ( -T( "$cgi" ) ? 'Yes' : 'No' ), "." );
1971             if( $^O =~ /^(dos|mswin32|NetWare|symbian|win32)$/i && $mime eq 'text/x-perl' )
1972 0         0 {
1973 0         0 $self->message( 3, "Calling $^X $file" );
1974             $buf = `$^X $file`;
1975             }
1976             else
1977 1         10406 {
1978             $buf = qx( "$file" );
1979             }
1980             };
1981 1 50       55 ## Failed to execute
1982             if( $? == -1 )
1983 0         0 {
1984 0         0 $self->message( 3, "CGI exit value was not 0 but '$?'." );
1985 0         0 $cgi->code( 500 );
1986             return( $self->errmsg );
1987 1         15 }
1988 1         28 my( $key, $val );
1989 1         77 my $headers = {};
1990             while( $buf =~ s/([^\012]*)\012// )
1991 3         32 {
1992             my $line = $1;
1993 3         21 ## if we need to restore as content when illegal headers are found.
1994             my $save = "$line\012";
1995 3         19
1996 3 100       31 $line =~ s/\015$//;
1997             last unless( length( $line ) );
1998 2 50 0     49
    0          
1999             if( $line =~ /^([a-zA-Z0-9_\-.]+)\s*:\s*(.*)/ )
2000             {
2001 2 100       40 ## $response->push_header( $key, $val ) if( $key );
2002 2         33 $headers->{ $key } = $val if( $key );
2003             ( $key, $val ) = ( $1, $2 );
2004             }
2005             elsif( $line =~ /^\s+(.*)/ && $key )
2006 0         0 {
2007             $val .= " $1";
2008             }
2009             else
2010             {
2011 0         0 ## $response->push_header( "Client-Bad-Header-Line" => $line );
2012             $headers->{ 'Client-Bad-Header-Line' } = $line;
2013             }
2014             }
2015 1 50       19 ## $response->push_header( $key, $val ) if( $key );
2016 1     0   97 $headers->{ $key } = $val if( $key );
  0         0  
2017 1         600 $self->message( 3, "Headers found are: ", sub{ $self->dump( $headers ) } );
2018             return( $buf );
2019             }
2020             }
2021              
2022             sub parse_expr
2023 71     71 1 6233 {
2024 71         125 my $self = shift( @_ );
2025 71         112 my $text = shift( @_ );
2026 71 100       170 my $opts = {};
2027             if( @_ )
2028 3 0       14 {
    50          
2029             $opts = ref( $_[0] ) eq 'HASH'
2030             ? shift( @_ )
2031             : !( @_ % 2 )
2032             ? { @_ }
2033             : {};
2034 71 100       544 }
2035 71         166 $opts->{embedded} = 0 if( !exists( $opts->{embedded} ) );
2036 71         1229 my $r = $self->apache_request;
2037 71         343 my $env = $self->env;
2038 71         1040 $self->message( 3, "Processing text '$text'." );
2039 71 100       177 my $prev_regexp_capture = $self->{_regexp_capture};
2040             unless( $self->{_exp} )
2041 38         115 {
2042             $self->{_exp} = Apache2::Expression->new( legacy => 1, debug => $self->debug );
2043             }
2044 71         126
2045 71         114 my $exp = $self->{_exp};
2046 71         101 my $hash = {};
2047 71     71   91 try
2048 71         1368 {
  0         0  
2049 71         538 local $SIG{ALRM} = sub{ die( "Timeout!\n" ) };
2050 71         372 alarm( 90 );
2051 71         1775 $hash = $exp->parse( $text );
2052             alarm( 0 );
2053 71 50       365 }
  71 50       337  
  71 50       150  
  71 0       91  
  71 50       126  
  71         83  
  71         91  
  71         115  
  71         200  
  0         0  
  71         114  
  0         0  
  71         211  
  71         151  
  71         172  
  71         192  
  0         0  
  0         0  
  0         0  
  0         0  
2054 0     0   0 catch( $e )
2055 0         0 {
2056 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         1280  
  0         0  
2057 71         180 }
2058 71         164 my $res = [];
2059 71         117 $opts->{top} = 1;
  71         196  
2060             foreach my $this ( @{$hash->{elements}} )
2061 71         316 {
2062 71         256 my $res2 = $self->ap2perl_expr( $this, [], $opts );
2063             push( @$res, @$res2 );
2064 71         361 }
2065 71         1909 $self->message( 3, "Returning '", join( ' ', @$res ), "'." );
2066             return( join( ' ', @$res ) );
2067             }
2068              
2069             sub parse_flastmod
2070 1     1 1 2 {
2071 1         4 my( $self, $args ) = @_;
2072 1 50       2 my $p = $self->find_file( $args );
2073             unless( $p->code == 200 )
2074 0         0 {
2075             return( $self->errmsg );
2076 1   33     8 }
2077             return( $self->_lastmod( $p, $args->{timefmt} || $self->{timefmt} ) );
2078             }
2079              
2080             sub parse_fsize
2081 2     2 1 14 {
2082             my( $self, $args ) = @_;
2083 2         17 ## $self->message( 3, "Got here with args: ", sub{ $self->dump( $args ) });
2084 2 50       8 my $f = $self->find_file( $args );
2085             unless( $f->code == 200 )
2086 0         0 {
2087 0         0 $self->message( "Requested file \"", $f->filename, "\" not found." );
2088             return( $self->errmsg );
2089 2         7 }
2090 2         9 my $finfo = $f->finfo;
2091 2         20 my $size = $finfo->size;
2092 2         52 $self->message( 3, "File \"$f\" size is: '$size'" );
2093 2 100       99616 my $n = Module::Generic::Number->new( $size );
    50          
2094             if( $self->{sizefmt} eq 'bytes' )
2095             {
2096             ## Not everyone is using a comma as thousand separator
2097             ## 1 while( $size =~ s/^(\d+)(\d{3})/$1,$2/g );
2098 1         16 ## return( $size );
2099 1         339 my $str = $n->format( 0 )->scalar;
2100 1         75 $self->message( 3, "Returning \"$str\" (", overload::StrVal( $str ), ")." );
2101 1 50       6 undef( $n );
2102 1         52 return( '' ) if( !defined( $str ) );
2103             return( $str );
2104             }
2105             elsif( $self->{sizefmt} eq 'abbrev' )
2106 1 50       62 {
2107 0         0 return( $size ) if( $size < 1024 );
2108 0         0 my $n = Module::Generic::Number->new( $size );
2109 0         0 my $str = $n->format_bytes->scalar;
2110 0 0       0 undef( $n );
2111 0         0 return( '' ) if( !defined( $str ) );
2112             return( $str );
2113             }
2114             else
2115 0         0 {
2116 0         0 $self->error( "Unrecognized size format '$self->{sizefmt}'" );
2117             return( $self->errmsg );
2118             }
2119             }
2120              
2121             ## Functions
2122             ## See https://httpd.apache.org/docs/trunk/en/expr.html#page-header
2123 1     1 1 11 # base64|env|escape|http|ldap|md5|note|osenv|replace|req|reqenv|req_novary|resp|sha1|tolower|toupper|unbase64|unescape
2124             sub parse_func_base64 { return( shift->encode_base64( join( '', @_ ) ) ); }
2125              
2126             ## Return first match of note, reqenv, osenv
2127             sub parse_func_env
2128 1     1 1 4 {
2129 1         3 my $self = shift( @_ );
2130 1         6 my $var = shift( @_ );
2131 1         37 my $r = $self->apache_request;
2132 1         16 my $env = $self->env;
2133 1 50       21 $self->message( 3, "Getting environment value for variable '${var}'." );
2134             if( $r )
2135 0         0 {
2136 0     0   0 try
2137 0   0     0 {
2138             return( $r->subprocess_env( $var ) || $env->{ $var } || $self->notes( $var ) );
2139 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2140 0     0   0 catch( $e )
2141 0         0 {
2142 0         0 $self->message( 3, "An error occurred trying to get the environment value for variable \"${var}\": $e" );
2143 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  
2144             }
2145             }
2146             else
2147 1   33     18 {
2148             return( $env->{ $var } || $self->notes( $var ) );
2149             }
2150             }
2151 1     1 1 13  
2152             sub parse_func_escape { return( shift->encode_uri( join( '', @_ ) ) ); }
2153              
2154             sub parse_func_http
2155 0     0 1 0 {
2156 0         0 my $self = shift( @_ );
2157 0         0 my $header_name = shift( @_ );
2158 0 0       0 my $r = $self->apache_request;
2159             if( $r )
2160 0         0 {
2161 0         0 my $headers = $r->headers_in;
2162             return( $headers->{ $header_name } );
2163             }
2164             ## No http header outside of Apache
2165             else
2166 0         0 {
2167 0 0       0 my $env = $self->env;
2168 0         0 return( $env->{ $header_name } ) if( length( $env->{ $header_name } ) );
2169 0 0       0 my $name = $header_name =~ tr/-/_/;
2170 0 0       0 return( $env->{"HTTP_\U${name}\E"} ) if( length( $env->{"HTTP_\U${name}\E"} ) );
2171 0         0 return( $env->{ uc( $name ) } ) if( length( $env->{ uc( $name ) } ) );
2172             return( '' );
2173             }
2174             }
2175              
2176             ## Apache documentation: "Escape characters as required by LDAP distinguished name escaping (RFC4514) and LDAP filter escaping (RFC4515)"
2177             ## Taken from Net::LDAP::Util
2178             sub parse_func_ldap
2179 1     1 1 4 {
2180 1         5 my $self = shift( @_ );
2181 1         10 my $val = join( '', @_ );
  5         25  
2182 1         13 $val =~ s/([\x00-\x1F\*\(\)\\])/'\\' . unpack( 'H2', $1 )/oge;
2183             return( $val );
2184             }
2185 1     1 1 8  
2186             sub parse_func_md5 { return( shift->encode_md5( @_ ) ); }
2187              
2188             ## Notes are stored in the ENV global hash so they can be shared across processes
2189             sub parse_func_note
2190 0     0 1 0 {
2191 0         0 my $self = shift( @_ );
2192 0         0 my $var = shift( @_ );
2193             return( $self->notes( $var ) );
2194             }
2195              
2196             ## Essentially same as parse_func_note
2197             sub parse_func_osenv
2198 0     0 1 0 {
2199 0         0 my $self = shift( @_ );
2200 0         0 my $var = shift( @_ );
2201             return( $ENV{ $var } );
2202             }
2203              
2204             sub parse_func_replace
2205 1     1 1 5 {
2206 1         4 my $self = shift( @_ );
2207 1         24 my( $str, $what, $with ) = @_;
2208 1         13 $str =~ s/$what/$with/g;
2209             return( $str );
2210             }
2211 0     0 1 0  
2212             sub parse_func_req { return( shift->parse_func_http( @_ ) ); }
2213              
2214             sub parse_func_reqenv
2215 0     0 1 0 {
2216 0         0 my $self = shift( @_ );
2217 0         0 my $var = shift( @_ );
2218 0 0       0 my $r = $self->apache_request;
2219             if( $r )
2220 0         0 {
2221             return( $r->subprocess_env( $var ) );
2222             }
2223             else
2224 0         0 {
2225 0         0 my $env = $self->env;
2226             return( $env->{ $var } );
2227             }
2228             }
2229 0     0 1 0  
2230             sub parse_func_req_novary { return( shift->parse_func_http( @_ ) ); }
2231              
2232             sub parse_func_resp
2233 0     0 1 0 {
2234 0         0 my $self = shift( @_ );
2235 0         0 my $header_name = shift( @_ );
2236 0 0       0 my $r = $self->apache_request;
2237             if( $r )
2238 0         0 {
2239 0     0   0 my $headers = $r->headers_out;
  0         0  
2240 0         0 $self->message( 3, "Checking http header '$header_name' => '", $headers->{ $header_name }, "'. Existing headers are: ", sub{ $self->dump( {%$headers} ) } );
2241             return( $headers->{ $header_name } );
2242             }
2243             ## No http header outside of Apache
2244             else
2245 0         0 {
2246             return( '' );
2247             }
2248             }
2249              
2250             sub parse_func_sha1
2251 1     1 1 4 {
2252 1         5 my $self = shift( @_ );
2253 1         22 my $val = join( '', @_ );
2254             return( Digest::SHA::sha1_hex( $val ) );
2255             }
2256              
2257             sub parse_func_tolower
2258 2     2 1 7 {
2259 2         28 my $self = shift( @_ );
2260             return( lc( join( '', @_ ) ) );
2261             }
2262              
2263             sub parse_func_toupper
2264 1     1 1 4 {
2265 1         22 my $self = shift( @_ );
2266             return( uc( join( '', @_ ) ) );
2267             }
2268 1     1 1 11  
2269             sub parse_func_unbase64 { return( shift->decode_base64( join( '', @_ ) ) ); }
2270 1     1 1 11  
2271             sub parse_func_unescape { return( shift->decode_uri( join( '', @_ ) ) ); }
2272              
2273             sub parse_if
2274 39     39 1 101 {
2275 39         59 my( $self, $args ) = @_;
  39         114  
2276 39         56 unshift( @{$self->{if_state}}, 0 );
  39         103  
2277 39 100       109 unshift( @{$self->{suspend}}, $self->{suspend}->[0] );
2278 38         139 return( '' ) if( $self->{suspend}->[0] );
2279             return( $self->_handle_ifs( $self->parse_eval_expr( $args->{expr} ) ) );
2280             }
2281              
2282             sub parse_include
2283 4     4 1 13 {
2284 4 50 66     27 my( $self, $args ) = @_;
2285             unless( exists( $args->{file} ) or exists( $args->{virtual} ) )
2286 0         0 {
2287             return( $self->error( "No 'file' or 'virtual' attribute found in SSI 'include' tag" ) );
2288 4         18 }
2289 4 50       13 my $f = $self->find_file( $args );
2290             unless( $f->code == 200 )
2291 0         0 {
2292 0         0 $self->message( "File to include \"", $f->filename, "\" could not be found." );
2293             return( $self->errmsg );
2294 4         14 }
2295 4 50       89 my $filename = $f->filename;
2296             if( !-e( "$filename" ) )
2297 0         0 {
2298 0         0 $self->message( 3, "File to include \"$filename\" does not exists." );
2299             return( $self->errmsg );
2300             }
2301            
2302             # XXX This needs to be improved, as we should not assume the file encoding is utf8
2303             ## It could be binary or some other text encoding like iso-2022-jp
2304             ## 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
2305 4         29 ## But this complicates things quite a bit, so for now, it is just utf8 simply
2306 4 50       17 my $html = $f->slurp_utf8;
2307             if( !defined( $html ) )
2308 0         0 {
2309 0         0 $self->error( "Unable to get html data of included file \"", $f->filename, "\": ", $f->error );
2310             return( $self->errmsg );
2311             }
2312 4   33     18 my $clone = $self->clone || do
2313             {
2314             warn( $self->error );
2315             return( $self->errmsg );
2316             };
2317             ## share our environment variables with our clone so we pass it to included files.
2318 4         16 ## If we are running under mod_perl, we'll use subprocess_env
2319 4         11 my $env = $self->env;
2320 4         24 $clone->{_env} = $env;
2321             return( $clone->parse( $html ) );
2322             }
2323              
2324             # XXX Legacy
2325             # http://perl.apache.org/docs/1.0/guide/snippets.html#Passing_Arguments_to_a_SSI_script
2326             sub parse_perl
2327 0     0 1 0 {
2328 0         0 my( $self, $args, $margs ) = @_;
2329             my $r = $self->apache_request;
2330 0         0  
2331             my( $pass_r, @arg1, @arg2, $sub ) = (1);
2332 0         0 {
  0         0  
2333 0         0 my @a;
2334             while( @a = splice( @$margs, 0, 2 ) )
2335 0         0 {
2336 0 0       0 $a[1] =~ s/\\(.)/$1/gs;
    0          
    0          
    0          
    0          
2337             if( lc( $a[0] ) eq 'sub' )
2338 0         0 {
2339             $sub = $a[1];
2340             }
2341             elsif( lc( $a[0] ) eq 'arg' )
2342 0         0 {
2343             push( @arg1, $a[1] );
2344             }
2345             elsif( lc( $a[0] ) eq 'args' )
2346 0         0 {
2347             push( @arg1, split( /,/, $a[1] ) );
2348             }
2349             elsif( lc( $a[0] ) eq 'pass_request' )
2350 0 0       0 {
2351             $pass_r = 0 if( lc( $a[1] ) eq 'no' );
2352             }
2353             elsif( $a[0] =~ s/^-// )
2354 0         0 {
2355             push( @arg2, @a );
2356             }
2357             ## Any unknown get passed as key-value pairs
2358             else
2359 0         0 {
2360             push( @arg2, @a );
2361             }
2362             }
2363             }
2364 0         0  
2365 0         0 $self->message( "sub is $sub, args are @arg1 & @arg2" );
2366             my $subref;
2367 0 0       0 ## for <!--#perl sub="sub {print ++$Access::Cnt }" -->
2368             if( $sub =~ /^[[:blank:]\h]*sub[[:blank:]\h]/ )
2369 0         0 {
2370 0 0       0 $subref = eval( $sub );
2371             if( $@ )
2372 0         0 {
2373             $self->error( "Perl eval of '$sub' failed: $@" )
2374             }
2375 0 0       0 ## return( $self->error( "sub=\"sub ...\" didn't return a reference" ) ) unless( ref( $subref ) );
2376             unless( ref( $subref ) )
2377 0         0 {
2378 0         0 $self->error( "sub=\"sub ...\" didn't return a reference" );
2379             return( $self->errmsg );
2380             }
2381             }
2382             ## for <!--#perl sub="package::subr" -->
2383             else
2384 14     14   149 {
  14         45  
  14         67871  
2385 0         0 no strict( 'refs' );
2386 0         0 $subref = ( defined( &{$sub} )
2387 0         0 ? \&{$sub}
2388 0         0 : defined( &{"${sub}::handler"} )
2389 0 0       0 ? \&{"${sub}::handler"}
  0 0       0  
2390             : \&{"main::$sub"});
2391             }
2392 0 0       0
2393             if( $r )
2394 0 0 0     0 {
2395 0 0       0 $pass_r = 0 if( $r and lc( $r->dir_config( 'SSIPerlPass_Request' ) ) eq 'no' );
2396             unshift( @arg1, $r ) if( $pass_r );
2397 0         0 }
2398 0         0 $self->message( 3, "sub is $subref, args are @arg1 & @arg2" );
2399             return( scalar( $subref->( @arg1, @arg2 ) ) );
2400             }
2401              
2402             sub parse_printenv
2403 0     0 1 0 {
2404 0         0 my $self = shift( @_ );
2405 0         0 my $env = $self->env;
  0         0  
2406             return( join( '', map( {"$_: $env->{$_}<br />\n"} sort( keys( %$env ) ) ) ) );
2407             }
2408              
2409             sub parse_set
2410 8     8 1 30 {
2411 8         21 my( $self, $args ) = @_;
2412 8         124 my $r = $self->apache_request;
2413 8         53 my $env = $self->env;
2414             $self->message( 3, "Setting variable \"$args->{var}\" to value \"$args->{value}\"." );
2415            
2416             ## $self->_interp_vars( $args->{value} );
2417             ## Do we need to decode and encode it?
2418 8 50 33     117 ## Possible values are: none, url, urlencoded, base64 or entity
2419             if( $args->{decoding} && lc( $args->{decoding} ) ne 'none' )
2420 0         0 {
2421 0         0 $args->{decoding} = lc( $args->{decoding} );
2422 0     0   0 try
2423 0 0       0 {
    0          
    0          
    0          
2424             if( $args->{decoding} eq 'url' )
2425 0         0 {
2426             $args->{value} = $self->decode_uri( $args->{value} );
2427             }
2428             elsif( $args->{decoding} eq 'urlencoded' )
2429 0         0 {
2430             $args->{value} = $self->decode_url( $args->{value} );
2431             }
2432             elsif( $args->{decoding} eq 'base64' )
2433 0         0 {
2434             $args->{value} = $self->decode_base64( $args->{value} );
2435             }
2436             elsif( $args->{decoding} eq 'entity' )
2437 0         0 {
2438             $args->{value} = $self->decode_entities( $args->{value} );
2439             }
2440 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2441 0     0   0 catch( $e )
2442 0         0 {
2443 0         0 $self->error( "Decoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
2444 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  
2445             }
2446             }
2447 8         32
2448             $args->{value} = $self->parse_eval_expr( $args->{value} );
2449 8 50 33     36
2450             if( $args->{encoding} && lc( $args->{encoding} ) ne 'none' )
2451 0         0 {
2452 0         0 $args->{encoding} = lc( $args->{encoding} );
2453 0     0   0 try
2454 0 0       0 {
    0          
    0          
    0          
2455             if( $args->{encoding} eq 'url' )
2456 0         0 {
2457             $args->{value} = $self->encode_uri( $args->{value} );
2458             }
2459             elsif( $args->{encoding} eq 'urlencoded' )
2460 0         0 {
2461             $args->{value} = $self->encode_url( $args->{value} );
2462             }
2463             elsif( $args->{encoding} eq 'base64' )
2464 0         0 {
2465             $args->{value} = $self->encode_base64( $args->{value} );
2466             }
2467             elsif( $args->{encoding} eq 'entity' )
2468 0         0 {
2469             $args->{value} = $self->encode_entities( $args->{value} );
2470             }
2471 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2472 0     0   0 catch( $e )
2473 0         0 {
2474 0         0 $self->error( "Enecoding of value with method \"$args->{decoding}\" for variable \"$args->{var}\" failed: $e" );
2475 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  
2476             }
2477             }
2478 8 50       23
2479             if( $r )
2480 0         0 {
2481 0         0 $r->subprocess_env( $args->{var}, $args->{value} );
2482             $env->{ $args->{var} } = $args->{value};
2483             }
2484             else
2485 8         32 {
2486             $env->{ $args->{var} } = $args->{value};
2487 8         50 }
2488             return( '' );
2489             }
2490              
2491             sub parse_ssi
2492 158     158 1 499 {
2493             my( $self, $html ) = @_;
2494            
2495 158         255 ## For error reporting
2496 158 50       866 my $orig = $html;
2497             if( $html =~ s/^(\w+)[[:blank:]\h]*// )
2498 158         358 {
2499 158 100 100     705 my $tag = $1;
2500 157         499 return if( $self->{suspend}->[0] and !( $tag =~ /^(if|elif|else|endif)/ ) );
2501 157   50     766 my $method = lc( "parse_${tag}" );
2502             my $code = $self->can( $method ) ||
2503             return( $self->error( "ssi function $tag is unsupported. No method $method found in package \"", ref( $self ), "\"." ) );
2504              
2505 157 50       466 ## Special case for comment directive because there is no key-value pair, but just text
2506 157         742 return( $self->$method( $html ) ) if( lc( $tag ) eq 'comment' );
2507 157         2473 $self->message( 3, "Parsing directive parameters for tag '$tag' and text '$html'" );
2508 157         500 my $args = {};
2509 157 100       573 pos( $html ) = 0;
2510             if( $html =~ /^expr[[:blank:]\h]*\=/ )
2511 43 50       1419 {
2512             if( $html =~ /^$EXPR_RE$/ )
2513 43         721 {
2514 43         1000 $self->message( 3, "Found expression name '$+{attr_name}' and value '$+{attr_val}'." );
2515             $args->{ $+{attr_name} } = $+{attr_val};
2516             }
2517             else
2518 0         0 {
2519             warn( "Expression '$orig' is malformed\n" );
2520             }
2521             }
2522             else
2523 114         3065 {
2524             while( $html =~ /\G($ATTRIBUTES_RE)/gmcs )
2525 45         790 {
2526             $args->{ $+{attr_name} } = $+{attr_val};
2527             }
2528 157     0   1136 }
  0         0  
2529             $self->message( 3, "Calling method \"$method\" with args: ", sub{ $self->dump( $args ) } );
2530 157         2769 # return( $self->$method( {@$args}, $args ) );
2531             return( $self->$method( $args ) );
2532 0         0 }
2533             return( '' );
2534             }
2535 0     0 1 0  
2536             sub path_info { return( shift->uri->path_info( @_ ) ); }
2537 0     0 1 0  
2538             sub query_string { return( shift->uri->query_string( @_ ) ); }
2539              
2540             ## http://httpd.apache.org/docs/2.4/developer/new_api_2_4.html
2541             ## https://github.com/eprints/eprints/issues/214
2542             sub remote_ip
2543 12     12 1 209 {
2544 12         32 my $self = shift( @_ );
2545 12         197 my $r = $self->apache_request;
2546 12 100       44 my $new = '';
2547 12         25 $new = shift( @_ ) if( @_ );
2548 12 50       34 my $ip;
2549             if( $r )
2550             {
2551 0         0 ## In Apache v2.4 or higher, client_ip is used instead of remote_ip
2552 0   0     0 my $c = $r->connection;
2553 0         0 my $coderef = $c->can( 'client_ip' ) // $c->can( 'remote_ip' );
2554 0     0   0 try
2555 0 0       0 {
2556 0         0 $coderef->( $c, $new ) if( $new );
2557             $ip = $coderef->( $c );
2558 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2559 0     0   0 catch( $e )
2560 0 0       0 {
2561 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  
2562 0 0       0 }
2563             $ip = $self->parse_echo({ var => 'REMOTE_ADDR' }) if( !CORE::length( $ip ) );
2564             }
2565             else
2566 12 100       39 {
2567 12         39 $self->{remote_ip} = $new if( $new );
2568 12 100       65 $ip = $self->{remote_ip};
2569             $ip = $self->parse_echo({ var => 'REMOTE_ADDR' }) if( !CORE::length( $ip ) );
2570 12 100       47 }
2571 5         19 return( $ip ) if( CORE::length( $ip ) );
2572             return( '' );
2573             }
2574              
2575 0     0 1 0 ## Same as document_uri
2576             sub request_uri { return( shift->uri->document_uri( @_ ) ); }
2577              
2578             sub server_version
2579 0     0 1 0 {
2580 0 0 0     0 my $self = shift( @_ );
2581 0 0       0 $self->{server_version} = $SERVER_VERSION if( !CORE::length( $self->{server_version} ) && CORE::length( $SERVER_VERSION ) );
2582 0 0       0 $self->{server_version} = shift( @_ ) if( @_ );
2583 0         0 return( $self->{server_version} ) if( $self->{server_version} );
2584 0 0       0 my $vers = '';
2585             if( $self->mod_perl )
2586 0         0 {
2587 0     0   0 try
2588 0         0 {
2589 0         0 my $desc = Apache2::ServerUtil::get_server_description();
2590 0 0       0 $self->message( 3, "Apache description is: '$desc'" );
2591             if( $desc =~ /\bApache\/([\d\.]+)/ )
2592 0         0 {
2593             $vers = $1;
2594             }
2595 0 0       0 }
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
2596 0     0   0 catch( $e )
2597 0         0 {
2598 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  
2599 0         0 }
2600             $self->message( 3, "Found Apache version '$vers' from its description" );
2601             }
2602            
2603 0 0 0     0 ## XXX to test our alternative approach
2604             if( !$vers && ( my $apxs = File::Which::which( 'apxs' ) ) )
2605 0         0 {
2606 0         0 $vers = qx( $apxs -q -v HTTPD_VERSION );
2607 0 0       0 chomp( $vers );
2608             $vers = '' unless( $vers =~ /^[\d\.]+$/ );
2609             }
2610 0 0       0 ## Try apache2
2611             if( !$vers )
2612 0         0 {
2613             foreach my $bin ( qw( apache2 httpd ) )
2614 0 0       0 {
2615             if( ( my $apache2 = File::Which::which( $bin ) ) )
2616 0         0 {
2617 0 0       0 my $v_str = qx( $apache2 -v );
2618             if( ( split( /\r?\n/, $v_str ) )[0] =~ /\bApache\/([\d\.]+)/ )
2619 0         0 {
2620 0         0 $vers = $1;
2621 0         0 chomp( $vers );
2622             last;
2623             }
2624             }
2625             }
2626 0         0 }
2627 0 0       0 $self->message( 3, "Returning version '$vers'." );
2628             if( $vers )
2629 0         0 {
2630 0         0 $self->{server_version} = $SERVER_VERSION = version->parse( $vers );
2631             return( $self->{server_version} );
2632 0         0 }
2633             return( '' );
2634             }
2635 4     4 1 103  
2636             sub sizefmt { return( shift->_set_get_scalar( 'sizefmt', @_ ) ); }
2637 4     4 1 98  
2638             sub timefmt { return( shift->_set_get_scalar( 'timefmt', @_ ) ); }
2639 65     65 1 2187  
2640             sub trunk { return( shift->_set_get_boolean( 'trunk', @_ ) ); }
2641 28     28 0 118  
2642             sub uri { return( shift->_set_get_object( 'uri', 'Apache2::SSI::URI', @_ ) ); }
2643              
2644             sub parse_expr_args
2645 15     15 0 58 {
2646 15         47 my $self = shift( @_ );
2647 15 50       117 my $args = shift( @_ );
2648 15         207 return( $self->error( "I was expecting an array reference, but instead got '$args'." ) ) if( !$self->_is_array( $args ) );
2649 15         37 my $buff = [];
2650 15         42 my $prev_regexp_capture = $self->{_regexp_capture};
2651 15         251 my $r = $self->apache_request;
2652 15         73 my $env = $self->env;
2653             foreach my $this ( @$args )
2654 21   50     178 {
      100        
2655 21         374 $self->message( 3, "Processing argument of type '", ( $this->{type} // '' ), "' and sub type '", ( $this->{subtype} // '' ), "'." );
2656 21 50       121 my $res = $self->ap2perl_expr( $this, [] );
2657             push( @$buff, @$res ) if( $res );
2658 15         102 }
2659             return( join( ', ', @$buff ) );
2660             }
2661              
2662             sub _format_time
2663 3     3   9 {
2664 3         8 my( $self, $time, $format, $tzone ) = @_;
2665 3   66     10 my $env = $self->env;
2666 3         85 $format ||= $self->{timefmt};
2667             $self->message( 3, "Time provided is ", scalar( localtime( $time ) ) );
2668 3         243 ## Quotes are important as they are used to stringify overloaded $time
2669 3   50     152 my $params = { epoch => "$time" };
2670 3 50       6 $params->{time_zone} = ( $tzone || 'local' );
2671 3         4 $params->{locale} = $env->{lang} if( length( $env->{lang} ) );
2672 3     3   2 try
2673 3         16 {
2674 3 50       2718 my $dt = DateTime->from_epoch( %$params );
2675             if( length( $format ) )
2676             {
2677             my $fmt = DateTime::Format::Strptime->new(
2678 3   50     15 pattern => $format,
2679             time_zone => ( $params->{time_zone} || 'local' ),
2680             locale => $dt->locale->code,
2681 3         11144 );
2682 3         137 $dt->set_formatter( $fmt );
2683             return( $dt );
2684             }
2685             else
2686 0         0 {
2687             return( $dt->format_cldr( $dt->locale->date_format_full ) );
2688             }
2689 3 50       14 }
  0 50       0  
  3 50       6  
  3 0       3  
  3 50       5  
  3         4  
  3         3  
  3         4  
  3         7  
  0         0  
  3         5  
  0         0  
  3         10  
  3         6  
  3         4  
  3         8  
  0         0  
  0         0  
  0         0  
  0         0  
2690 0     0   0 catch( $e )
2691 0         0 {
2692 0         0 $self->message( 3, "An error occurred getting a DateTime object for time \"$time\" with format \"$format\": $e" );
2693 0         0 $self->error( "An error occurred getting a DateTime object for time \"$time\" with format \"$format\": $e" );
2694 0 0 33     0 return( $self->errmsg );
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  3         39  
  3         42  
2695             }
2696             }
2697              
2698             sub _handle_ifs
2699 79     79   117 {
2700 79         117 my $self = shift( @_ );
2701             my $cond = shift( @_ );
2702 79 100       221
2703             if( $self->{if_state}->[0] )
2704 32         62 {
2705             $self->{suspend}->[0] = 1;
2706             }
2707             else
2708 47         154 {
2709             $self->{suspend}->[0] = !( $self->{if_state}->[0] = !!$cond );
2710 79         432 }
2711             return( '' );
2712             }
2713              
2714             sub _has_utf8
2715 2     2   6 {
2716 2         71 my $self = shift( @_ );
2717             return( $_[0] =~ /$IS_UTF8/ );
2718             }
2719              
2720             sub _interp_vars
2721             {
2722 7     7   17 ## Find all $var and ${var} expressions in the string and fill them in.
2723             my $self = shift( @_ );
2724 7         15 ## Because ssi_echo may change $1, $2, ...
2725 7         35 my( $a, $b, $c );
  0         0  
2726 0 0       0 $_[0] =~ s{ (^|[^\\]) (\\\\)* \$(\{)?(\w+)(\})? }
2727             { ($a,$b,$c) = ($1,$2,$4);
2728             $a . ( length( $b ) ? substr( $b, length( $b ) / 2 ) : '' ) . $self->parse_echo({ var => $c }) }exg;
2729             }
2730              
2731 3     3   9 sub _ipmatch
2732 3   50     11 {
2733 3   33     10 my $self = shift( @_ );
2734 3         6 my $subnet = shift( @_ ) || return( $self->error( "No subnet provided" ) );
2735 3     3   3 my $ip = shift( @_ ) || $self->remote_ip;
2736 3         23 try
2737 3         18 {
2738 3         77053 local $SIG{__WARN__} = sub{};
2739 3 100       56136 my $net = Net::Subnet::subnet_matcher( $subnet );
2740             my $res = $net->( $ip );
2741 3 100       16 return( $res ? 1 : 0 );
  0 50       0  
  3 50       11  
  3 0       5  
  3 50       7  
  3         5  
  3         3  
  3         6  
  3         10  
  2         8  
  1         3  
  0         0  
  3         20  
  3         12  
  3         12  
  3         15  
  0         0  
  0         0  
  0         0  
  0         0  
2742 0     0   0 }
2743 0         0 catch( $e )
2744 0         0 {
2745 0 0 33     0 $self->error( "Error while calling Net::Subnet: $e" );
  0 0 33     0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
  3         55  
  3         71  
2746             return( 0 );
2747             }
2748             }
2749              
2750 2     2   4 sub _is_ip
2751 2         3 {
2752 2 50       7 my $self = shift( @_ );
2753             my $ip = shift( @_ );
2754 2 50       10 return( 0 ) if( !length( $ip ) );
2755             ## We need to return either 1 or 0. By default, perl return undef for false
2756             return( $ip =~ /^(?:$RE{net}{IPv4}|$RE{net}{IPv6})$/ ? 1 : 0 );
2757             }
2758              
2759 15     15   26 sub _is_number
2760 15         27 {
2761 15 50       41 my $self = shift( @_ );
2762 15 100       79 my $word = shift( @_ );
2763             return( 0 ) if( !length( $word ) );
2764             return( $word =~ /^(?:$RE{num}{int}|$RE{num}{real})$/ ? 1 : 0 );
2765             }
2766              
2767 0     0   0 sub _is_perl_script
2768 0         0 {
2769 0 0       0 my $self = shift( @_ );
2770 0 0       0 my $file = shift( @_ );
2771             return( $self->error( "No file was provided to check if it looks like a perl script." ) ) if( !length( "$file" ) );
2772 0   0     0 if( -T( "$finfo" ) )
2773 0         0 {
2774 0         0 my $io = IO::File->new( "<$file" ) || return( $self->error( "Unable to open file \"$file\" in read mode: $!" ) );
2775 0         0 my $shebang = $io->getline;
2776             chomp( $shebang );
2777 0 0       0 $io->close;
2778             ## We explicitly return 1 or 0, because otherwise upon failure perl would return undef which we reserve for errors
2779 0         0 return( $shebang =~ /^\#\!(.*?)\bperl\b/i ? 1 : 0 );
2780             }
2781             return( 0 );
2782             }
2783              
2784 1     1   3 sub _lastmod
2785 1         3 {
2786 1         13 my( $self, $file, $format ) = @_;
2787             $self->message( 3, "Formatting time for file \"$file\" with format '$format'." );
2788             return( $self->_format_time( ( stat( "$file" ) )[9], $format ) );
2789             }
2790              
2791             ## This is different from the env() method. This one is obviously private
2792             ## whereas the env() one has triggers that could otherwise create an infinite loop.
2793 63     63   117 sub _set_env
2794 63         142 {
2795 63 50       197 my $self = shift( @_ );
2796 63 50       257 my $name = shift( @_ );
2797 63         128 return( $self->error( "No environment variable name provided." ) ) if( !length( $name ) );
2798 63         166 $self->{_env} = {} if( !ref( $self->{_env} ) );
2799 63         104 my $env = $self->{_env};
2800             $env->{ $name } = shift( @_ );
2801             return( $self );
2802             }
2803              
2804 0     0     sub _set_var
2805 0           {
2806 0 0         my $self = shift( @_ );
2807             my $r = shift( @_ );
2808 0           if( $r )
2809             {
2810             $r->subprocess_env( $_[0], $_[1] );
2811             }
2812 0           else
2813 0           {
2814             my $env = $self->env;
2815 0           $env->{ $_[0] } = $_[1];
2816             }
2817             return( $_[1] );
2818             }
2819              
2820             sub _time_args
2821 0     0     {
2822 0 0 0       ## This routine must respect the caller's wantarray() context.
2823             my( $self, $time, $zone ) = @_;
2824             return( ( $zone && $zone =~ /GMT/ ) ? gmtime( $time ) : localtime( $time ) );
2825             }
2826              
2827             ## Credits: Torsten Förtsch
2828             {
2829             package
2830             Apache2::SSI::Filter;
2831              
2832             if( exists( $ENV{MOD_PERL} ) &&
2833             $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ )
2834             {
2835             require Apache2::Filter;
2836             require Apache2::RequestUtil;
2837             require APR::Brigade;
2838             require APR::Bucket;
2839             require parent;
2840             parent->import( qw( Apache2::Filter ) );
2841             require Apache2::Const;
2842             Apache2::Const->import( -compile => qw( OK DECLINED HTTP_OK ) );
2843             eval( "sub fetch_content_filter : FilterRequestHandler { return( &apache_filter_handler ); }" );
2844             }
2845              
2846 0     0     sub read_bb
2847 0           {
2848 0           my( $bb, $buffer ) = @_;
2849             my $r = Apache2::RequestUtil->request;
2850 0           my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
2851              
2852             my $eos = 0;
2853             ## Cycling through APR::Bucket
2854             # while( my $b = $bb->first )
2855             # {
2856             # $eos++ if( $b->is_eos );
2857             # $r->log->debug( __PACKAGE__ . ": ", $b->length, " bytes of data received." );
2858             # ## $b->read( my $bdata );
2859             # my $len = $b->read( my $bdata );
2860             # $r->log->debug( __PACKAGE__ . ": data read is '$bdata' ($len byts read)" );
2861             # push( @$buffer, $bdata ) if( $buffer and length( $bdata ) );
2862 0 0         # $b->delete;
2863 0           # }
2864             $r->log->debug( __PACKAGE__, ": cycling through all the Brigade buckets." ) if( $debug > 0 );
2865 0 0         for( my $b = $bb->first; $b; $b = $bb->next( $b ) )
2866 0           {
2867 0 0         $r->log->debug( __PACKAGE__ . ": ", $b->length, " bytes of data received." ) if( $debug > 0 );
2868 0 0 0       my $len = $b->read( my $bdata );
2869 0           $r->log->debug( __PACKAGE__ . ": data read is '$bdata' ($len byts read)" ) if( $debug > 0 );
2870 0 0         push( @$buffer, $bdata ) if( $buffer and length( $bdata ) );
2871             $b->delete;
2872 0           $eos++, last if( $b->is_eos );
2873             }
2874             return( $eos );
2875             }
2876            
2877             ## We cannot declare it now. Instead we eval it so that it works under Apache and gets discarded outside
2878             ## sub fetch_content_filter : FilterRequestHandler
2879 0     0     sub apache_filter_handler
2880 0           {
2881 0 0         my( $f, $bb ) = @_;
2882             my $r = $f->r;
2883 0 0 0       unless( $f->ctx )
2884             {
2885             unless( $r->status == Apache2::Const::HTTP_OK or
2886 0           $r->pnotes->{force_fetch_content} )
2887 0           {
2888             $f->remove;
2889 0           return( Apache2::Const::DECLINED );
2890             }
2891             $f->ctx(1);
2892 0           }
2893            
2894 0           my $debug = int( $r->dir_config( 'Apache2_SSI_DEBUG' ) );
2895 0 0          
2896 0 0         my $out = $f->r->pnotes->{out};
    0          
2897             $r->log->debug( __PACKAGE__ . ": reading data using '$out'." ) if( $debug > 0 );
2898 0           if( ref( $out ) eq 'ARRAY' )
2899 0 0         {
2900             read_bb( $bb, $out );
2901             $r->log->debug( __PACKAGE__ . ": data read is: ", join( '', @$out ) ) if( $debug > 0 );
2902             }
2903 0           elsif( ref( $out ) eq 'CODE' )
2904 0           {
2905             read_bb( $bb, my $buf = [] );
2906             $out->( $f->r, @$buf );
2907             }
2908 0 0         else
2909 0           {
2910 0           $r->log->debug( __PACKAGE__ . ": request is declined because \$out is neither an array or code." ) if( $debug > 0 );
2911             $f->remove;
2912 0           return( Apache2::Const::DECLINED );
2913             }
2914             return( Apache2::Const::OK );
2915             }
2916             }
2917              
2918             {
2919             package
2920             Apache2::RequestRec;
2921              
2922             if( exists( $ENV{MOD_PERL} ) &&
2923             $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ )
2924             {
2925             require Apache2::RequestRec;
2926             require Apache2::SubRequest;
2927             require APR::Table;
2928             require APR::Finfo;
2929             require APR::Const;
2930             APR::Const->import( -compile => qw( FILETYPE_REG ) );
2931             require Apache2::Const;
2932             Apache2::Const->import( -compile => qw( HTTP_OK OK HTTP_NOT_FOUND ) );
2933             require Apache2::Filter;
2934             require Apache2::FilterRec;
2935             require Apache2::Module;
2936             require ModPerl::Util;
2937             }
2938              
2939 0     0     sub headers_sent
2940             {
2941             my( $I ) = @_;
2942             # Check if any output has already been sent. If so the HTTP_HEADER
2943             # filter is missing in the output chain. If it is still present we
2944 0           # can send a normal error message or modify headers, see ap_die()
2945             # in httpd-2.2.x/modules/http/http_request.c.
2946 0 0         for( my $n = $I->output_filters; $n; $n = $n->next )
2947             {
2948             return if( $n->frec->name eq 'http_header' );
2949 0           }
2950             # http_header filter missing -- that means headers are sent
2951             return( 1 );
2952             }
2953              
2954 0     0     sub fetch_uri
2955 0 0 0       {
2956             my( $I, $url, $headers, $outfn ) = @_;
2957 0           if( @_ == 3 and ref( $headers ) eq 'CODE' )
2958 0           {
2959             $outfn = $headers;
2960             undef( $headers );
2961 0           }
2962 0            
2963 0           my $output = [];
2964 0 0         my $proxy = $url =~ m!^\w+?://!;
2965             my $subr;
2966 0 0         if( $proxy )
2967 0           {
2968             return unless( Apache2::Module::loaded( 'mod_proxy.c' ) );
2969             $subr = $I->lookup_uri( '/' );
2970             }
2971 0           else
2972             {
2973 0 0 0       $subr = $I->lookup_uri( $url );
      0        
2974             }
2975             if( $subr->status == Apache2::Const::HTTP_OK and
2976             ( length( $subr->handler ) ||
2977 0   0       $subr->finfo->filetype == APR::Const::FILETYPE_REG ) )
  0            
2978 0           {
2979 0 0         @{$subr->pnotes}{qw( out force_fetch_content )} = ( $outfn || $output, 1 );
2980             $subr->add_output_filter( \&Apache2::SSI::Filter::apache_filter_handler );
2981 0           if( $proxy )
2982 0           {
2983 0           $subr->proxyreq(2);
2984             $subr->filename( "proxy:" . $url );
2985 0           $subr->handler( 'proxy_server' );
2986 0 0         }
2987             $subr->headers_in->clear;
2988 0           if( $headers )
2989             {
2990 0           for( my $i = 0; $i < @$headers; $i += 2 )
2991             {
2992             $subr->headers_in->add( @$headers[ $i, $i + 1 ] );
2993             }
2994 0 0         }
2995             $subr->headers_in->add( 'User-Agent' => "Apache2::SSI/$VERSION" )
2996 0 0 0       unless( exists( $subr->headers_in->{'User-Agent'} ) );
2997 0           $_ = $I->headers_in->{Host} and $subr->headers_in->add( 'Host' => $_ )
2998 0 0         unless( exists( $subr->headers_in->{'Host'} ) );
2999             $subr->run;
3000 0           if( wantarray )
3001 0           {
3002 0           my( %hout );
3003             $hout{STATUS} = $subr->status;
3004             $hout{STATUSLINE} = $subr->status_line;
3005 0     0     $subr->headers_out->do(sub
3006 0           {
3007 0           $hout{ lc( $_[0] ) } = $_[1];
3008 0           1;
3009             });
3010             return( ( join( '', @$output ), \%hout ) );
3011             }
3012 0           else
3013             {
3014             return( join( '', @$output ) );
3015 0 0         }
3016             }
3017 0           if( wantarray )
3018 0           {
3019             my( %hout );
3020 0 0         $hout{STATUS} = $subr->status;
3021             $hout{STATUS} = Apache2::Const::HTTP_NOT_FOUND
3022             if( $hout{STATUS} == Apache2::Const::HTTP_OK );
3023 0     0     $subr->headers_out->do(sub
3024 0           {
3025 0           $hout{ lc( $_[0] ) } = $_[1];
3026 0           1;
3027             });
3028             return( ( undef, \%hout ) );
3029             }
3030 0           else
3031             {
3032 0           return;
3033             }
3034             return;
3035             }
3036             }
3037              
3038             1;
3039              
3040             __END__
3041              
3042             =encoding utf-8
3043              
3044             =head1 NAME
3045              
3046             Apache2::SSI - Apache2 Server Side Include
3047              
3048             =head1 SYNOPSIS
3049              
3050             Outside of Apache:
3051              
3052             use Apache2::SSI;
3053             my $ssi = Apache2::SSI->new(
3054             ## If running outside of Apache
3055             document_root => '/path/to/base/directory'
3056             ## Default error message to display when ssi failed to parse
3057             ## Default to [an error occurred while processing this directive]
3058             errmsg => '[Oops]'
3059             );
3060             my $fh = IO::File->new( "</some/file.html" ) || die( "$!\n" );
3061             $fh->binmode( ':utf8' );
3062             my $size = -s( $fh );
3063             my $html;
3064             $fh->read( $html, $size );
3065             $fh->close;
3066             if( !defined( my $result = $ssi->parse( $html ) ) )
3067             {
3068             $ssi->throw;
3069             };
3070             print( $result );
3071              
3072             Inside Apache, in the VirtualHost configuration, for example:
3073              
3074             PerlModule Apache2::SSI
3075             PerlOptions +GlobalRequest
3076             PerlSetupEnv On
3077             <Directory "/home/joe/www">
3078             Options All +Includes +ExecCGI -Indexes -MultiViews
3079             AllowOverride All
3080             SetHandler modperl
3081             # You can choose to set this as a response handler or a output filter, whichever works.
3082             # PerlResponseHandler Apache2::SSI
3083             PerlOutputFilterHandler Apache2::SSI
3084             # If you do not set this to On, path info will not work, example:
3085             # /path/to/file.html/path/info
3086             # See: <https://httpd.apache.org/docs/current/en/mod/core.html#acceptpathinfo>
3087             AcceptPathInfo On
3088             # To enable no-caching (see no_cache() in Apache2::RequestUtil:
3089             PerlSetVar Apache2_SSI_NO_CACHE On
3090             # This is required for exec cgi to work:
3091             # <https://httpd.apache.org/docs/current/en/mod/mod_include.html#element.exec>
3092             <Files ~ "\.pl$">
3093             SetHandler perl-script
3094             AcceptPathInfo On
3095             PerlResponseHandler ModPerl::PerlRun
3096             ## Even better for stable cgi scripts:
3097             ## PerlResponseHandler ModPerl::Registry
3098             ## Change this in mod_perl1 PerlSendHeader On to the following:
3099             ## <https://perl.apache.org/docs/2.0/user/porting/compat.html#C_PerlSendHeader_>
3100             PerlOptions +ParseHeaders
3101             </Files>
3102             <Files ~ "\.cgi$">
3103             SetHandler cgi-script
3104             AcceptPathInfo On
3105             </Files>
3106             # To enable debugging output in the Apache error log
3107             # PerlSetVar Apache2_SSI_DEBUG 3
3108             # To set the default echo message
3109             # PerlSetVar Apache2_SSI_Echomsg
3110             # To Set the default error message
3111             # PerlSetVar Apache2_SSI_Errmsg "Oops, something went wrong"
3112             # To Set the default size format: bytes or abbrev
3113             # PerlSetVar Apache2_SSI_Sizefmt "bytes"
3114             # To Set the default date time format
3115             # PerlSetVar Apache2_SSI_Timefmt ""
3116             # To enable legacy mode:
3117             # PerlSetVar Apache2_SSI_Expression "legacy"
3118             # To enable trunk mode:
3119             # PerlSetVar Apache2_SSI_Expression "trunk"
3120             </Directory>
3121              
3122             =head1 VERSION
3123              
3124             v0.2.0
3125              
3126             =head1 DESCRIPTION
3127              
3128             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.
3129              
3130             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>.
3131              
3132             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}>.
3133              
3134             See below details in this documentation and in the section on L</"SSI Directives">
3135              
3136             Under Apache mod_perl, you would implement it like this in your C<apache2.conf> or C<httpd.conf>
3137              
3138             <Files *.phtml>
3139             SetHandler modperl
3140             PerlOutputFilterHandler Apache2::SSI
3141             </Files>
3142              
3143             This would enable L<Apache2::SSI> for files whose extension is C<.phtml>. You can also limit this by location, such as:
3144              
3145             <Location /some/web/path>
3146             <Files *.html>
3147             SetHandler modperl
3148             PerlOutputFilterHandler Apache2::SSI
3149             </Files>
3150             </Location>
3151              
3152             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.
3153              
3154             As pointed out by Ken Williams, the original author of L<Apache::SSI>, the benefit for using L<Apache2::SSI> is:
3155              
3156             =over 4
3157              
3158             =item 1. You want to subclass L<Apache2::SSI> and have granular control on how to render ssi
3159              
3160             =item 2. You want to "parse the output of other mod_perl handlers, or send the SSI output through another handler"
3161              
3162             =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
3163              
3164             =back
3165              
3166             =head2 INSTALLATION
3167              
3168             perl Makefile.PL
3169             make
3170             make test
3171             sudo make install
3172              
3173             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>
3174              
3175             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.
3176              
3177             It tries hard to find the Apache configuration file. You can help it by providing command line modifiers, such as:
3178              
3179             perl Makefile.PL -apxs /usr/bin/apxs
3180              
3181             or, even specify the Apache configuration file:
3182              
3183             perl Makefile.PL -apxs /usr/bin/apxs -httpd_conf /home/john/etc/apache2/apache2.conf
3184              
3185             To run only some tests, for example:
3186              
3187             make test TEST_FILES="./t/31.file.t"
3188              
3189             If you are on a Linux type system, you can install C<apxs> by issuing on the command line:
3190              
3191             apt install apache2-dev
3192              
3193             You can check if you have it installed with the following command:
3194              
3195             dpkg -l | grep apache
3196              
3197             See L<ExtUtils::MakeMaker> for more information.
3198              
3199             =head1 METHODS
3200              
3201             =head2 new
3202              
3203             This instantiate an object that is used to access other key methods. It takes the following parameters:
3204              
3205             =over 4
3206              
3207             =item I<apache_filter>
3208              
3209             This is the L<Apache2::Filter> object object that is provided if running under mod_perl.
3210              
3211             =item I<apache_request>
3212              
3213             This is the L<Apache2::RequestRec> object that is provided if running under mod_perl.
3214              
3215             it can be retrieved from L<Apache2::RequestUtil/request> or via L<Apache2::Filter/r>
3216              
3217             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.
3218              
3219             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:
3220              
3221             use Apache2::RequestUtil (); # extends Apache2::RequestRec objects
3222             my $r = $r->is_initial_req ? $r : $r->main;
3223              
3224             =item I<debug>
3225              
3226             Sets the debug level. Starting from 3, this will output on the STDERR or in Apache error log a lot of debugging output.
3227              
3228             =item I<document_root>
3229              
3230             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>.
3231              
3232             =item I<document_uri>
3233              
3234             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>
3235              
3236             =item I<errmsg>
3237              
3238             The error message to be returned when a ssi directive fails. By default, it is C<[an error occurred while processing this directive]>
3239              
3240             =item I<html>
3241              
3242             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.
3243              
3244             =item I<legacy>
3245              
3246             Takes a boolean value suchas C<1> or C<0> to indicate whether the Apache2 expression supported accepts legacy style.
3247              
3248             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:
3249              
3250             $HTTP_COOKIES = /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/
3251              
3252             Modern expression equivalent would be:
3253              
3254             %{HTTP_COOKIES} =~ /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/
3255              
3256             See L<Regexp::Common::Apache2> for more information.
3257              
3258             See also the property I<trunk> to enable experimental expressions.
3259              
3260             =item I<remote_ip>
3261              
3262             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:
3263              
3264             my $ssi = Apache2::SSI->new( remote_ip => '192.168.2.10' ) ||
3265             die( Apache2::SSI->error );
3266              
3267             <!--#if expr="-R '192.168.2.0/24' || -R '127.0.0.1/24'" -->
3268             Remote ip is part of my private network
3269             <!--#else -->
3270             Go away!
3271             <!--#endif -->
3272              
3273             =item I<sizefmt>
3274              
3275             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.
3276              
3277             See L<Apache2 documentation|https://httpd.apache.org/docs/current/en/howto/ssi.html> for more information on this.
3278              
3279             =item I<timefmt>
3280              
3281             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.
3282              
3283             See L<Apache2 documentation|https://httpd.apache.org/docs/current/en/howto/ssi.html> for more information on this.
3284              
3285             =item I<trunk>
3286              
3287             This takes a boolean value such as C<0> or C<1> and when enabled this allows the support for Apache2 experimental expressions.
3288              
3289             See L<Regexp::Common::Apache2> for more information.
3290              
3291             Also, see the property I<legacy> to enable legacy Apache2 expressions.
3292              
3293             =back
3294              
3295             =head2 handler
3296              
3297             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>
3298              
3299             =head2 ap2perl_expr
3300              
3301             This method is used to convert Apache2 expressions into perl equivalents to be then eval'ed.
3302              
3303             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.
3304              
3305             It parse recursively the structure provided in the hash reference to provide the perl equivalent for each Apache2 expression component.
3306              
3307             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.
3308              
3309             =head2 apache_filter
3310              
3311             Set or get the L<Apache2::Filter> object.
3312              
3313             When running under Apache mod_perl this is set automatically from the special L</handler> method.
3314              
3315             =head2 apache_filter_handler
3316              
3317             This method is called from L</handler> to handle the Apache response when this module L<Apache2::SSI> is used as a filter handler.
3318              
3319             See also L</apache_response_handler>
3320              
3321             =head2 apache_request
3322              
3323             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.
3324              
3325             When running under Apache mod_perl this is set automatically from the special L</handler> method, such as:
3326              
3327             my $r = $f->r; # $f is the Apache2::Filter object provided by Apache
3328              
3329             =head2 apache_response_handler
3330              
3331             This method is called from L</handler> to handle the Apache response when this module L<Apache2::SSI> is used as a response handler.
3332              
3333             See also L</apache_filter_handler>
3334              
3335             =head2 clone
3336              
3337             Create a clone of the object and return it.
3338              
3339             =head2 decode_base64
3340              
3341             Decode base64 data provided. When running under Apache mod_perl, this uses L<APR::Base64/decode> module, otherwise it uses L<MIME::Base64/decode>
3342              
3343             If the decoded data contain utf8 data, this will decoded the utf8 data using L<Encode/decode>
3344              
3345             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3346              
3347             =head2 decode_entities
3348              
3349             Decode html data containing entities. This uses L<HTML::Entities/decode_entities>
3350              
3351             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3352              
3353             Example:
3354              
3355             $ssi->decode_entities( 'Tous les &Atilde;&ordf;tres humains naissent libres et &Atilde;&copy;gaux en dignit&Atilde;&copy; et en droits.' );
3356             # Tous les êtres humains naissent libres et égaux en dignité et en droits.
3357              
3358             =head2 decode_uri
3359              
3360             Decode uri encoded data. This uses L<URI::Escape/uri_unescape>.
3361              
3362             Not to be confused with x-www-form-urlencoded data. For that see L</decode_url>
3363              
3364             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3365              
3366             Example:
3367              
3368             $ssi->decode_uri( 'https%3A%2F%2Fwww.example.com%2F' );
3369             # https://www.example.com/
3370              
3371             =head2 decode_url
3372              
3373             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.
3374              
3375             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3376              
3377             Example:
3378              
3379             $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.' );
3380             # Tous les êtres humains naissent libres et égaux en dignité et en droits.
3381              
3382             =head2 document_filename
3383              
3384             This is an alias for L<Apache2::SSI::URI/filename>
3385              
3386             =head2 document_directory
3387              
3388             Returns an L<Apache2::SSI::URI> object of the current directory of the L</document_uri> provided.
3389              
3390             =head2 document_path
3391              
3392             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>.
3393              
3394             =head2 document_root
3395              
3396             Sets or gets the document root.
3397              
3398             Wen running under Apache mod_perl, this value will be available automatically, using L<Apache2::RequestRec/document_root> method.
3399              
3400             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>.
3401              
3402             =head2 document_uri
3403              
3404             Sets or gets the document uri, which is the uri of the document being processed.
3405              
3406             For example:
3407              
3408             /index.html
3409              
3410             Under Apache, this will get the environment variable C<DOCUMENT_URI> or calls the L<Apache2::RequestRec/uri> method.
3411              
3412             Outside of Apache, this will rely on a value being provided upon instantiating an object, or the environment variable C<DOCUMENT_URI> be present.
3413              
3414             The value should be an absolute uri.
3415              
3416             =head2 echomsg
3417              
3418             The default message to be returned for the C<echo> command when the variable called is not defined.
3419              
3420             Example:
3421              
3422             $ssi->echomsg( '[Value Undefined]' );
3423             ## or in the document itself
3424             <!--#config echomsg="[Value Undefined]" -->
3425             <!--#echo var="NON_EXISTING" encoding="none" -->
3426              
3427             would produce:
3428              
3429             [Value Undefined]
3430              
3431             =head2 encode_base64
3432              
3433             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>
3434              
3435             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.
3436              
3437             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.
3438              
3439             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3440              
3441             =head2 encode_entities
3442              
3443             Encode data into html entities. This uses L<HTML::Entities/encode_entities>
3444              
3445             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3446              
3447             Example:
3448              
3449             $ssi->encode_entities( 'Tous les êtres humains naissent libres et égaux en dignité et en droits.' );
3450             # Tous les &Atilde;&ordf;tres humains naissent libres et &Atilde;&copy;gaux en dignit&Atilde;&copy; et en droits.
3451              
3452             =head2 encode_uri
3453              
3454             Encode uri data. This uses L<URI::Escape/uri_escape_utf8>.
3455              
3456             Not to be confused with x-www-form-urlencoded data. For that see L</encode_url>
3457              
3458             If an error occurred during encoding, it will return undef and set an L</error> object accordingly.
3459              
3460             Example:
3461              
3462             $ssi->encode_uri( 'https://www.example.com/' );
3463             # https%3A%2F%2Fwww.example.com%2F
3464              
3465             =head2 encode_url
3466              
3467             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)
3468              
3469             If an error occurred during decoding, it will return undef and set an L</error> object accordingly.
3470              
3471             Example:
3472              
3473             $ssi->encode_url( 'Tous les êtres humains naissent libres et égaux en dignité et en droits.' );
3474             # Tous+les+%C3%83%C2%AAtres+humains+naissent+libres+et+%C3%83%C2%A9gaux+en+dignit%C3%83%C2%A9+et+en+droits.
3475              
3476             =head2 env
3477              
3478             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.
3479              
3480             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.
3481              
3482             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.
3483              
3484             For example, let assume you set the environment variable C<REQUEST_URI> or C<DOCUMENT_URI> like this:
3485              
3486             $ssi->env( REQUEST_URI => '/some/path/to/file.html?q=something&l=ja_JP' );
3487              
3488             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.
3489              
3490             =head2 errmsg
3491              
3492             Sets or gets the error message to be displayed in lieu of a faulty ssi directive. This is the same behaviour as in Apache.
3493              
3494             =head2 error
3495              
3496             Retrieve the error object set. This is a L<Module::Generic::Error> object.
3497              
3498             This module does not die nor "croak", but instead returns undef when an error occurs and set the error object.
3499              
3500             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:
3501              
3502             if( !defined( $ssi->parse( $some_html_data ) ) )
3503             {
3504             die( $ssi->error );
3505             }
3506              
3507             or maybe more simply, when you are sure you will not get a false, but defined value:
3508              
3509             $ssi->parse( $some_html_data ) || die( $ssi->error );
3510              
3511             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.
3512              
3513             =head2 filename
3514              
3515             This is an alias for L<Apache2::SSI::URI/filename>
3516              
3517             =head2 find_file
3518              
3519             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.
3520              
3521             This will call L</lookup_file> or L</lookup_uri> depending on whether it is dealing with a file or an uri.
3522              
3523             It returns a L<Apache2::SSI::URI> object which is stringifyable and contain the file path.
3524              
3525             =head2 finfo
3526              
3527             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.
3528              
3529             =head2 html
3530              
3531             Sets or gets the html data to be processed.
3532              
3533             =head2 lookup_file
3534              
3535             Provided with a file path and this will look up the file.
3536              
3537             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>.
3538              
3539             As per Apache SSI documentation, you cannot specify a path starting with C</> or C<../>
3540              
3541             It returns a L<Apache2::SSI::File> object.
3542              
3543             =head2 lookup_uri
3544              
3545             Provided with an uri, and this will loo it up and return a L<Apache2::SSI::URI> object.
3546              
3547             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.
3548              
3549             It returns a L<Apache2::SSI::URI> object.
3550              
3551             =head2 mod_perl
3552              
3553             Returns true when running under mod_perl, false otherwise.
3554              
3555             =head2 parse
3556              
3557             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.
3558              
3559             It returns the html string with the ssi result.
3560              
3561             =head2 parse_config
3562              
3563             Provided with an hash reference of parameters and this sets three of the object parameters that can also be set during object instantiation:
3564              
3565             =over 4
3566              
3567             =item I<echomsg>
3568              
3569             The value is a message that is sent back to the client if the echo element attempts to echo an undefined variable.
3570              
3571             This overrides any default value set for the parameter I<echomsg> upon object instantiation.
3572              
3573             =item I<errmsg>
3574              
3575             This is the default error message to be used as the result for a faulty ssi directive.
3576              
3577             See the L</echomsg> method.
3578              
3579             =item I<sizefmt>
3580              
3581             This is the format to be used to format the files size. Value can be either C<bytes> or C<abbrev>
3582              
3583             See also the L</sizefmt> method.
3584              
3585             =item I<timefmt>
3586              
3587             This is the format to be used to format the dates and times. The value is a date formatting based on L<POSIX/strftime>
3588              
3589             See also the L</timefmt> method.
3590              
3591             =back
3592              
3593             =head2 parse_echo
3594              
3595             Provided with an hash reference of parameter and this process the C<echo> ssi directive and returns its output as a string.
3596              
3597             For example:
3598              
3599             Query string passed: <!--#echo var="QUERY_STRING" -->
3600              
3601             There are a number of standard environment variable accessible under SSI on top of other environment variables set. See L<SSI Directives> section below.
3602              
3603             =head2 parse_echo_date_gmt
3604              
3605             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>.
3606              
3607             =head2 parse_echo_date_local
3608              
3609             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>.
3610              
3611             Example:
3612              
3613             <!--#echo var="DATE_LOCAL" -->
3614              
3615             =head2 parse_echo_document_name
3616              
3617             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>
3618              
3619             Outside of Apache, this returns the environment variable C<DOCUMENT_NAME>, if set, or the base name of the value for L</document_uri>
3620              
3621             Example:
3622              
3623             <!--#echo var="DOCUMENT_NAME" -->
3624              
3625             If the uri were C</some/where/file.html>, this would return only C<file.html>
3626              
3627             =head2 parse_echo_document_uri
3628              
3629             Returns the value of L</document_uri>
3630              
3631             Example:
3632              
3633             <!--#echo var="DOCUMENT_URI" -->
3634              
3635             The document uri would include, if any, any path info and query string.
3636              
3637             =head2 parse_echo_last_modified
3638              
3639             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>).
3640              
3641             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>
3642              
3643             Example:
3644              
3645             <!--#echo var="LAST_MODIFIED" -->
3646              
3647             =head2 parse_eval_expr
3648              
3649             Provided with a string representing an Apache2 expression and this will parse it, transform it into a perl equivalent and return its value.
3650              
3651             It does the parsing using L<Apache2::Expression/parse> called from L</parse_expr>
3652              
3653             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:
3654              
3655             <!--#config errmsg="[Include error]" -->
3656             <!--#if expr="%{HTTP_COOKIE} =~ /lang\%22\%3A\%22([a-zA-Z]+\-[a-zA-Z]+)\%22\%7D;?/"-->
3657             <!--#set var="CONTENT_LANGUAGE" value="%{tolower:$1}"-->
3658             <!--#elif expr="-z %{CONTENT_LANGUAGE}"-->
3659             <!--#set var="CONTENT_LANGUAGE" value="en"-->
3660             <!--#endif-->
3661             <!DOCTYPE html>
3662             <html lang="<!--#echo encoding="none" var="CONTENT_LANGUAGE" -->">
3663              
3664             =head2 parse_exec
3665              
3666             Provided with an hash reference of parameters and this process the C<exec> ssi directives.
3667              
3668             Example:
3669              
3670             <!--#exec cgi="/uri/path/to/progr.cgi" -->
3671              
3672             or
3673              
3674             <!--#exec cmd="/some/system/file/path.sh" -->
3675              
3676             =head2 parse_expr
3677              
3678             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>.
3679              
3680             It returns the perl representation of the Apache2 expression.
3681              
3682             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.
3683              
3684             =head2 parse_elif
3685              
3686             Parse the C<elif> condition.
3687              
3688             Example:
3689              
3690             <!--#if expr=1 -->
3691             Hi, should print
3692             <!--#elif expr=1 -->
3693             Shouldn't print
3694             <!--#else -->
3695             Shouldn't print
3696             <!--#endif -->
3697              
3698             =head2 parse_else
3699              
3700             Parse the C<else> condition.
3701              
3702             See L</parse_elif> above for example.
3703              
3704             =head2 parse_endif
3705              
3706             Parse the C<endif> condition.
3707              
3708             See L</parse_elif> above for example.
3709              
3710             =head2 parse_flastmod
3711              
3712             Process the ssi directive C<flastmod>
3713              
3714             Provided with an hash reference of parameters and this will return the formatted date time of the file last modification time.
3715              
3716             =head2 parse_fsize
3717              
3718             Provided with an hash reference of parameters and this will return the formatted file size.
3719              
3720             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.
3721              
3722             Example
3723              
3724             <!--#config sizefmt="abbrev" -->
3725             This file size is <!--#fsize file="/some/filesystem/path/to/archive.tar.gz" -->
3726              
3727             would return:
3728              
3729             This file size is 12.7M
3730              
3731             Or:
3732              
3733             <!--#config sizefmt="bytes" -->
3734             This file size is <!--#fsize virtual="/some/filesystem/path/to/archive.tar.gz" -->
3735              
3736             would return:
3737              
3738             This file size is 13,316,917 bytes
3739              
3740             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>
3741              
3742             =head2 parse_func_base64
3743              
3744             Returns the arguments provided into a base64 string.
3745              
3746             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.
3747              
3748             Example:
3749              
3750             <!--#set var="payload" value='{"sub":"1234567890","name":"John Doe","iat":1609047546}' encoding="base64" -->
3751             <!--#if expr="$payload == 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNjA5MDQ3NTQ2fQo='" -->
3752             Payload matches
3753             <!--#else -->
3754             Sorry, this failed
3755             <!--#endif -->
3756              
3757             =head2 parse_func_env
3758              
3759             Return first match of L<note>, L<reqenv>, and L<osenv>
3760              
3761             Example:
3762              
3763             <!--#if expr="env( $QUERY_STRING ) == /\bl=ja_JP/" -->
3764             Showing Japanese data
3765             <!--#else -->
3766             Defaulting to English
3767             <!--#endif -->
3768              
3769             =head2 parse_func_escape
3770              
3771             Escape special characters in %hex encoding.
3772              
3773             Example:
3774              
3775             <!--#set var="website" value="https://www.example.com/" -->
3776             Please go to <a href="<!--#echo var='website' encoding='escape' -->"><!--#echo var="website" --></a>
3777              
3778             =head2 parse_func_http
3779              
3780             Get HTTP request header; header names may be added to the Vary header.
3781              
3782             Example:
3783              
3784             <!--#if expr="http('X-API-ID') == 1234567" -->
3785             You're good to go.
3786             <!--#endif -->
3787              
3788             However, outside of an Apache environment this will return the value of the environment variable in the following order:
3789              
3790             =over 4
3791              
3792             =item X-API-ID (i.e. the name as-is)
3793              
3794             =item HTTP_X_API_ID (i.e. adding C<HTTP_> and replace C<-> for C<_>)
3795              
3796             =item X_API_ID (i.e. same as above, but without the C<HTTP_> prefix)
3797              
3798             =back
3799              
3800             If none is found, it returns an empty string.
3801              
3802             For an equivalent function for response headers, see L</parse_func_resp>
3803              
3804             =head2 parse_func_ldap
3805              
3806             Escape characters as required by LDAP distinguished name escaping (RFC4514) and LDAP filter escaping (RFC4515).
3807              
3808             See L<Apache documentation|https://httpd.apache.org/docs/trunk/en/expr.html#page-header> for more information
3809              
3810             Example:
3811              
3812             <!--#set var="phrase" value="%{ldap:'Tous les êtres humains naissent libres (et égaux) en dignité et\ en\ droits.\n'}" -->
3813             # Tous les êtres humains naissent libres \28et égaux\29 en dignité et\5c en\5c droits.\5cn
3814              
3815             =head2 parse_func_md5
3816              
3817             Hash the string using MD5, then encode the hash with hexadecimal encoding.
3818              
3819             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.
3820              
3821             Example:
3822              
3823             <!--#if expr="md5( $hash_data ) == '2f50e645b6ef04b5cfb76aed6de343eb'" -->
3824             You're good to go.
3825             <!--#endif -->
3826              
3827             =head2 parse_func_note
3828              
3829             Lookup request note
3830              
3831             <!--#set var="CUSTOMER_ID" value="1234567" -->
3832             <!--#if expr="note('CUSTOMER_ID') == 1234567" -->
3833             Showing special message
3834             <!--#endif -->
3835              
3836             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.
3837              
3838             For example:
3839              
3840             In your perl script outside of Apache:
3841              
3842             # Basic parameters to make Apache2::SSI happy
3843             my $ssi = Apache2::SSI->new( document_root => '/home/john/www', document_uri => '/' ) ||
3844             die( Apache2::SSI->error );
3845             $ssi->notes( API_VERSION => 2 );
3846              
3847             Then, in your perl script running under the web server, be it Apache2/mod_perl2 or not:
3848              
3849             my $ssi = Apache2::SSI->new || die( Apache2::SSI->error );
3850             my $api_version = $ssi->notes( 'API_VERSION' );
3851              
3852             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.
3853              
3854             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:
3855              
3856             use Apache2::SSI::Notes;
3857             my $notes = Apache2::SSI::Notes->new;
3858             $notes->remove;
3859              
3860             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.
3861              
3862             =head2 parse_func_osenv
3863              
3864             Lookup operating system environment variable
3865              
3866             <!--#if expr="env('LANG') =~ /en(_(GB|US))/" -->
3867             Showing English language
3868             <!--#endif -->
3869              
3870             =head2 parse_func_replace
3871              
3872             replace(string, "from", "to") replaces all occurrences of "from" in the string with "to".
3873              
3874             Example:
3875              
3876             <!--#if expr="replace( 'John is in Tokyo', 'John', 'Jack' ) == 'Jack is in Tokyo'" -->
3877             This worked!
3878             <!--#else -->
3879             Nope, it failed.
3880             <!--#endif -->
3881              
3882             =head2 parse_func_req
3883              
3884             See L</parse_func_http>
3885              
3886             =head2 parse_func_reqenv
3887              
3888             Lookup request environment variable (as a shortcut, v can also be used to access variables).
3889              
3890             This is only different from L</parse_func_env> under Apache.
3891              
3892             See L</parse_func_env>
3893              
3894             Example:
3895              
3896             <!--#if expr="reqenv('ProcessId') == '$$'" -->
3897             This worked!
3898             <!--#else -->
3899             Nope, it failed.
3900             <!--#endif -->
3901              
3902             Or using the Apache SSI C<v> shortcut:
3903              
3904             <!--#if expr="v('ProcessId') == '$$'" -->
3905              
3906             =head2 parse_func_req_novary
3907              
3908             Same as L</parse_func_req>, but header names will not be added to the Vary header.
3909              
3910             =head2 parse_func_resp
3911              
3912             Get HTTP response header.
3913              
3914             Example:
3915              
3916             <!--#if expr="resp('X-ProcessId') == '$$'" -->
3917             This worked!
3918             <!--#else -->
3919             Nope, it failed.
3920             <!--#endif -->
3921              
3922             An important note here:
3923              
3924             First, there is obviously no response header available for perl scripts running outside of Apache2/mod_perl2 framework.
3925              
3926             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>).
3927              
3928             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.
3929              
3930             =head2 parse_func_sha1
3931              
3932             Hash the string using SHA1, then encode the hash with hexadecimal encoding.
3933              
3934             Example:
3935              
3936             <!--#if expr="sha1('Tous les êtres humains naissent libres et égaux en dignité et en droits.') == '8c244078c64a51e8924ecf646df968094a818d59'" -->
3937             This worked!
3938             <!--#else -->
3939             Nope, it failed.
3940             <!--#endif -->
3941              
3942             =head2 parse_func_tolower
3943              
3944             Convert string to lower case.
3945              
3946             Example:
3947              
3948             <!--#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.'" -->
3949             This worked!
3950             <!--#else -->
3951             Nope, it failed.
3952             <!--#endif -->
3953              
3954             =head2 parse_func_toupper
3955              
3956             Convert string to upper case.
3957              
3958             Example:
3959              
3960             <!--#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.'" -->
3961             This worked!
3962             <!--#else -->
3963             Nope, it failed.
3964             <!--#endif -->
3965              
3966             =head2 parse_func_unbase64
3967              
3968             Decode base64 encoded string, return truncated string if 0x00 is found.
3969              
3970             Example:
3971              
3972             <!--#if expr="unbase64('VG91cyBsZXMgw6p0cmVzIGh1bWFpbnMgbmFpc3NlbnQgbGlicmVzIGV0IMOpZ2F1eCBlbiBkaWduaXTDqSBldCBlbiBkcm9pdHMu') == 'Tous les êtres humains naissent libres et égaux en dignité et en droits.'" -->
3973             This worked!
3974             <!--#else -->
3975             Nope, it failed.
3976             <!--#endif -->
3977              
3978             =head2 parse_func_unescape
3979              
3980             Unescape %hex encoded string, leaving encoded slashes alone; return empty string if %00 is found.
3981              
3982             Example:
3983              
3984             <!--#if expr="unescape('https%3A%2F%2Fwww.example.com%2F') == 'https://www.example.com/'" -->
3985             This worked!
3986             <!--#else -->
3987             Nope, it failed.
3988             <!--#endif -->
3989              
3990             =head2 parse_if
3991              
3992             Parse the C<if> condition.
3993              
3994             See L</parse_elif> above for example.
3995              
3996             =head2 parse_include
3997              
3998             Provided with an hash reference of parameters and this process the ssi directive C<include>, which is arguably the most used.
3999              
4000             It will try to resolve the file to include by calling L</find_file> with the same arguments this is called with.
4001              
4002             Under Apache, if the previous look up succeeded, it calls L<Apache2::SubRequest/run>
4003              
4004             Outside of Apache, it reads the entire file, utf8 decode it and return it.
4005              
4006             =head2 parse_perl
4007              
4008             Provided with an hash reference of parameters and this parse some perl command and returns the output as a string.
4009              
4010             Example:
4011              
4012             <!--#perl sub="sub{ print 'Hello!' }" -->
4013              
4014             or
4015              
4016             <!--#perl sub="package::subroutine" -->
4017              
4018             =head2 parse_printenv
4019              
4020             This returns a list of environment variables sorted and their values.
4021              
4022             =head2 parse_set
4023              
4024             Provided with an hash reference of parameters and this process the ssi directive C<set>.
4025              
4026             Possible parameters are:
4027              
4028             =over 4
4029              
4030             =item I<decoding>
4031              
4032             The decoding of the variable before it is set. This can be C<none>, C<url>, C<urlencoded>, C<base64> or C<entity>
4033              
4034             =item I<encoding>
4035              
4036             This instruct to encode the variable value before display. It can the same possible value as for decoding.
4037              
4038             =item I<value>
4039              
4040             The string value for the variable to be set.
4041              
4042             =item I<var>
4043              
4044             The variable name
4045              
4046             =back
4047              
4048             Example:
4049              
4050             <!--#set var="debug" value="2" -->
4051             <!--#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" -->
4052              
4053             See the L<Apache SSI documentation|https://httpd.apache.org/docs/current/en/mod/mod_include.html> for more information.
4054              
4055             =head2 parse_ssi
4056              
4057             Provided with the html data as a string and this will parse its embedded ssi directives and return its output as a string.
4058              
4059             If it fails, it sets an L</error> and returns an empty string.
4060              
4061             =head2 path_info
4062              
4063             Sets or gets the path info for the current uri.
4064              
4065             Example:
4066              
4067             my $string = $ssi->path_info;
4068             $ssi->path_info( '/my/path/info' );
4069              
4070             The path info value is also set automatically when L</document_uri> is called, such as:
4071              
4072             $ssi->document_uri( '/some/path/to/file.html/my/path/info?q=something&l=ja_JP' );
4073              
4074             This will also set automatically the C<PATH_INFO> environment variable.
4075              
4076             =head2 query_string
4077              
4078             Set or gets the query string for the current uri.
4079              
4080             Example:
4081              
4082             my $string = $ssi->query_string;
4083             $ssi->query_string( 'q=something&l=ja_JP' );
4084              
4085             or, using the L<URI> module:
4086              
4087             $ssi->query_string( $uri->query );
4088              
4089             The query string value is set automatically when you provide an L<document_uri> upon instantiation or after:
4090              
4091             $ssi->document_uri( '/some/path/to/file.html?q=something&l=ja_JP' );
4092              
4093             This will also set automatically the C<QUERY_STRING> environment variable.
4094              
4095             =head2 remote_ip
4096              
4097             Sets or gets the remote ip address of the visitor.
4098              
4099             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>
4100              
4101             This value can also be overriden by being provided during object instantiation.
4102              
4103             # Pretend the ssi directives are accessed from this ip
4104             $ssi->remote_ip( '192.168.2.20' );
4105              
4106             This is useful when one wants to check how the rendering will be when accessed from certain ip addresses.
4107              
4108             This is used primarily when there is an expression such as
4109              
4110             <!--#if expr="-R '192.168.1.0/24' -->
4111             Visitor is part of my private network
4112             <!--#endif -->
4113              
4114             or
4115              
4116             <!--#if expr="v('REMOTE_ADDR') -R '192.168.1.0/24' -->
4117             <!--#include file="/home/john/special_hidden_login_feature.html" -->
4118             <!--#endif -->
4119              
4120             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:
4121              
4122             use APR::SockAddr ();
4123             my $ip = $r->connection->remote_addr->ip_get();
4124              
4125             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>
4126              
4127             =head2 request_uri
4128              
4129             This is an alias for L</document_uri>
4130              
4131             =head2 server_version
4132              
4133             Returns the server version as a L<version> object can caches that value.
4134              
4135             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.
4136              
4137             =head2 sizefmt
4138              
4139             Sets or gets the formatting for file sizes. Value can be either C<bytes> or C<abbrev>
4140              
4141             =head2 timefmt
4142              
4143             Sets or gets the formatting for date and time values. The format takes the same values as L<POSIX/strftime>
4144              
4145             =head1 Encoding
4146              
4147             At present time, the html data are treated as utf8 data and decoded and encoded back as such.
4148              
4149             If there is a need to broaden support for other charsets, let me know.
4150              
4151             =head1 SSI Directives
4152              
4153             This is taken from Apache documentation and summarised here for convenience and clarity to the perl community.
4154              
4155             =head2 config
4156              
4157             <!--#config errmsg="Error occurred" sizefmt="abbrev" timefmt="%B %Y" -->
4158             <!--#config errmsg="Oopsie" -->
4159             <!--#config sizefmt="bytes" -->
4160             # Thursday 24 December 2020
4161             <!--#config timefmt="%A $d %B %Y" -->
4162              
4163             =head2 echo
4164              
4165             <!--#set var="HTMl_TITLE" value="Un sujet intéressant" -->
4166             <!--#echo var="HTMl_TITLE" encoding="entity" -->
4167              
4168             Encoding can be either C<entity>, C<url> or C<none>
4169              
4170             =head2 exec
4171              
4172             # pwd is "print working directory" in shell
4173             <!--#exec cmd="pwd" -->
4174             <!--#exec cgi="/uri/path/to/prog.cgi" -->
4175              
4176             =head2 include
4177              
4178             # Filesystem file path
4179             <!--#include file="/home/john/var/quote_of_the_day.txt" -->
4180             # Relative to the document root
4181             <!--#include virtual="/footer.html" -->
4182              
4183             =head2 flastmod
4184              
4185             <!--#flastmod file="/home/john/var/quote_of_the_day.txt" -->
4186             <!--#flastmod virtual="/copyright.html" -->
4187              
4188             =head2 fsize
4189              
4190             <!--#fsize file="/download/software-v1.2.tgz" -->
4191             <!--#fsize virtual="/images/logo.jpg" -->
4192              
4193             =head2 printenv
4194              
4195             <!--#printenv -->
4196              
4197             =head2 set
4198              
4199             <!--#set var="debug" value="2" -->
4200              
4201             =head2 if, elif, endif and else
4202              
4203             <!--#if expr="$debug > 1" -->
4204             I will print a lot of debugging
4205             <!--#else -->
4206             Debugging output will be reasonable
4207             <!--#endif -->
4208              
4209             or with new version of Apache SSI:
4210              
4211             No such file or directory.
4212             <!--#if expr="v('HTTP_REFERER') != ''" -->
4213             Please let the admin of the <a href="<!--#echo encoding="url" var="HTTP_REFERER" -->"referring site</a> know about their dead link.
4214             <!--#endif -->
4215              
4216             =head2 functions
4217              
4218             Apache SSI supports the following functions, as of Apache version 2.4.
4219              
4220             See L<Apache documentation|https://httpd.apache.org/docs/current/en/expr.html#page-header> for detailed description of what they do.
4221              
4222             You can also refer to the methods C<parse_func_*> documented above, which implement those Apache functions.
4223              
4224             =over 4
4225              
4226             =item I<base64>
4227              
4228             =item I<env>
4229              
4230             =item I<escape>
4231              
4232             =item I<http>
4233              
4234             =item I<ldap>
4235              
4236             =item I<md5>
4237              
4238             =item I<note>
4239              
4240             =item I<osenv>
4241              
4242             =item I<replace>
4243              
4244             =item I<req>
4245              
4246             =item I<reqenv>
4247              
4248             =item I<req_novary>
4249              
4250             =item I<resp>
4251              
4252             =item I<sha1>
4253              
4254             =item I<tolower>
4255              
4256             =item I<toupper>
4257              
4258             =item I<unbase64>
4259              
4260             =item I<unescape>
4261              
4262             =back
4263              
4264             =head2 variables
4265              
4266             On top of all environment variables available, Apache makes the following ones also accessible:
4267              
4268             =over 4
4269              
4270             =item DATE_GMT
4271              
4272             =item DATE_LOCAL
4273              
4274             =item DOCUMENT_ARGS
4275              
4276             =item DOCUMENT_NAME
4277              
4278             =item DOCUMENT_PATH_INFO
4279              
4280             =item DOCUMENT_URI
4281              
4282             =item LAST_MODIFIED
4283              
4284             =item QUERY_STRING_UNESCAPED
4285              
4286             =item USER_NAME
4287              
4288             =back
4289              
4290             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.
4291              
4292             =head2 expressions
4293              
4294             There is reasonable, but limited support for Apache expressions. For example, the followings are supported
4295              
4296             In the examples below, we use the variable C<QUERY_STRING>, but you can use any other variable of course.
4297              
4298             The regular expression are the ones L<PCRE|http://www.pcre.org/> compliant, so your perl regular expressions should work.
4299              
4300             <!--#if expr="$QUERY_STRING = 'something'" -->
4301             <!--#if expr="v('QUERY_STRING') = 'something'" -->
4302             <!--#if expr="%{QUERY_STRING} = 'something'" -->
4303             <!--#if expr="$QUERY_STRING = /^something/" -->
4304             <!--#if expr="$QUERY_STRING == /^something/" -->
4305             # works also with eq, ne, lt, le, gt and ge
4306             <!--#if expr="9 gt 3" -->
4307             <!--#if expr="9 -gt 3" -->
4308             # Other operators work too, namely == != < <= > >= =~ !~
4309             <!--#if expr="9 > 3" -->
4310             <!--#if expr="9 !> 3" -->
4311             <!--#if expr="9 !gt 3" -->
4312             # Checks the remote ip is part of this subnet
4313             <!--#if expr="-R 192.168.2.0/24" -->
4314             <!--#if expr="192.168.2.10 -R 192.168.2.0/24" -->
4315             <!--#if expr="192.168.2.10 -ipmatch 192.168.2.0/24" -->
4316             # Checks if variable is non-empty
4317             <!--#if expr="-n $some_variable" -->
4318             # Checks if variable is empty
4319             <!--#if expr="-z $some_variable" -->
4320             # Checks if the visitor can access the uri /restricted/uri
4321             <!--#if expr="-A /restricted/uri" -->
4322              
4323             For subnet checks, this uses L<Net::Subnet>
4324              
4325             Expressions that would not work outside of Apache, i.e. it will return an empty string:
4326              
4327             <!--#expr="%{HTTP:X-example-header} in { 'foo', 'bar', 'baz' }" -->
4328              
4329             See L<Apache documentation|http://httpd.apache.org/docs/2.4/en/expr.html> for more information.
4330              
4331             =head1 CREDITS
4332              
4333             Credits to Ken Williams for his implementation of L<Apache::SSI> from which I borrowed some code.
4334              
4335             =head1 AUTHOR
4336              
4337             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
4338              
4339             CPAN ID: jdeguest
4340              
4341             L<https://git.deguest.jp/jack/Apache2-SSI>
4342              
4343             =head1 SEE ALSO
4344              
4345             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>
4346              
4347             mod_include, mod_perl(3), L<Apache::SSI>,
4348             L<https://httpd.apache.org/docs/current/en/mod/mod_include.html>,
4349             L<https://httpd.apache.org/docs/current/en/howto/ssi.html>,
4350             L<https://httpd.apache.org/docs/current/en/expr.html>
4351             L<https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_>
4352              
4353             =head1 COPYRIGHT & LICENSE
4354              
4355             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
4356              
4357             You can use, copy, modify and redistribute this package and associated
4358             files under the same terms as Perl itself.
4359              
4360             =cut