File Coverage

blib/lib/POE/Component/Server/SimpleContent.pm
Criterion Covered Total %
statement 182 235 77.4
branch 51 94 54.2
condition 21 54 38.8
subroutine 30 34 88.2
pod 11 12 91.6
total 295 429 68.7


line stmt bran cond sub pod time code
1             package POE::Component::Server::SimpleContent;
2              
3             # We export some stuff
4             require Exporter;
5             @ISA = qw( Exporter );
6             @EXPORT = qw(generate_301 generate_404 generate_403);
7              
8 8     8   220520 use strict;
  8         19  
  8         361  
9 8     8   44 use warnings;
  8         17  
  8         229  
10 8     8   46 use Carp;
  8         16  
  8         783  
11 8     8   11167 use POE qw( Wheel::ReadWrite Filter::Stream );
  8         530852  
  8         191  
12 8     8   1151873 use CGI qw(:standard);
  8         160731  
  8         73  
13 8     8   42700 use URI::Escape;
  8         16616  
  8         548  
14 8     8   8020 use Filesys::Virtual::Plain;
  8         244097  
  8         311  
15 8     8   8204 use MIME::Types;
  8         52533  
  8         372  
16 8     8   21889 use Storable;
  8         30762  
  8         636  
17 8     8   150 use File::Basename;
  8         17  
  8         751  
18 8     8   44 use vars qw($VERSION);
  8         13  
  8         54186  
