]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
5 | # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC | |
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) = @_; | |
208 | $RT::Logger->error( $self->full_message ); | |
209 | return "An internal RT error has occurred. Your administrator can find more details in RT's log files."; | |
210 | } | |
211 | ||
212 | ||
213 | # PSGI App | |
214 | ||
215 | use RT::Interface::Web::Handler; | |
216 | use CGI::Emulate::PSGI; | |
217 | use Plack::Request; | |
218 | use Plack::Response; | |
219 | use Plack::Util; | |
220 | use Encode qw(encode_utf8); | |
221 | ||
222 | sub PSGIApp { | |
223 | my $self = shift; | |
224 | ||
225 | # XXX: this is fucked | |
226 | require HTML::Mason::CGIHandler; | |
227 | require HTML::Mason::PSGIHandler::Streamy; | |
228 | my $h = RT::Interface::Web::Handler::NewHandler('HTML::Mason::PSGIHandler::Streamy'); | |
229 | ||
230 | $self->InitSessionDir; | |
231 | ||
232 | return sub { | |
233 | my $env = shift; | |
234 | RT::ConnectToDatabase() unless RT->InstallMode; | |
235 | ||
236 | my $req = Plack::Request->new($env); | |
237 | ||
238 | # CGI.pm normalizes .. out of paths so when you requested | |
239 | # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html | |
240 | # PSGI doesn't normalize .. so we have to deal ourselves. | |
241 | if ( $req->path_info =~ m{/\.} ) { | |
242 | $RT::Logger->crit("Invalid request for ".$req->path_info." aborting"); | |
243 | my $res = Plack::Response->new(400); | |
244 | return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest }); | |
245 | } | |
246 | $env->{PATH_INFO} = $self->_mason_dir_index( $h->interp, $req->path_info); | |
247 | ||
248 | my $ret; | |
249 | { | |
250 | # XXX: until we get rid of all $ENV stuff. | |
251 | local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); | |
252 | ||
253 | $ret = $h->handle_psgi($env); | |
254 | } | |
255 | ||
256 | $RT::Logger->crit($@) if $@ && $RT::Logger; | |
257 | warn $@ if $@ && !$RT::Logger; | |
258 | if (ref($ret) eq 'CODE') { | |
259 | my $orig_ret = $ret; | |
260 | $ret = sub { | |
261 | my $respond = shift; | |
262 | local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); | |
263 | $orig_ret->($respond); | |
264 | }; | |
265 | } | |
266 | ||
267 | return $self->_psgi_response_cb($ret, | |
268 | sub { | |
269 | $self->CleanupRequest() | |
270 | }); | |
271 | }; | |
272 | ||
273 | sub _psgi_response_cb { | |
274 | my $self = shift; | |
275 | my ($ret, $cleanup) = @_; | |
276 | Plack::Util::response_cb | |
277 | ($ret, | |
278 | sub { | |
279 | my $res = shift; | |
280 | ||
281 | if ( RT->Config->Get('Framebusting') ) { | |
282 | # XXX TODO: Do we want to make the value of this header configurable? | |
283 | Plack::Util::header_set($res->[1], 'X-Frame-Options' => 'DENY'); | |
284 | } | |
285 | ||
286 | return sub { | |
287 | if (!defined $_[0]) { | |
288 | $cleanup->(); | |
289 | return ''; | |
290 | } | |
291 | return utf8::is_utf8($_[0]) ? encode_utf8($_[0]) : $_[0]; | |
292 | return $_[0]; | |
293 | }; | |
294 | }); | |
295 | } | |
296 | } | |
297 | ||
298 | 1; |