File Coverage

blib/lib/HTTP/Daemon.pm
Criterion Covered Total %
statement 77 390 19.7
branch 13 206 6.3
condition 3 55 5.4
subroutine 19 39 48.7
pod 4 4 100.0
total 116 694 16.7


line stmt bran cond sub pod time code
1             package HTTP::Daemon; # git description: v6.15-42-g961eff5
2              
3             # ABSTRACT: A simple http server class
4              
5 2     2   204923 use strict;
  2         3  
  2         57  
6 2     2   11 use warnings;
  2         4  
  2         108  
7              
8             our $VERSION = '6.17';
9              
10 2     2   452 use Socket ();
  2         3207  
  2         45  
11 2     2   851 use IO::Socket::IP;
  2         50745  
  2         9  
12             our @ISA = qw(IO::Socket::IP);
13              
14             our $PROTO = "HTTP/1.1";
15              
16             our $DEBUG;
17              
18             sub new {
19 1     1 1 398674 my ($class, %args) = @_;
20 1   50     8 $args{Listen} ||= 5;
21 1   50     5 $args{Proto} ||= 'tcp';
22              
23             # Handle undefined or empty local address the same way as
24             # IO::Socket::INET -- use unspecified address
25 1         2 for my $key (qw(LocalAddr LocalHost)) {
26 2 0 0     6 if (exists $args{$key} && (!defined $args{$key} || $args{$key} eq '')) {
      33        
27 0         0 delete $args{$key};
28             }
29             }
30 1         11 return $class->SUPER::new(%args);
31             }
32              
33             sub accept {
34 0     0 1 0 my $self = shift;
35 0   0     0 my $pkg = shift || "HTTP::Daemon::ClientConn";
36 0         0 my ($sock, $peer) = $self->SUPER::accept($pkg);
37 0 0       0 if ($sock) {
38 0         0 ${*$sock}{'httpd_daemon'} = $self;
  0         0  
39 0 0       0 return wantarray ? ($sock, $peer) : $sock;
40             }
41             else {
42 0         0 return;
43             }
44             }
45              
46             sub url {
47 1     1 1 1859 my $self = shift;
48              
49 1         4 my $host = $self->sockhost;
50 1         7 $host =~ s/%/%25/g;
51 1 50       4 $host = "127.0.0.1" if $host eq "0.0.0.0";
52 1 50       2 $host = "::1" if $host eq "::";
53 1 50       3 $host = "[$host]" if $self->sockdomain == Socket::AF_INET6;
54              
55 1         8 my $url = $self->_default_scheme . "://" . $host;
56 1         7 my $port = $self->sockport;
57 1 50       47 $url .= ":$port" if $port != $self->_default_port;
58 1         2 $url .= "/";
59 1         7 $url;
60             }
61              
62             sub _default_port {
63 1     1   4 80;
64             }
65              
66             sub _default_scheme {
67 1     1   2 "http";
68             }
69              
70             sub product_tokens {
71 0     0 1 0 "libwww-perl-daemon/$HTTP::Daemon::VERSION";
72             }
73              
74             package # hide from PAUSE
75             HTTP::Daemon::ClientConn;
76              
77 2     2   1613 use strict;
  2         2  
  2         54  
78 2     2   6 use warnings;
  2         2  
  2         87  
79              
80 2     2   5 use IO::Socket::IP ();
  2         3  
  2         92  
81             our @ISA = qw(IO::Socket::IP);
82             our $DEBUG;
83             *DEBUG = \$HTTP::Daemon::DEBUG;
84              
85 2     2   848 use HTTP::Request ();
  2         36880  
  2         49  
86 2     2   847 use HTTP::Response ();
  2         13573  
  2         72  
87 2     2   13 use HTTP::Status;
  2         3  
  2         511  
88 2     2   740 use HTTP::Date qw(time2str);
  2         7372  
  2         134  
89 2     2   753 use LWP::MediaTypes qw(guess_media_type);
  2         20903  
  2         122  
90 2     2   11 use Carp ();
  2         4  
  2         6941  
