| blib/lib/PLP/Functions.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 71 | 122 | 58.2 |
| branch | 23 | 62 | 37.1 |
| condition | 0 | 3 | 0.0 |
| subroutine | 16 | 21 | 76.1 |
| pod | 12 | 13 | 92.3 |
| total | 122 | 221 | 55.2 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package PLP::Functions; | ||||||
| 2 | |||||||
| 3 | 3 | 3 | 83250 | use strict; | |||
| 3 | 7 | ||||||
| 3 | 715 | ||||||
| 4 | 3 | 3 | 19 | use warnings; | |||
| 3 | 5 | ||||||
| 3 | 101 | ||||||
| 5 | |||||||
| 6 | 3 | 3 | 17 | use base 'Exporter'; | |||
| 3 | 10 | ||||||
| 3 | 849 | ||||||
| 7 | 3 | 3 | 17 | use Carp; | |||
| 3 | 7 | ||||||
| 3 | 324 | ||||||
| 8 | 3 | 3 | 144 | use Fcntl qw(:flock); | |||
| 3 | 6 | ||||||
| 3 | 946 | ||||||
| 9 | |||||||
| 10 | our $VERSION = '1.01'; | ||||||
| 11 | our @EXPORT = qw/Entity DecodeURI EncodeURI Include include PLP_END | ||||||
| 12 | EscapeHTML | ||||||
| 13 | AddCookie ReadFile WriteFile AutoURL Counter exit/; | ||||||
| 14 | |||||||
| 15 | sub Include ($) { | ||||||
| 16 | 3 | 3 | 15 | no strict; | |||
| 3 | 7 | ||||||
| 3 | 12080 | ||||||
| 17 | 2 | 2 | 1 | 3 | $PLP::file = $_[0]; | ||
| 18 | 2 | 3 | $PLP::inA = 0; | ||||
| 19 | 2 | 3 | $PLP::inB = 0; | ||||
| 20 | 2 | 3 | local $@; | ||||
| 21 | 2 | 1 | 14 | eval 'package PLP::Script; no warnings; ' . PLP::source($PLP::file, 0, join ' ', (caller)[2,1]); | |||
| 1 | 1 | 6 | |||||
| 1 | 2 | ||||||
| 1 | 49 | ||||||
| 1 | 5 | ||||||
| 1 | 1 | ||||||
| 1 | 28 | ||||||
| 22 | 2 | 100 | 15 | if ($@) { | |||
| 23 | 1 | 50 | 4 | PLP::Functions::exit() if $@ =~ /\cS\cT\cO\cP/; | |||
| 24 | 1 | 3 | PLP::error($@, 1); | ||||
| 25 | } | ||||||
| 26 | } | ||||||
| 27 | |||||||
| 28 | sub include ($) { | ||||||
| 29 | 2 | 2 | 1 | 11 | goto &Include; | ||
| 30 | } | ||||||
| 31 | |||||||
| 32 | sub exit (;$) { | ||||||
| 33 | 1 | 1 | 0 | 12 | die "\cS\cT\cO\cP\n"; | ||
| 34 | } | ||||||
| 35 | |||||||
| 36 | sub PLP_END (&) { | ||||||
| 37 | 2 | 2 | 1 | 5 | push @PLP::END, shift; | ||
| 38 | } | ||||||
| 39 | |||||||
| 40 | sub EscapeHTML { | ||||||
| 41 | 5 | 100 | 5 | 1 | 267 | @_ == 1 or croak "Unsupported parameters given to EscapeHTML"; | |
| 42 | 4 | 100 | 18 | unshift @_, shift if defined wantarray; # dereference if not void | |||
| 43 | 4 | 9 | for ($_[0]) { | ||||
| 44 | 4 | 100 | 13 | defined or next; | |||
| 45 | 3 | 22 | s/&/&/g; | ||||
| 46 | 2 | 9 | s/"/"/g; | ||||
| 47 | 2 | 8 | s/</g; | ||||
| 48 | 2 | 11 | s/>/>/g; | ||||
| 49 | } | ||||||
| 50 | 3 | 17 | return $_[0]; | ||||
| 51 | } | ||||||
| 52 | |||||||
| 53 | sub Entity (@) { | ||||||
| 54 | 2 | 50 | 2 | 1 | 21 | my $ref = defined wantarray ? [@_] : \@_; | |
| 55 | 2 | 7 | for (@$ref) { | ||||
| 56 | 2 | 50 | 8 | defined or next; | |||
| 57 | 2 | 4 | eval { | ||||
| 58 | 2 | 10 | s/&/&/g; | ||||
| 59 | 2 | 7 | s/"/"/g; | ||||
| 60 | 2 | 6 | s/</g; | ||||
| 61 | 2 | 4 | s/>/>/g; | ||||
| 62 | 2 | 8 | s/\n/ \n/g; |
||||
| 63 | 2 | 6 | s/\t/ /g; | ||||
| 64 | 2 | 8 | s/ / /g; | ||||
| 65 | }; | ||||||
| 66 | } | ||||||
| 67 | 2 | 50 | 23 | return defined wantarray ? (wantarray ? @$ref : "@$ref") : undef; | |||
| 50 | |||||||
| 68 | } | ||||||
| 69 | |||||||
| 70 | sub DecodeURI (@) { | ||||||
| 71 | 38 | 100 | 38 | 1 | 87 | my $ref = defined wantarray ? [@_] : \@_; | |
| 72 | 38 | 63 | for (@$ref) { | ||||
| 73 | 74 | 50 | 126 | defined or next; | |||
| 74 | 74 | 116 | eval { | ||||
| 75 | 74 | 86 | tr/+/ /; # Browsers do tr/ /+/ - I don't care about RFCs, but | ||||
| 76 | # I do care about real-life situations. | ||||||
| 77 | 74 | 155 | s/%([0-9A-Fa-f][0-9A-Fa-f])/chr hex $1/ge; | ||||
| 7 | 29 | ||||||
| 78 | }; | ||||||
| 79 | } | ||||||
| 80 | 38 | 50 | 135 | return defined wantarray ? (wantarray ? @$ref : "@$ref") : undef; | |||
| 100 | |||||||
| 81 | } | ||||||
| 82 | |||||||
| 83 | sub EncodeURI (@) { | ||||||
| 84 | 1 | 50 | 1 | 1 | 7 | my $ref = defined wantarray ? [@_] : \@_; | |
| 85 | 1 | 3 | for (@$ref) { | ||||
| 86 | 1 | 50 | 5 | defined or next; | |||
| 87 | 1 | 1 | eval { | ||||
| 88 | 1 | 7 | s{([^A-Za-z0-9\-_.!~*'()/?:@\$,])}{sprintf("%%%02x", ord $1)}ge; | ||||
| 4 | 20 | ||||||
| 89 | }; | ||||||
| 90 | } | ||||||
| 91 | 1 | 50 | 11 | return defined wantarray ? (wantarray ? @$ref : "@$ref") : undef; | |||
| 50 | |||||||
| 92 | } | ||||||
| 93 | |||||||
| 94 | sub AddCookie ($) { | ||||||
| 95 | 0 | 0 | 0 | 1 | 0 | if ($PLP::Script::header{'Set-Cookie'}) { | |
| 96 | 0 | 0 | $PLP::Script::header{'Set-Cookie'} .= "\n" . $_[0]; | ||||
| 97 | } else { | ||||||
| 98 | 0 | 0 | $PLP::Script::header{'Set-Cookie'} = $_[0]; | ||||
| 99 | } | ||||||
| 100 | } | ||||||
| 101 | |||||||
| 102 | sub ReadFile ($) { | ||||||
| 103 | 0 | 0 | 1 | 0 | local $/ = undef; | ||
| 104 | 0 | 0 | 0 | open (my $fh, '<', $_[0]) or do { | |||
| 105 | 0 | 0 | PLP::error("Cannot open $_[0] for reading ($!)", 1); | ||||
| 106 | 0 | 0 | return undef; | ||||
| 107 | }; | ||||||
| 108 | 0 | 0 | my $r = readline $fh; | ||||
| 109 | 0 | 0 | close $fh; | ||||
| 110 | 0 | 0 | return $r; | ||||
| 111 | } | ||||||
| 112 | |||||||
| 113 | sub WriteFile ($$) { | ||||||
| 114 | 0 | 0 | 0 | 1 | 0 | open (my $fh, '>', $_[0]) or do { | |
| 115 | 0 | 0 | PLP::error("Cannot open $_[0] for writing ($!)", 1); | ||||
| 116 | 0 | 0 | return undef; | ||||
| 117 | }; | ||||||
| 118 | 0 | 0 | flock $fh, LOCK_EX; | ||||
| 119 | 0 | 0 | 0 | print $fh $_[1] or do { | |||
| 120 | 0 | 0 | PLP::error("Cannot write to $_[0] ($!)"); | ||||
| 121 | 0 | 0 | return undef; | ||||
| 122 | }; | ||||||
| 123 | 0 | 0 | 0 | close $fh or do { | |||
| 124 | 0 | 0 | PLP::error("Cannot close $_[0] ($!)"); | ||||
| 125 | 0 | 0 | return undef; | ||||
| 126 | }; | ||||||
| 127 | 0 | 0 | return 1; | ||||
| 128 | } | ||||||
| 129 | |||||||
| 130 | sub Counter ($) { | ||||||
| 131 | 0 | 0 | 1 | 0 | local $/ = undef; | ||
| 132 | 0 | 0 | my $fh; | ||||
| 133 | 0 | 0 | 0 | 0 | open $fh, '+<', $_[0] or | ||
| 134 | open $fh, '>', $_[0] or return undef; | ||||||
| 135 | 0 | 0 | flock $fh, 2; | ||||
| 136 | 0 | 0 | seek $fh, 0, 0; | ||||
| 137 | 0 | 0 | my $counter = <$fh>; | ||||
| 138 | 0 | 0 | seek $fh, 0, 0; | ||||
| 139 | 0 | 0 | truncate $fh, 0; | ||||
| 140 | 0 | 0 | 0 | print $fh ++$counter or return undef; | |||
| 141 | 0 | 0 | 0 | close $fh or return undef; | |||
| 142 | 0 | 0 | return $counter; | ||||
| 143 | } | ||||||
| 144 | |||||||
| 145 | sub AutoURL ($) { | ||||||
| 146 | # This sub assumes your string does not match /(["<>])\cC\1/ | ||||||
| 147 | 0 | 0 | 0 | 1 | 0 | my $ref = defined wantarray ? \(my $copy = $_[0]) : \$_[0]; | |
| 148 | 0 | 0 | eval { | ||||
| 149 | 0 | 0 | $$ref =~ s/"/"\cC"/g; # Single characters are easier to match :) | ||||
| 150 | 0 | 0 | $$ref =~ s/>/>\cC>/g; # so we can just use a character class [] | ||||
| 151 | 0 | 0 | $$ref =~ s/</<\cC | ||||
| 152 | |||||||
| 153 | # Now this is a big, ugly regex! But hey - it works :) | ||||||
| 154 | 0 | 0 | $$ref =~ s{((\w+://|www\.|WWW\.)[a-zA-Z0-9\.\@:-]+[^\"\'>< \r\t\n]*)}{ | ||||
| 155 | 0 | 0 | local $_ = $1; | ||||
| 156 | 0 | 0 | my $scheme = $2; | ||||
| 157 | 0 | 0 | 0 | s/// if (my $trailing) = /([\.,!\?\(\)\[\]]+$)/; | |||
| 158 | 0 | 0 | s/&(?!\x23?\w+;)/&/g; | ||||
| 159 | 0 | 0 | s/\"/"/g; | ||||
| 160 | 0 | 0 | 0 | my $href = ($scheme =~ /www\./i ? "http://$_" : $_); | |||
| 161 | 0 | 0 | qq{$_$trailing}; | ||||
| 162 | }eg; | ||||||
| 163 | |||||||
| 164 | 0 | 0 | $$ref =~ s/"\cC"/"/g; | ||||
| 165 | 0 | 0 | $$ref =~ s/>\cC>/>/g; | ||||
| 166 | 0 | 0 | $$ref =~ s/<\cC</g; | ||||
| 167 | }; | ||||||
| 168 | 0 | 0 | 0 | if ($@){ return defined wantarray ? @_ : undef } # return original on error | |||
| 0 | 0 | 0 | |||||
| 169 | 0 | 0 | 0 | return defined wantarray ? $$ref : undef; | |||
| 170 | } | ||||||
| 171 | |||||||
| 172 | 1; | ||||||
| 173 | |||||||
| 174 | =head1 NAME | ||||||
| 175 | |||||||
| 176 | PLP::Functions - Functions that are available in PLP documents | ||||||
| 177 | |||||||
| 178 | =head1 DESCRIPTION | ||||||
| 179 | |||||||
| 180 | The functions are exported into the PLP::Script package that is used by PLP documents. Although uppercased letters are unusual in Perl, they were chosen to stand out. | ||||||
| 181 | |||||||
| 182 | Most of these functions are context-hybird. Before using them, one should know about contexts in Perl. The three major contexts are: B
|
||||||
| 183 | |||||||
| 184 | Some context examples: | ||||||
| 185 | |||||||
| 186 | print foo(); # foo is in list context (print LIST) | ||||||
| 187 | foo(); # foo is in void context | ||||||
| 188 | $bar = foo(); # foo is in scalar context | ||||||
| 189 | @bar = foo(); # foo is in list context | ||||||
| 190 | length foo(); # foo is in scalar context (length EXPR) | ||||||
| 191 | |||||||
| 192 | =head2 The functions | ||||||
| 193 | |||||||
| 194 | =over 10 | ||||||
| 195 | |||||||
| 196 | =item Include FILENAME | ||||||
| 197 | |||||||
| 198 | Executes another PLP file, that will be parsed (i.e. code must be in C<< <: :> >>). As with Perl's C |
||||||
| 199 | |||||||
| 200 | Include can be used recursively, and there is no depth limit: | ||||||
| 201 | |||||||
| 202 | |||||||
| 203 | <: | ||||||
| 204 | include 'crash.plp'; | ||||||
| 205 | # This example will loop forever, | ||||||
| 206 | # and dies with an out of memory error. | ||||||
| 207 | # Do not try this at home. | ||||||
| 208 | :> | ||||||
| 209 | |||||||
| 210 | =item include FILENAME | ||||||
| 211 | |||||||
| 212 | An alias for C |
||||||
| 213 | |||||||
| 214 | =item PLP_END BLOCK | ||||||
| 215 | |||||||
| 216 | Adds a piece of code that is executed when at the end of the PLP document. This is useful when creating a template file: | ||||||
| 217 | |||||||
| 218 | |||||||
| 219 | <: PLP_END { :> | ||||||
| 220 | |||||||
| 221 | <: } :> | ||||||
| 222 | |||||||
| 223 | <(template.plp)> | ||||||
| 224 | Hello, world! | ||||||
| 225 | |||||||
| 226 | You should use this function instead of Perl's built-in C |
||||||
| 227 | |||||||
| 228 | =item EscapeHTML STRING | ||||||
| 229 | |||||||
| 230 | Replaces HTML syntax characters by HTML entities, so the text can be output safely. | ||||||
| 231 | You should always use this when displaying user input (or database output), | ||||||
| 232 | to avoid cross-site-scripting vurnerabilities. | ||||||
| 233 | |||||||
| 234 | In void context, B |
||||||
| 235 | |||||||
| 236 | <: EscapeHTML($user_input); print "$user_input"; :> |
||||||
| 237 | |||||||
| 238 | In other contexts, returns the changed version. | ||||||
| 239 | |||||||
| 240 | |||||||
| 241 | |||||||
| 242 | Be warned that single quotes are not substituted, so always use double quotes for attributes. | ||||||
| 243 | Also does not convert whitespace for formatted output; use Entity() for that. | ||||||
| 244 | |||||||
| 245 | To escape high-bit characters as well, refer to L |
||||||
| 246 | |||||||
| 247 | =item Entity LIST | ||||||
| 248 | |||||||
| 249 | Formats given arguments for literal display in HTML documents. | ||||||
| 250 | Similar to EscapeHTML(), but also preserves newlines and consecutive spaces | ||||||
| 251 | using corresponding C<< >> and C< > respectively. |
||||||
| 252 | |||||||
| 253 | In void context, B |
||||||
| 254 | |||||||
| 255 | <: print ' ' . Entity($user_input) . ' '; :> |
||||||
| 256 | |||||||
| 257 | Inside attributes, always use EscapeHTML() instead. | ||||||
| 258 | |||||||
| 259 | =item EncodeURI LIST | ||||||
| 260 | |||||||
| 261 | Encodes URI strings according to RFC 3986. All disallowed characters are replaced by their %-encoded values. | ||||||
| 262 | |||||||
| 263 | In void context, B |
||||||
| 264 | |||||||
| 265 | Link | ||||||
| 266 | |||||||
| 267 | Note that the following reserved characters are I |
||||||
| 268 | |||||||
| 269 | / ? : @ $ | ||||||
| 270 | |||||||
| 271 | This should be safe for escaping query values (as in the example above), | ||||||
| 272 | but otherwise it may be a better idea to use L |
||||||
| 273 | |||||||
| 274 | =item DecodeURI LIST | ||||||
| 275 | |||||||
| 276 | Decodes %-encoded strings. Unlike L |
||||||
| 277 | it also translates + characters to spaces (as browsers use those). | ||||||
| 278 | |||||||
| 279 | In void context, B |
||||||
| 280 | |||||||
| 281 | =item ReadFile FILENAME | ||||||
| 282 | |||||||
| 283 | Returns the contents of FILENAME in one large string. Returns undef on failure. | ||||||
| 284 | |||||||
| 285 | =item WriteFile FILENAME, STRING | ||||||
| 286 | |||||||
| 287 | Writes STRING to FILENAME (overwrites FILENAME if it already exists). Returns true on success, false on failure. | ||||||
| 288 | |||||||
| 289 | =item Counter FILENAME | ||||||
| 290 | |||||||
| 291 | Increases the contents of FILENAME by one and returns the new value. Returns undef on failure. Fails silently. | ||||||
| 292 | |||||||
| 293 | You are visitor number <:= Counter('counter.txt') :>. | ||||||
| 294 | |||||||
| 295 | =item AutoURL STRING | ||||||
| 296 | |||||||
| 297 | Replaces URLs (actually, replace things that look like URLs) by links. | ||||||
| 298 | |||||||
| 299 | In void context, B |
||||||
| 300 | |||||||
| 301 | <: print AutoURL(Entity($user_input)); :> | ||||||
| 302 | |||||||
| 303 | =item AddCookie STRING | ||||||
| 304 | |||||||
| 305 | Adds a Set-Cookie header. STRING must be a valid Set-Cookie header value. | ||||||
| 306 | |||||||
| 307 | =back | ||||||
| 308 | |||||||
| 309 | =head1 AUTHOR | ||||||
| 310 | |||||||
| 311 | Juerd Waalboer |
||||||
| 312 | |||||||
| 313 | Current maintainer: Mischa POSLAWSKY |
||||||
| 314 | |||||||
| 315 | =cut | ||||||
| 316 |