File Coverage

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
    \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{
  • $item
  • \n};
    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__