File Coverage

blib/lib/Email/MIME/Encode.pm
Criterion Covered Total %
statement 92 92 100.0
branch 29 40 72.5
condition 15 19 78.9
subroutine 16 16 100.0
pod 0 4 0.0
total 152 171 88.8


line stmt bran cond sub pod time code
1 20     20   149305 use v5.12.0;
  20         98  
2 20     20   130 use warnings;
  20         34  
  20         1436  
3             package Email::MIME::Encode 1.954;
4             # ABSTRACT: a private helper for MIME header encoding
5              
6 20     20   115 use Carp ();
  20         85  
  20         385  
7 20     20   776 use Encode ();
  20         25042  
  20         467  
8 20     20   10013 use Email::MIME::Header;
  20         129  
  20         854  
9 20     20   8295 use MIME::Base64();
  20         13735  
  20         602  
10 20     20   117 use Module::Runtime ();
  20         39  
  20         399  
11 20     20   83 use Scalar::Util;
  20         31  
  20         35149  
12              
13             our @CARP_NOT;
14              
15             my %no_mime_headers = map { $_ => undef } qw(date message-id in-reply-to references downgraded-message-id downgraded-in-reply-to downgraded-references);
16              
17             sub maybe_mime_encode_header {
18 44     44 0 329002 my ($header, $val, $charset) = @_;
19              
20 44         91 $header = lc $header;
21              
22 44         80 my $header_name_length = length($header) + length(": ");
23              
24 44 100 100     152 if (Scalar::Util::blessed($val) && $val->can("as_mime_string")) {
25 2         9 return $val->as_mime_string({
26             charset => $charset,
27             header_name_length => $header_name_length,
28             });
29             }
30              
31             return _object_encode($val, $charset, $header_name_length, $Email::MIME::Header::header_to_class_map{$header})
32 42 100       155 if exists $Email::MIME::Header::header_to_class_map{$header};
33              
34 20         35 my $min_wrap_length = 78 - $header_name_length + 1;
35              
36 20 100 66     37 return $val
37             unless _needs_mime_encode($val) || $val =~ /[^\s]{$min_wrap_length,}/;
38              
39             return $val
40 11 50       31 if exists $no_mime_headers{$header};
41              
42 11         24 return mime_encode($val, $charset, $header_name_length);
43             }
44              
45             sub _needs_mime_encode {
46 100     100   157 my ($val) = @_;
47 100   100     1286 return defined $val && $val =~ /(?:\P{ASCII}|=\?|[^\s]{79,}|^\s+|\s+$)/s;
48             }
49              
50             sub _needs_mime_encode_addr {
51 80     80   474 my ($val) = @_;
52 80   66     131 return _needs_mime_encode($val) || ( defined $val && $val =~ /[:;,]/ );
53             }
54              
55             sub _object_encode {
56 22     22   52 my ($val, $charset, $header_name_length, $class) = @_;
57              
58 22         83 local @CARP_NOT = qw(Email::MIME Email::MIME::Header);
59              
60             {
61 22         37 local $@;
  22         36  
62 22 50       43 Carp::croak("Cannot load package '$class': $@") unless eval { Module::Runtime::require_module($class) };
  22         63  
63             }
64              
65 22 50       977 Carp::croak("Class '$class' does not have method 'from_string'") unless $class->can('from_string');
66              
67 22 100       108 my $object = $class->from_string(ref $val eq 'ARRAY' ? @{$val} : $val);
  2         5  
68              
69 22 50       95 Carp::croak("Object from class '$class' does not have method 'as_mime_string'") unless $object->can('as_mime_string');
70              
71 22         132 return $object->as_mime_string({
72             charset => $charset,
73             header_name_length => $header_name_length,
74             });
75             }
76              
77             # XXX this is copied directly out of Courriel::Header
78             # eventually, this should be extracted out into something that could be shared
79             sub mime_encode {
80 36     36 0 177 my ($text, $charset, $header_name_length) = @_;
81              
82 36   100     158 $header_name_length //= 0;
83 36   100     68 $charset //= 'UTF-8';
84              
85 36         113 my $enc_obj = Encode::find_encoding($charset);
86              
87 36         1245 my $head = '=?' . $enc_obj->mime_name() . '?B?';
88 36         337 my $tail = '?=';
89              
90 36         59 my $mime_length = length($head) + length($tail);
91              
92             # This code is copied from Mail::Message::Field::Full in the Mail-Box
93             # distro.
94 36         97 my $real_length = int( ( 75 - $mime_length ) / 4 ) * 3;
95 36         89 my $first_length = int( ( 75 - $header_name_length - $mime_length ) / 4 ) * 3;
96              
97 36         47 my @result;
98 36         69 my $chunk = q{};
99 36         42 my $first_processed = 0;
100 36         164 while ( length( my $chr = substr( $text, 0, 1, '' ) ) ) {
101 832         1835 my $chr = $enc_obj->encode( $chr, 0 );
102              
103 832 100       1805 if ( length($chunk) + length($chr) > ( $first_processed ? $real_length : $first_length ) ) {
    100          
104 8 50       26 if ( length($chunk) > 0 ) {
105 8         46 push @result, $head . MIME::Base64::encode_base64( $chunk, q{} ) . $tail;
106 8         18 $chunk = q{};
107             }
108 8 50       26 $first_processed = 1
109             unless $first_processed;
110             }
111              
112 832         2079 $chunk .= $chr;
113             }
114              
115 36 50       166 push @result, $head . MIME::Base64::encode_base64( $chunk, q{} ) . $tail
116             if length $chunk;
117              
118 36         255 return join q{ }, @result;
119             }
120              
121             sub maybe_mime_decode_header {
122 37     37 0 72 my ($header, $val) = @_;
123              
124 37         60 $header = lc $header;
125              
126             return _object_decode($val, $Email::MIME::Header::header_to_class_map{$header})
127 37 100       118 if exists $Email::MIME::Header::header_to_class_map{$header};
128              
129             return $val
130 16 100       31 if exists $no_mime_headers{$header};
131              
132 15 100       40 return $val
133             unless $val =~ /=\?/;
134              
135 14         27 return mime_decode($val);
136             }
137              
138             sub _object_decode {
139 21     21   41 my ($string, $class) = @_;
140              
141 21         67 local @CARP_NOT = qw(Email::MIME Email::MIME::Header);
142              
143             {
144 21         859 local $@;
  21         37  
145 21 50       38 Carp::croak("Cannot load package '$class': $@") unless eval { Module::Runtime::require_module($class) };
  21         53  
146             }
147              
148 21 50       1374 Carp::croak("Class '$class' does not have method 'from_mime_string'") unless $class->can('from_mime_string');
149              
150 21         68 my $object = $class->from_mime_string($string);
151              
152 21 50       132 Carp::croak("Object from class '$class' does not have method 'as_string'") unless $object->can('as_string');
153              
154 21         48 return $object->as_string();
155             }
156              
157             sub mime_decode {
158 64     64 0 241 my ($text) = @_;
159 64 50       109 return undef unless defined $text;
160              
161             # The eval is to cope with unknown encodings, like Latin-62, or other
162             # nonsense that gets put in there by spammers and weirdos
163             # -- rjbs, 2014-12-04
164 64         72 local $@;
165 64         89 my $result = eval { Encode::decode("MIME-Header", $text) };
  64         341  
166 64   33     22804 return $result // $text;
167             }
168              
169             1;
170              
171             __END__