File Coverage

lib/Net/FastCGI/Protocol/PP.pm
Criterion Covered Total %
statement 183 183 100.0
branch 159 160 99.3
condition 72 75 96.0
subroutine 34 34 100.0
pod 0 29 0.0
total 448 481 93.1


line stmt bran cond sub pod time code
1             package Net::FastCGI::Protocol::PP;
2 20     20   114 use strict;
  20         42  
  20         843  
3 20     20   101 use warnings;
  20         35  
  20         863  
4              
5 20     20   104 use Carp qw[];
  20         33  
  20         380  
6 20     20   102 use Net::FastCGI::Constant qw[:all];
  20         56  
  20         12799  
7              
8             BEGIN {
9 20     20   48 our $VERSION = '0.14';
10 20         106 our @EXPORT_OK = qw[ build_begin_request
11             build_begin_request_body
12             build_begin_request_record
13             build_end_request
14             build_end_request_body
15             build_end_request_record
16             build_header
17             build_params
18             build_record
19             build_stream
20             build_unknown_type_body
21             build_unknown_type_record
22             check_params
23             parse_begin_request_body
24             parse_end_request_body
25             parse_header
26             parse_params
27             parse_record
28             parse_record_body
29             parse_unknown_type_body
30             is_known_type
31             is_management_type
32             is_discrete_type
33             is_stream_type
34             get_record_length
35             get_role_name
36             get_type_name
37             get_protocol_status_name ];
38              
39 20         73 our %EXPORT_TAGS = ( all => \@EXPORT_OK );
40              
41 20         138 require Exporter;
42 20         136987 *import = \&Exporter::import;
43             }
44              
45             sub TRUE () { !!1 }
46             sub FALSE () { !!0 }
47              
48             sub ERRMSG_OCTETS () { q/FastCGI: Insufficient number of octets to parse %s/ }
49             sub ERRMSG_MALFORMED () { q/FastCGI: Malformed record %s/ }
50             sub ERRMSG_VERSION () { q/FastCGI: Protocol version mismatch (0x%.2X)/ }
51             sub ERRMSG_OCTETS_LE () { q/Invalid Argument: '%s' cannot exceed %u octets in length/ }
52              
53             sub throw {
54 96 100   96 0 750 @_ = ( sprintf($_[0], @_[1..$#_]) ) if @_ > 1;
55 96         2385 goto \&Carp::croak;
56             }
57              
58             # FCGI_Header
59              
60             sub build_header {
61 110 100   110 0 54126 @_ == 4 || throw(q/Usage: build_header(type, request_id, content_length, padding_length)/);
62 109         463 return pack(FCGI_Header, FCGI_VERSION_1, @_);
63             }
64              
65             sub parse_header {
66 61 100   61 0 10785 @_ == 1 || throw(q/Usage: parse_header(octets)/);
67 60 100 100     1537 (defined $_[0] && length $_[0] >= 8)
68             || throw(ERRMSG_OCTETS, q/FCGI_Header/);
69 58 100       3054 (vec($_[0], 0, 8) == FCGI_VERSION_1)
70             || throw(ERRMSG_VERSION, unpack('C', $_[0]));
71 56 100       371 return unpack('xCnnCx', $_[0])
72             if wantarray;
73 2         5 my %header;
74 2         19 @header{qw(type request_id content_length padding_length)}
75             = unpack('xCnnCx', $_[0]);
76 2         9 return \%header;
77             }
78              
79             # FCGI_BeginRequestBody
80              
81             sub build_begin_request_body {
82 17 100   17 0 2217 @_ == 2 || throw(q/Usage: build_begin_request_body(role, flags)/);
83 16         219 return pack(FCGI_BeginRequestBody, @_);
84             }
85              
86             sub parse_begin_request_body {
87 11 100   11 0 4127 @_ == 1 || throw(q/Usage: parse_begin_request_body(octets)/);
88 10 100 100     90 (defined $_[0] && length $_[0] >= 8)
89             || throw(ERRMSG_OCTETS, q/FCGI_BeginRequestBody/);
90 8         56 return unpack(FCGI_BeginRequestBody, $_[0]);
91             }
92              
93             # FCGI_EndRequestBody
94              
95             sub build_end_request_body {
96 17 100   17 0 2263 @_ == 2 || throw(q/Usage: build_end_request_body(app_status, protocol_status)/);
97 16         219 return pack(FCGI_EndRequestBody, @_);
98             }
99              
100             sub parse_end_request_body {
101 12 100   12 0 3698 @_ == 1 || throw(q/Usage: parse_end_request_body(octets)/);
102 11 100 100     79 (defined $_[0] && length $_[0] >= 8)
103             || throw(ERRMSG_OCTETS, q/FCGI_EndRequestBody/);
104 9         149 return unpack(FCGI_EndRequestBody, $_[0]);
105             }
106              
107             # FCGI_UnknownTypeBody
108              
109             sub build_unknown_type_body {
110 17 100   17 0 3132 @_ == 1 || throw(q/Usage: build_unknown_type_body(type)/);
111 16         273 return pack(FCGI_UnknownTypeBody, @_);
112             }
113              
114             sub parse_unknown_type_body {
115 18 100   18 0 6259 @_ == 1 || throw(q/Usage: parse_unknown_type_body(octets)/);
116 17 100 100     101 (defined $_[0] && length $_[0] >= 8)
117             || throw(ERRMSG_OCTETS, q/FCGI_UnknownTypeBody/);
118 15         60 return unpack(FCGI_UnknownTypeBody, $_[0]);
119             }
120              
121             # FCGI_BeginRequestRecord
122              
123             sub build_begin_request_record {
124 11 100   11 0 1253 @_ == 3 || throw(q/Usage: build_begin_request_record(request_id, role, flags)/);
125 10         15 my ($request_id, $role, $flags) = @_;
126 10         26 return build_record(FCGI_BEGIN_REQUEST, $request_id,
127             build_begin_request_body($role, $flags));
128             }
129              
130             # FCGI_EndRequestRecord
131              
132             sub build_end_request_record {
133 10 100   10 0 1318 @_ == 3 || throw(q/Usage: build_end_request_record(request_id, app_status, protocol_status)/);
134 9         15 my ($request_id, $app_status, $protocol_status) = @_;
135 9         26 return build_record(FCGI_END_REQUEST, $request_id,
136             build_end_request_body($app_status, $protocol_status));
137             }
138              
139             # FCGI_UnknownTypeRecord
140              
141             sub build_unknown_type_record {
142 3 100   3 0 4090 @_ == 1 || throw(q/Usage: build_unknown_type_record(type)/);
143 2         5 my ($type) = @_;
144 2         7 return build_record(FCGI_UNKNOWN_TYPE, FCGI_NULL_REQUEST_ID,
145             build_unknown_type_body($type));
146             }
147              
148             sub build_record {
149 60 100 100 60 0 22844 @_ == 2 || @_ == 3 || throw(q/Usage: build_record(type, request_id [, content])/);
150 57         92 my ($type, $request_id) = @_;
151              
152 57 100       166 my $content_length = defined $_[2] ? length $_[2] : 0;
153 57         119 my $padding_length = (8 - ($content_length % 8)) % 8;
154              
155 57 100       137 ($content_length <= FCGI_MAX_CONTENT_LEN)
156             || throw(ERRMSG_OCTETS_LE, q/content/, FCGI_MAX_CONTENT_LEN);
157              
158 56         141 my $res = build_header($type, $request_id, $content_length, $padding_length);
159              
160 56 100       138 if ($content_length) {
161 43         91 $res .= $_[2];
162             }
163              
164 56 100       149 if ($padding_length) {
165 8         36 $res .= "\x00" x $padding_length;
166             }
167              
168 56         181 return $res;
169             }
170              
171             sub parse_record {
172 53 100   53 0 62834 @_ == 1 || throw(q/Usage: parse_record(octets)/);
173 52         133 my ($type, $request_id, $content_length) = &parse_header;
174              
175 52 50       158 (length $_[0] >= FCGI_HEADER_LEN + $content_length)
176             || throw(ERRMSG_OCTETS, q/FCGI_Record/);
177              
178             return wantarray
179 52 100       285 ? ($type, $request_id, substr($_[0], FCGI_HEADER_LEN, $content_length))
180             : parse_record_body($type, $request_id,
181             substr($_[0], FCGI_HEADER_LEN, $content_length));
182             }
183              
184             sub parse_record_body {
185 64 100   64 0 69112 @_ == 3 || throw(q/Usage: parse_record_body(type, request_id, content)/);
186 62         116 my ($type, $request_id) = @_;
187              
188 62 100       245 my $content_length = defined $_[2] ? length $_[2] : 0;
189              
190 62 100       2037 ($content_length <= FCGI_MAX_CONTENT_LEN)
191             || throw(ERRMSG_OCTETS_LE, q/content/, FCGI_MAX_CONTENT_LEN);
192              
193 49         177 my %record = (type => $type, request_id => $request_id);
194 49 100 100     642 if ($type == FCGI_BEGIN_REQUEST) {
    100 100        
    100 100        
    100 100        
    100 100        
    100          
195 5 100 100     281 ($request_id != FCGI_NULL_REQUEST_ID && $content_length == 8)
196             || throw(ERRMSG_MALFORMED, q/FCGI_BeginRequestRecord/);
197 2         14 @record{ qw(role flags) } = parse_begin_request_body($_[2]);
198             }
199             elsif ($type == FCGI_ABORT_REQUEST) {
200 3 100 100     30 ($request_id != FCGI_NULL_REQUEST_ID && $content_length == 0)
201             || throw(ERRMSG_MALFORMED, q/FCGI_AbortRequestRecord/);
202             }
203             elsif ($type == FCGI_END_REQUEST) {
204 5 100 100     68 ($request_id != FCGI_NULL_REQUEST_ID && $content_length == 8)
205             || throw(ERRMSG_MALFORMED, q/FCGI_EndRequestRecord/);
206 2         15 @record{ qw(app_status protocol_status) }
207             = parse_end_request_body($_[2]);
208             }
209             elsif ( $type == FCGI_PARAMS
210             || $type == FCGI_STDIN
211             || $type == FCGI_STDOUT
212             || $type == FCGI_STDERR
213             || $type == FCGI_DATA) {
214 22 100       85 ($request_id != FCGI_NULL_REQUEST_ID)
215             || throw(ERRMSG_MALFORMED, $FCGI_RECORD_NAME[$type]);
216 12 100       55 $record{content} = $content_length ? $_[2] : '';
217             }
218             elsif ( $type == FCGI_GET_VALUES
219             || $type == FCGI_GET_VALUES_RESULT) {
220 8 100       40 ($request_id == FCGI_NULL_REQUEST_ID)
221             || throw(ERRMSG_MALFORMED, $FCGI_RECORD_NAME[$type]);
222 4         15 $record{values} = parse_params($_[2]);
223             }
224             elsif ($type == FCGI_UNKNOWN_TYPE) {
225 4 100 100     232 ($request_id == FCGI_NULL_REQUEST_ID && $content_length == 8)
226             || throw(ERRMSG_MALFORMED, q/FCGI_UnknownTypeRecord/);
227 1         5 $record{unknown_type} = parse_unknown_type_body($_[2]);
228             }
229             else {
230             # unknown record type, pass content so caller can decide appropriate action
231 2 100       8 $record{content} = $_[2] if $content_length;
232             }
233              
234 24         92 return \%record;
235             }
236              
237             # Reference implementation use 8192 (libfcgi)
238             sub FCGI_SEGMENT_LEN () { 32768 - FCGI_HEADER_LEN }
239              
240             sub build_stream {
241 37 100 100 37 0 4877 @_ == 3 || @_ == 4 || throw(q/Usage: build_stream(type, request_id, content [, terminate])/);
242 36         64 my ($type, $request_id, undef, $terminate) = @_;
243              
244 36 100       77 my $len = defined $_[2] ? length $_[2] : 0;
245 36         41 my $res = '';
246              
247 36 100       97 if ($len) {
248 15 100       30 if ($len < FCGI_SEGMENT_LEN) {
249 11         29 $res = build_record($type, $request_id, $_[2]);
250             }
251             else {
252 4         10 my $header = build_header($type, $request_id, FCGI_SEGMENT_LEN, 0);
253 4         5 my $off = 0;
254 4         9 while ($len >= FCGI_SEGMENT_LEN) {
255 4         6 $res .= $header;
256 4         174 $res .= substr($_[2], $off, FCGI_SEGMENT_LEN);
257 4         5 $len -= FCGI_SEGMENT_LEN;
258 4         9 $off += FCGI_SEGMENT_LEN;
259             }
260 4 100       9 if ($len) {
261 2         8 $res .= build_record($type, $request_id, substr($_[2], $off, $len));
262             }
263             }
264             }
265              
266 36 100       71 if ($terminate) {
267 31         57 $res .= build_header($type, $request_id, 0, 0);
268             }
269              
270 36         289 return $res;
271             }
272              
273             sub build_params {
274 31 100   31 0 19915 @_ == 1 || throw(q/Usage: build_params(params)/);
275 30         48 my ($params) = @_;
276 30         89 my $res = '';
277 30         120 while (my ($key, $val) = each(%$params)) {
278 25         55 for ($key, $val) {
279 50 100       104 my $len = defined $_ ? length : 0;
280 50 100       210 $res .= $len < 0x80 ? pack('C', $len) : pack('N', $len | 0x8000_0000);
281             }
282 25         40 $res .= $key;
283 25 100       116 $res .= $val if defined $val;
284             }
285 30         124 return $res;
286             }
287              
288             sub parse_params {
289 17 100   17 0 8843 @_ == 1 || throw(q/Usage: parse_params(octets)/);
290 16         31 my ($octets) = @_;
291              
292 16 100       45 (defined $octets)
293             || return +{};
294              
295 15         41 my ($params, $klen, $vlen) = ({}, 0, 0);
296 15         47 while (length $octets) {
297 20         39 for ($klen, $vlen) {
298 40 100       83 (1 <= length $octets)
299             || throw(ERRMSG_OCTETS, q/FCGI_NameValuePair/);
300 38         80 $_ = vec(substr($octets, 0, 1, ''), 0, 8);
301 38 100       91 next if $_ < 0x80;
302 4 100       12 (3 <= length $octets)
303             || throw(ERRMSG_OCTETS, q/FCGI_NameValuePair/);
304 2         14 $_ = vec(pack('C', $_ & 0x7F) . substr($octets, 0, 3, ''), 0, 32);
305             }
306 16 100       53 ($klen + $vlen <= length $octets)
307             || throw(ERRMSG_OCTETS, q/FCGI_NameValuePair/);
308 12         26 my $key = substr($octets, 0, $klen, '');
309 12         48 $params->{$key} = substr($octets, 0, $vlen, '');
310             }
311 7         29 return $params;
312             }
313              
314             sub check_params {
315 29 100   29 0 6044 @_ == 1 || throw(q/Usage: check_params(octets)/);
316 28 100       63 (defined $_[0])
317             || return FALSE;
318              
319 27         57 my ($len, $off, $klen, $vlen) = (length $_[0], 0, 0, 0);
320 27         59 while ($off < $len) {
321 28         60 for ($klen, $vlen) {
322 56 100       129 (($off += 1) <= $len)
323             || return FALSE;
324 54         88 $_ = vec($_[0], $off - 1, 8);
325 54 100       136 next if $_ < 0x80;
326 7 100       26 (($off += 3) <= $len)
327             || return FALSE;
328 5         20 $_ = vec(substr($_[0], $off - 4, 4), 0, 32) & 0x7FFF_FFFF;
329             }
330 24 100       102 (($off += $klen + $vlen) <= $len)
331             || return FALSE;
332             }
333 16         61 return TRUE;
334             }
335              
336             sub build_begin_request {
337 13 100 100 13 0 7911 (@_ >= 4 && @_ <= 6) || throw(q/Usage: build_begin_request(request_id, role, flags, params [, stdin [, data]])/);
338 8         11 my ($request_id, $role, $flags, $params) = @_;
339              
340 8         20 my $r = build_begin_request_record($request_id, $role, $flags)
341             . build_stream(FCGI_PARAMS, $request_id, build_params($params), TRUE);
342              
343 8 100       24 if (@_ > 4) {
344 6         17 $r .= build_stream(FCGI_STDIN, $request_id, $_[4], TRUE);
345 6 100       15 if (@_ > 5) {
346 3         10 $r .= build_stream(FCGI_DATA, $request_id, $_[5], TRUE);
347             }
348             }
349 8         24 return $r;
350             }
351              
352             sub build_end_request {
353 11 100 100 11 0 6897 (@_ >= 3 && @_ <= 5) || throw(q/Usage: build_end_request(request_id, app_status, protocol_status [, stdout [, stderr]])/);
354 7         13 my ($request_id, $app_status, $protocol_status) = @_;
355              
356 7         8 my $r;
357 7 100       21 if (@_ > 3) {
358 6         23 $r .= build_stream(FCGI_STDOUT, $request_id, $_[3], TRUE);
359 6 100       23 if (@_ > 4) {
360 3         9 $r .= build_stream(FCGI_STDERR, $request_id, $_[4], TRUE);
361             }
362             }
363 7         16 $r .= build_end_request_record($request_id, $app_status, $protocol_status);
364 7         31 return $r;
365             }
366              
367             sub get_record_length {
368 21 100   21 0 5058 @_ == 1 || throw(q/Usage: get_record_length(octets)/);
369 19 100 100     140 (defined $_[0] && length $_[0] >= FCGI_HEADER_LEN)
370             || return 0;
371 9         37 return FCGI_HEADER_LEN + vec($_[0], 2, 16) # contentLength
372             + vec($_[0], 6, 8); # paddingLength
373             }
374              
375             sub is_known_type {
376 16 100   16 0 5822 @_ == 1 || throw(q/Usage: is_known_type(type)/);
377 15         23 my ($type) = @_;
378 15   100     130 return ($type > 0 && $type <= FCGI_MAXTYPE);
379             }
380              
381             sub is_discrete_type {
382 15 100   15 0 1768 @_ == 1 || throw(q/Usage: is_discrete_type(type)/);
383 14         20 my ($type) = @_;
384 14   100     193 return ( $type == FCGI_BEGIN_REQUEST
385             || $type == FCGI_ABORT_REQUEST
386             || $type == FCGI_END_REQUEST
387             || $type == FCGI_GET_VALUES
388             || $type == FCGI_GET_VALUES_RESULT
389             || $type == FCGI_UNKNOWN_TYPE );
390             }
391              
392             sub is_management_type {
393 7 100   7 0 3168 @_ == 1 || throw(q/Usage: is_management_type(type)/);
394 6         11 my ($type) = @_;
395 6   100     62 return ( $type == FCGI_GET_VALUES
396             || $type == FCGI_GET_VALUES_RESULT
397             || $type == FCGI_UNKNOWN_TYPE );
398             }
399              
400             sub is_stream_type {
401 15 100   15 0 5640 @_ == 1 || throw(q/Usage: is_stream_type(type)/);
402 14         25 my ($type) = @_;
403 14   100     175 return ( $type == FCGI_PARAMS
404             || $type == FCGI_STDIN
405             || $type == FCGI_STDOUT
406             || $type == FCGI_STDERR
407             || $type == FCGI_DATA );
408             }
409              
410             sub get_type_name {
411 89 100   89 0 9571 @_ == 1 || throw(q/Usage: get_type_name(type)/);
412 88         120 my ($type) = @_;
413 88   66     663 return $FCGI_TYPE_NAME[$type] || sprintf('0x%.2X', $type);
414             }
415              
416             sub get_role_name {
417 11 100   11 0 4668 @_ == 1 || throw(q/Usage: get_role_name(role)/);
418 10         19 my ($role) = @_;
419 10   66     133 return $FCGI_ROLE_NAME[$role] || sprintf('0x%.4X', $role);
420             }
421              
422             sub get_protocol_status_name {
423 11 100   11 0 4097 @_ == 1 || throw(q/Usage: get_protocol_status_name(protocol_status)/);
424 10         18 my ($status) = @_;
425 10   66     120 return $FCGI_PROTOCOL_STATUS_NAME[$status] || sprintf('0x%.2X', $status);
426             }
427              
428             1;
429