source: main/trunk/greenstone2/perllib/cpan/Mojolicious/Static.pm@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

File size: 8.3 KB
Line 
1package Mojolicious::Static;
2use Mojo::Base -base;
3
4use Mojo::Asset::File;
5use Mojo::Asset::Memory;
6use Mojo::Date;
7use Mojo::File 'path';
8use Mojo::Home;
9use Mojo::Loader qw(data_section file_is_binary);
10use Mojo::Util qw(encode md5_sum);
11
12# Bundled files
13my $PUBLIC = Mojo::Home->new(Mojo::Home->new->mojo_lib_dir)
14 ->child('Mojolicious', 'resources', 'public');
15my %EXTRA = $PUBLIC->list_tree->map(
16 sub { join('/', @{$_->to_rel($PUBLIC)}), $_->realpath->to_string })->each;
17
18has classes => sub { ['main'] };
19has extra => sub { +{%EXTRA} };
20has paths => sub { [] };
21
22sub dispatch {
23 my ($self, $c) = @_;
24
25 # Method (GET or HEAD)
26 my $req = $c->req;
27 my $method = $req->method;
28 return undef unless $method eq 'GET' || $method eq 'HEAD';
29
30 # Canonical path
31 my $stash = $c->stash;
32 my $path = $req->url->path;
33 $path = $stash->{path} ? $path->new($stash->{path}) : $path->clone;
34 return undef unless my @parts = @{$path->canonicalize->parts};
35
36 # Serve static file and prevent path traversal
37 my $canon_path = join '/', @parts;
38 return undef if $canon_path =~ /^\.\.\/|\\/ || !$self->serve($c, $canon_path);
39 $stash->{'mojo.static'} = 1;
40 return !!$c->rendered;
41}
42
43sub file {
44 my ($self, $rel) = @_;
45
46 # Search all paths
47 my @parts = split '/', $rel;
48 for my $path (@{$self->paths}) {
49 next unless my $asset = _get_file(path($path, @parts)->to_string);
50 return $asset;
51 }
52
53 # Search DATA
54 if (my $asset = $self->_get_data_file($rel)) { return $asset }
55
56 # Search extra files
57 my $extra = $self->extra;
58 return exists $extra->{$rel} ? _get_file($extra->{$rel}) : undef;
59}
60
61sub is_fresh {
62 my ($self, $c, $options) = @_;
63
64 my $res_headers = $c->res->headers;
65 my ($last, $etag) = @$options{qw(last_modified etag)};
66 $res_headers->last_modified(Mojo::Date->new($last)->to_string) if $last;
67 $res_headers->etag($etag = qq{"$etag"}) if $etag;
68
69 # Unconditional
70 my $req_headers = $c->req->headers;
71 my $match = $req_headers->if_none_match;
72 return undef unless (my $since = $req_headers->if_modified_since) || $match;
73
74 # If-None-Match
75 return undef if $match && ($etag // $res_headers->etag // '') ne $match;
76
77 # If-Modified-Since
78 return !!$match unless ($last //= $res_headers->last_modified) && $since;
79 return _epoch($last) <= (_epoch($since) // 0);
80}
81
82sub serve {
83 my ($self, $c, $rel) = @_;
84
85 return undef unless my $asset = $self->file($rel);
86 my $headers = $c->res->headers;
87 return !!$self->serve_asset($c, $asset) if $headers->content_type;
88
89 # Content-Type
90 my $types = $c->app->types;
91 my $type = $rel =~ /\.(\w+)$/ ? $types->type($1) : undef;
92 $headers->content_type($type || $types->type('txt'));
93 return !!$self->serve_asset($c, $asset);
94}
95
96sub serve_asset {
97 my ($self, $c, $asset) = @_;
98
99 # Last-Modified and ETag
100 my $res = $c->res;
101 $res->code(200)->headers->accept_ranges('bytes');
102 my $mtime = $asset->mtime;
103 my $options = {etag => md5_sum($mtime), last_modified => $mtime};
104 return $res->code(304) if $self->is_fresh($c, $options);
105
106 # Range
107 return $res->content->asset($asset)
108 unless my $range = $c->req->headers->range;
109
110 # Not satisfiable
111 return $res->code(416) unless my $size = $asset->size;
112 return $res->code(416) unless $range =~ /^bytes=(\d+)?-(\d+)?/;
113 my ($start, $end) = ($1 // 0, defined $2 && $2 < $size ? $2 : $size - 1);
114 return $res->code(416) if $start > $end;
115
116 # Satisfiable
117 $res->code(206)->headers->content_length($end - $start + 1)
118 ->content_range("bytes $start-$end/$size");
119 return $res->content->asset($asset->start_range($start)->end_range($end));
120}
121
122sub warmup {
123 my $self = shift;
124 my $index = $self->{index} = {};
125 for my $class (reverse @{$self->classes}) {
126 $index->{$_} = $class for keys %{data_section $class};
127 }
128}
129
130sub _epoch { Mojo::Date->new(shift)->epoch }
131
132sub _get_data_file {
133 my ($self, $rel) = @_;
134
135 # Protect files without extensions and templates with two extensions
136 return undef if $rel !~ /\.\w+$/ || $rel =~ /\.\w+\.\w+$/;
137
138 $self->warmup unless $self->{index};
139
140 # Find file
141 my @args = ($self->{index}{$rel}, $rel);
142 return undef unless defined(my $data = data_section(@args));
143 return Mojo::Asset::Memory->new->add_chunk(
144 file_is_binary(@args) ? $data : encode 'UTF-8', $data);
145}
146
147sub _get_file {
148 my $path = shift;
149 no warnings 'newline';
150 return -f $path && -r _ ? Mojo::Asset::File->new(path => $path) : undef;
151}
152
1531;
154
155=encoding utf8
156
157=head1 NAME
158
159Mojolicious::Static - Serve static files
160
161=head1 SYNOPSIS
162
163 use Mojolicious::Static;
164
165 my $static = Mojolicious::Static->new;
166 push @{$static->classes}, 'MyApp::Controller::Foo';
167 push @{$static->paths}, '/home/sri/public';
168
169=head1 DESCRIPTION
170
171L<Mojolicious::Static> is a static file server with C<Range>,
172C<If-Modified-Since> and C<If-None-Match> support, based on
173L<RFC 7232|http://tools.ietf.org/html/rfc7232> and
174L<RFC 7233|http://tools.ietf.org/html/rfc7233>.
175
176=head1 ATTRIBUTES
177
178L<Mojolicious::Static> implements the following attributes.
179
180=head2 classes
181
182 my $classes = $static->classes;
183 $static = $static->classes(['main']);
184
185Classes to use for finding files in C<DATA> sections with L<Mojo::Loader>,
186first one has the highest precedence, defaults to C<main>. Only files with
187exactly one extension will be used, like C<index.html>. Note that for files to
188be detected, these classes need to have already been loaded and added before
189L</"warmup"> is called, which usually happens automatically during application
190startup.
191
192 # Add another class with static files in DATA section
193 push @{$static->classes}, 'Mojolicious::Plugin::Fun';
194
195 # Add another class with static files in DATA section and higher precedence
196 unshift @{$static->classes}, 'Mojolicious::Plugin::MoreFun';
197
198=head2 extra
199
200 my $extra = $static->extra;
201 $static = $static->extra({'foo/bar.txt' => '/home/sri/myapp/bar.txt'});
202
203Paths for extra files to be served from locations other than L</"paths">, such
204as the images used by the built-in exception and not found pages. Note that
205extra files are only served if no better alternative could be found in
206L</"paths"> and L</"classes">.
207
208 # Remove built-in favicon
209 delete $static->extra->{'favicon.ico'};
210
211=head2 paths
212
213 my $paths = $static->paths;
214 $static = $static->paths(['/home/sri/public']);
215
216Directories to serve static files from, first one has the highest precedence.
217
218 # Add another "public" directory
219 push @{$static->paths}, '/home/sri/public';
220
221 # Add another "public" directory with higher precedence
222 unshift @{$static->paths}, '/home/sri/themes/blue/public';
223
224=head1 METHODS
225
226L<Mojolicious::Static> inherits all methods from L<Mojo::Base> and implements
227the following new ones.
228
229=head2 dispatch
230
231 my $bool = $static->dispatch(Mojolicious::Controller->new);
232
233Serve static file for L<Mojolicious::Controller> object.
234
235=head2 file
236
237 my $asset = $static->file('images/logo.png');
238 my $asset = $static->file('../lib/MyApp.pm');
239
240Build L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object for a file,
241relative to L</"paths"> or from L</"classes">, or return C<undef> if it doesn't
242exist. Note that this method uses a relative path, but does not protect from
243traversing to parent directories.
244
245 my $content = $static->file('foo/bar.html')->slurp;
246
247=head2 is_fresh
248
249 my $bool = $static->is_fresh(Mojolicious::Controller->new, {etag => 'abc'});
250
251Check freshness of request by comparing the C<If-None-Match> and
252C<If-Modified-Since> request headers to the C<ETag> and C<Last-Modified>
253response headers.
254
255These options are currently available:
256
257=over 2
258
259=item etag
260
261 etag => 'abc'
262
263Add C<ETag> header before comparing.
264
265=item last_modified
266
267 last_modified => $epoch
268
269Add C<Last-Modified> header before comparing.
270
271=back
272
273=head2 serve
274
275 my $bool = $static->serve(Mojolicious::Controller->new, 'images/logo.png');
276 my $bool = $static->serve(Mojolicious::Controller->new, '../lib/MyApp.pm');
277
278Serve a specific file, relative to L</"paths"> or from L</"classes">. Note that
279this method uses a relative path, but does not protect from traversing to parent
280directories.
281
282=head2 serve_asset
283
284 $static->serve_asset(Mojolicious::Controller->new, Mojo::Asset::File->new);
285
286Serve a L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object with C<Range>,
287C<If-Modified-Since> and C<If-None-Match> support.
288
289=head2 warmup
290
291 $static->warmup;
292
293Prepare static files from L</"classes"> for future use.
294
295=head1 SEE ALSO
296
297L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
298
299=cut
Note: See TracBrowser for help on using the repository browser.