File Coverage

blib/lib/Courriel/Part/Single.pm
Criterion Covered Total %
statement 59 64 92.1
branch 13 14 92.8
condition 2 3 66.6
subroutine 18 20 90.0
pod 3 3 100.0
total 95 104 91.3


line stmt bran cond sub pod time code
1             package Courriel::Part::Single;
2              
3 7     7   499 use strict;
  7         9  
  7         189  
4 7     7   23 use warnings;
  7         10  
  7         183  
5 7     7   28 use namespace::autoclean;
  7         7  
  7         54  
6              
7             our $VERSION = '0.42';
8              
9 7     7   472 use Courriel::Header::Disposition;
  7         11  
  7         141  
10 7     7   22 use Courriel::Types qw( NonEmptyStr StringRef );
  7         8  
  7         121  
11 7     7   27374 use Email::MIME::Encodings;
  7         3660  
  7         160  
12 7     7   33 use Encode qw( decode encode );
  7         7  
  7         284  
13 7     7   27 use MIME::Base64 ();
  7         8  
  7         74  
14 7     7   19 use MIME::QuotedPrint ();
  7         8  
  7         71  
15              
16 7     7   21 use Moose;
  7         10  
  7         40  
17 7     7   29815 use MooseX::StrictConstructor;
  7         11  
  7         46  
