File Coverage

blib/lib/POE/Filter/HTTP/Parser.pm
Criterion Covered Total %
statement 111 118 94.0
branch 19 30 63.3
condition 17 35 48.5
subroutine 18 19 94.7
pod 6 6 100.0
total 171 208 82.2


line stmt bran cond sub pod time code
1             package POE::Filter::HTTP::Parser;
2             BEGIN {
3 5     5   420962 $POE::Filter::HTTP::Parser::VERSION = '1.06';
4             }
5              
6             # ABSTRACT: A HTTP POE filter for HTTP clients or servers
7              
8 5     5   38 use strict;
  5         10  
  5         140  
9 5     5   25 use warnings;
  5         8  
  5         152  
10 5     5   4326 use HTTP::Parser;
  5         197205  
  5         216  
11 5     5   55 use HTTP::Status qw(status_message RC_BAD_REQUEST RC_OK RC_LENGTH_REQUIRED);
  5         9  
  5         1052  
12 5     5   27 use base 'POE::Filter';
  5         9  
  5         4207  
13 5     5   7238 use Encode qw[encode_utf8];
  5         71162  
  5         6295  
14              
15             my %type_map = (
16             'server', 'request',
17             'client', 'response',
18             );
19              
20             sub new {
21 7     7 1 8100 my $class = shift;
22 7         34 my %opts = @_;
23 7         56 $opts{lc $_} = delete $opts{$_} for keys %opts;
24 7 100 66     85 if ( $opts{type} and defined $type_map{ $opts{type} } ) {
25 4         12 $opts{type} = $type_map{ $opts{type} };
26             }
27 7 50 33     81 $opts{type} = 'response' unless $opts{type} and $opts{type} =~ /^(request|response)$/;
28 7         15 my $self = \%opts;
29 7         19 $self->{BUFFER} = [];
30 7         67 $self->{parser} = HTTP::Parser->new( $self->{type} => 1 );
31 7         173 bless $self, $class;
32             }
33              
34             sub get_one_start {
35 12     12 1 37916 my ($self, $raw) = @_;
36 12         37 push @{ $self->{BUFFER} }, $_ for @$raw;
  12         68  
37             }
38              
39             sub get_one {
40 32     32 1 1506 my $self = shift;
41 32         52 my $events = [];
42              
43 32         42 my $string = shift @{ $self->{BUFFER} };
  32         67  
44 32 100       103 return [] unless $string;
45              
46 20         23 my $status;
47 20         32 eval { $status = $self->{parser}->add( $string ); };
  20         88  
48              
49 20 100 66     16602 if ( $@ and $self->{type} eq 'request' ) {
50             # Build a HTTP::Response error message
51 1         7 return [ $self->_build_error( RC_BAD_REQUEST, "

$@

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

Error $status: $message

" .
195             "

$details

" .
196             "" .
197             ""
198             ),
199             "text/html",
200             $status
201             );
202             }
203              
204             'I filter therefore I am';
205              
206              
207             __END__