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