File Coverage

blib/lib/Mail/Message/Body/Multipart.pm
Criterion Covered Total %
statement 196 230 85.2
branch 89 128 69.5
condition 17 29 58.6
subroutine 27 34 79.4
pod 21 23 91.3
total 350 444 78.8


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