Complete rewrite to use Plack Middleware.
[catagits/Catalyst-Plugin-Static-Simple.git] / lib / Catalyst / Plugin / Static / Simple / Middleware.pm
1 package Catalyst::Plugin::Static::Simple::Middleware;
2 use strict;
3 use warnings;
4
5 use parent qw/Plack::Middleware/;
6
7 use Plack::Util::Accessor qw/content_type config cat_app/;
8 use Plack::App::File;
9 use Carp;
10
11 sub call {
12     my ( $self, $env )  = @_;
13
14     #wrap everything in an eval so if something goes wrong the user
15     #gets an "internal server error" message, and we get a warning
16     #instead of the user getting the warning and us getting nothing
17     my $return = eval {
18         my ( $res, $c );
19
20         if(! $self->_check_static_simple_path( $env ) ) {
21             return $self->app->($env)
22         }
23
24         my @ipaths = @{$self->config->{include_path}};
25         while ( my $ipath = shift @ipaths ) {
26             my $root;
27             if ( ref($ipath) eq 'CODE' ) {
28                 $c ||= $self->cat_app->prepare(env => $env, response_cb => sub {});
29                 my $paths = $ipath->( $c );
30
31                 unshift @ipaths, @{$paths};
32                 next;
33             } else {
34                 $root = $ipath;
35             }
36
37             if(! -f $root . $env->{PATH_INFO} ) {
38                 next;
39             }
40
41             my $file = Plack::App::File->new( {
42                 root            => $root,
43                 content_type    => $self->content_type
44             } );
45             $res = $file->call($env);
46
47             if( $res && ($res->[0] != 404) ) {
48                 return $res;
49             }
50         }
51
52         if(! scalar ( @{$self->config->{dirs}})) {
53             $self->_debug( "Forwarding to Catalyst (or other middleware).", $env );
54             return $self->app->($env);
55         }
56
57         $self->_debug( "404: file not found: " . $env->{PATH_INFO}, $env );
58         return $self->return_404;
59     };
60
61     if ( $@ ) {
62         if($env->{'psgix.logger'} && ref($env->{'psgix.logger'}) eq 'CODE') {
63             $env->{'psgix.logger'}->({ level => 'error', message => $@ });
64         } else {
65             carp $@;
66         }
67         return $self->return_500;
68     }
69
70     return $return;
71 }
72
73 sub _check_static_simple_path {
74     my ( $self, $env ) = @_;
75     my $path = $env->{PATH_INFO};
76     
77     for my $ignore_ext ( @{ $self->config->{ignore_extensions} } ) {
78         if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
79             $self->_debug ( "Ignoring extension `$ignore_ext`", $env );
80             return undef
81         }
82     }
83
84     for my $ignore ( @{ $self->config->{ignore_dirs} } ) {
85         $ignore =~ s{(/|\\)$}{};
86
87         if ( $path =~ /^\/$ignore(\/|\\)/ ) {
88             $self->_debug( "Ignoring directory `$ignore`", $env );
89             return undef;
90         }
91     }
92
93     #we serve everything if it exists and dirs is not set
94     #we check if it exists in the middleware, once we've built the include paths
95     return 1 if ( !scalar(@{$self->config->{dirs}}) );
96
97     if( $self->_path_matches_dirs($path, $self->config->{dirs}) ) {
98         return 1;
99     }
100
101     return undef;
102 }
103
104 sub _path_matches_dirs {
105     my ( $self, $path, $dirs ) = @_;
106
107     $path =~ s!^/!!; #Remove leading slashes
108
109     foreach my $dir ( @$dirs ) {
110         my $re;
111         if (ref($dir) eq 'Regexp') {
112             $re = $dir;
113         } elsif ( $dir =~ m{^qr/}xms ) {
114             $re = eval $dir;
115
116             if ($@) {
117                 die( "Error compiling static dir regex '$dir': $@" );
118             }
119         } else {
120             my $dir_re = quotemeta $dir;
121             $dir_re =~ s{/$}{};
122             $re = qr{^${dir_re}/};
123         }
124
125         if( $path =~ $re ) {
126             return 1;
127         }
128     }
129
130     return undef;
131 }
132
133 sub _debug {
134     my ( $self, $msg, $env ) = @_;
135
136     if($env && $env->{'psgix.logger'} && ref($env->{'psgix.logger'}) eq 'CODE') {
137         $env->{'psgix.logger'}->({ level => 'error', message => $@ });
138     } else {
139         warn "Static::Simple: $msg\n" if $self->config->{debug};
140     }
141 }
142
143 sub return_404 {
144     #for backcompat we can't use the one in Plack::App::File as it has the content-type of plain
145     return [404, ['Content-Type' => 'text/html', 'Content-Length' => 9], ['not found']];
146 }
147
148 sub return_500 {
149     return [500, ['Content-Type' => 'text/pain', 'Content-Length' => 9], ['internal server error']];
150 }
151
152 1;