File Coverage

blib/lib/Mail/Message/Construct/Rebuild.pm
Criterion Covered Total %
statement 130 147 88.4
branch 51 70 72.8
condition 14 35 40.0
subroutine 22 25 88.0
pod 2 14 14.2
total 219 291 75.2


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Message version 4.04.
2             # The POD got stripped from this file by OODoc version 3.06.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2026 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Message;{
13             our $VERSION = '4.04';
14             }
15              
16              
17 2     2   2550 use strict;
  2         6  
  2         101  
18 2     2   12 use warnings;
  2         4  
  2         146  
19              
20 2     2   12 use Log::Report 'mail-message', import => [ qw/__x error trace/ ];
  2         5  
  2         18  
21              
22 2     2   1010 use Mail::Message::Head::Complete ();
  2         5  
  2         62  
23 2     2   86 use Mail::Message::Body::Lines ();
  2         6  
  2         68  
24 2     2   11 use Mail::Message::Body::Multipart ();
  2         4  
  2         36  
25 2     2   24 use Mail::Box::FastScalar ();
  2         4  
  2         54  
26              
27 2     2   12 use Mail::Address ();
  2         2  
  2         79  
28              
29 2     2   12 use Scalar::Util qw/blessed/;
  2         4  
  2         181  
30 2     2   16 use List::Util qw/first/;
  2         4  
  2         5064  
