File Coverage

blib/lib/Mail/Message/Body.pm
Criterion Covered Total %
statement 173 218 79.3
branch 97 138 70.2
condition 47 77 61.0
subroutine 36 55 65.4
pod 32 34 94.1
total 385 522 73.7


line stmt bran cond sub pod time code
1             # Copyrights 2001-2023 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.03.
5             # This code is part of distribution Mail-Message. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Message::Body;
10 34     34   81595 use vars '$VERSION';
  34         89  
  34         1822  
11             $VERSION = '3.013';
12              
13 34     34   208 use base 'Mail::Reporter';
  34         102  
  34         9739  
14              
15 34     34   261 use strict;
  34         62  
  34         778  
16 34     34   177 use warnings;
  34         72  
  34         966  
17              
18 34     34   8133 use Mail::Message::Field;
  34         82  
  34         1254  
19 34     34   4770 use Mail::Message::Body::Lines;
  34         83  
  34         1010  
20 34     34   15056 use Mail::Message::Body::File;
  34         116  
  34         1354  
21              
22 34     34   229 use Carp;
  34         92  
  34         2135  
23 34     34   227 use Scalar::Util qw/weaken refaddr/;
  34         78  
  34         1767  
24 34     34   205 use File::Basename qw/basename/;
  34         131  
  34         3218  
25              
26 34     34   16836 use MIME::Types;
  34         147983  
  34         5623  
27             my $mime_types = MIME::Types->new;
28             my $mime_plain = $mime_types->type('text/plain');
29              
30              
31             use overload
32 567     567   2618 bool => sub {1} # $body->print if $body
33             , '""' => 'string_unless_carp'
34             , '@{}' => 'lines'
35 14 50   14   150 , '==' => sub {ref $_[1] && refaddr $_[0] == refaddr $_[1]}
36 34 50   34   330 , '!=' => sub {ref $_[1] && refaddr $_[0] != refaddr $_[1]};
  34     43   219  
  34         426  
  43         449  
