[Rt-commit] rt branch, 4.4/email-file-attachments, created. rt-4.4.2-20-g00e9942

Jim Brandt jbrandt at bestpractical.com
Mon Jul 31 14:13:12 EDT 2017


The branch, 4.4/email-file-attachments has been created
        at  00e9942ef6edd0f2ecea59d3c4903b81e31ee9ba (commit)

- Log -----------------------------------------------------------------
commit 805acb135a36b33fba0b31806ce5bc95efd06a65
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jul 28 13:39:21 2017 -0400

    Add an option to treat attached email messages as normal file attachments

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 0899c7e..48fd0fe 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -503,6 +503,23 @@ clean up intentional double newlines as well.
 
 Set( $CheckMoreMSMailHeaders, 0);
 
+=item C<$TreatAttachedEmailAsFiles>
+
+Email coming into RT can sometimes have another email attached, when
+an email is forwarded as an attachment, for example. By default, RT recognizes
+that the attached content is an email and does some processing, including
+some parsing the headers of the attached email. You can see this in suggested
+email addresses on the People page and One-time Cc on reply.
+
+If you want RT to treat attached email files as regular file attachments,
+set this option to true (1). With this option enabled, attached email will
+show up in the "Attachments" section like other types of file attachments
+and content like headers will not be processed.
+
+=cut
+
+Set( $TreatAttachedEmailAsFiles, 0);
+
 =back
 
 
diff --git a/lib/RT/EmailParser.pm b/lib/RT/EmailParser.pm
index 61fcd13..26da75e 100644
--- a/lib/RT/EmailParser.pm
+++ b/lib/RT/EmailParser.pm
@@ -60,6 +60,7 @@ use MIME::Entity;
 use MIME::Head;
 use MIME::Parser;
 use File::Temp qw/tempdir/;
+use RT::Util qw(mime_recommended_filename EntityLooksLikeEmailMessage);
 
 =head1 NAME
 
