File Coverage

blib/lib/Mail/Message/Body/Multipart.pm
Criterion Covered Total %
statement 198 232 85.3
branch 94 128 73.4
condition 19 32 59.3
subroutine 27 34 79.4
pod 21 23 91.3
total 359 449 79.9


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::Multipart;
10 34     34   934 use vars '$VERSION';
  34         84  
  34         1846  
11             $VERSION = '3.013';
12              
13 34     34   216 use base 'Mail::Message::Body';
  34         120  
  34         4929  
14              
15 34     34   260 use strict;
  34         82  
  34         824  
16 34     34   197 use warnings;
  34         84  
  34         1102  
17              
18 34     34   203 use Mail::Message::Body::Lines;
  34         87  
  34         1188  
19 34     34   211 use Mail::Message::Part;
  34         111  
  34         1215  
20              
21 34     34   15055 use Mail::Box::FastScalar;
  34         101  
  34         1033  
22 34     34   228 use Carp;
  34         76  
  34         100188  
23              
24              
25             sub init($)
26 40     40 0 114 { my ($self, $args) = @_;
27 40         77 my $based = $args->{based_on};
28 40 100 66     257 $args->{mime_type} ||= defined $based ? $based->type : 'multipart/mixed';
29              
30 40         180 $self->SUPER::init($args);
31              
32 40         92 my @parts;
33 40 100       150 if($args->{parts})
34 31         55 { foreach my $raw (@{$args->{parts}})
  31         92  
35 54 50       114 { next unless defined $raw;
36 54         180 my $cooked = Mail::Message::Part->coerce($raw, $self);
37              
38 54 50       150 $self->log(ERROR => 'Data not convertible to a message (type is '
39             , ref $raw,")\n"), next unless defined $cooked;
40              
41 54         148 push @parts, $cooked;
42             }
43             }
44              
45 40         107 my $preamble = $args->{preamble};
46 40 50 66     139 $preamble = Mail::Message::Body->new(data => $preamble)
47             if defined $preamble && ! ref $preamble;
48            
49 40         77 my $epilogue = $args->{epilogue};
50 40 50 66     119 $epilogue = Mail::Message::Body->new(data => $epilogue)
51             if defined $epilogue && ! ref $epilogue;
52            
53 40 100       150 if($based)
54 24   33     112 { $self->boundary($args->{boundary} || $based->boundary);
55             $self->{MMBM_preamble}
56 24 100       128 = defined $preamble ? $preamble : $based->preamble;
57              
58             $self->{MMBM_parts}
59             = @parts ? \@parts
60 24 100 66     98 : !$args->{parts} && $based->isMultipart
    100          
61             ? [ $based->parts('ACTIVE') ]
62             : [];
63              
64             $self->{MMBM_epilogue}
65 24 100       80 = defined $epilogue ? $epilogue : $based->epilogue;
66             }
67             else
68 16   66     99 { $self->boundary($args->{boundary} ||$self->type->attribute('boundary'));
69 16         68 $self->{MMBM_preamble} = $preamble;
70 16         50 $self->{MMBM_parts} = \@parts;
71 16         38 $self->{MMBM_epilogue} = $epilogue;
72             }
73              
74 40         216 $self;
75             }
76              
77             sub isMultipart() {1}
78              
79             # A multipart body is never binary itself. The parts may be.
80             sub isBinary() {0}
81              
82             sub clone()
83 3     3 1 8 { my $self = shift;
84 3         9 my $preamble = $self->preamble;
85 3         10 my $epilogue = $self->epilogue;
86              
87             my $body = ref($self)->new
88             ( $self->logSettings
89             , based_on => $self
90             , preamble => ($preamble ? $preamble->clone : undef)
91             , epilogue => ($epilogue ? $epilogue->clone : undef)
92 3 100       17 , parts => [ map {$_->clone} $self->parts('ACTIVE') ]
  7 100       35  
93             );
94              
95             }
96              
97             sub nrLines()
98 22     22 1 47 { my $self = shift;
99 22         38 my $nr = 1; # trailing part-sep
100              
101 22 100       56 if(my $preamble = $self->preamble)
102 3         11 { $nr += $preamble->nrLines;
103 3 50       12 $nr++ if $preamble->endsOnNewline;
104             }
105              
106 22         67 foreach my $part ($self->parts('ACTIVE'))
107 33         129 { $nr += 1 + $part->nrLines;
108 33 100       98 $nr++ if $part->body->endsOnNewline;
109             }
110              
111 22 100       103 if(my $epilogue = $self->epilogue)
112 3         8 { $nr += $epilogue->nrLines;
113             }
114              
115 22         62 $nr;
116             }
117              
118             sub size()
119 26     26 1 49 { my $self = shift;
120 26         65 my $bbytes = length($self->boundary) +4; # \n--$b\n
121              
122 26         63 my $bytes = $bbytes +2; # last boundary, \n--$b--\n
123 26 100       73 if(my $preamble = $self->preamble)
124 6         23 { $bytes += $preamble->size }
125 20         42 else { $bytes -= 1 } # no leading \n
126              
127 26         78 $bytes += $bbytes + $_->size foreach $self->parts('ACTIVE');
128 26 100       80 if(my $epilogue = $self->epilogue)
129 7         19 { $bytes += $epilogue->size;
130             }
131 26         74 $bytes;
132             }
133              
134 4     4 1 15 sub string() { join '', shift->lines }
135              
136             sub lines()
137 4     4 1 8 { my $self = shift;
138              
139 4         15 my $boundary = $self->boundary;
140 4         6 my @lines;
141              
142 4         15 my $preamble = $self->preamble;
143 4 50       11 push @lines, $preamble->lines if $preamble;
144              
145 4         13 foreach my $part ($self->parts('ACTIVE'))
146             { # boundaries start with \n
147 8 100       97 if(!@lines) { ; }
    50          
148 4         10 elsif($lines[-1] =~ m/\n$/) { push @lines, "\n" }
149 0         0 else { $lines[-1] .= "\n" }
150 8         37 push @lines, "--$boundary\n", $part->lines;
151             }
152              
153 4 50       75 if(!@lines) { ; }
    50          
154 4         9 elsif($lines[-1] =~ m/\n$/) { push @lines, "\n" }
155 0         0 else { $lines[-1] .= "\n" }
156 4         12 push @lines, "--$boundary--";
157              
158 4 50       11 if(my $epilogue = $self->epilogue)
159 0         0 { $lines[-1] .= "\n";
160 0         0 push @lines, $epilogue->lines;
161             }
162              
163 4 50       63 wantarray ? @lines : \@lines;
164             }
165              
166             sub file() # It may be possible to speed-improve the next
167 0     0 1 0 { my $self = shift; # code, which first produces a full print of
168 0         0 my $text; # the message in memory...
169 0         0 my $dump = Mail::Box::FastScalar->new(\$text);
170 0         0 $self->print($dump);
171 0         0 $dump->seek(0,0);
172 0         0 $dump;
173             }
174              
175             sub print(;$)
176 8     8 1 19 { my $self = shift;
177 8   33     24 my $out = shift || select;
178              
179 8         57 my $boundary = $self->boundary;
180 8         15 my $count = 0;
181 8 100       25 if(my $preamble = $self->preamble)
182 2         16 { $preamble->print($out);
183 2         4 $count++;
184             }
185              
186 8 50       24 if(ref $out eq 'GLOB')
187 0         0 { foreach my $part ($self->parts('ACTIVE'))
188 0 0       0 { print $out "\n" if $count++;
189 0         0 print $out "--$boundary\n";
190 0         0 $part->print($out);
191             }
192 0 0       0 print $out "\n" if $count++;
193 0         0 print $out "--$boundary--";
194             }
195             else
196 8         24 { foreach my $part ($self->parts('ACTIVE'))
197 13 100       47 { $out->print("\n") if $count++;
198 13         108 $out->print("--$boundary\n");
199 13         150 $part->print($out);
200             }
201 8 100       30 $out->print("\n") if $count++;
202 8         69 $out->print("--$boundary--");
203             }
204              
205 8 100       85 if(my $epilogue = $self->epilogue)
206 2         8 { $out->print("\n");
207 2         21 $epilogue->print($out);
208             }
209              
210 8         20 $self;
211             }
212              
213              
214             sub foreachLine($)
215 0     0 1 0 { my ($self, $code) = @_;
216 0         0 $self->log(ERROR => "You cannot use foreachLine on a multipart");
217 0         0 confess;
218             }
219              
220             sub check()
221 0     0 1 0 { my $self = shift;
222 0     0   0 $self->foreachComponent( sub {$_[1]->check} );
  0         0  
223             }
224              
225             sub encode(@)
226 0     0 1 0 { my ($self, %args) = @_;
227 0     0   0 $self->foreachComponent( sub {$_[1]->encode(%args)} );
  0         0  
228             }
229              
230             sub encoded()
231 19     19 1 46 { my $self = shift;
232 19     34   162 $self->foreachComponent( sub {$_[1]->encoded} );
  34         147  
233             }
234              
235             sub read($$$$)
236 7     7 1 33 { my ($self, $parser, $head, $bodytype) = @_;
237              
238 7         20 my $boundary = $self->boundary;
239              
240 7         62 $parser->pushSeparator("--$boundary");
241 7         47 my @msgopts = $self->logSettings;
242              
243 7         18 my $te;
244 7 100 100     28 $te = lc $1
245             if +($head->get('Content-Transfer-Encoding') || '') =~ m/(\w+)/;
246            
247 7         26 my @sloppyopts =
248             ( mime_type => 'text/plain'
249             , transfer_encoding => $te
250             );
251              
252             # Get preamble.
253 7         18 my $headtype = ref $head;
254              
255 7         29 my $begin = $parser->filePosition;
256 7         36 my $preamble = Mail::Message::Body::Lines->new(@msgopts, @sloppyopts)
257             ->read($parser, $head);
258              
259 7 100       42 $preamble->nrLines
260             or undef $preamble;
261              
262 7 100       32 $self->{MMBM_preamble} = $preamble
263             if defined $preamble;
264              
265             # Get the parts.
266              
267 7         24 my ($has_epilogue, @parts);
268 7         23 while(my $sep = $parser->readSeparator)
269 20 100       200 { if($sep =~ m/--\Q$boundary\E--[ \t]*\n?/)
270             { # Per RFC 2046, a CRLF after the close-delimiter marks the presence
271             # of an epilogue. Preserve the epilogue, even if empty, so that the
272             # printed multipart body will also have the CRLF.
273             # This, however, is complicated w.r.t. mbox folders.
274 7         29 $has_epilogue = $sep =~ /\n/;
275 7         16 last;
276             }
277              
278 13         73 my $part = Mail::Message::Part->new
279             ( @msgopts
280             , container => $self
281             );
282              
283 13 50       59 last unless $part->readFromParser($parser, $bodytype);
284 13 50 33     53 push @parts, $part
285             if $part->head->names || $part->body->size;
286             }
287 7         24 $self->{MMBM_parts} = \@parts;
288              
289             # Get epilogue
290              
291 7         28 $parser->popSeparator;
292 7         25 my $epilogue = Mail::Message::Body::Lines->new(@msgopts, @sloppyopts)
293             ->read($parser, $head);
294              
295 7 0       33 my $end = defined $epilogue ? ($epilogue->fileLocation)[1]
    0          
    50          
296             : @parts ? ($parts[-1]->body->fileLocation)[1]
297             : defined $preamble ? ($preamble->fileLocation)[1]
298             : $begin;
299 7         37 $self->fileLocation($begin, $end);
300              
301 7 100 66     51 $has_epilogue || $epilogue->nrLines
302             or undef $epilogue;
303              
304 7 100       26 $self->{MMBM_epilogue} = $epilogue
305             if defined $epilogue;
306              
307 7         51 $self;
308             }
309              
310             #------------------------------------------
311              
312              
313             sub foreachComponent($)
314 19     19 1 53 { my ($self, $code) = @_;
315 19         41 my $changes = 0;
316              
317 19         34 my $new_preamble;
318 19 100       53 if(my $preamble = $self->preamble)
319 2         7 { $new_preamble = $code->($self, $preamble);
320 2 100       12 $changes++ unless $preamble == $new_preamble;
321             }
322              
323 19         36 my $new_epilogue;
324 19 100       45 if(my $epilogue = $self->epilogue)
325 2         6 { $new_epilogue = $code->($self, $epilogue);
326 2 100       8 $changes++ unless $epilogue == $new_epilogue;
327             }
328              
329 19         47 my @new_bodies;
330 19         56 foreach my $part ($self->parts('ACTIVE'))
331 30         128 { my $part_body = $part->body;
332 30         95 my $new_body = $code->($self, $part_body);
333              
334 30 100       90 $changes++ if $new_body != $part_body;
335 30         113 push @new_bodies, [$part, $new_body];
336             }
337              
338 19 100       87 return $self unless $changes;
339              
340 10         21 my @new_parts;
341 10         28 foreach (@new_bodies)
342 21         53 { my ($part, $body) = @$_;
343 21         74 my $new_part = Mail::Message::Part->new
344             ( head => $part->head->clone,
345             container => undef
346             );
347 21         91 $new_part->body($body);
348 21         58 push @new_parts, $new_part;
349             }
350              
351 10         70 my $constructed = (ref $self)->new
352             ( preamble => $new_preamble
353             , parts => \@new_parts
354             , epilogue => $new_epilogue
355             , based_on => $self
356             );
357              
358             $_->container($constructed)
359 10         44 foreach @new_parts;
360              
361 10         57 $constructed;
362             }
363              
364              
365             sub attach(@)
366 2     2 1 7 { my $self = shift;
367 2         8 my $new = ref($self)->new
368             ( based_on => $self
369             , parts => [$self->parts, @_]
370             );
371             }
372              
373              
374             sub stripSignature(@)
375 1     1 1 3 { my $self = shift;
376              
377 1         4 my @allparts = $self->parts;
378 1         2 my @parts = grep {! $_->body->mimeType->isSignature} @allparts;
  2         38  
379              
380 1 50       35 @allparts==@parts ? $self
381             : (ref $self)->new(based_on => $self, parts => \@parts);
382             }
383              
384             #------------------------------------------
385              
386              
387 103     103 1 361 sub preamble() {shift->{MMBM_preamble}}
388              
389              
390 107     107 1 300 sub epilogue() {shift->{MMBM_epilogue}}
391              
392              
393             sub parts(;$)
394 157     157 1 379 { my $self = shift;
395 157 100       350 return @{$self->{MMBM_parts}} unless @_;
  66         354  
396              
397 91         147 my $what = shift;
398 91         132 my @parts = @{$self->{MMBM_parts}};
  91         237  
399              
400 0         0 $what eq 'RECURSE' ? (map {$_->parts('RECURSE')} @parts)
401             : $what eq 'ALL' ? @parts
402 0         0 : $what eq 'DELETED' ? (grep {$_->isDeleted} @parts)
403 154         415 : $what eq 'ACTIVE' ? (grep {not $_->isDeleted} @parts)
404 91 0       438 : ref $what eq 'CODE'? (grep {$what->($_)} @parts)
  0 50       0  
    50          
    50          
    50          
405             : ($self->log(ERROR => "Unknown criterium $what to select parts."), return ());
406             }
407              
408              
409 13     13 1 124 sub part($) { shift->{MMBM_parts}[shift] }
410              
411             sub partNumberOf($)
412 8     8 1 22 { my ($self, $part) = @_;
413 8         22 my $msg = $self->message;
414 8 50       28 unless($msg)
415 0         0 { $self->log(ERROR => 'multipart is not connected');
416 0         0 return 'ERROR';
417             }
418              
419 8 100       54 my $base = $msg->isa('Mail::Message::Part') ? $msg->partNumber.'.' : '';
420              
421 8         23 my @parts = $self->parts('ACTIVE');
422 8         28 foreach my $partnr (0..@parts)
423 15 100       93 { return $base.($partnr+1)
424             if $parts[$partnr] == $part;
425             }
426 0         0 $self->log(ERROR => 'multipart is not found or not active');
427 0         0 'ERROR';
428             }
429              
430              
431             sub boundary(;$)
432 112     112 1 201 { my $self = shift;
433 112         306 my $mime = $self->type;
434              
435 112 100       313 unless(@_)
436 71         262 { my $boundary = $mime->attribute('boundary');
437 71 50       332 return $boundary if defined $boundary;
438             }
439              
440 41 100 66     462 my $boundary = @_ && defined $_[0] ? (shift) : "boundary-".int rand(1000000);
441 41         140 $self->type->attribute(boundary => $boundary);
442             }
443              
444             sub endsOnNewline() { 1 }
445              
446 0 0   0 0   sub toplevel() { my $msg = shift->message; $msg ? $msg->toplevel : undef}
  0            
447              
448             #-------------------------------------------
449              
450              
451             1;