19              
20             $VERSION = '1.14';
21              
22             sub spawn {
23 7     7 1 115 my $package = shift;
24 7 50       38 croak "$package needs an even number of parameters" if @_ & 1;
25 7         39 my %params = @_;
26              
27 7         70 $params{lc $_} = delete $params{$_} for keys %params;
28              
29 7 50 33     258 die "$package requires a 'root_dir' argument\n"
30             unless $params{root_dir} and -d $params{root_dir};
31              
32 7 50       37 _massage_handlers( $params{handlers} ) if $params{handlers};
33 7 50       49 $params{handlers} = { } unless $params{handlers};
34              
35 7         24 my $options = delete $params{'options'};
36              
37 7         30 my $self = bless \%params, $package;
38              
39 7 50       195 $self->{vdir} = Filesys::Virtual::Plain->new( { root_path => $self->{root_dir} } )
40             or die "Could not create a Filesys::Virtual::Plain object for $self->{root_dir}\n";
41              
42 7         998 $self->{mt} = MIME::Types->new();
43              
44 7 50 33     444420 $self->{auto_index} = 1 unless defined ( $self->{auto_index} ) and $self->{auto_index} == 0;
45 7 50       46 $self->{index_file} = 'index.html' unless $self->{index_file};
46              
47 7 100       41 $self->{prefix_fix} = delete $self->{alias_path} if $self->{alias_path};
48              
49 7 100       32 $self->{prefix_fix} = quotemeta( $self->{prefix_fix} ) if $self->{prefix_fix};
50              
51 7         11 my $mm;
52              
53 7         22 eval {
54 7         3773 require File::MMagic::XS;
55 0         0 import File::MMagic::XS qw(:compat);
56 0         0 $mm = File::MMagic::XS->new();
57             };
58              
59 7 50       55 eval {
60 7         3084 require File::MMagic;
61 0         0 $mm = File::MMagic->new();
62             } unless $mm;
63            
64 7         260 $self->{mm} = $mm;
65              
66 7 50 33     197 $self->{session_id} = POE::Session->create(
67             object_states => [
68             $self => {
69             request => '_request',
70             shutdown => '_shutdown',
71             -input => '_read_input',
72             -error => '_read_error',
73             },
74             $self => [ qw(_start) ],
75             ],
76             ( ( defined ( $options ) and ref ( $options ) eq 'HASH' ) ? ( options => $options ) : () ),
77             )->ID();
78              
79 7         1446 return $self;
80             }
81              
82             sub _start {
83 7     7   2153 my ($kernel,$self) = @_[KERNEL,OBJECT];
84              
85 7         40 $self->{session_id} = $_[SESSION]->ID();
86              
87 7 50       63 if ( $self->{alias} ) {
88 0         0 $kernel->alias_set( $self->{alias} );
89             } else {
90 7         41 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
91             }
92              
93 7         279 return;
94             }
95              
96             sub request {
97 30     30 1 74689 my $self = shift;
98 30         108 $poe_kernel->post( $self->session_id() => 'request' => @_ );
99             }
100              
101             sub _request {
102 30     30   6232 my ($kernel,$self,$request,$response) = @_[KERNEL,OBJECT,ARG0 .. ARG1];
103 30         125 my $sender = $_[SENDER]->ID();
104              
105             # Sanity check the $request and $response objects *sigh*
106 30 50 33     446 return unless $response and $response->isa("HTTP::Response");
107              
108 30 50 33     337 unless ( $request and $request->isa("HTTP::Request") ) {
109 0         0 $kernel->post( $sender => 'DONE' => $response );
110 0         0 return;
111             }
112              
113 30         125 my $path = uri_unescape( $request->uri->path );
114 30         1638 my $realpath = $path;
115              
116 30 100       122 $realpath = $self->{prefix_path} . $path if $self->{prefix_path};
117 30 100       151 $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix};
118              
119             SWITCH: {
120 30 100       45 if ( $self->{vdir}->test('d', $realpath) ) {
  30         183  
121 18 100       3110 if ( $path !~ /\/$/ ) {
122 6         17 $path .= '/';
123 6         38 $response = $self->_generate_301( $path, $response );
124 6         25 last SWITCH;
125             }
126 12 50 33     69 if ( $self->{auto_index} and !$self->{vdir}->test('e', $realpath . $self->{index_file} ) ) {
127 0         0 $response = $self->_generate_dir_listing( $path, $response );
128 0         0 last SWITCH;
129             }
130 12 100       108 if ( $self->{vdir}->test('e', $realpath . $self->{index_file} ) ) {
131 6         768 my ($filename, $directory, $suffix) = fileparse($self->{index_file}, keys %{ $self->{handlers} } );
  6         273  
132 6 50       29 if ( $suffix ) {
133 0         0 $kernel->post(
134             $self->{handlers}->{ $suffix }->{SESSION},
135             $self->{handlers}->{ $suffix }->{EVENT},
136             {
137             request => $request,
138             response => $response,
139             session => $sender,
140             script_name => $path . $self->{index_file},
141             script_filename => $self->{vdir}->root_path() . $realpath . $self->{index_file},
142             },
143             );
144 0         0 return;
145             }
146 6         52 $response = $self->_generate_content( $sender, $path . $self->{index_file}, $response );
147 6         28 last SWITCH;
148             }
149 6         698 $response = $self->_generate_403( $response );
150 6         16 last SWITCH;
151             }
152 12 100       1609 if ( $self->{vdir}->test('e', $realpath) ) {
153 3         344 my ($filename, $directory, $suffix) = fileparse($realpath, keys %{ $self->{handlers} } );
  3         106  
154 3 50       15 if ( $suffix ) {
155 3         27 $kernel->post(
156             $self->{handlers}->{ $suffix }->{SESSION},
157             $self->{handlers}->{ $suffix }->{EVENT},
158             {
159             request => $request,
160             response => $response,
161             session => $sender,
162             script_name => $path,
163             script_filename => $self->{vdir}->root_path() . $realpath,
164             },
165             );
166 3         1563 return;
167             }
168 0         0 $response = $self->_generate_content( $sender, $path, $response );
169 0         0 last SWITCH;
170             }
171 9         989 $response = $self->_generate_404( $response );
172             }
173              
174 27 100       187 $kernel->post( $sender => 'DONE' => $response ) if defined $response;
175 27         2498 undef;
176             }
177              
178             sub shutdown {
179 7     7 1 7021 my $self = shift;
180 7         40 $poe_kernel->post( $self->session_id() => 'shutdown' => @_ );
181             }
182              
183             sub _shutdown {
184 7     7   1909 my ($kernel,$self) = @_[KERNEL,OBJECT];
185              
186 7 50       38 if ( $self->{alias} ) {
187 0         0 $kernel->alias_remove( $_ ) for $kernel->alias_list();
188             } else {
189 7         55 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ );
190             }
191 7         667 undef;
192             }
193              
194             sub session_id {
195 37     37 1 230 return $_[0]->{session_id};
196             }
197              
198             # Alias for deprecated function
199             sub autoindex {
200 0     0 0 0 warn "autoindex is deprecated: please use auto_index";
201 0         0 goto &auto_index;
202             }
203              
204             sub auto_index {
205 6     6 1 3781 my $self = shift;
206 6         12 my $value = shift;
207 6 50       27 return $self->{auto_index} unless defined $value;
208 6         20 $self->{auto_index} = $value;
209             }
210              
211             sub index_file {
212 0     0 1 0 my $self = shift;
213 0         0 my $value = shift;
214 0 0       0 return $self->{index_file} unless defined $value;
215 0         0 $self->{index_file} = $value;
216             }
217              
218             sub _generate_404 {
219 9     9   23 my $self = shift;
220 9   50     41 my $response = shift || return;
221 9         48 return generate_404( $response );
222             }
223              
224             sub generate_404 {
225 9   50 9 1 35 my $response = shift || return;
226 9 50       54 return unless $response->isa('HTTP::Response');
227 9         46 $response->code( 404 );
228 9         156 $response->header( 'Content-Type', 'text/html' );
229 9         752 $response->content( start_html('404') . h1('Not Found') . end_html );
230 9         25874 return $response;
231             }
232              
233             sub _generate_403 {
234 6     6   19 my $self = shift;
235 6   50     32 my $response = shift || return;
236 6         24 return generate_403( $response );
237             }
238              
239             sub generate_403 {
240 6   50 6 1 26 my $response = shift || return;
241 6 50       68 return unless $response->isa('HTTP::Response');
242 6         37 $response->code( 403 );
243 6         108 $response->header( 'Content-Type', 'text/html' );
244 6         533 $response->content( start_html('403') . h1('Forbidden') . end_html );
245 6         2955 return $response;
246             }
247              
248             sub _generate_301 {
249 6     6   15 my $self = shift;
250 6   50     34 my $path = shift || return;
251 6   50     29 my $response = shift || return;
252 6         25 return generate_301( $path, $response );
253             }
254              
255             sub generate_301 {
256 6   50 6 1 28 my $path = shift || return;
257 6   50     27 my $response = shift || return;
258 6 50       38 return unless $response->isa('HTTP::Response');
259 6         45 $response->code( 301 );
260 6         144 $response->header( 'Location' => $path );
261 6         493 $response->header( 'Content-Type', 'text/html' );
262 6         393 $response->content( start_html('301') . h1('Moved Permanently') . '

The document has moved here.

' . end_html );
263 6         32810 return $response;
264             }
265              
266             sub _generate_dir_listing {
267 0     0   0 my $self = shift;
268 0   0     0 my $path = shift || return;
269 0   0     0 my $response = shift || return undef;
270 0         0 my $content = start_html('Index of ' . $path) . h1('Index of ' . $path) . qq{
\n
    \n};
271              
272 0         0 my $realpath = $path;
273 0 0       0 $realpath = $self->{prefix_path} . $path if $self->{prefix_path};
274 0 0       0 $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix};
275            
276 0         0 foreach my $item ( $self->{vdir}->list( $realpath ) ) {
277 0 0       0 next if $item =~ /^\./;
278 0         0 $content .= qq{
  • $item
  • \n};
    279             }
    280            
    281 0         0 $content .= qq{\n} . end_html;
    282 0         0 $response->code( 200 );
    283 0         0 $response->header( 'Content-Type', 'text/html' );
    284 0         0 $response->content( $content );
    285 0         0 return $response;
    286             }
    287              
    288             sub _read_input {
    289 6     6   2241 ${ $_[OBJECT]{read}{$_[ARG1]}{content} } .= $_[ARG0];
      6         106  
    290             }
    291              
    292             # Read finished
    293             sub _read_error {
    294 6     6   20526 my ($self, $kernel, $error, $wheelid) = @_[ OBJECT, KERNEL, ARG1, ARG3 ];
    295 6         29 my $read = delete $self->{read}{$wheelid};
    296 6         22 my $response = delete $read->{response};
    297 6         28 my $content = delete $read->{content};
    298 6         23 my $mimetype = delete $read->{mimetype};
    299 6         22 my $sender = delete $read->{sender};
    300              
    301 6         53 delete $read->{wheel};
    302            
    303 6 50       2084 if ($error) {
    304 0         0 $response->content("Internal Server Error");
    305 0         0 $response->code(500);
    306             }
    307             else {
    308 6 50       56 unless ( $mimetype ) {
    309 0 0       0 if ( $self->{mm} ) {
    310 0         0 $mimetype = $self->{mm}->checktype_contents( $$content );
    311             }
    312             else {
    313 0         0 $mimetype = 'application/octet-stream';
    314             }
    315             }
    316 6         95 $response->code( 200 );
    317 6         171 $response->content_type( $mimetype );
    318 6         324 $response->content_ref( $content );
    319             }
    320            
    321 6         142 $kernel->post( $sender => 'DONE' => $response );
    322             }
    323              
    324             sub _generate_content {
    325 6     6   16 my $self = shift;
    326 6   50     27 my $sender = shift || return;
    327 6   50     26 my $path = shift || return;
    328 6   50     26 my $response = shift || return;
    329 6         10 my $realpath = $path;
    330 6 100       32 $realpath = $self->{prefix_path} . $path if $self->{prefix_path};
    331 6 100       47 $realpath =~ s/^$self->{prefix_fix}// if $self->{prefix_fix};
    332              
    333 6         52 my $mimetype = $self->{mt}->mimeTypeOf( $path );
    334              
    335 6 50       1263 if ( my $fh = $self->{vdir}->open_read( $realpath ) ) {
    336 6         1210 binmode($fh);
    337 6 50 33     66 if ( $^O eq 'MSWin32' or $self->{blocking} ) {
    338 0         0 local $/ = undef;
    339 0         0 my $content = <$fh>;
    340 0 0       0 unless ( $mimetype ) {
    341 0 0       0 if ( $self->{mm} ) {
    342 0         0 $mimetype = $self->{mm}->checktype_contents( $content );
    343             }
    344             else {
    345 0         0 $mimetype = 'application/octet-stream';
    346             }
    347             }
    348 0         0 $response->code( 200 );
    349 0         0 $response->content_type( $mimetype );
    350 0         0 $response->content_ref( \$content );
    351             } else {
    352 6         86 my $readwrite = POE::Wheel::ReadWrite->new(
    353             Handle => $fh,
    354             Filter => POE::Filter::Stream->new(),
    355             InputEvent => "-input",
    356             ErrorEvent => "-error",
    357             );
    358              
    359 6         2451 my $content = "";
    360              
    361 6         33 my $wheelid = $readwrite->ID;
    362 6         62 my $readheap = {
    363             wheel => $readwrite,
    364             response => $response,
    365             mimetype => $mimetype,
    366             sender => $sender,
    367             content => \$content,
    368             };
    369              
    370 6         30 $self->{read}{$wheelid} = $readheap;
    371              
    372 6         19 return;
    373             }
    374             } else {
    375 0         0 $response = $self->_generate_404( $response );
    376             }
    377              
    378 0         0 return $response;
    379             }
    380              
    381             sub _massage_handlers {
    382 3   50 3   19 my $handler = shift || return;
    383 3 50 33     40 croak( "HANDLERS is not a ref to an hash!" )
    384             unless ref $handler and ref $handler eq 'HASH';
    385 3         7 foreach my $ext ( keys %{ $handler } ) {
      3         14  
    386 3 50       29 delete $handler->{ $ext } unless ref $handler->{ $ext } eq 'HASH';
    387 3 50       47 croak( "HANDLER for '$ext' does not have a SESSION argument!" )
    388             unless $handler->{ $ext }->{'SESSION'};
    389 3 50       16 croak( "HANDLER for '$ext' does not have an EVENT argument!" )
    390             unless $handler->{ $ext }->{'EVENT'};
    391 3 50       31 $handler->{ $ext }->{'SESSION'} = $handler->{ $ext }->{'SESSION'}->ID()
    392             if UNIVERSAL::isa( $handler->{ $ext }->{'SESSION'}, 'POE::Session' );
    393             }
    394 3         10 return 1;
    395             }
    396              
    397             sub get_handlers {
    398 0     0 1 0 my $self = shift;
    399 0         0 my $handlers = Storable::dclone( $self->{handlers} );
    400 0         0 return $handlers;
    401             }
    402              
    403             sub set_handlers {
    404 3     3 1 3554 my $self = shift;
    405 3   50     44 my $handlers = shift || return;
    406 3         16 _massage_handlers( $handlers );
    407 3         8 $self->{handlers} = $handlers;
    408 3         11 return 1;
    409             }
    410              
    411             1;
    412              
    413             __END__