starting to work...
[freeside.git] / rt / lib / RT / Interface / Web / Handler.pm
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",
72     allow_globals        => [qw(%session)],
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': 'brief'),
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     return($handler);
120 }
121
122 =head2 _mason_dir_index
123
124 =cut
125
126 sub _mason_dir_index {
127     my ($self, $interp, $path) = @_;
128     $path =~ s!/$!!;
129     if (   !$interp->comp_exists( $path )
130          && $interp->comp_exists( $path . "/index.html" ) )
131     {
132         return $path . "/index.html";
133     }
134
135     return $path;
136 }
137
138
139 =head2 CleanupRequest
140
141 Clean ups globals, caches and other things that could be still
142 there from previous requests:
143
144 =over 4
145
146 =item Rollback any uncommitted transaction(s)
147
148 =item Flush the ACL cache
149
150 =item Flush records cache of the L<DBIx::SearchBuilder> if
151 WebFlushDbCacheEveryRequest option is enabled, what is true by default
152 and is not recommended to change.
153
154 =item Clean up state of RT::Action::SendEmail using 'CleanSlate' method
155
156 =item Flush tmp GnuPG key preferences
157
158 =back
159
160 =cut
161
162 sub CleanupRequest {
163
164     if ( $RT::Handle && $RT::Handle->TransactionDepth ) {
165         $RT::Handle->ForceRollback;
166         $RT::Logger->crit(
167             "Transaction not committed. Usually indicates a software fault."
168             . "Data loss may have occurred" );
169     }
170
171     # Clean out the ACL cache. the performance impact should be marginal.
172     # Consistency is imprived, too.
173     RT::Principal->InvalidateACLCache();
174     DBIx::SearchBuilder::Record::Cachable->FlushCache
175       if ( RT->Config->Get('WebFlushDbCacheEveryRequest')
176         and UNIVERSAL::can(
177             'DBIx::SearchBuilder::Record::Cachable' => 'FlushCache' ) );
178
179     # cleanup global squelching of the mails
180     require RT::Action::SendEmail;
181     RT::Action::SendEmail->CleanSlate;
182     
183     if (RT->Config->Get('GnuPG')->{'Enable'}) {
184         require RT::Crypt::GnuPG;
185         RT::Crypt::GnuPG::UseKeyForEncryption();
186         RT::Crypt::GnuPG::UseKeyForSigning( undef );
187     }
188
189     %RT::Ticket::MERGE_CACHE = ( effective => {}, merged => {} );
190
191     # RT::System persists between requests, so its attributes cache has to be
192     # cleared manually. Without this, for example, subject tags across multiple
193     # processes will remain cached incorrectly
194     delete $RT::System->{attributes};
195
196     # Explicitly remove any tmpfiles that GPG opened, and close their
197     # filehandles.  unless we are doing inline psgi testing, which kills all the tmp file created by tests.
198     File::Temp::cleanup()
199             unless $INC{'Test/WWW/Mechanize/PSGI.pm'};
200
201
202 }
203
204
205 # PSGI App
206
207 use RT::Interface::Web::Handler;
208 use CGI::Emulate::PSGI;
209 use Plack::Request;
210 use Plack::Response;
211 use Plack::Util;
212 use Encode qw(encode_utf8);
213
214 sub PSGIApp {
215     my $self = shift;
216
217     # XXX: this is fucked
218     require HTML::Mason::CGIHandler;
219     require HTML::Mason::PSGIHandler::Streamy;
220     my $h = RT::Interface::Web::Handler::NewHandler('HTML::Mason::PSGIHandler::Streamy');
221
222     $self->InitSessionDir;
223
224     return sub {
225         my $env = shift;
226         RT::ConnectToDatabase() unless RT->InstallMode;
227
228         my $req = Plack::Request->new($env);
229
230         # CGI.pm normalizes .. out of paths so when you requested
231         # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html
232         # PSGI doesn't normalize .. so we have to deal ourselves.
233         if ( $req->path_info =~ m{/\.} ) {
234             $RT::Logger->crit("Invalid request for ".$req->path_info." aborting");
235             my $res = Plack::Response->new(400);
236             return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest });
237         }
238         $env->{PATH_INFO} = $self->_mason_dir_index( $h->interp, $req->path_info);
239
240         my $ret;
241         {
242             # XXX: until we get rid of all $ENV stuff.
243             local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env));
244
245             $ret = $h->handle_psgi($env);
246         }
247
248         $RT::Logger->crit($@) if $@ && $RT::Logger;
249         warn $@ if $@ && !$RT::Logger;
250         if (ref($ret) eq 'CODE') {
251             my $orig_ret = $ret;
252             $ret = sub {
253                 my $respond = shift;
254                 local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env));
255                 $orig_ret->($respond);
256             };
257         }
258
259         return $self->_psgi_response_cb($ret,
260                                         sub {
261                                             $self->CleanupRequest()
262                                         });
263 };
264
265 sub _psgi_response_cb {
266     my $self = shift;
267     my ($ret, $cleanup) = @_;
268     Plack::Util::response_cb
269             ($ret,
270              sub {
271                  my $res = shift;
272
273                  if ( RT->Config->Get('Framebusting') ) {
274                      # XXX TODO: Do we want to make the value of this header configurable?
275                      Plack::Util::header_set($res->[1], 'X-Frame-Options' => 'DENY');
276                  }
277
278                  return sub {
279                      if (!defined $_[0]) {
280                          $cleanup->();
281                          return '';
282                      }
283                      return utf8::is_utf8($_[0]) ? encode_utf8($_[0]) : $_[0];
284                      return $_[0];
285                  };
286              });
287     }
288 }
289
290 1;