File Coverage

blib/lib/Apache2/SSI/URI.pm
Criterion Covered Total %
statement 378 634 59.6
branch 136 334 40.7
condition 50 136 36.7
subroutine 40 54 74.0
pod 18 24 75.0
total 622 1182 52.6


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI/URI.pm
3             ## Version v0.1.1
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2020/12/18
7             ## Modified 2021/02/01
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::URI;
14             BEGIN
15             {
16 15     15   127042 use strict;
  15         42  
  15         578  
17 15     15   84 use warnings::register;
  15         36  
  15         2404  
18 15     15   98 use parent qw( Apache2::SSI::Common );
  15         32  
  15         347  
19 15     15   1749 use Apache2::SSI::Finfo;
  15         39  
  15         641  
20 15     15   95 use Cwd;
  15         30  
  15         908  
21 15     15   89 use File::Spec ();
  15         43  
  15         255  
22             ## Used for debugging
23             ## use Devel::Confess;
24 15     15   70 use Nice::Try;
  15         27  
  15         127  
25 15     15   30666375 use Scalar::Util ();
  15         44  
  15         572  
26 15     15   1846 require constant;
27 15     15   117 use URI;
  15         33  
  15         478  
28 15     15   79 use constant URI_CLASS => 'URI';
  15         28  
  15         1404  
29 15     15   100 use URI::file;
  15         32  
  15         2409  
30 15 50       81 if( $ENV{MOD_PERL} )
31             {
32 0         0 require Apache2::RequestRec;
33 0         0 require Apache2::RequestUtil;
34 0         0 require Apache2::SubRequest;
35 0         0 require Apache2::Access;
36 0         0 require Apache2::Const;
37 0         0 Apache2::Const->import( compile => qw( :common :http OK DECLINED ) );
38             }
39             ## use Devel::Confess;
40 15         28 our( $DEBUG );
41             use overload (
42 93     93   1900 q{""} => sub { $_[0]->document_uri->as_string },
43             bool => sub () { 1 },
44 15         219 fallback => 1,
45 15     15   100 );
  15         33  
46 15         29 our $VERSION = 'v0.1.1';
47 15         40241 our $DIR_SEP = $Apache2::SSI::Common::DIR_SEP;
48             };
49              
50             ## document_root = /home/joe/www
51             ## base_uri = /my/uri/file.html/some/path/info?q=something&l=ja_JP
52             ## base_uri is the current reference document
53             ## document_uri = ./about.html
54             ## document_uri is the uri which is the purpose of this object. It will be made absolute and its dots flattened
55             ## Example: ../about.html?q=hello would become /my/about.html?q=hello
56             sub init
57             {
58 129     129 1 165326 my $self = shift( @_ );
59 129         1370 $self->{apache_request} = '';
60 129 50       564 $self->{base_uri} = '/' unless( length( $self->{base_uri} ) );
61             ## By default
62 129         336 $self->{code} = 200;
63 129         272 $self->{document_path} = '';
64 129         299 $self->{document_root} = '';
65             ## Reference document for the main request
66 129         275 $self->{document_uri} = '';
67 129         271 $self->{filepath} = '';
68 129         377 $self->{finfo} = '';
69 129         513 $self->{_init_params_order} = [qw( apache_request document_root base_uri document_uri document_path filepath )];
70 129         287 $self->{_init_strict_use_sub} = 1;
71 129 50       576 $self->SUPER::init( @_ ) || return;
72 129         4566 $self->{_env} = {};
73 129         256 $self->{_path_info_processed} = 0;
74 129         244 $self->{_uri_reset} = 0;
75 129   33     316 $self->{document_root} ||= $self->env( 'DOCUMENT_ROOT' );
76 129   33     418 $self->{base_uri} ||= $self->env( 'DOCUMENT_URI' );
77             ## $self->message( 4, "Apache RequestRec object provided ? '$self->{apache_request}' for document uri '$self->{document_uri}'." );
78 129 50       344 return( $self->error( "No document root was provided." ) ) if( !length( $self->{document_root} ) );
79 129 50       294 return( $self->error( "No base uri was provided." ) ) if( !length( $self->{base_uri} ) );
80 129 50       392 return( $self->error( "No document uri was provided." ) ) if( !length( $self->{document_uri} ) );
81             ## Small correction if necessary. If the base uri is a directory, it needs to have a trailing "/", so URI knows this is a directory and not a file.
82             ## URI->new( "./file.pl" )->abs( "/ssi/plop" ) becomes "/ssi/file.pl" whereas it should be /ssi/plop/file.pl
83             ## $self->{base_uri} .= '/' if( length( $self->{base_uri} ) && -d( "$self->{document_root}$self->{base_uri}" ) && substr( $self->{base_uri}, -1, 1 ) ne '/' );
84 129         1087 return( $self );
85             }
86              
87 2334     2334 1 5422 sub apache_request { return( shift->_set_get_object_without_init( 'apache_request', 'Apache2::RequestRec', @_ ) ); }
88              
89             sub base_dir
90             {
91 0     0 0 0 my $self = shift( @_ );
92 0 0       0 return( $self->{base_dir} ) if( length( $self->{base_dir} ) );
93             ## Just in case
94 0 0       0 return( $self->root ) if( !length( $self->{base_uri} ) );
95 0         0 my $base = $self->base_uri;
96 0 0       0 return( $self->error( "No base uri defined." ) ) if( !length( $base ) );
97 0         0 my $path = $base->document_path;
98 0         0 my @segments = split( '/', $path, -1 );
99 0         0 pop( @segments );
100 0 0       0 return( $base ) if( !scalar( @segments ) );
101 0         0 my $r = $self->apache_request;
102 0         0 my $dir_path = join( '/', @segments );
103            
104 0         0 my $hash = {};
105 0 0       0 if( $r )
106             {
107 0         0 my $rr = $self->lookup_uri( $dir_path );
108 0 0       0 if( !defined( $rr ) )
    0          
    0          
109             {
110 0         0 $self->message( 3, "Error occured looking up uri '$path': ", $self->error );
111 0         0 return;
112             }
113             elsif( $rr->status != Apache2::Const::HTTP_OK )
114             {
115 0         0 $self->message( 3, "There was an error looking up bas directory \"$dir_path\"." );
116 0         0 return( $self->error( "Could not look up base directory \"$dir_path\". Returned code is: ", $rr->status ) );
117             }
118             elsif( $rr->finfo->filetype == APR::Const::FILETYPE_NOFILE )
119             {
120 0         0 $self->message( 3, "Base directory \"$dir_path\" is not found." );
121 0         0 return( $self->error( "Could not find base directory \"$dir_path\"." ) );
122             }
123             ## Remove trailing slash
124 0         0 my $u = $self->_trim_trailing_slash( $rr->uri );
125            
126 0         0 $hash =
127             {
128             apache_request => $self->apache_request,
129             base_dir => $self->root,
130             base_uri => $self->root,
131             document_path => "$u",
132             document_root => $rr->document_root,
133             document_uri => "$u",
134             filename => $rr->filename,
135             path_info => $rr->path_info,
136             query_string => scalar( $rr->args ),
137             _path_info_processed => 1,
138             };
139             }
140             else
141             {
142 0         0 $hash =
143             {
144             base_dir => $self->root,
145             base_uri => $self->root,
146             document_path => $dir_path,
147             document_root => $self->document_root,
148             document_uri => $dir_path,
149             filename => $self->document_root . $dir_path,
150             path_info => '',
151             query_string => '',
152             _path_info_processed => 1,
153             };
154             }
155 0         0 $self->{base_dir} = bless( $hash => ref( $self ) );
156 0         0 return( $self->{base_dir} );
157             }
158              
159             sub base_uri
160             {
161 28     28 1 709 my $self = shift( @_ );
162 28         103 my $new;
163 28 100       181 if( @_ )
    100          
164             {
165 12         50 $new = shift( @_ );
166             }
167             elsif( !ref( $self->{base_uri} ) )
168             {
169 3         30 $new = $self->{base_uri};
170             }
171            
172 28 100       113 unless( length( $new ) )
173             {
174 13         59 $self->message( 4, "Returning base_uri object '", overload::StrVal( $self->{base_uri} ), "' (", ref( $self->{base_uri} ), ")." );
175 13         298 return( $self->{base_uri} );
176             }
177            
178 15         156 $self->message( 4, "Processing new base uri '$new'." );
179 15         326 my $r = $self->apache_request;
180             ## We create an URI object, so we can get the path only
181 15         299 my $u = $self->new_uri( $new );
182 15         55 my $path = $u->path;
183 15 50       367 if( $r )
184             {
185 0         0 $self->message( 3, "Looking up uri \"$path\"." );
186 0         0 my $rr = $self->lookup_uri( $path );
187 0 0       0 if( !defined( $rr ) )
    0          
    0          
188             {
189 0         0 $self->message( 3, "Error occured looking up uri '$path': ", $self->error );
190 0         0 return;
191             }
192             elsif( $rr->status != Apache2::Const::HTTP_OK )
193             {
194 0         0 my $hdrs = $rr->headers_out;
195 0     0   0 $self->message( 3, "There was an error looking up uri \"$path\" with resulting uri \"", $rr->uri, "\". Headers were: ", sub{ $self->dump( $hdrs ) } );
  0         0  
196 0         0 return( $self->error( "Could not look up uri \"$path\". Returned code is: ", $rr->status ) );
197             }
198             elsif( $rr->finfo->filetype == APR::Const::FILETYPE_NOFILE )
199             {
200 0         0 $self->message( 3, "URI \"$path\" is not found." );
201 0         0 return( $self->error( "Could not find uri \"$path\" (originally $u)." ) );
202             }
203            
204             ## Remove trailing slash
205 0         0 my $u2 = $self->_trim_trailing_slash( $rr->unparsed_uri );
206            
207 0         0 $self->message( 3, "Setting base_uri value via document_uri to '$u2'. Path info found '", $rr->path_info, "'" );
208 0         0 my $hash =
209             {
210             apache_request => $r,
211             base_dir => $self->root,
212             base_uri => $self->root,
213             document_path => substr( $u2->path, 0, length( $u2->path ) - length( $rr->path_info ) ),
214             document_root => $self->document_root,
215             document_uri => $u2,
216             filename => $rr->filename,
217             path_info => $rr->path_info,
218             query_string => scalar( $rr->args ),
219             _path_info_processed => 1,
220             };
221 0 0       0 if( $rr->finfo->filetype == APR::Const::FILETYPE_DIR )
222             {
223 0         0 $self->{base_dir} = bless( $hash => ref( $self ) );
224             }
225 0         0 $self->{base_uri} = bless( $hash => ref( $self ) );
226             }
227             else
228             {
229 15         133 $self->message( 4, "Resolving uri \"$path\"." );
230             ## We need to ensure the base uri is free of any path info or query string !
231 15         304 my $ref = $self->_find_path_info( $u->path );
232 15     0   224 $self->message( 4, "_find_path_info reslulted in: ", sub{ $self->dump( $ref ) });
  0         0  
233 15 50       403 if( !defined( $ref ) )
    50          
234             {
235 0         0 $self->message( 3, "Error resolving \"$path\"." );
236 0         0 return( $self->error( "Unable to resolve \"$u\"." ) );
237             }
238             elsif( $ref->{code} != 200 )
239             {
240 0         0 $self->message( 3, "URI \"$path\" is not found." );
241 0         0 $self->error( "Failed to resolve \"$u\". Resulting code is '$ref->{code}'." );
242             }
243 15         84 $self->message( 4, "Creating object." );
244             my $hash =
245             {
246             base_dir => $self->root,
247             base_uri => $self->root,
248             document_path => $ref->{path},
249             document_root => $self->document_root,
250             filename => $ref->{filepath},
251             path_info => $ref->{path_info},
252             query_string => $ref->{query_string},
253 15         285 _path_info_processed => 1,
254             };
255 15 100       108 my $tmp = $self->new_uri( $ref->{path_info} ? join( '', $ref->{path}, $ref->{path_info} ) : $ref->{path} );
256 15 50       75 $tmp->query( $ref->{query_string} ) if( $ref->{query_string} );
257 15         52 $hash->{document_uri} = $tmp;
258 15 100       345 $self->{base_dir} = bless( $hash => ref( $self ) ) if( -d( $ref->{path} ) );
259 15         109 $self->{base_uri} = bless( $hash => ref( $self ) );
260             }
261 15         82 $self->message( 3, "Returning base_uri: '", overload::StrVal( $self->{base_uri} ), "' ($self->{base_uri})." );
262 15         348 return( $self->{base_uri} );
263             }
264              
265             sub clone
266             {
267 1     1 1 338 my $self = shift( @_ );
268 1         3 my $new = {};
269 1         40 my @fields = grep( !/^(apache_request|finfo)$/, keys( %$self ) );
270 1         17 @$new{ @fields } = @$self{ @fields };
271 1         4 $new->{apache_request} = $self->{apache_request};
272 1         8 my $env = {};
273 1         7 %$env = %{$self->{_env}};
  1         83  
274 1         9 $new->{_env} = $env;
275 1   33     23 return( bless( $new => ( ref( $self ) || $self ) ) );
276             }
277              
278             sub code
279             {
280 220     220 1 522 my $self = shift( @_ );
281 220         406 my $r = $self->apache_request;
282 220 50       3101 if( $r )
283             {
284 0 0       0 $r->status( @_ ) if( @_ );
285 0         0 return( $r->status );
286             }
287             else
288             {
289 220 100       611 $self->{code} = shift( @_ ) if( @_ );
290 220         633 return( int( $self->{code} ) );
291             }
292             }
293              
294 0     0 0 0 sub document_dir { return( shift->document_directory( @_ ) ); }
295              
296             sub document_directory
297             {
298 1     1 1 347 my $self = shift( @_ );
299 1   50     19 my $doc_path = $self->document_path || return( $self->error( "No document path set." ) );
300 1   50     25 my $doc_root = $self->document_root || return( $self->error( "No document root set." ) );
301 1         11 $self->message( 3, "Document path is '$doc_path' and document root is '$doc_root'." );
302 1 50 33     35 return( $self->make( document_uri => $doc_path ) ) if( -e( "${doc_root}${doc_path}" ) && -d( _ ) );
303 1         57 my $parent = $self->parent;
304 1         13 $self->message( 3, "Returning parent '$parent'." );
305 1         23 return( $parent );
306             }
307              
308 2     2 1 389 sub document_filename { return( shift->filename( @_ ) ); }
309              
310             sub document_path
311             {
312 155     155 1 1238 my $self = shift( @_ );
313 155         312 my $class = ref( $self );
314 155   100     1054 my $caller = (caller(1))[3] // '';
315             ## my $caller = substr( $sub, rindex( $sub, ':' ) + 1 );
316 155         616 my $r = $self->apache_request;
317 155 50       2444 if( $r )
318             {
319 0 0       0 if( @_ )
    0          
320             {
321 0         0 my $uri = shift( @_ );
322 0         0 $self->message( 4, "Looking up document path '$uri'." );
323 0 0       0 $r = $r->is_initial_req ? $r : $r->main;
324 0         0 my $rr = $self->lookup_uri( $uri );
325 0 0       0 if( !defined( $rr ) )
326             {
327 0         0 $self->message( 3, "Error occured looking up uri '$path': ", $self->error );
328 0         0 return;
329             }
330 0         0 $self->message( 4, "New path looked up is '", $rr->uri, "'." );
331 0         0 my $u = APR::URI->parse( $rr->pool, $r->uri );
332 0         0 $self->message( 4, "Document parsed derived from '", $rr->uri, "' by APR::URI is: '", $u->rpath, "'." );
333             ## Remove trailing slash
334 0         0 my $u2 = $self->_trim_trailing_slash( $u->rpath );
335 0         0 $self->{document_path} = $u2;
336 0 0       0 $self->{_uri_reset} = 'document_path' unless( $caller eq "${class}\::document_uri" );
337             }
338             elsif( !length( $self->{document_path} ) )
339             {
340 0         0 $self->message( 4, "No document path set. Guessing it from \$r->uri '", $r->uri, "'." );
341 0         0 my $u = APR::URI->parse( $r->pool, $r->uri );
342 0         0 $self->message( 3, "Setting document path to '", $u->rpath, "'." );
343 0         0 $self->{document_path} = $self->new_uri( $u->rpath );
344             }
345             }
346             else
347             {
348 155 100       436 if( @_ )
349             {
350 146         256 my $uri = shift( @_ );
351 146         578 $self->message( 4, "Setting new document path for '$uri'." );
352 146         2291 $self->{document_path} = $self->new_uri( $self->collapse_dots( $uri ) );
353 146         619 $self->message( 3, "Document path value is now: '", $self->{document_path}, "' (", overload::StrVal( $self->{document_path} ), ")." );
354 146 50       3412 $self->{_uri_reset} = 'document_path' unless( $caller eq "${class}\::document_uri" );
355             }
356             }
357 155         314 return( $self->{document_path} );
358             }
359              
360             sub document_root
361             {
362 618     618 1 21964 my $self = shift( @_ );
363 618         1334 my $r = $self->apache_request;
364 618         8893 my $new;
365 618 100       1312 if( @_ )
366             {
367 129         230 $new = shift( @_ );
368 129         715 $self->message( 4, "New document root provided: '$new'." );
369             # unless( substr( $new, 0, 1 ) eq '/' )
370 129 100       3324 unless( File::Spec->file_name_is_absolute( $new ) )
371             {
372 4         39 $new = URI::file->new_abs( $new )->file( $^O );
373             }
374             }
375            
376 618 50       40990 if( $r )
377             {
378 0 0       0 $r->document_root( $new ) if( defined( $new ) );
379 0         0 $r->subprocess_env( DOCUMENT_ROOT => $r->document_root );
380 0         0 return( $r->document_root );
381             }
382             else
383             {
384 618 100       1204 if( defined( $new ) )
385             {
386 129         335 $self->{document_root} = $new;
387 129         627 $self->_set_env( DOCUMENT_ROOT => $self->{document_root} );
388             }
389 618   33     3189 return( $self->{document_root} || $self->env( 'DOCUMENT_ROOT' ) );
390             }
391             }
392              
393             sub document_uri
394             {
395 236     236 1 2857 my $self = shift( @_ );
396 236         533 my $r = $self->apache_request;
397 236         3762 my $new = '';
398 236 100       592 if( @_ )
399             {
400 129         219 $new = shift( @_ );
401 129         643 $self->message( 3, "New document uri provided '$new'." );
402 129         2105 local $URI::ABS_REMOTE_LEADING_DOTS = 1;
403 129 100       674 unless( substr( "$new", 0, 1 ) eq '/' )
404             {
405 13         98 my $base_uri = $self->base_uri;
406 13         108 $self->message( 4, "New document uri '$new' is not absolute. Making it absolute using base uri '", $base_uri->{document_path}, "'." );
407 13         256 $self->message( 4, "Base uri is '", overload::StrVal( $base_uri ), "' ($base_uri)." );
408 13         283 $new = URI->new( $new )->abs( $base_uri->{document_path} );
409             }
410             }
411            
412             ## return( $self->error( "Document URI needs to be an absolute URL path. Value provided was '$new'." ) ) if( length( $new ) && substr( $new, 0, 1 ) ne '/' );
413            
414 236 50       4712 if( $r )
415             {
416             ## We do a lookup unless we are already in a sub request, and we do not want to end up in an infinite loop
417             ## $r = $r->is_initial_req ? $r : $r->main;
418 0 0       0 if( length( "$new" ) )
    0          
    0          
419             {
420 0 0       0 $r = $r->is_initial_req ? $r : $r->main;
421 0         0 my $rr = $self->lookup_uri( "$new" );
422 0 0       0 if( !defined( $rr ) )
423             {
424 0         0 $self->message( 3, "Error occured looking up uri '$path': ", $self->error );
425 0         0 return;
426             }
427 0         0 $self->message( 3, "Resulting uri from lookup_uri is \"", $rr->uri, "\" (", $rr->unparsed_uri, ")." );
428 0         0 $self->apache_request( $rr );
429             ## Remove trailing slash
430 0         0 my $u = $self->_trim_trailing_slash( $rr->unparsed_uri );
431 0         0 $self->{document_uri} = $u;
432 0         0 $self->_set_env( DOCUMENT_URI => $self->{document_uri} );
433 0         0 $self->_set_env( REQUEST_URI => $self->{document_uri} );
434 0 0       0 $self->_set_env( QUERY_STRING => scalar( $rr->args ) ) if( scalar( $rr->args ) );
435 0 0       0 $self->_set_env( PATH_INFO => $rr->path_info ) if( $rr->path_info );
436             }
437             elsif( $self->{_uri_reset} )
438             {
439 0         0 $self->message( 4, "URI has been reset by '$self->{_uri_reset}'" );
440 0   0     0 my $u = URI->new( $r->uri . ( $r->path_info // '' ) );
441 0 0       0 $u->query( scalar( $r->args ) ) if( length( scalar( $r->args ) ) );
442             ## Cannot change the value of $r->unparsed_uri
443 0         0 $r->uri( "$u" );
444 0         0 $self->message( 4, "Document uri has been updated after reset to '$self->{document_uri}'." );
445 0         0 $self->{document_uri} = $u;
446 0         0 $self->{_uri_reset} = 0;
447             }
448             elsif( !length( $self->{document_uri} ) )
449             {
450 0         0 $self->message( 3, "URI not set or reset. Using '", $r->unparsed_uri, "'." );
451 0         0 $self->{document_uri} = $self->new_uri( $r->unparsed_uri );
452 0         0 $self->_set_env( DOCUMENT_URI => $self->{document_uri} );
453 0         0 $self->_set_env( REQUEST_URI => $self->{document_uri} );
454 0 0       0 $self->_set_env( QUERY_STRING => scalar( $r->args ) ) if( scalar( $r->args ) );
455 0 0       0 $self->_set_env( PATH_INFO => $r->path_info ) if( $r->path_info );
456             }
457 0         0 $self->message( 4, "Returning document uri value of '$self->{document_uri}'." );
458 0         0 return( $self->{document_uri} );
459             }
460             else
461             {
462 236 100       712 if( length( "$new" ) )
463             {
464 129         394 $self->{_path_info_processed} = 0;
465             }
466 236 50 66     1252 $self->message( 4, "Returning nothing." ) if( !length( $self->{document_uri} ) && $self->{_path_info_processed} );
467 236 50 66     1442 return( '' ) if( !length( $self->{document_uri} ) && $self->{_path_info_processed} );
468 236   66     1112 my $v = $new || $self->{document_uri};
469 236         1402 $self->message( 3, "New document uri provided is '$new' (possibly null) and document_uri value is: '$self->{document_uri}'" );
470 236 100       4221 if( !$self->{_path_info_processed} )
471             {
472 146         493 $self->message( 4, "Path info from document uri '$v' not processed yet, doing it now." );
473 146         2102 $self->{_path_info_processed}++;
474 146         241 my $res;
475 146 50       409 if( defined( $res = $self->_find_path_info( $v ) ) )
476             {
477 146     0   1183 $self->message( 4, "_find_path_info returned: ", sub{ $self->dump( $res ) });
  0         0  
478 146         3062 $self->{document_uri} = URI->new( $v );
479 146   50     6713 $self->message( 3, "Document uri set to '", ( $self->{document_uri} // '' ), "'" );
480 146   50     3425 $self->message( 4, "Setting document_path to '", ( $res->{path} // '' ), "'" );
481 146         2199 $self->document_path( $res->{path} );
482 146   50     696 $self->message( 4, "Setting filename to '", ( $res->{filepath} // '' ), "'" );
483 146         2393 $self->filename( $res->{filepath} );
484 146   100     888 $self->message( 4, "Setting path_info to '", ( $res->{path_info} // '' ), "'" );
485 146 100       2102 $self->path_info( $res->{path_info} ) if( length( $res->{path_info} ) );
486 146   100     763 $self->message( 4, "Setting query_string to '", ( $res->{query_string} // '' ), "'" );
487 146 100       2184 $self->query_string( $res->{query_string} ) if( length( $res->{query_string} ) );
488 146         430 $self->_set_env( DOCUMENT_URI => $self->{document_uri} );
489 146         359 $self->_set_env( REQUEST_URI => $self->{document_uri} );
490 146         628 $self->message( 4, "Setting code to '$res->{code}'" );
491 146         2588 $self->code( $res->{code} );
492             }
493             else
494             {
495 0         0 $self->message( 3, "_find_path_info returned an error: ", $self->error );
496             }
497             }
498            
499 236 100       775 if( $self->{_uri_reset} )
500             {
501 3         12 $self->message( 4, "URI has been reset by '$self->{_uri_reset}'" );
502 3         42 $self->{_uri_reset} = 0;
503 3   50     8 my $u = URI->new( $self->document_path . ( $self->path_info // '' ) );
504 3 100       173 $u->query( $self->query_string ) if( $self->query_string );
505 3         90 $self->{document_uri} = $u;
506 3         13 $self->message( 4, "Document uri reset to '$self->{document_uri}'" );
507             }
508 236         812 $self->message( 4, "Returning document_uri = '$self->{document_uri}'" );
509 236         4584 return( $self->{document_uri} );
510             }
511             }
512              
513             sub env
514             {
515 147     147 1 302 my $self = shift( @_ );
516             ## The user wants the entire hash reference
517 147 50       370 unless( @_ )
518             {
519 0         0 my $r = $self->apache_request;
520 0 0       0 if( $r )
521             {
522             ## $r = $r->is_initial_req ? $r : $r->main;
523 0         0 return( $r->subprocess_env )
524             }
525             else
526             {
527 0 0       0 unless( scalar( keys( %{$self->{_env}} ) ) )
  0         0  
528             {
529 0         0 $self->{_env} = {%ENV};
530             }
531 0         0 return( $self->{_env} );
532             }
533             }
534 147         266 my $name = shift( @_ );
535 147 50       342 return( $self->error( "No environment variable name was provided." ) ) if( !length( $name ) );
536 147         266 my $opts = {};
537 15     15   169 no warnings 'uninitialized';
  15         902  
  15         22498  
538 147 50 33     922 $opts = pop( @_ ) if( scalar( @_ ) && Scalar::Util::reftype( $_[-1] ) eq 'HASH' );
539             ## 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, '""' ) ) ) );
540 147   33     631 my $r = $opts->{apache_request} || $self->apache_request;
541 147 50       2454 if( $r )
542             {
543             ## $r = $r->is_initial_req ? $r : $r->main;
544 0 0       0 $r->subprocess_env( $name => shift( @_ ) ) if( @_ );
545 0         0 my $v = $r->subprocess_env( $name );
546 0         0 return( $v );
547             }
548             else
549             {
550 147         251 my $env = {};
551 147 100       210 unless( scalar( keys( %{$self->{_env}} ) ) )
  147         647  
552             {
553             ## Make a copy of the environment variables
554 17         1078 $self->{_env} = {%ENV};
555             }
556 147         396 $env = $self->{_env};
557 147 50       326 if( @_ )
558             {
559 147         367 $env->{ $name } = shift( @_ );
560 147         316 my $meth = lc( $name );
561 147 50       750 if( $self->can( $meth ) )
562             {
563 0         0 $self->$meth( $env->{ $name } );
564             }
565             }
566 147         381 return( $env->{ $name } );
567             }
568             }
569              
570             ## This is set by document_uri
571             sub filename
572             {
573 277     277 1 902 my $self = shift( @_ );
574 277         418 my $class = ref( $self );
575 277   100     1559 my $caller = (caller(1))[3] // '';
576             ## my $caller = substr( $sub, rindex( $sub, ':' ) + 1 );
577 277         746 my $r = $self->apache_request;
578 277         4039 my $newfile;
579 277 100       624 if( @_ )
580             {
581 147         216 $newfile = shift( @_ );
582 147 50 33     832 return( $self->error( "New file provided, but it was an empty string." ) ) if( !defined( $newfile ) || !length( $newfile ) );
583             }
584            
585 277 50       485 if( $r )
586             {
587 0 0       0 if( defined( $newfile ) )
    0          
588             {
589 0         0 $self->message( 4, "Setting new file path '$newfile'. Looking up file." );
590 0 0       0 $r = $r->is_initial_req ? $r : $r->main;
591 0         0 my $rr = $r->lookup_file( $newfile );
592 0 0       0 if( $rr->status == Apache2::Const::HTTP_OK )
593             {
594 0         0 $newfile = $rr->filename;
595 0         0 $self->message( 3, "File found and resolved to: '$newfile'." );
596             }
597             else
598             {
599 0         0 $self->message( 3, "File not found. Setting it to: '$newfile' nevertheless." );
600 0         0 $r->filename( $self->collapse_dots( $newfile, { separator => $DIR_SEP }) );
601 0         0 $self->message( 3, "File path is now '", $r->filename, "'." );
602             ## <https://perl.apache.org/docs/2.0/api/Apache2/RequestRec.html#toc_C_filename_>
603 0         0 $r->finfo( APR::Finfo::stat( $newfile, APR::Const::FINFO_NORM, $r->pool ) );
604 0         0 $self->finfo( $newfile );
605             }
606 0         0 $r->subprocess_env( SCRIPT_FILENAME => $newfile );
607             ## Force to create new Apache2::SSI::URI object
608 0         0 $self->{filename} = $newfile;
609 0 0       0 $self->{_uri_reset} = 'filename' unless( $caller eq "${class}\::document_uri" );
610             }
611             elsif( !length( $self->{filename} ) )
612             {
613 0         0 $self->{filename} = $r->filename;
614             }
615             }
616             else
617             {
618 277 100       598 if( defined( $newfile ) )
619             {
620 147         591 $self->message( 3, "New file path provided is: '$newfile'" );
621 147         12035 my $try = Cwd::realpath( $newfile );
622             ## Cwd::realpath would convert
623             ## Z:\perl\Apache2-SSI\t\htdocs\ssi\include.cgi
624             ## into
625             ## Z:/perl/Apache2-SSI/t/htdocs/ssi/include.cgi
626             ## amazingly enough, so to make sure this keeps working on windows related platform, we need to call URI::file
627 147 50       1006 $newfile = URI::file->new( $try )->file( $^O ) if( defined( $try ) );
628 147         35789 $self->message( 3, "Getting the new file real path: '$newfile'" );
629 147 50       3383 unless( File::Spec->file_name_is_absolute( $newfile ) )
630             {
631 0         0 $newfile = URI::file->new_abs( $newfile )->file( $^O );
632 0         0 $self->message( 3, "Made file provided absolute => $newfile" );
633             }
634 147         520 $self->env( SCRIPT_FILENAME => $newfile );
635 147         496 $self->finfo( $newfile );
636             ## Force to create new Apache2::SSI::URI object
637             ## Either a URI object or an URI::file object
638 147         801 $self->{filename} = $self->collapse_dots( $newfile, { separator => $DIR_SEP })->file( $^O );
639 147         19612 $self->message( 3, "After collapsing dots, filename is '$self->{filename}'." );
640             ## Pass the file as new argument to URI::file which will create an object based on the value of the current OS
641             ## and transform it into a path à la linux, which is same as web, which is what we want
642             ## All this is unnecessary for linux type system or those who use / as directory separator,
643             ## but for windows type systems this is necessary
644 147 50       2605 if( CORE::index( $self->{filename}, $self->document_root ) != -1 )
645             {
646 147         523 $self->{document_path} = $self->new_uri( URI::file->new( substr( $self->{filename}, length( $self->document_root ) ) )->file( 'linux' ) );
647             }
648             else
649             {
650 0         0 $self->{document_path} = $self->new_uri( URI::file->new( $self->{filename} )->file( 'linux' ) );
651             }
652 147         916 $self->message( 3, "Document path is set to '$self->{document_path}'" );
653 147 100       3590 $self->{_uri_reset} = 'filename' unless( $caller eq "${class}\::document_uri" );
654             }
655             }
656 277         901 $self->message( 4, "Returning filename '$self->{filename}'" );
657 277         3994 return( $self->{filename} );
658             }
659              
660             ## Alias
661 54     54 0 287 sub filepath { return( shift->filename( @_ ) ); }
662              
663             sub finfo
664             {
665 156     156 1 879 my $self = shift( @_ );
666 156         339 my $r = $self->apache_request;
667 156         2299 my $newfile;
668 156 100       386 if( @_ )
    50          
669             {
670 147         235 $newfile = shift( @_ );
671 147 50 33     755 return( $self->error( "New file path specified but is an empty string." ) ) if( !defined( $newfile ) || !length( $newfile ) );
672             }
673             elsif( !$self->{finfo} )
674             {
675 0         0 $newfile = $self->filename;
676 0 0       0 return( $self->error( "No file path set. This should not happen." ) ) if( !$newfile );
677             }
678            
679 156 100       324 if( defined( $newfile ) )
680             {
681 147         605 $self->message( 3, "No finfo object yet, creating one with file '$newfile'." );
682 147 50       2525 $self->{finfo} = Apache2::SSI::Finfo->new( $newfile, ( $r ? ( apache_request => $r ) : () ), debug => $self->debug );
683 147 50       928 return( $self->pass_error( Apache2::SSI::Finfo->error ) ) if( !$self->{finfo} );
684             }
685 156         373 return( $self->{finfo} );
686             }
687              
688             sub lookup_uri
689             {
690 0     0 0 0 my $self = shift( @_ );
691 0         0 my $uri = '';
692 0 0 0     0 $uri = shift( @_ ) if( @_ && !ref( $_[0] ) && ( scalar( @_ ) % 2 ) );
      0        
693 0         0 my $opts = {};
694 0 0       0 $opts = Scalar::Util::reftype( $_[0] ) eq 'HASH'
    0          
695             ? shift( @_ )
696             : !( scalar( @_ ) % 2 )
697             ? { @_ }
698             : {};
699 0 0       0 $uri = $opts->{uri} if( !length( $uri ) );
700 0 0       0 return( $self->error( "No uri provided." ) ) if( !length( $uri ) );
701 0   0     0 my $r = $opts->{apache_request} || $self->apache_request;
702 0   0     0 my $max_redirects = $opts->{max_redirect} || 10;
703 0         0 my $c = 0;
704 0         0 my $rr = $r->lookup_uri( $uri );
705 0   0     0 while( ++$c <= $max_redirects &&
      0        
706             ( $rr->status == Apache2::Const::HTTP_MOVED_PERMANENTLY ||
707             $rr->status == Apache2::Const::HTTP_MOVED_TEMPORARILY ) )
708             {
709 0         0 $self->message( 3, "Getting next \$r in redirect." );
710 0         0 my $next_r = $rr->next;
711 0 0       0 if( !defined( $next_r ) )
712             {
713 0         0 last;
714             }
715             else
716             {
717 0         0 $self->message( 3, "Resulting status is: ", $next_r->status );
718 0         0 $rr = $next_r;
719             }
720             }
721 0         0 $self->message( 3, "Resulting Apache2::RequestRec is '$rr' with status '", $rr->status, "'." );
722 0 0 0     0 if( defined( $rr ) &&
      0        
723             ( $rr->status == Apache2::Const::HTTP_MOVED_PERMANENTLY ||
724             $rr->status == Apache2::Const::HTTP_MOVED_TEMPORARILY ) )
725             {
726 0         0 my $hdrs = $rr->headers_out;
727 0     0   0 $self->message( 3, "Redirect headers are: ", sub{ $self->dump( $hdrs ) });
  0         0  
728             ## Weird, should not happen, but just in case
729 0 0 0     0 if( !exists( $hdrs->{Location} ) || !length( $hdrs->{Location} ) )
730             {
731 0         0 $self->message( 3, "Could not find any 'Location' header." );
732 0         0 return( $rr );
733             }
734            
735 0         0 try
736 0     0   0 {
737             ## No, we cannot use $rr->uri. This would give us the initial requested uri, not the redirected uri
738 0         0 my $u = URI->new( $hdrs->{Location} );
739 0         0 $uri = $u->path;
740 0         0 $self->message( 3, "Found uri \"$uri\" from Location header field." );
741 0 0       0 if( ++$self->{_lookup_looping} > 1 )
742             {
743 0         0 $self->message( 3, "Lookup is looping, return current \$r '$rr'." );
744 0         0 return( $rr );
745             }
746             else
747             {
748 0         0 delete( $self->{_lookup_looping} );
749 0         0 my $new_r = $self->lookup_uri( $uri );
750 0         0 $self->message( 3, "Returning new \$r '$new_r' with status '", $new_r->status, "' and uri '", $new_r->uri, "' and filename '", $new_r->filename, "'" );
751 0         0 return( $new_r );
752             }
753             }
754 0 0       0 catch( $e )
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
755 0     0   0 {
756 0         0 $self->message( 3, "An error occurred while creating URI object for \"$hdrs->{Location}\": $e" );
757 0         0 $self->error( "An error occurred while creating URI object for \"$hdrs->{Location}\": $e" );
758 0         0 return( $rr );
759 0 0 0     0 }
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
760             }
761 0         0 return( $rr );
762             }
763              
764             sub make
765             {
766 2     2 0 9 my $self = shift( @_ );
767 2 50       37 return( $self->error( "Must be called with an existing object and not as ", __PACKAGE__, "->make()" ) ) if( !Scalar::Util::blessed( $self ) );
768 2         12 my $p = {};
769 2 50 33     19 @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) );
770 2 50       25 if( scalar( @_ ) )
771             {
772 15     15   129 no warnings 'uninitialized';
  15         30  
  15         25784  
773 2 50       30 $p = Scalar::Util::reftype( $_[0] ) eq 'HASH'
    50          
774             ? shift( @_ )
775             : !( scalar( @_ ) % 2 )
776             ? { @_ }
777             : {};
778             }
779 2         11 my $r = $self->apache_request;
780 2         45 my $d = $self->document_root;
781 2         19 my $b = $self->base_uri;
782 2         20 my $f = $self->document_uri;
783 2 50 33     31 $p->{apache_request} = $r if( !$p->{apache_request} && $r );
784 2 50 33     26 $p->{document_root} = "$d" if( !$p->{document_root} && length( $d ) );
785 2 50 33     24 $p->{base_uri} = "$b" if( !$p->{base_uri} && length( $b ) );
786 2 50       17 $p->{document_uri} = "$f" if( !$p->{document_uri} );
787 2 50       34 $p->{debug} = $self->debug if( !length( $p->{debug} ) );
788 2     0   67 $self->message( 4, "Creating new file object with parameters: ", sub{ $self->dump( $p ) });
  0         0  
789 2         49 return( $self->new( $p ) );
790             }
791              
792             sub new_uri
793             {
794 338     338 1 25638 my $self = shift( @_ );
795 338         572 my $class = URI_CLASS;
796 338         517 my $uri = shift( @_ );
797 338         460 try
798 338     338   394 {
799 338         991 return( $class->new( $uri ) );
800             }
801 338 100       1445 catch( $e )
  0 50       0  
  338 50       844  
  338 0       463  
  338 50       579  
  338         433  
  338         425  
  338         546  
  338         916  
  15         51  
  323         548  
  0         0  
  338         14630  
  338         577  
  338         642  
  338         760  
  0         0  
  0         0  
  0         0  
  0         0  
802 0     0   0 {
803 0         0 return( $self->error( "Unable to instantiate an URI object with \"$uri\": $e" ) );
804 0 0 33     0 }
  0 0 33     0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
  338         5061  
  338         2417  
805             }
806              
807             sub parent
808             {
809 2     2 1 10 my $self = shift( @_ );
810 2         12 my $path = $self->document_path;
811 2         19 my $r = $self->apache_request;
812             ## I deliberately did not do split( '/', $path, -1 ) so that if there is a trailing '/', it will not be counted
813 2         56 $self->message( 4, "Document path value is '$path' (", overload::StrVal( $path ), ")." );
814 2         78 my @segments = $self->document_path->path_segments;
815 2     0   131 $self->message( 4, "Path segments are: ", sub{ $self->dump( \@segments )} );
  0         0  
816 2         42 pop( @segments );
817 2 50       11 return( $self ) if( !scalar( @segments ) );
818 2         21 $self->message( 4, "Creating new object with document uri '", join( '/', @segments ), "'." );
819 2         56 return( $self->make( document_uri => join( '/', @segments ) ) );
820             }
821              
822             sub path_info
823             {
824 10     10 1 340 my $self = shift( @_ );
825 10         23 my $class = ref( $self );
826 10   100     63 my $caller = (caller(1))[3] // '';
827             ## my $caller = substr( $sub, rindex( $sub, ':' ) + 1 );
828 10         35 my $r = $self->apache_request;
829 10 50       170 if( $r )
830             {
831 0 0       0 if( @_ )
832             {
833 0         0 $self->message( 3, "Setting path info to '", $_[0], "'." );
834 0         0 $r->path_info( shift( @_ ) );
835 0         0 $self->message( 4, "Path info updated with '", $r->path_info, "'." );
836 0         0 $self->_set_env( PATH_INFO => $r->path_info );
837 0 0       0 $self->{_uri_reset} = 'path_info' unless( $caller eq "${class}\::document_uri" );
838             }
839 0         0 $self->message( 4, "Returning path info '", $r->path_info, "'." );
840 0         0 return( $r->path_info );
841             }
842             else
843             {
844 10 100       29 if( @_ )
845             {
846 4         21 $self->message( 3, "Setting path info to '", $_[0], "'." );
847 4         65 $self->{path_info} = shift( @_ );
848 4         20 $self->message( 4, "Path info updated with '", $self->{path_info}, "'." );
849 4         65 $self->_set_env( PATH_INFO => $self->{path_info} );
850 4 100       21 $self->{_uri_reset} = 'path_info' unless( $caller eq "${class}\::document_uri" );
851             }
852 10         38 return( $self->{path_info} );
853             }
854             }
855              
856             sub query_string
857             {
858 34     34 1 797 my $self = shift( @_ );
859 34         73 my $class = ref( $self );
860 34   100     205 my $caller = (caller(1))[3] // '';
861             ## my $caller = substr( $sub, rindex( $sub, ':' ) + 1 );
862 34         96 my $r = $self->apache_request;
863 34 50       530 if( $r )
864             {
865 0 0       0 if( @_ )
866             {
867 0         0 my $qs = shift( @_ );
868 0         0 $self->message( 3, "Setting query string to '$qs'." );
869 0         0 $r->args( $qs );
870 0         0 $self->message( 4, "Query string is now '", scalar( $r->args ), "'." );
871 0         0 $self->_set_env( QUERY_STRING => $qs );
872 0 0       0 $self->{_uri_reset} = 'query_string' unless( $caller eq "${class}\::document_uri" );
873             }
874 0         0 return( $r->args );
875             }
876             else
877             {
878 34 100       102 if( @_ )
879             {
880 22         70 $self->message( 3, "Setting query string to '", $_[0], "'." );
881 22         303 $self->{query_string} = shift( @_ );
882 22         84 $self->message( 4, "Query string is now '", $self->{query_string}, "'." );
883 22         325 $self->_set_env( QUERY_STRING => $self->{query_string} );
884 22 100       86 $self->{_uri_reset} = 'query_string' unless( $caller eq "${class}\::document_uri" );
885             }
886 34         91 return( $self->{query_string} );
887             }
888             }
889              
890             sub root
891             {
892 30     30 1 77 my $self = shift( @_ );
893 30 100       188 return( $self->{root} ) if( $self->{root} );
894 15         76 my $hash =
895             {
896             code => 200,
897             document_uri => $self->new_uri( '/' ),
898             document_root => $self->document_root,
899             debug => $self->debug,
900             path_info => '',
901             query_string => '',
902             _path_info_processed => 1,
903             };
904 15         560 $hash->{document_path} = $hash->{document_uri};
905 15 50       51 $hash->{apache_request} = $self->apache_request if( $self->apache_request );
906 15         300 my $root = bless( $hash => ref( $self ) );
907             # Scalar::Util::weaken( $copy );
908 15         79 $root->{base_dir} = $root;
909 15         58 $root->{base_uri} = $root;
910 15         75 $self->{root} = $root;
911 15         74 return( $root );
912             }
913              
914             # shortcut
915 1     1 0 435 sub uri { return( shift->document_uri( @_ ) ); }
916              
917             ## Path info works as a path added to a document uri, such as:
918             ## /my/doc.html/path/info
919             ## But we need to distinguish with missing document hierarchy inside a directory, such as:
920             ## /my/folder/missing_doc.html/path/info
921             ## otherwise we would be treating /missing_doc.html/path/info as a path info
922             sub _find_path_info
923             {
924 161     161   527 my $self = shift( @_ );
925 161         347 my( $uri_path, $doc_root ) = @_;
926 161   33     754 $doc_root //= $self->document_root;
927 161         357 my $qs = '';
928 161         310 my $sep = $DIR_SEP;
929 161 50       451 $sep = '/' if( !length( $sep ) );
930 161 50 66     785 if( Scalar::Util::blessed( $uri_path ) && $uri_path->isa( 'URI::file' ) )
931             {
932 0         0 $uri_path = $uri_path->file;
933             }
934 161         791 my $u = $self->collapse_dots( $uri_path );
935 161         712 $qs = $u->query;
936 161         2018 $uri_path = $u->path;
937             ## Pass the OS to ensure we get ./ss/include.cgi becomes .\ssi\include.cgi
938 161         1918 my $path = URI::file->new( $uri_path )->file( $^O );
939 161 50 33     31785 $doc_root = $doc_root->file( $^O ) if( Scalar::Util::blessed( $doc_root ) && $doc_root->isa( 'URI::file' ) );
940 161 50       628 $doc_root = substr( $doc_root, 0, length( $doc_root ) - length( $sep ) ) if( substr( $doc_root, -length( $sep ), length( $sep ) ) eq $sep );
941 161         928 $self->message( 4, "Document root is '$doc_root', uri path '$uri_path' and file path is '$path'" );
942 161 50       3062 return( $self->error( "URI path must be an absolute path starting with '/'. Path provided was \"$uri_path\"." ) ) if( substr( $uri_path, 0, 1 ) ne '/' );
943             ## No need to go further
944 161 100       4264 if( -e( "${doc_root}${path}" ) )
    50          
945             {
946             return({
947 150         1716 filepath => "${doc_root}${path}",
948             path => $uri_path,
949             query_string => $qs,
950             code => 200,
951             });
952             }
953             elsif( $uri_path eq '/' )
954             {
955             return({
956 0 0       0 filepath => $doc_root,
957             path => $uri_path,
958             path_info => undef(),
959             query_string => $qs,
960             code => ( -e( $doc_root ) ? 200 : 404 ),
961             });
962             }
963 11         87 my @parts = split( '/', substr( $uri_path, 1 ) );
964 11     0   170 $self->message( 4, "Document root is '$doc_root' and parts contains: ", sub{ $self->dump( \@parts ) } );
  0         0  
965 11         256 my $trypath = '';
966 11         43 my $trypath_uri = '';
967 11         45 my $pathinfo = '';
968 11         48 foreach my $p ( @parts )
969             {
970 20 50       217 $self->message( 4, "Checking path '${trypath_uri}/${p}'", ( $sep ne '/' ? " (${trypath}${sep}${p})" : '' ) ) unless( $pathinfo );
    50          
971             ## The last path was a directory, and we cannot find the element within. So, the rest of the path is not path info, but rather a 404 missing document hierarchy
972             ## We test the $pathinfo string, so we do not bother checking further if it is already set.
973 20 100 66     1136 if( !$pathinfo && -d( "${doc_root}${trypath}" ) && !-e( "${doc_root}${trypath}/${p}" ) )
    100 100        
      66        
974             {
975 7         109 $self->message( 4, "Document $p is not found inside directory ${doc_root}${trypath}" );
976             ## We return the original path provided (minus any query string)
977             return({
978 7 100       307 filepath => $doc_root . ( length( $trypath ) ? $trypath : $path ),
979             path => $uri_path,
980             code => 404,
981             query_string => $qs,
982             });
983             }
984             elsif( !$pathinfo && -e( "${doc_root}${trypath}/${p}" ) )
985             {
986 9         60 $self->message( 4, "ok, path ${trypath}/${p} exists." );
987 9         171 $trypath_uri .= "/${p}";
988 9         20 $trypath .= "${sep}${p}";
989             }
990             else
991             {
992 4 50       30 $self->message( 4, "nope, this path $trypath does not exist." ) if( !$pathinfo );
993 4         73 $pathinfo .= "/$p";
994 4         16 $self->message( 4, "Path info is now: '$pathinfo'." );
995             }
996             }
997 4         67 $self->message( 4, "Real path: $trypath, path info: $pathinfo" );
998             return({
999 4         106 filepath => "${doc_root}${trypath}",
1000             path => $trypath_uri,
1001             path_info => $pathinfo,
1002             code => 200,
1003             query_string => $qs,
1004             });
1005             }
1006              
1007             # *_set_env = \&Apache2::SSI::_set_env;
1008             ## This is different from the env() method. This one is obviously private
1009             ## whereas the env() one has triggers that could otherwise create an infinite loop.
1010             sub _set_env
1011             {
1012 447     447   670 my $self = shift( @_ );
1013 447         721 my $name = shift( @_ );
1014 447 50       971 return( $self->error( "No environment variable name provided." ) ) if( !length( $name ) );
1015 447 100       1231 $self->{_env} = {} if( !ref( $self->{_env} ) );
1016 447         625 my $env = $self->{_env};
1017 447         868 my $r = $self->apache_request;
1018 447 50       6751 if( @_ )
1019             {
1020 447         622 my $v = shift( @_ );
1021 447 50       837 $r->subprocess_env( $name => $v ) if( $r );
1022 447         1075 $env->{ $name } = $v;
1023             }
1024 447         608 return( $self );
1025             }
1026              
1027             sub _trim_trailing_slash
1028             {
1029 0     0     my $self = shift( @_ );
1030 0           my $uri = shift( @_ );
1031 0 0         return( $self->error( "No uri provided to trim trailing slash." ) ) if( !length( "$uri" ) );
1032 0 0 0       unless( Scalar::Util::blessed( $uri ) && $uri->isa( 'URI' ) )
1033             {
1034 0           $uri = $self->new_uri( "$uri" );
1035             }
1036 0 0 0       if( substr( $uri->path, -1, 1 ) eq '/' && length( $uri->path ) > 1 )
1037             {
1038             ## By splitting the string on '/' and without the last argument for split being -1, perl will remove trailing blank entries
1039 0           $uri->path( join( '/', split( '/', $uri->path ) ) );
1040             }
1041 0           $self->message( 3, "Returning uri object '", overload::StrVal( $uri ), "' with value '$uri'." );
1042 0           return( $uri );
1043             }
1044              
1045             1;
1046              
1047             __END__
1048              
1049             =encoding utf-8
1050              
1051             =head1 NAME
1052              
1053             Apache2::SSI::URI - Apache2 Server Side Include URI Object Class
1054              
1055             =head1 SYNOPSIS
1056              
1057             # if the global option PerlOptions +GlobalRequest is set in your VirtualHost
1058             my $r = Apache2::RequestUtil->request
1059             my $uri = Apache2::SSI::URI->new(
1060             apache_request => $r,
1061             document_uri => '/some/uri/file.html',
1062             document_root => '/home/john/www',
1063             base_uri => '/',
1064             ) || die( "Unable to create an Apache2::SSI::URI object: ", Apache2::SSI::URI->error );
1065              
1066             unless( $uri->code == Apache2::Const::HTTP_OK )
1067             {
1068             die( "Sorry, the uri does not exist.\n" );
1069             }
1070             print( $uri->slurp_utf8 );
1071              
1072             # Changing the base uri, which is used to resolve relative uri
1073             $uri->base_uri( '/ssi' );
1074              
1075             my $uri2 = $uri->clone;
1076             $uri2->filename( '/home/john/some-file.txt' );
1077             die( "No such file\n" ) if( $uri2->finfo->filetype == Apache2::SSI::Finfo::FILETYPE_NOFILE );
1078              
1079             my $dir = $uri->document_directory;
1080              
1081             # Full path to the filename, e.g. /home/john/www/some/dir/file.html
1082             # Possible dots are resolved /home/john/www/some/dir/../ssi/../dir/./file.html => /home/john/www/some/dir/file.html
1083             my $filename = $uri->document_filename;
1084              
1085             # The uri without path info or query string
1086             my $path = $uri->document_path;
1087              
1088             my $doc_root = $uri->document_root;
1089            
1090             # The document uri including path info, and query string if any
1091             my $u = $uri->document_uri;
1092              
1093             my $req_uri = $uri->env( 'REQUEST_URI' );
1094              
1095             # Access to the Apache2::SSI::Finfo object
1096             my $finfo = $uri->finfo;
1097              
1098             # A new Apache2::SSI::URI object
1099             my $uri3 = $uri->new_uri( document_uri => '/some/where/about.html', document_root => '/home/john/www' );
1100              
1101             # Returns /some/uri
1102             my $parent = $uri->parent;
1103              
1104             # The uri is now /some/uri/file.html/some/path
1105             $uri->path_info( '/some/path' );
1106              
1107             # The uri is now /some/uri/file.html/some/path?q=something&l=ja_JP
1108             $uri->query_string( 'q=something&l=ja_JP' );
1109              
1110             my $html = $uri->slurp_utf8;
1111             my $raw = $uri->slurp({ binmode => ':raw' });
1112              
1113             # Same as $uri->document_uri
1114             my $uri = $uri->uri;
1115              
1116             =head1 VERSION
1117              
1118             v0.1.1
1119              
1120             =head1 DESCRIPTION
1121              
1122             L<Apache2::SSI::URI> is used to manipulate and query http uri. It is used by L<Apache2::SSI> both for the main query, and also for sub queries like when there is an C<include> directive.
1123              
1124             In this case, there would be the main document uri such as C</some/path/file.html> and containing a directive such as:
1125              
1126             <!--#include virtual="../other.html" -->
1127              
1128             An L<Apache2::SSI::URI> object would be instantiated to process the uri C<../other.html>, flatten the dots and get its underlying filename.
1129              
1130             Even if the uri provided does not exist, am L<Apache2::SSI::URI> object would still be returned, so you need to check if the file exists by doing:
1131              
1132             if( $uri->code == 404 )
1133             {
1134             die( "Not there\n" );
1135             }
1136              
1137             Or, this would work too:
1138              
1139             if( $uri->finfo->filetype == Apache2::SSI::Finfo::FILETYPE_NOFILE )
1140             {
1141             die( "No such file !\n" );
1142             }
1143              
1144             =head1 METHODS
1145              
1146             =head2 new
1147              
1148             This instantiate an object that is used to access other key methods. It takes the following parameters:
1149              
1150             =over 4
1151              
1152             =item I<apache_request>
1153              
1154             This is the L<Apache2::RequestRec> object that is provided if running under mod_perl.
1155              
1156             it can be retrieved from L<Apache2::RequestUtil/request> or via L<Apache2::Filter/r>
1157              
1158             You can get this L<Apache2::RequestRec> object by requiring L<Apache2::RequestUtil> and calling its class method L<Apache2::RequestUtil/request> such as C<Apache2::RequestUtil->request> and assuming you have set C<PerlOptions +GlobalRequest> in your Apache Virtual Host configuration.
1159              
1160             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:
1161              
1162             use Apache2::RequestUtil (); # extends Apache2::RequestRec objects
1163             my $r = $r->is_initial_req ? $r : $r->main;
1164              
1165             =item I<base_uri>
1166              
1167             This is the base uri which is used to make uri absolute.
1168              
1169             For example, if the main document uri is C</some/folder/file.html> containing a directive:
1170              
1171             <!--#include virtual="../other.html" -->
1172              
1173             One would instantiate an object using C</some/folder/file.html> as the base_uri like this:
1174              
1175             my $uri = Apache2::SSI::URI->new(
1176             base_uri => '/some/folder/file.html',
1177             apache_request => $r,
1178             document_uri => '../other.html',
1179             # No need to specify document_root, because it will be derived from
1180             # the Apache2::RequestRec provided with the apache_request parameter.
1181             );
1182              
1183             =item I<document_root>
1184              
1185             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>.
1186              
1187             =item I<document_uri>
1188              
1189             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>
1190              
1191             =back
1192              
1193             =head2 apache_request
1194              
1195             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.
1196              
1197             When running under Apache mod_perl this is set automatically from the special L</handler> method, such as:
1198              
1199             my $r = $f->r; # $f is the Apache2::Filter object provided by Apache
1200              
1201             =head2 base_uri
1202              
1203             Sets or gets the base reference uri. This is used to render the L</document_uri> provided an absolute uri.
1204              
1205             =head2 clone
1206              
1207             Create a clone of the object and return it.
1208              
1209             =head2 code
1210              
1211             Sets or gets the http code for this uri.
1212              
1213             $uri->code( 404 );
1214              
1215             =head2 collapse_dots
1216              
1217             Provided with an uri, and this will resolve the path and removing the dots, such as C<.> and C<..> and return an L<URI> object.
1218              
1219             This is done as per the L<RFC 3986 section 5.2.4 algorithm|https://tools.ietf.org/html/rfc3986#page-33>
1220              
1221             my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html' );
1222             # would become /a/c/d.html
1223             my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html?foo=../bar' );
1224             # would become /a/c/d.html?foo=../bar
1225             $uri->query # foo=../bar
1226              
1227             =head2 document_directory
1228              
1229             Returns an L<Apache2::SSI::URI> object of the current directory of the L</document_uri> provided.
1230              
1231             This can also be called as C<$uri->document_dir>
1232              
1233             =head2 document_filename
1234              
1235             This is an alias for L<Apache2::SSI::URI/filename>
1236              
1237             =head2 document_path
1238              
1239             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>.
1240              
1241             =head2 document_root
1242              
1243             Sets or gets the document root.
1244              
1245             Wen running under Apache mod_perl, this value will be available automatically, using L<Apache2::RequestRec/document_root> method.
1246              
1247             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>.
1248              
1249             =head2 document_uri
1250              
1251             Sets or gets the document uri, which is the uri of the document being processed.
1252              
1253             For example:
1254              
1255             /index.html
1256              
1257             Under Apache, this will get the environment variable C<DOCUMENT_URI> or calls the L<Apache2::RequestRec/uri> method.
1258              
1259             Outside of Apache, this will rely on a value being provided upon instantiating an object, or the environment variable C<DOCUMENT_URI> be present.
1260              
1261             The value should be an absolute uri.
1262              
1263             =head2 env
1264              
1265             Sets or gets environment variables that are distinct for this uri.
1266              
1267             $uri->env( REQUEST_URI => '/some/path/file.html' );
1268             my $loc = $uri->env( 'REQUEST_URI' );
1269              
1270             If it is called without any parameters, it returns all the environment variables as a hash reference:
1271              
1272             my $all_env = $uri->env;
1273             print $all_env->{REQUEST_URI};
1274              
1275             Setting an environment variable using L</env> does not actually populate it. So this would not work:
1276              
1277             $uri->env( REQUEST_URI => '/some/path/file.html' );
1278             print( $ENV{REQUEST_URI};
1279              
1280             It is the equivalent of L<Apache2::RequestRec/subprocess_env>. Actually it uses L<Apache2::RequestRec/subprocess_env> if running under Apache/mod_perl, other wise it uses a private hash reference to store the values.
1281              
1282             =head2 filename
1283              
1284             This returns the system file path to the document uri as a string.
1285              
1286             =head2 finfo
1287              
1288             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 an identical interface otherwise. See L<Apache2::SSI::Finfo> for more information.
1289              
1290             =head2 new_uri
1291              
1292             A short-hand for C<Apache2::SSI::URI->new>
1293              
1294             =head2 parent
1295              
1296             Returns the parent of the document uri, or if there is no parent, it returns the current object itself.
1297              
1298             my $up = $uri->parent;
1299             # would return /some/path assuming the document uri was /some/path/file.html
1300              
1301             =head2 path_info
1302              
1303             Sets or gets the path info for the current uri.
1304              
1305             Example:
1306              
1307             my $string = $ssi->path_info;
1308             $ssi->path_info( '/my/path/info' );
1309              
1310             The path info value is also set automatically when L</document_uri> is called, such as:
1311              
1312             $ssi->document_uri( '/some/path/to/file.html/my/path/info?q=something&l=ja_JP' );
1313              
1314             This will also set automatically the C<PATH_INFO> environment variable.
1315              
1316             =head2 query_string
1317              
1318             Set or gets the query string for the current uri.
1319              
1320             Example:
1321              
1322             my $string = $ssi->query_string;
1323             $ssi->query_string( 'q=something&l=ja_JP' );
1324              
1325             or, using the L<URI> module:
1326              
1327             $ssi->query_string( $uri->query );
1328              
1329             The query string value is set automatically when you provide an L<document_uri> upon instantiation or after:
1330              
1331             $ssi->document_uri( '/some/path/to/file.html?q=something&l=ja_JP' );
1332              
1333             This will also set automatically the C<QUERY_STRING> environment variable.
1334              
1335             =head2 root
1336              
1337             Returns an object representation of the root uri, i.e. C</>
1338              
1339             =head2 slurp
1340              
1341             It returns the content of the L</filename>
1342              
1343             it takes an hash reference of parameters:
1344              
1345             =over 4
1346              
1347             =item I<binmode>
1348              
1349             my $content = $uri->slurp({ binmode => ':utf-8' });
1350              
1351             =back
1352              
1353             It will return undef and sets an L<Module::Generic/error> if there is no L</filename> value set or if the file cannot be opened.
1354              
1355             =head2 slurp_utf8
1356              
1357             It returns the content of the file L</filename> utf-8 decoded.
1358              
1359             This is equivalent to:
1360              
1361             my $content = $uri->slurp({ binmode => ':utf8' });
1362              
1363             C<:utf8> is slightly a bit more lax than C<:utf-8>, so it you want strict utf8, you can do:
1364              
1365             my $content = $uri->slurp({ binmode => ':utf-8' });
1366              
1367             =head1 AUTHOR
1368              
1369             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1370              
1371             CPAN ID: jdeguest
1372              
1373             L<https://git.deguest.jp/jack/Apache2-SSI>
1374              
1375             =head1 SEE ALSO
1376              
1377             L<Apache2::SSI::File>, L<Apache2::SSI::Finfo>, L<Apache2::SSI>
1378              
1379             mod_include, mod_perl(3), L<APR::URI>, L<URI>
1380             L<https://httpd.apache.org/docs/current/en/mod/mod_include.html>,
1381             L<https://httpd.apache.org/docs/current/en/howto/ssi.html>,
1382             L<https://httpd.apache.org/docs/current/en/expr.html>
1383             L<https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_>
1384              
1385             =head1 COPYRIGHT & LICENSE
1386              
1387             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
1388              
1389             You can use, copy, modify and redistribute this package and associated
1390             files under the same terms as Perl itself.
1391              
1392             =cut