File Coverage

blib/lib/Email/MIME/Kit/Assembler/Standard.pm
Criterion Covered Total %
statement 150 158 94.9
branch 53 76 69.7
condition 20 26 76.9
subroutine 24 24 100.0
pod 0 4 0.0
total 247 288 85.7


line stmt bran cond sub pod time code
1             package Email::MIME::Kit::Assembler::Standard 3.000007;
2             # ABSTRACT: the standard kit assembler
3              
4 4     4   2460 use v5.20.0;
  4         17  
5 4     4   27 use Moose;
  4         16  
  4         24  
6 4     4   26903 use Moose::Util::TypeConstraints;
  4         10  
  4         38  
7              
8             #pod =head1 WARNING
9             #pod
10             #pod Email::MIME::Kit::Assembler::Standard works well, but is poorly decomposed,
11             #pod internally. Its methods may change substantially in the future, so relying on
12             #pod it as a base class is a bad idea.
13             #pod
14             #pod Because I<being able to> rely on it would be so useful, its behaviors will in
15             #pod the future be more reliable or factored out into roles. Until then, be
16             #pod careful.
17             #pod
18             #pod =cut
19              
20             with 'Email::MIME::Kit::Role::Assembler';
21              
22 4     4   9286 use Email::MIME::Creator;
  4         12  
  4         171  
23 4     4   41 use Encode ();
  4         16  
  4         100  
24 4     4   22 use File::Basename;
  4         11  
  4         8086  
