File Coverage

blib/lib/Apache2/SSI/Finfo.pm
Criterion Covered Total %
statement 282 387 72.8
branch 49 138 35.5
condition 7 31 22.5
subroutine 79 88 89.7
pod 38 40 95.0
total 455 684 66.5


line stmt bran cond sub pod time code
1             ## <https://perl.apache.org/docs/2.0/api/APR/Finfo.html>
2             ##----------------------------------------------------------------------------
3             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI/Finfo.pm
4             ## Version v0.1.0
5             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
6             ## Author: Jacques Deguest <jack@deguest.jp>
7             ## Created 2020/12/18
8             ## Modified 2021/01/13
9             ## All rights reserved
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package Apache2::SSI::Finfo;
15             BEGIN
16             {
17 17     17   106623 use strict;
  17         42  
  17         671  
18 17     17   88 use warnings;
  17         33  
  17         522  
19 17     17   94 use warnings::register;
  17         26  
  17         2603  
20 17     17   554 use parent qw( Module::Generic );
  17         320  
  17         112  
21 17     17   18068522 use Exporter qw( import );
  17         43  
  17         573  
22 17     17   15493 use DateTime;
  17         7533201  
  17         925  
23 17     17   12186 use DateTime::Format::Strptime;
  17         914798  
  17         119  
24 17     17   1611 use File::Basename ();
  17         36  
  17         363  
25 17     17   88 use Nice::Try;
  17         29  
  17         208  
26 17     17   55 our( $AUTOLOAD );
27             use overload (
28 25     25   335 q{""} => sub { $_[0]->{filepath} },
29             bool => sub () { 1 },
30 17         261 fallback => 1,
31 17     17   14302003 );
  17         47  
32 17 50       209 if( exists( $ENV{MOD_PERL} ) )
33             {
34 0         0 require APR::Pool;
35 0         0 require APR::Finfo;
36 0         0 require APR::Const;
37 0         0 APR::Const->import( -compile => qw( :filetype FINFO_NORM ) );
38             }
39 17     17   2984 use constant FINFO_DEV => 0;
  17         37  
  17         1620  
40 17     17   104 use constant FINFO_INODE => 1;
  17         35  
  17         788  
41 17     17   95 use constant FINFO_MODE => 2;
  17         33  
  17         709  
42 17     17   89 use constant FINFO_NLINK => 3;
  17         32  
  17         692  
43 17     17   87 use constant FINFO_UID => 4;
  17         34  
  17         753  
44 17     17   96 use constant FINFO_GID => 5;
  17         27  
  17         781  
45 17     17   100 use constant FINFO_RDEV => 6;
  17         28  
  17         730  
46 17     17   89 use constant FINFO_SIZE => 7;
  17         28  
  17         682  
47 17     17   95 use constant FINFO_ATIME => 8;
  17         42  
  17         671  
48 17     17   92 use constant FINFO_MTIME => 9;
  17         30  
  17         655  
49 17     17   93 use constant FINFO_CTIME => 10;
  17         35  
  17         667  
50 17     17   85 use constant FINFO_BLOCK_SIZE => 11;
  17         32  
  17         705  
51 17     17   91 use constant FINFO_BLOCKS => 12;
  17         26  
  17         688  
52             ## Sames constant value as in APR::Const
53             ## the file type is undetermined.
54 17     17   87 use constant FILETYPE_NOFILE => 0;
  17         31  
  17         685  
55             ## a file is a regular file.
56 17     17   92 use constant FILETYPE_REG => 1;
  17         30  
  17         774  
57             ## a file is a directory
58 17     17   93 use constant FILETYPE_DIR => 2;
  17         40  
  17         707  
59             ## a file is a character device
60 17     17   99 use constant FILETYPE_CHR => 3;
  17         34  
  17         703  
61             ## a file is a block device
62 17     17   89 use constant FILETYPE_BLK => 4;
  17         31  
  17         712  
63             ## a file is a FIFO or a pipe.
64 17     17   88 use constant FILETYPE_PIPE => 5;
  17         32  
  17         686  
65             ## a file is a symbolic link
66 17     17   90 use constant FILETYPE_LNK => 6;
  17         35  
  17         662  
67             ## a file is a [unix domain] socket.
68 17     17   88 use constant FILETYPE_SOCK => 7;
  17         27  
  17         724  
69             ## a file is of some other unknown type or the type cannot be determined.
70 17     17   89 use constant FILETYPE_UNKFILE => 127;
  17         43  
  17         1737  
71 17         106 our %EXPORT_TAGS = ( all => [qw( FILETYPE_NOFILE FILETYPE_REG FILETYPE_DIR FILETYPE_CHR FILETYPE_BLK FILETYPE_PIPE FILETYPE_LNK FILETYPE_SOCK FILETYPE_UNKFILE )] );
72 17         59 our @EXPORT_OK = qw( FILETYPE_NOFILE FILETYPE_REG FILETYPE_DIR FILETYPE_CHR FILETYPE_BLK FILETYPE_PIPE FILETYPE_LNK FILETYPE_SOCK FILETYPE_UNKFILE );
73 17         41584 our $VERSION = 'v0.1.0';
74             };
75              
76             sub init
77             {
78 160     160 1 9112 my $self = shift( @_ );
79 160   50     695 my $file = shift( @_ ) || return( $self->error( "No file provided to instantiate a ", ref( $self ), " object." ) );
80             ## return( $self->error( "File or directory \"$file\" does not exist." ) ) if( !-e( $file ) );
81 160         1594 $self->{apache_request} = '';
82 160         511 $self->{apr_finfo} = '';
83 160         446 $self->{_init_strict_use_sub} = 1;
84 160         1369 $self->SUPER::init( @_ );
85 160         16745 $self->{filepath} = $file;
86 160         866 $self->{_data} = [];
87 160         449 my $r = $self->{apache_request};
88 160 50       482 if( $r )
89             {
90             ## <https://perl.apache.org/docs/2.0/api/Apache2/RequestRec.html#toc_C_filename_>
91 0         0 try
92 0     0   0 {
93 0         0 my $finfo;
94 0 0       0 if( $r->filename eq $file )
95             {
96 0         0 $finfo = $r->finfo;
97             }
98             else
99             {
100 0         0 $finfo = APR::Finfo::stat( $file, APR::Const::FINFO_NORM, $r->pool );
101 0         0 $r->finfo( $finfo );
102             }
103 0         0 $self->{apr_finfo} = $finfo;
104             }
105 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  
106 0     0   0 {
107             ## This makes it possible to query this api even if provided with a non-existing file
108 0 0       0 if( $e =~ /No[[:blank:]\h]+such[[:blank:]\h]+file[[:blank:]\h]+or[[:blank:]\h]+directory/i )
109             {
110 0         0 $self->{_data} = [];
111             }
112             else
113             {
114 0         0 return( $self->error( "Unable to set the APR::Finfo object: $e" ) );
115             }
116 0 0 0     0 }
  0 0 0     0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
117             }
118             else
119             {
120 160         4231 $self->{_data} = [CORE::stat( $file )];
121             }
122 160         1117 return( $self );
123             }
124              
125 5     5 1 41 sub apache_request { return( shift->_set_get_object_without_init( 'apache_request', 'Apache2::RequestRec', @_ ) ); }
126              
127 49     49 1 353 sub apr_finfo { return( shift->_set_get_object( 'apr_finfo', 'APR::Finfo', @_ ) ); }
128              
129             sub atime
130             {
131 1     1 1 3 my $self = shift( @_ );
132 1         2 my $f = $self->apr_finfo;
133 1         82 my $t;
134 1 50       7 if( $f )
135             {
136 0         0 $t = $f->atime;
137             }
138             else
139             {
140 1         2 my $data = $self->{_data};
141 1 50       5 return( '' ) if( !scalar( @$data ) );
142 1         3 $t = $data->[ FINFO_ATIME ];
143             }
144 1         5 return( $self->_datetime( $t ) );
145             }
146              
147 0     0 1 0 sub blksize { return( shift->block_size( @_ ) ); }
148              
149             sub block_size
150             {
151 0     0 0 0 my $self = shift( @_ );
152 0         0 my $f = $self->apr_finfo;
153 0 0       0 if( $f )
154             {
155 0         0 return( ( CORE::stat( $self->{filepath} ) )[ FINFO_BLOCK_SIZE ] );
156             }
157             else
158             {
159 0         0 my $data = $self->{_data};
160 0 0       0 return( '' ) if( !scalar( @$data ) );
161 0         0 return( $data->[ FINFO_BLOCK_SIZE ] );
162             }
163             }
164              
165             sub blocks
166             {
167 0     0 1 0 my $self = shift( @_ );
168 0         0 my $f = $self->apr_finfo;
169 0 0       0 if( $f )
170             {
171 0         0 return( ( CORE::stat( $self->{filepath} ) )[ FINFO_BLOCKS ] );
172             }
173             else
174             {
175 0         0 my $data = $self->{_data};
176 0 0       0 return( '' ) if( !scalar( @$data ) );
177 0         0 return( $data->[ FINFO_BLOCKS ] );
178             }
179             }
180              
181 1     1 1 6 sub can_read { return( -r( shift->filepath ) ); }
182              
183 1     1 1 15 sub can_write { return( -w( shift->filepath ) ); }
184              
185 1     1 1 4 sub can_exec { return( -x( shift->filepath ) ); }
186              
187 1     1 1 488 sub can_execute { return( -x( shift->filepath ) ); }
188              
189 1     1 1 4 sub csize { return( shift->size ); }
190              
191             sub ctime
192             {
193 1     1 1 3 my $self = shift( @_ );
194 1         5 my $f = $self->apr_finfo;
195 1         140 my $t;
196 1 50       5 if( $f )
197             {
198 0         0 $t = $f->ctime;
199             }
200             else
201             {
202 1         4 my $data = $self->{_data};
203 1 50       4 return( '' ) if( !scalar( @$data ) );
204 1         4 $t = $data->[ FINFO_CTIME ];
205             }
206 1         5 return( $self->_datetime( $t ) );
207             }
208              
209 0     0 1 0 sub dev { return( shift->device( @_ ) ); }
210              
211             sub device
212             {
213 1     1 1 3 my $self = shift( @_ );
214 1         3 my $f = $self->apr_finfo;
215 1 50       92 if( $f )
216             {
217 0         0 return( $f->device );
218             }
219             else
220             {
221 1         3 my $data = $self->{_data};
222 1 50       5 return( '' ) if( !scalar( @$data ) );
223 1         5 return( $data->[ FINFO_DEV ] );
224             }
225             }
226              
227 10 100   10 1 98 sub exists { return( shift->filetype == FILETYPE_NOFILE ? 0 : 1 ); }
228              
229             ## Read-only
230 5     5 1 34 sub filepath { return( shift->_set_get_scalar( 'filepath' ) ); }
231              
232             sub filetype
233             {
234 23     23 1 571 my $self = shift( @_ );
235 23         112 my $f = $self->apr_finfo;
236 23 50       3469 if( $f )
237             {
238 0         0 return( $f->filetype );
239             }
240             else
241             {
242 23         102 my $file = $self->{filepath};
243 23         522 CORE::stat( $file );
244 23 100       221 if( !-e( _ ) )
    100          
    50          
    0          
    0          
    0          
    0          
    0          
245             {
246 4         55 return( FILETYPE_NOFILE );
247             }
248             elsif( -f( _ ) )
249             {
250 17         175 return( FILETYPE_REG );
251             }
252             elsif( -d( _ ) )
253             {
254 2         35 return( FILETYPE_DIR );
255             }
256             elsif( -l( _ ) )
257             {
258 0         0 return( FILETYPE_LNK );
259             }
260             elsif( -p( _ ) )
261             {
262 0         0 return( FILETYPE_PIPE );
263             }
264             elsif( -S( _ ) )
265             {
266 0         0 return( FILETYPE_SOCK );
267             }
268             elsif( -b( _ ) )
269             {
270 0         0 return( FILETYPE_BLK );
271             }
272             elsif( -c( _ ) )
273             {
274 0         0 return( FILETYPE_CHR );
275             }
276             else
277             {
278 0         0 return( FILETYPE_UNKFILE );
279             }
280             }
281             }
282              
283             sub fname
284             {
285 4     4 1 12 my $self = shift( @_ );
286 4         47 my $r = $self->apache_request;
287 4 50       110 if( $r )
288             {
289 0         0 return( $r->fname );
290             }
291             else
292             {
293 4         272 return( $self->{filepath} );
294             }
295             }
296              
297 1     1 1 5 sub gid { return( shift->group ); }
298              
299             sub group
300             {
301 2     2 1 5 my $self = shift( @_ );
302 2         5 my $f = $self->apr_finfo;
303 2 50       203 if( $f )
304             {
305 0         0 return( $f->fname );
306             }
307             else
308             {
309 2         5 my $data = $self->{_data};
310 2 50       6 return( '' ) if( !scalar( @$data ) );
311 2         37 return( $data->[ FINFO_GID ] );
312             }
313             }
314              
315 0     0 0 0 sub ino { return( shift->inode( @_ ) ); }
316              
317             sub inode
318             {
319 1     1 1 2 my $self = shift( @_ );
320 1         4 my $f = $self->apr_finfo;
321 1 50       84 if( $f )
322             {
323 0         0 return( $f->inode );
324             }
325             else
326             {
327 1         2 my $data = $self->{_data};
328 1 50       4 return( '' ) if( !scalar( @$data ) );
329 1         5 return( $data->[ FINFO_INODE ] );
330             }
331             }
332              
333 1     1 1 5 sub is_block { return( shift->filetype == FILETYPE_BLK ); }
334              
335 1     1 1 5 sub is_char { return( shift->filetype == FILETYPE_CHR ); }
336              
337 2     2 1 10 sub is_dir { return( shift->filetype == FILETYPE_DIR ); }
338              
339 3     3 1 34 sub is_file { return( shift->filetype == FILETYPE_REG ); }
340              
341 1     1 1 4 sub is_link { return( shift->filetype == FILETYPE_LNK ); }
342              
343 1     1 1 5 sub is_pipe { return( shift->filetype == FILETYPE_PIPE ); }
344              
345 1     1 1 4 sub is_socket { return( shift->filetype == FILETYPE_SOCK ); }
346              
347             sub mode
348             {
349 3     3 1 27 my $self = shift( @_ );
350 3         25 my $f = $self->apr_finfo;
351 3 50       455 if( $f )
352             {
353             # Something like 1860
354 0         0 my $hex = $f->protection;
355 0         0 return( oct( sprintf( '%x', $hex ) ) );
356             }
357             else
358             {
359 3         19 my $data = $self->{_data};
360 3 50       23 return( '' ) if( !scalar( @$data ) );
361 3         47 return( $data->[ FINFO_MODE ] & 07777 );
362             }
363             }
364              
365             sub mtime
366             {
367 3     3 1 7 my $self = shift( @_ );
368 3         13 my $f = $self->apr_finfo;
369 3         324 my $t;
370 3 50       7 if( $f )
371             {
372 0         0 $t = $f->mtime;
373             }
374             else
375             {
376 3         7 my $data = $self->{_data};
377 3 50       25 return( '' ) if( !scalar( @$data ) );
378 3         8 $t = $data->[ FINFO_MTIME ];
379             }
380 3         11 return( $self->_datetime( $t ) );
381             }
382              
383             sub name
384             {
385 3     3 1 8 my $self = shift( @_ );
386 3         17 my $f = $self->apr_finfo;
387 3 50       405 if( $f )
388             {
389 0   0     0 return( $f->name || File::Basename::basename( $f->fname ) );
390             }
391             else
392             {
393 3         18 return( File::Basename::basename( $self->fname ) );
394             }
395             }
396              
397             sub nlink
398             {
399 1     1 1 4 my $self = shift( @_ );
400 1         4 my $f = $self->apr_finfo;
401 1 50       90 if( $f )
402             {
403 0         0 return( $f->nlink );
404             }
405             else
406             {
407 1         3 my $data = $self->{_data};
408 1 50       5 return( '' ) if( !scalar( @$data ) );
409 1         5 return( $data->[ FINFO_NLINK ] );
410             }
411             }
412              
413             sub protection
414             {
415 1     1 1 3 my $self = shift( @_ );
416 1         4 my $f = $self->apr_finfo;
417 1 50       152 if( $f )
418             {
419             ## Will return something like 1860 (i.e. 744 = hex(1860))
420 0         0 return( $f->protection );
421             }
422             else
423             {
424 1         5 my @stat = CORE::stat( $self->filepath );
425 1 50       77 return( '' ) if( !scalar( @stat ) );
426 1         15 return( hex( sprintf( '%04o', $stat[2] & 07777 ) ) );
427             }
428             }
429              
430             sub rdev
431             {
432 0     0 1 0 my $self = shift( @_ );
433 0         0 my $f = $self->apr_finfo;
434 0 0       0 if( $f )
435             {
436 0         0 return( ( CORE::stat( $self->{filepath} ) )[ FINFO_RDEV ] );
437             }
438             else
439             {
440 0         0 my $data = $self->{_data};
441 0 0       0 return( '' ) if( !scalar( @$data ) );
442 0         0 return( $data->[ FINFO_RDEV ] );
443             }
444             }
445              
446             sub size
447             {
448 6     6 1 1063 my $self = shift( @_ );
449 6         46 my $f = $self->apr_finfo;
450 6 50       565 if( $f )
451             {
452 0         0 return( $f->size );
453             }
454             else
455             {
456 6         49 my $data = $self->{_data};
457 6 50       41 return( '' ) if( !scalar( @$data ) );
458 6         131 return( $data->[ FINFO_SIZE ] );
459             }
460             }
461              
462             sub stat
463             {
464 1     1 1 3 my $self = shift( @_ );
465 1         4 my $r = $self->apache_request;
466 1         27 my $file = shift( @_ );
467 1 50       6 my $p = scalar( @_ ) ? { @_ } : {};
468 1 50 33     6 $p->{apache_request} = $r if( $r && !$p->{apache_request} );
469 1         8 return( $self->new( $file, $p ) );
470             }
471              
472 2     2 1 1052 sub uid { return( shift->user ); }
473              
474             sub user
475             {
476 3     3 1 9 my $self = shift( @_ );
477 3         9 my $f = $self->apr_finfo;
478 3 50       268 if( $f )
479             {
480 0         0 return( $f->user );
481             }
482             else
483             {
484 3         9 my $data = $self->{_data};
485 3 50       10 return( '' ) if( !scalar( @$data ) );
486 3         23 return( $data->[ FINFO_UID ] );
487             }
488             }
489              
490             sub _datetime
491             {
492 5     5   10 my $self = shift( @_ );
493 5         13 my $t = shift( @_ );
494 5 50       19 return( $self->error( "No epoch time was provided." ) ) if( !length( $t ) );
495 5 50       32 return( $self->error( "Invalid epoch time provided \"$t\"." ) ) if( $t !~ /^\d+$/ );
496 5         9 try
497 5     5   8 {
498 5         42 my $dt = DateTime->from_epoch( epoch => $t, time_zone => 'local' );
499 5         11690 my $fmt = DateTime::Format::Strptime->new(
500             pattern => '%s',
501             time_zone => 'local',
502             );
503 5         10419 $dt->set_formatter( $fmt );
504 5         358 return( Apache2::SSI::Datetime->new( $dt ) );
505             }
506 5 100       33 catch( $e )
  0 50       0  
  5 50       16  
  5 0       11  
  5 50       13  
  5         7  
  5         8  
  5         9  
  5         21  
  2         3  
  3         5  
  0         0  
  5         68  
  5         13  
  5         17  
  5         16  
  0         0  
  0         0  
  0         0  
  0         0  
507 0     0   0 {
508 0         0 return( $self->error( "Unable to get the datetime object for \"$t\": $e" ) );
509 0 0 33     0 }
  0 0 33     0  
  0 100       0  
  0 50       0  
  0         0  
  0         0  
  5         134  
  5         101  
510             }
511              
512             package Apache2::SSI::Datetime;
513             BEGIN
514             {
515 17     17   209 use strict;
  17         35  
  17         546  
516 17     17   118 use warnings;
  17         376  
  17         1514  
517             use overload (
518 5     5   29 q{""} => sub { $_[0]->{dt}->stringify },
519             bool => sub () { 1 },
520 17         145 fallback => 1,
521 17     17   109 );
  17         33  
522 17     17   4171 our( $ERROR );
523             };
524              
525             sub new
526             {
527 5     5   25 my $this = shift( @_ );
528 5   50     30 my $dt = shift( @_ ) || return;
529 5         56 my $self = { dt => $dt };
530 5   33     51 return( bless( { dt => $dt } => ( ref( $this ) || $this ) ) );
531             }
532              
533             sub error
534             {
535 5     5   13 my $self = shift( @_ );
536 5 50       13 if( @_ )
537             {
538 5         45 $self->{error} = $ERROR = join( '', @_ );
539 5         373 return;
540             }
541 0   0     0 return( $self->{error} || $ERROR );
542             }
543              
544             AUTOLOAD
545             {
546 5     5   3443 my( $method ) = our $AUTOLOAD =~ /([^:]+)$/;
547 17     17   134 no overloading;
  17         33  
  17         2517  
548 5         24 my $self = shift( @_ );
549 5   33     24 my $class = ref( $self ) || $self;
550 5 50       19 die( "DateTime object is gone !\n" ) if( !ref( $self->{dt} ) );
551 5         14 my $dt = $self->{dt};
552 5 50       32 if( $dt->can( $method ) )
553             {
554 0         0 return( $dt->$method( @_ ) );
555             }
556             else
557             {
558 5         54 return( $self->error( "No method \"$method\" available in DateTime" ) );
559             }
560             };
561              
562             1;
563              
564             __END__
565              
566             =encoding utf-8
567              
568             =head1 NAME
569              
570             Apache2::SSI::Finfo - Apache2 Server Side Include File Info Object Class
571              
572             =head1 SYNOPSIS
573              
574             my $finfo = Apache2::SSI::Finfo->new( '/some/file/path.html' );
575             # or with Apache
576             use Apache2::RequestRec ();
577             use apache2::RequestUtil ();
578             my $r = Apache2::RequestUtil->request;
579             my $finfo = Apache2::SSI::Finfo->new( '/some/file/path.html', apache_request => $r );
580             # Direct access to APR::Finfo
581             my $apr = $finfo->apr_finfo;
582             # Get access time as a DateTime object
583             $finfo->atime;
584             # Block site
585             $finfo->blksize;
586             # Number of blocks
587             $finfo->blocks;
588             if( $finfo->can_read )
589             {
590             # Do something
591             }
592             # Can also use
593             $finfo->can_write;
594             $finfo->can_exec;
595             $finfo->csize;
596             # Inode change time as a DateTime object
597             $finfo->ctime;
598             $finfo->dev;
599             if( $finfo->exists )
600             {
601             # Do something
602             }
603             print "File path is: ", $finfo->filepath;
604             if( $finfo->filetype == Apache2::SSI::Finfo::FILETYPE_NOFILE )
605             {
606             # File does not exist
607             }
608             # Same as $finfo->filepath
609             print "File path is: ", $finfo->fname;
610             print "File group id is: ", $finfo->gid;
611             # Can also use $finfo->group which will yield the same result
612             $finfo->ino;
613             # or $finfo->inode;
614             if( $finfo->is_block )
615             {
616             # Do something
617             }
618             elsif( $finfo->is_char )
619             {
620             # Do something else
621             }
622             elsif( $finfo->is_dir )
623             {
624             # It's a directory
625             }
626             elsif( $finfo->is_file )
627             {
628             # It's a regular file
629             }
630             elsif( $finfo->is_link )
631             {
632             # A file alias
633             }
634             elsif( $info->is_pipe )
635             {
636             # A Unix pipe !
637             }
638             elsif( $finfo->is_socket )
639             {
640             # It's a socket
641             }
642             elsif( ( $info->mode & 0100 ) )
643             {
644             # Can execute
645             }
646             $finfo->mtime->strftime( '%A %d %B %Y %H:%m:%S' );
647             print "File base name is: ", $finfo->name;
648             printf "File has %d links\n", $finfo->nlink;
649             print "File permission in hexadecimal: ", $finfo->protection;
650             $finfo->rdev;
651             $finfo->size;
652             my $new_object = $finfo->stat( '/some/other/file.txt' );
653             # Get the user id
654             $finfo->uid;
655             # Or
656             $finfo->user;
657              
658             =head1 VERSION
659              
660             v0.1.0
661              
662             =head1 DESCRIPTION
663              
664             This class provides a file info object oriented consistant whether it is accessed from Apache/mod_perl2 environment or from outside of it.
665              
666             The other advantage is that even if a non-existing file is provided, an object is returned. Obviously many of this module's methods will return an empty value since the file does not actually exist. This is an advantage, because one cannot create an L<APR::Finfo> object over a non-existing file.
667              
668             =head1 METHODS
669              
670             =head2 new
671              
672             This instantiate an object that is used to access other key methods. It takes a file path followed by the following parameters:
673              
674             =over 4
675              
676             =item I<apache_request>
677              
678             This is the L<Apache2::RequestRec> object that is provided if running under mod_perl.
679              
680             it can be retrieved from L<Apache2::RequestUtil/request> or via L<Apache2::Filter/r>
681              
682             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.
683              
684             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:
685              
686             use Apache2::RequestUtil (); # extends Apache2::RequestRec objects
687             my $r = $r->is_initial_req ? $r : $r->main;
688              
689             =back
690              
691             =head2 apache_request
692              
693             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.
694              
695             When running under Apache mod_perl this is set automatically from the special L</handler> method, such as:
696              
697             my $r = $f->r; # $f is the Apache2::Filter object provided by Apache
698              
699             =head2 apr_finfo
700              
701             Sets or gets the L<APR::Finfo> object when running under Apache/mod_perl. Note that this value might be empty if the file does not exist. This is mentioned here for completeness only.
702              
703             =head2 atime
704              
705             Returns the file last access time as a L<Apache2::SSI::Datetime> object, which stringifies to its value in second since epoch. L<Apache2::SSI::Datetime> is just a wrapper around L<DateTime> to allow a L<DateTime> to be used in comparison with another non L<DateTime> value.
706              
707             For example:
708              
709             if( $finfo->atime > time() + 86400 )
710             {
711             print( "You are traveling in the future\n" );
712             }
713              
714             =head2 blksize
715              
716             Returns the preferred I/O size in bytes for interacting with the file.
717             You can also use C<block_size>.
718              
719             =head2 blocks
720              
721             Returns the actual number of system-specific blocks allocated on disk (often, but not always, 512 bytes each).
722              
723             =head2 can_read
724              
725             Returns true if the the effective user can read the file.
726              
727             =head2 can_write
728              
729             Returns true if the the effective user can write to the file.
730              
731             =head2 can_exec
732              
733             Returns true if the the effective user can execute the file. Same as L</execute>
734              
735             =head2 can_execute
736              
737             Returns true if the the effective user can execute the file. Same as L</exec>
738              
739             =head2 csize
740              
741             Returns the total size of file, in bytes. Same as L</size>
742              
743             =head2 ctime
744              
745             Returns the file inode change time as a L<Apache2::SSI::Datetime> object, which stringifies to its value in second since epoch. L<Apache2::SSI::Datetime> is just a wrapper around L<DateTime> to allow a L<DateTime> to be used in comparison with another non L<DateTime> value.
746              
747             =head2 dev
748              
749             Returns the device number of filesystem. Same as L</dev>
750              
751             =head2 device
752              
753             Returns the device number of filesystem. Same as L</device>
754              
755             =head2 exists
756              
757             Returns true if the filetype is not L</FILETYPE_NOFILE>
758              
759             =head2 filepath
760              
761             Returns the file path as a string. Same as L</fname>
762              
763             =head2 filetype
764              
765             Returns the file type which is one of the L</CONSTANTS> below.
766              
767             =head2 fname
768              
769             Returns the file path as a string. Same as L</filepath>
770              
771             =head2 gid
772              
773             Returns the numeric group ID of file's owner. Same as L</group>
774              
775             =head2 group
776              
777             Returns the numeric group ID of file's owner. Same as L</gid>
778              
779             =head2 inode
780              
781             Returns the inode number.
782              
783             =head2 is_block
784              
785             Returns true if this is a block file, false otherwise.
786              
787             =head2 is_char
788              
789             Returns true if this is a character file, false otherwise.
790              
791             =head2 is_dir
792              
793             Returns true if this is a directory, false otherwise.
794              
795             =head2 is_file
796              
797             Returns true if this is a regular file, false otherwise.
798              
799             =head2 is_link
800              
801             Returns true if this is a symbolic link, false otherwise.
802              
803             =head2 is_pipe
804              
805             Returns true if this is a pipe, false otherwise.
806              
807             =head2 is_socket
808              
809             Returns true if this is a socket, false otherwise.
810              
811             =head2 mode
812              
813             Returns the file mode. This is equivalent to the mode & 07777, ie without the file type bit.
814              
815             So you could do something like:
816              
817             if( $finfo->mode & 0100 )
818             {
819             print( "Owner can execute\n" );
820             }
821             if( $finfo->mode & 0001 )
822             {
823             print( "Everyone can execute too!\n" );
824             }
825              
826             =head2 mtime
827              
828             Returns the file last modify time as a L<Apache2::SSI::Datetime> object, which stringifies to its value in second since epoch. L<Apache2::SSI::Datetime> is just a wrapper around L<DateTime> to allow a L<DateTime> to be used in comparison with another non L<DateTime> value.
829              
830             =head2 name
831              
832             Returns the file base name. So if the file is C</home/john/www/some/file.html> this would return C<file.html>
833              
834             Interesting to note that L<APR::Finfo/name> which is advertised as returning the file base name, actually returns just an empty string. With this module, this uses a workaround to provide the proper value. It use L<File::Basename/basename> on the value returned by L</fname>
835              
836             =head2 nlink
837              
838             Returns the number of (hard) links to the file.
839              
840             =head2 protection
841              
842             =head2 rdev
843              
844             Returns the device identifier (special files only).
845              
846             =head2 size
847              
848             Returns the total size of file, in bytes. Same as L</csize>
849              
850             =head2 stat
851              
852             Provided with a file path and this returns a new L<Apache2::SSI::Finfo> object.
853              
854             =head2 uid
855              
856             =head2 user
857              
858             Returns the numeric user ID of file's owner. Same as L</uid>
859              
860             =head2 uid
861              
862             Returns the numeric user ID of file's owner. Same as L</user>
863              
864             =head1 CONSTANTS
865              
866             =head2 FILETYPE_NOFILE
867              
868             File type constant to indicate the file does not exist.
869              
870             =head2 FILETYPE_REG
871              
872             Regular file
873              
874             =head2 FILETYPE_DIR
875              
876             The element is a directory
877              
878             =head2 FILETYPE_CHR
879              
880             The element is a character block
881              
882             =head2 FILETYPE_BLK
883              
884             A block device
885              
886             =head2 FILETYPE_PIPE
887              
888             The file is a FIFO or a pipe
889              
890             =head2 FILETYPE_LNK
891              
892             The file is a symbolic link
893              
894             =head2 FILETYPE_SOCK
895              
896             The file is a (unix domain) socket
897              
898             =head2 FILETYPE_UNKFILE
899              
900             The file is of some other unknown type or the type cannot be determined
901              
902             =head1 AUTHOR
903              
904             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
905              
906             CPAN ID: jdeguest
907              
908             L<https://git.deguest.jp/jack/Apache2-SSI>
909              
910             =head1 SEE ALSO
911              
912             L<Apache2::SSI::File>, L<Apache2::SSI::URI>, L<Apache2::SSI>
913              
914             mod_include, mod_perl(3), L<APR::Finfo>, L<perlfunc/stat>
915             L<https://httpd.apache.org/docs/current/en/mod/mod_include.html>,
916             L<https://httpd.apache.org/docs/current/en/howto/ssi.html>,
917             L<https://httpd.apache.org/docs/current/en/expr.html>
918             L<https://perl.apache.org/docs/2.0/user/handlers/filters.html#C_PerlOutputFilterHandler_>
919              
920             =head1 COPYRIGHT & LICENSE
921              
922             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
923              
924             You can use, copy, modify and redistribute this package and associated
925             files under the same terms as Perl itself.
926              
927             =cut
928