[Rt-commit] rt branch, 4.2/inline-images, created. rt-4.1.8-24-g4780104

Thomas Sibley trs at bestpractical.com
Wed Apr 10 15:32:33 EDT 2013


The branch, 4.2/inline-images has been created
        at  4780104c3527f526d83264c01eeec1f87b71303b (commit)

- Log -----------------------------------------------------------------
commit fbf575d05da309d134dff8e75a1527d2d8798ecb
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Sat Jun 30 22:38:01 2012 -0700

    Method to find the siblings of an RT::Attachment
    
    Siblings are attachments on the same MIME part level as each other.
    
    This is especially useful for children of multipart/related parents,
    since the first child usually embeds its siblings in its content.

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index be01ea9..a7e2985 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -278,6 +278,30 @@ sub Children {
     return($kids);
 }
 
+=head2 Siblings
+
+Returns an L<RT::Attachments> object containing all the attachments sharing
+the same immediate parent as the current object, excluding the current
+attachment itself.
+
+If the current attachment is a top-level part (i.e. Parent == 0) then a
+guaranteed empty L<RT::Attachments> object is returned.
+
+=cut
+
+sub Siblings {
+    my $self = shift;
+    my $siblings = RT::Attachments->new( $self->CurrentUser );
+    if ($self->Parent) {
+        $siblings->ChildrenOf( $self->Parent );
+        $siblings->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id );
+    } else {
+        # Ensure emptiness
+        $siblings->Limit( FIELD => 'id', VALUE => 0 );
+    }
+    return $siblings;
+}
+
 =head2 Content
 
 Returns the attachment's content. if it's base64 encoded, decode it 

commit 6da0171e6fe439a03e651b617438314cd4c631ed
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Apr 10 11:59:15 2013 -0700

    Method to find the nearest ancestor of an RT::Attachment with a certain MIME type
    
    The nearest matching ancestor, or closest containing part, is
    particularly useful when finding the enclosing multipart parent for
    further processing.

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index a7e2985..d7a1af5 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -262,6 +262,35 @@ sub ParentObj {
     return $parent;
 }
 
+=head2 Closest
+
+Takes a MIME type as a string or regex.  Returns an L<RT::Attachment> object
+for the nearest containing part with a matching L</ContentType>.  Strings must
+match exactly and all matches are done case insensitively.  Strings ending in a
+C</> must only match the first part of the MIME type.  For example:
+
+    # Find the nearest multipart/* container
+    my $container = $attachment->Closest("multipart/");
+
+Returns undef if no such object is found.
+
+=cut
+
+sub Closest {
+    my $self = shift;
+    my $type = shift;
+    my $part = $self->ParentObj or return undef;
+
+    $type = qr/^\Q$type\E$/
+        unless ref $type eq "REGEX";
+
+    while (lc($part->ContentType) !~ $type) {
+        $part = $part->ParentObj or last;
+    }
+
+    return ($part and $part->id) ? $part : undef;
+}
+
 =head2 Children
 
 Returns an L<RT::Attachments> object which is preloaded with

commit 891ba348a58b6bac6fb1e0fbd814127f9344740d
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Nov 7 10:44:31 2011 -0500

    Display embedded images in HTML messages inline with the text
    
    Attached images with a Content-ID header referred to by an <img
    src="cid:..."> tag in the HTML are now properly displayed as long as the
    existing $ShowTransactionImages option is enabled.  If the option is
    disabled, images are not inlined with the HTML.
    
    This makes RT's message display even more like a real mail client's.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 7056d24..cd09202 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1568,6 +1568,89 @@ sub PotentialPageAction {
     return "";
 }
 
