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

Jim Brandt jbrandt at bestpractical.com
Fri Jul 28 13:48:50 EDT 2017


The branch, 4.4/email-file-attachments has been created
        at  b6e53aad08954ea497ca5ad96230132f3a92b09a (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 b6e53aad08954ea497ca5ad96230132f3a92b09a
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