File Coverage

blib/lib/MIME/EcoEncode.pm
Criterion Covered Total %
statement 357 428 83.4
branch 165 220 75.0
condition 24 31 77.4
subroutine 12 13 92.3
pod 0 5 0.0
total 558 697 80.0


line stmt bran cond sub pod time code
1             # Copyright (C) 2011-2013 MURATA Yasuhisa
2             # This program is free software; you can redistribute it and/or
3             # modify it under the same terms as Perl itself.
4              
5             package MIME::EcoEncode;
6              
7 1     1   23841 use 5.008005;
  1         4  
  1         37  
8 1     1   5 use strict;
  1         2  
  1         30  
9 1     1   18 use warnings;
  1         9  
  1         91  
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14             our @EXPORT_OK = qw($VERSION);
15             our @EXPORT = qw(mime_eco mime_deco);
16             our $VERSION = '0.95';
17              
18 1     1   890 use MIME::Base64;
  1         792  
  1         66  
19 1     1   857 use MIME::QuotedPrint;
  1         261  
  1         57  
20              
21 1     1   7 use constant TAIL => '?=';
  1         2  
  1         5422  
22              
23             our $LF; # line feed
24             our $BPL; # bytes per line
25             our $MODE; # unstructured : 0, structured : 1, auto : 2
26              
27             our $HEAD; # head string
28             our $HTL; # head + tail length
29             our $UTF8;
30             our $REG_W;
31             our $ADD_EW;
32             our $REG_RP;
33              
34             sub mime_eco {
35 52     52 0 29189 my $str = shift;
36              
37 52 50       168 return '' unless defined $str;
38 52 50       112 return '' if $str eq '';
39              
40 52         207 my ($trailing_crlf) = ($str =~ /(\x0d?\x0a|\x0d)$/);
41 52         101 $str =~ tr/\n\r//d;
42 52 100       173 if ($str =~ /^\s*$/) {
43 2 50       10 return $trailing_crlf ? $str . $trailing_crlf : $str;
44             }
45              
46 50   100     115 my $charset = shift || 'UTF-8';
47              
48             # invalid option
49             return undef
50 50 50       230 unless $charset =~ /^([-0-9A-Za-z_]+)(?:\*[^\?]*)?(\?[QB])?$/i;
51              
52 50         111 my $cs = lc($1);
53 50 100       132 $charset .= '?B' unless defined $2;
54              
55 50   100     193 our $LF = shift || "\n"; # line feed
56 50   100     156 our $BPL = shift || 76; # bytes per line
57 50         59 our $MODE = shift;
58 50 50       105 $MODE = 2 unless defined $MODE;
59              
60 50         53 my $lss = shift;
61 50 100       92 $lss = 25 unless defined $lss;
62              
63 50         46 our $HEAD; # head string
64 50         42 our $HTL; # head + tail length
65 50         51 our $UTF8 = 1;
66 50         187 our $REG_W = qr/(.)/;
67 50         86 our $ADD_EW;
68 50         81 our $REG_RP;
69              
70 50         46 my $jp = 0;
71              
72 50         51 my $pos;
73             my $np;
74 0         0 my $refsub;
75 0         0 my $reg_rp1;
76              
77 0         0 my ($w1, $w1_len, $w2);
78 0         0 my ($sps, $sps_len);
79 50         63 my $sp1 = '';
80 50         61 my $sp1_bak;
81             my $result;
82 0         0 my $ascii;
83 0         0 my $tmp;
84 50         55 my $count = 0;
85              
86 50 100       141 my $q_enc = ($charset =~ /Q$/i) ? 1 : 0;
87 50         77 $HEAD = '=?' . $charset . '?';
88 50         73 $HTL = length($HEAD) + 2;
89              
90 50 100       102 if ($cs ne 'utf-8') {
91 27         30 $UTF8 = 0;
92 27 100       83 if ($cs eq 'iso-2022-jp') {
    50          
    100          
    100          
    100          
93 21         30 $jp = 1;
94             }
95             elsif ($cs eq 'shift_jis') {
96             # range of 2nd byte : [\x40-\x7e\x80-\xfc]
97 0         0 $REG_W = qr/([\x81-\x9f\xe0-\xfc]?.)/;
98             }
99             elsif ($cs eq 'gb2312') { # Simplified Chinese
100             # range of 2nd byte : [\xa1-\xfe]
101 1         4 $REG_W = qr/([\xa1-\xfe]?.)/;
102             }
103             elsif ($cs eq 'euc-kr') { # Korean
104             # range of 2nd byte : [\xa1-\xfe]
105 1         4 $REG_W = qr/([\xa1-\xfe]?.)/;
106             }
107             elsif ($cs eq 'big5') { # Traditional Chinese
108             # range of 2nd byte : [\x40-\x7e\xa1-\xfe]
109 1         3 $REG_W = qr/([\x81-\xfe]?.)/;
110             }
111             else { # Single Byte (Latin, Cyrillic, ...)
112             ;
113             }
114             }
115              
116 50         143 $str =~ /(\s*)(\S+)/gc;
117 50         116 ($sps, $w2) = ($1, $2);
118              
119 50 100       122 if ($w2 =~ /[^\x21-\x7e]/) {
120 8         11 $ascii = 0;
121 8         10 $sps_len = length($sps);
122 8 100       16 if ($sps_len > $lss) {
123 2         7 $result = substr($sps, 0, $lss);
124 2         4 $w1 = substr($sps, $lss) . $w2;
125 2         4 $pos = $lss;
126             }
127             else {
128 6         10 $result = $sps;
129 6         7 $w1 = $w2;
130 6         9 $pos = $sps_len;
131             }
132             }
133             else {
134 42         43 $ascii = 1;
135 42         42 $result = '';
136 42         45 $w1 = "$sps$w2";
137 42         64 $pos = 0;
138             }
139              
140 50 50       106 if ($MODE == 2) {
141 50 100       172 $MODE = ($w1 =~ /^(?:Subject:|Comments:)$/i) ? 0 : 1;
142             }
143              
144 50 100       83 if ($jp) {
145 21 50       35 if ($q_enc) {
146 0         0 require MIME::EcoEncode::JP_Q;
147 0         0 $MIME::EcoEncode::JP_Q::HEAD = $HEAD;
148 0         0 $MIME::EcoEncode::JP_Q::HTL = $HTL;
149 0         0 $MIME::EcoEncode::JP_Q::LF = $LF;
150 0         0 $MIME::EcoEncode::JP_Q::BPL = $BPL;
151 0         0 $MIME::EcoEncode::JP_Q::MODE = $MODE;
152 0 0       0 if ($MODE == 0) {
153 0         0 $refsub = \&MIME::EcoEncode::JP_Q::add_ew_jp_q;
154             }
155             else {
156 0         0 $refsub = \&add_ew_sh;
157 0         0 $reg_rp1 = qr/\e\(B[\x21-\x7e]*\)\,?$/;
158 0         0 $REG_RP = qr/\e\(B[\x21-\x7e]*?(\){1,3}\,?)$/;
159 0         0 $ADD_EW = \&MIME::EcoEncode::JP_Q::add_ew_jp_q;
160             }
161             }
162             else {
163 21         920 require MIME::EcoEncode::JP_B;
164 21         35 $MIME::EcoEncode::JP_B::HEAD = $HEAD;
165 21         25 $MIME::EcoEncode::JP_B::HTL = $HTL;
166 21         26 $MIME::EcoEncode::JP_B::LF = $LF;
167 21         24 $MIME::EcoEncode::JP_B::BPL = $BPL;
168 21 100       38 if ($MODE == 0) {
169 10         19 $refsub = \&MIME::EcoEncode::JP_B::add_ew_jp_b;
170             }
171             else {
172 11         18 $refsub = \&add_ew_sh;
173 11         35 $reg_rp1 = qr/\e\(B[\x21-\x7e]*\)\,?$/;
174 11         26 $REG_RP = qr/\e\(B[\x21-\x7e]*?(\){1,3}\,?)$/;
175 11         27 $ADD_EW = \&MIME::EcoEncode::JP_B::add_ew_jp_b;
176             }
177             }
178             }
179             else {
180 29 100       49 if ($MODE == 0) {
181 16 100       48 $refsub = $q_enc ? \&add_ew_q : \&add_ew_b;
182             }
183             else {
184 13         27 $refsub = \&add_ew_sh;
185 13         34 $reg_rp1 = qr/\)\,?$/;
186 13         39 $REG_RP = qr/(\){1,3}\,?)$/;
187 13 100       35 $ADD_EW = $q_enc ? \&add_ew_q : \&add_ew_b;
188             }
189             }
190              
191 50         180 while ($str =~ /(\s*)(\S+)/gc) {
192 142         305 ($sps, $w2) = ($1, $2);
193 142 100       336 if ($w2 =~ /[^\x21-\x7e]/) {
194 73         73 $sps_len = length($sps);
195 73 100       111 if ($ascii) { # "ASCII \s+ non-ASCII"
196 50         50 $sp1_bak = $sp1;
197 50         70 $sp1 = chop($sps);
198 50 100       88 $w1 .= $sps if $sps_len > $lss;
199 50         49 $w1_len = length($w1);
200 50 100       87 if ($count == 0) {
201 15         22 $result = $w1;
202 15         18 $pos = $w1_len;
203             }
204             else {
205 35 100 100     124 if (($count > 1) and ($pos + $w1_len + 1 > $BPL)) {
206 4         10 $result .= "$LF$sp1_bak$w1";
207 4         7 $pos = $w1_len + 1;
208             }
209             else {
210 31         47 $result .= "$sp1_bak$w1";
211 31         142 $pos += $w1_len + 1;
212             }
213             }
214 50 100       89 if ($sps_len <= $lss) {
215 48 100       115 if ($pos >= $BPL) {
    50          
216 1         2 $result .= $LF . $sps;
217 1         2 $pos = $sps_len - 1;
218             }
219             elsif ($pos + $sps_len - 1 > $BPL) {
220 0         0 $result .= substr($sps, 0, $BPL - $pos) . $LF
221             . substr($sps, $BPL - $pos);
222 0         0 $pos += $sps_len - $BPL - 1;
223             }
224             else {
225 47         45 $result .= $sps;
226 47         59 $pos += $sps_len - 1;
227             }
228             }
229 50         54 $w1 = $w2;
230             }
231             else { # "non-ASCII \s+ non-ASCII"
232 23 100 66     76 if (($MODE == 1) and ($sps_len <= $lss)) {
233 18 100 100     129 if ($w1 =~ /$reg_rp1/ or $w2 =~ /^\(/) {
234 12 100       19 if ($count == 0) {
235 2         6 $result .= &$refsub($w1, $pos, \$np, 0);
236             }
237             else {
238 10         26 $tmp = &$refsub($w1, 1 + $pos, \$np, 0);
239 10 100       29 $result .= ($tmp =~ s/^ /$sp1/) ?
240             "$LF$tmp" : "$sp1$tmp";
241             }
242 12         16 $pos = $np;
243 12         16 $sp1 = chop($sps);
244 12 50       20 if ($pos + $sps_len - 1 > $BPL) {
245 0         0 $result .= substr($sps, 0, $BPL - $pos) . $LF
246             . substr($sps, $BPL - $pos);
247 0         0 $pos += $sps_len - $BPL - 1;
248             }
249             else {
250 12         9 $result .= $sps;
251 12         15 $pos += $sps_len - 1;
252             }
253 12         14 $w1 = $w2;
254             }
255             else {
256 6         12 $w1 .= "$sps$w2";
257             }
258             }
259             else {
260 5         11 $w1 .= "$sps$w2";
261             }
262             }
263 73         78 $ascii = 0;
264             }
265             else { # "ASCII \s+ ASCII" or "non-ASCII \s+ ASCII"
266 69         74 $w1_len = length($w1);
267 69 100       105 if ($ascii) { # "ASCII \s+ ASCII"
268 40 100       63 if ($count == 0) {
269 21         24 $result = $w1;
270 21         27 $pos = $w1_len;
271             }
272             else {
273 19 50 66     63 if (($count > 1) and ($pos + $w1_len + 1 > $BPL)) {
274 0         0 $result .= "$LF$sp1$w1";
275 0         0 $pos = $w1_len + 1;
276             }
277             else {
278 19         31 $result .= "$sp1$w1";
279 19         25 $pos += $w1_len + 1;
280             }
281             }
282             }
283             else { # "non-ASCII \s+ ASCII"
284 29 50       45 if ($count == 0) {
285 0         0 $result .= &$refsub($w1, $pos, \$np, 0);
286 0         0 $pos = $np;
287             }
288             else {
289 29         80 $tmp = &$refsub($w1, 1 + $pos, \$np, 0);
290 29 100       116 $result .= ($tmp =~ s/^ /$sp1/) ? "$LF$tmp" : "$sp1$tmp";
291 29         39 $pos = $np;
292             }
293             }
294 69         69 $sps_len = length($sps);
295 69 100       154 if ($pos >= $BPL) {
    100          
296 10         17 $sp1 = substr($sps, 0, 1);
297 10         18 $w2 = substr($sps, 1) . $w2;
298             }
299             elsif ($pos + $sps_len - 1 > $BPL) {
300 1         3 $result .= substr($sps, 0, $BPL - $pos);
301 1         2 $sp1 = substr($sps, $BPL - $pos, 1);
302 1         3 $w2 = substr($sps, $BPL - $pos + 1) . $w2;
303 1         2 $pos = $BPL;
304             }
305             else {
306 58         72 $sp1 = chop($sps);
307 58         60 $result .= $sps;
308 58         77 $pos += $sps_len - 1;
309             }
310 69         80 $w1 = $w2;
311 69         67 $ascii = 1;
312             }
313 142 100       562 $count++ if $count <= 1;
314             }
315 50         285 ($sps) = ($str =~ /(.*)/g); # All space of the remainder
316              
317 50 100       97 if ($ascii) {
318 21         24 $w1 .= $sps;
319 21 100       32 if ($count == 0) {
320 6         8 $result = $w1;
321             }
322             else {
323 15         16 $w1_len = length($w1);
324 15 100 100     75 if (($count > 1) and ($pos + $w1_len + 1 > $BPL)) {
325 5         13 $result .= "$LF$sp1$w1";
326             }
327             else {
328 10         21 $result .= "$sp1$w1";
329             }
330             }
331             }
332             else {
333 29         28 $sps_len = length($sps);
334 29 100       49 if ($count == 0) {
335 6 100       12 if ($sps_len > $lss) {
336 2         5 $w1 .= substr($sps, 0, $sps_len - $lss);
337 2         13 $result .= &$refsub($w1, $pos, \$np, $lss) .
338             substr($sps, $sps_len - $lss);
339             }
340             else {
341 4         12 $result .= &$refsub($w1, $pos, \$np, $sps_len) . $sps;
342             }
343             }
344             else {
345 23 50       34 if ($sps_len > $lss) {
346 0         0 $w1 .= substr($sps, 0, $sps_len - $lss);
347 0         0 $tmp = &$refsub($w1, 1 + $pos, \$np, $lss) .
348             substr($sps, $sps_len - $lss);
349             }
350             else {
351 23         64 $tmp = &$refsub($w1, 1 + $pos, \$np, $sps_len) . $sps;
352             }
353 23 100       89 $result .= ($tmp =~ s/^ /$sp1/) ? "$LF$tmp" : "$sp1$tmp";
354             }
355             }
356 50 100       337 return $trailing_crlf ? $result . $trailing_crlf : $result;
357             }
358              
359              
360             # add encoded-word (for structured header)
361             # parameters:
362             # sp : start position (indentation of the first line)
363             # ep : end position of last line (call by reference)
364             # rll : room of last line (default: 0)
365             sub add_ew_sh {
366 28     28 0 46 my ($str, $sp, $ep, $rll) = @_;
367              
368 28         25 our $ADD_EW;
369 28         23 our $REG_RP;
370              
371 28         27 my ($lp, $rp); # '(' & ')' : left/right parenthesis
372 28         36 my ($lp_len, $rp_len) = (0, 0);
373 28         26 my $tmp;
374              
375 28 100       98 if ($str =~ s/^(\({1,3})//) {
376 20         31 $lp = $1;
377 20         17 $lp_len = length($lp);
378 20         25 $sp += $lp_len;
379             }
380 28 100       134 if ($str =~ /$REG_RP/) {
381 14         24 $rp = $1;
382 14         16 $rp_len = length($rp);
383 14         15 $rll = $rp_len;
384 14         22 substr($str, -$rp_len) = '';
385             }
386 28         69 $tmp = &$ADD_EW($str, $sp, $ep, $rll);
387 28 100       68 if ($lp_len > 0) {
388 20 100       54 if ($tmp !~ s/^ / $lp/) {
389 16         29 $tmp = $lp . $tmp;
390             }
391             }
392 28 100       55 if ($rp_len > 0) {
393 14         15 $tmp .= $rp;
394 14         18 $$ep += $rp_len;
395             }
396 28         73 return $tmp;
397             }
398              
399              
400             # add encoded-word for "B" encoding
401             sub add_ew_b {
402 30     30 0 58 my ($str, $sp, $ep, $rll, $fof) = @_;
403              
404 30 50       62 return '' if $str eq '';
405              
406 30         30 our $LF; # line feed
407 30         25 our $BPL; # bytes per line
408 30         80 our $HEAD; # head string
409 30         33 our $HTL; # head + tail length
410 30         25 our $UTF8;
411 30         29 our $REG_W;
412              
413 30         36 my $str_len = length($str);
414              
415             # encoded size + sp
416 30         75 my $ep_v = int(($str_len + 2) / 3) * 4 + $HTL + $sp;
417              
418 30 100       63 if ($ep_v + $rll <= $BPL) {
419 21         28 $$ep = $ep_v;
420 21         105 return $HEAD . encode_base64($str, '') . TAIL;
421             }
422              
423 9         11 my $result = '';
424 9         9 my $w;
425              
426 9 50       41 utf8::decode($str) if $UTF8; # UTF8 flag on
427              
428 9 100       18 if ($ep_v <= $BPL) {
429 1         17 $str =~ s/$REG_W$//;
430 1         3 $w = $1;
431 1 50       5 utf8::encode($w) if $UTF8; # UTF8 flag off
432 1         3 $$ep = int((length($w) + 2) / 3) * 4 + $HTL + 1; # 1 is space
433 1 50       4 utf8::encode($str) if $UTF8; # UTF8 flag off
434 1 50       6 $result = ($str eq '') ? ' ' :
435             $HEAD . encode_base64($str, '') . TAIL . "$LF ";
436 1         6 return $result . $HEAD . encode_base64($w, '') . TAIL;
437             }
438              
439 8         15 my ($chunk, $chunk_len) = ('', 0);
440 8         53 my $w_len;
441 8         12 my $str_pos = 0;
442 8         17 my $max_len = int(($BPL - $HTL - $sp) / 4) * 3;
443 8         15 my $max_len2 = int(($BPL - $HTL - 1) / 4) * 3;
444              
445 8         49 while ($str =~ /$REG_W/g) {
446 42         60 $w = $1;
447 42 50       99 utf8::encode($w) if $UTF8; # UTF8 flag off
448 42         70 $w_len = length($w); # size of one character
449              
450 42 100       79 if ($chunk_len + $w_len > $max_len) {
451 8 100       13 if ($chunk_len == 0) { # size over at the first time
452 5         8 $result = ' ';
453 5 50       12 return $result if $fof;
454             }
455             else {
456 3         28 $result .= $HEAD . encode_base64($chunk, '') . TAIL . "$LF ";
457             }
458 8         10 $str_pos += $chunk_len;
459              
460             # encoded size (1 is space)
461 8         18 $ep_v = int(($str_len - $str_pos + 2) / 3) * 4 + $HTL + 1;
462 8 50       21 if ($ep_v + $rll <= $BPL) {
463 8 50       28 utf8::encode($str) if $UTF8; # UTF8 flag off
464 8         12 $chunk = substr($str, $str_pos);
465 8         12 last;
466             }
467 0 0       0 if ($ep_v <= $BPL) {
468 0         0 $str =~ s/$REG_W$//;
469 0         0 $w = $1;
470 0 0       0 utf8::encode($w) if $UTF8; # UTF8 flag off
471 0         0 $w_len = length($w);
472 0 0       0 utf8::encode($str) if $UTF8; # UTF8 flag off
473 0         0 $chunk = substr($str, $str_pos);
474 0         0 $result .= $HEAD . encode_base64($chunk, '') . TAIL . "$LF ";
475 0         0 $ep_v = int(($w_len + 2) / 3) * 4 + $HTL + 1; # 1 is space
476 0         0 $chunk = $w;
477 0         0 last;
478             }
479 0         0 $chunk = $w;
480 0         0 $chunk_len = $w_len;
481 0         0 $max_len = $max_len2;
482             }
483             else {
484 34         36 $chunk .= $w;
485 34         146 $chunk_len += $w_len;
486             }
487             }
488 8         9 $$ep = $ep_v;
489 8         42 return $result . $HEAD . encode_base64($chunk, '') . TAIL;
490             }
491              
492              
493             # add encoded-word for "Q" encoding
494             sub add_ew_q {
495 10     10 0 16 my ($str, $sp, $ep, $rll, $fof) = @_;
496              
497 10 50       21 return '' if $str eq '';
498              
499 10         9 our $LF; # line feed
500 10         11 our $BPL; # bytes per line
501 10         8 our $MODE; # unstructured : 0, structured : 1
502 10         8 our $HEAD; # head string
503 10         7 our $HTL; # head + tail length
504 10         10 our $UTF8;
505 10         10 our $REG_W;
506              
507             # '.' is added to invalidate RFC 2045 6.7.(3)
508 10         56 my $qstr = encode_qp($str . '.', '');
509              
510 10         25 local *qlen;
511              
512 10         11 chop($qstr); # cut '.'
513 10         20 $qstr =~ s/_/=5F/g;
514 10         13 $qstr =~ tr/ /_/;
515 10         15 $qstr =~ s/\t/=09/g;
516 10 100       23 if ($MODE) { # structured
517 2         8 $qstr =~ s/([^\w\!\*\+\-\/\=])/sprintf("=%X",ord($1))/ego;
  25         54  
518             *qlen = sub {
519 15     15   17 my $str = shift;
520 15         23 return length($str) * 3 - ($str =~ tr/ A-Za-z0-9\!\*\+\-\///) * 2;
521 2         14 };
522             }
523             else { # unstructured
524 8         12 $qstr =~ s/\?/=3F/g;
525             *qlen = sub {
526 0     0   0 my $str = shift;
527 0         0 return length($str) * 3 - ($str =~ tr/ -\<\>\@-\^\`-\~//) * 2;
528 8         36 };
529             }
530              
531 10         17 my $ep_v = length($qstr) + $HTL + $sp;
532 10 100       33 if ($ep_v + $rll <= $BPL) {
533 9         11 $$ep = $ep_v;
534 9         51 return $HEAD . $qstr . TAIL;
535             }
536              
537 1 50       6 utf8::decode($str) if $UTF8; # UTF8 flag on
538              
539 1         2 my $result = '';
540 1         1 my $chunk_qlen = 0;
541 1         2 my $w_qlen;
542             my $enc_len;
543 0         0 my $w;
544              
545 1 50       3 if ($ep_v <= $BPL) {
546 0         0 $str =~ s/$REG_W$//;
547 0         0 $w = $1;
548 0 0       0 utf8::encode($w) if $UTF8; # UTF8 flag off
549 0         0 $w_qlen = qlen($w);
550 0         0 $$ep = $w_qlen + $HTL + 1; # 1 is space
551 0 0       0 $result = ($str eq '') ? ' ' :
552             $HEAD . substr($qstr, 0, -$w_qlen, '') . TAIL . "$LF ";
553 0         0 return $result . $HEAD . $qstr . TAIL;
554             }
555              
556 1         2 my $max_len = $BPL - $HTL - $sp;
557 1         2 my $max_len2 = $BPL - $HTL - 1;
558              
559 1         13 while ($str =~ /$REG_W/g) {
560 15         18 $w = $1;
561 15 50       34 utf8::encode($w) if $UTF8; # UTF8 flag off
562 15         19 $w_qlen = qlen($w);
563 15 100       26 if ($chunk_qlen + $w_qlen > $max_len) {
564 1 50       4 if ($chunk_qlen == 0) { # size over at the first time
565 0         0 $result = ' ';
566 0 0       0 return $result if $fof;
567             }
568             else {
569 1         6 $result .= $HEAD . substr($qstr, 0, $chunk_qlen, '')
570             . TAIL . "$LF ";
571             }
572 1         2 $ep_v = length($qstr) + $HTL + 1; # 1 is space
573 1 50       3 if ($ep_v + $rll <= $BPL) {
574 1         2 last;
575             }
576 0 0       0 if ($ep_v <= $BPL) {
577 0         0 $str =~ s/$REG_W$//;
578 0         0 $w = $1;
579 0 0       0 utf8::encode($w) if $UTF8; # UTF8 flag off
580 0         0 $w_qlen = qlen($w);
581 0         0 $result .= $HEAD . substr($qstr, 0, -$w_qlen, '')
582             . TAIL . "$LF ";
583 0         0 $ep_v = $w_qlen + $HTL + 1; # 1 is space
584 0         0 last;
585             }
586 0         0 $chunk_qlen = $w_qlen;
587 0         0 $max_len = $max_len2;
588             }
589             else {
590 14         57 $chunk_qlen += $w_qlen;
591             }
592             }
593 1         2 $$ep = $ep_v;
594 1         7 return $result . $HEAD . $qstr . TAIL;
595             }
596              
597              
598             sub mime_deco {
599 9     9 0 3877 my $str = shift;
600 9         14 my $cb = shift;
601              
602 9         10 my ($charset, $lang, $b_enc, $q_enc);
603 9         11 my $result = '';
604 9         13 my $enc = 0;
605 9         10 my $w_bak = '';
606 9         10 my $sp_len = 0;
607 9         10 my ($lp, $rp); # '(' & ')' : left/right parenthesis
608              
609 9         30 my $reg_ew =
610             qr{^
611             =\?
612             ([-0-9A-Za-z_]+) # charset
613             (?:\*([A-Za-z]{1,8} # language
614             (?:-[A-Za-z]{1,8})*))? # (RFC 2231 section 5)
615             \?
616             (?:
617             [Bb]\?([0-9A-Za-z\+\/]+={0,2})\?= # "B" encoding
618             |
619             [Qq]\?([\x21-\x3e\x40-\x7e]+)\?= # "Q" encoding
620             )
621             $}x;
622              
623 9         34 my ($trailing_crlf) = ($str =~ /(\x0d?\x0a|\x0d)$/);
624 9         19 $str =~ tr/\n\r//d;
625              
626 9 100       19 if ($cb) {
627 1         6 for my $w (split /([\s]+)/, $str) {
628 9         20 $w =~ s/^(\(*)//;
629 9         13 $lp = $1;
630 9         30 $w =~ s/(\)*)$//;
631 9         11 $rp = $1;
632 9 100       38 if ($w =~ qr/$reg_ew/o) {
633 2         7 ($charset, $lang, $b_enc, $q_enc) = ($1, $2, $3, $4);
634 2 100       5 $lang = '' unless defined $lang;
635 2 50 33     6 substr($result, -$sp_len) = "" if ($enc and !$lp);
636 2 50       4 if (defined $q_enc) {
637 0         0 $q_enc =~ tr/_/ /;
638 0         0 $result .= $lp . &$cb($w, $charset, $lang,
639             decode_qp($q_enc)) . $rp;
640             }
641             else {
642 2         12 $result .= $lp . &$cb($w, $charset, $lang,
643             decode_base64($b_enc)) . $rp;
644             }
645 2         420 $enc = 1;
646             }
647             else {
648 7 100       14 if ($enc) {
649 2 100       6 if ($w =~ /^\s+$/) {
650 1         2 $sp_len = length($w);
651             }
652             else {
653 1         1 $enc = 0;
654             }
655             }
656 7         17 $result .= $lp . $w . $rp;
657             }
658             }
659             }
660             else {
661 8         10 my $cs1 = '';
662 8         11 my $res_cs1 = '';
663 8         8 my $res_lang1 = '';
664 8         123 for my $w (split /([\s]+)/, $str) {
665 53         143 $w =~ s/^(\(*)//;
666 53         79 $lp = $1;
667 53         221 $w =~ s/(\)*)$//;
668 53         69 $rp = $1;
669 53 100       249 if ($w =~ qr/$reg_ew/o) {
670 16         48 ($charset, $lang, $b_enc, $q_enc) = ($1, $2, $3, $4);
671 16 50       39 if ($charset !~ /^US-ASCII$/i) {
672 16 100       20 if ($cs1) {
673 8 50       20 if ($cs1 ne lc($charset)) {
674 0         0 $result .= $w;
675 0         0 $enc = 0;
676 0         0 next;
677             }
678             }
679             else {
680 8         17 $cs1 = lc($charset);
681 8   50     22 $res_cs1 = $charset || '';
682 8   50     33 $res_lang1 = $lang || '';
683             }
684             }
685 16 100 66     47 substr($result, -$sp_len) = "" if ($enc and !$lp);
686 16 100       24 if (defined $q_enc) {
687 5         8 $q_enc =~ tr/_/ /;
688 5         27 $result .= $lp . decode_qp($q_enc) . $rp;
689             }
690             else {
691 11         50 $result .= $lp . decode_base64($b_enc) . $rp;
692             }
693 16 50       57 $enc = $rp ? 0 : 1;
694             }
695             else {
696 37 100       71 if ($enc) {
697 15 100       39 if ($w =~ /^\s+$/) {
698 10         14 $sp_len = length($w);
699             }
700             else {
701 5         6 $enc = 0;
702             }
703             }
704 37         87 $result .= $lp . $w . $rp;
705             }
706             }
707 8 100       23 if ($cs1 eq 'iso-2022-jp') { # remove redundant ESC sequences
708 3         24 $result =~ s/(\e..)([^\e]+)\e\(B(?=\1)/$1$2\n/g;
709 3         7 $result =~ s/\n\e..//g;
710 3         9 $result =~ s/\e\(B(\e..)/$1/g;
711             }
712 8 50       34 if (wantarray) {
713 0 0       0 return ($trailing_crlf ? $result . $trailing_crlf : $result,
714             $res_cs1, $res_lang1);
715             }
716             }
717 9 50       52 return $trailing_crlf ? $result . $trailing_crlf : $result;
718             }
719              
720             1;