line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Plugin::CGI; |
2
|
34
|
|
|
34
|
|
101173
|
use Mojo::Base 'Mojolicious::Plugin'; |
|
34
|
|
|
|
|
69
|
|
|
34
|
|
|
|
|
258
|
|
3
|
|
|
|
|
|
|
|
4
|
34
|
|
|
34
|
|
6641
|
use File::Basename; |
|
34
|
|
|
|
|
72
|
|
|
34
|
|
|
|
|
2284
|
|
5
|
34
|
|
|
34
|
|
199
|
use File::Spec; |
|
34
|
|
|
|
|
75
|
|
|
34
|
|
|
|
|
822
|
|
6
|
34
|
|
|
34
|
|
16555
|
use IO::Pipely 'pipely'; |
|
34
|
|
|
|
|
85934
|
|
|
34
|
|
|
|
|
2192
|
|
7
|
34
|
|
|
34
|
|
240
|
use Mojo::Util qw(b64_decode encode); |
|
34
|
|
|
|
|
75
|
|
|
34
|
|
|
|
|
1739
|
|
8
|
34
|
|
|
34
|
|
221
|
use POSIX 'WNOHANG'; |
|
34
|
|
|
|
|
62
|
|
|
34
|
|
|
|
|
279
|
|
9
|
34
|
|
|
34
|
|
16751
|
use Perl::OSType 'is_os_type'; |
|
34
|
|
|
|
|
13604
|
|
|
34
|
|
|
|
|
2036
|
|
10
|
34
|
|
|
34
|
|
255
|
use Socket qw(AF_INET inet_aton); |
|
34
|
|
|
|
|
74
|
|
|
34
|
|
|
|
|
1764
|
|
11
|
34
|
|
|
34
|
|
14367
|
use Sys::Hostname; |
|
34
|
|
|
|
|
34338
|
|
|
34
|
|
|
|
|
2449
|
|
12
|
|
|
|
|
|
|
|
13
|
34
|
|
50
|
34
|
|
350
|
use constant CHECK_CHILD_INTERVAL => $ENV{CHECK_CHILD_INTERVAL} || 0.01; |
|
34
|
|
|
|
|
74
|
|
|
34
|
|
|
|
|
3230
|
|
14
|
34
|
|
|
34
|
|
202
|
use constant DEBUG => $ENV{MOJO_PLUGIN_CGI_DEBUG}; |
|
34
|
|
|
|
|
66
|
|
|
34
|
|
|
|
|
2029
|
|
15
|
34
|
|
|
34
|
|
194
|
use constant IS_WINDOWS => is_os_type('Windows'); |
|
34
|
|
|
|
|
64
|
|
|
34
|
|
|
|
|
123
|
|
16
|
34
|
|
|
34
|
|
2115
|
use constant READ => 0; |
|
34
|
|
|
|
|
290
|
|
|
34
|
|
|
|
|
1573
|
|
17
|
34
|
|
|
34
|
|
187
|
use constant WRITE => 1; |
|
34
|
|
|
|
|
53
|
|
|
34
|
|
|
|
|
96580
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our $VERSION = '0.39'; |
20
|
|
|
|
|
|
|
our %ORIGINAL_ENV = %ENV; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
has env => sub { +{%ORIGINAL_ENV} }; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub register { |
25
|
43
|
|
|
43
|
1
|
27172
|
my ($self, $app, $args) = @_; |
26
|
43
|
|
100
|
|
|
288
|
my $pids = $app->{'mojolicious_plugin_cgi.pids'} ||= {}; |
27
|
|
|
|
|
|
|
|
28
|
43
|
100
|
|
|
|
199
|
$args = {route => shift @$args, script => shift @$args} if ref $args eq 'ARRAY'; |
29
|
43
|
|
66
|
|
|
253
|
$args->{env} ||= $self->env; |
30
|
43
|
50
|
|
|
|
146
|
$args->{run} = delete $args->{script} if ref $args->{script} eq 'CODE'; |
31
|
43
|
|
|
|
|
87
|
$args->{pids} = $pids; |
32
|
|
|
|
|
|
|
|
33
|
43
|
100
|
|
9
|
|
248
|
$app->helper('cgi.run' => sub { _run($args, @_) }) unless $app->renderer->helpers->{'cgi.run'}; |
|
9
|
|
|
|
|
101488
|
|
34
|
|
|
|
|
|
|
$app->{'mojolicious_plugin_cgi.tid'} |
35
|
43
|
|
66
|
1478
|
|
2114
|
||= Mojo::IOLoop->recurring(CHECK_CHILD_INTERVAL, sub { local ($?, $!); _waitpids($pids); }); |
|
1478
|
|
|
|
|
13531649
|
|
|
1478
|
|
|
|
|
5548
|
|
36
|
|
|
|
|
|
|
|
37
|
43
|
100
|
66
|
|
|
4235
|
if ($args->{support_semicolon_in_query_string} |
38
|
|
|
|
|
|
|
and !$app->{'mojolicious_plugin_cgi.before_dispatch'}++) |
39
|
|
|
|
|
|
|
{ |
40
|
|
|
|
|
|
|
$app->hook( |
41
|
|
|
|
|
|
|
before_dispatch => sub { |
42
|
2
|
|
|
2
|
|
4502
|
$_[0]->stash('cgi.query_string' => $_[0]->req->url->query->to_string); |
43
|
|
|
|
|
|
|
} |
44
|
2
|
|
|
|
|
24
|
); |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
43
|
100
|
|
|
|
190
|
return unless $args->{route}; # just register the helper |
48
|
37
|
50
|
66
|
|
|
269
|
die "Neither 'run', nor 'script' is specified." unless $args->{run} or $args->{script}; |
49
|
|
|
|
|
|
|
$args->{route} = $app->routes->any("$args->{route}/*path_info", {path_info => ''}) |
50
|
37
|
50
|
|
|
|
404
|
unless ref $args->{route}; |
51
|
37
|
100
|
33
|
|
|
19563
|
$args->{script} = File::Spec->rel2abs($args->{script}) || $args->{script} if $args->{script}; |
52
|
37
|
|
|
47
|
|
336
|
$args->{route}->to(cb => sub { _run($args, @_) }); |
|
47
|
|
|
|
|
437285
|
|
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub _child { |
56
|
20
|
|
|
20
|
|
794
|
my ($c, $args, $stdin, $stdout, $stderr) = @_; |
57
|
20
|
100
|
|
|
|
1283
|
my @STDERR = @$stderr ? ('>&', fileno $stderr->[WRITE]) : ('>>', $args->{errlog}); |
58
|
|
|
|
|
|
|
|
59
|
20
|
|
|
|
|
2018
|
Mojo::IOLoop->reset; |
60
|
20
|
|
|
|
|
62975
|
warn "[CGI:$args->{name}:$$] <<< (@{[$stdin->slurp]})\n" if DEBUG; |
61
|
20
|
100
|
50
|
|
|
810
|
open STDIN, '<', $stdin->path or die "STDIN @{[$stdin->path]}: $!" if -s $stdin->path; |
|
0
|
|
|
|
|
0
|
|
62
|
20
|
50
|
|
|
|
3621
|
open STDERR, $STDERR[0], $STDERR[1] or die "STDERR: @$stderr: $!"; |
63
|
20
|
50
|
|
|
|
775
|
open STDOUT, '>&', fileno $stdout->[WRITE] or die "STDOUT: $!"; |
64
|
20
|
|
|
|
|
362
|
select STDERR; |
65
|
20
|
|
|
|
|
426
|
$| = 1; |
66
|
20
|
|
|
|
|
381
|
select STDOUT; |
67
|
20
|
|
|
|
|
188
|
$| = 1; |
68
|
|
|
|
|
|
|
|
69
|
20
|
|
|
|
|
349
|
%ENV = _emulate_environment($c, $args); |
70
|
|
|
|
|
|
|
$args->{run} ? $args->{run}->($c) : exec $args->{script} |
71
|
20
|
50
|
50
|
|
|
0
|
|| die "Could not execute $args->{script}: $!"; |
72
|
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
0
|
eval { POSIX::_exit($!) } unless IS_WINDOWS; |
|
0
|
|
|
|
|
0
|
|
74
|
0
|
|
|
|
|
0
|
eval { CORE::kill KILL => $$ }; |
|
0
|
|
|
|
|
0
|
|
75
|
0
|
|
|
|
|
0
|
exit $!; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub _emulate_environment { |
79
|
20
|
|
|
20
|
|
193
|
my ($c, $args) = @_; |
80
|
20
|
|
|
|
|
524
|
my $tx = $c->tx; |
81
|
20
|
|
|
|
|
693
|
my $req = $tx->req; |
82
|
20
|
|
|
|
|
453
|
my $headers = $req->headers; |
83
|
20
|
100
|
|
|
|
654
|
my $content_length = $req->content->is_multipart ? $req->body_size : $headers->content_length; |
84
|
20
|
|
|
|
|
1773
|
my %env_headers = (HTTP_COOKIE => '', HTTP_REFERER => ''); |
85
|
20
|
|
|
|
|
160
|
my ($remote_user, $script_name); |
86
|
|
|
|
|
|
|
|
87
|
20
|
|
|
|
|
110
|
for my $name (@{$headers->names}) { |
|
20
|
|
|
|
|
646
|
|
88
|
86
|
|
|
|
|
2601
|
my $key = uc "http_$name"; |
89
|
86
|
|
|
|
|
690
|
$key =~ s!\W!_!g; |
90
|
86
|
|
|
|
|
558
|
$env_headers{$key} = $headers->header($name); |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
20
|
100
|
|
|
|
765
|
if (my $userinfo = $c->req->url->to_abs->userinfo) { |
|
|
50
|
|
|
|
|
|
94
|
2
|
50
|
|
|
|
1718
|
$remote_user = $userinfo =~ /([^:]+)/ ? $1 : ''; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
elsif (my $authenticate = $headers->authorization) { |
97
|
0
|
0
|
|
|
|
0
|
$remote_user = $authenticate =~ /Basic\s+(.*)/ ? b64_decode $1 : ''; |
98
|
0
|
0
|
|
|
|
0
|
$remote_user = $remote_user =~ /([^:]+)/ ? $1 : ''; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
20
|
100
|
|
|
|
13862
|
if ($args->{route}) { |
|
|
50
|
|
|
|
|
|
102
|
17
|
|
|
|
|
671
|
$script_name = $c->url_for($args->{route}->name, {path_info => ''})->path->to_string; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
elsif (my $name = $c->stash('script_name')) { |
105
|
3
|
|
|
|
|
125
|
my $name = quotemeta $name; |
106
|
3
|
50
|
|
|
|
28
|
$script_name = $c->req->url->path =~ m!^(.*?/$name)! ? $1 : $c->stash('script_name'); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
return ( |
110
|
20
|
|
|
|
|
1688
|
%{$args->{env}}, |
111
|
|
|
|
|
|
|
CONTENT_LENGTH => $content_length || 0, |
112
|
|
|
|
|
|
|
CONTENT_TYPE => $headers->content_type || '', |
113
|
|
|
|
|
|
|
GATEWAY_INTERFACE => 'CGI/1.1', |
114
|
|
|
|
|
|
|
HTTPS => $req->is_secure ? 'YES' : 'NO', |
115
|
|
|
|
|
|
|
%env_headers, |
116
|
|
|
|
|
|
|
PATH_INFO => '/' . encode('UTF-8', $c->stash('path_info') // ''), |
117
|
|
|
|
|
|
|
QUERY_STRING => $c->stash('cgi.query_string') || $req->url->query->to_string, |
118
|
|
|
|
|
|
|
REMOTE_ADDR => $tx->remote_address, |
119
|
|
|
|
|
|
|
REMOTE_HOST => gethostbyaddr(inet_aton($tx->remote_address || '127.0.0.1'), AF_INET) || '', |
120
|
|
|
|
|
|
|
REMOTE_PORT => $tx->remote_port, |
121
|
|
|
|
|
|
|
REMOTE_USER => $remote_user || '', |
122
|
|
|
|
|
|
|
REQUEST_METHOD => $req->method, |
123
|
|
|
|
|
|
|
SCRIPT_FILENAME => $args->{script} || '', |
124
|
|
|
|
|
|
|
SCRIPT_NAME => $script_name || $args->{name}, |
125
|
20
|
50
|
100
|
|
|
28893
|
SERVER_ADMIN => $ENV{USER} || '', |
|
|
50
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
50
|
|
|
|
|
126
|
|
|
|
|
|
|
SERVER_NAME => hostname, |
127
|
|
|
|
|
|
|
SERVER_PORT => $tx->local_port, |
128
|
|
|
|
|
|
|
SERVER_PROTOCOL => $req->is_secure ? 'HTTPS' : 'HTTP', # TODO: Version is missing |
129
|
|
|
|
|
|
|
SERVER_SOFTWARE => __PACKAGE__, |
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub _run { |
134
|
56
|
|
|
56
|
|
206
|
my ($defaults, $c) = (shift, shift); |
135
|
56
|
50
|
|
|
|
290
|
my $args = @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}; |
|
0
|
100
|
|
|
|
0
|
|
136
|
56
|
|
66
|
|
|
617
|
my $before = $args->{before} || $defaults->{before}; |
137
|
56
|
|
|
|
|
267
|
my $stdin = _stdin($c); |
138
|
56
|
|
|
|
|
58624
|
my @stdout = pipely; |
139
|
56
|
|
|
|
|
5369
|
my ($pid, $log_key, @stderr); |
140
|
|
|
|
|
|
|
|
141
|
56
|
|
100
|
|
|
1150
|
$args->{$_} ||= $defaults->{$_} for qw(env errlog route run script); |
142
|
56
|
100
|
|
|
|
4706
|
$args->{name} = $args->{run} ? "$args->{run}" : basename $args->{script}; |
143
|
56
|
100
|
|
|
|
266
|
$c->$before($args) if $before; |
144
|
56
|
100
|
|
|
|
556
|
@stderr = (pipely) unless $args->{errlog}; |
145
|
56
|
50
|
|
|
|
104116
|
defined($pid = fork) or die "[CGI:$args->{name}] fork failed: $!"; |
146
|
56
|
100
|
|
|
|
3250
|
_child($c, $args, $stdin, \@stdout, \@stderr) unless $pid; |
147
|
36
|
|
|
|
|
2283
|
$args->{pids}{$pid} = $args->{name}; |
148
|
36
|
|
|
|
|
915
|
$log_key = "CGI:$args->{name}:$pid"; |
149
|
36
|
|
66
|
|
|
2737
|
$c->app->log->debug("[$log_key] START @{[$args->{script} || $args->{run}]}"); |
|
36
|
|
|
|
|
4647
|
|
150
|
|
|
|
|
|
|
|
151
|
36
|
|
|
|
|
3686
|
for my $p (\@stdout, \@stderr) { |
152
|
72
|
100
|
|
|
|
13403
|
next unless $p->[READ]; |
153
|
69
|
|
|
|
|
1265
|
close $p->[WRITE]; |
154
|
69
|
|
|
|
|
2265
|
$p->[READ] = Mojo::IOLoop::Stream->new($p->[READ])->timeout(0); |
155
|
69
|
|
|
|
|
8619
|
Mojo::IOLoop->stream($p->[READ]); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
36
|
|
|
|
|
4935
|
$c->stash('cgi.pid' => $pid, 'cgi.stdin' => $stdin); |
159
|
36
|
|
|
|
|
3235
|
$c->render_later; |
160
|
|
|
|
|
|
|
|
161
|
36
|
100
|
|
|
|
1725
|
$stderr[READ]->on(read => _stderr_cb($c, $log_key)) if $stderr[READ]; |
162
|
36
|
|
|
|
|
875
|
$stdout[READ]->on(read => _stdout_cb($c, $log_key)); |
163
|
|
|
|
|
|
|
$stdout[READ]->on(close => sub { |
164
|
36
|
|
|
36
|
|
54574
|
my $GUARD = 50; |
165
|
36
|
|
|
|
|
106
|
warn "[CGI:$args->{name}:$pid] Child closed STDOUT\n" if DEBUG; |
166
|
36
|
50
|
50
|
|
|
622
|
unlink $stdin->path or die "Could not remove STDIN @{[$stdin->path]}" if -e $stdin->path; |
|
0
|
|
|
|
|
0
|
|
167
|
36
|
|
|
|
|
3651
|
local ($?, $!); |
168
|
|
|
|
|
|
|
_waitpids({$pid => $args->{pids}{$pid}}) |
169
|
36
|
|
66
|
|
|
1925
|
while $args->{pids}{$pid} |
|
|
|
66
|
|
|
|
|
170
|
|
|
|
|
|
|
and kill 0, $pid |
171
|
|
|
|
|
|
|
and $GUARD--; |
172
|
36
|
50
|
|
|
|
362
|
$defaults->{pids}{$pid} = $args->{pids}{$pid} if kill 0, $pid; |
173
|
36
|
100
|
|
|
|
395
|
return $c->finish if $c->res->code; |
174
|
3
|
|
|
|
|
180
|
return $c->render(text => "Could not run CGI script ($?, $!).\n", status => 500); |
175
|
|
|
|
|
|
|
} |
176
|
36
|
|
|
|
|
1108
|
); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub _stderr_cb { |
180
|
33
|
|
|
33
|
|
309
|
my ($c, $log_key) = @_; |
181
|
33
|
|
|
|
|
663
|
my $log = $c->app->log; |
182
|
33
|
|
|
|
|
575
|
my $buf = ''; |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
return sub { |
185
|
3
|
|
|
3
|
|
12234
|
my ($stream, $chunk) = @_; |
186
|
3
|
|
|
|
|
33
|
warn "[$log_key] !!! ($chunk)\n" if DEBUG; |
187
|
3
|
|
|
|
|
24
|
$buf .= $chunk; |
188
|
3
|
|
|
|
|
96
|
$log->warn("[$log_key] $1") while $buf =~ s!^(.+)[\r\n]+$!!m; |
189
|
33
|
|
|
|
|
1586
|
}; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub _stdout_cb { |
193
|
36
|
|
|
36
|
|
276
|
my ($c, $log_key) = @_; |
194
|
36
|
|
|
|
|
281
|
my $buf = ''; |
195
|
36
|
|
|
|
|
192
|
my $headers; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
return sub { |
198
|
33
|
|
|
33
|
|
208561
|
my ($stream, $chunk) = @_; |
199
|
33
|
|
|
|
|
182
|
warn "[$log_key] >>> ($chunk)\n" if DEBUG; |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# true if HTTP header has been written to client |
202
|
33
|
50
|
|
|
|
316
|
return $c->write($chunk) if $headers; |
203
|
|
|
|
|
|
|
|
204
|
33
|
|
|
|
|
268
|
$buf .= $chunk; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# false until all headers has been read from the CGI script |
207
|
33
|
50
|
|
|
|
813
|
$buf =~ s/^(.*?\x0a\x0d?\x0a\x0d?)//s or return; |
208
|
33
|
|
|
|
|
601
|
$headers = $1; |
209
|
|
|
|
|
|
|
|
210
|
33
|
100
|
|
|
|
456
|
if ($headers =~ /^HTTP/) { |
211
|
6
|
100
|
|
|
|
100
|
$c->res->code($headers =~ m!^HTTP (\d\d\d)! ? $1 : 200); |
212
|
6
|
|
|
|
|
434
|
$c->res->parse($headers); |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
else { |
215
|
27
|
100
|
|
|
|
337
|
$c->res->code($1) if $headers =~ /^Status: (\d\d\d)/m; |
216
|
27
|
100
|
|
|
|
591
|
$c->res->code($headers =~ /Location:/ ? 302 : 200) unless $c->res->code; |
|
|
100
|
|
|
|
|
|
217
|
27
|
|
|
|
|
2302
|
$c->res->parse($c->res->get_start_line_chunk(0) . $headers); |
218
|
|
|
|
|
|
|
} |
219
|
33
|
100
|
|
|
|
39842
|
$c->write($buf) if length $buf; |
220
|
36
|
|
|
|
|
1281
|
}; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub _stdin { |
224
|
56
|
|
|
56
|
|
158
|
my $c = shift; |
225
|
56
|
|
|
|
|
143
|
my $stdin; |
226
|
|
|
|
|
|
|
|
227
|
56
|
100
|
|
|
|
263
|
if ($c->req->content->is_multipart) { |
228
|
2
|
|
|
|
|
52
|
$stdin = Mojo::Asset::File->new; |
229
|
2
|
|
|
|
|
48
|
$stdin->add_chunk($c->req->build_body); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
else { |
232
|
54
|
|
|
|
|
1013
|
$stdin = $c->req->content->asset; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
56
|
100
|
|
|
|
5496
|
return $stdin if $stdin->isa('Mojo::Asset::File'); |
236
|
54
|
|
|
|
|
435
|
return Mojo::Asset::File->new->add_chunk($stdin->slurp); |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub _waitpids { |
240
|
1514
|
|
|
1514
|
|
3301
|
my $pids = shift; |
241
|
|
|
|
|
|
|
|
242
|
1514
|
|
|
|
|
8682
|
for my $pid (keys %$pids) { |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# no idea why i need to do this, but it seems like waitpid() below return -1 if not |
245
|
36
|
|
|
|
|
1542
|
local $SIG{CHLD} = 'DEFAULT'; |
246
|
36
|
50
|
|
|
|
1175
|
next unless waitpid $pid, WNOHANG; |
247
|
36
|
|
50
|
|
|
249
|
my $name = delete $pids->{$pid} || 'unknown'; |
248
|
36
|
|
|
|
|
299
|
my ($exit_value, $signal) = ($? >> 8, $? & 127); |
249
|
36
|
|
|
|
|
894
|
warn "[CGI:$name:$pid] Child exit_value=$exit_value ($signal)\n" if DEBUG; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
1; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=encoding utf8 |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head1 NAME |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Mojolicious::Plugin::CGI - Run CGI script from Mojolicious |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head1 VERSION |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
0.39 |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=head1 DESCRIPTION |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
This plugin enables L to run Perl CGI scripts. It does so by forking |
268
|
|
|
|
|
|
|
a new process with a modified environment and reads the STDOUT in a non-blocking |
269
|
|
|
|
|
|
|
manner. |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head1 SYNOPSIS |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head2 Standard usage |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
use Mojolicious::Lite; |
276
|
|
|
|
|
|
|
plugin CGI => [ "/cgi-bin/script" => "/path/to/cgi/script.pl" ]; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
Using the code above is enough to run C when accessing |
279
|
|
|
|
|
|
|
L. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=head2 Complex usage |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
plugin CGI => { |
284
|
|
|
|
|
|
|
# Specify the script and mount point |
285
|
|
|
|
|
|
|
script => "/path/to/cgi/script.pl", |
286
|
|
|
|
|
|
|
route => "/some/route", |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# %ENV variables visible from inside the CGI script |
289
|
|
|
|
|
|
|
env => {}, # default is \%ENV |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# Path to where STDERR from cgi script goes |
292
|
|
|
|
|
|
|
errlog => "/path/to/file.log", |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# The "before" hook is called before script start |
295
|
|
|
|
|
|
|
# It receives a Mojolicious::Controller which can be modified |
296
|
|
|
|
|
|
|
before => sub { |
297
|
|
|
|
|
|
|
my $c = shift; |
298
|
|
|
|
|
|
|
$c->req->url->query->param(a => 123); |
299
|
|
|
|
|
|
|
}, |
300
|
|
|
|
|
|
|
}; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
The above contains all the options you can pass on to the plugin. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=head2 Helper |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
plugin "CGI"; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# GET /cgi-bin/some-script.cgi/path/info?x=123 |
309
|
|
|
|
|
|
|
get "/cgi-bin/#script_name/*path_info" => {path_info => ''}, sub { |
310
|
|
|
|
|
|
|
my $c = shift; |
311
|
|
|
|
|
|
|
my $name = $c->stash("script_name"); |
312
|
|
|
|
|
|
|
$c->cgi->run(script => File::Spec->rel2abs("/path/to/cgi/$name")); |
313
|
|
|
|
|
|
|
}; |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
The helper can take most of the arguments that L takes, with the |
316
|
|
|
|
|
|
|
exception of C. |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
It is critical that "script_name" and "path_info" is present in |
319
|
|
|
|
|
|
|
L. Whether the values are extracted directly |
320
|
|
|
|
|
|
|
from the path or set manually does not matter. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
Note that the helper is registered in all of the examples. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=head2 Running code refs |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
plugin CGI => { |
327
|
|
|
|
|
|
|
route => "/some/path", |
328
|
|
|
|
|
|
|
run => sub { |
329
|
|
|
|
|
|
|
my $cgi = CGI->new; |
330
|
|
|
|
|
|
|
# ... |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
}; |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
Instead of calling a script, you can run a code block when accessing the route. |
335
|
|
|
|
|
|
|
This is (pretty much) safe, even if the code block modifies global state, |
336
|
|
|
|
|
|
|
since it runs in a separate fork/process. |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=head2 Support for semicolon in query string |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
plugin CGI => { |
341
|
|
|
|
|
|
|
support_semicolon_in_query_string => 1, |
342
|
|
|
|
|
|
|
... |
343
|
|
|
|
|
|
|
}; |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
The code above needs to be added before other plugins or handlers which use |
346
|
|
|
|
|
|
|
L. It will inject a C |
347
|
|
|
|
|
|
|
hook which saves the original QUERY_STRING, before it is split on |
348
|
|
|
|
|
|
|
"&" in L. |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head2 env |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
Holds a hash ref containing the environment variables that should be |
355
|
|
|
|
|
|
|
used when starting the CGI script. Defaults to C<%ENV> when this module |
356
|
|
|
|
|
|
|
was loaded. |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
This plugin will create a set of environment variables depenendent on the |
359
|
|
|
|
|
|
|
request passed in which is according to the CGI spec. In addition to L, |
360
|
|
|
|
|
|
|
these dynamic variables are set: |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
CONTENT_LENGTH, CONTENT_TYPE, HTTPS, PATH, PATH_INFO, QUERY_STRING, |
363
|
|
|
|
|
|
|
REMOTE_ADDR, REMOTE_HOST, REMOTE_PORT, REMOTE_USER, REQUEST_METHOD, |
364
|
|
|
|
|
|
|
SCRIPT_NAME, SERVER_PORT, SERVER_PROTOCOL. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
Additional static variables: |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
GATEWAY_INTERFACE = "CGI/1.1" |
369
|
|
|
|
|
|
|
SERVER_ADMIN = $ENV{USER} |
370
|
|
|
|
|
|
|
SCRIPT_FILENAME = Script name given as argument to register. |
371
|
|
|
|
|
|
|
SERVER_NAME = Sys::Hostname::hostname() |
372
|
|
|
|
|
|
|
SERVER_SOFTWARE = "Mojolicious::Plugin::CGI" |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
Plus all headers are exposed. Examples: |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
.----------------------------------------. |
377
|
|
|
|
|
|
|
| Header | Variable | |
378
|
|
|
|
|
|
|
|-----------------|----------------------| |
379
|
|
|
|
|
|
|
| Referer | HTTP_REFERER | |
380
|
|
|
|
|
|
|
| User-Agent | HTTP_USER_AGENT | |
381
|
|
|
|
|
|
|
| X-Forwarded-For | HTTP_X_FORWARDED_FOR | |
382
|
|
|
|
|
|
|
'----------------------------------------' |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head2 register |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
$self->register($app, [ $route => $script ]); |
387
|
|
|
|
|
|
|
$self->register($app, %args); |
388
|
|
|
|
|
|
|
$self->register($app, \%args); |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
C and L need to exist as keys in C<%args> unless given as plain |
391
|
|
|
|
|
|
|
arguments. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
C<$route> can be either a plain path or a route object. |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Copyright (C) 2014, Jan Henning Thorsen |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
This program is free software, you can redistribute it and/or modify it under |
400
|
|
|
|
|
|
|
the terms of the Artistic License version 2.0. |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=head1 AUTHOR |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
Jan Henning Thorsen - C |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=cut |