rt 4.0.23
[freeside.git] / rt / lib / RT / Dashboard / Mailer.pm
index 40b53b1..8582acd 100644 (file)
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
 #                                          <sales@bestpractical.com>
 #
 # (Except where explicitly superseded by other copyright notices)
@@ -59,6 +59,7 @@ use RT::Dashboard;
 use RT::Interface::Web::Handler;
 use RT::Interface::Web;
 use File::Temp 'tempdir';
+use HTML::Scrubber;
 
 sub MailDashboards {
     my $self = shift;
@@ -145,7 +146,7 @@ sub IsSubscriptionReady {
     my $sub_hour      = $subscription->SubValue('Hour');
     my $sub_dow       = $subscription->SubValue('Dow');
     my $sub_dom       = $subscription->SubValue('Dom');
-    my $sub_fow       = $subscription->SubValue('Fow');
+    my $sub_fow       = $subscription->SubValue('Fow') || 1;
 
     my ($hour, $dow, $dom) = @{ $args{LocalTime} };
 
@@ -164,8 +165,6 @@ sub IsSubscriptionReady {
         return 0 if $sub_dow ne $dow;
 
         # does it match the "every N weeks" clause?
-        $sub_fow = 1 if !$sub_fow;
-
         return 1 if $counter % $sub_fow == 0;
 
         $subscription->SetSubValues(Counter => $counter + 1)
@@ -248,6 +247,8 @@ SUMMARY
         }
     }
 
+    $content = ScrubContent($content);
+
     $RT::Logger->debug("Got ".length($content)." characters of output.");
 
     $content = HTML::RewriteAttributes::Links->rewrite(
@@ -381,9 +382,14 @@ sub BuildEmail {
             $cid_of{$uri} = time() . $$ . int(rand(1e6));
             my ($data, $filename, $mimetype, $encoding) = GetResource($uri);
 
-            # downgrade non-text strings, because all strings are utf8 by
-            # default, which is wrong for non-text strings.
-            if ( $mimetype !~ m{text/} ) {
+            # Encode textual data in UTF-8, and downgrade (treat
+            # codepoints as codepoints, and ensure the UTF-8 flag is
+            # off) everything else.
+            my @extra;
+            if ( $mimetype =~ m{text/} ) {
+                $data = Encode::encode( "UTF-8", $data );
+                @extra = ( Charset => "UTF-8" );
+            } else {
                 utf8::downgrade( $data, 1 ) or $RT::Logger->warning("downgrade $data failed");
             }
 
@@ -393,8 +399,9 @@ sub BuildEmail {
                 Type         => $mimetype,
                 Encoding     => $encoding,
                 Disposition  => 'inline',
-                Name         => $filename,
+                Name         => RT::Interface::Email::EncodeToMIME( String => $filename ),
                 'Content-Id' => $cid_of{$uri},
+                @extra,
             );
 
             return "cid:$cid_of{$uri}";
@@ -408,23 +415,26 @@ sub BuildEmail {
     );
 
     my $entity = MIME::Entity->build(
-        From    => $args{From},
-        To      => $args{To},
-        Subject => $args{Subject},
+        From    => Encode::encode("UTF-8", $args{From}),
+        To      => Encode::encode("UTF-8", $args{To}),
+        Subject => RT::Interface::Email::EncodeToMIME( String => $args{Subject} ),
         Type    => "multipart/mixed",
     );
 
     $entity->attach(
-        Data        => Encode::encode_utf8($content),
         Type        => 'text/html',
         Charset     => 'UTF-8',
+        Data        => Encode::encode("UTF-8", $content),
         Disposition => 'inline',
+        Encoding    => "base64",
     );
 
     for my $part (@parts) {
         $entity->add_part($part);
     }
 
+    $entity->make_singlepart;
+
     return $entity;
 }
 
@@ -463,6 +473,33 @@ sub BuildEmail {
 }
 
 {
+    my $scrubber;
+
+    sub _scrubber {
+        unless ($scrubber) {
+            $scrubber = HTML::Scrubber->new;
+            # Allow everything by default, except JS attributes ...
+            $scrubber->default(
+                1 => {
+                    '*' => 1,
+                    map { ("on$_" => 0) }
+                         qw(blur change click dblclick error focus keydown keypress keyup load
+                            mousedown mousemove mouseout mouseover mouseup reset select submit unload)
+                }
+            );
+            # ... and <script>s
+            $scrubber->deny('script');
+        }
+        return $scrubber;
+    }
+
+    sub ScrubContent {
+        my $content = shift;
+        return _scrubber->scrub($content);
+    }
+}
+
+{
     my %cache;
 
     sub HourDowDomIn {
@@ -516,6 +553,9 @@ sub GetResource {
 
         for ($k, $v) { s/%(..)/chr hex $1/ge }
 
+        # Decode from bytes to characters
+        $_ = Encode::decode( "UTF-8", $_ ) for $k, $v;
+
         # no value yet, simple key=value
         if (!exists $args{$k}) {
             $args{$k} = $v;
@@ -560,8 +600,9 @@ sub GetResource {
 {
     package RT::Dashboard::FakeRequest;
     sub new { bless {}, shift }
-    sub header_out { shift }
-    sub headers_out { shift }
+    sub header_out { return undef }
+    sub headers_out { wantarray ? () : {} }
+    sub err_headers_out { wantarray ? () : {} }
     sub content_type {
         my $self = shift;
         $self->{content_type} = shift if @_;