@@ -270,6 +271,8 @@ sub _PostProcessNewEntity {
 
     #Now we've got a parsed mime object. 
 
+    _DetectAttachedEmailFiles($self->{'entity'});
+
     # Unfold headers that are have embedded newlines
     #  Better do this before conversion or it will break
     #  with multiline encoded Subject (RFC2047) (fsck.com #5594)
@@ -279,6 +282,70 @@ sub _PostProcessNewEntity {
     RT::I18N::SetMIMEEntityToEncoding($self->{'entity'}, 'utf-8');
 }
 
+=head2 _DetectAttachedEmailFiles
+
+Email messages submitted as attachments will be processed as a nested email
+rather than an attached file. Users may prefer to treat attached emails
+as normal file attachments and not have them processed.
+
+If TreatAttachedEmailAsFiles is selected, treat attached email files
+as regular file attachments. We do this by checking MIME entities that
+have email (message/) content types and also have a defined filename,
+indicating they are attachments.
+
+See the extract_nested_messages documentation in in the L<MIME::Parser>
+module for details on how it deals with nested email messages.
+
+=cut
+
+sub _DetectAttachedEmailFiles {
+    my $entity = shift;
+
+    return unless RT->Config->Get('TreatAttachedEmailAsFiles');
+
+    my $filename = mime_recommended_filename($entity);
+
+    # This detection is based on the way MIME::Parser handles email with
+    # extract_nested_messages enabled.
+
+    if ( EntityLooksLikeEmailMessage($entity)
+         && not defined $entity->bodyhandle
+         && $filename ) {
+
+        # Fixup message
+        # TODO: Investigate proposing an option upstream in MIME::Parser to avoid the initial parse
+        if ( $entity->parts(0) ){
+            my $bodyhandle = $entity->parts(0)->bodyhandle;
+
+            # Get the headers from the part and write them back to the body.
+            # This will create a file attachment that looks like the file you get if
+            # you "Save As" and email message in your email client.
+            # If we don't save them here, the headers from the attached email will be lost.
+
+            my $headers = $entity->parts(0)->head->as_string;
+            my $body = $bodyhandle->as_string;
+
+            my $IO = $bodyhandle->open("w")
+                || RT::Logger->error("Unable to open email body: $!");
+            $IO->print($headers . "\n");
+            $IO->print($body);
+            $IO->close
+                || RT::Logger->error("Unable to close email body: $!");
+
+            $entity->parts([]);
+            $entity->bodyhandle($bodyhandle);
+            RT::Logger->debug("Manually setting bodyhandle for attached email file");
+        }
+    }
+    elsif ( $entity->parts ) {
+        foreach my $part ( $entity->parts ){
+            _DetectAttachedEmailFiles( $part );
+        }
+    }
+
+    return;
+}
+
 =head2 IsRTaddress ADDRESS
 
 Takes a single parameter, an email address. 
@@ -333,9 +400,6 @@ sub CullRTAddresses {
 }
 
 
-
-
-
 # LookupExternalUserInfo is a site-definable method for synchronizing
 # incoming users with an external data source. 
 #
diff --git a/lib/RT/Util.pm b/lib/RT/Util.pm
index 06cd046..f3cce37 100644
--- a/lib/RT/Util.pm
+++ b/lib/RT/Util.pm
@@ -52,7 +52,7 @@ use warnings;
 
 
 use base 'Exporter';
-our @EXPORT = qw/safe_run_child mime_recommended_filename/;
+our @EXPORT = qw/safe_run_child mime_recommended_filename EntityLooksLikeEmailMessage/;
 
 use Encode qw/encode/;
 
@@ -215,6 +215,30 @@ sub constant_time_eq {
     return 0 + not $result;
 }
 
+=head2 EntityLooksLikeEmailMessage( MIME::Entity )
+
+Check MIME type headers for entities that look like email.
+
+=cut
+
+sub EntityLooksLikeEmailMessage {
+    my $entity = shift;
+
+    return unless $entity;
+
+    # Use mime_type instead of effective_type to get the same headers
+    # MIME::Parser used.
+    my $mime_type = $entity->mime_type();
+
+    # This is the same list of MIME types MIME::Parser uses. The partial and
+    # external-body types are unlikely to produce usable attachments, but they
+    # are still recognized as email for the purposes of this function.
+
+    my @email_types = ('message/rfc822', 'message/partial', 'message/external-body');
+
+    return 1 if grep { $mime_type eq $_ } @email_types;
+    return 0;
+}
 
 RT::Base->_ImportOverlays();
 

commit f4dae84f9bd346bae654a2fdbe3c38a0d7fc3cde
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jul 28 15:38:44 2017 -0400

    Restore attached email parsing when $TreatAttachedEmailAsFiles is not set
    
    For cases where RT parses emails that come in as attachments to
    other email, restore the previous parsing of additional headers.
    This will show email addresses from attached email as suggestions on
    the People page and on reply. This is useful to more easily set up
    roles on a ticket in cases where someone forwards email into RT
    that was intended for another person or queue.
    
    The $TreatAttachedEmailAsFiles option now provides an alternate
    behavior for email attachments that doesn't try to parse attached
    email. When this is set, get email addresses only from the parent
    attachment.

diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 3a24100..0f073c3 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1052,10 +1052,14 @@ sub TransactionAddresses {
     $attachments->LimitByTicket( $self->id );
     $attachments->Columns( qw( id Headers TransactionId));
 
-    $attachments->Limit(
-        FIELD => 'Parent',
-        VALUE => 0,
-    );
+    # If $TreatAttachedEmailAsFiles is set, don't parse child attachments
+    # for email addresses.
+    if ( RT->Config->Get('TreatAttachedEmailAsFiles') ){
+        $attachments->Limit(
+            FIELD => 'Parent',
+            VALUE => 0,
+        );
+    }
 
     $attachments->Limit(
         ALIAS         => $attachments->TransactionAlias,

commit 00e9942ef6edd0f2ecea59d3c4903b81e31ee9ba
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Jul 28 13:46:46 2017 -0400

    Tests for email file attachments

diff --git a/t/data/emails/email-file-attachment.eml b/t/data/emails/email-file-attachment.eml
new file mode 100644
index 0000000..129bd9f
--- /dev/null
+++ b/t/data/emails/email-file-attachment.eml
@@ -0,0 +1,67 @@
+Return-Path: <user1 at example.com>
+X-Spam-Flag: NO
+X-Spam-Score: -2.449
+X-Spam-Level: 
+X-Spam-Status: No, score=-2.449 tagged_above=-99.9 required=10
+	tests=[AWL=0.250, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,
+	DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, KHOP_DYNAMIC=0.001,
+	RCVD_IN_DNSWL_LOW=-0.7, SPF_PASS=-0.001] autolearn=ham
+X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,,
+ definitions=2017-07-28_05:,, signatures=0
+X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0
+ clxscore=1034 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0
+ bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1
+ engine=8.0.1-1701120000 definitions=main-1707280209
+From: Jim Brandt <user1 at example.com>
+Content-type: multipart/mixed;
+ boundary="Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B"
+Subject: This is a test
+Message-id: <A23A3AC0-D9E3-4965-83DF-F29217AF28F6 at mac.com>
+Date: Fri, 28 Jul 2017 09:21:48 -0400
+To: rt at example.com
+MIME-version: 1.0 (Mac OS X Mail 9.3 \(3124\))
+X-Mailer: Apple Mail (2.3124)
+
+
+--Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B
+Content-Transfer-Encoding: 7bit
+Content-Type: text/plain;
+	charset=us-ascii
+
+This is a test with an email file attachment.
+
+
+--Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B
+Content-Disposition: attachment;
+	filename=test-email.eml
+Content-Type: message/rfc822;
+	name="test-email.eml"
+Content-Transfer-Encoding: binary
+
+Return-Path: <user1 at example.com>
+X-Spam-Flag: NO
+X-Spam-Score: -2.199
+X-Spam-Level: 
+X-Spam-Status: No, score=-2.199 tagged_above=-99.9 required=10
+	tests=[AWL=0.500, BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,
+	DKIM_VALID_AU=-0.1, FREEMAIL_FROM=0.001, KHOP_DYNAMIC=0.001,
+	RCVD_IN_DNSWL_LOW=-0.7, SPF_PASS=-0.001] autolearn=ham
+X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,,
+ definitions=2017-07-28_05:,, signatures=0
+X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0
+ clxscore=1034 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0
+ bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1
+ engine=8.0.1-1701120000 definitions=main-1707280209
+From: Jim Brandt <user1 at example.com>
+Content-type: text/plain; charset=us-ascii
+Content-transfer-encoding: 7bit
+Subject: This is a test
+Message-id: <8DB138B7-D410-4640-81B0-7A3363EB0180 at mac.com>
+Date: Fri, 28 Jul 2017 09:19:28 -0400
+To: rt at example.com
+MIME-version: 1.0 (Mac OS X Mail 9.3 \(3124\))
+X-Mailer: Apple Mail (2.3124)
+
+This is a test.
+
+--Apple-Mail=_E5E623A8-7064-4736-9F2E-2A0F35E3635B--
diff --git a/t/mail/email-file-attachments.t b/t/mail/email-file-attachments.t
new file mode 100644
index 0000000..265424e
--- /dev/null
+++ b/t/mail/email-file-attachments.t
@@ -0,0 +1,58 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef,
+    config => 'Set( $TreatAttachedEmailAsFiles, 1);'
+;
+
+use File::Spec ();
+use Email::Abstract;
+
+# We're not testing acls here.
+my $everyone = RT::Group->new(RT->SystemUser);
+$everyone->LoadSystemInternalGroup('Everyone');
+$everyone->PrincipalObj->GrantRight( Right =>'SuperUser' );
+
+# some utils
+sub first_txn    { return $_[0]->Transactions->First }
+sub count_attachs { return first_txn($_[0])->Attachments->Count }
+
+sub mail_in_ticket {
+    my ($filename) = @_;
+    my $path = RT::Test::get_relocatable_file($filename,
+        (File::Spec->updir(), 'data', 'emails'));
+    my $content = RT::Test->file_content($path);
+
+    RT::Test->clean_caught_mails;
+    my ($status, $id) = RT::Test->send_via_mailgate( $content );
+    ok( !$status, "Fed $filename into mailgate");
+
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    $ticket->Load($id);
+    ok( $ticket->Id, "Successfully created ticket ".$ticket->Id);
+
+    my @mail = map {Email::Abstract->new($_)->cast('MIME::Entity')}
+        RT::Test->fetch_caught_mails;
+    return ($ticket, @mail);
+}
+
+diag "Process email with an email file attached";
+{
+    my ($ticket) = mail_in_ticket('email-file-attachment.eml');
+    like( first_txn($ticket)->Content , qr/This is a test with an email file attachment/, "Parsed the email body");
+    is( count_attachs($ticket), 3,
+        "Has three attachments, presumably multipart/mixed, text-plain, message");
+
+    my $attachments = $ticket->Transactions->First->Attachments;
+
+    my $attachment = $attachments->Next;
+    is( $attachment->Subject, 'This is a test', 'Subject is correct' );
+
+    $attachment = $attachments->Next;
+    is( $attachment->ContentType, 'text/plain', 'Got the first part of the main email' );
+
+    $attachment = $attachments->Next;
+    is( $attachment->Filename, 'test-email.eml', 'Got a filename for the attached email file' );
+}
+
+done_testing();

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


More information about the rt-commit mailing list