Parsing email with Email::MIME and multipart/signed - perl

I'm a perl novice trying to figure out how to decode a MIME-encoded email with multiple parts. I'm not sure of conventions, so I'll just include the pieces of the email that I believe are relevant:
Content-Type: multipart/mixed; boundary="===============3385789078715843912=="
Mime-Version: 1.0
--===============3385789078715843912==
Content-Type: multipart/signed; micalg="pgp-sha256";
protocol="application/pgp-signature"; boundary="=-0+dmFxz+BsFOEAAxvudu"
--=-0+dmFxz+BsFOEAAxvudu
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: base64
PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09
PT09PT09PT09PT09PT09PT0KVWJ1bnR1IFNlY3VyaXR5IE5vdGljZSBVU04tMzIxMC0xCkZlYnJ1
YXJ5IDIzLCAyMDE3CgpMaWJyZU9mZmljZSB2dWxuZXJhYmlsaXR5Cj09PT09PT09PT09PT09PT09
PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09
CgpBIHNlY3VyaXR5IGlzc3VlIGFmZmVjdHMgdGhlc2UgcmVsZWFzZXMgb2YgVWJ1bnR1IGFuZCBp
dHMgZGVyaXZhdGl2ZXM6CgotIFVidW50dSAxNi4wNCBMVFMKLSBVYnVudHUgMTQuMDQgTFRTCi0g
I've got the following bit of code:
my $msg = Email::MIME->new($buf);
for my $part ($msg->parts) {
if ($part->content_type =~ m!multipart/mixed!
or $part->content_type eq '' )
{
print "Found Multipart";
for my $subpart ($part->parts) {
print $subpart->body;
}
}
}
I really don't know what to do next. I've had a dozen different variations on this, and haven't gotten any closer after four hours of working on it. I'd appreciate if someone could help me identify the proper perl modules and functions to be used to read this text sub-part of a signed email.

The documentation of Email::MIME suggests not to use parts, because it's a stupid method. It returns its own object if there are no parts. That is weird.
Instead use the subparts method to get the parts of the email. Then use it again to iterate all parts of that part. If there are any, it will go in. Print the body of that sub part and you're done.
foreach my $part ( $msg->subparts ) {
foreach my $sub_part ($part->subparts) {
print $sub_part->body;
}
}

Related

Sending a mail with a dynamic table from perl script

