Skip to content

Commit 1b6597d

Browse files
Test variations of multipart/related email structures
1 parent 6b299b5 commit 1b6597d

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Subject: multipart/alternative wrapping a multipart/related html part
2+
From: root@localhost
3+
To: rt@example.com
4+
Content-Type: multipart/alternative; boundary="=_alt_boundary"
5+
6+
This is a multipart message in MIME format.
7+
8+
--=_alt_boundary
9+
Content-Type: text/plain; charset="UTF-8"
10+
Content-Transfer-Encoding: 7bit
11+
12+
PLAINDOWNGRADEMARKER this is the plain-text downgrade of the rich message.
13+
14+
--=_alt_boundary
15+
Content-Type: multipart/related; boundary="=_rel_boundary"
16+
17+
--=_rel_boundary
18+
Content-Type: text/html; charset="UTF-8"
19+
Content-Transfer-Encoding: 7bit
20+
21+
<div>HTMLBODYMARKER this is the composed rich body.<br />
22+
<img src="cid:inline-image@example.com" /></div>
23+
24+
--=_rel_boundary
25+
Content-Type: image/png; name="inline.png"
26+
Content-Transfer-Encoding: base64
27+
Content-Disposition: inline; filename="inline.png"
28+
Content-ID: <inline-image@example.com>
29+
30+
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M8A
31+
AAMBAQDJ/pLvAAAAAElFTkSuQmCC
32+
33+
--=_rel_boundary--
34+
35+
--=_alt_boundary--
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Subject: multipart/related with a text/plain root
2+
From: root@localhost
3+
To: rt@example.com
4+
Content-Type: multipart/related; boundary="=_related_boundary_42"
5+
6+
This is a multipart message in MIME format.
7+
8+
--=_related_boundary_42
9+
Content-Type: text/plain; charset="UTF-8"
10+
Content-Transfer-Encoding: 7bit
11+
12+
UNIQUEPLAINBODY this is the plain text body of a multipart/related message that
13+
has no text/html alternative part. It must still be displayed.
14+
15+
--=_related_boundary_42
16+
Content-Type: application/octet-stream; name="data1.bin"
17+
Content-Transfer-Encoding: base64
18+
Content-Disposition: attachment; filename="data1.bin"
19+
20+
aGVsbG8gd29ybGQK
21+
22+
--=_related_boundary_42
23+
Content-Type: application/octet-stream; name="data2.bin"
24+
Content-Transfer-Encoding: base64
25+
Content-Disposition: attachment; filename="data2.bin"
26+
27+
Z29vZGJ5ZSB3b3JsZAo=
28+
29+
--=_related_boundary_42--

t/web/ticket_txn_content.t

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,92 @@ This is the original forwarded email
247247
</div></blockquote></div><hr class="clear"></div></div>
248248
EOF
249249

250+
diag "multipart/related with a text/plain root (no text/html) must still display the body";
251+
{
252+
my $path = RT::Test::get_relocatable_file( 'multipart-related-text-plain',
253+
( File::Spec->updir(), 'data', 'emails' ) );
254+
my $email = RT::Test->file_content($path);
255+
256+
my ( $status, $id ) = RT::Test->send_via_mailgate($email);
257+
is( $status >> 8, 0, 'mail gateway exited normally' );
258+
ok( $id, "created ticket #$id from multipart/related message" );
259+
260+
my $ticket = RT::Ticket->new( RT->SystemUser );
261+
$ticket->Load($id);
262+
is( $ticket->Id, $id, "loaded ticket #$id" );
263+
264+
# Confirm the message really has the structure under test before asserting
265+
# on how it is displayed.
266+
my $txn = $ticket->Transactions->First;
267+
my $top = $txn->Attachments->First;
268+
is( lc $top->ContentType, 'multipart/related',
269+
'top-level part is multipart/related' );
270+
271+
my %child_types =
272+
map { lc( $_->ContentType ) => 1 }
273+
grep { $_->Parent == $top->Id } @{ $txn->Attachments->ItemsArrayRef };
274+
ok( $child_types{'text/plain'}, 'has a text/plain child part' );
275+
ok( !$child_types{'text/html'}, 'has no text/html child part' );
276+
277+
$m->get_ok( "/Ticket/History.html?id=$id", 'fetched ticket history' );
278+
279+
my @bodies = $m->dom->find('div.messagebody')->map('all_text')->each;
280+
ok(
281+
( grep { /UNIQUEPLAINBODY/ } @bodies ),
282+
'text/plain body of the multipart/related message is displayed inline'
283+
) or diag "Rendered message bodies: " . join( '||', @bodies );
284+
}
285+
286+
# Returns the concatenated text of every rendered message body for a ticket.
287+
sub history_bodies {
288+
my $tid = shift;
289+
$m->get_ok( "/Ticket/History.html?id=$tid", "fetched history for #$tid" );
290+
return join '||', $m->dom->find('div.messagebody')->map('all_text')->each;
291+
}
292+
293+
# Used by both the PreferRichText on/off cases below.
294+
my $alt_id;
295+
296+
diag "nested multipart/alternative -> multipart/related(html): exactly one textual part renders";
297+
{
298+
# The common MUA structure (Outlook, Apple Mail, Gmail):
299+
# multipart/alternative
300+
# text/plain # plain downgrade
301+
# multipart/related
302+
# text/html # composed rich body
303+
# image/png # inline CID image
304+
# The text/html part's direct parent is the multipart/related. RT must
305+
# show exactly one textual part, never both (the "duplicate message"
306+
# regression fixed in 4a38585f75) and never neither. With PreferRichText
307+
# on (the default) the rich body wins.
308+
my $path = RT::Test::get_relocatable_file( 'multipart-alternative-related-html',
309+
( File::Spec->updir(), 'data', 'emails' ) );
310+
my $email = RT::Test->file_content($path);
311+
312+
my ( $status, $id ) = RT::Test->send_via_mailgate($email);
313+
is( $status >> 8, 0, 'mail gateway exited normally' );
314+
ok( $id, "created ticket #$id from nested alternative/related message" );
315+
$alt_id = $id;
316+
317+
my $bodies = history_bodies($id);
318+
like( $bodies, qr/HTMLBODYMARKER/,
319+
'PreferRichText on: text/html body is displayed' );
320+
unlike( $bodies, qr/PLAINDOWNGRADEMARKER/,
321+
'PreferRichText on: text/plain downgrade is suppressed (no duplicate)' );
322+
}
323+
324+
RT::Test->stop_server;
325+
RT->Config->Set( PreferRichText => 0 );
326+
( $baseurl, $m ) = RT::Test->started_ok;
327+
ok( $m->login, 'logged in after restart' );
328+
329+
diag "same message with PreferRichText off: the plain downgrade wins";
330+
{
331+
my $bodies = history_bodies($alt_id);
332+
like( $bodies, qr/PLAINDOWNGRADEMARKER/,
333+
'PreferRichText off: text/plain downgrade is displayed' );
334+
unlike( $bodies, qr/HTMLBODYMARKER/,
335+
'PreferRichText off: text/html body is suppressed (no duplicate)' );
336+
}
337+
250338
done_testing;

0 commit comments

Comments
 (0)