18              
19             with 'Courriel::Role::Part';
20              
21             has content_ref => (
22             is => 'ro',
23             isa => StringRef,
24             coerce => 1,
25             init_arg => 'content',
26             lazy => 1,
27             builder => '_build_content_ref',
28             predicate => '_has_content_ref',
29             );
30              
31             has encoded_content_ref => (
32             is => 'ro',
33             isa => StringRef,
34             coerce => 1,
35             init_arg => 'encoded_content',
36             lazy => 1,
37             builder => '_build_encoded_content_ref',
38             predicate => '_has_encoded_content_ref',
39             );
40              
41             has disposition => (
42             is => 'ro',
43             isa => 'Courriel::Header::Disposition',
44             lazy => 1,
45             builder => '_build_disposition',
46             predicate => '_has_disposition',
47             handles => [qw( is_attachment is_inline filename )],
48             );
49              
50             has encoding => (
51             is => 'rw',
52             writer => '_set_encoding',
53             isa => NonEmptyStr,
54             lazy => 1,
55             default => '8bit',
56             predicate => '_has_encoding',
57             );
58              
59             sub BUILD {
60             my $self = shift;
61              
62             unless ( $self->_has_content_ref || $self->_has_encoded_content_ref ) {
63             die
64             'You must provide a content or encoded_content parameter when constructing a Courriel::Part::Single object.';
65             }
66              
67             if ( !$self->_has_encoding ) {
68             my @enc = $self->headers->get('Content-Transfer-Encoding');
69              
70             $self->_set_encoding( $enc[0]->value )
71             if @enc && $enc[0];
72             }
73              
74             $self->_sync_headers_with_self;
75              
76             return;
77             }
78              
79             after _set_headers => sub {
80             my $self = shift;
81              
82             $self->_sync_headers_with_self;
83              
84             return;
85             };
86              
87             sub _sync_headers_with_self {
88 119     119   119 my $self = shift;
89              
90 119         236 $self->_maybe_set_disposition_in_headers;
91              
92 119         2505 $self->headers->replace( 'Content-Transfer-Encoding' => $self->encoding );
93              
94 119         168 return;
95             }
96              
97             sub _maybe_set_disposition_in_headers {
98 119     119   123 my $self = shift;
99              
100 119 100       3234 return unless $self->_has_disposition;
101              
102 2         40 $self->headers->replace( 'Content-Disposition' => $self->disposition );
103             }
104              
105             {
106             my $fake_disp = Courriel::Header::Disposition->new_from_value(
107             name => 'Content-Disposition',
108             value => 'inline',
109             );
110              
111             sub _build_disposition {
112 3     3   4 my $self = shift;
113              
114 3         66 my @disp = $self->headers->get('Content-Disposition');
115 3 50       8 if ( @disp > 1 ) {
116 0         0 die
117             'This email defines more than one Content-Disposition header.';
118             }
119              
120 3   66     79 return $disp[0] // $fake_disp;
121             }
122             }
123              
124 0     0 1 0 sub is_multipart {0}
125              
126             {
127             my %unencoded = map { $_ => 1 } qw( 7bit 8bit binary );
128              
129             sub _build_content_ref {
130 9     9   11 my $self = shift;
131              
132 9         195 my $encoding = $self->encoding;
133              
134             my $bytes
135 9 100       35 = $unencoded{ lc $encoding }
136             ? $self->encoded_content
137             : Email::MIME::Encodings::decode(
138             $encoding,
139             $self->encoded_content,
140             );
141              
142 9 100       251 return \$bytes if $self->content_type->is_binary;
143              
144 4 100       89 return \$bytes
145             if lc $self->content_type->charset eq 'unknown-8bit';
146              
147             return \(
148 3         71 decode(
149             $self->content_type->charset,
150             $bytes,
151             )
152             );
153             }
154              
155             sub _build_encoded_content_ref {
156 4     4   6 my $self = shift;
157              
158 4         87 my $encoding = $self->encoding;
159              
160 4 100       85 my $bytes = $self->content_type->is_binary ? $self->content : encode(
161             $self->content_type->charset,
162             $self->content,
163             );
164              
165 4 100       110 return \$bytes if $unencoded{ lc $encoding };
166              
167             return \(
168 2         5 Email::MIME::Encodings::encode(
169             $encoding,
170             $bytes,
171             )
172             );
173             }
174             }
175              
176             sub content {
177 13     13 1 32 return ${ $_[0]->content_ref };
  13         301  
178             }
179              
180             sub encoded_content {
181 13     13 1 19 return ${ $_[0]->encoded_content_ref };
  13         313  
182             }
183              
184             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
185             sub _stream_content {
186 0     0     my $self = shift;
187 0           my $output = shift;
188              
189 0           return $output->( $self->encoded_content );
190             }
191             ## use critic
192              
193             __PACKAGE__->meta->make_immutable;
194              
195             1;
196              
197             # ABSTRACT: A part which does not contain other parts, only content
198              
199             __END__
200              
201             =pod
202              
203             =encoding utf-8
204              
205             =head1 NAME
206              
207             Courriel::Part::Single - A part which does not contain other parts, only content
208              
209             =head1 VERSION
210              
211             version 0.42
212              
213             =head1 SYNOPSIS
214              
215             my $headers = $part->headers;
216             my $ct = $part->content_type;
217              
218             my $content = $part->content;
219             print ${$content};
220              
221             =head1 DESCRIPTION
222              
223             This class represents a single part that does not contain other parts, just
224             content.
225              
226             =head1 API
227              
228             This class provides the following methods:
229              
230             =head2 Courriel::Part::Single->new( ... )
231              
232             This method creates a new part object. It accepts the following parameters:
233              
234             =over 4
235              
236             =item * content
237              
238             This can either be a string or a reference to a scalar. It should be a character
239             string, I<not> a byte string.
240              
241             If you pass a reference, then the scalar underlying the reference may be
242             modified, so don't pass in something you don't want modified.
243              
244             =item * encoded_content
245              
246             This can either be a string or a reference to a scalar.
247              
248             If you pass a reference, then the scalar underlying the reference may be
249             modified, so don't pass in something you don't want modified.
250              
251             =item * content_type
252              
253             A L<Courriel::Header::ContentType> object. This will default to one with the mime type
254             "text/plain".
255              
256             =item * disposition
257              
258             A L<Courriel::Header::Disposition> object representing this part's content
259             disposition. This will default to "inline" with no other attributes.
260              
261             =item * encoding
262              
263             The Content-Transfer-Encoding for this part. This defaults to the value found
264             in the part's headers, or "8bit" if no header is found.
265              
266             =item * headers
267              
268             A L<Courriel::Headers> object containing headers for this part.
269              
270             =back
271              
272             You must pass a C<content> or C<encoded_content> value when creating a new part,
273             but there's really no point in passing both.
274              
275             It is strongly recommended that you pass a C<content> parameter and letting
276             this module do the encoding for you internally.
277              
278             =head2 $part->content()
279              
280             This returns returns the decoded content for the part. It will be in Perl's
281             native utf-8 encoding, decoded from whatever character set the content is in.
282              
283             =head2 $part->encoded_content()
284              
285             This returns returns the encoded content for the part.
286              
287             =head2 $part->mime_type()
288              
289             Returns the mime type for this part.
290              
291             =head2 $part->has_charset()
292              
293             Return true if the part has a charset defined. Binary attachments will usually
294             not have this defined.
295              
296             =head2 $part->charset()
297              
298             Returns the charset for this part.
299              
300             =head2 $part->is_inline(), $part->is_attachment()
301              
302             These methods return boolean values based on the part's content disposition.
303              
304             =head2 $part->filename()
305              
306             Returns the filename from the part's content disposition, if any.
307              
308             =head2 $part->content_type()
309              
310             Returns the L<Courriel::Header::ContentType> object for this part.
311              
312             =head2 $part->disposition()
313              
314             Returns the L<Courriel::Header::Disposition> object for this part.
315              
316             =head2 $part->encoding()
317              
318             Returns the encoding for the part.
319              
320             =head2 $part->headers()
321              
322             Returns the L<Courriel::Headers> object for this part.
323              
324             =head2 $part->is_multipart()
325              
326             Returns false.
327              
328             =head2 $part->container()
329              
330             Returns the L<Courriel> or L<Courriel::Part::Multipart> object to which this
331             part belongs, if any. This is set when the part is added to another object.
332              
333             =head2 $part->content_ref()
334              
335             This returns returns a reference to a scalar containing the decoded content
336             for the part.
337              
338             =head2 $part->encoded_content_ref()
339              
340             This returns returns a reference to a scalar containing the encoded content
341             for the part, without any decoding.
342              
343             =head2 $part->as_string()
344              
345             Returns the part as a string, along with its headers. Lines will be terminated
346             with "\r\n".
347              
348             =head2 $part->stream_to( output => $output )
349              
350             This method will send the stringified part to the specified output. The
351             output can be a subroutine reference, a filehandle, or an object with a
352             C<print()> method. The output may be sent as a single string, as a list of
353             strings, or via multiple calls to the output.
354              
355             =head1 ROLES
356              
357             This class does the C<Courriel::Role::Part> and C<Courriel::Role::Streams>
358             roles.
359              
360             =head1 SUPPORT
361              
362             Bugs may be submitted through L<the RT bug tracker|http://rt.cpan.org/Public/Dist/Display.html?Name=Courriel>
363             (or L<bug-courriel@rt.cpan.org|mailto:bug-courriel@rt.cpan.org>).
364              
365             I am also usually active on IRC as 'drolsky' on C<irc://irc.perl.org>.
366              
367             =head1 AUTHOR
368              
369             Dave Rolsky <autarch@urth.org>
370              
371             =head1 COPYRIGHT AND LICENCE
372              
373             This software is Copyright (c) 2016 by Dave Rolsky.
374              
375             This is free software, licensed under:
376              
377             The Artistic License 2.0 (GPL Compatible)
378              
379             =cut