| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sisimai::MIME; |
|
2
|
83
|
|
|
83
|
|
72636
|
use feature ':5.10'; |
|
|
83
|
|
|
|
|
217
|
|
|
|
83
|
|
|
|
|
5987
|
|
|
3
|
83
|
|
|
83
|
|
573
|
use strict; |
|
|
83
|
|
|
|
|
190
|
|
|
|
83
|
|
|
|
|
1904
|
|
|
4
|
83
|
|
|
83
|
|
433
|
use warnings; |
|
|
83
|
|
|
|
|
243
|
|
|
|
83
|
|
|
|
|
2349
|
|
|
5
|
83
|
|
|
83
|
|
1070
|
use Encode; |
|
|
83
|
|
|
|
|
11047
|
|
|
|
83
|
|
|
|
|
7750
|
|
|
6
|
83
|
|
|
83
|
|
39902
|
use MIME::Base64 (); |
|
|
83
|
|
|
|
|
54954
|
|
|
|
83
|
|
|
|
|
2068
|
|
|
7
|
83
|
|
|
83
|
|
35413
|
use MIME::QuotedPrint (); |
|
|
83
|
|
|
|
|
20825
|
|
|
|
83
|
|
|
|
|
1889
|
|
|
8
|
83
|
|
|
83
|
|
869
|
use Sisimai::String; |
|
|
83
|
|
|
|
|
171
|
|
|
|
83
|
|
|
|
|
15995
|
|
|
9
|
83
|
|
|
|
|
268486
|
use constant ReE => { |
|
10
|
|
|
|
|
|
|
'7bit-encoded' => qr/^content-transfer-encoding:[ ]*7bit/m, |
|
11
|
|
|
|
|
|
|
'quoted-print' => qr/^content-transfer-encoding:[ ]*quoted-printable/m, |
|
12
|
|
|
|
|
|
|
'some-iso2022' => qr/^content-type:[ ]*.+;[ ]*charset=["']?(iso-2022-[-a-z0-9]+)['"]?\b/m, |
|
13
|
|
|
|
|
|
|
'with-charset' => qr/^content[-]type:[ ]*.+[;][ ]*charset=['"]?([-0-9a-z]+)['"]?\b/, |
|
14
|
|
|
|
|
|
|
'only-charset' => qr/^[\s\t]+charset=['"]?([-0-9a-z]+)['"]?\b/, |
|
15
|
|
|
|
|
|
|
'html-message' => qr|^content-type:[ ]*text/html;|m, |
|
16
|
83
|
|
|
83
|
|
712
|
}; |
|
|
83
|
|
|
|
|
213
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub is_mimeencoded { |
|
19
|
|
|
|
|
|
|
# Check that the argument is MIME-Encoded string or not |
|
20
|
|
|
|
|
|
|
# @param [String] argv1 String to be checked |
|
21
|
|
|
|
|
|
|
# @return [Integer] 0: Not MIME encoded string |
|
22
|
|
|
|
|
|
|
# 1: MIME encoded string |
|
23
|
8778
|
|
|
8778
|
1
|
13060
|
my $class = shift; |
|
24
|
8778
|
|
50
|
|
|
15036
|
my $argv1 = shift || return undef; |
|
25
|
8778
|
50
|
|
|
|
16855
|
return undef unless ref $argv1 eq 'SCALAR'; |
|
26
|
|
|
|
|
|
|
|
|
27
|
8778
|
|
|
|
|
12698
|
my $text1 = $$argv1; $text1 =~ y/"//d; |
|
|
8778
|
|
|
|
|
11161
|
|
|
28
|
8778
|
|
|
|
|
10442
|
my $mime1 = 0; |
|
29
|
8778
|
|
|
|
|
9334
|
my @piece; |
|
30
|
|
|
|
|
|
|
|
|
31
|
8778
|
100
|
|
|
|
18040
|
if( rindex($text1, ' ') > -1 ) { |
|
32
|
|
|
|
|
|
|
# Multiple MIME-Encoded strings in a line |
|
33
|
3514
|
|
|
|
|
10713
|
@piece = split(' ', $text1); |
|
34
|
|
|
|
|
|
|
} else { |
|
35
|
5264
|
|
|
|
|
8581
|
push @piece, $text1; |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
|
|
38
|
8778
|
|
|
|
|
12038
|
for my $e ( @piece ) { |
|
39
|
|
|
|
|
|
|
# Check all the string in the array |
|
40
|
20922
|
100
|
|
|
|
39082
|
next unless $e =~ /[ \t]*=[?][-_0-9A-Za-z]+[?][BbQq][?].+[?]=?[ \t]*/; |
|
41
|
1276
|
|
|
|
|
2095
|
$mime1 = 1; |
|
42
|
|
|
|
|
|
|
} |
|
43
|
8778
|
|
|
|
|
25499
|
return $mime1; |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub mimedecode { |
|
47
|
|
|
|
|
|
|
# Decode MIME-Encoded string |
|
48
|
|
|
|
|
|
|
# @param [Array] argvs Reference to an array including MIME-Encoded text |
|
49
|
|
|
|
|
|
|
# @return [String] MIME-Decoded text |
|
50
|
2692
|
|
|
2692
|
1
|
8302
|
my $class = shift; |
|
51
|
2692
|
|
|
|
|
3492
|
my $argvs = shift; |
|
52
|
2692
|
50
|
|
|
|
5528
|
return undef unless ref $argvs eq 'ARRAY'; |
|
53
|
|
|
|
|
|
|
|
|
54
|
2692
|
|
|
|
|
3357
|
my $characterset = ''; |
|
55
|
2692
|
|
|
|
|
4259
|
my $encodingname = ''; |
|
56
|
2692
|
|
|
|
|
3210
|
my @decodedtext0; |
|
57
|
|
|
|
|
|
|
|
|
58
|
2692
|
|
|
|
|
4292
|
for my $e ( @$argvs ) { |
|
59
|
|
|
|
|
|
|
# Check and decode each element |
|
60
|
2909
|
|
|
|
|
6181
|
$e =~ s/\A[ \t]+//g; |
|
61
|
2909
|
|
|
|
|
5322
|
$e =~ s/[ \t]+\z//g; |
|
62
|
2909
|
|
|
|
|
4497
|
$e =~ y/"//d; |
|
63
|
|
|
|
|
|
|
|
|
64
|
2909
|
100
|
|
|
|
5355
|
if( __PACKAGE__->is_mimeencoded(\$e) ) { |
|
65
|
|
|
|
|
|
|
# =?utf-8?B?55m954yr44Gr44KD44KT44GT?= |
|
66
|
490
|
100
|
|
|
|
3181
|
next unless $e =~ m{\A(.*)=[?]([-_0-9A-Za-z]+)[?]([BbQq])[?](.+)[?]=?(.*)\z}; |
|
67
|
468
|
|
66
|
|
|
2585
|
$characterset ||= lc $2; |
|
68
|
468
|
|
66
|
|
|
1830
|
$encodingname ||= uc $3; |
|
69
|
|
|
|
|
|
|
|
|
70
|
468
|
|
|
|
|
973
|
push @decodedtext0, $1; |
|
71
|
468
|
100
|
|
|
|
3815
|
push @decodedtext0, $encodingname eq 'B' |
|
72
|
|
|
|
|
|
|
? MIME::Base64::decode($4) |
|
73
|
|
|
|
|
|
|
: MIME::QuotedPrint::decode($4); |
|
74
|
468
|
|
|
|
|
924
|
$decodedtext0[-1] =~ y/\r\n//d; |
|
75
|
468
|
|
|
|
|
1231
|
push @decodedtext0, $5; |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
} else { |
|
78
|
2419
|
100
|
|
|
|
6489
|
push @decodedtext0, scalar @decodedtext0 ? ' '.$e : $e; |
|
79
|
|
|
|
|
|
|
} |
|
80
|
|
|
|
|
|
|
} |
|
81
|
2692
|
50
|
|
|
|
5617
|
return '' unless scalar @decodedtext0; |
|
82
|
|
|
|
|
|
|
|
|
83
|
2692
|
|
|
|
|
5985
|
my $decodedtext1 = join('', @decodedtext0); |
|
84
|
2692
|
100
|
66
|
|
|
6980
|
if( $characterset && $encodingname ) { |
|
85
|
|
|
|
|
|
|
# utf-8 => utf8 |
|
86
|
403
|
100
|
|
|
|
995
|
$characterset = 'utf8' if $characterset eq 'utf-8'; |
|
87
|
|
|
|
|
|
|
|
|
88
|
403
|
100
|
|
|
|
1131
|
if( $characterset ne 'utf8' ) { |
|
89
|
|
|
|
|
|
|
# Characterset is not UTF-8 |
|
90
|
138
|
|
|
|
|
254
|
eval { Encode::from_to($decodedtext1, $characterset, 'utf8') }; |
|
|
138
|
|
|
|
|
668
|
|
|
91
|
138
|
50
|
|
|
|
29377
|
$decodedtext1 = 'FAILED TO CONVERT THE SUBJECT' if $@; |
|
92
|
|
|
|
|
|
|
} |
|
93
|
|
|
|
|
|
|
} |
|
94
|
2692
|
|
|
|
|
7842
|
return $decodedtext1; |
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub qprintd { |
|
98
|
|
|
|
|
|
|
# Decode MIME Quoted-Printable Encoded string |
|
99
|
|
|
|
|
|
|
# @param [String] argv1 MIME Encoded text |
|
100
|
|
|
|
|
|
|
# @param [Hash] heads Email header |
|
101
|
|
|
|
|
|
|
# @return [String] MIME Decoded text |
|
102
|
245
|
|
|
245
|
1
|
2774
|
my $class = shift; |
|
103
|
245
|
|
50
|
|
|
748
|
my $argv1 = shift // return undef; |
|
104
|
245
|
|
100
|
|
|
1008
|
my $heads = shift // {}; |
|
105
|
245
|
|
|
|
|
488
|
my $plain = ''; |
|
106
|
245
|
50
|
|
|
|
817
|
return \'' unless ref $argv1 eq 'SCALAR'; |
|
107
|
|
|
|
|
|
|
|
|
108
|
245
|
100
|
66
|
|
|
909
|
if( ! exists $heads->{'content-type'} || ! $heads->{'content-type'} ) { |
|
109
|
|
|
|
|
|
|
# There is no Content-Type: field |
|
110
|
244
|
|
|
|
|
4811
|
$plain = MIME::QuotedPrint::decode($$argv1); |
|
111
|
244
|
|
|
|
|
1459
|
return \$plain; |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# Quoted-printable encoded part is the part of the text |
|
115
|
1
|
|
|
|
|
5
|
my $boundary00 = __PACKAGE__->boundary($heads->{'content-type'}, 0); |
|
116
|
1
|
50
|
33
|
|
|
19
|
if( ! $boundary00 || lc($$argv1) !~ ReE->{'quoted-print'} ) { |
|
117
|
|
|
|
|
|
|
# There is no boundary string or no |
|
118
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable field. |
|
119
|
0
|
|
|
|
|
0
|
$plain = MIME::QuotedPrint::decode($$argv1); |
|
120
|
0
|
|
|
|
|
0
|
return \$plain; |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
|
|
123
|
1
|
|
|
|
|
3
|
my $boundary01 = Sisimai::MIME->boundary($heads->{'content-type'}, 1); |
|
124
|
1
|
|
|
|
|
4
|
my $bodystring = ''; |
|
125
|
1
|
|
|
|
|
2
|
my $notdecoded = ''; |
|
126
|
|
|
|
|
|
|
|
|
127
|
1
|
|
|
|
|
1
|
my $encodename = undef; |
|
128
|
1
|
|
|
|
|
1
|
my $ctencoding = undef; |
|
129
|
1
|
|
|
|
|
2
|
my $mimeinside = 0; |
|
130
|
|
|
|
|
|
|
|
|
131
|
1
|
|
|
|
|
14
|
for my $e ( split("\n", $$argv1) ) { |
|
132
|
|
|
|
|
|
|
# This is a multi-part message in MIME format. Your mail reader does not |
|
133
|
|
|
|
|
|
|
# understand MIME message format. |
|
134
|
|
|
|
|
|
|
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ |
|
135
|
|
|
|
|
|
|
# Content-Type: text/plain; charset=iso-8859-15 |
|
136
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable |
|
137
|
31
|
100
|
|
|
|
44
|
if( $mimeinside ) { |
|
138
|
|
|
|
|
|
|
# Quoted-Printable encoded text block |
|
139
|
21
|
100
|
|
|
|
28
|
if( $e eq $boundary00 ) { |
|
140
|
|
|
|
|
|
|
# The next boundary string has appeared |
|
141
|
|
|
|
|
|
|
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ |
|
142
|
1
|
|
|
|
|
16
|
my $hasdecoded = MIME::QuotedPrint::decode($notdecoded); |
|
143
|
1
|
|
|
|
|
11
|
$hasdecoded = Sisimai::String->to_utf8(\$hasdecoded, $encodename); |
|
144
|
1
|
|
|
|
|
4
|
$bodystring .= $$hasdecoded; |
|
145
|
1
|
|
|
|
|
4
|
$bodystring .= $e . "\n"; |
|
146
|
|
|
|
|
|
|
|
|
147
|
1
|
|
|
|
|
1
|
$notdecoded = ''; |
|
148
|
1
|
|
|
|
|
2
|
$mimeinside = 0; |
|
149
|
1
|
|
|
|
|
1
|
$ctencoding = undef; |
|
150
|
1
|
|
|
|
|
2
|
$encodename = undef; |
|
151
|
|
|
|
|
|
|
} else { |
|
152
|
|
|
|
|
|
|
# Inside of Queoted printable encoded text |
|
153
|
20
|
|
|
|
|
38
|
$notdecoded .= $e . "\n"; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
} else { |
|
156
|
|
|
|
|
|
|
# NOT Quoted-Printable encoded text block |
|
157
|
10
|
100
|
66
|
|
|
116
|
if( (my $lowercased = lc $e) =~ /\A[-]{2}[^\s]+[^-]\z/ ) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Start of the boundary block |
|
159
|
|
|
|
|
|
|
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ |
|
160
|
1
|
50
|
|
|
|
5
|
unless( $e eq $boundary00 ) { |
|
161
|
|
|
|
|
|
|
# New boundary string has appeared |
|
162
|
0
|
|
|
|
|
0
|
$boundary00 = $e; |
|
163
|
0
|
|
|
|
|
0
|
$boundary01 = $e . '--'; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
} elsif( $lowercased =~ ReE->{'with-charset'} || $lowercased =~ ReE->{'only-charset'} ) { |
|
166
|
|
|
|
|
|
|
# Content-Type: text/plain; charset=ISO-2022-JP |
|
167
|
1
|
|
|
|
|
4
|
$encodename = $1; |
|
168
|
1
|
50
|
|
|
|
3
|
$mimeinside = 1 if $ctencoding; |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
} elsif( $lowercased =~ ReE->{'quoted-print'} ) { |
|
171
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable |
|
172
|
1
|
|
|
|
|
2
|
$ctencoding = $e; |
|
173
|
1
|
50
|
|
|
|
3
|
$mimeinside = 1 if $encodename; |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
} elsif( $e eq $boundary01 ) { |
|
176
|
|
|
|
|
|
|
# The end of boundary block |
|
177
|
|
|
|
|
|
|
# --=_gy7C4Gpes0RP4V5Bs9cK4o2Us2ZT57b-3OLnRN+4klS8dTmQ-- |
|
178
|
0
|
|
|
|
|
0
|
$mimeinside = 0; |
|
179
|
|
|
|
|
|
|
} |
|
180
|
10
|
|
|
|
|
29
|
$bodystring .= $e . "\n"; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
1
|
50
|
|
|
|
10
|
$bodystring .= $notdecoded if length $notdecoded; |
|
185
|
1
|
|
|
|
|
4
|
return \$bodystring; |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub base64d { |
|
189
|
|
|
|
|
|
|
# Decode MIME BASE64 Encoded string |
|
190
|
|
|
|
|
|
|
# @param [String] argv1 MIME Encoded text |
|
191
|
|
|
|
|
|
|
# @return [String] MIME-Decoded text |
|
192
|
35
|
|
|
35
|
1
|
377
|
my $class = shift; |
|
193
|
35
|
|
50
|
|
|
115
|
my $argv1 = shift // return undef; |
|
194
|
35
|
50
|
|
|
|
139
|
return \'' unless ref $argv1 eq 'SCALAR'; |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Decode BASE64 |
|
197
|
35
|
50
|
|
|
|
446
|
my $plain = $$argv1 =~ m|([+/=0-9A-Za-z\r\n]+)| ? MIME::Base64::decode($1) : ''; |
|
198
|
35
|
|
|
|
|
132
|
return \$plain; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub boundary { |
|
202
|
|
|
|
|
|
|
# Get boundary string |
|
203
|
|
|
|
|
|
|
# @param [String] argv1 The value of Content-Type header |
|
204
|
|
|
|
|
|
|
# @param [Integer] start -1: boundary string itself |
|
205
|
|
|
|
|
|
|
# 0: Start of boundary |
|
206
|
|
|
|
|
|
|
# 1: End of boundary |
|
207
|
|
|
|
|
|
|
# @return [String] Boundary string |
|
208
|
2347
|
|
|
2347
|
1
|
3923
|
my $class = shift; |
|
209
|
2347
|
|
50
|
|
|
4167
|
my $argv1 = shift || return undef; |
|
210
|
2347
|
|
100
|
|
|
4233
|
my $start = shift // -1; |
|
211
|
2347
|
|
|
|
|
2955
|
my $value = ''; |
|
212
|
|
|
|
|
|
|
|
|
213
|
2347
|
100
|
|
|
|
13092
|
if( lc $argv1 =~ /\bboundary=([^ ]+)/ ) { |
|
214
|
|
|
|
|
|
|
# Content-Type: multipart/mixed; boundary=Apple-Mail-5--931376066 |
|
215
|
|
|
|
|
|
|
# Content-Type: multipart/report; report-type=delivery-status; |
|
216
|
|
|
|
|
|
|
# boundary="n6H9lKZh014511.1247824040/mx.example.jp" |
|
217
|
2302
|
|
|
|
|
6366
|
$value = $1; |
|
218
|
2302
|
|
|
|
|
4251
|
$value =~ y/"';\\//d; |
|
219
|
2302
|
100
|
|
|
|
6763
|
$value = '--'.$value if $start > -1; |
|
220
|
2302
|
100
|
|
|
|
4965
|
$value = $value.'--' if $start > 0; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
2347
|
|
|
|
|
5196
|
return $value; |
|
223
|
|
|
|
|
|
|
} |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub breaksup { |
|
226
|
|
|
|
|
|
|
# Breaks up each multipart/* block |
|
227
|
|
|
|
|
|
|
# @param [String] argv0 Text block of multipart/* |
|
228
|
|
|
|
|
|
|
# @param [String] argv1 MIME type of the outside part |
|
229
|
|
|
|
|
|
|
# @return [String] Decoded part as a plain text(text part only) |
|
230
|
5068
|
|
|
5068
|
1
|
9386
|
my $class = shift; |
|
231
|
5068
|
|
100
|
|
|
8924
|
my $argv0 = shift || return undef; |
|
232
|
5067
|
|
50
|
|
|
8164
|
my $argv1 = shift || ''; |
|
233
|
|
|
|
|
|
|
|
|
234
|
5067
|
|
|
|
|
5485
|
state $alsoappend = qr{\A(?:text/rfc822-headers|message/)}; |
|
235
|
5067
|
|
|
|
|
5062
|
state $thisformat = qr/\A(?:Content-Transfer-Encoding:\s*.+\n)?Content-Type:\s*([^ ;\s]+)/; |
|
236
|
5067
|
|
|
|
|
5136
|
state $leavesonly = qr{\A(?> |
|
237
|
|
|
|
|
|
|
text/(?:plain|html|rfc822-headers) |
|
238
|
|
|
|
|
|
|
|message/(?:x?delivery-status|rfc822|partial|feedback-report) |
|
239
|
|
|
|
|
|
|
|multipart/(?:report|alternative|mixed|related|partial) |
|
240
|
|
|
|
|
|
|
) |
|
241
|
|
|
|
|
|
|
}x; |
|
242
|
|
|
|
|
|
|
|
|
243
|
5067
|
50
|
|
|
|
32344
|
my $mimeformat = $$argv0 =~ $thisformat ? lc($1) : ''; |
|
244
|
5067
|
100
|
|
|
|
11157
|
my $alternates = index($argv1, 'multipart/alternative') == 0 ? 1 : 0; |
|
245
|
5067
|
|
|
|
|
6098
|
my $hasflatten = ''; # Message body including only text/plain and message/* |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Sisimai require only MIME types defined in $leavesonly variable |
|
248
|
5067
|
100
|
|
|
|
22659
|
return \'' unless $mimeformat =~ $leavesonly; |
|
249
|
5062
|
50
|
66
|
|
|
10454
|
return \'' if $alternates && $mimeformat eq 'text/html'; |
|
250
|
|
|
|
|
|
|
|
|
251
|
5062
|
|
|
|
|
25899
|
my ($upperchunk, $lowerchunk) = split(/^$/m, $$argv0, 2); |
|
252
|
5062
|
|
|
|
|
9187
|
$upperchunk =~ y/\n/ /; |
|
253
|
5062
|
|
|
|
|
7410
|
$upperchunk =~ y/ //s; |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# Content-Description: Undelivered Message |
|
256
|
|
|
|
|
|
|
# Content-Type: message/rfc822 |
|
257
|
|
|
|
|
|
|
# |
|
258
|
5062
|
|
50
|
|
|
8463
|
$lowerchunk ||= ''; |
|
259
|
|
|
|
|
|
|
|
|
260
|
5062
|
100
|
|
|
|
9011
|
if( index($mimeformat, 'multipart/') == 0 ) { |
|
261
|
|
|
|
|
|
|
# Content-Type: multipart/* |
|
262
|
269
|
|
|
|
|
997
|
my $mpboundary = __PACKAGE__->boundary($upperchunk, 0); |
|
263
|
269
|
|
|
|
|
5566
|
my @innerparts = split(/\Q$mpboundary\E\n/, $lowerchunk); |
|
264
|
269
|
50
|
|
|
|
1092
|
shift @innerparts unless length $innerparts[0]; |
|
265
|
269
|
100
|
|
|
|
769
|
shift @innerparts if $innerparts[0] eq "\n"; |
|
266
|
|
|
|
|
|
|
|
|
267
|
269
|
|
|
|
|
573
|
for my $e ( @innerparts ) { |
|
268
|
|
|
|
|
|
|
# Find internal multipart/* blocks and decode |
|
269
|
546
|
100
|
|
|
|
2653
|
if( $e =~ $thisformat ) { |
|
270
|
|
|
|
|
|
|
# Found Content-Type field at the first or second line of this |
|
271
|
|
|
|
|
|
|
# split part |
|
272
|
309
|
|
|
|
|
915
|
my $nextformat = lc $1; |
|
273
|
309
|
100
|
|
|
|
1508
|
next unless $nextformat =~ $leavesonly; |
|
274
|
308
|
100
|
|
|
|
740
|
next if $nextformat eq 'text/html'; |
|
275
|
285
|
|
|
|
|
371
|
$hasflatten .= ${ __PACKAGE__->breaksup(\$e, $mimeformat) }; |
|
|
285
|
|
|
|
|
987
|
|
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
} else { |
|
278
|
|
|
|
|
|
|
# The content of this part is almost '--': a part of boundary |
|
279
|
|
|
|
|
|
|
# string which is used for splitting multipart/* blocks. |
|
280
|
237
|
|
|
|
|
591
|
$hasflatten .= "\n"; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
} else { |
|
284
|
|
|
|
|
|
|
# Is not "Content-Type: multipart/*" |
|
285
|
4793
|
100
|
|
|
|
11009
|
if( $upperchunk =~ /Content-Transfer-Encoding: ([^\s;]+)/ ) { |
|
286
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable|base64|7bit|... |
|
287
|
1288
|
|
|
|
|
2349
|
my $getdecoded = ''; |
|
288
|
|
|
|
|
|
|
|
|
289
|
1288
|
100
|
|
|
|
5674
|
if( (my $ctencoding = lc $1) eq 'quoted-printable' ) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable |
|
291
|
197
|
|
|
|
|
359
|
$getdecoded = ${ __PACKAGE__->qprintd(\$lowerchunk) }; |
|
|
197
|
|
|
|
|
751
|
|
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
} elsif( $ctencoding eq 'base64' ) { |
|
294
|
|
|
|
|
|
|
# Content-Transfer-Encoding: base64 |
|
295
|
29
|
|
|
|
|
147
|
$getdecoded = ${ __PACKAGE__->base64d(\$lowerchunk) }; |
|
|
29
|
|
|
|
|
169
|
|
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
} elsif( $ctencoding eq '7bit' ) { |
|
298
|
|
|
|
|
|
|
# Content-Transfer-Encoding: 7bit |
|
299
|
788
|
100
|
|
|
|
4154
|
if( lc($upperchunk) =~ ReE->{'some-iso2022'} ) { |
|
300
|
|
|
|
|
|
|
# Content-Type: text/plain; charset=ISO-2022-JP |
|
301
|
59
|
|
|
|
|
144
|
$getdecoded = ${ Sisimai::String->to_utf8(\$lowerchunk, $1) }; |
|
|
59
|
|
|
|
|
476
|
|
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
} else { |
|
304
|
|
|
|
|
|
|
# No "charset" parameter in Content-Type: field |
|
305
|
729
|
|
|
|
|
1254
|
$getdecoded = $lowerchunk; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
} else { |
|
308
|
|
|
|
|
|
|
# Content-Transfer-Encoding: 8bit, binary, and so on |
|
309
|
274
|
|
|
|
|
616
|
$getdecoded = $lowerchunk; |
|
310
|
|
|
|
|
|
|
} |
|
311
|
1288
|
100
|
|
|
|
3312
|
$getdecoded =~ s|\r\n|\n|g if index($getdecoded, "\r\n") > -1; # Convert CRLF to LF |
|
312
|
|
|
|
|
|
|
|
|
313
|
1288
|
100
|
|
|
|
6145
|
if( $mimeformat =~ $alsoappend ) { |
|
|
|
50
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Append field when the value of Content-Type: begins with |
|
315
|
|
|
|
|
|
|
# message/ or equals text/rfc822-headers. |
|
316
|
614
|
|
|
|
|
2641
|
$upperchunk =~ s/Content-Transfer-Encoding:\s*[^\s]+//; |
|
317
|
614
|
100
|
|
|
|
2371
|
$upperchunk =~ s/\A[ ]//g if substr($upperchunk, 0, 1) eq ' '; |
|
318
|
614
|
50
|
|
|
|
2656
|
$upperchunk =~ s/[ ]\z//g if substr($upperchunk, -1, 1) eq ' '; |
|
319
|
614
|
|
|
|
|
1243
|
$hasflatten .= $upperchunk; |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
} elsif( $mimeformat eq 'text/html' ) { |
|
322
|
|
|
|
|
|
|
# Delete HTML tags inside of text/html part whenever possible |
|
323
|
0
|
|
|
|
|
0
|
$getdecoded = ${ Sisimai::String->to_plain(\$getdecoded) }; |
|
|
0
|
|
|
|
|
0
|
|
|
324
|
|
|
|
|
|
|
} |
|
325
|
1288
|
50
|
|
|
|
5640
|
$hasflatten .= $getdecoded."\n\n" if length $getdecoded; |
|
326
|
|
|
|
|
|
|
} else { |
|
327
|
|
|
|
|
|
|
# Content-Type: text/plain OR text/rfc822-headers OR message/* |
|
328
|
3505
|
100
|
100
|
|
|
9411
|
if( index($mimeformat, 'message/') == 0 || $mimeformat eq 'text/rfc822-headers' ) { |
|
329
|
|
|
|
|
|
|
# Append headers of multipart/* when the value of "Content-Type" |
|
330
|
|
|
|
|
|
|
# is inlucded in the following MIME types: |
|
331
|
|
|
|
|
|
|
# - message/delivery-status |
|
332
|
|
|
|
|
|
|
# - message/rfc822 |
|
333
|
|
|
|
|
|
|
# - text/rfc822-headers |
|
334
|
2760
|
|
|
|
|
5729
|
$hasflatten .= $upperchunk; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
3505
|
|
|
|
|
7911
|
$lowerchunk =~ s/^--\z//m; |
|
337
|
3505
|
100
|
|
|
|
10448
|
$lowerchunk .= "\n" unless $lowerchunk =~ /\n\z/; |
|
338
|
3505
|
|
|
|
|
10133
|
$hasflatten .= $lowerchunk; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
} |
|
341
|
5062
|
|
|
|
|
22490
|
return \$hasflatten; |
|
342
|
|
|
|
|
|
|
} |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub makeflat { |
|
345
|
|
|
|
|
|
|
# MIME decode entire message body |
|
346
|
|
|
|
|
|
|
# @param [String] argv0 Content-Type header |
|
347
|
|
|
|
|
|
|
# @param [String] argv1 Entire message body |
|
348
|
|
|
|
|
|
|
# @return [String] Decoded message body |
|
349
|
1960
|
|
|
1960
|
1
|
6788
|
my $class = shift; |
|
350
|
1960
|
|
100
|
|
|
4221
|
my $argv0 = shift // return undef; |
|
351
|
1959
|
|
50
|
|
|
3631
|
my $argv1 = shift // return undef; |
|
352
|
|
|
|
|
|
|
|
|
353
|
1959
|
|
|
|
|
3918
|
my $ehboundary = __PACKAGE__->boundary($argv0, 0); |
|
354
|
1959
|
100
|
|
|
|
18801
|
my $mimeformat = $argv0 =~ qr|\A([0-9a-z]+/[^ ;]+)| ? $1 : ''; |
|
355
|
1959
|
|
|
|
|
4707
|
my $bodystring = ''; |
|
356
|
|
|
|
|
|
|
|
|
357
|
1959
|
100
|
|
|
|
5092
|
return \'' unless index($mimeformat, 'multipart/') > -1; |
|
358
|
1928
|
100
|
|
|
|
3979
|
return \'' unless $ehboundary; |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# Some bounce messages include lower-cased "content-type:" field such as |
|
361
|
|
|
|
|
|
|
# content-type: message/delivery-status |
|
362
|
|
|
|
|
|
|
# content-transfer-encoding: quoted-printable |
|
363
|
1915
|
|
|
|
|
32102
|
$$argv1 =~ s/[Cc]ontent-[Tt]ype:/Content-Type:/g; |
|
364
|
1915
|
|
|
|
|
7653
|
$$argv1 =~ s/[Cc]ontent-[Tt]ransfer-[Ee]ncodeing:/Content-Transfer-Encoding:/g; |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# 1. Some bounce messages include upper-cased "Content-Transfer-Encoding", |
|
367
|
|
|
|
|
|
|
# and "Content-Type" value such as |
|
368
|
|
|
|
|
|
|
# - Content-Type: multipart/RELATED; |
|
369
|
|
|
|
|
|
|
# - Content-Transfer-Encoding: 7BIT |
|
370
|
|
|
|
|
|
|
# 2. Unused fields inside of mutipart/* block should be removed |
|
371
|
1915
|
|
|
|
|
11570
|
$$argv1 =~ s/(Content-[A-Za-z-]+?):[ ]*([^\s]+)/$1.': '.lc($2)/eg; |
|
|
13063
|
|
|
|
|
65148
|
|
|
372
|
1915
|
|
|
|
|
14758
|
$$argv1 =~ s/^Content-(?:Description|Disposition):.+?\n//gm; |
|
373
|
|
|
|
|
|
|
|
|
374
|
1915
|
|
|
|
|
49428
|
my @multiparts = split(/\Q$ehboundary\E\n?/, $$argv1); |
|
375
|
1915
|
100
|
|
|
|
6652
|
shift @multiparts unless length $multiparts[0]; |
|
376
|
1915
|
|
|
|
|
3698
|
for my $e ( @multiparts ) { |
|
377
|
|
|
|
|
|
|
# Find internal multipart blocks and decode |
|
378
|
|
|
|
|
|
|
XCCT: { |
|
379
|
|
|
|
|
|
|
# Remove fields except Content-Type, Content-Transfer-Encoding in |
|
380
|
|
|
|
|
|
|
# each part such as the following: |
|
381
|
|
|
|
|
|
|
# Date: Thu, 29 Apr 2018 22:22:22 +0900 |
|
382
|
|
|
|
|
|
|
# MIME-Version: 1.0 |
|
383
|
|
|
|
|
|
|
# Message-ID: ... |
|
384
|
|
|
|
|
|
|
# Content-Transfer-Encoding: quoted-printable |
|
385
|
|
|
|
|
|
|
# Content-Type: text/plain; charset=us-ascii |
|
386
|
|
|
|
|
|
|
# |
|
387
|
|
|
|
|
|
|
# Fields before "Content-Type:" in each part should have been removed |
|
388
|
|
|
|
|
|
|
# and "Content-Type:" should be exist at the first line of each part. |
|
389
|
|
|
|
|
|
|
# The field works as a delimiter to decode contents of each part. |
|
390
|
|
|
|
|
|
|
# |
|
391
|
8400
|
100
|
|
|
|
10207
|
last(XCCT) if $e =~ /\AContent-T[ry]/; # The first field is "Content-Type:" |
|
|
8400
|
|
|
|
|
20298
|
|
|
392
|
3648
|
50
|
100
|
|
|
8901
|
my $p = $1 if $e =~ /\A(.+?)Content-Type:/s || last(XCCT); |
|
393
|
51
|
100
|
|
|
|
278
|
last(XCCT) if $p =~ /\n\n/m; # There is no field before "Content-Type:" |
|
394
|
29
|
|
|
|
|
246
|
$e =~ s/\A.+?(Content-T[ry].+)\z/$1/s; # Remove fields before "Content-Type:" |
|
395
|
|
|
|
|
|
|
} |
|
396
|
|
|
|
|
|
|
|
|
397
|
8400
|
100
|
|
|
|
34537
|
if( $e =~ /\A(?:Content-[A-Za-z-]+:.+?\r?\n)?Content-Type:[ ]*[^\s]+/ ) { |
|
398
|
|
|
|
|
|
|
# Content-Type: multipart/* |
|
399
|
4781
|
|
|
|
|
6150
|
$bodystring .= ${ __PACKAGE__->breaksup(\$e, $mimeformat) }; |
|
|
4781
|
|
|
|
|
10164
|
|
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
} else { |
|
402
|
|
|
|
|
|
|
# Is not multipart/* block |
|
403
|
3619
|
|
|
|
|
6522
|
$e =~ s|^Content-Transfer-Encoding:.+?\n||sim; |
|
404
|
3619
|
|
|
|
|
4952
|
$e =~ s|^Content-Type:\s*text/plain.+?\n||sim; |
|
405
|
3619
|
|
|
|
|
8155
|
$bodystring .= $e; |
|
406
|
|
|
|
|
|
|
} |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# Remove entire message body of the original message beginning from |
|
410
|
|
|
|
|
|
|
# Content-Type: message/rfc822 field so Sisimai does not read the message |
|
411
|
|
|
|
|
|
|
# body for detecting a bounce reason, for getting email header fields of |
|
412
|
|
|
|
|
|
|
# the original message. |
|
413
|
1915
|
|
|
|
|
23278
|
$bodystring =~ s{^(Content-Type:\s*message/(?:rfc822|delivery-status)).+$}{$1}gm; |
|
414
|
1915
|
|
|
|
|
19790
|
$bodystring =~ s|^\n{2,}|\n|gm; |
|
415
|
|
|
|
|
|
|
|
|
416
|
1915
|
|
|
|
|
8405
|
return \$bodystring; |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
1; |
|
420
|
|
|
|
|
|
|
__END__ |