91              
92             # "\r\n" is not portable
93             my $CRLF = "\015\012";
94             my $HTTP_1_0 = _http_version("HTTP/1.0");
95             my $HTTP_1_1 = _http_version("HTTP/1.1");
96              
97              
98             sub get_request {
99 0     0   0 my ($self, $only_headers) = @_;
100 0 0       0 if (${*$self}{'httpd_nomore'}) {
  0         0  
101 0         0 $self->reason("No more requests from this connection");
102 0         0 return;
103             }
104              
105 0         0 $self->reason("");
106 0         0 my $buf = ${*$self}{'httpd_rbuf'};
  0         0  
107 0 0       0 $buf = "" unless defined $buf;
108              
109 0         0 my $timeout = ${*$self}{'io_socket_timeout'};
  0         0  
110 0         0 my $fdset = "";
111 0         0 vec($fdset, $self->fileno, 1) = 1;
112 0         0 local ($_);
113              
114             READ_HEADER:
115 0         0 while (1) {
116              
117             # loop until we have the whole header in $buf
118 0         0 $buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines
119 0 0       0 if ($buf =~ /\012/) { # potential, has at least one line
    0          
120 0 0       0 if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) {
121 0 0       0 if ($buf =~ /\015?\012\015?\012/) {
    0          
122 0         0 last READ_HEADER; # we have it
123             }
124             elsif (length($buf) > 16 * 1024) {
125 0         0 $self->send_error(413); # REQUEST_ENTITY_TOO_LARGE
126 0         0 $self->reason("Very long header");
127 0         0 return;
128             }
129             }
130             else {
131 0         0 last READ_HEADER; # HTTP/0.9 client
132             }
133             }
134             elsif (length($buf) > 16 * 1024) {
135 0         0 $self->send_error(414); # REQUEST_URI_TOO_LARGE
136 0         0 $self->reason("Very long first line");
137 0         0 return;
138             }
139 0 0       0 print STDERR "Need more data for complete header\n" if $DEBUG;
140 0 0       0 return unless $self->_need_more($buf, $timeout, $fdset);
141             }
142 0 0       0 if ($buf !~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) {
143 0         0 ${*$self}{'httpd_client_proto'} = _http_version("HTTP/1.0");
  0         0  
144 0         0 $self->send_error(400); # BAD_REQUEST
145 0         0 $self->reason("Bad request line: $buf");
146 0         0 return;
147             }
148 0         0 my $method = $1;
149 0         0 my $uri = $2;
150 0   0     0 my $proto = $3 || "HTTP/0.9";
151 0 0       0 $uri = "http://$uri" if $method eq "CONNECT";
152 0         0 $uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url);
153 0         0 my $r = HTTP::Request->new($method, $uri);
154 0         0 $r->protocol($proto);
155 0         0 ${*$self}{'httpd_client_proto'} = $proto = _http_version($proto);
  0         0  
156 0         0 ${*$self}{'httpd_head'} = ($method eq "HEAD");
  0         0  
