File Coverage

blib/lib/HTTP/Daemon.pm
Criterion Covered Total %
statement 210 369 56.9
branch 69 198 34.8
condition 18 55 32.7
subroutine 30 39 76.9
pod 4 4 100.0
total 331 665 49.7


line stmt bran cond sub pod time code
1             package HTTP::Daemon; # git description: v6.13-4-ge6492b6
2              
3             # ABSTRACT: A simple http server class
4              
5 5     5   176067 use strict;
  5         52  
  5         128  
6 5     5   20 use warnings;
  5         5  
  5         174  
7              
8             our $VERSION = '6.14';
9              
10 5     5   2315 use Socket ();
  5         16514  
  5         171  
11 5     5   2672 use IO::Socket::IP;
  5         146176  
  5         24  
12             our @ISA = qw(IO::Socket::IP);
13              
14             our $PROTO = "HTTP/1.1";
15              
16             our $DEBUG;
17              
18             sub new {
19 4     4 1 5049 my ($class, %args) = @_;
20 4   50     176 $args{Listen} ||= 5;
21 4   50     151 $args{Proto} ||= 'tcp';
22              
23             # Handle undefined or empty local address the same way as
24             # IO::Socket::INET -- use unspecified address
25 4         26 for my $key (qw(LocalAddr LocalHost)) {
26 8 50 33     125 if (exists $args{$key} && (!defined $args{$key} || $args{$key} eq '')) {
      66        
27 0         0 delete $args{$key};
28             }
29             }
30 4         205 return $class->SUPER::new(%args);
31             }
32              
33             sub accept {
34 6     6 1 4698 my $self = shift;
35 6   50     144 my $pkg = shift || "HTTP::Daemon::ClientConn";
36 6         99 my ($sock, $peer) = $self->SUPER::accept($pkg);
37 6 50       4012297 if ($sock) {
38 6         16 ${*$sock}{'httpd_daemon'} = $self;
  6         55  
39 6 50       53 return wantarray ? ($sock, $peer) : $sock;
40             }
41             else {
42 0         0 return;
43             }
44             }
45              
46             sub url {
47 9     9 1 2953 my $self = shift;
48              
49 9         69 my $host = $self->sockhost;
50 9         676 $host =~ s/%/%25/g;
51 9 100       53 $host = "127.0.0.1" if $host eq "0.0.0.0";
52 9 50       36 $host = "::1" if $host eq "::";
53 9 100       55 $host = "[$host]" if $self->sockdomain == Socket::AF_INET6;
54              
55 9         147 my $url = $self->_default_scheme . "://" . $host;
56 9         52 my $port = $self->sockport;
57 9 50       388 $url .= ":$port" if $port != $self->_default_port;
58 9         16 $url .= "/";
59 9         234 $url;
60             }
61              
62             sub _default_port {
63 9     9   73 80;
64             }
65              
66             sub _default_scheme {
67 9     9   58 "http";
68             }
69              
70             sub product_tokens {
71 6     6 1 17 "libwww-perl-daemon/$HTTP::Daemon::VERSION";
72             }
73              
74             package # hide from PAUSE
75             HTTP::Daemon::ClientConn;
76              
77 5     5   4259 use strict;
  5         10  
  5         145  
78 5     5   24 use warnings;
  5         11  
  5         172  
79              
80 5     5   22 use IO::Socket::IP ();
  5         8  
  5         284  
81             our @ISA = qw(IO::Socket::IP);
82             our $DEBUG;
83             *DEBUG = \$HTTP::Daemon::DEBUG;
84              
85 5     5   2653 use HTTP::Request ();
  5         85566  
  5         113  
86 5     5   2155 use HTTP::Response ();
  5         29734  
  5         130  
87 5     5   38 use HTTP::Status;
  5         18  
  5         1044  
88 5     5   2117 use HTTP::Date qw(time2str);
  5         19013  
  5         314  
89 5     5   2107 use LWP::MediaTypes qw(guess_media_type);
  5         69115  
  5         374  
