Newer Older
209 lines | 5.45kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojolicious::Static;
2
use Mojo::Base -base;
3

            
4
use File::Spec::Functions 'catfile';
5
use Mojo::Asset::File;
6
use Mojo::Asset::Memory;
7
use Mojo::Home;
8
use Mojo::Loader;
9

            
10
has classes => sub { ['main'] };
11
has paths   => sub { [] };
12

            
13
# Last modified default
14
my $MTIME = time;
15

            
16
# Bundled files
17
my $HOME   = Mojo::Home->new;
18
my $PUBLIC = $HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/public');
19

            
20
sub dispatch {
21
  my ($self, $c) = @_;
22

            
23
  # Canonical path
24
  my $stash = $c->stash;
25
  my $path  = $c->req->url->path;
26
  $path = $stash->{path} ? $path->new($stash->{path}) : $path->clone;
27
  return undef unless my @parts = @{$path->canonicalize->parts};
28

            
29
  # Serve static file and prevent directory traversal
30
  return undef if $parts[0] eq '..' || !$self->serve($c, join('/', @parts));
31
  $stash->{'mojo.static'}++;
32
  return !!$c->rendered;
33
}
34

            
35
sub file {
36
  my ($self, $rel) = @_;
37

            
38
  # Search all paths
39
  for my $path (@{$self->paths}) {
40
    next unless my $asset = $self->_get_file(catfile $path, split('/', $rel));
41
    return $asset;
42
  }
43

            
44
  # Search DATA
45
  if (my $asset = $self->_get_data_file($rel)) { return $asset }
46

            
47
  # Search bundled files
48
  return $self->_get_file(catfile($PUBLIC, split('/', $rel)));
49
}
50

            
51
sub serve {
52
  my ($self, $c, $rel) = @_;
53
  return undef unless my $asset = $self->file($rel);
54
  my $type = $rel =~ /\.(\w+)$/ ? $c->app->types->type($1) : undef;
55
  $c->res->headers->content_type($type || 'text/plain');
56
  return !!$self->serve_asset($c, $asset);
57
}
58

            
59
sub serve_asset {
60
  my ($self, $c, $asset) = @_;
61

            
62
  # Last modified
63
  my $mtime = $asset->is_file ? (stat $asset->path)[9] : $MTIME;
64
  my $res = $c->res;
65
  $res->code(200)->headers->last_modified(Mojo::Date->new($mtime))
66
    ->accept_ranges('bytes');
67

            
68
  # If modified since
69
  my $headers = $c->req->headers;
70
  if (my $date = $headers->if_modified_since) {
71
    my $since = Mojo::Date->new($date)->epoch;
72
    return $res->code(304) if defined $since && $since == $mtime;
73
  }
74

            
75
  # Range
76
  my $size  = $asset->size;
77
  my $start = 0;
78
  my $end   = $size - 1;
79
  if (my $range = $headers->range) {
80

            
81
    # Not satisfiable
82
    return $res->code(416) unless $size && $range =~ m/^bytes=(\d+)?-(\d+)?/;
83
    $start = $1 if defined $1;
84
    $end = $2 if defined $2 && $2 <= $end;
85
    return $res->code(416) if $start > $end || $end > ($size - 1);
86

            
87
    # Satisfiable
88
    $res->code(206)->headers->content_length($end - $start + 1)
89
      ->content_range("bytes $start-$end/$size");
90
  }
91

            
92
  return $res->content->asset($asset->start_range($start)->end_range($end));
93
}
94

            
95
sub _get_data_file {
96
  my ($self, $rel) = @_;
97

            
98
  # Protect templates
99
  return undef if $rel =~ /\.\w+\.\w+$/;
100

            
101
  # Index DATA files
102
  my $loader = Mojo::Loader->new;
103
  unless ($self->{index}) {
104
    my $index = $self->{index} = {};
105
    for my $class (reverse @{$self->classes}) {
106
      $index->{$_} = $class for keys %{$loader->data($class)};
107
    }
108
  }
109

            
110
  # Find file
111
  return undef
112
    unless defined(my $data = $loader->data($self->{index}{$rel}, $rel));
113
  return Mojo::Asset::Memory->new->add_chunk($data);
114
}
115

            
116
sub _get_file {
117
  my ($self, $path) = @_;
118
  no warnings 'newline';
119
  return -f $path && -r $path ? Mojo::Asset::File->new(path => $path) : undef;
120
}
121

            
122
1;
123

            
124
=encoding utf8
125

            
126
=head1 NAME
127

            
128
Mojolicious::Static - Serve static files
129

            
130
=head1 SYNOPSIS
131

            
132
  use Mojolicious::Static;
133

            
134
  my $static = Mojolicious::Static->new;
135
  push @{$static->classes}, 'MyApp::Foo';
136
  push @{$static->paths}, '/home/sri/public';
137

            
138
=head1 DESCRIPTION
139

            
140
L<Mojolicious::Static> is a static file server with C<Range> and
141
C<If-Modified-Since> support.
142

            
143
=head1 ATTRIBUTES
144

            
145
L<Mojolicious::Static> implements the following attributes.
146

            
147
=head2 classes
148

            
149
  my $classes = $static->classes;
150
  $static     = $static->classes(['main']);
151

            
152
Classes to use for finding files in C<DATA> sections, first one has the
153
highest precedence, defaults to C<main>.
154

            
155
  # Add another class with static files in DATA section
156
  push @{$static->classes}, 'Mojolicious::Plugin::Fun';
157

            
158
=head2 paths
159

            
160
  my $paths = $static->paths;
161
  $static   = $static->paths(['/home/sri/public']);
162

            
163
Directories to serve static files from, first one has the highest precedence.
164

            
165
  # Add another "public" directory
166
  push @{$static->paths}, '/home/sri/public';
167

            
168
=head1 METHODS
169

            
170
L<Mojolicious::Static> inherits all methods from L<Mojo::Base> and implements
171
the following new ones.
172

            
173
=head2 dispatch
174

            
175
  my $bool = $static->dispatch(Mojolicious::Controller->new);
176

            
177
Serve static file for L<Mojolicious::Controller> object.
178

            
179
=head2 file
180

            
181
  my $asset = $static->file('images/logo.png');
182
  my $asset = $static->file('../lib/MyApp.pm');
183

            
184
Get L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object for a file, relative
185
to L</"paths"> or from L</"classes">. Note that this method does not protect
186
from traversing to parent directories.
187

            
188
  my $content = $static->file('foo/bar.html')->slurp;
189

            
190
=head2 serve
191

            
192
  my $bool = $static->serve(Mojolicious::Controller->new, 'images/logo.png');
193
  my $bool = $static->serve(Mojolicious::Controller->new, '../lib/MyApp.pm');
194

            
195
Serve a specific file, relative to L</"paths"> or from L</"classes">. Note
196
that this method does not protect from traversing to parent directories.
197

            
198
=head2 serve_asset
199

            
200
  $static->serve_asset(Mojolicious::Controller->new, Mojo::Asset::File->new);
201

            
202
Serve a L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object with C<Range>
203
and C<If-Modified-Since> support.
204

            
205
=head1 SEE ALSO
206

            
207
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
208

            
209
=cut