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.95';
3 5     5   21989 use strict;
  5         6  
  5         132  
4 5     5   17 use warnings;
  5         6  
  5         121  
5              
6             # ABSTRACT: Encoding of non-ASCII MIME headers
7              
8 5     5   486 use Encode ();
  5         6999  
  5         85  
9 5     5   406 use MIME::Base64 ();
  5         441  
  5         4404  
10              
11             my $rfc_specials = '()<>\[\]:;\@\\,."';
12              
13             sub new {
14 11     11 1 5684 my $package = shift;
15 11 50       47 my $options = ref($_[0]) ? $_[0] : { @_ };
16              
17 11         21 my ($encoding, $method) = ($options->{encoding}, $options->{method});
18              
19 11 100       23 if (!defined($encoding)) {
20 6         7 $encoding = 'utf-8';
21 6 50       19 $method = 'Q' if !defined($method);
22             }
23             else {
24 5 50       19 $method = 'B' if !defined($method);
25             }
26              
27 11 50       46 my $encoder = Encode::find_encoding($encoding)
28             or die("encoding '$encoding' not found");
29              
30 11         649 my $self = {
31             encoding => $encoding,
32             encoder => $encoder,
33             method => uc($method),
34             };
35              
36 11         35 return bless($self, $package);
37             }
38              
39             sub encode_text {
40 13     13 1 4652 my ($self, $string) = @_;
41              
42 13         25 return $self->_encode('text', $string);
43             }
44              
45             sub encode_phrase {
46 28     28 1 4529 my ($self, $string) = @_;
47              
48 28         50 return $self->_encode('phrase', $string);
49             }
50              
51             sub _encode {
52 41     41   44 my ($self, $mode, $string) = @_;
53              
54 41         51 my $encoder = $self->{encoder};
55 41         35 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         33 my $buffer = '';
64 41         38 my $buffer_type;
65              
66 41         165 for my $word (split(/\s+/, $string)) {
67 109 100       164 next if $word eq ''; # ignore leading white space
68              
69 107         126 $word =~ s/[\x00-\x1f\x7f]//g; # better remove control chars
70              
71 107         64 my $word_type;
72              
73 107 100       345 if ($word =~ /[\x80-\x{10ffff}]|(^=\?.*\?=\z)/s) {
    100          
74             # also encode any word that starts with '=?' and ends with '?='
75 54         44 $word_type = 'mime';
76             }
77             elsif ($mode eq 'phrase') {
78 40         36 $word_type = 'quoted';
79             }
80             else {
81 13         11 $word_type = 'text';
82             }
83              
84 107 100 100     291 $self->_finish_buffer(\$result, $buffer_type, \$buffer)
85             if $buffer ne '' && $buffer_type ne $word_type;
86 107         79 $buffer_type = $word_type;
87              
88 107 100       168 if ($word_type eq 'text') {
    100          
89 13 100       18 $result .= ' ' if $result ne '';
90 13         20 $result .= $word;
91             }
92             elsif ($word_type eq 'quoted') {
93 40 100       57 $buffer .= ' ' if $buffer ne '';
94 40         58 $buffer .= $word;
95             }
96             else {
97 54         52 my $max_len = 75 - 7 - length($self->{encoding});
98 54 50       81 $max_len = 3 * ($max_len >> 2) if $self->{method} eq 'B';
99              
100 54         39 my @chars;
101 54 100       73 push(@chars, ' ') if $buffer ne '';
102 54         97 push(@chars, split(//, $word));
103              
104 54         60 for my $char (@chars) {
105 224         128 my $chunk;
106              
107 224 50       573 if ($self->{method} eq 'B') {
    100          
    100          
    100          
108 0         0 $chunk = $encoder->encode($char);
109             }
110             elsif ($char =~ /[()<>@,;:\\".\[\]=?_]/) {
111             # special character
112 16         21 $chunk = sprintf('=%02x', ord($char));
113             }
114             elsif ($char =~ /[\x80-\x{10ffff}]/) {
115             # non-ASCII character
116              
117 100         205 my $enc_char = $encoder->encode($char);
118 100         75 $chunk = '';
119              
120 100         134 for my $byte (unpack('C*', $enc_char)) {
121 196         261 $chunk .= sprintf('=%02x', $byte);
122             }
123             }
124             elsif ($char eq ' ') {
125 28         24 $chunk = '_';
126             }
127             else {
128 80         63 $chunk = $char;
129             }
130              
131 224 100       280 if (length($buffer) + length($chunk) <= $max_len) {
132 218         238 $buffer .= $chunk;
133             }
134             else {
135 6         11 $self->_finish_buffer(\$result, 'mime', \$buffer);
136 6         11 $buffer = $chunk;
137             }
138             }
139             }
140             }
141              
142 41 100       115 $self->_finish_buffer(\$result, $buffer_type, \$buffer)
143             if $buffer ne '';
144              
145 41         93 return $result;
146             }
147              
148             sub _finish_buffer {
149 59     59   65 my ($self, $result, $buffer_type, $buffer) = @_;
150              
151 59 100       95 $$result .= ' ' if $$result ne '';
152              
153 59 100       92 if ($buffer_type eq 'quoted') {
    50          
154 27 100       147 if ($$buffer =~ /[$rfc_specials]/) {
155             # use quoted string if buffer contains special chars
156 9         23 $$buffer =~ s/[\\"]/\\$&/g;
157              
158 9         20 $$result .= qq("$$buffer");
159             }
160             else {
161 18         29 $$result .= $$buffer;
162             }
163             }
164             elsif ($buffer_type eq 'mime') {
165 32         66 $$result .= "=?$self->{encoding}?$self->{method}?";
166              
167 32 50       45 if ($self->{method} eq 'B') {
168 0         0 $$result .= MIME::Base64::encode_base64($$buffer, '');
169             }
170             else {
171 32         34 $$result .= $$buffer;
172             }
173              
174 32         31 $$result .= '?=';
175             }
176              
177 59         58 $$buffer = '';
178              
179 59         59 return;
180             }
181              
182             1;
183              
184             __END__