Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion lib/Email/MIME.pm
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ sub parts_multipart {
# rfc1521 7.2.1
my ($body, $epilogue) = split /^--\Q$boundary\E--\s*$/sm, $self->body_raw, 2;

my @bits = split /^--\Q$boundary\E\s*$/sm, ($body || '');
# Split on boundaries, but keep blank lines after them intact
my @bits = split /^--\Q$boundary\E\s*?(?=$self->{mycrlf})/m, ($body || '');

$self->SUPER::body_set(undef);

Expand All @@ -377,9 +378,39 @@ sub parts_multipart {

my @parts;
for my $bit (@bits) {
# Parts don't need headers. If they don't have them, they look like this:
#
# --90e6ba6e8d06f1723604fc1b809a
#
# Part 2
#
# Part 2a
#
# $bit will contain two new lines before Part 2.
#
# Anything with headers will only have one new line.
#
# RFC 1341 Section 7.2 says parts without headers are to be considered
# plain US-ASCII text. -- alh
# 2016-08-01
my $added_header;

if ($bit =~ /^(?:$self->{mycrlf}){2}/) {
$bit = "Content-type: text/plain; charset=us-ascii" . $bit;

$added_header = 1;
}

$bit =~ s/\A[\n\r]+//smg;
$bit =~ s/(?<!\x0d)$self->{mycrlf}\Z//sm;

my $email = (ref $self)->new($bit);

if ($added_header) {
# Remove our changes so we don't change the raw email content
$email->header_str_set('Content-Type');
}

push @parts, $email;
}

Expand Down
80 changes: 80 additions & 0 deletions t/multipart.t
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ use Test::More;
use Carp; $SIG{__WARN__} = sub { Carp::cluck @_ };

use_ok 'Email::MIME::Creator';
use_ok 'Email::MIME::ContentType';

sub ct {
return (
type => $_[0], # okay!
subtype => $_[1], # okay!

discrete => $_[0], # dumb!
composite => $_[1], # dumb!
);
}

my $hi = Email::MIME->create(body => "Hi");
my $hello = Email::MIME->create(body => "Hello");
Expand Down Expand Up @@ -89,4 +100,73 @@ END
unlike($email->as_string, qr/Postlude/, "postlude in string");
}

{
my $email_str = <<'END';
From: Test <[email protected]>
To: Test <[email protected]>
Subject: Test
Content-Type: multipart/alternative; boundary=90e6ba6e8d06f1723604fc1b809a

--90e6ba6e8d06f1723604fc1b809a
Content-Type: text/plain; charset=UTF-8

Part 1

Part 1a

--90e6ba6e8d06f1723604fc1b809a

Part 2

Part 2a

--90e6ba6e8d06f1723604fc1b809a--
END

my @emails = (["lf-delimited", $email_str]);

# Also test with CRLF email
$email_str =~ s/\n/\r\n/g;

push @emails, ["crlf-delimited", $email_str];

for my $test (@emails) {
my ($desc, $email_str) = @$test;

note("Testing $desc email");

my $email = Email::MIME->new($email_str);

my @parts = $email->subparts;
is(@parts, 2, 'got 2 parts');

# Force a change to the email so as_string gives us something fresh from
# the parts involved. Ensure that the body parts in the message have not
# changed (so we don't interfere with DKIM signing, for example).
$email->parts_set([ @parts ]);

like($parts[0]->body, qr/^Part 1.*Part 1a\r?$/s, 'Part 1 looks right');
is_deeply( parse_content_type($parts[0]->header('Content-Type')), {
ct(qw(text plain)),
attributes => {
charset => 'UTF-8',
},
}, 'explicit ct worked' );

like($parts[1]->body, qr/^Part 2.*Part 2a\r?$/s, 'Part 2 looks right');
is_deeply( parse_content_type($parts[1]->header('Content-Type')), {
ct(qw(text plain)),
attributes => {
charset => 'us-ascii',
},
}, 'default ct worked' );

like(
$email->as_string,
qr/--90e6ba6e8d06f1723604fc1b809a\r?\n\r?\nPart 2/,
"Email string not modified"
);
}
}

done_testing;