File Coverage

blib/lib/Net/ICAP/Server.pm
Criterion Covered Total %
statement 33 179 18.4
branch 0 42 0.0
condition 0 15 0.0
subroutine 11 19 57.8
pod 2 2 100.0
total 46 257 17.9


line stmt bran cond sub pod time code
1             # Net::ICAP::Server -- ICAP Server Implementation
2             #
3             # (c) 2014, Arthur Corliss
4             #
5             # $Id: lib/Net/ICAP/Server.pm, v0.03 $
6             #
7             # This software is licensed under the same terms as Perl, itself.
8             # Please see http://dev.perl.org/licenses/ for more information.
9             #
10             #####################################################################
11              
12             #####################################################################
13             #
14             # Environment definitions
15             #
16             #####################################################################
17              
18             package Net::ICAP::Server;
19              
20 1     1   553 use 5.006;
  1         4  
  1         446  
21              
22 1     1   8 use strict;
  1         12  
  1         37  
23 1     1   6 use warnings;
  1         2  
  1         44  
24 1     1   5 use vars qw($VERSION @ISA @_properties @_methods);
  1         2  
  1         125  
25              
26             ($VERSION) = ( q$Revision: 0.03 $ =~ /(\d+(?:\.(\d+))+)/sm );
27              
28             @ISA = qw(Class::EHierarchy);
29              
30 1     1   1085 use Socket;
  1         4452  
  1         715  
31 1     1   1882 use IO::Socket::INET;
  1         20357  
  1         8  
32 1     1   1134 use Class::EHierarchy qw(:all);
  1         1  
  1         145  
33 1     1   491 use Net::ICAP;
  1         2  
  1         28  
34 1     1   4 use Net::ICAP::Common qw(:all);
  1         2  
  1         234  
35 1     1   5 use Paranoid::Debug;
  1         3  
  1         66  
36 1     1   827 use Paranoid::Process qw(:all);
  1         11348  
  1         1828  
