blib/lib/POE/Component/Server/SimpleContent.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 177 | 228 | 77.6 |
branch | 50 | 92 | 54.3 |
condition | 21 | 54 | 38.8 |
subroutine | 29 | 33 | 87.8 |
pod | 11 | 12 | 91.6 |
total | 288 | 419 | 68.7 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package POE::Component::Server::SimpleContent; | ||||||
2 | $POE::Component::Server::SimpleContent::VERSION = '1.16'; | ||||||
3 | #ABSTRACT: The easy way to serve web content with POE::Component::Server::SimpleHTTP. | ||||||
4 | |||||||
5 | # We export some stuff | ||||||
6 | require Exporter; | ||||||
7 | @ISA = qw( Exporter ); | ||||||
8 | @EXPORT = qw(generate_301 generate_404 generate_403); | ||||||
9 | |||||||
10 | 7 | 7 | 114167 | use strict; | |||
7 | 14 | ||||||
7 | 212 | ||||||
11 | 7 | 7 | 55 | use warnings; | |||
7 | 11 | ||||||
7 | 197 | ||||||
12 | 7 | 7 | 26 | use Carp; | |||
7 | 8 | ||||||
7 | 603 | ||||||
13 | 7 | 7 | 3594 | use POE qw( Wheel::ReadWrite Filter::Stream ); | |||
7 | 272593 | ||||||
7 | 48 | ||||||
14 | 7 | 7 | 389865 | use CGI qw(:standard); | |||
7 | 158295 | ||||||
7 | 41 | ||||||
15 | 7 | 7 | 16736 | use URI::Escape; | |||
7 | 7864 | ||||||
7 | 451 | ||||||
16 | 7 | 7 | 3157 | use Filesys::Virtual::Plain; | |||
7 | 68246 | ||||||
7 | 215 | ||||||
17 | 7 | 7 | 2979 | use MIME::Types; | |||
7 | 37275 | ||||||
7 | 453 | ||||||
18 | 7 | 7 | 4364 | use Storable; | |||
7 | 16232 | ||||||
7 | 392 | ||||||
19 | 7 | 7 | 122 | use File::Basename; | |||
7 | 9 | ||||||
7 | 13124 | ||||||
20 | |||||||
21 | sub spawn { | ||||||
22 | 7 | 7 | 1 | 106 | my $package = shift; | ||
23 | 7 | 50 | 31 | croak "$package needs an even number of parameters" if @_ & 1; | |||
24 | 7 | 26 | my %params = @_; | ||||
25 | |||||||
26 | 7 | 56 | $params{lc $_} = delete $params{$_} for keys %params; | ||||
27 | |||||||
28 | die "$package requires a 'root_dir' argument\n" | ||||||
29 | 7 | 50 | 33 | 180 | unless $params{root_dir} and -d $params{root_dir}; | ||
30 | |||||||
31 | 7 | 50 | 33 | _massage_handlers( $params{handlers} ) if $params{handlers}; | |||
32 | 7 | 50 | 30 | $params{handlers} = { } unless $params{handlers}; | |||
33 | |||||||
34 | 7 | 15 | my $options = delete $params{'options'}; | ||||
35 | |||||||
36 | 7 | 19 | my $self = bless \%params, $package; | ||||
37 | |||||||
38 | $self->{vdir} = Filesys::Virtual::Plain->new( { root_path => $self->{root_dir} } ) | ||||||
39 | 7 | 50 | 127 | or die "Could not create a Filesys::Virtual::Plain object for $self->{root_dir}\n"; | |||
40 | |||||||
41 | 7 | 629 | $self->{mt} = MIME::Types->new(); | ||||
42 | |||||||
43 | 7 | 50 | 33 | 269392 | $self->{auto_index} = 1 unless defined ( $self->{auto_index} ) and $self->{auto_index} == 0; | ||
44 | 7 | 50 | 42 | $self->{index_file} = 'index.html' unless $self->{index_file}; | |||
45 | |||||||
46 | 7 | 100 | 30 | $self->{prefix_fix} = delete $self->{alias_path} if $self->{alias_path}; | |||
47 | |||||||
48 | 7 | 100 | 29 | $self->{prefix_fix} = quotemeta( $self->{prefix_fix} ) if $self->{prefix_fix}; | |||
49 | |||||||
50 | 7 | 14 | my $mm; | ||||
51 | |||||||
52 | 7 | 14 | eval { | ||||
53 | 7 | 1611 | require File::LibMagic; | ||||
54 | 0 | 0 | $mm = File::MMagic->new(); | ||||
55 | }; | ||||||
56 | |||||||
57 | 7 | 36 | $self->{mm} = $mm; | ||||
58 | |||||||
59 | 7 | 50 | 33 | 181 | $self->{session_id} = POE::Session->create( | ||
60 | object_states => [ | ||||||
61 | $self => { | ||||||
62 | request => '_request', | ||||||
63 | shutdown => '_shutdown', | ||||||
64 | -input => '_read_input', | ||||||
65 | -error => '_read_error', | ||||||
66 | }, | ||||||
67 | $self => [ qw(_start) ], | ||||||
68 | ], | ||||||
69 | ( ( defined ( $options ) and ref ( $options ) eq 'HASH' ) ? ( options => $options ) : () ), | ||||||
70 | )->ID(); | ||||||
71 | |||||||
72 | 7 | 929 | return $self; | ||||
73 | } | ||||||
74 | |||||||
75 | sub _start { | ||||||
76 | 7 | 7 | 1833 | my ($kernel,$self) = @_[KERNEL,OBJECT]; | |||
77 | |||||||
78 | 7 | 32 | $self->{session_id} = $_[SESSION]->ID(); | ||||
79 | |||||||
80 | 7 | 50 | 47 | if ( $self->{alias} ) { | |||
81 | 0 | 0 | $kernel->alias_set( $self->{alias} ); | ||||
82 | } else { | ||||||
83 | 7 | 196 | $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ ); | ||||
84 | } | ||||||
85 | |||||||
86 | 7 | 236 | return; | ||||
87 | } | ||||||
88 | |||||||
89 | sub request { | ||||||
90 | 30 | 30 | 1 | 44909 | my $self = shift; | ||
91 | 30 | 76 | $poe_kernel->post( $self->session_id() => 'request' => @_ ); | ||||
92 | } | ||||||
93 | |||||||
94 | sub _request { | ||||||
95 | 30 | 30 | 4198 | my ($kernel,$self,$request,$response) = @_[KERNEL,OBJECT,ARG0 .. ARG1]; | |||
96 | 30 | 69 | my $sender = $_[SENDER]->ID(); | ||||
97 | |||||||
98 | # Sanity check the $request and $response objects *sigh* | ||||||
99 | 30 | 50 | 33 | 315 | return unless $response and $response->isa("HTTP::Response"); | ||
100 | |||||||
101 | 30 | 50 | 33 | 162 | unless ( $request and $request->isa("HTTP::Request") ) { | ||
102 | 0 | 0 | $kernel->post( $sender => 'DONE' => $response ); | ||||
103 | 0 | 0 | return; | ||||
104 | } | ||||||
105 | |||||||
106 | 30 | 85 | my $path = uri_unescape( $request->uri->path ); | ||||
107 | 30 | 930 | my $realpath = $path; | ||||
108 | |||||||
109 | 30 | 100 | 85 | $realpath = $self->{prefix_path} . $path if $self->{prefix_path}; | |||
110 | 30 | 100 | 133 | $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix}; | |||
111 | |||||||
112 | SWITCH: { | ||||||
113 | 30 | 100 | 32 | if ( $self->{vdir}->test('d', $realpath) ) { | |||
30 | 123 | ||||||
114 | 18 | 100 | 2156 | if ( $path !~ /\/$/ ) { | |||
115 | 6 | 13 | $path .= '/'; | ||||
116 | 6 | 28 | $response = $self->_generate_301( $path, $response ); | ||||
117 | 6 | 16 | last SWITCH; | ||||
118 | } | ||||||
119 | 12 | 50 | 33 | 47 | if ( $self->{auto_index} and !$self->{vdir}->test('e', $realpath . $self->{index_file} ) ) { | ||
120 | 0 | 0 | $response = $self->_generate_dir_listing( $path, $response ); | ||||
121 | 0 | 0 | last SWITCH; | ||||
122 | } | ||||||
123 | 12 | 100 | 57 | if ( $self->{vdir}->test('e', $realpath . $self->{index_file} ) ) { | |||
124 | 6 | 617 | my ($filename, $directory, $suffix) = fileparse($self->{index_file}, keys %{ $self->{handlers} } ); | ||||
6 | 251 | ||||||
125 | 6 | 50 | 25 | if ( $suffix ) { | |||
126 | $kernel->post( | ||||||
127 | $self->{handlers}->{ $suffix }->{SESSION}, | ||||||
128 | $self->{handlers}->{ $suffix }->{EVENT}, | ||||||
129 | { | ||||||
130 | request => $request, | ||||||
131 | response => $response, | ||||||
132 | session => $sender, | ||||||
133 | script_name => $path . $self->{index_file}, | ||||||
134 | script_filename => $self->{vdir}->root_path() . $realpath . $self->{index_file}, | ||||||
135 | }, | ||||||
136 | 0 | 0 | ); | ||||
137 | 0 | 0 | return; | ||||
138 | } | ||||||
139 | 6 | 41 | $response = $self->_generate_content( $sender, $path . $self->{index_file}, $response ); | ||||
140 | 6 | 15 | last SWITCH; | ||||
141 | } | ||||||
142 | 6 | 548 | $response = $self->_generate_403( $response ); | ||||
143 | 6 | 17 | last SWITCH; | ||||
144 | } | ||||||
145 | 12 | 100 | 1232 | if ( $self->{vdir}->test('e', $realpath) ) { | |||
146 | 3 | 270 | my ($filename, $directory, $suffix) = fileparse($realpath, keys %{ $self->{handlers} } ); | ||||
3 | 90 | ||||||
147 | 3 | 50 | 11 | if ( $suffix ) { | |||
148 | $kernel->post( | ||||||
149 | $self->{handlers}->{ $suffix }->{SESSION}, | ||||||
150 | $self->{handlers}->{ $suffix }->{EVENT}, | ||||||
151 | { | ||||||
152 | request => $request, | ||||||
153 | response => $response, | ||||||
154 | session => $sender, | ||||||
155 | script_name => $path, | ||||||
156 | 3 | 17 | script_filename => $self->{vdir}->root_path() . $realpath, | ||||
157 | }, | ||||||
158 | ); | ||||||
159 | 3 | 239 | return; | ||||
160 | } | ||||||
161 | 0 | 0 | $response = $self->_generate_content( $sender, $path, $response ); | ||||
162 | 0 | 0 | last SWITCH; | ||||
163 | } | ||||||
164 | 9 | 797 | $response = $self->_generate_404( $response ); | ||||
165 | } | ||||||
166 | |||||||
167 | 27 | 100 | 129 | $kernel->post( $sender => 'DONE' => $response ) if defined $response; | |||
168 | 27 | 1793 | undef; | ||||
169 | } | ||||||
170 | |||||||
171 | sub shutdown { | ||||||
172 | 7 | 7 | 1 | 4028 | my $self = shift; | ||
173 | 7 | 28 | $poe_kernel->post( $self->session_id() => 'shutdown' => @_ ); | ||||
174 | } | ||||||
175 | |||||||
176 | sub _shutdown { | ||||||
177 | 7 | 7 | 1349 | my ($kernel,$self) = @_[KERNEL,OBJECT]; | |||
178 | |||||||
179 | 7 | 50 | 26 | if ( $self->{alias} ) { | |||
180 | 0 | 0 | $kernel->alias_remove( $_ ) for $kernel->alias_list(); | ||||
181 | } else { | ||||||
182 | 7 | 34 | $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ); | ||||
183 | } | ||||||
184 | 7 | 289 | undef; | ||||
185 | } | ||||||
186 | |||||||
187 | sub session_id { | ||||||
188 | 37 | 37 | 1 | 161 | return $_[0]->{session_id}; | ||
189 | } | ||||||
190 | |||||||
191 | # Alias for deprecated function | ||||||
192 | sub autoindex { | ||||||
193 | 0 | 0 | 0 | 0 | warn "autoindex is deprecated: please use auto_index"; | ||
194 | 0 | 0 | goto &auto_index; | ||||
195 | } | ||||||
196 | |||||||
197 | sub auto_index { | ||||||
198 | 6 | 6 | 1 | 3251 | my $self = shift; | ||
199 | 6 | 8 | my $value = shift; | ||||
200 | 6 | 50 | 26 | return $self->{auto_index} unless defined $value; | |||
201 | 6 | 16 | $self->{auto_index} = $value; | ||||
202 | } | ||||||
203 | |||||||
204 | sub index_file { | ||||||
205 | 0 | 0 | 1 | 0 | my $self = shift; | ||
206 | 0 | 0 | my $value = shift; | ||||
207 | 0 | 0 | 0 | return $self->{index_file} unless defined $value; | |||
208 | 0 | 0 | $self->{index_file} = $value; | ||||
209 | } | ||||||
210 | |||||||
211 | sub _generate_404 { | ||||||
212 | 9 | 9 | 16 | my $self = shift; | |||
213 | 9 | 50 | 47 | my $response = shift || return; | |||
214 | 9 | 28 | return generate_404( $response ); | ||||
215 | } | ||||||
216 | |||||||
217 | sub generate_404 { | ||||||
218 | 9 | 50 | 9 | 1 | 27 | my $response = shift || return; | |
219 | 9 | 50 | 49 | return unless $response->isa('HTTP::Response'); | |||
220 | 9 | 36 | $response->code( 404 ); | ||||
221 | 9 | 128 | $response->header( 'Content-Type', 'text/html' ); | ||||
222 | 9 | 492 | $response->content( start_html('404') . h1('Not Found') . end_html ); | ||||
223 | 9 | 19287 | return $response; | ||||
224 | } | ||||||
225 | |||||||
226 | sub _generate_403 { | ||||||
227 | 6 | 6 | 12 | my $self = shift; | |||
228 | 6 | 50 | 21 | my $response = shift || return; | |||
229 | 6 | 26 | return generate_403( $response ); | ||||
230 | } | ||||||
231 | |||||||
232 | sub generate_403 { | ||||||
233 | 6 | 50 | 6 | 1 | 19 | my $response = shift || return; | |
234 | 6 | 50 | 29 | return unless $response->isa('HTTP::Response'); | |||
235 | 6 | 28 | $response->code( 403 ); | ||||
236 | 6 | 67 | $response->header( 'Content-Type', 'text/html' ); | ||||
237 | 6 | 258 | $response->content( start_html('403') . h1('Forbidden') . end_html ); | ||||
238 | 6 | 1049 | return $response; | ||||
239 | } | ||||||
240 | |||||||
241 | sub _generate_301 { | ||||||
242 | 6 | 6 | 12 | my $self = shift; | |||
243 | 6 | 50 | 22 | my $path = shift || return; | |||
244 | 6 | 50 | 22 | my $response = shift || return; | |||
245 | 6 | 20 | return generate_301( $path, $response ); | ||||
246 | } | ||||||
247 | |||||||
248 | sub generate_301 { | ||||||
249 | 6 | 50 | 6 | 1 | 19 | my $path = shift || return; | |
250 | 6 | 50 | 19 | my $response = shift || return; | |||
251 | 6 | 50 | 30 | return unless $response->isa('HTTP::Response'); | |||
252 | 6 | 28 | $response->code( 301 ); | ||||
253 | 6 | 109 | $response->header( 'Location' => $path ); | ||||
254 | 6 | 504 | $response->header( 'Content-Type', 'text/html' ); | ||||
255 | 6 | 199 | $response->content( start_html('301') . h1('Moved Permanently') . ' The document has moved here. ' . end_html ); |
||||
256 | 6 | 15652 | return $response; | ||||
257 | } | ||||||
258 | |||||||
259 | sub _generate_dir_listing { | ||||||
260 | 0 | 0 | 0 | my $self = shift; | |||
261 | 0 | 0 | 0 | my $path = shift || return; | |||
262 | 0 | 0 | 0 | my $response = shift || return undef; | |||
263 | 0 | 0 | my $content = start_html('Index of ' . $path) . h1('Index of ' . $path) . qq{ \n
|
||||
264 | |||||||
265 | 0 | 0 | my $realpath = $path; | ||||
266 | 0 | 0 | 0 | $realpath = $self->{prefix_path} . $path if $self->{prefix_path}; | |||
267 | 0 | 0 | 0 | $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix}; | |||
268 | |||||||
269 | 0 | 0 | foreach my $item ( $self->{vdir}->list( $realpath ) ) { | ||||
270 | 0 | 0 | 0 | next if $item =~ /^\./; | |||
271 | 0 | 0 | $content .= qq{ |
||||
272 | } | ||||||
273 | |||||||
274 | 0 | 0 | $content .= qq{\n} . end_html; | ||||
275 | 0 | 0 | $response->code( 200 ); | ||||
276 | 0 | 0 | $response->header( 'Content-Type', 'text/html' ); | ||||
277 | 0 | 0 | $response->content( $content ); | ||||
278 | 0 | 0 | return $response; | ||||
279 | } | ||||||
280 | |||||||
281 | sub _read_input { | ||||||
282 | 6 | 6 | 1569 | ${ $_[OBJECT]{read}{$_[ARG1]}{content} } .= $_[ARG0]; | |||
6 | 38 | ||||||
283 | } | ||||||
284 | |||||||
285 | # Read finished | ||||||
286 | sub _read_error { | ||||||
287 | 6 | 6 | 15019 | my ($self, $kernel, $error, $wheelid) = @_[ OBJECT, KERNEL, ARG1, ARG3 ]; | |||
288 | 6 | 20 | my $read = delete $self->{read}{$wheelid}; | ||||
289 | 6 | 15 | my $response = delete $read->{response}; | ||||
290 | 6 | 14 | my $content = delete $read->{content}; | ||||
291 | 6 | 14 | my $mimetype = delete $read->{mimetype}; | ||||
292 | 6 | 16 | my $sender = delete $read->{sender}; | ||||
293 | |||||||
294 | 6 | 37 | delete $read->{wheel}; | ||||
295 | |||||||
296 | 6 | 50 | 1245 | if ($error) { | |||
297 | 0 | 0 | $response->content("Internal Server Error"); | ||||
298 | 0 | 0 | $response->code(500); | ||||
299 | } | ||||||
300 | else { | ||||||
301 | 6 | 50 | 41 | unless ( $mimetype ) { | |||
302 | 0 | 0 | 0 | if ( $self->{mm} ) { | |||
303 | 0 | 0 | $mimetype = $self->{mm}->checktype_contents( $$content ); | ||||
304 | } | ||||||
305 | else { | ||||||
306 | 0 | 0 | $mimetype = 'application/octet-stream'; | ||||
307 | } | ||||||
308 | } | ||||||
309 | 6 | 80 | $response->code( 200 ); | ||||
310 | 6 | 101 | $response->content_type( $mimetype ); | ||||
311 | 6 | 224 | $response->content_ref( $content ); | ||||
312 | } | ||||||
313 | |||||||
314 | 6 | 109 | $kernel->post( $sender => 'DONE' => $response ); | ||||
315 | } | ||||||
316 | |||||||
317 | sub _generate_content { | ||||||
318 | 6 | 6 | 13 | my $self = shift; | |||
319 | 6 | 50 | 24 | my $sender = shift || return; | |||
320 | 6 | 50 | 29 | my $path = shift || return; | |||
321 | 6 | 50 | 23 | my $response = shift || return; | |||
322 | 6 | 9 | my $realpath = $path; | ||||
323 | 6 | 100 | 32 | $realpath = $self->{prefix_path} . $path if $self->{prefix_path}; | |||
324 | 6 | 100 | 36 | $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix}; | |||
325 | |||||||
326 | 6 | 41 | my $mimetype = $self->{mt}->mimeTypeOf( $path ); | ||||
327 | |||||||
328 | 6 | 50 | 825 | if ( my $fh = $self->{vdir}->open_read( $realpath ) ) { | |||
329 | 6 | 806 | binmode($fh); | ||||
330 | 6 | 50 | 33 | 50 | if ( $^O eq 'MSWin32' or $self->{blocking} ) { | ||
331 | 0 | 0 | local $/ = undef; | ||||
332 | 0 | 0 | my $content = <$fh>; | ||||
333 | 0 | 0 | 0 | unless ( $mimetype ) { | |||
334 | 0 | 0 | 0 | if ( $self->{mm} ) { | |||
335 | 0 | 0 | $mimetype = $self->{mm}->checktype_contents( $content ); | ||||
336 | } | ||||||
337 | else { | ||||||
338 | 0 | 0 | $mimetype = 'application/octet-stream'; | ||||
339 | } | ||||||
340 | } | ||||||
341 | 0 | 0 | $response->code( 200 ); | ||||
342 | 0 | 0 | $response->content_type( $mimetype ); | ||||
343 | 0 | 0 | $response->content_ref( \$content ); | ||||
344 | } else { | ||||||
345 | 6 | 65 | my $readwrite = POE::Wheel::ReadWrite->new( | ||||
346 | Handle => $fh, | ||||||
347 | Filter => POE::Filter::Stream->new(), | ||||||
348 | InputEvent => "-input", | ||||||
349 | ErrorEvent => "-error", | ||||||
350 | ); | ||||||
351 | |||||||
352 | 6 | 1994 | my $content = ""; | ||||
353 | |||||||
354 | 6 | 39 | my $wheelid = $readwrite->ID; | ||||
355 | 6 | 75 | my $readheap = { | ||||
356 | wheel => $readwrite, | ||||||
357 | response => $response, | ||||||
358 | mimetype => $mimetype, | ||||||
359 | sender => $sender, | ||||||
360 | content => \$content, | ||||||
361 | }; | ||||||
362 | |||||||
363 | 6 | 18 | $self->{read}{$wheelid} = $readheap; | ||||
364 | |||||||
365 | 6 | 15 | return; | ||||
366 | } | ||||||
367 | } else { | ||||||
368 | 0 | 0 | $response = $self->_generate_404( $response ); | ||||
369 | } | ||||||
370 | |||||||
371 | 0 | 0 | return $response; | ||||
372 | } | ||||||
373 | |||||||
374 | sub _massage_handlers { | ||||||
375 | 3 | 50 | 3 | 8 | my $handler = shift || return; | ||
376 | 3 | 50 | 33 | 21 | croak( "HANDLERS is not a ref to an hash!" ) | ||
377 | unless ref $handler and ref $handler eq 'HASH'; | ||||||
378 | 3 | 5 | foreach my $ext ( keys %{ $handler } ) { | ||||
3 | 10 | ||||||
379 | 3 | 50 | 11 | delete $handler->{ $ext } unless ref $handler->{ $ext } eq 'HASH'; | |||
380 | croak( "HANDLER for '$ext' does not have a SESSION argument!" ) | ||||||
381 | 3 | 50 | 12 | unless $handler->{ $ext }->{'SESSION'}; | |||
382 | croak( "HANDLER for '$ext' does not have an EVENT argument!" ) | ||||||
383 | 3 | 50 | 18 | unless $handler->{ $ext }->{'EVENT'}; | |||
384 | $handler->{ $ext }->{'SESSION'} = $handler->{ $ext }->{'SESSION'}->ID() | ||||||
385 | 3 | 50 | 17 | if UNIVERSAL::isa( $handler->{ $ext }->{'SESSION'}, 'POE::Session' ); | |||
386 | } | ||||||
387 | 3 | 6 | return 1; | ||||
388 | } | ||||||
389 | |||||||
390 | sub get_handlers { | ||||||
391 | 0 | 0 | 1 | 0 | my $self = shift; | ||
392 | 0 | 0 | my $handlers = Storable::dclone( $self->{handlers} ); | ||||
393 | 0 | 0 | return $handlers; | ||||
394 | } | ||||||
395 | |||||||
396 | sub set_handlers { | ||||||
397 | 3 | 3 | 1 | 2690 | my $self = shift; | ||
398 | 3 | 50 | 9 | my $handlers = shift || return; | |||
399 | 3 | 9 | _massage_handlers( $handlers ); | ||||
400 | 3 | 3 | $self->{handlers} = $handlers; | ||||
401 | 3 | 6 | return 1; | ||||
402 | } | ||||||
403 | |||||||
404 | qq[Content Simples]; | ||||||
405 | |||||||
406 | __END__ |