File Coverage

blib/lib/Apache2/SSI/Finfo.pm
Criterion Covered Total %
statement 290 395 73.4
branch 49 138 35.5
condition 7 31 22.5
subroutine 81 90 90.0
pod 39 41 95.1
total 466 695 67.0


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