File Coverage

blib/lib/POE/Filter/FastCGI.pm
Criterion Covered Total %
statement 40 138 28.9
branch 0 56 0.0
condition 0 6 0.0
subroutine 13 24 54.1
pod 0 6 0.0
total 53 230 23.0


line stmt bran cond sub pod time code
1             package POE::Filter::FastCGI;
2             BEGIN {
3 2     2   344560 $POE::Filter::FastCGI::VERSION = '0.19';
4             }
5              
6 2     2   23 use strict;
  2         5  
  2         72  
7 2     2   11 use bytes;
  2         5  
  2         19  
8              
9             our(@ROLE, @TYPE);
10              
11             BEGIN {
12             # Some normal constants
13 2     2   106 use constant FCGI_VERSION_1 => 1;
  2         4  
  2         135  
14 2     2   17 use constant HEADER_LENGTH => 8;
  2         4  
  2         101  
15 2     2   25 use constant STATE_WAIT => 1;
  2         3  
  2         79  
16 2     2   10 use constant STATE_DATA => 2;
  2         3  
  2         97  
17              
18 2     2   11 use constant REQUEST_COMPLETE => 0;
  2         2  
  2         89  
19 2     2   10 use constant CANT_MPX_CONN => 1;
  2         3  
  2         91  
20 2     2   10 use constant OVERLOADED => 2;
  2         17  
  2         79  
21 2     2   9 use constant UNKNOWN_ROLE => 3;
  2         3  
  2         79  
22              
23             # Request flag constants
24 2     2   9 use constant FCGI_KEEP_CONN => 1;
  2         9  
  2         340  
25              
26             # Constant maps
27 2     2   8 @TYPE = qw(
28             NULL
29             BEGIN_REQUEST
30             ABORT_REQUEST
31             END_REQUEST
32             PARAMS
33             FCGI_STDIN
34             FCGI_STDOUT
35             FCGI_STDERR
36             );
37 2         4 my $c = 1;
38 2         300 constant->import($_ => $c++) for @TYPE[1 .. $#TYPE];
39              
40 2         7 @ROLE = qw(
41             NULL
42             RESPONDER
43             AUTHORIZER
44             FILTER
45             );
46 2         4 $c = 1;
47 2         3561 constant->import($_ => $c++) for @ROLE[1 .. $#ROLE];
48             }
49              
50             sub new {
51 0     0 0   my($class) = @_;
52 0           my $self = bless {
53             buffer => "",
54             conn => [ ],
55             state => STATE_WAIT,
56             }, $class;
57 0           return $self;
58             }
59              
60             sub get {
61 0     0 0   my($self, $stream) = @_;
62 0           $self->get_one_start($stream);
63 0           my(@out, $conn);
64 0           do {
65 0           $conn = $self->get_one;
66 0 0         push @out => @$conn if @$conn;
67             }while(@$conn);
68 0           return \@out;
69             }
70              
71             sub get_pending {
72 0     0 0   my($self) = @_;
73 0 0         return $self->{buffer} ? $self->{buffer} : undef;
74             }
75              
76             sub get_one {
77 0     0 0   my($self) = @_;
78              
79 0           while($self->{buffer}) {
80 0 0         if($self->{state} == STATE_WAIT) {
81 0 0         return [ ] unless length $self->{buffer} >= HEADER_LENGTH;
82              
83             # Remove FastCGI header from buffer
84 0           my $header = substr $self->{buffer}, 0, HEADER_LENGTH, "";
85              
86 0           @$self{qw/version type requestid contentlen padlen/} =
87             unpack "CCnnC", $header;
88              
89 0 0         warn "Wrong version, or direct request from a browser"
90             if $self->{version} != FCGI_VERSION_1;
91              
92 0 0         if($self->{contentlen}) {
93 0           $self->{state} = STATE_DATA;
94             }else{
95 0           my $conn = $self->_do_record;
96 0 0         return [$conn] if defined $conn;
97 0           next;
98             }
99             }
100              
101 0 0         if(length $self->{buffer} >= ($self->{contentlen} + $self->{padlen})) {
102             # Remove content from buffer
103 0           my $content = substr $self->{buffer}, 0, $self->{contentlen}, "";
104             # Remove padding
105 0           substr $self->{buffer}, 0, $self->{padlen}, "";
106              
107 0           my $conn = $self->_do_record($content);
108 0 0         return [$conn] if defined $conn;
109             } else {
110 0           return [ ];
111             }
112             }
113 0           return [ ];
114             }
115              
116             sub get_one_start {
117 0     0 0   my($self, $stream) = @_;
118 0           $self->{buffer} .= join '', @$stream;
119             }
120              
121             # Process FastCGI record
122             sub _do_record {
123 0     0     my($self, $content) = @_;
124              
125 0 0         if($self->{type} == BEGIN_REQUEST) {
126 0           my($role, $flags) = unpack "nC", $content;
127 0           $self->{conn}->[$self->{requestid}] = {
128             state => BEGIN_REQUEST,
129             flags => $flags,
130             role => $ROLE[$role],
131             cgi => { },
132             };
133              
134 0 0         $self->{conn}->[$self->{requestid}]{keepconn} = $flags & FCGI_KEEP_CONN ? 1 : 0;
135 0           return $self->_cleanup;
136             }
137              
138 0 0         return $self->_cleanup if not defined $self->{conn}->[$self->{requestid}];
139              
140 0           my $conn = $self->{conn}->[$self->{requestid}];
141 0           $conn->{state} = $self->{type};
142              
143 0 0         if($self->{type} == PARAMS) {
    0          
144 0 0         if(defined $content) {
145 0           my $offset = 0;
146 0           my($nlen, $vlen);
147 0   0       while(defined($nlen = _read_nv_len(\$content, \$offset)) &&
148             defined($vlen = _read_nv_len(\$content, \$offset))) {
149 0           my($name, $value) = (substr($content, $offset, $nlen),
150             substr($content, $offset + $nlen, $vlen));
151 0           $conn->{cgi}->{$name} = $value;
152 0           $offset += $nlen + $vlen;
153             }
154             }
155             }elsif($self->{type} == FCGI_STDIN) {
156 0 0         if(defined $content) {
157 0           $conn->{postdata} .= $content;
158             }else{
159 0           my $cgi = delete $conn->{cgi};
160 0           return [$self->{requestid}, $conn, $cgi];
161             }
162             }
163              
164 0           return $self->_cleanup;
165             }
166              
167             sub _cleanup {
168 0     0     my($self, $request) = @_;
169 0           delete @$self{qw/version type requestid contentlen padlen/};
170 0           $self->{state} = STATE_WAIT;
171 0           return $request;
172             }
173              
174             sub _read_nv_len {
175 0     0     my($dataref, $offsetref) = @_;
176 0           my $buf = substr($$dataref, $$offsetref++, 1);
177 0 0         return undef unless length $buf;
178 0           my $len = unpack("C", $buf);
179              
180 0 0         if($len & 0x80) { # High order bit set
181 0           $buf = substr($$dataref, $$offsetref, 3);
182 0 0         return undef unless $buf;
183 0           $$offsetref += 3;
184 0           $len = unpack("N", (pack("C", $len & 0x7f) . $buf));
185             }
186              
187 0           return $len;
188             }
189              
190             sub put {
191 0     0 0   my($self, $input) = @_;
192 0           my @output;
193              
194 0           for my $response(@$input) {
195 0 0         if(UNIVERSAL::isa($response, "POE::Component::FastCGI::Response")) {
    0          
196 0           $self->_write(\@output, $response->{requestid},
197             FCGI_STDOUT, $response->as_string);
198 0           $self->_close(\@output, $response->{requestid});
199             }elsif(ref $response eq "HASH") {
200 0 0         if(length $response->{content}) {
201 0           $self->_write(\@output, $response->{requestid},
202             FCGI_STDOUT, $response->{content});
203             }
204 0 0 0       if(exists $response->{close} and $response->{close}) {
205 0           $self->_close(\@output, $response->{requestid});
206             }
207             }else{
208 0           warn "Unhandled put";
209             }
210             }
211              
212 0           return [ join '', @output ];
213             }
214              
215             # Close a connection
216             sub _close {
217 0     0     my($self, $output, $id, $status, $appstatus) = @_;
218 0 0         $status = REQUEST_COMPLETE unless defined $status;
219 0 0         $self->_write($output, $id, FCGI_STDOUT, "") if $status == REQUEST_COMPLETE;
220 0 0         $self->_write($output, $id, END_REQUEST,
221             pack("NCx3", (defined $appstatus ? $appstatus : 0), $status, 0));
222 0           delete $self->{conn}->[$id];
223             }
224              
225             # Append FastCGI packets to @$output.
226             sub _write {
227 0     0     my ($self, $output, $id, $type, $content) = @_;
228 0           my $length = length $content;
229 0           my $offset = 0;
230              
231 0 0         if($length == 0) {
232             # Null packet
233 0           push @$output, pack("CCnnCx", FCGI_VERSION_1, $type, $id, 0, 0);
234 0           return;
235             }
236              
237             # Create as many 32KiB packets as needed
238 0           while ($length > 0) {
239 0 0         my $len = $length > 32*1024 ? 32*1024 : $length;
240 0           my $padlen = (8 - ($len % 8)) % 8;
241 0           push @$output, pack("CCnnCxa${len}x$padlen",
242             FCGI_VERSION_1, $type, $id, $len, $padlen,
243             substr($content, $offset, $len));
244              
245 0           $length -= $len;
246 0           $offset += $len;
247             }
248             }
249              
250             1;
251              
252             =head1 NAME
253              
254             POE::Filter::FastCGI - Parse and create FastCGI requests
255              
256             =head1 SYNOPSIS
257              
258             $fastcgi = POE::Filter::FastCGI->new;
259             $arrayref_with_binary_fastcgi_response = $fastcgi->put($put);
260             $arrayref_with_fastcgi_request_array = $fastcgi->get($chunks);
261              
262             =head1 DESCRIPTION
263              
264             Parses the FastCGI binary protocol into a perl array with the CGI
265             environment and any POST or other data that is sent.
266              
267             Accepts either L objects or a
268             simple hash reference via C and converts into the FastCGI
269             binary protocol. The hash reference should have keys of requestid
270             and content and an optional key of close to end the FastCGI
271             request.
272              
273             =head1 AUTHOR
274              
275             Copyright 2005, David Leadbeater L. All rights reserved.
276              
277             This library is free software; you can redistribute it and/or modify
278             it under the same terms as Perl itself.
279              
280             Some parts taken from FCGI's Pure Perl implementation.
281              
282             =head1 BUGS
283              
284             This is rather tightly coupled with L, ideally
285             there would be some form of intermediate perl object to use for FastCGI
286             like L can make use of L.
287              
288             This code is pure perl, it's probably slow compared to L (which is
289             mostly C) and it doesn't handle as many record types as L. However
290             L doesn't allow more than one concurrent request.
291              
292             =head1 SEE ALSO
293              
294             L, L, L.
295              
296             =cut