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-2022 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 31     31   193 use vars '$VERSION';
  31         68  
  31         1644  
11             $VERSION = '3.012';
12              
13 31     31   169 use base 'Mail::Message::Body';
  31         60  
  31         4179  
14              
15 31     31   199 use strict;
  31         59  
  31         684  
16 31     31   177 use warnings;
  31         78  
  31         943  
17              
18 31     31   187 use Mail::Message::Body::Lines;
  31         72  
  31         855  
19 31     31   310 use Mail::Message::Part;
  31         85  
  31         895  
20              
21 31     31   11837 use Mail::Box::FastScalar;
  31         76  
  31         887  
22 31     31   234 use Carp;
  31         66  
  31         76931  
23              
24              
25             sub init($)
26 33     33 0 94 { my ($self, $args) = @_;
27 33         79 my $based = $args->{based_on};
28 33 100 66     286 $args->{mime_type} ||= defined $based ? $based->type : 'multipart/mixed';
29              
30 33         168 $self->SUPER::init($args);
31              
32 33         63 my @parts;
33 33 100       122 if($args->{parts})
34 29         52 { foreach my $raw (@{$args->{parts}})
  29         98  
35 50 50       122 { next unless defined $raw;
36 50         202 my $cooked = Mail::Message::Part->coerce($raw, $self);
37              
38 50 50       130 $self->log(ERROR => 'Data not convertible to a message (type is '
39             , ref $raw,")\n"), next unless defined $cooked;
40              
41 50         129 push @parts, $cooked;
42             }
43             }
44              
45 33         71 my $preamble = $args->{preamble};
46 33 50 66     141 $preamble = Mail::Message::Body->new(data => $preamble)
47             if defined $preamble && ! ref $preamble;
48            
49 33         71 my $epilogue = $args->{epilogue};
50 33 50 66     121 $epilogue = Mail::Message::Body->new(data => $epilogue)
51             if defined $epilogue && ! ref $epilogue;
52            
53 33 100       115 if($based)
54 22   33     156 { $self->boundary($args->{boundary} || $based->boundary);
55             $self->{MMBM_preamble}
56 22 100       110 = defined $preamble ? $preamble : $based->preamble;
57              
58             $self->{MMBM_parts}
59             = @parts ? \@parts
60 22 100 66     105 : !$args->{parts} && $based->isMultipart
    100          
61             ? [ $based->parts('ACTIVE') ]
62             : [];
63              
64             $self->{MMBM_epilogue}
65 22 100       89 = defined $epilogue ? $epilogue : $based->epilogue;
66             }
67             else
68 11   66     114 { $self->boundary($args->{boundary} ||$self->type->attribute('boundary'));
69 11         41 $self->{MMBM_preamble} = $preamble;
70 11         41 $self->{MMBM_parts} = \@parts;
71 11         26 $self->{MMBM_epilogue} = $epilogue;
72             }
73              
74 33         179 $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         6 my $preamble = $self->preamble;
85 2         7 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       10 , parts => [ map {$_->clone} $self->parts('ACTIVE') ]
  5 100       25  
93             );
94              
95             }
96              
97             sub nrLines()
98 20     20 1 41 { my $self = shift;
99 20         45 my $nr = 1; # trailing part-sep
100              
101 20 100       56 if(my $preamble = $self->preamble)
102 1         5 { $nr += $preamble->nrLines;
103 1 50       5 $nr++ if $preamble->endsOnNewline;
104             }
105              
106 20         70 foreach my $part ($self->parts('ACTIVE'))
107 29         97 { $nr += 1 + $part->nrLines;
108 29 100       84 $nr++ if $part->body->endsOnNewline;
109             }
110              
111 20 100       60 if(my $epilogue = $self->epilogue)
112 1         3 { $nr += $epilogue->nrLines;
113             }
114              
115 20         53 $nr;
116             }
117              
118             sub size()
119 24     24 1 56 { my $self = shift;
120 24         69 my $bbytes = length($self->boundary) +4; # \n--$b\n
121              
122 24         52 my $bytes = $bbytes +2; # last boundary, \n--$b--\n
123 24 100       67 if(my $preamble = $self->preamble)
124 4         12 { $bytes += $preamble->size }
125 20         46 else { $bytes -= 1 } # no leading \n
126              
127 24         163 $bytes += $bbytes + $_->size foreach $self->parts('ACTIVE');
128 24 100       70 if(my $epilogue = $self->epilogue)
129 1         4 { $bytes += $epilogue->size;
130             }
131 24         68 $bytes;
132             }
133              
134 4     4 1 16 sub string() { join '', shift->lines }
135              
136             sub lines()
137 4     4 1 7 { my $self = shift;
138              
139 4         13 my $boundary = $self->boundary;
140 4         9 my @lines;
141              
142 4         15 my $preamble = $self->preamble;
143 4 50       14 push @lines, $preamble->lines if $preamble;
144              
145 4         15 foreach my $part ($self->parts('ACTIVE'))
146             { # boundaries start with \n
147 8 100       153 if(!@lines) { ; }
    50          
148 4         12 elsif($lines[-1] =~ m/\n$/) { push @lines, "\n" }
149 0         0 else { $lines[-1] .= "\n" }
150 8         33 push @lines, "--$boundary\n", $part->lines;
151             }
152              
153 4 50       62 if(!@lines) { ; }
    50          
154 4         12 elsif($lines[-1] =~ m/\n$/) { push @lines, "\n" }
155 0         0 else { $lines[-1] .= "\n" }
156 4         13 push @lines, "--$boundary--";
157              
158 4 50       12 if(my $epilogue = $self->epilogue)
159 0         0 { $lines[-1] .= "\n";
160 0         0 push @lines, $epilogue->lines;
161             }
162              
163 4 50       72 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     26 my $out = shift || select;
178              
179 8         52 my $boundary = $self->boundary;
180 8         17 my $count = 0;
181 8 100       24 if(my $preamble = $self->preamble)
182 2         10 { $preamble->print($out);
183 2         2 $count++;
184             }
185              
186 8 50       30 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         21 { foreach my $part ($self->parts('ACTIVE'))
197 13 100       34 { $out->print("\n") if $count++;
198 13         93 $out->print("--$boundary\n");
199 13         122 $part->print($out);
200             }
201 8 100       32 $out->print("\n") if $count++;
202 8         57 $out->print("--$boundary--");
203             }
204              
205 8 100       75 if(my $epilogue = $self->epilogue)
206 2         7 { $out->print("\n");
207 2         16 $epilogue->print($out);
208             }
209              
210 8         19 $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 46 { my $self = shift;
232 18     30   184 $self->foreachComponent( sub {$_[1]->encoded} );
  30         161  
233             }
234              
235             sub read($$$$)
236 2     2 1 6 { my ($self, $parser, $head, $bodytype) = @_;
237              
238 2         4 my $boundary = $self->boundary;
239              
240 2         12 $parser->pushSeparator("--$boundary");
241 2         9 my @msgopts = $self->logSettings;
242              
243 2         3 my $te;
244 2 100 100     6 $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         4 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       9 $preamble->nrLines
260             or undef $preamble;
261              
262 2 100       8 $self->{MMBM_preamble} = $preamble
263             if defined $preamble;
264              
265             # Get the parts.
266              
267 2         3 my @parts;
268 2         8 while(my $sep = $parser->readSeparator)
269 6 100       25 { last if $sep eq "--$boundary--\n";
270              
271 4         22 my $part = Mail::Message::Part->new
272             ( @msgopts
273             , container => $self
274             );
275              
276 4 50       16 last unless $part->readFromParser($parser, $bodytype);
277 4 50 33     11 push @parts, $part
278             if $part->head->names || $part->body->size;
279             }
280 2         7 $self->{MMBM_parts} = \@parts;
281              
282             # Get epilogue
283              
284 2         8 $parser->popSeparator;
285 2         7 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         21 $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         10 $self;
301             }
302              
303             #------------------------------------------
304              
305              
306             sub foreachComponent($)
307 18     18 1 60 { my ($self, $code) = @_;
308 18         35 my $changes = 0;
309              
310 18         25 my $new_preamble;
311 18 100       59 if(my $preamble = $self->preamble)
312 1         3 { $new_preamble = $code->($self, $preamble);
313 1 50       5 $changes++ unless $preamble == $new_preamble;
314             }
315              
316 18         31 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         29 my @new_bodies;
323 18         66 foreach my $part ($self->parts('ACTIVE'))
324 28         117 { my $part_body = $part->body;
325 28         72 my $new_body = $code->($self, $part_body);
326              
327 28 100       104 $changes++ if $new_body != $part_body;
328 28         111 push @new_bodies, [$part, $new_body];
329             }
330              
331 18 100       88 return $self unless $changes;
332              
333 9         20 my @new_parts;
334 9         21 foreach (@new_bodies)
335 19         41 { my ($part, $body) = @$_;
336 19         71 my $new_part = Mail::Message::Part->new
337             ( head => $part->head->clone,
338             container => undef
339             );
340 19         74 $new_part->body($body);
341 19         53 push @new_parts, $new_part;
342             }
343              
344 9         76 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         43 foreach @new_parts;
353              
354 9         50 $constructed;
355             }
356              
357              
358             sub attach(@)
359 2     2 1 6 { my $self = shift;
360 2         7 my $new = ref($self)->new
361             ( based_on => $self
362             , parts => [$self->parts, @_]
363             );
364             }
365              
366              
367             sub stripSignature(@)
368 1     1 1 2 { my $self = shift;
369              
370 1         4 my @allparts = $self->parts;
371 1         2 my @parts = grep {! $_->body->mimeType->isSignature} @allparts;
  2         43  
372              
373 1 50       31 @allparts==@parts ? $self
374             : (ref $self)->new(based_on => $self, parts => \@parts);
375             }
376              
377             #------------------------------------------
378              
379              
380 97     97 1 343 sub preamble() {shift->{MMBM_preamble}}
381              
382              
383 97     97 1 331 sub epilogue() {shift->{MMBM_epilogue}}
384              
385              
386             sub parts(;$)
387 147     147 1 334 { my $self = shift;
388 147 100       309 return @{$self->{MMBM_parts}} unless @_;
  64         326  
389              
390 83         156 my $what = shift;
391 83         118 my @parts = @{$self->{MMBM_parts}};
  83         209  
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         371 : $what eq 'ACTIVE' ? (grep {not $_->isDeleted} @parts)
397 83 0       411 : 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 163 sub part($) { shift->{MMBM_parts}[shift] }
403              
404             sub partNumberOf($)
405 6     6 1 12 { my ($self, $part) = @_;
406 6         13 my @parts = $self->parts('ACTIVE');
407 6         17 my $msg = $self->message;
408 6 50       14 unless($msg)
409 0         0 { $self->log(ERROR => 'multipart is not connected');
410 0         0 return 'ERROR';
411             }
412 6 50       31 my $base = $msg->isa('Mail::Message::Part') ? $msg->partNumber.'.' : '';
413 6         18 foreach my $partnr (0..@parts)
414 12 100       55 { 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 176 { my $self = shift;
424 96         248 my $mime = $self->type;
425              
426 96 100       233 unless(@_)
427 62         199 { my $boundary = $mime->attribute('boundary');
428 62 50       278 return $boundary if defined $boundary;
429             }
430              
431 34 100 66     425 my $boundary = @_ && defined $_[0] ? (shift) : "boundary-".int rand(1000000);
432 34         103 $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;