File Coverage

blib/lib/AnyEvent/HTTPD/HTTPConnection.pm
Criterion Covered Total %
statement 198 209 94.7
branch 67 92 72.8
condition 9 18 50.0
subroutine 28 31 90.3
pod 1 10 10.0
total 303 360 84.1


line stmt bran cond sub pod time code
1             package AnyEvent::HTTPD::HTTPConnection;
2 12     12   74 use common::sense;
  12         25  
  12         260  
3 12     12   14201 use IO::Handle;
  12         129192  
  12         1769  
4 12     12   114 use AnyEvent::Handle;
  12         25  
  12         278  
5 12     12   64 use Object::Event;
  12         24  
  12         2123  
6 12     12   41700 use Time::Local;
  12         40642  
  12         997  
7              
8 12     12   99 use AnyEvent::HTTPD::Util;
  12         26  
  12         966  
9              
10 12     12   108 use Scalar::Util qw/weaken/;
  12         22  
  12         58169  
11             our @ISA = qw/Object::Event/;
12              
13             =head1 NAME
14              
15             AnyEvent::HTTPD::HTTPConnection - A simple HTTP connection for request and response handling
16              
17             =head1 DESCRIPTION
18              
19             This class is a helper class for L<AnyEvent:HTTPD::HTTPServer> and L<AnyEvent::HTTPD>,
20             it handles TCP reading and writing as well as parsing and serializing
21             http requests.
22              
23             It has no public interface yet.
24              
25             =head1 COPYRIGHT & LICENSE
26              
27             Copyright 2008-2011 Robin Redeker, all rights reserved.
28              
29             This program is free software; you can redistribute it and/or modify it
30             under the same terms as Perl itself.
31              
32             =cut
33              
34             sub new {
35 15     15 1 35 my $this = shift;
36 15   33     97 my $class = ref($this) || $this;
37 15         129 my $self = { @_ };
38 15         55 bless $self, $class;
39              
40 15 50       134 $self->{request_timeout} = 60
41             unless defined $self->{request_timeout};
42              
43             $self->{hdl} =
44             AnyEvent::Handle->new (
45             fh => $self->{fh},
46 0     0   0 on_eof => sub { $self->do_disconnect },
47 2     2   144 on_error => sub { $self->do_disconnect ("Error: $!") },
48 15 50       206 ($self->{ssl}
49             ? (tls => "accept", tls_ctx => $self->{ssl})
50             : ()),
51             );
52              
53 15         1002 $self->push_header_line;
54              
55 15         1290 return $self
56             }
57              
58             sub error {
59 7     7 0 15 my ($self, $code, $msg, $hdr, $content) = @_;
60              
61 7 50       25 if ($code !~ /^(1\d\d|204|304)$/o) {
62 7 50       15 unless (defined $content) { $content = "$code $msg\n" }
  7         15  
63 7         17 $hdr->{'Content-Type'} = 'text/plain';
64             }
65              
66 7         17 $self->response ($code, $msg, $hdr, $content);
67             }
68              
69             sub response_done {
70 25     25 0 43 my ($self) = @_;
71              
72 25 100       90 (delete $self->{transfer_cb})->() if $self->{transfer_cb};
73              
74             # sometimes a response might be written after connection is already dead:
75 25 50 33     206 return unless defined ($self->{hdl}) && !$self->{disconnected};
76              
77 25         104 $self->{hdl}->on_drain; # clear any drain handlers
78              
79 25 100       177 if ($self->{keep_alive}) {
80 13         33 $self->push_header_line;
81              
82             } else {
83 12     12   96 $self->{hdl}->on_drain (sub { $self->do_disconnect });
  12         136  
84             }
85             }
86              
87             our @DoW = qw(Sun Mon Tue Wed Thu Fri Sat);
88             our @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
89             our %MoY;
90             @MoY{@MoY} = (1..12);
91              
92             # Taken from HTTP::Date module of LWP.
93             sub _time_to_http_date
94             {
95 25     25   44 my $time = shift;
96 25 50       89 $time = time unless defined $time;
97              
98 25         125 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($time);
99              
100 25         293 sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
101             $DoW[$wday],
102             $mday, $MoY[$mon], $year + 1900,
103             $hour, $min, $sec);
104             }
105              
106              
107             sub response {
108 25     25 0 77 my ($self, $code, $msg, $hdr, $content, $no_body) = @_;
109 25 50       80 return if $self->{disconnected};
110 25 50       89 return unless $self->{hdl};
111              
112 25         81 my $res = "HTTP/1.0 $code $msg\015\012";
113 25 50       103 header_set ($hdr, 'Date' => _time_to_http_date time)
114             unless header_exists ($hdr, 'Date');
115 25 100       82 header_set ($hdr, 'Expires' => header_get ($hdr, 'Date'))
116             unless header_exists ($hdr, 'Expires');
117 25 100       94 header_set ($hdr, 'Cache-Control' => "max-age=0")
118             unless header_exists ($hdr, 'Cache-Control');
119 25 100       121 header_set ($hdr, 'Connection' =>
120             ($self->{keep_alive} ? 'Keep-Alive' : 'close'));
121              
122 25 100 100     75 header_set ($hdr, 'Content-Length' => length "$content")
123             unless header_exists ($hdr, 'Content-Length')
124             || ref $content;
125              
126 25 100       78 unless (defined header_get ($hdr, 'Content-Length')) {
127             # keep alive with no content length will NOT work.
128 2         6 delete $self->{keep_alive};
129 2         6 header_set ($hdr, 'Connection' => 'close');
130             }
131              
132 25         127 while (my ($h, $v) = each %$hdr) {
133 148 100       269 next unless defined $v;
134 145         508 $res .= "$h: $v\015\012";
135             }
136              
137 25         52 $res .= "\015\012";
138              
139 25 100       77 if ($no_body) { # for HEAD requests!
140 3         24 $self->{hdl}->push_write ($res);
141 3         285 $self->response_done;
142 3         117 return;
143             }
144              
145 22 100       66 if (ref ($content) eq 'CODE') {
146 1         3 weaken $self;
147              
148             my $chunk_cb = sub {
149 5     5   163 my ($chunk) = @_;
150              
151 5 50 33     52 return 0 unless defined ($self) && defined ($self->{hdl}) && !$self->{disconnected};
      33        
152              
153 5         8 delete $self->{transport_polled};
154              
155 5 100 66     28 if (defined ($chunk) && length ($chunk) > 0) {
156 4         19 $self->{hdl}->push_write ($chunk);
157              
158             } else {
159 1         5 $self->response_done;
160             }
161              
162 5         72 return 1;
163 1         5 };
164              
165 1         4 $self->{transfer_cb} = $content;
166              
167             $self->{hdl}->on_drain (sub {
168 5 50   5   339 return unless $self;
169              
170 5 100       16 if (length $res) {
    50          
171 1         2 my $r = $res;
172 1         2 undef $res;
173 1         2 $chunk_cb->($r);
174              
175             } elsif (not $self->{transport_polled}) {
176 4         7 $self->{transport_polled} = 1;
177 4 50       19 $self->{transfer_cb}->($chunk_cb) if $self;
178             }
179 1         12 });
180              
181             } else {
182 21         41 $res .= $content;
183 21         112 $self->{hdl}->push_write ($res);
184 21         2078 $self->response_done;
185             }
186             }
187              
188             sub _unquote {
189 10     10   12 my ($str) = @_;
190 10 100       32 if ($str =~ /^"(.*?)"$/o) {
191 8         14 $str = $1;
192 8         10 my $obo = '';
193 8         28 while ($str =~ s/^(?:([^"]+)|\\(.))//so) {
194 8         21 $obo .= $1;
195             }
196 8         11 $str = $obo;
197             }
198             $str
199 10         19 }
200              
201             sub decode_part {
202 6     6 0 9 my ($self, $hdr, $cont) = @_;
203              
204 6         10 $hdr = _parse_headers ($hdr);
205 6 50       30 if ($hdr->{'content-disposition'} =~ /form-data|attachment/o) {
206 6         31 my ($dat, @pars) = split /\s*;\s*/o, $hdr->{'content-disposition'};
207 6         34 my @params;
208              
209             my %p;
210              
211 0         0 my @res;
212              
213 6         10 for my $name_para (@pars) {
214 8         26 my ($name, $par) = split /\s*=\s*/o, $name_para;
215 8 50       25 if ($par =~ /^".*"$/o) { $par = _unquote ($par) }
  8         12  
216 8         20 $p{$name} = $par;
217             }
218              
219 6         14 my ($ctype, $bound) = _content_type_boundary ($hdr->{'content-type'});
220              
221 6 100       12 if ($ctype eq 'multipart/mixed') {
222 1         5 my $parts = $self->decode_multipart ($cont, $bound);
223 1         6 for my $sp (keys %$parts) {
224 1         2 for (@{$parts->{$sp}}) {
  1         3  
225 2         9 push @res, [$p{name}, @$_];
226             }
227             }
228              
229             } else {
230 5         16 push @res, [$p{name}, $cont, $hdr->{'content-type'}, $p{filename}];
231             }
232              
233             return @res
234 6         31 }
235              
236 0         0 ();
237             }
238              
239             sub decode_multipart {
240 2     2 0 4 my ($self, $cont, $boundary) = @_;
241              
242 2         3 my $parts = {};
243              
244 2         82 while ($cont =~ s/
245             ^--\Q$boundary\E \015?\012
246             ((?:[^\015\012]+\015\012)* ) \015?\012
247             (.*?) \015?\012
248             (--\Q$boundary\E (--)? \015?\012)
249             /\3/xs) {
250 6         18 my ($h, $c, $e) = ($1, $2, $4);
251              
252 6 50       23 if (my (@p) = $self->decode_part ($h, $c)) {
253 6         7 for my $part (@p) {
254 7         6 push @{$parts->{$part->[0]}}, [$part->[1], $part->[2], $part->[3]];
  7         30  
255             }
256             }
257              
258 6 100       83 last if $e eq '--';
259             }
260              
261 2         4 return $parts;
262             }
263              
264             # application/x-www-form-urlencoded
265             #
266             # This is the default content type. Forms submitted with this content type must
267             # be encoded as follows:
268             #
269             # 1. Control names and values are escaped. Space characters are replaced by
270             # `+', and then reserved characters are escaped as described in [RFC1738],
271             # section 2.2: Non-alphanumeric characters are replaced by `%HH', a percent
272             # sign and two hexadecimal digits representing the ASCII code of the
273             # character. Line breaks are represented as "CR LF" pairs (i.e., `%0D%0A').
274             #
275             # 2. The control names/values are listed in the order they appear in the
276             # document. The name is separated from the value by `=' and name/value pairs
277             # are separated from each other by `&'.
278             #
279              
280             sub _content_type_boundary {
281 24     24   56 my ($ctype) = @_;
282 24         91 my ($c, @params) = split /\s*[;,]\s*/o, $ctype;
283 24         38 my $bound;
284 24         57 for (@params) {
285 4 100       21 if (/^\s*boundary\s*=\s*(.*?)\s*$/o) {
286 2         4 $bound = _unquote ($1);
287             }
288             }
289 24         280 ($c, $bound)
290             }
291              
292             sub handle_request {
293 18     18 0 49 my ($self, $method, $uri, $hdr, $cont) = @_;
294              
295 18         77 $self->{keep_alive} = ($hdr->{connection} =~ /keep-alive/io);
296              
297 18         113 my ($ctype, $bound) = _content_type_boundary ($hdr->{'content-type'});
298              
299 18 100       103 if ($ctype eq 'multipart/form-data') {
    50          
300 1         4 $cont = $self->decode_multipart ($cont, $bound);
301              
302             } elsif ($ctype =~ /x-www-form-urlencoded/o) {
303 0         0 $cont = parse_urlencoded ($cont);
304             }
305              
306 18         115 $self->event (request => $method, $uri, $hdr, $cont);
307             }
308              
309             # loosely adopted from AnyEvent::HTTP:
310             sub _parse_headers {
311 24     24   40 my ($header) = @_;
312 24         30 my $hdr;
313              
314 24         62 $header =~ y/\015//d;
315              
316 24         146 while ($header =~ /\G
317             ([^:\000-\037]+):
318             [\011\040]*
319             ( (?: [^\012]+ | \012 [\011\040] )* )
320             \012
321             /sgcxo) {
322              
323 49         299 $hdr->{lc $1} .= ",$2"
324             }
325              
326 24 50       105 return undef unless $header =~ /\G$/sgxo;
327              
328 24         86 for (keys %$hdr) {
329 49         69 substr $hdr->{$_}, 0, 1, '';
330             # remove folding:
331 49         93 $hdr->{$_} =~ s/\012([\011\040])/$1/sgo;
332             }
333              
334             $hdr
335 24         64 }
336              
337             sub push_header {
338 18     18 0 37 my ($self, $hdl) = @_;
339              
340             $self->{hdl}->unshift_read (line =>
341             qr{(?<![^\012])\015?\012}o,
342             sub {
343 18     18   2197 my ($hdl, $data) = @_;
344 18         72 my $hdr = _parse_headers ($data);
345              
346 18 50       62 unless (defined $hdr) {
347 0         0 $self->error (599 => "garbled headers");
348             }
349              
350 18         25 push @{$self->{last_header}}, $hdr;
  18         50  
351              
352 18 100       61 if (defined $hdr->{'content-length'}) {
353             $self->{hdl}->unshift_read (chunk => $hdr->{'content-length'}, sub {
354 3         98 my ($hdl, $data) = @_;
355 3         7 $self->handle_request (@{$self->{last_header}}, $data);
  3         14  
356 3         23 });
357             } else {
358 15         25 $self->handle_request (@{$self->{last_header}});
  15         72  
359             }
360             }
361 18         215 );
362             }
363              
364             sub push_header_line {
365 29     29 0 49 my ($self) = @_;
366              
367 29 50       92 return if $self->{disconnected};
368              
369 29         81 weaken $self;
370              
371             $self->{req_timeout} =
372             AnyEvent->timer (after => $self->{request_timeout}, cb => sub {
373 0 0   0   0 return unless defined $self;
374              
375 0         0 $self->do_disconnect ("request timeout ($self->{request_timeout})");
376 29         271 });
377              
378             $self->{hdl}->push_read (line => sub {
379 26     26   8507 my ($hdl, $line) = @_;
380 26 50       83 return unless defined $self;
381              
382 26         326 delete $self->{req_timeout};
383              
384 26 100       351 if ($line =~ /(\S+) \040 (\S+) \040 HTTP\/(\d+)\.(\d+)/xso) {
    100          
385 20         280 my ($meth, $url, $vm, $vi) = ($1, $2, $3, $4);
386              
387 20 100       212 if (not grep { $meth eq $_ } @{ $self->{allowed_methods} }) {
  60         981  
  20         56  
388 2         16 $self->error (501, "not implemented",
389 2         5 { Allow => join(",", @{ $self->{allowed_methods} })});
390 2         95 return;
391             }
392              
393 18 50       76 if ($vm >= 2) {
394 0         0 $self->error (506, "http protocol version not supported");
395 0         0 return;
396             }
397              
398 18         68 $self->{last_header} = [$meth, $url];
399 18         85 $self->push_header;
400              
401             } elsif ($line eq '') {
402             # ignore empty lines before requests, this prevents
403             # browser bugs w.r.t. keep-alive (according to marc lehmann).
404 1         3 $self->push_header_line;
405              
406             } else {
407 5         13 $self->error (400 => 'bad request');
408             }
409 29         847 });
410             }
411              
412             sub do_disconnect {
413 14     14 0 25 my ($self, $err) = @_;
414              
415 14 100       55 return if $self->{disconnected};
416              
417 12         49 $self->{disconnected} = 1;
418 12 50       162 $self->{transfer_cb}->() if $self->{transfer_cb};
419 12         26 delete $self->{transfer_cb};
420 12         26 delete $self->{req_timeout};
421 12         129 $self->event ('disconnect', $err);
422 12         1701 shutdown $self->{hdl}->{fh}, 1;
423 12     2   260 $self->{hdl}->on_read (sub { });
  2         108  
424 12         170 $self->{hdl}->on_eof (undef);
425 12         87 my $timer;
426             $timer = AE::timer 2, 0, sub {
427 0     0     undef $timer;
428 0           delete $self->{hdl};
429 12         100 };
430             }
431              
432             1;