157              
158 0 0       0 if ($proto >= $HTTP_1_0) {
159              
160             # we expect to find some headers
161 0         0 my ($key, $val);
162             HEADER:
163 0         0 while ($buf =~ s/^([^\012]*)\012//) {
164 0         0 $_ = $1;
165 0         0 s/\015$//;
166 0 0       0 if (/^([^:\s]+)\s*:\s*(.*)/) {
    0          
167 0 0       0 $r->push_header($key, $val) if $key;
168 0         0 ($key, $val) = ($1, $2);
169             }
170             elsif (/^\s+(.*)/) {
171 0         0 $val .= " $1";
172             }
173             else {
174 0         0 last HEADER;
175             }
176             }
177 0 0       0 $r->push_header($key, $val) if $key;
178             }
179              
180 0         0 my $conn = $r->header('Connection');
181 0 0       0 if ($proto >= $HTTP_1_1) {
182 0 0 0     0 ${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/;
  0         0  
183             }
184             else {
185 0 0 0     0 ${*$self}{'httpd_nomore'}++
  0         0  
186             unless $conn && lc($conn) =~ /\bkeep-alive\b/;
187             }
188              
189 0 0       0 if ($only_headers) {
190 0         0 ${*$self}{'httpd_rbuf'} = $buf;
  0         0  
191 0         0 return $r;
192             }
193              
194             # Find out how much content to read
195 0         0 my $tr_enc = $r->header('Transfer-Encoding');
196 0         0 my $ct_type = $r->header('Content-Type');
197 0         0 my $ct_len = $r->header('Content-Length');
198              
199             # Act on the Expect header, if it's there
200 0         0 for my $e ($r->header('Expect')) {
201 0 0       0 if (lc($e) eq '100-continue') {
202 0         0 $self->send_status_line(100);
203 0         0 $self->send_crlf;
204             }
205             else {
206 0         0 $self->send_error(417);
207 0         0 $self->reason("Unsupported Expect header value");
208 0         0 return;
209             }
210             }
211              
212 0 0 0     0 if ($tr_enc && lc($tr_enc) eq 'chunked') {
    0 0        
    0          
    0          
213              
214             # Handle chunked transfer encoding
215 0         0 my $body = "";
216             CHUNK:
217 0         0 while (1) {
218 0 0       0 print STDERR "Chunked\n" if $DEBUG;
219 0 0       0 if ($buf =~ s/^([^\012]*)\012//) {
220 0         0 my $chunk_head = $1;
221 0 0       0 unless ($chunk_head =~ /^([0-9A-Fa-f]+)/) {
222 0         0 $self->send_error(400);
223 0         0 $self->reason("Bad chunk header $chunk_head");
224 0         0 return;
225             }
226 0         0 my $size = hex($1);
227 0 0       0 last CHUNK if $size == 0;
228              
229 0         0 my $missing = $size - length($buf) + 2; # 2=CRLF at chunk end
230             # must read until we have a complete chunk
231 0         0 while ($missing > 0) {
232 0 0       0 print STDERR "Need $missing more bytes\n" if $DEBUG;
233 0         0 my $n = $self->_need_more($buf, $timeout, $fdset);
234 0 0       0 return unless $n;
235 0         0 $missing -= $n;
236             }
237 0         0 $body .= substr($buf, 0, $size);
238 0         0 substr($buf, 0, $size + 2) = '';
239              
240             }
241             else {
242             # need more data in order to have a complete chunk header
243 0 0       0 return unless $self->_need_more($buf, $timeout, $fdset);
244             }
245             }
246 0         0 $r->content($body);
247              
248             # pretend it was a normal entity body
249 0         0 $r->remove_header('Transfer-Encoding');
250 0         0 $r->header('Content-Length', length($body));
251              
252 0         0 my ($key, $val);
253             FOOTER:
254 0         0 while (1) {
255 0 0       0 if ($buf !~ /\012/) {
256              
257             # need at least one line to look at
258 0 0       0 return unless $self->_need_more($buf, $timeout, $fdset);
259             }
260             else {
261 0         0 $buf =~ s/^([^\012]*)\012//;
262 0         0 $_ = $1;
263 0         0 s/\015$//;
264 0 0       0 if (/^([\w\-]+)\s*:\s*(.*)/) {
    0          
    0          
265 0 0       0 $r->push_header($key, $val) if $key;
266 0         0 ($key, $val) = ($1, $2);
267             }
268             elsif (/^\s+(.*)/) {
269 0         0 $val .= " $1";
270             }
271             elsif (!length) {
272 0         0 last FOOTER;
273             }
274             else {
275 0         0 $self->reason("Bad footer syntax");
276 0         0 return;
277             }
278             }
279             }
280 0 0       0 $r->push_header($key, $val) if $key;
281              
282             }
283             elsif ($tr_enc) {
284 0         0 $self->send_error(501); # Unknown transfer encoding
285 0         0 $self->reason("Unknown transfer encoding '$tr_enc'");
286 0         0 return;
287              
288             }
289             elsif ($ct_len) {
290              
291             # After a security issue, we ensure we comply to
292             # RFC-7230 -- HTTP/1.1 Message Syntax and Routing
293             # section 3.3.2 -- Content-Length
294             # section 3.3.3 -- Message Body Length
295              
296             # split and clean up Content-Length ', ' separated string
297             my @vals
298 0         0 = map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  0         0  
  0         0  
  0         0  
  0         0  
299             split ',', $ct_len;
300              
301             # check that they are all numbers (RFC: Content-Length = 1*DIGIT)
302 0         0 my @nums = grep {/^[0-9]+$/} @vals;
  0         0  
303 0 0       0 unless (@vals == @nums) {
304 0         0 my $reason = "Content-Length value must be an unsigned integer";
305 0         0 $self->send_error(400, $reason);
306 0         0 $self->reason($reason);
307 0         0 return;
308             }
309              
310             # check they are all the same
311 0         0 my $ct_len = shift @nums;
312 0         0 foreach (@nums) {
313 0 0       0 next if $_ == $ct_len;
314 0         0 my $reason = "Content-Length values are not the same";
315 0         0 $self->send_error(400, $reason);
316 0         0 $self->reason($reason);
317 0         0 return;
318             }
319              
320             # ensure we have now a fixed header, with only 1 value
321 0         0 $r->header('Content-Length' => $ct_len);
322              
323             # Plain body specified by "Content-Length"
324 0         0 my $missing = $ct_len - length($buf);
325 0         0 while ($missing > 0) {
326 0 0       0 print "Need $missing more bytes of content\n" if $DEBUG;
327 0         0 my $n = $self->_need_more($buf, $timeout, $fdset);
328 0 0       0 return unless $n;
329 0         0 $missing -= $n;
330             }
331 0 0       0 if (length($buf) > $ct_len) {
332 0         0 $r->content(substr($buf, 0, $ct_len));
333 0         0 substr($buf, 0, $ct_len) = '';
334             }
335             else {
336 0         0 $r->content($buf);
337 0         0 $buf = '';
338             }
339             }
340             elsif ($ct_type
341             && $ct_type =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*("?)(\w+)\1/i)
342             {
343              
344             # Handle multipart content type
345 0         0 my $boundary = "$CRLF--$2--";
346 0         0 my $index;
347 0         0 while (1) {
348 0         0 $index = index($buf, $boundary);
349 0 0       0 last if $index >= 0;
350              
351             # end marker not yet found
352 0 0       0 return unless $self->_need_more($buf, $timeout, $fdset);
353             }
354 0         0 $index += length($boundary);
355 0         0 $r->content(substr($buf, 0, $index));
356 0         0 substr($buf, 0, $index) = '';
357              
358             }
359 0         0 ${*$self}{'httpd_rbuf'} = $buf;
  0         0  
360              
361 0         0 $r;
362             }
363              
364             sub _need_more {
365 0     0   0 my $self = shift;
366              
367             #my($buf,$timeout,$fdset) = @_;
368 0 0       0 if ($_[1]) {
369 0         0 my ($timeout, $fdset) = @_[1, 2];
370 0 0       0 print STDERR "select(,,,$timeout)\n" if $DEBUG;
371 0         0 my $n = select($fdset, undef, undef, $timeout);
372 0 0       0 unless ($n) {
373 0 0       0 $self->reason(defined($n) ? "Timeout" : "select: $!");
374 0         0 return;
375             }
376             }
377 0 0       0 print STDERR "sysread()\n" if $DEBUG;
378 0         0 my $n = sysread($self, $_[0], 2048, length($_[0]));
379 0 0       0 $self->reason(defined($n) ? "Client closed" : "sysread: $!") unless $n;
    0          
380 0         0 $n;
381             }
382              
383             sub read_buffer {
384 0     0   0 my $self = shift;
385 0         0 my $old = ${*$self}{'httpd_rbuf'};
  0         0  
386 0 0       0 if (@_) {
387 0         0 ${*$self}{'httpd_rbuf'} = shift;
  0         0  
388             }
389 0         0 $old;
390             }
391              
392             sub reason {
393 0     0   0 my $self = shift;
394 0         0 my $old = ${*$self}{'httpd_reason'};
  0         0  
395 0 0       0 if (@_) {
396 0         0 ${*$self}{'httpd_reason'} = shift;
  0         0  
397             }
398 0         0 $old;
399             }
400              
401             sub proto_ge {
402 0     0   0 my $self = shift;
403 0         0 ${*$self}{'httpd_client_proto'} >= _http_version(shift);
  0         0  
404             }
405              
406             sub _http_version {
407 4     4   7 local ($_) = shift;
408 4 50       23 return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i;
409 4         15 $1 * 1000 + $2;
410             }
411              
412             sub antique_client {
413 0     0   0 my $self = shift;
414 0         0 ${*$self}{'httpd_client_proto'} < $HTTP_1_0;
  0         0  
415             }
416              
417             sub force_last_request {
418 0     0   0 my $self = shift;
419 0         0 ${*$self}{'httpd_nomore'}++;
  0         0  
420             }
421              
422             sub head_request {
423 0     0   0 my $self = shift;
424 0         0 ${*$self}{'httpd_head'};
  0         0  
425             }
426              
427              
428             sub send_status_line {
429 0     0   0 my ($self, $status, $message, $proto) = @_;
430 0 0       0 return if $self->antique_client;
431 0   0     0 $status ||= RC_OK;
432 0   0     0 $message ||= status_message($status) || "";
      0        
433 0   0     0 $proto ||= $HTTP::Daemon::PROTO || "HTTP/1.1";
      0        
434 0         0 print $self "$proto $status $message$CRLF";
435             }
436              
437             sub send_crlf {
438 0     0   0 my $self = shift;
439 0         0 print $self $CRLF;
440             }
441              
442             sub send_basic_header {
443 0     0   0 my $self = shift;
444 0 0       0 return if $self->antique_client;
445 0         0 $self->send_status_line(@_);
446 0         0 print $self "Date: ", time2str(time), $CRLF;
447 0         0 my $product = $self->daemon->product_tokens;
448 0 0       0 print $self "Server: $product$CRLF" if $product;
449             }
450              
451             sub send_header {
452 0     0   0 my $self = shift;
453 0         0 while (@_) {
454 0         0 my ($k, $v) = splice(@_, 0, 2);
455 0 0       0 $v = "" unless defined($v);
456 0         0 print $self "$k: $v$CRLF";
457             }
458             }
459              
460             sub send_response {
461 0     0   0 my $self = shift;
462 0         0 my $res = shift;
463 0 0       0 if (!ref $res) {
464 0   0     0 $res ||= RC_OK;
465 0         0 $res = HTTP::Response->new($res, @_);
466             }
467 0         0 my $content = $res->content;
468 0         0 my $chunked;
469 0 0       0 unless ($self->antique_client) {
470 0         0 my $code = $res->code;
471 0         0 $self->send_basic_header($code, $res->message, $res->protocol);
472 0 0 0     0 if ($code =~ /^(1\d\d|[23]04)$/) {
    0          
    0          
    0          
473              
474             # make sure content is empty
475 0         0 $res->remove_header("Content-Length");
476 0         0 $content = "";
477             }
478             elsif ($res->request && $res->request->method eq "HEAD") {
479              
480             # probably OK
481             }
482             elsif (ref($content) eq "CODE") {
483 0 0       0 if ($self->proto_ge("HTTP/1.1")) {
484 0         0 $res->push_header("Transfer-Encoding" => "chunked");
485 0         0 $chunked++;
486             }
487             else {
488 0         0 $self->force_last_request;
489             }
490             }
491             elsif (length($content)) {
492 0         0 $res->header("Content-Length" => length($content));
493             }
494             else {
495 0         0 $self->force_last_request;
496 0         0 $res->header('connection', 'close');
497             }
498 0         0 print $self $res->headers_as_string($CRLF);
499 0         0 print $self $CRLF; # separates headers and content
500             }
501 0 0       0 if ($self->head_request) {
    0          
    0          
502              
503             # no content
504             }
505             elsif (ref($content) eq "CODE") {
506 0         0 while (1) {
507 0         0 my $chunk = &$content();
508 0 0 0     0 last unless defined($chunk) && length($chunk);
509 0 0       0 if ($chunked) {
510 0         0 printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF;
511             }
512             else {
513 0         0 print $self $chunk;
514             }
515             }
516 0 0       0 print $self "0$CRLF$CRLF" if $chunked; # no trailers either
517             }
518             elsif (length $content) {
519 0         0 print $self $content;
520             }
521             }
522              
523             sub send_redirect {
524 0     0   0 my ($self, $loc, $status, $content) = @_;
525 0   0     0 $status ||= RC_MOVED_PERMANENTLY;
526 0 0       0 Carp::croak("Status '$status' is not redirect") unless is_redirect($status);
527 0         0 $self->send_basic_header($status);
528 0         0 my $base = $self->daemon->url;
529 0 0       0 $loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc);
530 0         0 $loc = $loc->abs($base);
531 0         0 print $self "Location: $loc$CRLF";
532              
533 0 0       0 if ($content) {
534 0 0       0 my $ct_type = $content =~ /^\s*
535 0         0 print $self "Content-Type: $ct_type$CRLF";
536             }
537 0         0 print $self $CRLF;
538 0 0 0     0 print $self $content if $content && !$self->head_request;
539 0         0 $self->force_last_request; # no use keeping the connection open
540             }
541              
542             sub send_error {
543 0     0   0 my ($self, $status, $error) = @_;
544 0   0     0 $status ||= RC_BAD_REQUEST;
545 0 0       0 Carp::croak("Status '$status' is not an error") unless is_error($status);
546 0         0 my $mess = status_message($status);
547 0   0     0 $error ||= "";
548 0         0 $mess = <
549             $status $mess
550            

$status $mess

551             $error
552             EOT
553 0 0       0 unless ($self->antique_client) {
554 0         0 $self->send_basic_header($status);
555 0         0 print $self "Content-Type: text/html$CRLF";
556 0         0 print $self "Content-Length: " . length($mess) . $CRLF;
557 0         0 print $self $CRLF;
558             }
559 0 0       0 print $self $mess unless $self->head_request;
560 0         0 $status;
561             }
562              
563             sub send_file_response {
564 0     0   0 my ($self, $file) = @_;
565 0 0       0 if (-d $file) {
    0          
566 0         0 $self->send_dir($file);
567             }
568             elsif (-f _) {
569              
570             # plain file
571 0         0 local (*F);
572 0 0       0 sysopen(F, $file, 0) or return $self->send_error(RC_FORBIDDEN);
573 0         0 binmode(F);
574 0         0 my ($mime_type, $file_enc) = guess_media_type($file);
575 0         0 my ($size, $mtime) = (stat _)[7, 9];
576 0 0       0 unless ($self->antique_client) {
577 0         0 $self->send_basic_header;
578 0         0 print $self "Content-Type: $mime_type$CRLF";
579 0 0       0 print $self "Content-Encoding: $file_enc$CRLF" if $file_enc;
580 0 0       0 print $self "Content-Length: $size$CRLF" if $size;
581 0 0       0 print $self "Last-Modified: ", time2str($mtime), "$CRLF" if $mtime;
582 0         0 print $self $CRLF;
583             }
584 0 0       0 $self->send_file(\*F) unless $self->head_request;
585 0         0 return RC_OK;
586             }
587             else {
588 0         0 $self->send_error(RC_NOT_FOUND);
589             }
590             }
591              
592             sub send_dir {
593 0     0   0 my ($self, $dir) = @_;
594 0 0       0 $self->send_error(RC_NOT_FOUND) unless -d $dir;
595 0         0 $self->send_error(RC_NOT_IMPLEMENTED);
596             }
597              
598             sub send_file {
599 5     5   406579 my ($self, $file) = @_;
600 5         8 my $opened = 0;
601 5 50       14 if (!ref($file)) {
602 5 100       135 open(my $fh, '<', $file) || return undef;
603 2 50       6 binmode($fh) || do { close($fh); return undef };
  0         0  
  0         0  
604 2         4 $file = $fh;
605 2         4 $opened++;
606             }
607 2         4 my $cnt = 0;
608 2         3 my $buf = "";
609 2         3 my $n;
610 2         21 while ($n = sysread($file, $buf, 8 * 1024)) {
611 1 50       3 last if !$n;
612 1         2 $cnt += $n;
613 1         5 print $self $buf;
614             }
615 2 50       13 close($file) if $opened;
616              
617             # Return a "true zero" for empty-but-successful copies so callers
618             # using `send_file or die` can distinguish open failure (undef)
619             # from a successful zero-byte transfer.
620 2 100       12 $cnt || '0E0';
621             }
622              
623             sub daemon {
624 0     0     my $self = shift;
625 0           ${*$self}{'httpd_daemon'};
  0            
626             }
627              
628              
629             1;
630              
631             __END__