diff options
Diffstat (limited to 'rt/lib/RT/Interface/Web.pm')
-rw-r--r-- | rt/lib/RT/Interface/Web.pm | 100 |
1 files changed, 88 insertions, 12 deletions
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index b4279fb4b..d6b854f4e 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -554,6 +554,58 @@ sub StaticFileHeaders { # $HTML::Mason::Commands::r->headers_out->{'Last-Modified'} = $date->RFC2616; } +=head2 PathIsSafe + +Takes a C<< Path => path >> and returns a boolean indicating that +the path is safely within RT's control or not. The path I<must> be +relative. + +This function does not consult the filesystem at all; it is merely +a logical sanity checking of the path. This explicitly does not handle +symlinks; if you have symlinks in RT's webroot pointing outside of it, +then we assume you know what you are doing. + +=cut + +sub PathIsSafe { + my $self = shift; + my %args = @_; + my $path = $args{Path}; + + # Get File::Spec to clean up extra /s, ./, etc + my $cleaned_up = File::Spec->canonpath($path); + + if (!defined($cleaned_up)) { + $RT::Logger->info("Rejecting path that canonpath doesn't understand: $path"); + return 0; + } + + # Forbid too many ..s. We can't just sum then check because + # "../foo/bar/baz" should be illegal even though it has more + # downdirs than updirs. So as soon as we get a negative score + # (which means "breaking out" of the top level) we reject the path. + + my @components = split '/', $cleaned_up; + my $score = 0; + for my $component (@components) { + if ($component eq '..') { + $score--; + if ($score < 0) { + $RT::Logger->info("Rejecting unsafe path: $path"); + return 0; + } + } + elsif ($component eq '.' || $component eq '') { + # these two have no effect on $score + } + else { + $score++; + } + } + + return 1; +} + =head2 SendStaticFile Takes a File => path and a Type => Content-type @@ -571,6 +623,12 @@ sub SendStaticFile { my %args = @_; my $file = $args{File}; my $type = $args{Type}; + my $relfile = $args{RelativeFile}; + + if (defined($relfile) && !$self->PathIsSafe(Path => $relfile)) { + $HTML::Mason::Commands::r->status(400); + $HTML::Mason::Commands::m->abort; + } $self->StaticFileHeaders(); @@ -874,7 +932,9 @@ sub CreateTicket { } foreach my $argument (qw(Encrypt Sign)) { - $MIMEObj->head->add( "X-RT-$argument" => $ARGS{$argument} ) if defined $ARGS{$argument}; + $MIMEObj->head->add( + "X-RT-$argument" => Encode::encode_utf8( $ARGS{$argument} ) + ) if defined $ARGS{$argument}; } my %create_args = ( @@ -1084,7 +1144,9 @@ sub ProcessUpdateMessage { Type => $args{ARGSRef}->{'UpdateContentType'}, ); - $Message->head->add( 'Message-ID' => RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'}, ) ); + $Message->head->add( 'Message-ID' => Encode::encode_utf8( + RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} ) + ) ); my $old_txn = RT::Transaction->new( $session{'CurrentUser'} ); if ( $args{ARGSRef}->{'QuoteTransaction'} ) { $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} ); @@ -1124,6 +1186,24 @@ sub ProcessUpdateMessage { TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'} ); + my @temp_squelch; + foreach my $type (qw(Cc AdminCc)) { + if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) { + push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} ); + push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses; + push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses; + } + } + if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) { + push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} ); + push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses; + } + + if (@temp_squelch) { + require RT::Action::SendEmail; + RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch ); + } + unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) { foreach my $key ( keys %{ $args{ARGSRef} } ) { next unless $key =~ /^Update(Cc|Bcc)-(.*)$/; @@ -1182,9 +1262,8 @@ sub MakeMIMEEntity { ); my $Message = MIME::Entity->build( Type => 'multipart/mixed', - Subject => $args{'Subject'} || "", - From => $args{'From'}, - Cc => $args{'Cc'}, + map { $_ => Encode::encode_utf8( $args{ $_} ) } + grep defined $args{$_}, qw(Subject From Cc) ); if ( defined $args{'Body'} && length $args{'Body'} ) { @@ -1192,12 +1271,8 @@ sub MakeMIMEEntity { # Make the update content have no 'weird' newlines in it $args{'Body'} =~ s/\r\n/\n/gs; - # MIME::Head is not happy in utf-8 domain. This only happens - # when processing an incoming email (so far observed). - no utf8; - use bytes; $Message->attach( - Type => $args{'Type'} || 'text/plain', + Type => $args{'Type'} || 'text/plain', Charset => 'UTF-8', Data => $args{'Body'}, ); @@ -1218,8 +1293,8 @@ sub MakeMIMEEntity { # Prefer the cached name first over CGI.pm stringification. my $filename = $RT::Mason::CGI::Filename; - $filename = "$filehandle" unless defined($filename); - $filename = Encode::decode_utf8($filename); + $filename = "$filehandle" unless defined $filename; + $filename = Encode::encode_utf8( $filename ); $filename =~ s{^.*[\\/]}{}; $Message->attach( @@ -1234,6 +1309,7 @@ sub MakeMIMEEntity { } $Message->make_singlepart; + RT::I18N::SetMIMEEntityToUTF8($Message); # convert text parts into utf-8 return ($Message); |