+=head2 RewriteInlineImages PARAMHASH
+
+Turns C<< <img src="cid:..."> >> elements in HTML into working images pointing
+back to RT's stored copy.
+
+Takes the following parameters:
+
+=over 4
+
+=item Content
+
+Scalar ref of the HTML content to rewrite.  Modified in place to support the
+most common use-case.
+
+=item Attachment
+
+The L<RT::Attachment> object from which the Content originates.
+
+=item Related (optional)
+
+Array ref of related L<RT::Attachment> objects to use for C<Content-ID> matching.
+
+Defaults to the result of the C<Siblings> method on the passed Attachment.
+
+=item AttachmentPath (optional)
+
+The base path to use when rewriting C<src> attributes.
+
+Defaults to C< $WebPath/Ticket/Attachment >
+
+=back
+
+In scalar context, returns the number of elements rewritten.
+
+In list content, returns the attachments IDs referred to by the rewritten <img>
+elements, in the order found.  There may be duplicates.
+
+=cut
+
+sub RewriteInlineImages {
+    my %args = (
+        Content         => undef,
+        Attachment      => undef,
+        Related         => undef,
+        AttachmentPath  => RT->Config->Get('WebPath')."/Ticket/Attachment",
+        @_
+    );
+
+    return unless defined $args{Content}
+              and ref $args{Content} eq 'SCALAR'
+              and defined $args{Attachment};
+
+    my $related_part = $args{Attachment}->Closest("multipart/related")
+        or return;
+
+    $args{Related} ||= $related_part->Children->ItemsArrayRef;
+    return unless @{$args{Related}};
+
+    my $content = $args{'Content'};
+    my @rewritten;
+
+    require HTML::RewriteAttributes::Resources;
+    $$content = HTML::RewriteAttributes::Resources->rewrite($$content, sub {
+        my $cid  = shift;
+        my %meta = @_;
+        return $cid unless    lc $meta{tag}  eq 'img'
+                          and lc $meta{attr} eq 'src'
+                          and $cid =~ s/^cid://i;
+
+        for my $attach (@{$args{Related}}) {
+            if (($attach->GetHeader('Content-ID') || '') eq "<$cid>") {
+                push @rewritten, $attach->Id;
+                return "$args{AttachmentPath}/" . $attach->TransactionId . '/' . $attach->Id;
+            }
+        }
+
+        # No attachments means this is a bogus CID. Just pass it through.
+        RT->Logger->debug(qq[Found bogus inline image src="cid:$cid"]);
+        return "cid:$cid";
+    });
+    return @rewritten;
+}
+
 package HTML::Mason::Commands;
 
 use vars qw/$r $m %session/;
