summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Interface/Web.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Interface/Web.pm')
-rw-r--r--rt/lib/RT/Interface/Web.pm100
1 files changed, 88 insertions, 12 deletions
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
index b4279fb..d6b854f 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);