File Coverage

blib/lib/MIME/Signature.pm
Criterion Covered Total %
statement 14 138 10.1
branch 0 68 0.0
condition 0 23 0.0
subroutine 5 34 14.7
pod 22 22 100.0
total 41 285 14.3


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