Skip to content

Commit

Permalink
Support multipart messages without content-type in subparts. (GH: rjb…
Browse files Browse the repository at this point in the history
…s#14)

Per RFC 1341, section 7.2

  https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

  A body part is NOT to be interpreted as actually being an RFC 822 message.
  To begin with, NO header fields are actually required in body parts. A
  body part that starts with a blank line, therefore, is allowed and is a
  body part for which all default values are to be assumed. In such a case,
  the absence of a Content-Type header field implies that the encapsulation
  is plain US-ASCII text.
  • Loading branch information
wolfsage committed May 20, 2016
1 parent 5d70889 commit 2b9d213
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
22 changes: 21 additions & 1 deletion lib/Email/MIME.pm
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ 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 || '');
my @bits = split /^--\Q$boundary\E\s*?$/m, ($body || '');
$self->SUPER::body_set(undef);
Expand All @@ -377,6 +377,26 @@ sub parts_multipart {

my @parts;
for my $bit (@bits) {
my $no_header;

# 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.
if ($bit =~ /^([\r\n][\r\n])/) {
$bit = "Content-type: text/plain; charset=us-ascii" . $bit;
}

$bit =~ s/\A[\n\r]+//smg;
$bit =~ s/(?<!\x0d)$self->{mycrlf}\Z//sm;
my $email = (ref $self)->new($bit);
Expand Down
55 changes: 55 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,48 @@ END
unlike($email->as_string, qr/Postlude/, "postlude in string");
}

{
my $email = Email::MIME->new(<<'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 @parts = $email->subparts;

is(@parts, 2, 'got 2 parts');

like($parts[0]->body, qr/^Part 1.*Part 1a$/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$/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' );
}

done_testing;

0 comments on commit 2b9d213

Please sign in to comment.