31              
32             #--------------------
33              
34             my @default_rules =
35             qw/replaceDeletedParts descendMultiparts descendNested
36             flattenMultiparts flattenEmptyMultiparts/;
37              
38             sub rebuild(@)
39 14     14 1 6006 { my ($self, %args) = @_;
40 14         37 my $keep = delete $args{keep_message_id};
41              
42             # Collect the rules to be run
43 14 100       76 my @rules = $args{rules} ? @{delete $args{rules}} : @default_rules;
  6         19  
44 14 50       57 unshift @rules, @{delete $args{extra_rules}} if $args{extra_rules};
  0         0  
45 14 100       48 unshift @rules, @{delete $args{extraRules}} if $args{extraRules}; #old name
  6         27  
46              
47 14         37 foreach my $rule (@rules)
48 65 50       153 { next if ref $rule;
49 65 50       1870 $self->can($rule)
50             or error __x"no rebuild rule '{name}' defined.", name => $rule;
51             }
52              
53             # Start off with the message
54              
55 14 50       61 my $rebuild = $self->recursiveRebuildPart($self, %args, rules => \@rules)
56             or return;
57              
58             # Be sure we end-up with a message
59              
60 14 100       105 if($rebuild->isa('Mail::Message::Part'))
61             { # a bit too much information is lost: we are left without the
62             # main message headers....
63 4         18 my $clone = Mail::Message->new(head => $self->head->clone);
64 4         22 $clone->body($rebuild->body); # to update the Content lines
65 4         11 $rebuild = $clone;
66             }
67              
68 14 50       81 $keep or $rebuild->takeMessageId;
69 14         105 $rebuild;
70             }
71              
72             # The general rules
73              
74             sub flattenNesting($@)
75 4     4 0 13 { my ($self, $part) = @_;
76 4 100       12 $part->isNested ? $part->body->nested : $part;
77             }
78              
79             sub flattenMultiparts($@)
80 35     35 0 72 { my ($self, $part) = @_;
81 35 100       101 $part->isMultipart or return $part;
82 10         75 my @active = $part->parts('ACTIVE');
83 10 100       48 @active==1 ? $active[0] : $part;
84             }
85              
86             sub removeEmptyMultiparts($@)
87 14     14 0 33 { my ($self, $part) = @_;
88 14 50 66     39 $part->isMultipart && $part->body->parts==0 ? undef : $part;
89             }
90              
91             sub flattenEmptyMultiparts($@)
92 25     25 0 56 { my ($self, $part) = @_;
93              
94 25 100 100     77 $part->isMultipart && $part->parts('ACTIVE')==0
95             or return $part;
96              
97 2         9 my $body = $part->body;
98 2   33     11 my $preamble = $body->preamble || Mail::Message::Body::Lines->new(data => '');
99 2         11 my $epilogue = $body->epilogue;
100 2         31 my $newbody = $preamble->concatenate($preamble, <
101             * PLEASE NOTE:
102             * This multipart did not contain any parts (anymore)
103             * and was therefore flattened.
104              
105             NO_PARTS
106              
107 2         14 my $rebuild = Mail::Message::Part->new(head => $part->head->clone, container => undef);
108 2         15 $rebuild->body($newbody);
109 2         9 $rebuild;
110             }
111              
112             sub removeEmptyBodies($@)
113 0     0 0 0 { my ($self, $part) = @_;
114 0 0       0 $part->body->lines==0 ? undef : $part;
115             }
116              
117             sub descendMultiparts($@)
118 45     45 0 115 { my ($self, $part, %args) = @_;
119 45 100       128 return $part unless $part->isMultipart;
120              
121 19         73 my $body = $part->body;
122 19         37 my $changed = 0;
123 19         36 my @newparts;
124              
125 19         73 foreach my $part ($body->parts)
126 28         106 { my $new = $self->recursiveRebuildPart($part, %args);
127 28 100       188 if(!defined $new) { $changed++ }
  9 50       23  
128 19         55 elsif($new==$part) { push @newparts, $part }
129 0         0 else { push @newparts, $new; $changed++ }
  0         0  
130             }
131              
132 19 100       75 $changed or return $part;
133              
134 7         53 my $newbody = ref($body)->new(based_on => $body, parts => \@newparts);
135 7         43 my $rebuild = ref($part)->new(head => $part->head->clone, container => undef);
136              
137 7         46 $rebuild->body($newbody); # update Content-* lines
138 7         37 $rebuild;
139             }
140              
141             sub descendNested($@)
142 32     32 0 86 { my ($self, $part, %args) = @_;
143 32 100       94 $part->isNested or return $part;
144              
145 7         26 my $body = $part->body;
146 7         31 my $srcnested = $body->nested;
147 7         46 my $newnested = $self->recursiveRebuildPart($srcnested, %args);
148              
149 7 50       28 defined $newnested or return undef;
150 7 100       32 return $part if $newnested==$srcnested;
151              
152             # Changes in the encapsulated message
153 3         24 my $newbody = (ref $body)->new(based_on => $body, nested => $newnested);
154 3         18 my $rebuild = (ref $part)->new(head => $part->head->clone, container => undef);
155              
156 3         20 $rebuild->body($newbody);
157 3         15 $rebuild;
158             }
159              
160             sub removeDeletedParts($@)
161 30     30 0 68 { my ($self, $part) = @_;
162 30 100       98 $part->isDeleted ? undef : $part;
163             }
164              
165             sub replaceDeletedParts($@)
166 23     23 0 51 { my ($self, $part) = @_;
167              
168 23 50 66     80 ($part->isNested && $part->body->nested->isDeleted) || $part->isDeleted
      33        
169             or return $part;
170              
171 0         0 my $structure = '';
172 0         0 my $output = Mail::Box::FastScalar->new(\$structure);
173 0         0 $part->printStructure($output);
174              
175 0   0     0 my $dispfn = $part->body->dispositionFilename || '';
176 0         0 Mail::Message::Part->build(data => "Removed content:\n\n$structure\n$dispfn");
177             }
178              
179             # The more complex rules
180              
181             sub removeHtmlAlternativeToText($@)
182 8     8 0 19 { my ($self, $part) = @_;
183 8 100       25 $part->body->mimeType eq 'text/html'
184             or return $part;
185              
186 2         1135 my $container = $part->container;
187              
188 2 50 33     25 defined $container && $container->mimeType eq 'multipart/alternative'
189             or return $part;
190              
191             # The HTML $part will be nulled when a plain text part is found
192 2         58 foreach my $subpart ($container->parts)
193 4 100       57 { return undef if $subpart->body->mimeType eq 'text/plain';
194             }
195              
196 0         0 $part;
197             }
198              
199             sub removeExtraAlternativeText($@)
200 0     0 0 0 { my ($self, $part) = @_;
201              
202 0         0 my $container = $part->container;
203 0 0 0     0 $container && $container->mimeType eq 'multipart/alternative'
204             or return $part;
205              
206             # The last part is the preferred part (as per RFC2046)
207 0         0 my $last = ($container->parts)[-1];
208 0 0 0     0 $last && $part==$last ? $part : undef;
209             }
210              
211             my $has_hft;
212             sub textAlternativeForHtml($@)
213 3     3 0 13 { my ($self, $part, %args) = @_;
214              
215 3         7 my $hft = 'Mail::Message::Convert::HtmlFormatText';
216 3 100       13 unless(defined $has_hft)
217 1         97 { eval "require $hft";
218 1         20 $has_hft = $hft->can('format');
219             }
220              
221 3 100 66     22 $has_hft && $part->body->mimeType eq 'text/html'
222             or return $part;
223              
224 1         70 my $container = $part->container;
225 1   33     6 my $in_alt = defined $container && $container->mimeType eq 'multipart/alternative';
226              
227             return $part
228 1 50 33 0   6 if $in_alt && first { $_->body->mimeType eq 'text/plain' } $container->parts;
  0         0  
229              
230              
231             # Create the plain part
232              
233 1         5 my $html_body = $part->body;
234 1         17 my $plain_body = $hft->new(%args)->format($html_body);
235              
236 1         16 my $plain_part = Mail::Message::Part->new(container => undef);
237 1         11 $plain_part->body($plain_body);
238              
239 1 50       6 return $container->attach($plain_part)
240             if $in_alt;
241              
242             # Recreate the html part to loose some header lines
243              
244 1         7 my $html_part = Mail::Message::Part->new(container => undef);
245 1         7 $html_part->body($html_body);
246              
247             # Create the new part, with the headers of the html part
248              
249 1         12 my $mp = Mail::Message::Body::Multipart->new(mime_type => 'multipart/alternative', parts => [ $plain_part, $html_part ]);
250 1         8 my $newpart = (ref $part)->new(head => $part->head->clone, container => undef);
251 1         8 $newpart->body($mp);
252 1         8 $newpart;
253             }
254              
255             #--------------------
256              
257             sub recursiveRebuildPart($@)
258 49     49 1 142 { my ($self, $part, %args) = @_;
259              
260             RULES:
261 49         101 foreach my $rule (@{$args{rules}})
  49         118  
262 219 50       490 { my %params = ( %args, %{$args{$rule} || {}} );
  219         1071  
263 219 100       905 my $rebuild = $self->$rule($part, %params)
264             or return undef;
265              
266 210 100       1257 if($part != $rebuild)
267 17         55 { $part = $rebuild;
268 17         59 redo RULES;
269             }
270             }
271              
272 40         117 $part;
273             }
274              
275             #--------------------
276              
277              
278             1;