I have the following code in Perl:
foreach my $result ( #results ) {
if ( $result->{Error} ) {
print"No response received \n";}
else {
my $H = "$result->{H}";
my $I = "$result->{I}";
$mailbody.=qq(<h4 style="background: blue; color: white;">$H--->$I</h4>);
}
}
Here, I am using Mime::Lite to send mails:
$msg = MIME::Lite->new(
From => $from,
To => $to,
Cc => $cc,
Subject => $subject,
Data => $mailbody
);
$msg->attr("content-type" => "text/html/css");
$msg->send;
What I want is that the result data i.e $H and $I to be represented in the form of a table in the mail.
H | I
1 | 46
2 | 565756756767
3 | 232132
The number of rows of the table are dynamic and depend on the input given by the user. How can I do this?
If you want it to be a table in the email, you should create a table in the email body something like this:
$mailbody . = '<table>';
foreach my $result ( #results ) {
if ( $result->{Error} ) {
print"No response received \n";}
else {
my $H = "$result->{H}";
my $I = "$result->{I}";
$mailbody.=qq(<tr><td>$H</td><td>$I</td></tr>);
}
}
$mailbody . = '</table>';
If you want an HTML table in your email, then add HTML table elements to your output.
# Note: border=1 attribute to make the table borders visible.
$mailbody .= '<table border="1">';
foreach my $result ( #results ) {
if ( $result->{Error} ) {
print"No response received \n";}
else {
$mailbody .= qq(<tr><td>$result->{H}</td>)
. qq(<td>$result->{I}</td></tr>);
}
}
$mailbody .= </table>
In a comment to another answer that suggested something similar, you said that this doesn't work because you can't see the table borders. That was, of course, a simple case of adding border=1 so that the borders are displayed.
However.
It's always worth repeating that putting raw HTML strings into your program code is a terrible idea. It's a recipe for an unmaintainable mess. It's a bad idea when creating web applications and it's a bad idea when creating HTML to go into email bodies.
Far better to separate the code from creating the output and the best way to do that is to use a templating engine like the Template Toolkit. By creating a template file that contains all of the HTML output, you make it easier to change the way that the HTML looks without getting bogged down in the Perl code.
Also (and I've suggested this to you before) I would suggest that you avoid using MIME::Lite. But don't take my word for it. The current documentation for the module says this:
MIME::Lite is not recommended by its current maintainer. There are a number of alternatives, like Email::MIME or MIME::Entity and Email::Sender, which you should probably use instead. MIME::Lite continues to accrue weird bug reports, and it is not receiving a large amount of refactoring due to the availability of better alternatives. Please consider using something else.
I recommend switching to Email::Sender (together with Email::MIME) or Email::Stuffer.

What are -- in email headers?

Recently i came across -- as a header of mail() function in php. I tried to google it out but didn't found any result so i am posting this question. While trying to edit my code i found that the id after these two -- should be unique.My php script to send email with attachement.
<?php
$file = "http://wpbrisk.com/download/email/email/doc1.pdf"; // Specify the Url of the attachment here
$filename = "doc1.pdf"; // Specify the name of the file here
$to="hudixt#gmail.com"; // Enter the mail to which you want to sent the attachement
$your_name = "Hudixt"; // Your name
$from_mail = "info#wpbrisk.com"; // Your email
$subject = "This is a mail with attachment."; // Subject
$message = "<div id='hello'>World</div>"; // message
// Get the content of the file
$content = file_get_contents($file);
$content = base64_encode($content);
$uid = md5(uniqid(time()));
$header = "From: ".$from_name." <".$from_mail.">\r\n";
$header .= "MIME-Version: 1.0\r\n";
$header .= "Content-Type: multipart/mixed; boundary=\"".$uid."\"\r\n\r\n";
$header .= "This is a multi-part message in MIME format.\r\n";
$header .= "--".$uid."\r\n";
$header .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$header .= $message."\r\n\r\n";
$header .= "--".$uid."\r\n";
$header .= "Content-Type: application/octet-stream; name=\"".$filename."\"\r\n"; // use different content types here
$header .= "Content-Transfer-Encoding: base64\r\n";
$header .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
$header .= $content."\r\n\r\n";
$header .= "--".$uid."--";
mail($mailto, $subject, "", $header);
?>
Can you please tell me what this -- means in header and why it should be unique.
These "--ID--" lines are MIME part boundaries (see https://en.wikipedia.org/wiki/MIME). They are described in https://www.rfc-editor.org/rfc/rfc2046. They are used to separate different parts of a message.
According to RFC#2046 those only must be unique "enough". If you want to encapsulate an email into another one, the boundaries must not infer with each other, i.e. they have to be distinct. So it's a good idea to make them as unique "as possible and needed":
This implies that it is crucial that the composing agent be able
to choose and specify a unique boundary parameter value that does
not contain the boundary parameter value of an enclosing multipart
as a prefix.
(https://www.rfc-editor.org/rfc/rfc2046#section-5.1)
Seems like a MIME boundary to me...
Basically, when you have multiple types of content in an e-mail message, the different parts are separated using an ID. Each part is then preceded by '--' followed by the boundary. From the RFC:
Thus, a typical multipart Content-Type header field might look like this:
Content-Type: multipart/mixed;
boundary=gc0p4Jq0M2Yt08jU534c0p
This indicates that the entity consists of several parts, each itself with a structure that is syntactically identical to an RFC 822 message, except that the header area might be completely empty, and that the parts are each preceded by the line
--gc0p4Jq0M2Yt08jU534c0p
See http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html for more info.
To answer your last question, when you have an e-mail with text and attachment, the whole message will have Content-Type: multipart/mixed, the text will be text/plain, the attachment (suppose a word file) will be application/msword. Each new content-type part will be separated by '--' . $boundary. The boundary is chosen to be unique and different from the other message parts so that it doesn't appear inside either of them - when the application interpreting the e-mail contents finds $boundary it must be safe to assume that's an intended boundary and not just a fluke inside the message.
Hope that helps!

How to change the body of one part using Email::MIME? Or: Who does body_set work?

I am trying to change the body text of a part inside a multi-part MIME-Email using Email::MIMEs (1.926) walk_parts and body_set.
The change is there but when sending the mail the old/non-changed mail text is being sent.
The question is: What do I have to do to 'activate' my changes?
See:
use Email::MIME;
my $raw_message_text = q!Date: Wed, 26 Feb 2014 08:02:39 +0100
From: Me <me#example.com>
To: You <you#example.com>
Subject: test
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="------------010309070301040606000908"
This is a multi-part message in MIME format.
--------------010309070301040606000908
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
this is a test
--------------010309070301040606000908
Content-Type: text/plain;
name="file-to-attach.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="file-to-attach.txt"
dGV4dCBpbnNpZGUgYXR0YWNobWVudAoK
--------------010309070301040606000908--
!;
my $parsed_email = Email::MIME->new($raw_message_text);
$parsed_email->walk_parts(sub {
my ($part) = #_;
return if $part->subparts; # multipart
return unless ($part->content_type =~ /text\/plain.*charset=utf-8/i);
$part->body_set("new body text");
});
print "As you see the change is there:\n";
$parsed_email->walk_parts(sub {
my ($part) = #_;
return if $part->subparts; # multipart
my $body = $part->body;
print "Body:$body\n";
});
print "But the email object itself doesn't notice that:\n\n";
print $parsed_email->as_string;
This will first show the changed body text, so you see it is there! But when the whole mail is shown the old body text is used. The same will happen if I just send the email using Email::Sender. So I wonder what the correct usage of body_set is...
I stumbled upon this problem as well. Eventually I realized all that's missing from the original poster, is just the following:
my #new_parts = $parsed_email->parts;
$parsed_email->parts_set( \#new_parts );
add the above before the final as_string call, and you are good.
The walk_parts doesn't seem to work properly i had to use the old classic method, i'm not sure if there is something broken with the new version but with this method it works, you'll just need to replace your code somehow:
This solution isn't efficient at all and i know its heavy on the memory but i'm lazy, i think i should look for another library with this model.
my #parts = $parsed->subparts;
my #new_parts;
if (#parts) {
foreach (#parts)
{
my $part = $_;
print $part->content_type."\r\n";
if ($part->content_type =~ /text\/plain.*charset=utf-8/i) {
$part->body_set("new body text");
push #new_parts, $part;
} else {
push #new_parts, $part;
}
}
} else {
print 'single part';#to replace for single mime
}
$parsed->parts_set(\#new_parts);

Perl sendmail to list is truncated

I am working with a perl based cgi pages for a webproject. I am encountering a strange problem with sendmail module which happens randomly.
Problem:
Sendmail would truncate the emails of the users appended at last. But not always, it happens randomly. I log the email list right before sending email and I don't see anything wrong.
Example Image (See Karl's last name is truncated at '.' after his first name.)
Headers for the email.
Message-ID: <201305221503.r4MF3dYf022792#pazmo.internal.company.com>
Subject: < ...>
MIME-Version: 1.0
Content-Type: text/plain
To: <biradavolu.ln#company.com>, <dessimira.ln#company.com>,
<yun.ln#company.com>, karl.
Date: Wed, 22 May 2013 10:03:39 -0500
From: <tool#company.com>
Return-Path: tool#company.com
X-MS-Exchange-Organization-AuthSource: eusaamw0712.domain.company.com
X-MS-Exchange-Organization-AuthAs: Internal
X-MS-Exchange-Organization-AuthMechanism: 10
X-MS-Exchange-Organization-AVStamp-Mailbox: MSFTFF;1;0;0 0 0
The logged input before sending email: ( I don't see anything wrong with the format)
biradavolu.ln#company.com;dessimira.ln#company.com;yun.lastName#company.com;karl.LastName#company.com;
use Mail::Sendmail;
# Step 1: Declare the mail variable
%mail = (
from => 'test#company.com',
to => 'user1FN.user1LN#company.com;user2FN.user2LN#company.com;user3FN.user3LN#company.com;' . "$requester_email; $responsible_email",
subject => ... ,
'content-type' => "multipart/alternative; "
);
my $toList='user1FN.user1LN#company.com;user2FN.user2LN#company.com;user3FN.user3LN#company.com;' . "$requester_email;";
# Step 2: Add members to toList based on different conditions
if(condition1)
$toList= $toList.'user4FN.user4LN#company.com;';
if(condition2)
$toList= $toList.'user5FN.user5LN#company.com;';
... # few other similar condition statement
...
# Step 3: Assign toList based on different conditions
$mail{ 'to' } = $toList;
# Step 4: Set Body of the $mail
if(sendmail(%mail)){
print LOGFILE "Mail send successfully to $mail{\"to\"}: ";
}else{
print LOGFILE "Mail was not send : Mail list was $mail{\"to\"} : ";
}
Wild guess here. You're hiding the actual lastnames of your users (which is fine), but it could be possible that the "random" truncating is always happening on a user with a space in the last name? Like "St. Pierre". Your string might get truncated right at the space.
Let me know if that's possible!

Perl MIME::Parser and encoding in nested bodys (message/rfc_822)

arghhh, it's not easy. I'm trying to parse some mails with perl. Let's take an example:
From: abc#def.de
Content-Type: multipart/mixed;
boundary="----_=_NextPart_001_01CBE273.65A0E7AA"
To: ghi#def.de
This is a multi-part message in MIME format.
------_=_NextPart_001_01CBE273.65A0E7AA
Content-Type: multipart/alternative;
boundary="----_=_NextPart_002_01CBE273.65A0E7AA"
------_=_NextPart_002_01CBE273.65A0E7AA
Content-Type: text/plain;
charset="UTF-8"
Content-Transfer-Encoding: base64
[base64-content]
------_=_NextPart_002_01CBE273.65A0E7AA
Content-Type: text/html;
charset="UTF-8"
Content-Transfer-Encoding: base64
[base64-content]
------_=_NextPart_002_01CBE273.65A0E7AA--
------_=_NextPart_001_01CBE273.65A0E7AA
Content-Type: message/rfc822
Content-Transfer-Encoding: 7bit
X-MimeOLE: Produced By Microsoft Exchange V6.5
Content-class: urn:content-classes:message
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----_=_NextPart_003_01CBE272.13692C80"
From: bla#bla.de
To: xxx#xxx.de
This is a multi-part message in MIME format.
------_=_NextPart_003_01CBE272.13692C80
Content-Type: multipart/alternative;
boundary="----_=_NextPart_004_01CBE272.13692C80"
------_=_NextPart_004_01CBE272.13692C80
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
=20
Viele Gr=FC=DFe
------_=_NextPart_004_01CBE272.13692C80
Content-Type: text/html;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
<html>...</html>
------_=_NextPart_004_01CBE272.13692C80--
------_=_NextPart_003_01CBE272.13692C80
Content-Type: application/x-zip-compressed;
name="abc.zip"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="abc.zip"
[base64-content]
------_=_NextPart_003_01CBE272.13692C80--
------_=_NextPart_001_01CBE273.65A0E7AA--
This mail is sent from Outlook with another attached message. As you can see, this is a very complex mail with many different content types (text/plain, text/html, message/rfc_822, application/xyz)...
And the rfc_822 part is the problem. I've written a script in Perl 5.8 (Debian Squeeze) to parse this message with MIME::Parser.
use MIME::Parser;
my $parser = MIME::Parser->new;
$parser->output_to_core(1);
my $top_entity = $parser->parse(\*STDIN);
my $plain_body = "";
my $html_body = "";
my $content_type;
foreach my $part ($top_entity->parts_DFS) {
$content_type = $part->effective_type;
$body = $part->bodyhandle;
if ($body) {
if ($content_type eq 'text/plain') {
$plain_body = $plain_body . "\n" if ($plain_body ne '');
$plain_body = $plain_body . $body->as_string;
} elsif ($content_type eq 'text/html') {
$html_body = $html_body . "\n" if ($html_body ne '');
$html_body = $html_body . $body->as_string;
}
}
}
# parsing of attachment comes later
print $plain_body;
The first message part (base64-content) contains german umlauts, which are shown correctly at STDOUT. The nested rfc_822 message is parsed by MIME::Parser automatically and is pooled with the top level body as one entity. This nested rfc_822 contains also german umlauts in quoted-printable as you can see. But these are not shown correctly at STDOUT. When doing a
utf8::encode($plain_body);
before print, the quoted-printable umlauts are shown correctly, but not the base64 encoded ones. I'm trying now for hours to extract the rfc_822 seperatly and doing some encoding, but nothing helps. Who else can help?
Regards
Assuming that your console displays UTF-8, this make sense.
It correctly shows what you have decoded, but, of course, latin1 characters are not shown correctly.
Later, you do a conversion to UTF-8, but this does not make sense if the data is already UTF8. So only the former latin1 umlauts are shown.
There is no way to get this right without looking at the "charset" in the content-type and acting accordingly.