[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