X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FWeb%2FHandler.pm;h=7cf18d1ab66f96715e38daa31fee1bfe7e2ae2ec;hb=ed1f84b4e8f626245995ecda5afcf83092c153b2;hp=772fab0ca257e49bdbb2d92b5617b48af38fef1c;hpb=fc6209f398899f0211cfcedeb81a3cd65e04a941;p=freeside.git diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm index 772fab0ca..7cf18d1ab 100644 --- a/rt/lib/RT/Interface/Web/Handler.pm +++ b/rt/lib/RT/Interface/Web/Handler.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -47,6 +47,8 @@ # END BPS TAGGED BLOCK }}} package RT::Interface::Web::Handler; +use warnings; +use strict; use CGI qw/-private_tempfiles/; use MIME::Entity; @@ -54,53 +56,29 @@ use Text::Wrapper; use CGI::Cookie; use Time::ParseDate; use Time::HiRes; -use HTML::Entities; use HTML::Scrubber; -use RT::Interface::Web::Handler; +use RT::Interface::Web; use RT::Interface::Web::Request; use File::Path qw( rmtree ); use File::Glob qw( bsd_glob ); use File::Spec::Unix; sub DefaultHandlerArgs { ( - comp_root => [ - [ local => $RT::MasonLocalComponentRoot ], - (map {[ "plugin-".$_->Name => $_->ComponentRoot ]} @{RT->Plugins}), - [ standard => $RT::MasonComponentRoot ] + comp_root => [ + RT::Interface::Web->ComponentRoots( Names => 1 ), ], default_escape_flags => 'h', data_dir => "$RT::MasonDataDir", - allow_globals => [qw(%session)], + allow_globals => [qw(%session $DECODED_ARGS)], # Turn off static source if we're in developer mode. static_source => (RT->Config->Get('DevelMode') ? '0' : '1'), use_object_files => (RT->Config->Get('DevelMode') ? '0' : '1'), autoflush => 0, - error_format => (RT->Config->Get('DevelMode') ? 'html': 'brief'), + error_format => (RT->Config->Get('DevelMode') ? 'html': 'rt_error'), request_class => 'RT::Interface::Web::Request', named_component_subs => $INC{'Devel/Cover.pm'} ? 1 : 0, ) }; -# {{{ sub new - -=head2 new - - Constructs a web handler of the appropriate class. - Takes options to pass to the constructor. - -=cut - -sub new { - my $class = shift; - $class->InitSessionDir; - - if ( ($mod_perl::VERSION && $mod_perl::VERSION >= 1.9908) || $CGI::MOD_PERL) { - goto &NewApacheHandler; - } - else { - goto &NewCGIHandler; - } -} - sub InitSessionDir { # Activate the following if running httpd as root (the normal case). # Resets ownership of all files created by Mason at startup. @@ -125,89 +103,40 @@ sub InitSessionDir { } -# }}} - -# {{{ sub NewApacheHandler - -=head2 NewApacheHandler - - Takes extra options to pass to HTML::Mason::ApacheHandler->new - Returns a new Mason::ApacheHandler object - -=cut - -sub NewApacheHandler { - require HTML::Mason::ApacheHandler; - return NewHandler('HTML::Mason::ApacheHandler', args_method => "CGI", @_); -} - -# }}} - -# {{{ sub NewCGIHandler - -=head2 NewCGIHandler - - Returns a new Mason::CGIHandler object - -=cut - -sub NewCGIHandler { - require HTML::Mason::CGIHandler; - return NewHandler( - 'HTML::Mason::CGIHandler', - out_method => sub { - my $m = HTML::Mason::Request->instance; - my $r = $m->cgi_request; - - # Send headers if they have not been sent by us or by user. - $r->send_http_header unless $r->http_header_sent; - - # Set up a default - $r->content_type('text/html; charset=utf-8') - unless $r->content_type; - - if ( $r->content_type =~ /charset=([\w-]+)$/ ) { - my $enc = $1; - if ( lc $enc !~ /utf-?8$/ ) { - for my $str (@_) { - next unless $str; - - # only encode perl internal strings - next unless utf8::is_utf8($str); - $str = Encode::encode( $enc, $str ); - } - } - } - - # default to utf8 encoding - for my $str (@_) { - next unless $str; - next unless utf8::is_utf8($str); - $str = Encode::encode( 'utf8', $str ); - } - - # We could perhaps install a new, faster out_method here that - # wouldn't have to keep checking whether headers have been - # sent and what the $r->method is. That would require - # additions to the Request interface, though. - print STDOUT grep {defined} @_; - }, - @_ - ); -} +use UNIVERSAL::require; sub NewHandler { my $class = shift; + $class->require or die $!; my $handler = $class->new( DefaultHandlerArgs(), + RT->Config->Get('MasonParameters'), @_ ); $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 ); $handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI ); + $handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS ); return($handler); } +=head2 _mason_dir_index + +=cut + +sub _mason_dir_index { + my ($self, $interp, $path) = @_; + $path =~ s!/$!!; + if ( !$interp->comp_exists( $path ) + && $interp->comp_exists( $path . "/index.html" ) ) + { + return $path . "/index.html"; + } + + return $path; +} + + =head2 CleanupRequest Clean ups globals, caches and other things that could be still @@ -260,10 +189,152 @@ sub CleanupRequest { %RT::Ticket::MERGE_CACHE = ( effective => {}, merged => {} ); + # RT::System persists between requests, so its attributes cache has to be + # cleared manually. Without this, for example, subject tags across multiple + # processes will remain cached incorrectly + delete $RT::System->{attributes}; + # Explicitly remove any tmpfiles that GPG opened, and close their - # filehandles. - File::Temp::cleanup; + # filehandles. unless we are doing inline psgi testing, which kills all the tmp file created by tests. + File::Temp::cleanup() + unless $INC{'Test/WWW/Mechanize/PSGI.pm'}; + + +} + + +sub HTML::Mason::Exception::as_rt_error { + my ($self) = @_; + $RT::Logger->error( $self->as_text ); + return "An internal RT error has occurred. Your administrator can find more details in RT's log files."; +} + +=head1 CheckModPerlHandler + +Make sure we're not running with SetHandler perl-script. + +=cut + +sub CheckModPerlHandler{ + my $self = shift; + my $env = shift; + + # Plack::Handler::Apache2 masks MOD_PERL, so use MOD_PERL_API_VERSION + return unless( $env->{'MOD_PERL_API_VERSION'} + and $env->{'MOD_PERL_API_VERSION'} == 2); + + my $handler = $env->{'psgi.input'}->handler; + + return unless defined $handler && $handler eq 'perl-script'; + + $RT::Logger->critical(<new(500); + $res->content_type("text/plain"); + $res->body("Server misconfiguration; see error log for details"); + return $res; +} + +# PSGI App + +use RT::Interface::Web::Handler; +use CGI::Emulate::PSGI; +use Plack::Request; +use Plack::Response; +use Plack::Util; + +sub PSGIApp { + my $self = shift; + + # XXX: this is fucked + require HTML::Mason::CGIHandler; + require HTML::Mason::PSGIHandler::Streamy; + my $h = RT::Interface::Web::Handler::NewHandler('HTML::Mason::PSGIHandler::Streamy'); + + $self->InitSessionDir; + + return sub { + my $env = shift; + + { + my $res = $self->CheckModPerlHandler($env); + return $self->_psgi_response_cb( $res->finalize ) if $res; + } + + RT::ConnectToDatabase() unless RT->InstallMode; + + my $req = Plack::Request->new($env); + + # CGI.pm normalizes .. out of paths so when you requested + # /NoAuth/../Ticket/Display.html we saw Ticket/Display.html + # PSGI doesn't normalize .. so we have to deal ourselves. + if ( $req->path_info =~ m{(^|/)\.\.?(/|$)} ) { + $RT::Logger->crit("Invalid request for ".$req->path_info." aborting"); + my $res = Plack::Response->new(400); + return $self->_psgi_response_cb($res->finalize,sub { $self->CleanupRequest }); + } + $env->{PATH_INFO} = $self->_mason_dir_index( $h->interp, $req->path_info); + + my $ret; + { + # XXX: until we get rid of all $ENV stuff. + local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); + + $ret = $h->handle_psgi($env); + } + + $RT::Logger->crit($@) if $@ && $RT::Logger; + warn $@ if $@ && !$RT::Logger; + if (ref($ret) eq 'CODE') { + my $orig_ret = $ret; + $ret = sub { + my $respond = shift; + local %ENV = (%ENV, CGI::Emulate::PSGI->emulate_environment($env)); + $orig_ret->($respond); + }; + } + + return $self->_psgi_response_cb($ret, + sub { + $self->CleanupRequest() + }); +}; + +sub _psgi_response_cb { + my $self = shift; + my ($ret, $cleanup) = @_; + Plack::Util::response_cb + ($ret, + sub { + my $res = shift; + + if ( RT->Config->Get('Framebusting') ) { + # XXX TODO: Do we want to make the value of this header configurable? + Plack::Util::header_set($res->[1], 'X-Frame-Options' => 'DENY'); + } + + return sub { + if (!defined $_[0]) { + $cleanup->(); + return ''; + } + # XXX: Ideally, responses should flag if they need + # to be encoded, rather than relying on the UTF-8 + # flag + return Encode::encode("UTF-8",$_[0]) if utf8::is_utf8($_[0]); + return $_[0]; + }; + }); + } } -# }}} 1;