90 5     5   40 use Carp ();
  5         10  
  5         16499  
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 6     6   150 my ($self, $only_headers) = @_;
100 6 50       30 if (${*$self}{'httpd_nomore'}) {
  6         47  
101 0         0 $self->reason("No more requests from this connection");
102 0         0 return;
103             }
104              
105 6         119 $self->reason("");
106 6         10 my $buf = ${*$self}{'httpd_rbuf'};
  6         35  
107 6 50       24 $buf = "" unless defined $buf;
108              
109 6         18 my $timeout = ${*$self}{'io_socket_timeout'};
  6         28  
110 6         48 my $fdset = "";
111 6         90 vec($fdset, $self->fileno, 1) = 1;
112 6         111 local ($_);
113              
114             READ_HEADER:
115 6         13 while (1) {
116              
117             # loop until we have the whole header in $buf
118 12         114 $buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines
119 12 100       113 if ($buf =~ /\012/) { # potential, has at least one line
    50          
120 6 50       57 if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) {
121 6 50       50 if ($buf =~ /\015?\012\015?\012/) {
    0          
122 6         32 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 6 50       34 print STDERR "Need more data for complete header\n" if $DEBUG;
140 6 50       62 return unless $self->_need_more($buf, $timeout, $fdset);
141             }
142 6 50       75 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 6         47 my $method = $1;
149 6         42 my $uri = $2;
150 6   50     33 my $proto = $3 || "HTTP/0.9";
151 6 50       27 $uri = "http://$uri" if $method eq "CONNECT";
152 6         70 $uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url);
153 6         21225 my $r = HTTP::Request->new($method, $uri);
154 6         844 $r->protocol($proto);
155 6         59 ${*$self}{'httpd_client_proto'} = $proto = _http_version($proto);
  6         32  
156 6         11 ${*$self}{'httpd_head'} = ($method eq "HEAD");
  6         22  
157              
158 6 50       15 if ($proto >= $HTTP_1_0) {
159              
160             # we expect to find some headers
161 6         16 my ($key, $val);
162             HEADER:
163 6         64 while ($buf =~ s/^([^\012]*)\012//) {
164 29         55 $_ = $1;
165 29         107 s/\015$//;
166 29 100       100 if (/^([^:\s]+)\s*:\s*(.*)/) {
    50          
167 23 100       105 $r->push_header($key, $val) if $key;
168 23         956 ($key, $val) = ($1, $2);
169             }
170             elsif (/^\s+(.*)/) {
171 0         0 $val .= " $1";
172             }
173             else {
174 6         14 last HEADER;
175             }
176             }
177 6 50       20 $r->push_header($key, $val) if $key;
178             }
179              
180 6         197 my $conn = $r->header('Connection');
181 6 50       387 if ($proto >= $HTTP_1_1) {
182 6 100 66     36 ${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/;
  1         6  
183             }
184             else {
185 0 0 0     0 ${*$self}{'httpd_nomore'}++
  0         0  
186             unless $conn && lc($conn) =~ /\bkeep-alive\b/;
187             }
188              
189 6 50       10 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 6         17 my $te = $r->header('Transfer-Encoding');
196 6         207 my $ct = $r->header('Content-Type');
197 6         202 my $len = $r->header('Content-Length');
198              
199             # Act on the Expect header, if it's there
200 6         194 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 6 100 66     238 if ($te && lc($te) eq 'chunked') {
    50 33        
    100          
    50          
213              
214             # Handle chunked transfer encoding
215 4         7 my $body = "";
216             CHUNK:
217 4         7 while (1) {
218 1031 50       1525 print STDERR "Chunked\n" if $DEBUG;
219 1031 50       3364 if ($buf =~ s/^([^\012]*)\012//) {
220 1031         1851 my $chunk_head = $1;
221 1031 50       2048 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 1031         1473 my $size = hex($1);
227 1031 100       1347 last CHUNK if $size == 0;
228              
229 1027         1156 my $missing = $size - length($buf) + 2; # 2=CRLF at chunk end
230             # must read until we have a complete chunk
231 1027         1455 while ($missing > 0) {
232 555 50       805 print STDERR "Need $missing more bytes\n" if $DEBUG;
233 555         922 my $n = $self->_need_more($buf, $timeout, $fdset);
234 555 50       917 return unless $n;
235 555         904 $missing -= $n;
236             }
237 1027         3374 $body .= substr($buf, 0, $size);
238 1027         1725 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 4         14 $r->content($body);
247              
248             # pretend it was a normal entity body
249 4         1033 $r->remove_header('Transfer-Encoding');
250 4         147 $r->header('Content-Length', length($body));
251              
252 4         167 my ($key, $val);
253             FOOTER:
254 4         5 while (1) {
255 4 50       19 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 4         11 $buf =~ s/^([^\012]*)\012//;
262 4         9 $_ = $1;
263 4         13 s/\015$//;
264 4 50       11 if (/^([\w\-]+)\s*:\s*(.*)/) {
    50          
    50          
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 4         9 last FOOTER;
273             }
274             else {
275 0         0 $self->reason("Bad footer syntax");
276 0         0 return;
277             }
278             }
279             }
280 4 50       10 $r->push_header($key, $val) if $key;
281              
282             }
283             elsif ($te) {
284 0         0 $self->send_error(501); # Unknown transfer encoding
285 0         0 $self->reason("Unknown transfer encoding '$te'");
286 0         0 return;
287              
288             }
289             elsif ($len) {
290              
291             # Plain body specified by "Content-Length"
292 1         4 my $missing = $len - length($buf);
293 1         4 while ($missing > 0) {
294 0 0       0 print "Need $missing more bytes of content\n" if $DEBUG;
295 0         0 my $n = $self->_need_more($buf, $timeout, $fdset);
296 0 0       0 return unless $n;
297 0         0 $missing -= $n;
298             }
299 1 50       3 if (length($buf) > $len) {
300 0         0 $r->content(substr($buf, 0, $len));
301 0         0 substr($buf, 0, $len) = '';
302             }
303             else {
304 1         5 $r->content($buf);
305 1         34 $buf = '';
306             }
307             }
308             elsif ($ct && $ct =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*("?)(\w+)\1/i) {
309              
310             # Handle multipart content type
311 0         0 my $boundary = "$CRLF--$2--";
312 0         0 my $index;
313 0         0 while (1) {
314 0         0 $index = index($buf, $boundary);
315 0 0       0 last if $index >= 0;
316              
317             # end marker not yet found
318 0 0       0 return unless $self->_need_more($buf, $timeout, $fdset);
319             }
320 0         0 $index += length($boundary);
321 0         0 $r->content(substr($buf, 0, $index));
322 0         0 substr($buf, 0, $index) = '';
323              
324             }
325 6         11 ${*$self}{'httpd_rbuf'} = $buf;
  6         25  
326              
327 6         34 $r;
328             }
329              
330             sub _need_more {
331 561     561   643 my $self = shift;
332              
333             #my($buf,$timeout,$fdset) = @_;
334 561 50       741 if ($_[1]) {
335 0         0 my ($timeout, $fdset) = @_[1, 2];
336 0 0       0 print STDERR "select(,,,$timeout)\n" if $DEBUG;
337 0         0 my $n = select($fdset, undef, undef, $timeout);
338 0 0       0 unless ($n) {
339 0 0       0 $self->reason(defined($n) ? "Timeout" : "select: $!");
340 0         0 return;
341             }
342             }
343 561 50       747 print STDERR "sysread()\n" if $DEBUG;
344 561         4904 my $n = sysread($self, $_[0], 2048, length($_[0]));
345 561 0       1284 $self->reason(defined($n) ? "Client closed" : "sysread: $!") unless $n;
    50          
346 561         963 $n;
347             }
348              
349             sub read_buffer {
350 0     0   0 my $self = shift;
351 0         0 my $old = ${*$self}{'httpd_rbuf'};
  0         0  
352 0 0       0 if (@_) {
353 0         0 ${*$self}{'httpd_rbuf'} = shift;
  0         0  
354             }
355 0         0 $old;
356             }
357              
358             sub reason {
359 6     6   36 my $self = shift;
360 6         18 my $old = ${*$self}{'httpd_reason'};
  6         19  
361 6 50       37 if (@_) {
362 6         22 ${*$self}{'httpd_reason'} = shift;
  6         84  
363             }
364 6         24 $old;
365             }
366              
367             sub proto_ge {
368 0     0   0 my $self = shift;
369 0         0 ${*$self}{'httpd_client_proto'} >= _http_version(shift);
  0         0  
370             }
371              
372             sub _http_version {
373 16     16   44 local ($_) = shift;
374 16 50       148 return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i;
375 16         70 $1 * 1000 + $2;
376             }
377              
378             sub antique_client {
379 18     18   23 my $self = shift;
380 18         26 ${*$self}{'httpd_client_proto'} < $HTTP_1_0;
  18         65  
381             }
382              
383             sub force_last_request {
384 6     6   30 my $self = shift;
385 6         7 ${*$self}{'httpd_nomore'}++;
  6         24  
386             }
387              
388             sub head_request {
389 6     6   13 my $self = shift;
390 6         7 ${*$self}{'httpd_head'};
  6         46  
391             }
392              
393              
394             sub send_status_line {
395 6     6   44 my ($self, $status, $message, $proto) = @_;
396 6 50       20 return if $self->antique_client;
397 6   50     42 $status ||= RC_OK;
398 6   50     52 $message ||= status_message($status) || "";
      33        
399 6   50     101 $proto ||= $HTTP::Daemon::PROTO || "HTTP/1.1";
      33        
400 6         451 print $self "$proto $status $message$CRLF";
401             }
402              
403             sub send_crlf {
404 0     0   0 my $self = shift;
405 0         0 print $self $CRLF;
406             }
407              
408             sub send_basic_header {
409 6     6   131 my $self = shift;
410 6 50       14 return if $self->antique_client;
411 6         28 $self->send_status_line(@_);
412 6         61 print $self "Date: ", time2str(time), $CRLF;
413 6         311 my $product = $self->daemon->product_tokens;
414 6 50       197 print $self "Server: $product$CRLF" if $product;
415             }
416              
417             sub send_header {
418 0     0   0 my $self = shift;
419 0         0 while (@_) {
420 0         0 my ($k, $v) = splice(@_, 0, 2);
421 0 0       0 $v = "" unless defined($v);
422 0         0 print $self "$k: $v$CRLF";
423             }
424             }
425              
426             sub send_response {
427 6     6   1955 my $self = shift;
428 6         14 my $res = shift;
429 6 50       17 if (!ref $res) {
430 0   0     0 $res ||= RC_OK;
431 0         0 $res = HTTP::Response->new($res, @_);
432             }
433 6         25 my $content = $res->content;
434 6         60 my $chunked;
435 6 50       19 unless ($self->antique_client) {
436 6         49 my $code = $res->code;
437 6         56 $self->send_basic_header($code, $res->message, $res->protocol);
438 6 50 33     76 if ($code =~ /^(1\d\d|[23]04)$/) {
    50          
    50          
    100          
439              
440             # make sure content is empty
441 0         0 $res->remove_header("Content-Length");
442 0         0 $content = "";
443             }
444             elsif ($res->request && $res->request->method eq "HEAD") {
445              
446             # probably OK
447             }
448             elsif (ref($content) eq "CODE") {
449 0 0       0 if ($self->proto_ge("HTTP/1.1")) {
450 0         0 $res->push_header("Transfer-Encoding" => "chunked");
451 0         0 $chunked++;
452             }
453             else {
454 0         0 $self->force_last_request;
455             }
456             }
457             elsif (length($content)) {
458 5         94 $res->header("Content-Length" => length($content));
459             }
460             else {
461 1         20 $self->force_last_request;
462 1         6 $res->header('connection', 'close');
463             }
464 6         327 print $self $res->headers_as_string($CRLF);
465 6         610 print $self $CRLF; # separates headers and content
466             }
467 6 50       24 if ($self->head_request) {
    50          
    100          
468              
469             # no content
470             }
471             elsif (ref($content) eq "CODE") {
472 0         0 while (1) {
473 0         0 my $chunk = &$content();
474 0 0 0     0 last unless defined($chunk) && length($chunk);
475 0 0       0 if ($chunked) {
476 0         0 printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF;
477             }
478             else {
479 0         0 print $self $chunk;
480             }
481             }
482 0 0       0 print $self "0$CRLF$CRLF" if $chunked; # no trailers either
483             }
484             elsif (length $content) {
485 5         145 print $self $content;
486             }
487             }
488              
489             sub send_redirect {
490 0     0   0 my ($self, $loc, $status, $content) = @_;
491 0   0     0 $status ||= RC_MOVED_PERMANENTLY;
492 0 0       0 Carp::croak("Status '$status' is not redirect") unless is_redirect($status);
493 0         0 $self->send_basic_header($status);
494 0         0 my $base = $self->daemon->url;
495 0 0       0 $loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc);
496 0         0 $loc = $loc->abs($base);
497 0         0 print $self "Location: $loc$CRLF";
498              
499 0 0       0 if ($content) {
500 0 0       0 my $ct = $content =~ /^\s*
501 0         0 print $self "Content-Type: $ct$CRLF";
502             }
503 0         0 print $self $CRLF;
504 0 0 0     0 print $self $content if $content && !$self->head_request;
505 0         0 $self->force_last_request; # no use keeping the connection open
506             }
507              
508             sub send_error {
509 0     0   0 my ($self, $status, $error) = @_;
510 0   0     0 $status ||= RC_BAD_REQUEST;
511 0 0       0 Carp::croak("Status '$status' is not an error") unless is_error($status);
512 0         0 my $mess = status_message($status);
513 0   0     0 $error ||= "";
514 0         0 $mess = <
515             $status $mess
516            

$status $mess

517             $error
518             EOT
519 0 0       0 unless ($self->antique_client) {
520 0         0 $self->send_basic_header($status);
521 0         0 print $self "Content-Type: text/html$CRLF";
522 0         0 print $self "Content-Length: " . length($mess) . $CRLF;
523 0         0 print $self $CRLF;
524             }
525 0 0       0 print $self $mess unless $self->head_request;
526 0         0 $status;
527             }
528              
529             sub send_file_response {
530 0     0   0 my ($self, $file) = @_;
531 0 0       0 if (-d $file) {
    0          
532 0         0 $self->send_dir($file);
533             }
534             elsif (-f _) {
535              
536             # plain file
537 0         0 local (*F);
538 0 0       0 sysopen(F, $file, 0) or return $self->send_error(RC_FORBIDDEN);
539 0         0 binmode(F);
540 0         0 my ($ct, $ce) = guess_media_type($file);
541 0         0 my ($size, $mtime) = (stat _)[7, 9];
542 0 0       0 unless ($self->antique_client) {
543 0         0 $self->send_basic_header;
544 0         0 print $self "Content-Type: $ct$CRLF";
545 0 0       0 print $self "Content-Encoding: $ce$CRLF" if $ce;
546 0 0       0 print $self "Content-Length: $size$CRLF" if $size;
547 0 0       0 print $self "Last-Modified: ", time2str($mtime), "$CRLF" if $mtime;
548 0         0 print $self $CRLF;
549             }
550 0 0       0 $self->send_file(\*F) unless $self->head_request;
551 0         0 return RC_OK;
552             }
553             else {
554 0         0 $self->send_error(RC_NOT_FOUND);
555             }
556             }
557              
558             sub send_dir {
559 0     0   0 my ($self, $dir) = @_;
560 0 0       0 $self->send_error(RC_NOT_FOUND) unless -d $dir;
561 0         0 $self->send_error(RC_NOT_IMPLEMENTED);
562             }
563              
564             sub send_file {
565 0     0   0 my ($self, $file) = @_;
566 0         0 my $opened = 0;
567 0         0 local (*FILE);
568 0 0       0 if (!ref($file)) {
569 0 0       0 open(FILE, $file) || return undef;
570 0         0 binmode(FILE);
571 0         0 $file = \*FILE;
572 0         0 $opened++;
573             }
574 0         0 my $cnt = 0;
575 0         0 my $buf = "";
576 0         0 my $n;
577 0         0 while ($n = sysread($file, $buf, 8 * 1024)) {
578 0 0       0 last if !$n;
579 0         0 $cnt += $n;
580 0         0 print $self $buf;
581             }
582 0 0       0 close($file) if $opened;
583 0         0 $cnt;
584             }
585              
586             sub daemon {
587 12     12   32 my $self = shift;
588 12         31 ${*$self}{'httpd_daemon'};
  12         78  
589             }
590              
591              
592             1;
593              
594             __END__