25              
26             sub BUILD {
27 18     18 0 48 my ($self) = @_;
28 18         65 $self->_setup_content_ids;
29 18         70 $self->_pick_and_set_renderer;
30 18         75 $self->_build_subassemblies;
31             }
32              
33             has parent => (
34             is => 'ro',
35             isa => maybe_type(role_type('Email::MIME::Kit::Role::Assembler')),
36             weak_ref => 1,
37             );
38              
39             has renderer => (
40             reader => 'renderer',
41             writer => '_set_renderer',
42             clearer => '_unset_renderer',
43             isa => maybe_type(role_type('Email::MIME::Kit::Role::Renderer')),
44             init_arg => undef,
45             );
46              
47             sub assemble {
48 20     20 0 49 my ($self, $stash) = @_;
49              
50 20         734 my $manifest = $self->manifest;
51              
52 20         59 my $has_body = defined $manifest->{body};
53 20         58 my $has_path = defined $manifest->{path};
54 20 50       32 my $has_alts = @{ $manifest->{alternatives} || [] };
  20         68  
55 20 50       28 my $has_att = @{ $manifest->{attachments} || [] };
  20         64  
56              
57 20 50 100     121 Carp::croak("neither body, path, nor alternatives provided")
      66        
58             unless $has_body or $has_path or $has_alts;
59              
60             Carp::croak("you must provide only one of body, path, or alternatives")
61 20 50       51 unless (grep {$_} $has_body, $has_path, $has_alts) == 1;
  60         156  
62              
63 20 50       93 my $assembly_method = $has_body ? '_assemble_from_manifest_body'
    100          
    100          
64             : $has_path ? '_assemble_from_kit'
65             : $has_alts ? '_assemble_mp_alt'
66             : confess "unreachable code is a mistake";
67              
68 20         80 $self->$assembly_method($stash);
69             }
70              
71             sub _assemble_from_string {
72 17     17   57 my ($self, $body, $stash) = @_;
73              
74 17 100       36 my %attr = %{ $self->manifest->{attributes} || {} };
  17         613  
75 17   100     75 $attr{content_type} ||= 'text/plain';
76              
77 17 100       72 if ($attr{content_type} =~ m{^text/}) {
78             # I really shouldn't have to do this, but I'm not going to go screw around
79             # with @#$@#$ Email::Simple/MIME just to deal with it right now. -- rjbs,
80             # 2009-01-19
81 13 100       74 $body .= "\x0d\x0a" unless $body =~ /[\x0d|\x0a]\z/;
82             }
83              
84 17         66 my $body_ref = $self->render(\$body, $stash);
85              
86             my $email = $self->_contain_attachments({
87             attributes => \%attr,
88             header => $self->manifest->{header},
89             stash => $stash,
90             body => $$body_ref,
91             container_type => $self->manifest->{container_type},
92 17         635 });
93             }
94              
95             sub _assemble_from_manifest_body {
96 7     7   47 my ($self, $stash) = @_;
97              
98             $self->_assemble_from_string(
99             $self->manifest->{body},
100 7         252 $stash,
101             );
102             }
103              
104             sub _assemble_from_kit {
105 10     10   29 my ($self, $stash) = @_;
106              
107 10   100     338 my $type = $self->manifest->{attributes}{content_type} || 'text/plain';
108 10 100       42 my $method = $type =~ m{^text/} ? 'get_decoded_kit_entry' : 'get_kit_entry';
109              
110 10         345 my $body_ref = $self->kit->$method($self->manifest->{path});
111              
112 10         48 $self->_assemble_from_string($$body_ref, $stash);
113             }
114              
115             sub _assemble_mp_alt {
116 3     3   13 my ($self, $stash) = @_;
117              
118 3 50       6 my %attr = %{ $self->manifest->{attributes} || {} };
  3         112  
119 3   50     21 $attr{content_type} = $attr{content_type} || 'multipart/alternative';
120              
121 3 50       45 if ($attr{content_type} !~ qr{\Amultipart/alternative\b}) {
122 0         0 confess "illegal content_type for mail with alts: $attr{content_type}";
123             }
124              
125 3         12 my $parts = [ map { $_->assemble($stash) } @{ $self->_alternatives } ];
  8         21395  
  3         106  
126              
127             my $email = $self->_contain_attachments({
128             attributes => \%attr,
129             header => $self->manifest->{header},
130 3         1951 stash => $stash,
131             parts => $parts,
132             });
133             }
134              
135             sub _renderer_from_override {
136 8     8   22 my ($self, $override) = @_;
137              
138             # Allow an explicit undef to mean "no rendering is to be done." -- rjbs,
139             # 2009-01-19
140 8 50       25 return undef unless defined $override;
141              
142 0         0 return $self->kit->_build_component(
143             'Email::MIME::Kit::Renderer',
144             $override,
145             );
146             }
147              
148             sub _pick_and_set_renderer {
149 18     18   41 my ($self) = @_;
150              
151             # "renderer" entry at top-level sets the kit default_renderer, so trying to
152             # look at the "renderer" entry at top-level for an override is nonsensical
153             # -- rjbs, 2009-01-22
154 18 100       607 unless ($self->parent) {
155 6         194 $self->_set_renderer($self->kit->default_renderer);
156 6         15 return;
157             }
158              
159             # If there's no override, we just use the parent. We don't need to worry
160             # about the "there is no parent" case, because that was handled above. --
161             # rjbs, 2009-01-22
162 12 100       371 unless (exists $self->manifest->{renderer}) {
163 10         319 $self->_set_renderer($self->parent->renderer);
164 10         26 return;
165             }
166              
167 2         64 my $renderer = $self->_renderer_from_override($self->manifest->{renderer});
168 2         73 $self->_set_renderer($renderer);
169             }
170              
171             has manifest => (
172             is => 'ro',
173             required => 1,
174             );
175              
176             has [ qw(_attachments _alternatives) ] => (
177             is => 'ro',
178             isa => 'ArrayRef',
179             init_arg => undef,
180             default => sub { [] },
181             );
182              
183             has _body => (
184             reader => 'body',
185             writer => '_set_body',
186             );
187              
188             sub _build_subassemblies {
189 18     18   40 my ($self) = @_;
190              
191 18 100       576 if (my $body = $self->manifest->{body}) {
192 6         214 $self->_set_body($body);
193             }
194              
195 18 50       36 for my $attach (@{ $self->manifest->{attachments} || [] }) {
  18         568  
196 4         124 my $assembler = $self->kit->_assembler_from_manifest($attach, $self);
197 4 50       27 $assembler->_set_attachment_info($attach)
198             if $assembler->can('_set_attachment_info');
199 4         9 push @{ $self->_attachments }, $assembler;
  4         151  
200             }
201              
202 18 50       38 for my $alt (@{ $self->manifest->{alternatives} || [] }) {
  18         591  
203 8         16 push @{ $self->_alternatives },
  8         333  
204             $self->kit->_assembler_from_manifest($alt, $self);
205             }
206             }
207              
208             sub _set_attachment_info {
209 4     4   10 my ($self, $manifest) = @_;
210              
211 4   50     14 my $attr = $manifest->{attributes} ||= {};
212              
213 4 50       17 $attr->{encoding} = 'base64' unless exists $attr->{encoding};
214 4 50       41 $attr->{disposition} = 'attachment' unless exists $attr->{disposition};
215              
216 4 100       28 unless (exists $attr->{filename}) {
217 2         12 my $filename;
218             ($filename) = File::Basename::fileparse($manifest->{path})
219 2 50       86 if $manifest->{path};
220              
221             # XXX: Steal the attachment-name-generator from Email::MIME::Modifier, or
222             # something. -- rjbs, 2009-01-20
223 2   50     10 $filename ||= "unknown-attachment";
224              
225 2         6 $attr->{filename} = $filename;
226             }
227             }
228              
229             sub render {
230 17     17 0 49 my ($self, $input_ref, $stash) = @_;
231 17     2   94 local $stash->{cid_for} = sub { $self->cid_for_path($_[0]) };
  2         16  
232 17 100       623 return $input_ref unless my $renderer = $self->renderer;
233 12         60 return $renderer->render($input_ref, $stash);
234             }
235              
236             sub _prep_header {
237 20     20   49 my ($self, $header, $stash) = @_;
238              
239 20         43 my @done_header;
240 20         64 for my $entry (@$header) {
241             confess "no field name candidates"
242 24 50       92 unless my (@hval) = grep { /^[^:]/ } keys %$entry;
  30         156  
243 24 50       73 confess "multiple field name candidates: @hval" if @hval > 1;
244 24         59 my $value = $entry->{ $hval[ 0 ] };
245              
246 24 50       61 if (ref $value) {
247 0         0 my ($v, $p) = @$value;
248 0         0 $value = join q{; }, $v, map { "$_=$p->{$_}" } keys %$p;
  0         0  
249             } else {
250             # I don't think I need to bother with $self->render, which will set up
251             # the cid_for callback. Honestly, who is going to be referencing a
252             # content-id from a header? Let's hope I never find out... -- rjbs,
253             # 2009-01-22
254             my $renderer = exists $entry->{':renderer'}
255 24 100       687 ? $self->_renderer_from_override($entry->{':renderer'})
256             : $self->renderer;
257              
258 24 100       63 $value = ${ $renderer->render(\$value, $stash) } if defined $renderer;
  12         39  
259             }
260              
261 24         87 push @done_header, $hval[0] => $value;
262             }
263              
264 20         51 return \@done_header;
265             }
266              
267             sub _contain_attachments {
268 20     20   59 my ($self, $arg) = @_;
269              
270 20         37 my @attachments = @{ $self->_attachments };
  20         679  
271 20         74 my $header = $self->_prep_header($arg->{header}, $arg->{stash});
272              
273 20         48 my $ct = $arg->{container_type};
274              
275 20         33 my %attr = %{ $arg->{attributes} };
  20         94  
276 20         46 my $body_type = 'body';
277              
278 20 100 100     147 if ($attr{content_type} =~ m{^text/}) {
    100          
279 13         28 $body_type = 'body_str';
280              
281 13   50     87 $attr{encoding} ||= 'quoted-printable';
282 13   100     68 $attr{charset} ||= 'UTF-8'
283             } elsif (($arg->{body} || '') =~ /\P{ASCII}/) {
284 4   50     14 $attr{encoding} ||= 'base64';
285             }
286              
287 20 100       60 unless (@attachments) {
288 16 50       50 confess "container_type given for single-part assembly" if $ct;
289              
290             return Email::MIME->create(
291             attributes => \%attr,
292             header_str => $header,
293             $body_type => $arg->{body},
294             parts => $arg->{parts},
295 16         145 );
296             }
297              
298             my $email = Email::MIME->create(
299             attributes => \%attr,
300             $body_type => $arg->{body},
301             parts => $arg->{parts},
302 4         30 );
303              
304 4         14411 my @att_parts = map { $_->assemble($arg->{stash}) } @attachments;
  4         20  
305              
306 4   100     7826 my $container = Email::MIME->create(
307             attributes => { content_type => ($ct || 'multipart/mixed') },
308             header_str => $header,
309             parts => [ $email, @att_parts ],
310             );
311              
312 4         32171 return $container;
313             }
314              
315             has _cid_registry => (
316             is => 'ro',
317             init_arg => undef,
318             default => sub { { } },
319             );
320              
321             sub cid_for_path {
322 2     2 0 9 my ($self, $path) = @_;
323 2         86 my $cid = $self->_cid_registry->{ $path };
324              
325 2 50       10 confess "no content-id for path $path" unless $cid;
326              
327 2         23 return $cid;
328             }
329              
330             sub _setup_content_ids {
331 18     18   36 my ($self) = @_;
332              
333 18 50       32 for my $att (@{ $self->manifest->{attachments} || [] }) {
  18         667  
334 4 50       34 next unless $att->{path};
335              
336 4         9 for my $header (@{ $att->{header} }) {
  4         20  
337 0         0 my ($header) = grep { /^[^:]/ } keys %$header;
  0         0  
338 0 0       0 Carp::croak("attachments must not supply content-id")
339             if lc $header eq 'content-id';
340             }
341              
342 4         139 my $cid = $self->kit->_generate_content_id;
343 4         2449 push @{ $att->{header} }, {
  4         28  
344             'Content-Id' => $cid->in_brackets,
345             ':renderer' => undef,
346             };
347              
348 4         89 $self->_cid_registry->{ $att->{path} } = $cid->as_string;
349             }
350             }
351              
352 4     4   3520 no Moose::Util::TypeConstraints;
  4         11  
  4         24  
