File Coverage

blib/lib/MIME/Signature.pm
Criterion Covered Total %
statement 17 141 12.0
branch 0 68 0.0
condition 0 23 0.0
subroutine 6 35 17.1
pod 22 22 100.0
total 45 289 15.5


line stmt bran cond sub pod time code
1             package MIME::Signature;
2              
3 3     3   136761 use 5.014;
  3         24  
4 3     3   13 use warnings;
  3         5  
  3         80  
5              
6 3     3   14 use base 'Exporter';
  3         4  
  3         434  
7              
8             our $VERSION = '0.22';
9              
10 3     3   19 use Carp qw(croak);
  3         5  
  3         155  
11 3     3   1438 use Encode qw(decode encode encode_utf8);
  3         45302  
  3         448  
12 3     3   2638 use MIME::Parser;
  3         325661  
  3         5531  
13              
14             our @EXPORT = ();
15             our @EXPORT_OK = (
16             qw(
17             &_decoded_body
18             &_replace_body
19             )
20             );
21             our %EXPORT_TAGS = ();
22              
23             sub _decoded_body {
24 0     0     my $entity = shift;
25 0           my $body = $entity->bodyhandle->as_string;
26 0 0         if ( my $charset = $entity->head->mime_attr('content-type.charset') ) {
27 0           $body = decode $charset, $body;
28             }
29 0           $body;
30             }
31              
32             sub _replace_body {
33 0     0     my ( $entity, $body ) = @_;
34 0 0         $body .= "\n" if $body !~ /\n\z/;
35 0           my $encoded_body;
36             {
37 0           my $encoding_ok;
  0            
38 0 0         if ( my $charset = $entity->head->mime_attr('content-type.charset') ) {
39 0           $encoding_ok = 1;
40             $encoded_body = encode $charset, $body,
41 0     0     sub { undef $encoding_ok; '' };
  0            
  0            
42             }
43 0 0         unless ($encoding_ok) {
44 0           my $head = $entity->head;
45 0 0         $head->mime_attr( 'Content-Type', 'text/plain' )
46             unless $head->mime_attr('content-type');
47 0           $head->mime_attr( 'content-type.charset' => 'UTF-8' );
48 0           $encoded_body = encode_utf8($body);
49             }
50             }
51 0 0         my $fh = $entity->bodyhandle->open('w') or die "Open body: $!\n";
52              
53             # Avoid "SMTP cannot transfer messages with partial final lines. (#5.6.2)":
54 0           $fh->print($encoded_body);
55              
56 0 0         $fh->close or die "Cannot replace body: $!\n";
57             }
58              
59             sub enriched {
60 0     0 1   my $self = shift;
61 0 0         if (@_) {
    0          
62 0           $self->{enriched} = shift;
63             }
64             elsif ( defined wantarray ) {
65 0 0 0       if ( !defined $self->{enriched} && defined( my $plain = $self->plain ) )
66             {
67 0           $self->{enriched} = $plain =~ s/
68             }
69 0           $self->{enriched};
70             }
71             }
72              
73             sub enriched_delimiter {
74 0     0 1   my $self = shift;
75 0 0         $self->{enriched_delimiter} = shift if @_;
76 0           $self->{enriched_delimiter};
77             }
78              
79             sub html {
80 0     0 1   my $self = shift;
81 0 0         if (@_) {
    0          
82 0           $self->{html} = shift;
83             }
84             elsif ( defined wantarray ) {
85 0 0 0       if ( !defined $self->{html} && defined( my $plain = $self->plain ) ) {
86             require HTML::Entities
87 0 0 0       and HTML::Entities->import('encode_entities')
88             unless defined &encode_entities;
89             $self->{html} =
90 0           join( '
', split /\n/, encode_entities($plain) ) . "\n";
91             }
92 0           $self->{html};
93             }
94             }
95              
96             sub html_delimiter {
97 0     0 1   my $self = shift;
98 0 0         $self->{html_delimiter} = shift if @_;
99 0           $self->{html_delimiter};
100             }
101              
102             sub plain {
103 0     0 1   my $self = shift;
104 0 0         $self->{plain} = shift if @_;
105 0           $self->{plain};
106             }
107              
108             sub plain_delimiter {
109 0     0 1   my $self = shift;
110 0 0         $self->{plain_delimiter} = shift if @_;
111 0           $self->{plain_delimiter};
112             }
113              
114             sub unsign {
115 0     0 1   my $self = shift;
116 0 0         $self->{unsign} = shift if @_;
117 0           $self->{unsign};
118             }
119              
120             sub _signature {
121 0     0     my ( $self, $type ) = @_;
122 0 0         defined( my $signature = $self->$type ) or return;
123 0           my $delimiter_method = $type . '_delimiter';
124 0           $self->$delimiter_method . $signature;
125             }
126              
127             sub handler_multipart_alternative { # add trailer to all parts
128 0     0 1   my ( $self, $entity ) = @_;
129 0           $self->append($_) for my @parts = $entity->parts;
130 0           @parts;
131             }
132              
133             sub handler_multipart_mixed { # append trailer as separate part
134 0     0 1   my ( $self, $entity ) = @_;
135 0 0 0       require Encode and Encode->import('encode_utf8')
136             unless defined &encode_utf8;
137 0 0         $entity->add_part(
    0          
138             my $e = MIME::Entity->build(
139             Top => 0,
140             Charset => 'UTF-8',
141             Encoding => '-SUGGEST',
142             Type => grep( lc $_->mime_type eq 'text/html', $entity->parts )
143             ? ( 'text/html', Data => encode_utf8( $self->_signature('html') ) )
144             : grep( lc $_->mime_type eq 'text/enriched', $entity->parts )
145             ? (
146             'text/enriched',
147             Data => encode_utf8( $self->_signature('enriched') )
148             )
149             : (
150             'text/plain', Data => encode_utf8( $self->_signature('plain') )
151             )
152             )
153             );
154             }
155              
156             sub handler_multipart_related { # add trailer to the first part
157 0     0 1   my ( $self, $entity ) = @_;
158 0           $self->append( ( $entity->parts )[0] );
159             }
160              
161             sub handler_multipart_signed {
162 0     0 1   my ( $self, $entity ) = @_;
163 0 0         return unless $self->unsign;
164              
165             { # Inspired by MIME::Entity->make_singlepart:
166              
167 0           my ($part) = my @parts = $entity->parts;
  0            
168 0 0 0       croak 'Invalid multipart/signed containing '
169             . @parts . ' part'
170             . ( @parts != 1 && 's' )
171             if @parts != 2;
172              
173             # Get rid of all our existing content info:
174 0   0       /^content-/i and $entity->head->delete($_) for $entity->head->tags;
175              
176             # Populate ourselves with any content info from the part:
177 0           for my $tag ( grep /^content-/i, $part->head->tags ) {
178 0           $entity->head->add( $tag, $_ ) for $part->head->get($tag);
179             }
180              
181             # Save reconstructed header, replace our guts, and restore header:
182 0           my $new_head = $entity->head;
183 0           %$entity = %$part; # shallow copy is ok!
184 0           $entity->head($new_head);
185             }
186              
187 0           $self->append($entity);
188             }
189              
190             sub handler_text_enriched { # append trailer
191 0     0 1   my ( $self, $entity ) = @_;
192 0           _replace_body( $entity,
193             _decoded_body($entity) . $self->_signature('enriched') );
194             }
195              
196             sub handler_text_html { # append trailer to
197 0     0 1   my ( $self, $entity ) = @_;
198 0           my $body = _decoded_body($entity);
199 0           require HTML::Parser;
200 0           my $new_body;
201             my $parser = HTML::Parser->new(
202             end_h => [
203             sub {
204 0     0     my ( $text, $tagname ) = @_;
205 0 0         $new_body .= $self->_signature('html') if lc $tagname eq 'body';
206 0           $new_body .= $text;
207             },
208             'text,tagname'
209             ],
210 0     0     default_h => [ sub { $new_body .= shift }, 'text' ],
  0            
211             );
212 0           $parser->parse($body);
213 0           _replace_body( $entity, $new_body );
214             }
215              
216             sub handler_text_plain { # append trailer
217 0     0 1   my ( $self, $entity ) = @_;
218 0           _replace_body( $entity,
219             _decoded_body($entity) . $self->_signature('plain') );
220             }
221              
222             sub new {
223 0     0 1   my $package = shift;
224 0 0         $package = ref $package if length ref $package;
225 0 0         croak 'Invalid number of arguments to ->new' if @_ % 2;
226 0           my $self = bless {
227             enriched_delimiter => "\n\n-- \n",
228             html_delimiter => '
',
229             plain_delimiter => "\n\n-- \n",
230             },
231             $package;
232 0           while ( my $method = shift ) {
233 0           $self->$method(shift);
234             }
235 0           $self;
236             }
237              
238             sub entity {
239 0     0 1   my $self = shift;
240 0 0         $self->{entity} = shift if @_;
241 0           $self->{entity};
242             }
243              
244             sub parser {
245 0     0 1   my $self = shift;
246 0 0         if (@_) {
    0          
247 0           $self->{parser} = shift;
248             }
249             elsif ( !$self->{parser} ) {
250 0           for ( $self->{parser} ) {
251 0           $_ = MIME::Parser->new;
252 0           $_->tmp_to_core(1);
253 0           $_->output_to_core(1);
254             }
255             }
256 0           $self->{parser};
257             }
258              
259             sub parse {
260 0     0 1   my $self = shift;
261 0           $self->{entity} = $self->parser->parse(@_);
262             }
263              
264             sub parse_data {
265 0     0 1   my $self = shift;
266 0           $self->{entity} = $self->parser->parse_data(@_);
267             }
268              
269             sub parse_open {
270 0     0 1   my $self = shift;
271 0           $self->{entity} = $self->parser->parse_open(@_);
272             }
273              
274             sub parse_two {
275 0     0 1   my $self = shift;
276 0           $self->{entity} = $self->parser->parse_two(@_);
277             }
278              
279             sub append {
280 0     0 1   my $self = shift;
281             my $entity = shift || $self->{entity}
282 0 0 0       or croak( 'You must first hand in an e-mail message'
283             . ' before trying to append a signature.' );
284 0           ( my $handler_method =
285             'handler_' . lc( my $mime_type = $entity->mime_type ) ) =~ y!/!_!;
286 0 0 0       $self->can($handler_method) and $self->$handler_method($entity)
287             or croak "Cannot handle $mime_type messages";
288 0           $entity;
289             }
290              
291             my $version_pod = <<'=cut';
292              
293             =head1 NAME
294              
295             MIME::Signature - appends signature to mail messages
296              
297             =head1 VERSION
298              
299             Version 0.16
300              
301             =cut
302              
303             # borrowed from tinita, see https://www.perlmonks.org/?node_id=562735
304             sub __test_version {
305 0     0     my $v = __PACKAGE__->VERSION;
306 0 0         return 1 if $version_pod =~ m/^Version \Q$v\E$/m;
307 0           return;
308             }
309              
310             __END__