37              
38             #------------------------------------------
39              
40              
41             my $body_count = 0; # to be able to compare bodies for equivalence.
42              
43             sub new(@)
44 256     256 1 27498 { my $class = shift;
45              
46 256 100       1230 return $class->SUPER::new(@_)
47             unless $class eq __PACKAGE__;
48              
49 19         75 my %args = @_;
50              
51             exists $args{file}
52 19 50       119 ? Mail::Message::Body::File->new(@_)
53             : Mail::Message::Body::Lines->new(@_);
54             }
55              
56             # All body implementations shall implement all of the following!!
57              
58 0     0   0 sub _data_from_filename(@) {shift->notImplemented}
59 0     0   0 sub _data_from_filehandle(@) {shift->notImplemented}
60 0     0   0 sub _data_from_glob(@) {shift->notImplemented}
61 0     0   0 sub _data_from_lines(@) {shift->notImplemented}
62              
63             sub init($)
64 237     237 0 538 { my ($self, $args) = @_;
65              
66 237         725 $self->SUPER::init($args);
67              
68 237   50     964 $self->{MMB_modified} = $args->{modified} || 0;
69              
70 237         387 my $filename = $args->{filename};
71 237         409 my $mime = $args->{mime_type};
72              
73 237 100 100     1248 if(defined(my $file = $args->{file}))
    100          
    100          
74             {
75 3 50       43 if(!ref $file)
    50          
    50          
76 0 0       0 { $self->_data_from_filename($file) or return;
77 0   0     0 $filename ||= $file;
78 0   0     0 $mime ||= $mime_types->mimeTypeOf($filename)
      0        
79             || (-T $file ? 'text/plain' : 'application/octet-stream');
80             }
81             elsif(ref $file eq 'GLOB')
82 0 0       0 { $self->_data_from_glob($file) or return }
83             elsif($file->isa('IO::Handle'))
84 3 50       17 { $self->_data_from_filehandle($file) or return }
85             else
86 0         0 { croak "message body: illegal datatype `".ref($file)."' for file option" }
87             }
88             elsif(defined(my $data = $args->{data}))
89             {
90 149 100       425 if(!ref $data)
    50          
91 86         385 { my @lines = split /^/, $data;
92 86         316 $self->_data_from_lines(\@lines)
93             }
94             elsif(ref $data eq 'ARRAY')
95 63 50       203 { $self->_data_from_lines($data) or return;
96             }
97             else
98 0         0 { croak "message body: illegal datatype `".ref($data)."' for data option" }
99             }
100             elsif(! $self->isMultipart && ! $self->isNested)
101             { # Neither 'file' nor 'data', so empty body.
102 32 50       193 $self->_data_from_lines( [] ) or return;
103             }
104              
105             # Set the content info
106              
107             my ($transfer, $disp, $descr, $cid) = @$args{
108 237         762 qw/transfer_encoding disposition description content_id/ };
109              
110 237 50       560 if(defined $filename)
111 0 0 0     0 { $disp //= Mail::Message::Field->new
112             ( 'Content-Disposition' => (-T $filename ? 'inline' : 'attachment')
113             , filename => basename($filename)
114             );
115 0   0     0 $mime //= $mime_types->mimeTypeOf($filename);
116             }
117              
118 237 50 66     1022 if(ref $mime && $mime->isa('MIME::Type'))
119 0         0 { $mime = $mime->type;
120             }
121              
122 237 100       612 if(defined(my $based = $args->{based_on}))
123 130   66     437 { $mime //= $based->type;
124 130   66     518 $transfer //= $based->transferEncoding;
125 130   33     524 $disp //= $based->disposition;
126 130   33     544 $descr //= $based->description;
127 130   33     546 $cid //= $based->contentId;
128              
129             $self->{MMB_checked}
130 130 100       523 = defined $args->{checked} ? $args->{checked} : $based->checked;
131             }
132             else
133 107         189 { $transfer = $args->{transfer_encoding};
134 107   100     446 $self->{MMB_checked} = $args->{checked} || 0;
135             }
136              
137 237   100     663 $mime ||= 'text/plain';
138 237         673 $mime = $self->type($mime);
139              
140 237 100       762 my $default_charset = exists $args->{charset} ? $args->{charset} : 'PERL';
141 237 100 100     1548 $mime->attribute(charset => $default_charset)
      100        
142             if $default_charset
143             && $mime =~ m!^text/!i
144             && !$mime->attribute('charset');
145              
146 237 100       916 $self->transferEncoding($transfer) if defined $transfer;
147 237 100       789 $self->disposition($disp) if defined $disp;
148 237 100       724 $self->description($descr) if defined $descr;
149 237 100       697 $self->contentId($cid) if defined $cid;
150 237         673 $self->type($mime);
151              
152 237   100     1124 $self->{MMB_eol} = $args->{eol} || 'NATIVE';
153              
154             # Set message where the body belongs to.
155              
156             $self->message($args->{message})
157 237 100       723 if defined $args->{message};
158              
159 237         566 $self->{MMB_seqnr} = $body_count++;
160 237         993 $self;
161             }
162              
163              
164              
165 0     0 1 0 sub clone() {shift->notImplemented}
166              
167             #------------------------------------------
168              
169              
170             sub decoded(@)
171 25     25 1 73 { my $self = shift;
172 25         104 $self->encode(charset => 'PERL', transfer_encoding => 'none', @_);
173             }
174              
175              
176             sub eol(;$)
177 2     2 1 4 { my $self = shift;
178 2 100       8 return $self->{MMB_eol} unless @_;
179              
180 1         5 my $eol = shift;
181 1 50       4 if($eol eq 'NATIVE')
182 1 50       9 { $eol = $^O =~ m/^win/i ? 'CRLF'
    50          
183             : $^O =~ m/^mac/i ? 'CR'
184             : 'LF';
185             }
186              
187 1 50 33     7 return $self if $eol eq $self->{MMB_eol} && $self->checked;
188 1         4 my $lines = $self->lines;
189 1 50       4 if(@$lines)
190             { # sometimes texts lack \n on last line
191 1         5 $lines->[-1] .= "\n";
192              
193              
194 1 50       5 if($eol eq 'CR') {s/[\015\012]+$/\015/ for @$lines}
  0 50       0  
    0          
195 1         11 elsif($eol eq 'LF') {s/[\015\012]+$/\012/ for @$lines}
196 0         0 elsif($eol eq 'CRLF') {s/[\015\012]+$/\015\012/ for @$lines}
197             else
198 0         0 { $self->log(WARNING => "Unknown line terminator $eol ignored");
199 0         0 return $self->eol('NATIVE');
200             }
201             }
202              
203 1         9 (ref $self)->new(based_on => $self, eol => $eol, data => $lines);
204             }
205              
206             #------------------------------------------
207              
208              
209             sub message(;$)
210 164     164 1 281 { my $self = shift;
211 164 100       366 if(@_)
212 153 50       463 { if($self->{MMB_message} = shift)
213 153         501 { weaken $self->{MMB_message};
214             }
215             }
216 164         339 $self->{MMB_message};
217             }
218              
219              
220             sub isDelayed() {0}
221              
222              
223             sub isMultipart() {0}
224              
225              
226             sub isNested() {0}
227              
228              
229             sub partNumberOf($)
230 0     0 1 0 { shift->log(ERROR => 'part number needs multi-part or nested');
231 0         0 'ERROR';
232             }
233              
234             #------------------------------------------
235              
236              
237             sub type(;$)
238 1040     1040 1 1683 { my $self = shift;
239 1040 100 66     5267 return $self->{MMB_type} if !@_ && defined $self->{MMB_type};
240              
241 514         920 delete $self->{MMB_mime};
242 514   100     1296 my $type = shift // 'text/plain';
243              
244 514 100       1836 $self->{MMB_type} = ref $type ? $type->clone
245             : Mail::Message::Field->new('Content-Type' => $type);
246             }
247              
248              
249             sub mimeType()
250 44     44 1 430 { my $self = shift;
251 44 100       213 return $self->{MMB_mime} if exists $self->{MMB_mime};
252              
253 26         63 my $field = $self->{MMB_type};
254 26 50       134 my $body = defined $field ? $field->body : '';
255              
256 26 50       95 return $self->{MMB_mime} = $mime_plain
257             unless length $body;
258              
259             $self->{MMB_mime}
260 26   33     129 = $mime_types->type($body) || MIME::Type->new(type => $body);
261             }
262              
263              
264 63     63 1 5668 sub charset() { shift->type->attribute('charset') }
265              
266              
267             sub transferEncoding(;$)
268 552     552 1 5050 { my $self = shift;
269 552 100 100     2503 return $self->{MMB_transfer} if !@_ && defined $self->{MMB_transfer};
270              
271 262 100       582 my $set = defined $_[0] ? shift : 'none';
272 262 100       968 $self->{MMB_transfer} = ref $set ? $set->clone
273             : Mail::Message::Field->new('Content-Transfer-Encoding' => $set);
274             }
275              
276              
277             sub description(;$)
278 387     387 1 638 { my $self = shift;
279 387 100 100     1157 return $self->{MMB_description} if !@_ && $self->{MMB_description};
280              
281 223 100       497 my $disp = defined $_[0] ? shift : 'none';
282 223 100       657 $self->{MMB_description} = ref $disp ? $disp->clone
283             : Mail::Message::Field->new('Content-Description' => $disp);
284             }
285              
286              
287             sub disposition(;$)
288 389     389 1 646 { my $self = shift;
289 389 100 100     1253 return $self->{MMB_disposition} if !@_ && $self->{MMB_disposition};
290              
291 223 100       521 my $disp = defined $_[0] ? shift : 'none';
292              
293 223 100       680 $self->{MMB_disposition} = ref $disp ? $disp->clone
294             : Mail::Message::Field->new('Content-Disposition' => $disp);
295             }
296              
297              
298             sub contentId(;$)
299 387     387 1 601 { my $self = shift;
300 387 100 100     1143 return $self->{MMB_id} if !@_ && $self->{MMB_id};
301              
302 223 100       461 my $cid = defined $_[0] ? shift : 'none';
303 223 100       665 $self->{MMB_id} = ref $cid ? $cid->clone
304             : Mail::Message::Field->new('Content-ID' => $cid);
305             }
306              
307              
308             sub checked(;$)
309 111     111 1 2336 { my $self = shift;
310 111 100       432 @_ ? ($self->{MMB_checked} = shift) : $self->{MMB_checked};
311             }
312              
313              
314 0     0 1 0 sub nrLines(@) {shift->notImplemented}
315              
316              
317 0     0 1 0 sub size(@) {shift->notImplemented}
318              
319             #------------------------------------------
320              
321              
322 0     0 1 0 sub string() {shift->notImplemented}
323              
324             sub string_unless_carp()
325 25     25 0 10491 { my $self = shift;
326 25 50       158 return $self->string unless (caller)[0] eq 'Carp';
327              
328 0         0 (my $class = ref $self) =~ s/^Mail::Message/MM/;
329 0         0 "$class object";
330             }
331              
332              
333 0     0 1 0 sub lines() {shift->notImplemented}
334              
335              
336 0     0 1 0 sub file(;$) {shift->notImplemented}
337              
338              
339 0     0 1 0 sub print(;$) {shift->notImplemented}
340              
341              
342 0     0 1 0 sub printEscapedFrom($) {shift->notImplemented}
343              
344              
345             sub write(@)
346 0     0 1 0 { my ($self, %args) = @_;
347 0         0 my $filename = $args{filename};
348 0 0       0 die "No filename for write() body" unless defined $filename;
349              
350 0 0       0 open OUT, '>', $filename or return;
351 0         0 $self->print(\*OUT);
352 0 0       0 close OUT or return undef;
353 0         0 $self;
354             }
355              
356              
357 0     0 1 0 sub endsOnNewline() {shift->notImplemented}
358              
359              
360 0     0 1 0 sub stripTrailingNewline() {shift->notImplemented}
361              
362             #------------------------------------------
363              
364              
365 0     0 1 0 sub read(@) {shift->notImplemented}
366              
367              
368             sub contentInfoTo($)
369 87     87 1 175 { my ($self, $head) = @_;
370 87 50       195 return unless defined $head;
371              
372 87         276 my $lines = $self->nrLines;
373 87         275 my $size = $self->size;
374 87 50       218 $size += $lines if $Mail::Message::crlf_platform;
375              
376 87         210 $head->set($self->type);
377 87         259 $head->set($self->transferEncoding);
378 87         277 $head->set($self->disposition);
379 87         270 $head->set($self->description);
380 87         280 $head->set($self->contentId);
381 87         208 $self;
382             }
383              
384              
385             sub contentInfoFrom($)
386 40     40 1 88 { my ($self, $head) = @_;
387              
388 40         142 $self->type($head->get('Content-Type', 0));
389              
390             my ($te, $disp, $desc, $cid)
391 40   100     124 = map { my $x = $head->get("Content-$_") || '';
  160         431  
392 160         413 s/^\s+//,s/\s+$// for $x;
393 160 100       401 length $x ? $x : undef
394             } qw/Transfer-Encoding Disposition Description ID/;
395              
396 40         161 $self->transferEncoding($te);
397 40         148 $self->disposition($disp);
398 40         157 $self->description($desc);
399 40         138 $self->contentId($cid);
400              
401 40         83 delete $self->{MMB_mime};
402 40         130 $self;
403              
404             }
405              
406              
407             sub modified(;$)
408 13     13 1 26 { my $self = shift;
409 13 50       35 return $self->isModified unless @_; # compat 2.036
410 13         33 $self->{MMB_modified} = shift;
411             }
412              
413              
414 0     0 1 0 sub isModified() { shift->{MMB_modified} }
415              
416              
417             sub fileLocation(;@)
418 46     46 1 77 { my $self = shift;
419 46 100       118 return @$self{ qw/MMB_begin MMB_end/ } unless @_;
420 39         187 @$self{ qw/MMB_begin MMB_end/ } = @_;
421             }
422              
423              
424             sub moveLocation($)
425 0     0 1 0 { my ($self, $dist) = @_;
426 0         0 $self->{MMB_begin} -= $dist;
427 0         0 $self->{MMB_end} -= $dist;
428 0         0 $self;
429             }
430              
431              
432 2     2 1 7 sub load() {shift}
433              
434             #------------------------------------------
435              
436              
437             my @in_encode = qw/check encode encoded eol isBinary isText unify
438             dispositionFilename/;
439             my %in_module = map { ($_ => 'encode') } @in_encode;
440              
441             sub AUTOLOAD(@)
442 13     13   1893 { my $self = shift;
443 13         40 our $AUTOLOAD;
444 13         109 (my $call = $AUTOLOAD) =~ s/.*\:\://g;
445              
446 13   100     98 my $mod = $in_module{$call} || 'construct';
447 13 100       69 if($mod eq 'encode'){ require Mail::Message::Body::Encode }
  10         5531  
448 3         1663 else { require Mail::Message::Body::Construct }
449              
450 34     34   110906 no strict 'refs';
  34         225  
  34         3990  
451 13 50       287 return $self->$call(@_) if $self->can($call); # now loaded
452              
453             # AUTOLOAD inheritance is a pain
454 0           confess "Method $call() is not defined for a ", ref $self;
455             }
456              
457             #------------------------------------------
458              
459              
460             1;