| 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 |  | 109913 | use strict; | 
|  | 17 |  |  |  |  | 47 |  | 
|  | 17 |  |  |  |  | 533 |  | 
| 17 | 17 |  |  | 17 |  | 87 | use warnings; | 
|  | 17 |  |  |  |  | 27 |  | 
|  | 17 |  |  |  |  | 495 |  | 
| 18 | 17 |  |  | 17 |  | 591 | use parent qw( Module::Generic ); | 
|  | 17 |  |  |  |  | 330 |  | 
|  | 17 |  |  |  |  | 94 |  | 
| 19 | 17 |  |  | 17 |  | 52950625 | use IO::File; | 
|  | 17 |  |  |  |  | 32 |  | 
|  | 17 |  |  |  |  | 2581 |  | 
| 20 | 17 |  |  | 17 |  | 255 | use Nice::Try; | 
|  | 17 |  |  |  |  | 26 |  | 
|  | 17 |  |  |  |  | 189 |  | 
| 21 | 17 |  |  | 17 |  | 4047569 | use Scalar::Util (); | 
|  | 17 |  |  |  |  | 49 |  | 
|  | 17 |  |  |  |  | 744 |  | 
| 22 | 17 |  |  | 17 |  | 2319 | use URI; | 
|  | 17 |  |  |  |  | 11578 |  | 
|  | 17 |  |  |  |  | 1291 |  | 
| 23 | 17 |  |  | 17 |  | 6175 | our $VERSION = 'v0.1.0'; | 
| 24 |  |  |  |  |  |  | }; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | ## RFC 3986 section 5.2.4 | 
| 27 |  |  |  |  |  |  | sub collapse_dots | 
| 28 |  |  |  |  |  |  | { | 
| 29 | 481 |  |  | 481 | 1 | 6689 | my $self = shift( @_ ); | 
| 30 | 481 |  |  |  |  | 896 | my $path = shift( @_ ); | 
| 31 | 481 | 50 |  |  |  | 1218 | return( '' ) if( !length( $path ) ); | 
| 32 | 481 |  |  |  |  | 2293 | my $u = URI->new( $path ); | 
| 33 | 481 |  |  |  |  | 27179 | $path = $u->path; | 
| 34 | 481 |  |  |  |  | 7228 | my @new = (); | 
| 35 | 481 |  |  |  |  | 849 | my $len = length( $path ); | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | ## "If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer" | 
| 38 | 481 | 100 | 66 |  |  | 5718 | if( substr( $path, 0, 2 ) eq './' ) | 
|  |  | 50 | 33 |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | { | 
| 40 | 1 |  |  |  |  | 4 | substr( $path, 0, 2 ) = ''; | 
| 41 |  |  |  |  |  |  | ## $self->message( 3, "Removed './'. Path is now '", substr( $path, 0 ), "'." ); | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  | elsif( substr( $path, 0, 3 ) eq '../' ) | 
| 44 |  |  |  |  |  |  | { | 
| 45 | 0 |  |  |  |  | 0 | substr( $path, 0, 3 ) = ''; | 
| 46 |  |  |  |  |  |  | } | 
| 47 |  |  |  |  |  |  | ## "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" | 
| 48 |  |  |  |  |  |  | elsif( substr( $path, 0, 3 ) eq '/./' ) | 
| 49 |  |  |  |  |  |  | { | 
| 50 | 0 |  |  |  |  | 0 | substr( $path, 0, 3 ) = '/'; | 
| 51 |  |  |  |  |  |  | } | 
| 52 |  |  |  |  |  |  | elsif( substr( $path, 0, 2 ) eq '/.' && 2 == $len ) | 
| 53 |  |  |  |  |  |  | { | 
| 54 | 0 |  |  |  |  | 0 | substr( $path, 0, 2 ) = '/'; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  | elsif( $path eq '..' || $path eq '.' ) | 
| 57 |  |  |  |  |  |  | { | 
| 58 | 0 |  |  |  |  | 0 | $path = ''; | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  | elsif( $path eq '/' ) | 
| 61 |  |  |  |  |  |  | { | 
| 62 | 6 |  |  |  |  | 49 | return( $u ); | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | ## -1 is used to ensure trailing blank entries do not get removed | 
| 66 | 475 |  |  |  |  | 2315 | my @segments = split( '/', $path, -1 ); | 
| 67 |  |  |  |  |  |  | ## $self->messagef( 3, "Found %d segments.", scalar( @segments ) ); | 
| 68 | 475 |  |  |  |  | 1466 | for( my $i = 0; $i < scalar( @segments ); $i++ ) | 
| 69 |  |  |  |  |  |  | { | 
| 70 | 2358 |  |  |  |  | 3144 | my $segment = $segments[$i]; | 
| 71 |  |  |  |  |  |  | ## "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" | 
| 72 | 2358 | 100 |  |  |  | 3984 | if( $segment eq '..' ) | 
|  |  | 100 |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | { | 
| 74 | 19 |  |  |  |  | 40 | pop( @new ); | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  | elsif( $segment eq '.' ) | 
| 77 |  |  |  |  |  |  | { | 
| 78 | 2 |  |  |  |  | 4 | next; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  | else | 
| 81 |  |  |  |  |  |  | { | 
| 82 | 2337 | 50 |  |  |  | 5697 | push( @new, ( defined( $segment ) ? $segment : '' ) ); | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  | ## Finally, the output buffer is returned as the result of remove_dot_segments. | 
| 86 | 475 |  |  |  |  | 1525 | my $new_path = join( '/', @new ); | 
| 87 | 475 | 100 |  |  |  | 1282 | substr( $new_path, 0, 0 ) = '/' unless( substr( $new_path, 0, 1 ) eq '/' ); | 
| 88 | 475 |  |  |  |  | 1317 | $u->path( $new_path ); | 
| 89 | 475 |  |  |  |  | 16202 | return( $u ); | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | ## Credits: Path::Tiny | 
| 93 |  |  |  |  |  |  | sub slurp | 
| 94 |  |  |  |  |  |  | { | 
| 95 | 58 |  |  | 58 | 1 | 139 | my $self = shift( @_ ); | 
| 96 | 58 |  |  |  |  | 111 | my $args = {}; | 
| 97 | 17 |  |  | 17 |  | 144 | no warnings 'uninitialized'; | 
|  | 17 |  |  |  |  | 33 |  | 
|  | 17 |  |  |  |  | 9558 |  | 
| 98 | 58 | 0 |  |  |  | 325 | $args = Scalar::Util::reftype( $_[0] ) eq 'HASH' | 
|  |  | 50 |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | ? shift( @_ ) | 
| 100 |  |  |  |  |  |  | : !( scalar( @_ ) % 2 ) | 
| 101 |  |  |  |  |  |  | ? { @_ } | 
| 102 |  |  |  |  |  |  | : {}; | 
| 103 | 58 |  | 0 |  |  | 176 | my $file = $args->{filename} || $args->{file} || $self->filename; | 
| 104 | 58 | 50 |  |  |  | 490 | return( $self->error( "No filename found." ) ) if( !length( $file ) ); | 
| 105 | 58 |  | 50 |  |  | 441 | my $binmode = $args->{binmode} // ''; | 
| 106 | 58 |  |  |  |  | 107 | try | 
| 107 | 58 |  |  | 58 |  | 136 | { | 
| 108 | 58 |  | 50 |  |  | 207 | my $fh = IO::File->new( "<$file" ) || | 
| 109 |  |  |  |  |  |  | return( $self->error( "Unable to open file \"$file\" in read mode: $!" ) ); | 
| 110 | 58 | 50 |  |  |  | 9687 | $fh->binmode( $binmode ) if( length( $binmode ) ); | 
| 111 | 58 |  |  |  |  | 999 | my $size; | 
| 112 | 58 | 50 | 33 |  |  | 302 | if( $binmode eq ':unix' && ( $size = -s( $fh ) ) ) | 
| 113 |  |  |  |  |  |  | { | 
| 114 | 0 |  |  |  |  | 0 | my $buf; | 
| 115 | 0 |  |  |  |  | 0 | $fh->read( $buf, $size ); | 
| 116 | 0 |  |  |  |  | 0 | return( $buf ); | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | else | 
| 119 |  |  |  |  |  |  | { | 
| 120 | 58 |  |  |  |  | 296 | local $/; | 
| 121 | 58 |  |  |  |  | 3306 | return( scalar( <$fh> ) ); | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  | } | 
| 124 | 58 | 50 |  |  |  | 539 | catch( $e ) | 
|  | 0 | 50 |  |  |  | 0 |  | 
|  | 58 | 50 |  |  |  | 183 |  | 
|  | 58 | 0 |  |  |  | 123 |  | 
|  | 58 | 50 |  |  |  | 143 |  | 
|  | 58 |  |  |  |  | 95 |  | 
|  | 58 |  |  |  |  | 100 |  | 
|  | 58 |  |  |  |  | 109 |  | 
|  | 58 |  |  |  |  | 219 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 58 |  |  |  |  | 147 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 58 |  |  |  |  | 366 |  | 
|  | 58 |  |  |  |  | 176 |  | 
|  | 58 |  |  |  |  | 165 |  | 
|  | 58 |  |  |  |  | 247 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 125 | 0 |  |  | 0 |  | 0 | { | 
| 126 | 0 |  |  |  |  | 0 | return( $self->error( "An error occured while trying to open and read file \"$file\": $e" ) ); | 
| 127 | 0 | 0 | 33 |  |  | 0 | } | 
|  | 0 | 0 | 33 |  |  | 0 |  | 
|  | 0 | 50 |  |  |  | 0 |  | 
|  | 0 | 50 |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 58 |  |  |  |  | 649 |  | 
|  | 58 |  |  |  |  | 1557 |  | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | sub slurp_utf8 | 
| 131 |  |  |  |  |  |  | { | 
| 132 | 58 |  |  | 58 | 1 | 605 | my $self = shift( @_ ); | 
| 133 | 58 |  |  |  |  | 135 | my $args = {}; | 
| 134 | 17 |  |  | 17 |  | 138 | no warnings 'uninitialized'; | 
|  | 17 |  |  |  |  | 31 |  | 
|  | 17 |  |  |  |  | 2233 |  | 
| 135 | 58 | 50 |  |  |  | 594 | $args = Scalar::Util::reftype( $_[0] ) eq 'HASH' | 
|  |  | 50 |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | ? shift( @_ ) | 
| 137 |  |  |  |  |  |  | : !( scalar( @_ ) % 2 ) | 
| 138 |  |  |  |  |  |  | ? { @_ } | 
| 139 |  |  |  |  |  |  | : {}; | 
| 140 | 58 |  |  |  |  | 275 | $args->{binmode} = ':utf8'; | 
| 141 | 58 |  | 33 |  |  | 509 | my $file = $args->{filename} || $args->{file} || $self->filename; | 
| 142 | 58 | 50 |  |  |  | 395 | return( $self->error( "No filename found." ) ) if( !length( $file ) ); | 
| 143 | 58 |  |  |  |  | 367 | $args->{filename} = $file; | 
| 144 | 58 |  |  |  |  | 270 | return( $self->slurp( $args ) ); | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | 1; | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | __END__ | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | =encoding utf-8 | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | =head1 NAME | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | Apache2::SSI::Common - Apache2 Server Side Include Common Resources | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | =head1 VERSION | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | v0.1.0 | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | use parent qw( Apache2::SSI::Common ); | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | There is no specific api for this. This module contains only common resources used by other modules in this distribution. | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | =head1 METHODS | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | =head2 collapse_dots | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | 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. | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | This is done as per the L<RFC 3986 section 5.2.4 algorithm|https://tools.ietf.org/html/rfc3986#page-33> | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html' ); | 
| 179 |  |  |  |  |  |  | # would become /a/c/d.html | 
| 180 |  |  |  |  |  |  | my $uri = $ssi->collapse_dots( '/../a/b/../c/./d.html?foo=../bar' ); | 
| 181 |  |  |  |  |  |  | # would become /a/c/d.html?foo=../bar | 
| 182 |  |  |  |  |  |  | $uri->query # foo=../bar | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | =head2 slurp | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | It returns the content of the L</filename> | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | it takes an hash reference of parameters: | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | =over 4 | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | =item I<binmode> | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | my $content = $uri->slurp({ binmode => ':utf-8' }); | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | =back | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | 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. | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head2 slurp_utf8 | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | It returns the content of the file L</filename> utf-8 decoded. | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | This is equivalent to: | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | my $content = $uri->slurp({ binmode => ':utf8' }); | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | C<:utf8> is slightly a bit more lax than C<:utf-8>, so it you want strict utf8, you can do: | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | my $content = $uri->slurp({ binmode => ':utf-8' }); | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =head1 AUTHOR | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | Jacques Deguest E<lt>F<jack@deguest.jp>E<gt> | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | CPAN ID: jdeguest | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | L<https://git.deguest.jp/jack/Apache2-SSI> | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | L<Apache2::SSI::File>, L<Apache2::SSI::URI> | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | Copyright (c) 2020-2021 DEGUEST Pte. Ltd. | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | You can use, copy, modify and redistribute this package and associated | 
| 229 |  |  |  |  |  |  | files under the same terms as Perl itself. | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | =cut | 
| 232 |  |  |  |  |  |  |  |