File Coverage

blib/lib/Apache2/SSI/URI.pm
Criterion Covered Total %
statement 365 618 59.0
branch 132 326 40.4
condition 50 136 36.7
subroutine 39 53 73.5
pod 18 24 75.0
total 604 1157 52.2


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