File Coverage

blib/lib/Email/MIME/RFC2047/Encoder.pm
Criterion Covered Total %
statement 80 82 97.5
branch 44 52 84.6
condition 3 3 100.0
subroutine 9 9 100.0
pod 3 3 100.0
total 139 149 93.2


line stmt bran cond sub pod time code
1             package Email::MIME::RFC2047::Encoder;
2             $Email::MIME::RFC2047::Encoder::VERSION = '0.96';
3 5     5   77754 use strict;
  5         17  
  5         305  
4 5     5   38 use warnings;
  5         16  
  5         187  
5              
6             # ABSTRACT: Encoding of non-ASCII MIME headers
7              
8 5     5   723 use Encode ();
  5         13087  
  5         265  
9 5     5   635 use MIME::Base64 ();
  5         786  
  5         5818  
10              
11             my $rfc_specials = '()<>\[\]:;\@\\,."';
12              
13             sub new {
14 11     11 1 12161 my $package = shift;
15 11 50       94 my $options = ref($_[0]) ? $_[0] : { @_ };
16              
17 11         61 my ($encoding, $method) = ($options->{encoding}, $options->{method});
18              
19 11 100       58 if (!defined($encoding)) {
20 6         23 $encoding = 'utf-8';
21 6 50       37 $method = 'Q' if !defined($method);
22             }
23             else {
24 5 50       28 $method = 'B' if !defined($method);
25             }
26              
27 11 50       86 my $encoder = Encode::find_encoding($encoding)
28             or die("encoding '$encoding' not found");
29              
30 11         1395 my $self = {
31             encoding => $encoding,
32             encoder => $encoder,
33             method => uc($method),
34             };
35              
36 11         67 return bless($self, $package);
37             }
38              
39             sub encode_text {
40 13     13 1 11938 my ($self, $string) = @_;
41              
42 13         56 return $self->_encode('text', $string);
43             }
44              
45             sub encode_phrase {
46 28     28 1 10332 my ($self, $string) = @_;
47              
48 28         113 return $self->_encode('phrase', $string);
49             }
50              
51             sub _encode {
52 41     41   126 my ($self, $mode, $string) = @_;
53              
54 41         109 my $encoder = $self->{encoder};
55 41         96 my $result = '';
56              
57             # $string is split on whitespace. Each $word is categorized into
58             # 'mime', 'quoted' or 'text'. The intermediate result of the conversion of
59             # consecutive words of the same types is accumulated in $buffer.
60             # The type of the buffer is tracked in $buffer_type.
61             # The method _finish_buffer is called to finish the encoding of the
62             # buffered content and append to the result.
63 41         91 my $buffer = '';
64 41         77 my $buffer_type;
65              
66 41         321 for my $word (split(/\s+/, $string)) {
67 109 100       328 next if $word eq ''; # ignore leading white space
68              
69 107         432 $word =~ s/[\x00-\x1f\x7f]//g; # better remove control chars
70              
71 107         197 my $word_type;
72              
73 107 100       559 if ($word =~ /[\x80-\x{10ffff}]|(^=\?.*\?=\z)/s) {
    100          
74             # also encode any word that starts with '=?' and ends with '?='
75 54         139 $word_type = 'mime';
76             }
77             elsif ($mode eq 'phrase') {
78 40         91 $word_type = 'quoted';
79             }
80             else {
81 13         30 $word_type = 'text';
82             }
83              
84 107 100 100     509 $self->_finish_buffer(\$result, $buffer_type, \$buffer)
85             if $buffer ne '' && $buffer_type ne $word_type;
86 107         228 $buffer_type = $word_type;
87              
88 107 100       354 if ($word_type eq 'text') {
    100          
89 13 100       47 $result .= ' ' if $result ne '';
90 13         48 $result .= $word;
91             }
92             elsif ($word_type eq 'quoted') {
93 40 100       122 $buffer .= ' ' if $buffer ne '';
94 40         108 $buffer .= $word;
95             }
96             else {
97 54         165 my $max_len = 75 - 7 - length($self->{encoding});
98 54 50       174 $max_len = 3 * ($max_len >> 2) if $self->{method} eq 'B';
99              
100 54         112 my @chars;
101 54 100       163 push(@chars, ' ') if $buffer ne '';
102 54         223 push(@chars, split(//, $word));
103              
104 54         171 for my $char (@chars) {
105 224         394 my $chunk;
106              
107 224 50       1220 if ($self->{method} eq 'B') {
    100          
    100          
    100          
108 0         0 $chunk = $encoder->encode($char);
109             }
110             elsif ($char =~ /[()<>@,;:\\".\[\]=?_]/) {
111             # special character
112 16         56 $chunk = sprintf('=%02x', ord($char));
113             }
114             elsif ($char =~ /[\x80-\x{10ffff}]/) {
115             # non-ASCII character
116              
117 100         545 my $enc_char = $encoder->encode($char);
118 100         661 $chunk = '';
119              
120 100         365 for my $byte (unpack('C*', $enc_char)) {
121 196         937 $chunk .= sprintf('=%02x', $byte);
122             }
123             }
124             elsif ($char eq ' ') {
125 28         100 $chunk = '_';
126             }
127             else {
128 80         176 $chunk = $char;
129             }
130              
131 224 100       638 if (length($buffer) + length($chunk) <= $max_len) {
132 218         632 $buffer .= $chunk;
133             }
134             else {
135 6         30 $self->_finish_buffer(\$result, 'mime', \$buffer);
136 6         24 $buffer = $chunk;
137             }
138             }
139             }
140             }
141              
142 41 100       241 $self->_finish_buffer(\$result, $buffer_type, \$buffer)
143             if $buffer ne '';
144              
145 41         192 return $result;
146             }
147              
148             sub _finish_buffer {
149 59     59   183 my ($self, $result, $buffer_type, $buffer) = @_;
150              
151 59 100       192 $$result .= ' ' if $$result ne '';
152              
153 59 100       211 if ($buffer_type eq 'quoted') {
    50          
154 27 100       300 if ($$buffer =~ /[$rfc_specials]/) {
155             # use quoted string if buffer contains special chars
156 9         45 $$buffer =~ s/[\\"]/\\$&/g;
157              
158 9         40 $$result .= qq("$$buffer");
159             }
160             else {
161 18         68 $$result .= $$buffer;
162             }
163             }
164             elsif ($buffer_type eq 'mime') {
165 32         165 $$result .= "=?$self->{encoding}?$self->{method}?";
166              
167 32 50       111 if ($self->{method} eq 'B') {
168 0         0 $$result .= MIME::Base64::encode_base64($$buffer, '');
169             }
170             else {
171 32         116 $$result .= $$buffer;
172             }
173              
174 32         85 $$result .= '?=';
175             }
176              
177 59         156 $$buffer = '';
178              
179 59         142 return;
180             }
181              
182             1;
183              
184             __END__