File Coverage

blib/lib/Apache2/SSI/Common.pm
Criterion Covered Total %
statement 101 124 81.4
branch 39 66 59.0
condition 11 27 40.7
subroutine 15 17 88.2
pod 3 3 100.0
total 169 237 71.3


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI/Common.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/01/13
7             ## Modified 2021/01/13
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::Common;
14             BEGIN
15             {
16 17     17   112506 use strict;
  17         45  
  17         493  
17 17     17   77 use warnings;
  17         32  
  17         459  
18 17     17   591 use parent qw( Module::Generic );
  17         456  
  17         91  
19 17     17   31148903 use File::Spec ();
  17         34  
  17         279  
20 17     17   79 use IO::File;
  17         28  
  17         2517  
21 17     17   105 use Nice::Try;
  17         33  
  17         287  
22 17     17   5467873 use Scalar::Util ();
  17         40  
  17         398  
23 17     17   2277 use URI;
  17         11245  
  17         3409  
24 17     17   64 our $VERSION = 'v0.1.0';
25             ## https://en.wikipedia.org/wiki/Path_(computing)
26             ## perlport
27 17         584 our $OS2SEP =
28             {
29             amigaos => '/',
30             android => '/',
31             aix => '/',
32             bsdos => '/',
33             beos => '/',
34             bitrig => '/',
35             cygwin => '/',
36             darwin => '/',
37             dec_osf => '/',
38             dgux => '/',
39             dos => "\\",
40             dragonfly => '/',
41             dynixptx => '/',
42             freebsd => '/',
43             gnu => '/',
44             gnukfreebsd => '/',
45             haiku => '/',
46             hpux => '/',
47             interix => '/',
48             iphoneos => '/',
49             irix => '/',
50             linux => '/',
51             machten => '/',
52             macos => ':',
53             midnightbsd => '/',
54             minix => '/',
55             mirbsd => '/',
56             mswin32 => "\\",
57             msys => '/',
58             netbsd => '/',
59             netware => "\\",
60             next => '/',
61             nto => '/',
62             openbsd => '/',
63             os2 => '/',
64             ## Extended Binary Coded Decimal Interchange Code
65             os390 => '/',
66             os400 => '/',
67             qnx => '/',
68             riscos => '.',
69             sco => '/',
70             sco_sv => '/',
71             solaris => '/',
72             sunos => '/',
73             svr4 => '/',
74             svr5 => '/',
75             symbian => "\\",
76             unicos => '/',
77             unicosmk => '/',
78             vms => '/',
79             vos => '>',
80             win32 => "\\",
81             };
82 17         8737 our $DIR_SEP = $OS2SEP->{ lc( $^O ) };
83             };
84              
85             ## RFC 3986 section 5.2.4
86             ## This is aimed for web URI initially, but is also used for filesystems in a simple way
87             sub collapse_dots
88             {
89 481     481 1 7123 my $self = shift( @_ );
90 481         839 my $path = shift( @_ );
91 481         1589 my $opts = $self->_get_args_as_hash( @_ );
92             ## To avoid warnings
93 481   100     6147 $opts->{separator} //= '';
94             ## A path separator is provided when dealing with filesystem and not web URI
95             ## We use this to know what to return and how to behave
96 481 100       1317 my $sep = length( $opts->{separator} ) ? $opts->{separator} : '/';
97 481 50       1069 return( '' ) if( !length( $path ) );
98 481 100       2241 my $u = $opts->{separator} ? URI::file->new( $path ) : URI->new( $path );
99 481         34416 my( @callinfo ) = caller;
100 481         2474 $self->message( 4, "URI based on '$path' is '$u' (", overload::StrVal( $u ), ") and separator to be used is '$sep' and uri path is '", $u->path, "' called from $callinfo[0] in file $callinfo[1] at line $callinfo[2]." );
101 481 100       20384 $path = $opts->{separator} ? $u->file( $^O ) : $u->path;
102 481         24613 my @new = ();
103 481         783 my $len = length( $path );
104            
105             ## "If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer"
106 481 100 66     5075 if( substr( $path, 0, 2 ) eq ".${sep}" )
    50 33        
    50          
    50          
    50          
    100          
107             {
108 1         4 substr( $path, 0, 2 ) = '';
109             ## $self->message( 3, "Removed './'. Path is now '", substr( $path, 0 ), "'." );
110             }
111             elsif( substr( $path, 0, 3 ) eq "..${sep}" )
112             {
113 0         0 substr( $path, 0, 3 ) = '';
114             }
115             ## "if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer"
116             elsif( substr( $path, 0, 3 ) eq "${sep}.${sep}" )
117             {
118 0         0 substr( $path, 0, 3 ) = $sep;
119             }
120             elsif( substr( $path, 0, 2 ) eq "${sep}." && 2 == $len )
121             {
122 0         0 substr( $path, 0, 2 ) = $sep;
123             }
124             elsif( $path eq '..' || $path eq '.' )
125             {
126 0         0 $path = '';
127             }
128             elsif( $path eq $sep )
129             {
130 6         53 return( $u );
131             }
132            
133             ## -1 is used to ensure trailing blank entries do not get removed
134 475         7017 my @segments = split( "\Q$sep\E", $path, -1 );
135 475     0   3598 $self->message( 3, "Found ", scalar( @segments ), " segments: ", sub{ $self->dump( \@segments ) } );
  0         0  
136 475         9714 for( my $i = 0; $i < scalar( @segments ); $i++ )
137             {
138 2358         3125 my $segment = $segments[$i];
139             ## "if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer"
140 2358 100       3817 if( $segment eq '..' )
    100          
141             {
142 19         35 pop( @new );
143             }
144             elsif( $segment eq '.' )
145             {
146 2         4 next;
147             }
148             else
149             {
150 2337 50       5200 push( @new, ( defined( $segment ) ? $segment : '' ) );
151             }
152             }
153             ## Finally, the output buffer is returned as the result of remove_dot_segments.
154 475         1249 my $new_path = join( $sep, @new );
155             # substr( $new_path, 0, 0 ) = $sep unless( substr( $new_path, 0, 1 ) eq '/' );
156 475 100       3452 substr( $new_path, 0, 0 ) = $sep unless( File::Spec->file_name_is_absolute( $new_path ) );
157 475         1983 $self->message( 4, "Adding back new path '$new_path' to uri '$u'." );
158 475 100       9557 if( $opts->{separator} )
159             {
160 156         580 $u = URI::file->new( $new_path );
161             }
162             else
163             {
164 319         841 $u->path( $new_path );
165             }
166 475 100       25614 $self->message( 4, "Returning uri '$u' (", ( $opts->{separator} ? $u->file( $^O ) : 'same' ), ")." );
167 475         33492 return( $u );
168             }
169              
170             ## Credits: Path::Tiny
171             sub slurp
172             {
173 58     58 1 137 my $self = shift( @_ );
174 58         105 my $args = {};
175 17     17   142 no warnings 'uninitialized';
  17         38  
  17         8685  
176 58 0       271 $args = Scalar::Util::reftype( $_[0] ) eq 'HASH'
    50          
177             ? shift( @_ )
178             : !( scalar( @_ ) % 2 )
179             ? { @_ }
180             : {};
181 58   0     202 my $file = $args->{filename} || $args->{file} || $self->filename;
182 58 50       160 return( $self->error( "No filename found." ) ) if( !length( $file ) );
183 58   50     162 my $binmode = $args->{binmode} // '';
184 58         91 try
185 58     58   90 {
186 58   50     634 my $fh = IO::File->new( "<$file" ) ||
187             return( $self->error( "Unable to open file \"$file\" in read mode: $!" ) );
188 58 50       7356 $fh->binmode( $binmode ) if( length( $binmode ) );
189 58         840 my $size;
190 58 50 33     338 if( $binmode eq ':unix' && ( $size = -s( $fh ) ) )
191             {
192 0         0 my $buf;
193 0         0 $fh->read( $buf, $size );
194 0         0 return( $buf );
195             }
196             else
197             {
198 58         259 local $/;
199 58         2610 return( scalar( <$fh> ) );
200             }
201             }
202 58 50       467 catch( $e )
  0 50       0  
  58 50       153  
  58 0       93  
  58 50       133  
  58         75  
  58         90  
  58         104  
  58         215  
  0         0  
  58         133  
  0         0  
  58         346  
  58         127  
  58         145  
  58         172  
  0         0  
  0         0  
  0         0  
  0         0  
203 0     0   0 {
204 0         0 return( $self->error( "An error occured while trying to open and read file \"$file\": $e" ) );
205 0 0 33     0 }
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  58         519  
  58         1338  
206             }
207              
208             sub slurp_utf8
209             {
210 58     58 1 468 my $self = shift( @_ );
211 58         99 my $args = {};
212 17     17   120 no warnings 'uninitialized';
  17         37  
  17         2059  
213 58 50       498 $args = Scalar::Util::reftype( $_[0] ) eq 'HASH'
    50          
214             ? shift( @_ )
215             : !( scalar( @_ ) % 2 )
216             ? { @_ }
217             : {};
218 58         227 $args->{binmode} = ':utf8';
219 58   33     449 my $file = $args->{filename} || $args->{file} || $self->filename;
220 58 50       176 return( $self->error( "No filename found." ) ) if( !length( $file ) );
221 58         137 $args->{filename} = $file;
222 58         245 return( $self->slurp( $args ) );
223             }
224              
225              
226             1;
227              
228             __END__
229              
230             =encoding utf-8
231              
232             =head1 NAME
233              
234             Apache2::SSI::Common - Apache2 Server Side Include Common Resources
235              
236             =head1 VERSION
237              
238             v0.1.0
239              
240             =head1 SYNOPSIS
241              
242             use parent qw( Apache2::SSI::Common );
243              
244             =head1 DESCRIPTION
245              
246             There is no specific api for this. This module contains only common resources used by other modules in this distribution.
247              
248             =head1 METHODS
249              
250             =head2 collapse_dots
251              
252             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.
253              
254             This is done as per the L<RFC 3986 section 5.2.4 algorithm|https://tools.ietf.org/html/rfc3986#page-33>
255              
256             my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html' );
257             # would become /a/c/d.html
258             my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html?foo=../bar' );
259             # would become /a/c/d.html?foo=../bar
260             $uri->query # foo=../bar
261              
262             =head2 slurp
263              
264             It returns the content of the L</filename>
265              
266             it takes an hash reference of parameters:
267              
268             =over 4
269              
270             =item I<binmode>
271              
272             my $content = $uri->slurp({ binmode => ':utf-8' });
273              
274             =back
275              
276             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.
277              
278             =head2 slurp_utf8
279              
280             It returns the content of the file L</filename> utf-8 decoded.
281              
282             This is equivalent to:
283              
284             my $content = $uri->slurp({ binmode => ':utf8' });
285              
286             C<:utf8> is slightly a bit more lax than C<:utf-8>, so it you want strict utf8, you can do:
287              
288             my $content = $uri->slurp({ binmode => ':utf-8' });
289              
290             =head1 AUTHOR
291              
292             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
293              
294             CPAN ID: jdeguest
295              
296             L<https://git.deguest.jp/jack/Apache2-SSI>
297              
298             =head1 SEE ALSO
299              
300             L<Apache2::SSI::File>, L<Apache2::SSI::URI>
301              
302             =head1 COPYRIGHT & LICENSE
303              
304             Copyright (c) 2020-2021 DEGUEST Pte. Ltd.
305              
306             You can use, copy, modify and redistribute this package and associated
307             files under the same terms as Perl itself.
308              
309             =cut
310