37              
38             @_properties = (
39             [ CEH_RESTR | CEH_SCALAR, '_addr', '0.0.0.0' ],
40             [ CEH_RESTR | CEH_SCALAR, '_port', ICAP_DEF_PORT ],
41             [ CEH_RESTR | CEH_SCALAR, '_time-out', 60 ],
42             [ CEH_RESTR | CEH_SCALAR, '_max_requests', 0 ],
43             [ CEH_RESTR | CEH_SCALAR, '_max_children', 0 ],
44             [ CEH_RESTR | CEH_SCALAR, '_options_ttl', 0 ],
45             [ CEH_RESTR | CEH_HASH, '_services' ],
46             [ CEH_RESTR | CEH_CODE, '_reqmod' ],
47             [ CEH_RESTR | CEH_CODE, '_respmod' ],
48             [ CEH_RESTR | CEH_CODE, '_logger' ],
49             [ CEH_RESTR | CEH_CODE, '_istag', sub { return time } ],
50             );
51             @_methods = ();
52              
53             #####################################################################
54             #
55             # Net::ICAP::Server code follows
56             #
57             #####################################################################
58              
59             sub _initialize {
60 0     0     my $obj = shift;
61 0           my %args = @_;
62 0           my $rv = 1;
63 0           my @props = $obj->propertyNames;
64 0           my $a;
65              
66 0           pdebug( "entering w/$obj and @{[ scalar keys %args ]}", ICAPDEBUG1 );
  0            
67 0           pIn();
68              
69             # Set internal state
70 0           foreach $a ( keys %args ) {
71 0 0         if ( grep { $_ eq "_$a" } @props ) {
  0            
72 0 0         unless (
    0          
73 0           $obj->property(
74             "_$a", $a eq 'services'
75             ? %{ $args{$a} }
76             : $args{$a} )
77             ) {
78 0           pdebug( "failed to set $a", ICAPDEBUG1 );
79 0           $rv = 0;
80 0           last;
81             }
82             } else {
83 0           pdebug( "unknown argument: $a", ICAPDEBUG1 );
84 0           $rv = 0;
85 0           last;
86             }
87             }
88              
89 0           pOut();
90 0           pdebug( "leaving w/rv: $rv", ICAPDEBUG1 );
91              
92 0           return $rv;
93             }
94              
95             sub istag ($) {
96              
97             # Purpose: Returns code ref to ISTag generation function
98             # Returns: Code ref
99             # Usage: $code = $obj->istag;
100              
101 0     0 1   my $obj = shift;
102 0           return $obj->property('_istag');
103             }
104              
105             sub _drain ($$) {
106              
107             # Purpose: Drains input buffer until the connection is silent for 5s
108             # Returns: Boolean
109             # Usage: $rv = $obj->_drain($client);
110              
111 0     0     my $obj = shift;
112 0           my $client = shift;
113 0           my $rv = 1;
114 0           my ( $line, $lastInput );
115              
116 0           pdebug( "entering w/$client", ICAPDEBUG1 );
117 0           pIn();
118              
119 0 0         if ( defined $client ) {
120 0           $client->blocking(0);
121 0           $lastInput = time;
122 0           while ( time - $lastInput < 5 ) {
123 0           while ( defined( $line = $client->getline ) ) {
124 0           $lastInput = time;
125             }
126 0           sleep 0.1;
127             }
128             }
129              
130 0           pOut();
131 0           pdebug( "leaving w/rv: $rv", ICAPDEBUG1 );
132              
133 0           return $rv;
134             }
135              
136             sub _error ($;$) {
137              
138             # Purpose: Writes an error response to client
139             # Returns: Boolean
140             # Usage: $rv = $obj->_error($client);
141              
142 0     0     my $obj = shift;
143 0           my $status = shift;
144 0 0         my $s = defined $status ? $status : 'undef';
145 0           my $resp;
146              
147 0           pdebug( "entering w/$s", ICAPDEBUG1 );
148 0           pIn();
149              
150 0 0         $status = ICAP_BAD_REQUEST unless defined $status;
151 0           $resp = Net::ICAP::Response->new(
152             status => $status,
153             headers => {
154 0           ISTag => &{ $obj->istag },
155             Connection => 'close',
156             },
157             );
158              
159 0           pOut();
160 0           pdebug( "leaving w/rv: $resp", ICAPDEBUG1 );
161              
162 0           return $resp;
163             }
164              
165             sub _options ($$) {
166              
167             # Purpose: Returns an options response
168             # Returns: Response object
169             # Usage: $resp = _options($req);
170              
171 0     0     my $obj = shift;
172 0           my $request = shift;
173 0           my $response = Net::ICAP::Response->new(
174             status => ICAP_OK,
175             headers => {
176 0           ISTag => &{ $obj->istag },
177             'Max-Connections' => $obj->property('_max_children'),
178             Allow => 204,
179             },
180             );
181              
182 0 0         $response->header( 'Options-TTL', $obj->property('_options_ttl') )
183             if $obj->property('_options_ttl');
184              
185 0           return $response;
186             }
187              
188             sub _dispatch ($$$) {
189              
190             # Purpose: Calls the applicable function depending on the method
191             # Returns: Boolean
192             # Usage: $rv = $obj->_dispatch($client, $r);
193              
194 0     0     my $obj = shift;
195 0           my $client = shift;
196 0           my $request = shift;
197 0           my $reqmod = $obj->property('_reqmod');
198 0           my $respmod = $obj->property('_respmod');
199 0           my $logger = $obj->property('_logger');
200 0           my %services = $obj->property('_services');
201 0           my $rv = 1;
202 0           my ( $service, $method, $response, $r );
203              
204 0           pdebug( "entering w/$client, $request", ICAPDEBUG1 );
205 0           pIn();
206              
207 0           $service = $request->service;
208 0 0         if ( exists $services{$service} ) {
209 0           $method = $request->method;
210 0 0 0       if ( $method eq ICAP_OPTIONS or $services{$service} eq $method ) {
211 0 0 0       if ( $method eq ICAP_REQMOD and defined $reqmod ) {
    0 0        
    0          
212 0           $response = &$reqmod( $client, $request );
213             } elsif ( $method eq ICAP_RESPMOD and defined $respmod ) {
214 0           $response = &$respmod( $client, $request );
215             } elsif ( $method eq ICAP_OPTIONS ) {
216 0           $response = $obj->_options($request);
217 0           $response->header( 'Methods', $services{$service} );
218             } else {
219 0           $response = $obj->_error(ICAP_METHOD_NOT_IMPLEMENTED);
220 0           $rv = 0;
221             }
222             } else {
223 0           $response = $obj->_error(ICAP_METHOD_NOT_ALLOWED);
224 0           $rv = 0;
225             }
226             } else {
227 0           $response = $obj->_error(ICAP_SERVICE_NOT_FOUND);
228 0           $rv = 0;
229             }
230              
231             # Add ISTag
232 0           $response->header( 'ISTag', &{ $obj->istag } );
  0            
233              
234             # Log transaction
235 0 0         &$logger( $client, $request, $response ) if defined $logger;
236              
237             # Send the response to the client
238 0           $r = $response->generate($client);
239 0           $rv &= $r;
240              
241 0           pOut();
242 0           pdebug( "leaving w/rv: $rv", ICAPDEBUG1 );
243              
244 0           return $rv;
245             }
246              
247             sub _process ($$) {
248              
249             # Purpose: Processes ICAP traffic on the connection
250             # Returns: Boolean
251             # Usage: $rv = $obj->_process($client);
252              
253 0     0     my $obj = shift;
254 0           my $client = shift;
255 0           my $rv = 1;
256 0           my $counter = 0;
257 0           my $max_r = $obj->property('_max_requests');
258 0           my $logger = $obj->property('_logger');
259 0           my ( $req, $resp, $c );
260              
261 0           pdebug( "entering w/$client", ICAPDEBUG1 );
262 0           pIn();
263              
264 0   0       while ( $max_r == 0 or $counter < $max_r ) {
265 0           $req = new Net::ICAP::Request;
266 0 0         if ( $req->parse($client) ) {
267              
268             # Send request to dispatcher
269 0           $c = $req->header('Connection');
270 0 0         if ( $obj->_dispatch( $client, $req ) ) {
271 0 0 0       last if defined $c and $c eq 'close';
272 0           $counter++;
273             } else {
274 0           $rv = 0;
275 0           last;
276             }
277             } else {
278 0           $obj->_drain($client);
279 0           $resp = $obj->_error;
280 0           $resp->generate($client);
281 0 0         &$logger( $client, $req, $resp ) if defined $logger;
282 0           $rv = 0;
283 0           last;
284             }
285             }
286              
287 0           pOut();
288 0           pdebug( "leaving w/rv: $rv", ICAPDEBUG1 );
289              
290 0           return $rv;
291             }
292              
293             sub run ($) {
294              
295             # Purpose: Opens the socket and runs
296             # Returns: Boolean
297             # Usage: $rv = $obj->run;
298              
299 0     0 1   my $obj = shift;
300 0           my $addr = $obj->property('_addr');
301 0           my $port = $obj->property('_port');
302 0           my $maxChildren = $obj->property('_max_children');
303 0           my $queueSize = $maxChildren * 2;
304 0           my $rv = 1;
305 0           my ( $socket, $client, $cpid );
306              
307 0           pdebug( 'entering', ICAPDEBUG1 );
308 0           pIn();
309              
310 0           MAXCHILDREN = $maxChildren;
311 0           local $SIG{CHLD} = \&sigchld;
312              
313             # Open the socket
314 0 0         $socket = IO::Socket::INET->new(
315             ( $addr eq '0.0.0.0' ? (qw(MultiHomed 1)) : ( 'LocalAddr', $addr ) ),
316             LocalPort => $port,
317             Listen => $queueSize,
318             Type => SOCK_STREAM,
319             Reuse => 1,
320             );
321              
322 0 0         if ( defined $socket ) {
323 0           while (1) {
324 0           while ( $client = $socket->accept ) {
325 0 0         if ( defined( $cpid = pfork() ) ) {
326 0 0         unless ($cpid) {
327 0           $obj->_process($client);
328 0           $client->close;
329 0           exit 0;
330             }
331             } else {
332 0           pdebug(
333             "failed to fork child for incoming connection: $!",
334             ICAPDEBUG1 );
335 0           $rv = 0;
336             }
337             }
338             }
339             } else {
340 0           $rv = 0;
341 0           pdebug( "failed to open socket: $!", ICAPDEBUG1 );
342             }
343              
344 0           pOut();
345 0           pdebug( "leaving w/rv: $rv", ICAPDEBUG1 );
346              
347 0           return $rv;
348             }
349              
350             1;
351              
352             __END__