File Coverage

blib/lib/POE/Filter/HTTP/Parser.pm
Criterion Covered Total %
statement 110 117 94.0
branch 18 30 60.0
condition 17 35 48.5
subroutine 17 18 94.4
pod 6 6 100.0
total 168 206 81.5


line stmt bran cond sub pod time code
1             package POE::Filter::HTTP::Parser;
2             $POE::Filter::HTTP::Parser::VERSION = '1.08';
3             # ABSTRACT: A HTTP POE filter for HTTP clients or servers
4              
5 5     5   178310 use strict;
  5         7  
  5         127  
6 5     5   18 use warnings;
  5         6  
  5         136  
7 5     5   2075 use HTTP::Parser;
  5         107469  
  5         159  
8 5     5   32 use HTTP::Status qw(status_message RC_BAD_REQUEST RC_OK RC_LENGTH_REQUIRED);
  5         6  
  5         521  
9 5     5   21 use base 'POE::Filter';
  5         5  
  5         2123  
10 5     5   3854 use Encode qw[encode_utf8];
  5         33992  
  5         3952  
11              
12             my %type_map = (
13             'server', 'request',
14             'client', 'response',
15             );
16              
17             sub new {
18 7     7 1 4515 my $class = shift;
19 7         19 my %opts = @_;
20 7         38 $opts{lc $_} = delete $opts{$_} for keys %opts;
21 7 100 66     55 if ( $opts{type} and defined $type_map{ $opts{type} } ) {
22 4         9 $opts{type} = $type_map{ $opts{type} };
23             }
24 7 50 33     57 $opts{type} = 'response' unless $opts{type} and $opts{type} =~ /^(request|response)$/;
25 7         10 my $self = \%opts;
26 7         12 $self->{BUFFER} = [];
27 7         41 $self->{parser} = HTTP::Parser->new( $self->{type} => 1 );
28 7         104 bless $self, $class;
29             }
30              
31             sub get_one_start {
32 12     12 1 10796 my ($self, $raw) = @_;
33 12         25 push @{ $self->{BUFFER} }, $_ for @$raw;
  12         38  
34             }
35              
36             sub get_one {
37 32     32 1 955 my $self = shift;
38 32         30 my $events = [];
39              
40 32         46 my $string = shift @{ $self->{BUFFER} };
  32         44  
41 32 100       70 return [] unless $string;
42              
43 20         15 my $status;
44 20         18 eval { $status = $self->{parser}->add( $string ); };
  20         49  
45              
46 20 100 66     10531 if ( $@ and $self->{type} eq 'request' ) {
47             # Build a HTTP::Response error message
48 1         5 return [ $self->_build_error( RC_BAD_REQUEST, "

$@

" ) ];
49             }
50              
51 19 0 33     34 if ( $@ and $self->{debug} ) {
52 0         0 warn "$@\n";
53 0         0 warn "Input was: '$string'\n";
54 0         0 return $events;
55             }
56              
57 19 50 33     90 if ( defined $status and $status == 0 ) {
58 19         62 push @$events, $self->{parser}->object();
59 19         70 my $data = $self->{parser}->data();
60 19 100       65 unshift @{ $self->{BUFFER} }, $data if $data;
  8         15  
61 19         42 $self->{parser} = HTTP::Parser->new( $self->{type} => 1 );
62             }
63              
64 19         236 return $events;
65             }
66              
67             sub _old_put {
68 0     0   0 my ($self, $chunks) = @_;
69 0         0 [ @$chunks ];
70             }
71              
72             sub put {
73 7     7 1 13450 my $self = shift;
74 7         4 my $return;
75 7 100       17 if ( $self->{type} eq 'request' ) {
76 4         11 $return = $self->_put_response( @_ );
77             }
78             else {
79 3         6 $return = $self->_put_request( @_ );
80             }
81 7         32 $return;
82             }
83              
84             sub _put_response {
85 4     4   4 my ($self, $responses) = @_;
86 4         5 my @raw;
87              
88             # HTTP::Response's as_string method returns the header lines
89             # terminated by "\n", which does not do the right thing if we want
90             # to send it to a client. Here I've stolen HTTP::Response's
91             # as_string's code and altered it to use network newlines so picky
92             # browsers like lynx get what they expect.
93              
94             # And this is shamelessly stolen from POE::Filter::HTTPD
95              
96 4         9 foreach (@$responses) {
97 4         9 my $code = $_->code;
98 4   50     31 my $status_message = status_message($code) || "Unknown Error";
99 4   50     23 my $message = $_->message || "";
100 4   50     33 my $proto = $_->protocol || 'HTTP/1.0';
101              
102 4         25 my $status_line = "$proto $code";
103 4 50       12 $status_line .= " ($status_message)" if $status_message ne $message;
104 4 50       10 $status_line .= " $message" if length($message);
105              
106             # Use network newlines, and be sure not to mangle newlines in the
107             # response's content.
108              
109 4         4 my @headers;
110 4         6 push @headers, $status_line;
111 4         12 push @headers, $_->headers_as_string("\x0D\x0A");
112              
113 4         134 push @raw, encode_utf8(join("\x0D\x0A", @headers, "")) . $_->content;
114             }
115              
116 4         46 \@raw;
117             }
118              
119             sub _put_request {
120 3     3   4 my ($self, $requests) = @_;
121 3         4 my @raw;
122              
123 3         7 foreach (@$requests) {
124 3   50     8 my $req_line = $_->method || "-";
125 3         26 my $uri = $_->uri;
126 3 50       22 $uri = (defined $uri) ? $uri->as_string : "-";
127 3         46 $req_line .= " $uri";
128 3         6 my $proto = $_->protocol;
129 3 50       20 $req_line .= " $proto" if $proto;
130              
131             # Use network newlines, and be sure not to mangle newlines in the
132             # response's content.
133              
134 3         4 my @headers;
135 3         5 push @headers, $req_line;
136 3         10 push @headers, $_->headers_as_string("\x0D\x0A");
137              
138 3         89 push @raw, encode_utf8(join("\x0D\x0A", @headers, "")) . $_->content;
139             }
140              
141 3         39 \@raw;
142             }
143              
144             sub clone {
145 6     6 1 3912 my $self = shift;
146 6         9 my $nself = { };
147 6         7 $nself->{$_} = $self->{$_} for keys %{ $self };
  6         46  
148 6         17 $nself->{BUFFER} = [ ];
149 6         21 $nself->{parser} = HTTP::Parser->new( $nself->{type} => 1 );
150 6         70 return bless $nself, ref $self;
151             }
152              
153             sub get_pending {
154 1     1 1 3 my $self = shift;
155 1         7 my $data = $self->{parser}->data();
156 1 50 50     6 return unless $data or scalar @{ $self->{BUFFER} };
  1         35  
157 0 0       0 return [ ( $data ? $data : () ), @{ $self->{BUFFER} } ];
  0         0  
158             }
159              
160             sub _build_basic_response {
161 1     1   13 my ($self, $content, $content_type, $status) = @_;
162              
163             # Need to check lengths in octets, not characters.
164 5 50   5   8 BEGIN { eval { require bytes } and bytes->import; }
  5         51  
165              
166 1   50     4 $content_type ||= 'text/html';
167 1   50     3 $status ||= RC_OK;
168              
169 1         9 my $response = HTTP::Response->new($status);
170              
171 1         66 $response->push_header( 'Content-Type', $content_type );
172 1         55 $response->push_header( 'Content-Length', length($content) );
173 1         19 $response->content($content);
174              
175 1         20 return $response;
176             }
177              
178             sub _build_error {
179 1     1   1 my($self, $status, $details) = @_;
180              
181 1   50     9 $status ||= RC_BAD_REQUEST;
182 1   50     2 $details ||= '';
183 1   50     4 my $message = status_message($status) || "Unknown Error";
184              
185 1         14 return $self->_build_basic_response(
186             ( "" .
187             "" .
188             "Error $status: $message" .
189             "" .
190             "" .
191             "

Error $status: $message

" .
192             "

$details

" .
193             "" .
194             ""
195             ),
196             "text/html",
197             $status
198             );
199             }
200              
201             'I filter therefore I am';
202              
203             __END__