353 4     4   1230 no Moose;
  4         8  
  4         31  
354             __PACKAGE__->meta->make_immutable;
355             1;
356              
357             __END__
358              
359             =pod
360              
361             =encoding UTF-8
362              
363             =head1 NAME
364              
365             Email::MIME::Kit::Assembler::Standard - the standard kit assembler
366              
367             =head1 VERSION
368              
369             version 3.000007
370              
371             =head1 PERL VERSION
372              
373             This library should run on perls released even a long time ago. It should work
374             on any version of perl released in the last five years.
375              
376             Although it may work on older versions of perl, no guarantee is made that the
377             minimum required version will not be increased. The version may be increased
378             for any reason, and there is no promise that patches will be accepted to lower
379             the minimum required perl.
380              
381             =head1 WARNING
382              
383             Email::MIME::Kit::Assembler::Standard works well, but is poorly decomposed,
384             internally. Its methods may change substantially in the future, so relying on
385             it as a base class is a bad idea.
386              
387             Because I<being able to> rely on it would be so useful, its behaviors will in
388             the future be more reliable or factored out into roles. Until then, be
389             careful.
390              
391             =head1 AUTHOR
392              
393             Ricardo Signes <rjbs@cpan.org>
394              
395             =head1 COPYRIGHT AND LICENSE
396              
397             This software is copyright (c) 2023 by Ricardo Signes.
398              
399             This is free software; you can redistribute it and/or modify it under
400             the same terms as the Perl 5 programming language system itself.
401              
402             =cut