root/lang/perl/MENTA/trunk/lib/MENTA.pm @ 24402

Revision 24402, 8.2 kB (checked in by tokuhirom, 5 years ago)

static/ 以外は見せない方向で。

Line 
1package MENTA;
2use strict;
3use warnings;
4use utf8;
5use CGI::ExceptionManager;
6
7our $VERSION = '0.05';
8our $REQ;
9our $CONFIG;
10our $REQUIRED;
11our $MOBILEAGENTRE;
12our $CARRIER;
13our $STASH;
14our $PLUGIN_LOADED;
15BEGIN {
16    $REQUIRED = {};
17}
18
19sub import {
20    strict->import;
21    warnings->import;
22    utf8->import;
23}
24
25package main; # ここ以下の関数はすべてコントローラで呼ぶことができます
26
27sub config () { $MENTA::CONFIG }
28
29sub run_menta {
30    my $config = shift @_;
31
32    local $MENTA::CONFIG = $config;
33    local $MENTA::REQ;
34    local $MENTA::STASH;
35
36    CGI::ExceptionManager->run(
37        callback => sub {
38            dispatch()
39        },
40        powered_by => '<strong>MENTA</strong>, Web Application Framework.',
41        (config->{menta}->{fatals_to_browser} ? () : (renderer => sub { "INTERNAL SERVER ERROR!" x 100 }))
42    );
43}
44
45sub dispatch {
46    my $path = $ENV{PATH_INFO} || '/';
47    $path =~ s!^/+!!g;
48    if ($path =~ /^[a-z0-9_]*$/) {
49        $path ||= 'index';
50        my $cdir = controller_dir();
51        my $controller = "${cdir}/${path}.pl";
52        my $controller_mt = controller_dir() . "/${path}.mt";
53        if (-f $controller) {
54            my $meth = "do_$path";
55            package main;
56            do $controller;
57            if (my $e = $@) {
58                if (ref $e) {
59                    warn "KTKR";
60                    return;
61                } else {
62                    die $e;
63                }
64            }
65            die $@ if $@;
66            if (my $code = main->can($meth)) {
67                $code->();
68                die "なにも出力してません";
69            } else {
70                die "「${path}」というモードは存在しません!${controller} の中に ${meth} が定義されていないようです";
71            }
72        } elsif (-f $controller_mt) {
73            my $out = __render_partial("${path}.mt", controller_dir());
74            utf8::encode($out);
75            print "Content-Type: text/html; charset=utf-8\r\n";
76            print "\r\n";
77            print $out;
78        } else {
79            die "「${path}」というモードは存在しません。コントローラファイルもありません(${controller})。テンプレートファイルもありません(${controller_mt})";
80        }
81    } elsif ($path ne 'menta.cgi' && -f "app/$path" && $path =~ /^static\//) {
82        $path = "app/$path";
83        if (open my $fh, '<', $path) {
84            binmode $fh;
85            binmode STDOUT;
86            printf "Content-Type: %s\r\n\r\n", guess_mime_type($path);
87            print do { local $/; <$fh> };
88            close $fh;
89        } else {
90            die "ファイルが開きません";
91        }
92    } elsif ($path =~ /^(?:crossdomain\.xml|favicon\.ico|robots\.txt)$/) {
93        print "status: 404\r\ncontent-type: text/plain\r\n\r\n";
94    } else {
95        die "${path} を処理する方法がわかりません";
96    }
97}
98
99sub escape_html {
100    local $_ = shift;
101    return $_ unless $_;
102    s/&/&amp;/g;
103    s/>/&gt;/g;
104    s/</&lt;/g;
105    s/"/&quot;/g;
106    s/'/&#39;/g;
107    return $_;
108}
109
110sub unescape_html {
111    local $_ = shift;
112    return $_ unless $_;
113    s/&gt;/>/g;
114    s/&lt;/</g;
115    s/&quot;/"/g;
116    s/&#39;/'/g;
117    s/&amp;/&/g;
118    return $_;
119}
120
121sub guess_mime_type {
122    my $ext = shift;
123    $ext =~ s/.+\.(.+)$/$1/;
124
125    # TODO should be moved to other.
126    my $mime_map = {
127        css => 'text/css',
128        js  => 'application/javascript',
129        jpg => 'image/jpeg',
130        gif => 'image/gif',
131        png => 'image/png',
132        txt => 'text/plain',
133    };
134    $mime_map->{$ext} || 'application/octet-stream';
135}
136
137sub cache_dir {
138    config->{menta}->{cache_dir} || 'cache'
139}
140
141sub tmpl_dir {
142    config->{menta}->{cache_dir} || 'app/tmpl/'
143}
144
145sub controller_dir {
146    config->{menta}->{controller_dir} || 'app/controller/'
147}
148
149sub data_dir {
150    config->{menta}->{data_dir} || 'app/data/'
151}
152
153sub static_dir {
154    config->{menta}->{static_dir} || 'app/static/'
155}
156
157# TODO: ディレクトリトラバーサル対策
158sub __render_partial {
159    my ($tmpl, $tmpldir, @params) = @_;
160    my $conf = config()->{menta};
161    my $cachedir = cache_dir();
162    mkdir $cachedir unless -d $cachedir;
163    my $cachefname = "$cachedir/$tmpl";
164    my $tmplfname = "$tmpldir/$tmpl";
165    my $use_cache = sub {
166        my @orig = stat $tmplfname or return;
167        my @cached = stat $cachefname or return;
168        return $orig[9] < $cached[9];
169    }->();
170    my $out;
171    if ($use_cache) {
172        my $tmplcode = do $cachefname;
173        die $@ if $@;
174        die "テンプレートキャッシュを読み込めませんでした: ${tmplfname}" unless $tmplcode;
175        $out = $tmplcode->(@params);
176    } else {
177        die "「${tmplfname}」という名前のテンプレートファイルは見つかりません" unless -f $tmplfname;
178        require_once('MENTA/Template.pm');
179        my $tmplsrc = read_file($tmplfname);
180        my $mt = MENTA::Template->new;
181        $mt->parse($tmplsrc);
182        $mt->build();
183        my $src = $mt->code();
184        my $tmplcode = eval $src;
185        die $@ if $@;
186        $out = $tmplcode->(@params);
187        write_file($cachefname, "package main; use utf8;\n${src}");
188    }
189    $out;
190}
191sub render_partial {
192    my ($tmpl, @params) = @_;
193    __render_partial($tmpl, tmpl_dir(), @params);
194}
195
196sub detach() { CGI::ExceptionManager::detach(@_) }
197
198sub render {
199    my ($tmpl, @params) = @_;
200    my $out = render_partial($tmpl, @params);
201    utf8::encode($out);
202    print "Content-Type: text/html; charset=utf-8\r\n";
203    print "\r\n";
204    print $out;
205
206    detach;
207}
208
209sub redirect {
210    my ($location, ) = @_;
211    print "Status: 302\r\n";
212    print "Location: $location\r\n";
213    print "\r\n";
214
215    detach;
216}
217
218sub finalize {
219    my $str = shift;
220    my $content_type = shift || 'text/html; charset=utf-8';
221
222    print "Content-Type: $content_type\r\n";
223    print "\r\n";
224    print $str;
225
226    detach;
227}
228
229sub read_file {
230    my $fname = shift;
231    open my $fh, '<:utf8', $fname or die "${fname} を読み込み用に開けません: $!";
232    my $s = do { local $/; join '', <$fh> };
233    close $fh;
234    $s;
235}
236
237sub write_file {
238    my ($fname, $stuff) = @_;
239    open my $fh, '>:utf8', $fname or die "${fname} を書き込み用に開けません: $!";
240    print $fh $stuff;
241    close $fh;
242}
243
244sub param {
245    my $key = shift;
246
247    unless (defined $MENTA::REQ) {
248        require_once('CGI/Simple.pm');
249        $CGI::Simple::PARAM_UTF8++;
250        $MENTA::REQ = CGI::Simple->new();
251    }
252
253    $MENTA::REQ->param($key);
254}
255
256sub upload {
257    unless (defined $MENTA::REQ) {
258        require_once('CGI/Simple.pm');
259        $CGI::Simple::PARAM_UTF8++;
260        $MENTA::REQ = CGI::Simple->new();
261    }
262    $MENTA::REQ->upload(@_);
263}
264
265sub require_once {
266    my $path = shift;
267    return if $MENTA::REQUIRED->{$path};
268    require $path;
269    $MENTA::REQUIRED->{$path} = 1;
270}
271
272sub load_plugin {
273    my $plugin = shift;
274    return if $MENTA::PLUGIN_LOADED->{$plugin};
275    my $path = "plugins/${plugin}.pl";
276    require $path;
277    $MENTA::PLUGIN_LOADED->{$plugin}++;
278    my $package = __menta_extract_package($path) || '';
279    no strict 'refs';
280    for (
281        grep { /$plugin/o }
282        grep { defined &{"${package}::$_"} }
283        keys %{"${package}::"}
284    ) {
285        *{"main::$_"} = *{"${package}::$_"}
286    }
287}
288
289sub __menta_extract_package {
290    my $modulefile = shift;
291    open my $fh, '<', $modulefile or die "$modulefile を開けません: $!";
292    my $in_pod = 0;
293    while (<$fh>) {
294        $in_pod = 1 if m/^=\w/;
295        $in_pod = 0 if /^=cut/;
296        next if ( $in_pod || /^=cut/ );    # skip pod text
297        next if /^\s*\#/;
298
299        /^\s*package\s+(.*?)\s*;/ and return $1;
300    }
301    return;
302}
303
304sub is_post_request () {
305    my $method = $ENV{REQUEST_METHOD};
306    return $method eq 'POST';
307}
308
309# TODO: CGI にはこのための環境変数ってなかったっけ?
310sub docroot () { $ENV{SCRIPT_NAME} || '' }
311
312sub uri_for {
313    my ($path, $query) = @_;
314    my @q;
315    while (my ($key, $val) = each %$query) {
316        $val = join '', map { /^[a-zA-Z0-9_.!~*'()-]$/ ? $_ : '%' . uc(unpack('H2', $_)) } split //, $val;
317        push @q, "${key}=${val}";
318    }
319    docroot . '/' . $path . (scalar @q ? '?' . join('&', @q) : '');
320}
321
322sub static_file_path {
323    my $path = shift;
324    docroot . '/static/' . $path;
325}
326
3271;
Note: See TracBrowser for help on using the browser.