@@ -3552,6 +3635,15 @@ our %SCRUBBER_ALLOWED_ATTRIBUTES = (
 
 our %SCRUBBER_RULES = ();
 
+# If we're displaying images, let embedded ones through
+if (RT->Config->Get('ShowTransactionImages')) {
+    $SCRUBBER_RULES{'img'} = {
+        '*' => 0,
+        alt => 1,
+        src => qr/^cid:/i,
+    };
+}
+
 sub _NewScrubber {
     require HTML::Scrubber;
     my $scrubber = HTML::Scrubber->new();
diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments
index b66929c..047eadf 100644
--- a/share/html/Elements/ShowTransactionAttachments
+++ b/share/html/Elements/ShowTransactionAttachments
@@ -86,7 +86,9 @@ $m->comp(
     $m->current_comp,
     %ARGS,
     Parent    => $message->id,
-    ParentObj => $message
+    ParentObj => $message,
+
+    displayed_inline => $displayed_inline,
 );
 
 </%PERL>
@@ -103,6 +105,9 @@ $AttachmentContent => {}
 $Parent => 0
 $ParentObj => undef
 $WarnUnsigned => 0
+
+# Keep track of CID images we display inline
+$displayed_inline => {}
 </%ARGS>
 <%INIT>
 my @DisplayHeaders=qw(_all);
@@ -194,6 +199,19 @@ my $render_attachment = sub {
                     object  => $Object,
                 );
 
+                if (RT->Config->Get('ShowTransactionImages')) {
+                    my @rewritten = RT::Interface::Web::RewriteInlineImages(
+                        Content         => \$content,
+                        Attachment      => $message,
+                        # Not technically correct to search all parts of the
+                        # MIME structure, but it saves having to go to the
+                        # database again and is unlikely to break display.
+                        Related         => [ map { @$_ } values %$Attachments ],
+                        AttachmentPath  => $AttachmentPath,
+                    );
+                    $displayed_inline->{$_}++ for @rewritten;
+                }
+
                 require HTML::Quoted;
                 $content = HTML::Quoted->extract($content) unless length $name;
 
@@ -229,7 +247,11 @@ my $render_attachment = sub {
 
     # if it's an image, show it as an image
     elsif ( RT->Config->Get('ShowTransactionImages') and  $content_type =~ m{^image/} ) {
-        if ( $disposition ne 'inline' ) {
+        if ( $displayed_inline->{$message->Id} ) {
+            $m->out('<p><i>'. loc( 'Image displayed inline above' ) .'</i></p>');
+            return;
+        }
+        elsif ( $disposition ne 'inline' ) {
             $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>');
             return;
         }

commit beb22f22f87e1252224e1c6e154ac5c7419bc55a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 29 15:11:49 2012 -0700

    Move the HTML::RewriteAttributes dependency to CORE from DASHBOARDS
    
    It's unlikely anyone configures RT with --without-dashboards, but just
    in case.

diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
index a2319dc..263017c 100644
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@ -204,6 +204,7 @@ HTML::FormatText
 HTML::Mason 1.43
 HTML::Mason::PSGIHandler 0.52
 HTML::Quoted
+HTML::RewriteAttributes 0.05
 HTML::Scrubber 0.08
 HTML::TreeBuilder
 HTTP::Message 6.0
@@ -325,7 +326,6 @@ Data::ICal
 .
 
 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
-HTML::RewriteAttributes 0.05
 MIME::Types
 URI 1.59
 .

commit 25e4571872827d7cffdebd6d71eb8e7547039dfe
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 29 17:21:47 2012 -0700

    Explain why attached images aren't shown if ShowTransactionImages is off
    
    This is similar to our behaviour with SuppressInlineTextFiles and more
    obvious what's happening.

diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments
index 047eadf..c770dfc 100644
--- a/share/html/Elements/ShowTransactionAttachments
+++ b/share/html/Elements/ShowTransactionAttachments
@@ -246,8 +246,12 @@ my $render_attachment = sub {
     }
 
     # if it's an image, show it as an image
-    elsif ( RT->Config->Get('ShowTransactionImages') and  $content_type =~ m{^image/} ) {
-        if ( $displayed_inline->{$message->Id} ) {
+    elsif ( $content_type =~ m{^image/} ) {
+        if (not RT->Config->Get('ShowTransactionImages')) {
+            $m->out('<p><i>'. loc( 'Image not shown because display is disabled in system configuration.' ) .'</i></p>');
+            return;
+        }
+        elsif ( $displayed_inline->{$message->Id} ) {
             $m->out('<p><i>'. loc( 'Image displayed inline above' ) .'</i></p>');
             return;
         }

commit 0f287bb0c5c277014401d530d49112dbd0e6ee18
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 29 17:23:26 2012 -0700

    It's not a message body, it's an image

diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments
index c770dfc..6c98ba9 100644
--- a/share/html/Elements/ShowTransactionAttachments
+++ b/share/html/Elements/ShowTransactionAttachments
@@ -256,7 +256,7 @@ my $render_attachment = sub {
             return;
         }
         elsif ( $disposition ne 'inline' ) {
-            $m->out('<p>'. loc( 'Message body is not shown because sender requested not to inline it.' ) .'</p>');
+            $m->out('<p>'. loc( 'Image not shown because sender requested not to inline it.' ) .'</p>');
             return;
         }
 

commit 2bdc86e2dc85eafbaa6a99c80222bfda65b61bc2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Sat Jun 30 22:57:15 2012 -0700

    Tidy up indenting and whitespace
    
    Whitespace only changes, no functional difference.

diff --git a/share/html/Ticket/Attachment/dhandler b/share/html/Ticket/Attachment/dhandler
index 4cd2493..e90e513 100644
--- a/share/html/Ticket/Attachment/dhandler
+++ b/share/html/Ticket/Attachment/dhandler
@@ -46,50 +46,48 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%perl>
-    my ($ticket, $trans,$attach, $filename);
-    my $arg = $m->dhandler_arg;                # get rest of path
-    if ($arg =~ m{^(\d+)/(\d+)}) {
-        $trans = $1;
-        $attach = $2;
-    }
-    else {
-        Abort("Corrupted attachment URL.");
-    }
-     my $AttachmentObj = RT::Attachment->new($session{'CurrentUser'});
-     $AttachmentObj->Load($attach) || Abort("Attachment '$attach' could not be loaded");
+my ( $ticket, $trans, $attach, $filename );
+my $arg = $m->dhandler_arg;    # get rest of path
+if ( $arg =~ m{^(\d+)/(\d+)} ) {
+    $trans  = $1;
+    $attach = $2;
+}
+else {
+    Abort("Corrupted attachment URL.");
+}
+my $AttachmentObj = RT::Attachment->new( $session{'CurrentUser'} );
+$AttachmentObj->Load($attach) || Abort("Attachment '$attach' could not be loaded");
 
+unless ( $AttachmentObj->id ) {
+    Abort("Bad attachment id. Couldn't find attachment '$attach'\n");
+}
+unless ( $AttachmentObj->TransactionId() == $trans ) {
+    Abort("Bad transaction number for attachment. $trans should be". $AttachmentObj->TransactionId() . "\n");
+}
 
-     unless ($AttachmentObj->id) {
-        Abort("Bad attachment id. Couldn't find attachment '$attach'\n");
-    }
-     unless ($AttachmentObj->TransactionId() == $trans ) {
-        Abort("Bad transaction number for attachment. $trans should be".$AttachmentObj->TransactionId() ."\n");
+my $content_type = $AttachmentObj->ContentType || 'text/plain';
 
-     }
+if ( RT->Config->Get('AlwaysDownloadAttachments') ) {
+    $r->headers_out->{'Content-Disposition'} = "attachment; filename=" . $AttachmentObj->Filename;
+}
+elsif ( !RT->Config->Get('TrustHTMLAttachments') ) {
+    $content_type = 'text/plain' if ( $content_type =~ /^text\/html/i );
+}
 
-     my $content_type = $AttachmentObj->ContentType || 'text/plain';
+my $enc  = $AttachmentObj->OriginalEncoding || 'utf-8';
+my $iana = Encode::find_encoding($enc);
+   $iana = $iana ? $iana->mime_name : $enc;
 
-     if (RT->Config->Get('AlwaysDownloadAttachments')) {
-         $r->headers_out->{'Content-Disposition'} = "attachment; filename=" . $AttachmentObj->Filename;
-     }
-     elsif (!RT->Config->Get('TrustHTMLAttachments')) {
-         $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);
-     }
+require MIME::Types;
+my $mimetype = MIME::Types->new->type($content_type);
+unless ( $mimetype && $mimetype->isBinary ) {
+    $content_type .= ";charset=$iana";
+}
 
-     my $enc = $AttachmentObj->OriginalEncoding || 'utf-8';
-     my $iana = Encode::find_encoding( $enc );
-     $iana = $iana? $iana->mime_name : $enc;
-
-     require MIME::Types;
-     my $mimetype = MIME::Types->new->type($content_type);
-     unless ( $mimetype && $mimetype->isBinary ) {
-         $content_type .= ";charset=$iana";
-     }
-
-     $r->content_type( $content_type );
-     $m->clear_buffer();
-     $m->out($AttachmentObj->OriginalContent);
-     $m->abort; 
+$r->content_type($content_type);
+$m->clear_buffer();
+$m->out( $AttachmentObj->OriginalContent );
+$m->abort;
 </%perl>
 <%attr>
 AutoFlush => 0

commit ac5a01f346cb9d5a760c0da4c6e29c36e67c8a24
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Sat Jun 30 23:06:41 2012 -0700

    Rewrite inline images when serving a single HTML attachment for display
    
    This only happens in the non-default configuration of
    AlwaysDownloadAttachments off (default) and TrustHTMLAttachments on
    (non-default).  The latter option is primarily used by people so HTML is
    rendered in full without the restrictions imposed within ticket history.
    It's reasonable to surmise that a site already trusting HTML attachments
    for better display will prefer working embedded images.

diff --git a/share/html/Ticket/Attachment/dhandler b/share/html/Ticket/Attachment/dhandler
index e90e513..5907427 100644
--- a/share/html/Ticket/Attachment/dhandler
+++ b/share/html/Ticket/Attachment/dhandler
@@ -65,6 +65,7 @@ unless ( $AttachmentObj->TransactionId() == $trans ) {
     Abort("Bad transaction number for attachment. $trans should be". $AttachmentObj->TransactionId() . "\n");
 }
 
+my $content = $AttachmentObj->OriginalContent;
 my $content_type = $AttachmentObj->ContentType || 'text/plain';
 
 if ( RT->Config->Get('AlwaysDownloadAttachments') ) {
@@ -73,6 +74,15 @@ if ( RT->Config->Get('AlwaysDownloadAttachments') ) {
 elsif ( !RT->Config->Get('TrustHTMLAttachments') ) {
     $content_type = 'text/plain' if ( $content_type =~ /^text\/html/i );
 }
+elsif (lc $content_type eq 'text/html') {
+    # If we're trusting and serving HTML for display not download, try to do
+    # inline <img> rewriting to be extra helpful.
+    my $count = RT::Interface::Web::RewriteInlineImages(
+        Content     => \$content,
+        Attachment  => $AttachmentObj,
+    );
+    RT->Logger->debug("Rewrote $count CID images when displaying original HTML attachment #$attach");
+}
 
 my $enc  = $AttachmentObj->OriginalEncoding || 'utf-8';
 my $iana = Encode::find_encoding($enc);
@@ -86,7 +96,7 @@ unless ( $mimetype && $mimetype->isBinary ) {
 
 $r->content_type($content_type);
 $m->clear_buffer();
-$m->out( $AttachmentObj->OriginalContent );
+$m->out($content);
 $m->abort;
 </%perl>
 <%attr>

commit 4780104c3527f526d83264c01eeec1f87b71303b
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Jun 29 16:14:53 2012 -0700

    Preserve the Content-ID of redistributed attachments
    
    This opens the door for supporting inlined images in HTML notifications.

diff --git a/lib/RT/Action/SendEmail.pm b/lib/RT/Action/SendEmail.pm
index abb0dac..65f99fd 100644
--- a/lib/RT/Action/SendEmail.pm
+++ b/lib/RT/Action/SendEmail.pm
@@ -406,6 +406,7 @@ sub AddAttachment {
         Data        => $attach->OriginalContent,
         Disposition => $disp, # a false value defaults to inline in MIME::Entity
         Filename    => $self->MIMEEncodeString( $attach->Filename ),
+        Id          => $attach->GetHeader('Content-ID'),
         'RT-Attachment:' => $self->TicketObj->Id . "/"
             . $self->TransactionObj->Id . "/"
             . $attach->id,

-----------------------------------------------------------------------


More information about the Rt-commit mailing list