]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
403d7b0b | 5 | # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC |
84fb5b46 MKG |
6 | # <sales@bestpractical.com> |
7 | # | |
8 | # (Except where explicitly superseded by other copyright notices) | |
9 | # | |
10 | # | |
11 | # LICENSE: | |
12 | # | |
13 | # This work is made available to you under the terms of Version 2 of | |
14 | # the GNU General Public License. A copy of that license should have | |
15 | # been provided with this software, but in any event can be snarfed | |
16 | # from www.gnu.org. | |
17 | # | |
18 | # This work is distributed in the hope that it will be useful, but | |
19 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | # General Public License for more details. | |
22 | # | |
23 | # You should have received a copy of the GNU General Public License | |
24 | # along with this program; if not, write to the Free Software | |
25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
26 | # 02110-1301 or visit their web page on the internet at | |
27 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. | |
28 | # | |
29 | # | |
30 | # CONTRIBUTION SUBMISSION POLICY: | |
31 | # | |
32 | # (The following paragraph is not intended to limit the rights granted | |
33 | # to you to modify and distribute this software under the terms of | |
34 | # the GNU General Public License and is only of importance to you if | |
35 | # you choose to contribute your changes and enhancements to the | |
36 | # community by submitting them to Best Practical Solutions, LLC.) | |
37 | # | |
38 | # By intentionally submitting any modifications, corrections or | |
39 | # derivatives to this work, or any other work intended for use with | |
40 | # Request Tracker, to Best Practical Solutions, LLC, you confirm that | |
41 | # you are the copyright holder for those contributions and you grant | |
42 | # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, | |
43 | # royalty-free, perpetual, license to use, copy, create derivative | |
44 | # works based on those contributions, and sublicense and distribute | |
45 | # those contributions and any derivatives thereof. | |
46 | # | |
47 | # END BPS TAGGED BLOCK }}} | |
48 | ||
49 | package RT::Interface::Web::Handler; | |
50 | use warnings; | |
51 | use strict; | |
52 | ||
53 | use CGI qw/-private_tempfiles/; | |
54 | use MIME::Entity; | |
55 | use Text::Wrapper; | |
56 | use CGI::Cookie; | |
57 | use Time::ParseDate; | |
58 | use Time::HiRes; | |
59 | use HTML::Scrubber; | |
60 | use RT::Interface::Web; | |
61 | use RT::Interface::Web::Request; | |
62 | use File::Path qw( rmtree ); | |
63 | use File::Glob qw( bsd_glob ); | |
64 | use File::Spec::Unix; | |
65 | ||
66 | sub DefaultHandlerArgs { ( | |
67 | comp_root => [ | |
68 | RT::Interface::Web->ComponentRoots( Names => 1 ), | |
69 | ], | |
70 | default_escape_flags => 'h', | |
71 | data_dir => "$RT::MasonDataDir", | |
35ef43cf | 72 | allow_globals => [qw(%session $DECODED_ARGS)], |
84fb5b46 MKG |
73 | # Turn off static source if we're in developer mode. |
74 | static_source => (RT->Config->Get('DevelMode') ? '0' : '1'), | |
75 | use_object_files => (RT->Config->Get('DevelMode') ? '0' : '1'), | |
76 | autoflush => 0, | |
77 | error_format => (RT->Config->Get('DevelMode') ? 'html': 'rt_error'), | |
78 | request_class => 'RT::Interface::Web::Request', | |
79 | named_component_subs => $INC{'Devel/Cover.pm'} ? 1 : 0, | |
80 | ) }; | |
81 | ||
82 | sub InitSessionDir { | |
83 | # Activate the following if running httpd as root (the normal case). | |
84 | # Resets ownership of all files created by Mason at startup. | |
85 | # Note that mysql uses DB for sessions, so there's no need to do this. | |
86 | unless ( RT->Config->Get('DatabaseType') =~ /(?:mysql|Pg)/ ) { | |
87 | ||
88 | # Clean up our umask to protect session files | |
89 | umask(0077); | |
90 | ||
91 | if ($CGI::MOD_PERL and $CGI::MOD_PERL < 1.9908 ) { | |
92 | ||
93 | chown( Apache->server->uid, Apache->server->gid, | |
94 | $RT::MasonSessionDir ) | |
95 | if Apache->server->can('uid'); | |
96 | } | |
97 | ||
98 | # Die if WebSessionDir doesn't exist or we can't write to it | |
99 | stat($RT::MasonSessionDir); | |
100 | die "Can't read and write $RT::MasonSessionDir" | |
101 | unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) ); | |
102 | } | |
103 | ||
104 | } | |
105 | ||
106 | ||
107 | use UNIVERSAL::require; | |
108 | sub NewHandler { | |
109 | my $class = shift; | |
110 | $class->require or die $!; | |
111 | my $handler = $class->new( | |
112 | DefaultHandlerArgs(), | |
113 | RT->Config->Get('MasonParameters'), | |
114 | @_ | |
115 | ); | |
116 | ||
117 | $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 ); | |
118 | $handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI ); | |
119 | $handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS ); | |
120 | return($handler); | |
121 | } | |
122 | ||
123 | =head2 _mason_dir_index | |
124 | ||
125 | =cut | |
126 | ||
127 | sub _mason_dir_index { | |
128 | my ($self, $interp, $path) = @_; | |
129 | $path =~ s!/$!!; | |
130 | if ( !$interp->comp_exists( $path ) | |
131 | && $interp->comp_exists( $path . "/index.html" ) ) | |
132 | { | |
133 | return $path . "/index.html"; | |
134 | } | |
135 | ||
136 | return $path; | |
137 | } | |
138 | ||
139 | ||
140 | =head2 CleanupRequest | |
141 | ||
142 | Clean ups globals, caches and other things that could be still | |
143 | there from previous requests: | |
144 | ||
145 | =over 4 | |
146 | ||
147 | =item Rollback any uncommitted transaction(s) | |
148 | ||
149 | =item Flush the ACL cache | |
150 | ||
151 | =item Flush records cache of the L<DBIx::SearchBuilder> if | |
152 | WebFlushDbCacheEveryRequest option is enabled, what is true by default | |
153 | and is not recommended to change. | |
154 | ||
155 | =item Clean up state of RT::Action::SendEmail using 'CleanSlate' method | |
156 | ||
157 | =item Flush tmp GnuPG key preferences | |
158 | ||
159 | =back | |
160 | ||
161 | =cut | |
162 | ||
163 | sub CleanupRequest { | |
164 | ||
165 | if ( $RT::Handle && $RT::Handle->TransactionDepth ) { | |
166 | $RT::Handle->ForceRollback; | |
167 | $RT::Logger->crit( | |
168 | "Transaction not committed. Usually indicates a software fault." | |
169 | . "Data loss may have occurred" ); | |
170 | } | |
171 | ||
172 | # Clean out the ACL cache. the performance impact should be marginal. | |
173 | # Consistency is imprived, too. | |
174 | RT::Principal->InvalidateACLCache(); | |
175 | DBIx::SearchBuilder::Record::Cachable->FlushCache | |
176 | if ( RT->Config->Get('WebFlushDbCacheEveryRequest') | |
177 | and UNIVERSAL::can( | |
178 | 'DBIx::SearchBuilder::Record::Cachable' => 'FlushCache' ) ); | |
179 | ||
180 | # cleanup global squelching of the mails | |
181 | require RT::Action::SendEmail; | |
182 | RT::Action::SendEmail->CleanSlate; | |
183 | ||
184 | if (RT->Config->Get('GnuPG')->{'Enable'}) { | |
185 | require RT::Crypt::GnuPG; | |
186 | RT::Crypt::GnuPG::UseKeyForEncryption(); | |
187 | RT::Crypt::GnuPG::UseKeyForSigning( undef ); | |
188 | } | |
189 | ||
190 | %RT::Ticket::MERGE_CACHE = ( effective => {}, merged => {} ); | |
191 | ||
192 | # RT::System persists between requests, so its attributes cache has to be | |
193 | # cleared manually. Without this, for example, subject tags across multiple | |
194 | # processes will remain cached incorrectly | |
195 | delete $RT::System->{attributes}; | |
196 | ||
197 | # Explicitly remove any tmpfiles that GPG opened, and close their | |
198 | # filehandles. unless we are doing inline psgi testing, which kills all the tmp file created by tests. | |
199 | File::Temp::cleanup() | |
200 | unless $INC{'Test/WWW/Mechanize/PSGI.pm'}; | |
201 | ||
202 | ||
203 | } | |
204 | ||
205 | ||
206 | sub HTML::Mason::Exception::as_rt_error { | |
207 | my ($self) = @_; | |
403d7b0b | 208 | $RT::Logger->error( $self->as_text ); |
84fb5b46 MKG |
209 | return "An internal RT error has occurred. Your administrator can find more details in RT's log files."; |
210 | } | |
211 | ||
c36a7e1d MKG |
212 | =head1 CheckModPerlHandler |
213 | ||
214 | Make sure we're not running with SetHandler perl-script. | |
215 | ||
216 | =cut | |
217 | ||
218 | sub CheckModPerlHandler{ | |
219 | my $self = shift; | |
220 | my $env = shift; | |
221 | ||
222 | # Plack::Handler::Apache2 masks MOD_PERL, so use MOD_PERL_API_VERSION | |
223 | return unless( $env->{'MOD_PERL_API_VERSION'} | |
224 | and $env->{'MOD_PERL_API_VERSION'} == 2); | |
225 | ||
226 | my $handler = $env->{'psgi.input'}->handler; | |
227 | ||
228 | return unless defined $handler && $handler eq 'perl-script'; | |
229 | ||
230 | $RT::Logger->critical(<<MODPERL); | |
231 | RT has problems when SetHandler is set to perl-script. | |
232 | Change SetHandler in your in httpd.conf to: | |
233 | ||
234 | SetHandler modperl | |
235 | ||
236 | For a complete example mod_perl configuration, see: | |
237 | ||
238 | https://bestpractical.com/rt/docs/@{[$RT::VERSION =~ /^(\d\.\d)/]}/web_deployment.html#mod_perl-2.xx | |
239 | MODPERL | |
240 | ||
241 | my $res = Plack::Response->new(500); | |
242 | $res->content_type("text/plain"); | |
243 | $res->body("Server misconfiguration; see error log for details"); | |
244 | return $res; | |
245 | } | |
84fb5b46 MKG |
246 | |
247 | # PSGI App | |
248 | ||
249 | use RT::Interface::Web::Handler; | |
250 | use CGI::Emulate::PSGI; | |
251 | use Plack::Request; | |
252 | use Plack::Response; | |
253 | use Plack::Util; | |
254 | use Encode qw(encode_utf8); | |
255 | ||
256 | sub PSGIApp { | |
257 | my $self = shift; | |
258 | ||
259 | # XXX: this is fucked | |
260 | require HTML::Mason::CGIHandler; | |
261 | require HTML::Mason::PSGIHandler::Streamy; | |
262 | my $h = RT::Interface::Web::Handler::NewHandler('HTML::Mason::PSGIHandler::Streamy'); | |
263 | ||
264 | $self->InitSessionDir; | |
265 | ||
266 | return sub { | |
267 | my $env = shift; | |
c36a7e1d MKG |
268 | |
269 | { | |
270 | my $res = $self->CheckModPerlHandler($env); | |
271 | return $self->_psgi_response_cb( $res->finalize ) if $res; | |
272 | } | |
273 | ||
84fb5b46 MKG |
274 | RT::ConnectToDatabase() unless RT->InstallMode; |
275 | ||
276 | my $req = Plack::Request->new($env); | |
277 | ||
278 | # CGI.pm normalizes .. out of paths so when you requested | |
279 | # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html | |
280 | # PSGI doesn't normalize .. so we have to deal ourselves. | |
281 | if ( $req->path_info =~ m{/\.} ) { | |
282 | $RT::Logger->crit("Invalid request for ".$req->path_info." aborting"); | |
283 | my $res = Plack::Response->new(400); | |
284 | return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest }); | |
285 | } | |
286 | $env->{PATH_INFO} = $self->_mason_dir_index( $h->interp, $req->path_info); | |
287 | ||
288 | my $ret; | |
289 | { | |
290 | # XXX: until we get rid of all $ENV stuff. | |
291 | local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); | |
292 | ||
293 | $ret = $h->handle_psgi($env); | |
294 | } | |
295 | ||
296 | $RT::Logger->crit($@) if $@ && $RT::Logger; | |
297 | warn $@ if $@ && !$RT::Logger; | |
298 | if (ref($ret) eq 'CODE') { | |
299 | my $orig_ret = $ret; | |
300 | $ret = sub { | |
301 | my $respond = shift; | |
302 | local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); | |
303 | $orig_ret->($respond); | |
304 | }; | |
305 | } | |
306 | ||
307 | return $self->_psgi_response_cb($ret, | |
308 | sub { | |
309 | $self->CleanupRequest() | |
310 | }); | |
311 | }; | |
312 | ||
313 | sub _psgi_response_cb { | |
314 | my $self = shift; | |
315 | my ($ret, $cleanup) = @_; | |
316 | Plack::Util::response_cb | |
317 | ($ret, | |
318 | sub { | |
319 | my $res = shift; | |
320 | ||
321 | if ( RT->Config->Get('Framebusting') ) { | |
322 | # XXX TODO: Do we want to make the value of this header configurable? | |
323 | Plack::Util::header_set($res->[1], 'X-Frame-Options' => 'DENY'); | |
324 | } | |
325 | ||
326 | return sub { | |
327 | if (!defined $_[0]) { | |
328 | $cleanup->(); | |
329 | return ''; | |
330 | } | |
331 | return utf8::is_utf8($_[0]) ? encode_utf8($_[0]) : $_[0]; | |
332 | return $_[0]; | |
333 | }; | |
334 | }); | |
335 | } | |
336 | } | |
337 | ||
338 | 1; |