Skip Menu |

This queue is for tickets about the Mail-Box CPAN distribution.

Report information
The Basics
Id: 92374
Status: resolved
Priority: 0/
Queue: Mail-Box

People
Owner: Nobody in particular
Requestors: JSTROM [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: 2.110
Fixed in: 2.111



Subject: Mail::Message::Body::File corrupts binary files
The attached error.pl creates an example binary file and prints a hex dump of the result. The final 0D0A is lost. There are two issues: Mail::Message::Body::File fails to set binmode before reading/writing files as appropriate and Mail::Message::Body::init() doesn't resolve the mime_type (and know if the file is binary or not) before acquiring the file data. The attached patches are a somewhat brute-force approach I took just to get it working temporarily, but may be useful to illustrate the issue.
Subject: Mail-Message-Body-File.patch
--- C:\strawberry\perl\site\lib\Mail\Message\Body\File.pm Sun Jan 05 12:51:19 2014 +++ \home\rse\lib\mail\message\body\file.pm Wed Jan 22 12:39:23 2014 @@ -37,6 +36,11 @@ return; } + if( $self->mimeType->isBinary ) { + binmode IN; + binmode OUT; + } + my $nrlines = 0; while(<IN>) { print OUT; $nrlines++ } @@ -153,8 +157,10 @@ my $size = eval { -s $self->tempFilename }; - $size -= $self->nrLines - if $Mail::Message::crlf_platform; # remove count for extra CR's + unless( $self->mimeType->isBinary ) { + $size -= $self->nrLines + if $Mail::Message::crlf_platform; # remove count for extra CR's + } $self->{MMBF_size} = $size; } @@ -169,6 +175,10 @@ open IN, '<', $file or die "Cannot read from $file: $!\n"; + if( $self->mimeType->isBinary ) { + binmode IN; + } + my $return = join '', <IN>; close IN; @@ -184,6 +194,10 @@ open IN, '<', $file or die "Cannot read from $file: $!\n"; + if( $self->mimeType->isBinary ) { + binmode IN; + } + my @r = <IN>; close IN; @@ -192,7 +206,15 @@ } sub file() -{ open my $tmp, '<', shift->tempFilename; +{ + my $self = shift; + + open my $tmp, '<', $self->tempFilename; + + if( $self->mimeType->isBinary ) { + binmode $tmp; + } + $tmp; } @@ -207,6 +229,10 @@ open IN, '<', $file or croak "Cannot read from $file: $!\n"; + if( $self->mimeType->isBinary ) { + binmode IN; + } + if(ref $fh eq 'GLOB') {print $fh $_ while <IN>} else {$fh->print($_) while <IN>} close IN;
Subject: Mail-Message-Body.patch
--- C:\strawberry\perl\site\lib\Mail\Message\Body.pm Sun Jan 05 12:51:19 2014 +++ \home\rse\lib\mail\message\body.pm Wed Jan 22 12:31:46 2014 @@ -63,44 +63,13 @@ $self->{MMB_modified} = $args->{modified} || 0; - my $filename; - if(defined(my $file = $args->{file})) - { - if(!ref $file) - { $self->_data_from_filename($file) or return; - $filename = $file; - } - elsif(ref $file eq 'GLOB') - { $self->_data_from_glob($file) or return } - elsif($file->isa('IO::Handle')) - { $self->_data_from_filehandle($file) or return } - else - { croak "message body: illegal datatype `".ref($file)."' for file option" } - } - elsif(defined(my $data = $args->{data})) - { - if(!ref $data) - { my @lines = split /^/, $data; - $self->_data_from_lines(\@lines) - } - elsif(ref $data eq 'ARRAY') - { $self->_data_from_lines($data) or return; - } - else - { croak "message body: illegal datatype `".ref($data)."' for data option" } - } - elsif(! $self->isMultipart && ! $self->isNested) - { # Neither 'file' nor 'data', so empty body. - $self->_data_from_lines( [] ) or return; - } - # Set the content info my ($mime, $transfer, $disp, $charset, $descr, $cid) = @$args{ qw/mime_type transfer_encoding disposition charset description content_id/ }; - if(defined $filename) + if(defined(my $filename = $args->{file})) { $disp = Mail::Message::Field->new ('Content-Disposition' => (-T $filename ? 'inline':'attachment') , filename => basename($filename) @@ -144,6 +113,35 @@ $self->type($mime); $self->{MMB_eol} = $args->{eol} || 'NATIVE'; + + if(defined(my $file = $args->{file})) + { + if(!ref $file) + { $self->_data_from_filename($file) or return; } + elsif(ref $file eq 'GLOB') + { $self->_data_from_glob($file) or return } + elsif($file->isa('IO::Handle')) + { $self->_data_from_filehandle($file) or return } + else + { croak "message body: illegal datatype `".ref($file)."' for file option" } + } + elsif(defined(my $data = $args->{data})) + { + if(!ref $data) + { my @lines = split /^/, $data; + $self->_data_from_lines(\@lines) + } + elsif(ref $data eq 'ARRAY') + { $self->_data_from_lines($data) or return; + } + else + { croak "message body: illegal datatype `".ref($data)."' for data option" } + } + elsif(! $self->isMultipart && ! $self->isNested) + { # Neither 'file' nor 'data', so empty body. + $self->_data_from_lines( [] ) or return; + } + # Set message where the body belongs to.
Subject: error.pl
use Mail::Message; $test = "\xFF\x0a\xFF\x0d\xFF\x0a\x0d\xFF\x0d\x0a\xFF"; open OUT, '>', 'example.dat'; binmode OUT; print OUT $test; close OUT; $mail = Mail::Message->build( From => 'me@example.com', To => 'you@example.com', Subject => 'Bug in Mail::Message::Body::File', data => ['See attached file'] ); $body = $mail->body; $body = $body->attach( Mail::Message::Body->new( file => 'example.dat', mime_type => 'application/octet-stream' ) ); $mail->body($body); open OUT, '>', 'example.mail'; $mail->print( \*OUT ); close OUT; open IN, '<', 'example.mail'; $newmail = Mail::Message->read( \*IN ); close IN; @parts = $newmail->parts; print "- - - - - - - - - - - - - - - - -\n"; for (@parts) { $data = $_->decoded(); hexdump( $data ); print "- - - - - - - - - - - - - - - - -\n"; } hexdump($test); sub hexdump { my $data = shift; while( length $data ) { $line = substr $data, 0, 16, ''; $hex = unpack 'H*', $line; $line =~ tr/\x20-\x7e/./c; printf "%-32s | %s\n", $hex, $line; } }
Subject: Re: [rt.cpan.org #92374] Mail::Message::Body::File corrupts binary files
Date: Thu, 23 Jan 2014 09:35:39 +0100
To: Joseph Strom via RT <bug-Mail-Box [...] rt.cpan.org>
From: Mark Overmeer <mark [...] overmeer.net>
* Joseph Strom via RT (bug-Mail-Box@rt.cpan.org) [140122 18:03]: Show quoted text
> Wed Jan 22 13:03:33 2014: Request 92374 was acted upon. > Transaction: Ticket created by JSTROM > Queue: Mail-Box > Subject: Mail::Message::Body::File corrupts binary files > > There are two issues: Mail::Message::Body::File fails to > set binmode before reading/writing files as appropriate and > Mail::Message::Body::init() doesn't resolve the mime_type (and know if > the file is binary or not) before acquiring the file data.
I have not encounted this problem before. I never use Windows, so have no idea about its quircks reading files... Show quoted text
> The attached patches are a somewhat brute-force approach I took just > to get it working temporarily, but may be useful to illustrate the issue.
In stead of binmode, I think we can do with opening <:raw / >:raw everywhere. The file is used as message body, so treating it as bytes seems ok in any situation. What do you think? -- Regards, MarkOv ------------------------------------------------------------------------ Mark Overmeer MSc MARKOV Solutions Mark@Overmeer.net solutions@overmeer.net http://Mark.Overmeer.net http://solutions.overmeer.net
Subject: Re: [rt.cpan.org #92374] Mail::Message::Body::File corrupts binary files
Date: Thu, 23 Jan 2014 09:30:38 -0500
To: bug-Mail-Box [...] rt.cpan.org
From: j-strom [...] verizon.net
Show quoted text
> > In stead of binmode, I think we can do with opening <:raw / >:raw > everywhere. The file is used as message body, so treating it as bytes > seems ok in any situation. What do you think? > --
That seems more elegant than my quick fix. The question is will it be OK for text files not to have their line-endings converted. Tracing through the code, it looks like line-endings are left to Net::Cmd to handle. And it can accept either CRLF or LF and produce the correct output (though possibly not for CR-only input; there are some odd checks in Net::Cmd that may be relevant to a CR-only environment, but I don't know enough to say). -js
Subject: Re: [rt.cpan.org #92374] Mail::Message::Body::File corrupts binary files
Date: Thu, 23 Jan 2014 17:26:57 +0100
To: "j-strom [...] verizon.net via RT" <bug-Mail-Box [...] rt.cpan.org>
From: Mark Overmeer <mark [...] overmeer.net>
* j-strom@verizon.net via RT (bug-Mail-Box@rt.cpan.org) [140123 15:21]: Show quoted text
> Queue: Mail-Box > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=92374 > >
> > In stead of binmode, I think we can do with opening <:raw / >:raw > > everywhere. The file is used as message body, so treating it as bytes > > seems ok in any situation. What do you think?
> > That seems more elegant than my quick fix. > > The question is will it be OK for text files not to have their line-endings > converted. Tracing through the code, it looks like line-endings are left to > Net::Cmd to handle. And it can accept either CRLF or LF and produce > the correct output (though possibly not for CR-only input; there are some > odd checks in Net::Cmd that may be relevant to a CR-only environment, > but I don't know enough to say).
The Net::Cmd only cares about protocol lines, but does not look at the content of email messages. The RFCs only speak about CRLF at the end of MIME Header lines, and MailBox takes care of that. Nothing about the body... email clients and/or applications needs to be aware about the CRLF/CR/LF problem... this is not always the case. The FTP protocol has this text/binary switch, where text gets actively translated, but email implementations are usually ignoring the fact. -- Regards, MarkOv ------------------------------------------------------------------------ Mark Overmeer MSc MARKOV Solutions Mark@Overmeer.net solutions@overmeer.net http://Mark.Overmeer.net http://solutions.overmeer.net
Subject: Re: [rt.cpan.org #92374] Mail::Message::Body::File corrupts binary files
Date: Thu, 23 Jan 2014 22:31:48 -0500
To: bug-Mail-Box [...] rt.cpan.org
From: j-strom [...] verizon.net
Show quoted text
> From: "Mark Overmeer via RT" <bug-Mail-Box@rt.cpan.org> > To: JSTROM@cpan.org > Date: Thu Jan 23 11:27:16 2014 > > <URL: https://rt.cpan.org/Ticket/Display.html?id=92374 > > > * j-strom@verizon.net via RT (bug-Mail-Box@rt.cpan.org) [140123 15:21]:
> > Queue: Mail-Box > > Ticket <URL: https://rt.cpan.org/Ticket/Display.html?id=92374 > > >
> > > In stead of binmode, I think we can do with opening <:raw / >:raw > > > everywhere. The file is used as message body, so treating it as bytes > > > seems ok in any situation. What do you think?
> >
Tested using :raw whenever a file is opened by Mail::Message::Body::File. The example.dat round-tripped without change.
--- C:\strawberry\perl\site\lib\Mail\Message\Body\File.pm Sun Jan 05 12:51:19 2014 +++ File.pm Thu Jan 23 22:23:58 2014 @@ -25,14 +25,14 @@ local $_; local (*IN, *OUT); - unless(open IN, '<', $filename) + unless(open IN, '< :raw', $filename) { $self->log(ERROR => "Unable to read file $filename for message body file: $!"); return; } my $file = $self->tempFilename; - unless(open OUT, '>', $file) + unless(open OUT, '> :raw', $file) { $self->log(ERROR => "Cannot write to temporary body file $file: $!"); return; } @@ -54,7 +54,7 @@ local *OUT; - unless(open OUT, '>', $file) + unless(open OUT, '> :raw', $file) { $self->log(ERROR => "Cannot write to temporary body file $file: $!"); return; } @@ -77,7 +77,7 @@ local $_; local *OUT; - unless(open OUT, '>', $file) + unless(open OUT, '> :raw', $file) { $self->log(ERROR => "Cannot write to temporary body file $file: $!"); return; } @@ -98,7 +98,7 @@ local *OUT; - unless(open OUT, '>', $file) + unless(open OUT, '> :raw', $file) { $self->log(ERROR => "Cannot write to $file: $!"); return; } @@ -134,7 +134,7 @@ local $_; local *IN; - open IN, '<', $file + open IN, '< :raw', $file or die "Cannot read from $file: $!\n"; $nrlines++ while <IN>; @@ -166,7 +166,7 @@ local *IN; - open IN, '<', $file + open IN, '< :raw', $file or die "Cannot read from $file: $!\n"; my $return = join '', <IN>; @@ -181,7 +181,7 @@ my $file = $self->tempFilename; local *IN; - open IN, '<', $file + open IN, '< :raw', $file or die "Cannot read from $file: $!\n"; my @r = <IN>; @@ -192,7 +192,7 @@ } sub file() -{ open my $tmp, '<', shift->tempFilename; +{ open my $tmp, '< :raw', shift->tempFilename; $tmp; } @@ -204,7 +204,7 @@ local $_; local *IN; - open IN, '<', $file + open IN, '< :raw', $file or croak "Cannot read from $file: $!\n"; if(ref $fh eq 'GLOB') {print $fh $_ while <IN>} @@ -220,7 +220,7 @@ local *OUT; - open OUT, '>', $file + open OUT, '> :raw', $file or die "Cannot write to $file: $!.\n"; (my $begin, my $end, $self->{MMBF_nrlines}) = $parser->bodyAsFile(\*OUT,@_);