File Coverage

blib/lib/Devel/Pillbug.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Devel::Pillbug::MasonHandler;
2              
3 1     1   792 use strict;
  1         2  
  1         41  
4 1     1   6 use warnings;
  1         2  
  1         37  
5              
6 1     1   15 use base qw| HTML::Mason::CGIHandler |;
  1         2  
  1         830  
7              
8             ### CGIHandler evals stuff before we can check on the result code,
9             ### which can make it hard or impossible to trap specific errors.
10             ###
11             ### Avoid this by delegating to H~::M~::Request's exec method instead.
12             sub exec {
13             my $self = shift;
14              
15             return HTML::Mason::Request::exec( $self, @_ );
16             }
17              
18             package Devel::Pillbug;
19              
20             our $VERSION = 0.006;
21              
22             use strict;
23             use warnings;
24              
25             use File::HomeDir;
26             use Media::Type::Simple;
27              
28             ### Media::Type::Simple's internal use of a cached filehandle makes
29             ### it not usable when forking, and its internal use of private globals
30             ### makes it hard to subclass.
31             ###
32             ### Sadly, Media::Type::Simple is currently the best thing going on CPAN
33             ### in terms of guessing MIME types from file extensions, so... until
34             ### its author applies a fix for this very common problem (or something
35             ### better comes along), I am adapting Jos Boumans's workaround from
36             ### rt.cpan.org #46474:
37             do {
38             no strict "refs";
39              
40             *{"Media::Type::Simple::__new"} = sub {
41             my $class = shift;
42             my $self = { types => {}, extens => {}, };
43              
44             bless $self, $class;
45              
46             if (@_) {
47             my $fh = shift;
48             return $self->add_types_from_file($fh);
49             } else {
50             my $offset = tell Media::Type::Simple::DATA;
51              
52             $Media::Type::Simple::Default =
53             $self->add_types_from_file( \*Media::Type::Simple::DATA );
54              
55             seek Media::Type::Simple::DATA, $offset, 0;
56              
57             return clone $Media::Type::Simple::Default;
58             }
59             }
60             };
61              
62             use base qw| HTTP::Server::Simple::Mason |;
63              
64             use constant DefaultServerType => "Net::Server::PreFork";
65             use constant DefaultHandlerClass => "Devel::Pillbug::MasonHandler";
66              
67             use constant DefaultIndexName => "index";
68             use constant DefaultCompExt => "html";
69              
70             our $serverType = DefaultServerType;
71             our $handlerClass = DefaultHandlerClass;
72              
73             #
74             #
75             #
76             sub net_server {
77             my $class = shift;
78             my $newServerType = shift;
79              
80             if ($newServerType) {
81             if ( !UNIVERSAL::isa( $newServerType, "Net::Server" ) ) {
82             warn "net_server() requires a Net::Server subclass";
83             }
84              
85             $serverType = $newServerType;
86             }
87              
88             return $serverType;
89             }
90              
91             #
92             #
93             #
94             sub handler_class {
95             my $class = shift;
96             my $newHandlerClass = shift;
97              
98             if ($newHandlerClass) {
99             if ( !UNIVERSAL::isa( $newHandlerClass, "HTML::Mason::Request" ) ) {
100             warn "handler_class() requires a HTML::Mason::Request subclass";
101             }
102              
103             $handlerClass = $newHandlerClass;
104             }
105              
106             return $handlerClass;
107             }
108              
109             #
110             #
111             #
112             sub pretty_html_header {
113             my $self = shift;
114             my $header = shift;
115              
116             if ($header) {
117             $self->{_html_header} = $header;
118              
119             return;
120             }
121              
122             if ( defined $self->{_html_header} ) {
123             print $self->{_html_header};
124              
125             return;
126             }
127              
128             print "\n";
129             print "\n";
130             print "\n";
150             print "\n";
151             print "\n";
152             }
153              
154             #
155             #
156             #
157             sub pretty_html_footer {
158             my $self = shift;
159             my $footer = shift;
160              
161             if ($footer) {
162             $self->{_html_footer} = $footer;
163              
164             return;
165             }
166              
167             if ( $self->{_html_footer} ) {
168             print $self->{_html_footer};
169              
170             return;
171             }
172              
173             my @time = localtime();
174             my $time = sprintf(
175             '%i-%02d-%02d %02d:%02d:%02d %s',
176             $time[5] + 1900,
177             $time[4] + 1,
178             $time[3], $time[2], $time[1], $time[0], POSIX::strftime( '%Z', @time )
179             );
180              
181             print "

$time

\n";
182             print "\n";
183             print "\n";
184             }
185              
186             #
187             #
188             #
189             sub docroot {
190             my $self = shift;
191             my $docroot = shift;
192              
193             $self->{_docroot} = $docroot if $docroot;
194              
195             if ( !$self->{_docroot} ) {
196             my $home = File::HomeDir->my_home;
197              
198             my $pubHtml = join "/", $home, "public_html";
199             my $sites = join "/", $home, "Sites";
200              
201             $self->{_docroot} = ( -d $sites ) ? $sites : $pubHtml;
202             }
203              
204             if ( !-d $self->{_docroot} ) {
205             warn "docroot $self->{_docroot} is not a usable directory";
206             }
207              
208             return $self->{_docroot};
209             }
210              
211             #
212             #
213             #
214             sub allow_index {
215             my $self = shift;
216              
217             $self->{_allow_index} ||= 0;
218              
219             if ( scalar(@_) ) {
220             $self->{_allow_index} = $_[0] ? 1 : 0;
221             }
222              
223             return $self->{_allow_index};
224             }
225              
226             #
227             #
228             #
229             sub index_name {
230             my $self = shift;
231             my $index = shift;
232              
233             $self->{_index} = $index if $index;
234              
235             $self->{_index} ||= DefaultIndexName;
236              
237             return $self->{_index};
238             }
239              
240             #
241             #
242             #
243             sub comp_ext {
244             my $self = shift;
245             my $ext = shift;
246              
247             $self->{_ext} = $ext if $ext;
248              
249             $self->{_ext} ||= DefaultCompExt;
250              
251             return $self->{_ext};
252             }
253              
254             #
255             #
256             #
257             sub mason_config {
258             my $self = shift;
259              
260             return ( comp_root => $self->docroot() );
261             }
262              
263             #
264             #
265             #
266             sub _handle_mason_request {
267             my $self = shift;
268             my $cgi = shift;
269             my $path = shift;
270              
271             my $r = HTML::Mason::FakeApache->new( cgi => $cgi );
272             my $buffer;
273              
274             ###
275             ### Brutal and tempoorary workaround for undef warnings caused
276             ### by anonymous components calling other components.
277             ###
278             ### https://rt.cpan.org/Public/Bug/Display.html?id=55159
279             do {
280             no strict "refs";
281             no warnings "redefine";
282              
283             *{"HTML::Mason::Component::dir_path"} = sub { return "" };
284             };
285              
286             eval {
287             my $m = $self->mason_handler;
288              
289             my $comp = $m->interp->make_component( comp_file => $path );
290              
291             my $req = $m->interp->make_request(
292             comp => $comp,
293             args => [ $cgi->Vars ],
294             cgi_request => $r,
295             out_method => \$buffer,
296             error_mode => "fatal",
297             error_format => "text",
298             );
299              
300             $r->{http_header_sent} = 1;
301              
302             $m->interp->set_global( '$r', $r );
303              
304             $req->exec;
305             };
306              
307             #
308             #
309             #
310             if ( $@ && ( !$r->status || ( $r->status !~ /^302/ ) ) ) {
311             $r->status("500 Internal Server Error");
312              
313             return $self->_handle_error( $r, $@ );
314             } elsif ( !$r->status ) {
315             $r->status("200 OK");
316             }
317              
318             #
319             #
320             #
321             my $header = $r->http_header;
322             $header =~ s|^Status:|HTTP/1.0|;
323              
324             print $header;
325              
326             print $buffer if $buffer;
327             }
328              
329             sub _handle_directory_request {
330             my $self = shift;
331             my $r = shift;
332              
333             my $fsPath = shift;
334             my $compPath = shift;
335              
336             print "HTTP/1.0 200 OK\r\n";
337             print "Content-Type: text/html\r\n";
338             print "\r\n";
339              
340             $self->pretty_html_header();
341              
342             print "

Index of $compPath

\n";
343              
344             print "\n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n";
345             print "
346             print " Name
347             print " Type
348             print " Last Modified
349             print " Size
350             print "
351              
352             my %conf = $self->mason_config;
353              
354             my @files;
355              
356             if ( $compPath ne "/" ) { push @files, ".." }
357              
358             for (<$fsPath/*>) { push @files, $_ }
359              
360             for (@files) {
361             my $path = $_;
362              
363             my @stat = stat($path);
364              
365             my $type;
366             my $size;
367              
368             if ( -d $path ) {
369             $path .= '/';
370             $type = "directory";
371             $size = "-";
372             } else {
373             my $ext = $path;
374             $ext =~ s/.*\.//;
375             my $o = Media::Type::Simple->__new();
376             eval { $type = $o->type_from_ext($ext); };
377             $type ||= "application/octet-stream";
378             $size = $stat[7];
379             }
380              
381             $path =~ s/^$conf{comp_root}$compPath\///;
382              
383             my @time = localtime( $stat[9] );
384             my $time = sprintf(
385             '%i-%02d-%02d %02d:%02d:%02d %s',
386             $time[5] + 1900,
387             $time[4] + 1,
388             $time[3], $time[2], $time[1], $time[0], POSIX::strftime( '%Z', @time )
389             );
390              
391             print "
392             print " $path
393             print " $type
394             print " $time
395             print " ${ size }
396             print "
397             }
398              
399             print "
\n";
400              
401             $self->pretty_html_footer();
402             }
403              
404             sub _handle_document_request {
405             my $self = shift;
406             my $r = shift;
407              
408             my $fsPath = shift;
409             my $compPath = shift;
410              
411             my $ext = $fsPath;
412             $ext =~ s/.*\.//;
413             my $o = Media::Type::Simple->__new();
414             my $type;
415             eval { $type = $o->type_from_ext($ext); };
416             $type ||= "application/octet-stream";
417              
418             my @out;
419              
420             eval {
421             open( IN, "<", $fsPath ) || die $!;
422             while () { push @out, $_ }
423             close(IN);
424             };
425              
426             if ($@) {
427             return $self->_handle_error( $r, $@ );
428             }
429              
430             print "HTTP/1.0 200 OK\r\n";
431             print "Content-Type: $type\r\n";
432             print "\r\n";
433              
434             while (@out) { print shift @out }
435             }
436              
437             sub _handle_notfound_request {
438             my $self = shift;
439             my $r = shift;
440              
441             my $fsPath = shift;
442             my $compPath = shift;
443              
444             print "HTTP/1.0 404 Not Found\r\n";
445             print "Content-Type: text/html\r\n";
446             print "\r\n";
447              
448             $self->pretty_html_header();
449              
450             print "

Not Found

\n";
451             print "

The requested URL $compPath was not found on this server.\n";

452              
453             $self->pretty_html_footer();
454             }
455              
456             sub _handle_error {
457             my $self = shift;
458             my $r = shift;
459              
460             my $err = shift;
461              
462             # $err =~ s/at \S+ line \d+.*//;
463              
464             $err = HTML::Entities::encode_entities($err);
465              
466             print "HTTP/1.0 500 Internal Server Error\r\n";
467             print "Content-type: text/html\r\n";
468             print "\r\n";
469              
470             $self->pretty_html_header();
471              
472             print "

Internal Server Error

\n";
473             print "

The server could not complete your request. The error was:

\n";
474             print "
$err
\n";
475              
476             $self->pretty_html_footer();
477             }
478              
479             sub _handle_directory_redirect {
480             my $self = shift;
481             my $compPath = shift;
482              
483             my $url = sprintf 'http://%s:%s%s/', $self->host, $self->port, $compPath;
484              
485             print "HTTP/1.0 302 Moved\r\n";
486             print "Location: $url\r\n";
487             print "\r\n";
488              
489             $self->pretty_html_header();
490              
491             print "

Moved

\n";
492             print "

The document is available here.

\n";
493              
494             $self->pretty_html_footer();
495             }
496              
497             #
498             # Override HTTP::Server::Simple::Mason to also deal with document requests,
499             # directory listings, and 404s
500             #
501             sub handle_request {
502             my $self = shift;
503             my $r = shift;
504              
505             local $@;
506              
507             my %conf = $self->mason_config;
508             my $m = $self->mason_handler;
509              
510             my $compPath = $r->path_info;
511             my $fsPath = join "", $conf{comp_root}, $compPath;
512              
513             my $ext = $self->comp_ext;
514              
515             my $indexFilename = join ".", $self->index_name, $ext;
516              
517             if ( -d $fsPath
518             && $compPath !~ m{/$}
519             && ( -e join( "/", $fsPath, $indexFilename ) || $self->allow_index ) )
520             {
521             return $self->_handle_directory_redirect($compPath);
522              
523             } elsif ( -d $fsPath ) {
524             my $indexPath = join "/", $fsPath, $indexFilename;
525              
526             if ( -e $indexPath ) {
527             $compPath .= $indexFilename;
528             $fsPath .= $indexFilename;
529              
530             $r->path_info($compPath);
531             }
532             }
533              
534             eval {
535             if ( $compPath =~ /$ext$/ && $m->interp->comp_exists($compPath) )
536             {
537             $self->_handle_mason_request( $r, $fsPath, $compPath );
538              
539             } elsif ( $self->allow_index && -d $fsPath ) {
540             $self->_handle_directory_request( $r, $fsPath, $compPath );
541              
542             } elsif ( !-d $fsPath && -e $fsPath ) {
543             $self->_handle_document_request( $r, $fsPath, $compPath );
544              
545             } else {
546             $self->_handle_notfound_request( $r, $fsPath, $compPath );
547              
548             }
549             };
550              
551             if ($@) {
552             warn $@;
553             }
554             }
555              
556             1;
557             __END__