File Coverage

blib/lib/Net/ICAP/Server.pm
Criterion Covered Total %
statement 32 176 18.1
branch 0 40 0.0
condition 0 15 0.0
subroutine 11 19 57.8
pod 2 2 100.0
total 45 252 17.8


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