line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Sisimai::RFC5322; |
2
|
84
|
|
|
84
|
|
60407
|
use feature ':5.10'; |
|
84
|
|
|
|
|
138
|
|
|
84
|
|
|
|
|
5149
|
|
3
|
84
|
|
|
84
|
|
392
|
use strict; |
|
84
|
|
|
|
|
115
|
|
|
84
|
|
|
|
|
1313
|
|
4
|
84
|
|
|
84
|
|
309
|
use warnings; |
|
84
|
|
|
|
|
128
|
|
|
84
|
|
|
|
|
5615
|
|
5
|
84
|
|
|
|
|
114330
|
use constant HEADERTABLE => { |
6
|
|
|
|
|
|
|
'messageid' => ['message-id'], |
7
|
|
|
|
|
|
|
'subject' => ['subject'], |
8
|
|
|
|
|
|
|
'listid' => ['list-id'], |
9
|
|
|
|
|
|
|
'date' => [qw|date posted-date posted resent-date|], |
10
|
|
|
|
|
|
|
'addresser' => [qw|from return-path reply-to errors-to reverse-path x-postfix-sender envelope-from x-envelope-from|], |
11
|
|
|
|
|
|
|
'recipient' => [qw|to delivered-to forward-path envelope-to x-envelope-to resent-to apparently-to|], |
12
|
84
|
|
|
84
|
|
477
|
}; |
|
84
|
|
|
|
|
138
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# Regular expression of valid RFC-5322 email address() |
15
|
|
|
|
|
|
|
my $Re = { 'rfc5322' => undef, 'ignored' => undef, 'domain' => undef, }; |
16
|
|
|
|
|
|
|
BUILD_REGULAR_EXPRESSIONS: { |
17
|
|
|
|
|
|
|
# See http://www.ietf.org/rfc/rfc5322.txt |
18
|
|
|
|
|
|
|
# or http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html ... |
19
|
|
|
|
|
|
|
# addr-spec = local-part "@" domain |
20
|
|
|
|
|
|
|
# local-part = dot-atom / quoted-string / obs-local-part |
21
|
|
|
|
|
|
|
# domain = dot-atom / domain-literal / obs-domain |
22
|
|
|
|
|
|
|
# domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] |
23
|
|
|
|
|
|
|
# dcontent = dtext / quoted-pair |
24
|
|
|
|
|
|
|
# dtext = NO-WS-CTL / ; Non white space controls |
25
|
|
|
|
|
|
|
# %d33-90 / ; The rest of the US-ASCII |
26
|
|
|
|
|
|
|
# %d94-126 ; characters not including "[", |
27
|
|
|
|
|
|
|
# ; "]", or "\" |
28
|
|
|
|
|
|
|
my $atom = qr;[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+;o; |
29
|
|
|
|
|
|
|
my $quoted_string = qr/"(?:\\[^\r\n]|[^\\"])*"/o; |
30
|
|
|
|
|
|
|
my $domain_literal = qr/\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\]/o; |
31
|
|
|
|
|
|
|
my $dot_atom = qr/$atom(?:[.]$atom)*/o; |
32
|
|
|
|
|
|
|
my $local_part = qr/(?:$dot_atom|$quoted_string)/o; |
33
|
|
|
|
|
|
|
my $domain = qr/(?:$dot_atom|$domain_literal)/o; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$Re->{'rfc5322'} = qr/\A$local_part[@]$domain\z/o; |
36
|
|
|
|
|
|
|
$Re->{'ignored'} = qr/\A$local_part[.]*[@]$domain\z/o; |
37
|
|
|
|
|
|
|
$Re->{'domain'} = qr/\A$domain\z/o; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
my $HEADERINDEX = {}; |
41
|
|
|
|
|
|
|
BUILD_FLATTEN_RFC822HEADER_LIST: { |
42
|
|
|
|
|
|
|
# Convert $HEADER: hash reference to flatten hash reference for being |
43
|
|
|
|
|
|
|
# called from Sisimai::Lhost::* |
44
|
|
|
|
|
|
|
for my $v ( values %{ HEADERTABLE() } ) { |
45
|
|
|
|
|
|
|
$HEADERINDEX->{ $_ } = 1 for @$v; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub HEADERFIELDS { |
50
|
|
|
|
|
|
|
# Grouped RFC822 headers |
51
|
|
|
|
|
|
|
# @param [String] group RFC822 Header group name |
52
|
|
|
|
|
|
|
# @return [Array,Hash] RFC822 Header list |
53
|
80
|
|
|
80
|
0
|
27642
|
my $class = shift; |
54
|
80
|
|
100
|
|
|
247
|
my $group = shift || return $HEADERINDEX; |
55
|
76
|
100
|
|
|
|
300
|
return HEADERTABLE->{ $group } if exists HEADERTABLE->{ $group }; |
56
|
75
|
|
|
|
|
171
|
return HEADERTABLE; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub LONGFIELDS { |
60
|
|
|
|
|
|
|
# Fields that might be long |
61
|
|
|
|
|
|
|
# @return [Hash] Long filed(email header) list |
62
|
4
|
|
|
4
|
0
|
17298
|
return { 'to' => 1, 'from' => 1, 'subject' => 1, 'message-id' => 1 }; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
sub is_emailaddress { |
66
|
|
|
|
|
|
|
# Check that the argument is an email address or not |
67
|
|
|
|
|
|
|
# @param [String] email Email address string |
68
|
|
|
|
|
|
|
# @return [Integer] 0: Not email address |
69
|
|
|
|
|
|
|
# 1: Email address |
70
|
6296
|
|
|
6296
|
1
|
19493
|
my $class = shift; |
71
|
6296
|
|
50
|
|
|
10360
|
my $email = shift // return 0; |
72
|
|
|
|
|
|
|
|
73
|
6296
|
50
|
|
|
|
26906
|
return 0 if $email =~ /(?:[\x00-\x1f]|\x1f)/; |
74
|
6296
|
50
|
|
|
|
9289
|
return 0 if length $email > 254; |
75
|
6296
|
100
|
|
|
|
42200
|
return 1 if $email =~ $Re->{'ignored'}; |
76
|
33
|
|
|
|
|
163
|
return 0; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub is_mailerdaemon { |
80
|
|
|
|
|
|
|
# Check that the argument is mailer-daemon or not |
81
|
|
|
|
|
|
|
# @param [String] email Email address |
82
|
|
|
|
|
|
|
# @return [Integer] 0: Not mailer-daemon |
83
|
|
|
|
|
|
|
# 1: Mailer-daemon |
84
|
204
|
|
|
204
|
0
|
131152
|
my $class = shift; |
85
|
204
|
|
50
|
|
|
410
|
my $email = shift // return 0; |
86
|
204
|
|
|
|
|
231
|
state $match = qr{(?> |
87
|
|
|
|
|
|
|
(?:mailer-daemon|postmaster)[@] |
88
|
|
|
|
|
|
|
|[<(](?:mailer-daemon|postmaster)[)>] |
89
|
|
|
|
|
|
|
|\A(?:mailer-daemon|postmaster)\z |
90
|
|
|
|
|
|
|
|[ ]?mailer-daemon[ ] |
91
|
|
|
|
|
|
|
) |
92
|
|
|
|
|
|
|
}x; |
93
|
204
|
100
|
|
|
|
1680
|
return 1 if lc($email) =~ $match; |
94
|
153
|
|
|
|
|
293
|
return 0; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub received { |
98
|
|
|
|
|
|
|
# Convert Received headers to a structured data |
99
|
|
|
|
|
|
|
# @param [String] argv1 Received header |
100
|
|
|
|
|
|
|
# @return [Array] Received header as a structured data |
101
|
2091
|
|
|
2091
|
1
|
40519
|
my $class = shift; |
102
|
2091
|
|
50
|
|
|
3933
|
my $argv1 = shift || return []; |
103
|
2091
|
|
|
|
|
2607
|
my $hosts = []; |
104
|
2091
|
|
|
|
|
4720
|
my $value = { 'from' => '', 'by' => '' }; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Received: (qmail 10000 invoked by uid 999); 24 Apr 2013 00:00:00 +0900 |
107
|
2091
|
100
|
|
|
|
6358
|
return [] if $argv1 =~ /qmail[ \t]+.+invoked[ \t]+/; |
108
|
|
|
|
|
|
|
|
109
|
1973
|
100
|
|
|
|
18223
|
if( $argv1 =~ /\Afrom[ \t]+(.+)[ \t]+by[ \t]+([^ ]+)/ ) { |
|
|
100
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Received: from localhost (localhost) |
111
|
|
|
|
|
|
|
# by nijo.example.jp (V8/cf) id s1QB5ma0018057; |
112
|
|
|
|
|
|
|
# Wed, 26 Feb 2014 06:05:48 -0500 |
113
|
1601
|
|
|
|
|
4218
|
$value->{'from'} = $1; |
114
|
1601
|
|
|
|
|
3026
|
$value->{'by'} = $2; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
} elsif( $argv1 =~ /\bby[ \t]+([^ ]+)(.+)/ ) { |
117
|
|
|
|
|
|
|
# Received: by 10.70.22.98 with SMTP id c2mr1838265pdf.3; Fri, 18 Jul 2014 |
118
|
|
|
|
|
|
|
# 00:31:02 -0700 (PDT) |
119
|
314
|
|
|
|
|
1008
|
$value->{'from'} = $1.$2; |
120
|
314
|
|
|
|
|
576
|
$value->{'by'} = $1; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
1973
|
100
|
|
|
|
5204
|
if( $value->{'from'} =~ / / ) { |
124
|
|
|
|
|
|
|
# Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222]) |
125
|
|
|
|
|
|
|
# (authenticated bits=0) |
126
|
|
|
|
|
|
|
# by nijo.example.jp (V8/cf) with ESMTP id s1QB5ka0018055; |
127
|
|
|
|
|
|
|
# Wed, 26 Feb 2014 06:05:47 -0500 |
128
|
1654
|
|
|
|
|
4861
|
my @received = split(' ', $value->{'from'}); |
129
|
1654
|
|
|
|
|
2515
|
my @namelist; |
130
|
|
|
|
|
|
|
my @addrlist; |
131
|
1654
|
|
|
|
|
1843
|
my $hostname = ''; |
132
|
1654
|
|
|
|
|
1695
|
my $hostaddr = ''; |
133
|
|
|
|
|
|
|
|
134
|
1654
|
|
|
|
|
2304
|
for my $e ( @received ) { |
135
|
|
|
|
|
|
|
# Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222]) |
136
|
7661
|
100
|
|
|
|
11514
|
if( $e =~ /\A[(\[]\d+[.]\d+[.]\d+[.]\d+[)\]]\z/ ) { |
137
|
|
|
|
|
|
|
# [192.0.2.1] or (192.0.2.1) |
138
|
243
|
|
|
|
|
510
|
$e =~ y/[]()//d; |
139
|
243
|
|
|
|
|
439
|
push @addrlist, $e; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
} else { |
142
|
|
|
|
|
|
|
# hostname |
143
|
7418
|
|
|
|
|
7905
|
$e =~ y/()//d; |
144
|
7418
|
|
|
|
|
8845
|
push @namelist, $e; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
1654
|
|
|
|
|
2293
|
for my $e ( @namelist ) { |
149
|
|
|
|
|
|
|
# 1. Hostname takes priority over all other IP addresses |
150
|
1991
|
100
|
|
|
|
3581
|
next unless rindex($e, '.') > -1; |
151
|
1593
|
|
|
|
|
1743
|
$hostname = $e; |
152
|
1593
|
|
|
|
|
1670
|
last; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
1654
|
100
|
|
|
|
2976
|
unless( $hostname ) { |
156
|
|
|
|
|
|
|
# 2. Use IP address as a remote host name |
157
|
61
|
|
|
|
|
112
|
for my $e ( @addrlist ) { |
158
|
|
|
|
|
|
|
# Skip if the address is a private address |
159
|
23
|
50
|
|
|
|
72
|
next if index($e, '10.') == 0; |
160
|
23
|
100
|
|
|
|
63
|
next if index($e, '127.') == 0; |
161
|
18
|
50
|
|
|
|
41
|
next if index($e, '192.168.') == 0; |
162
|
18
|
50
|
|
|
|
45
|
next if $e =~ /\A172[.](?:1[6-9]|2[0-9]|3[0-1])[.]/; |
163
|
18
|
|
|
|
|
29
|
$hostaddr = $e; |
164
|
18
|
|
|
|
|
28
|
last; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
} |
167
|
1654
|
|
100
|
|
|
4833
|
$value->{'from'} = $hostname || $hostaddr || $addrlist[-1]; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
1973
|
|
|
|
|
2768
|
for my $e ('from', 'by') { |
171
|
|
|
|
|
|
|
# Copy entries into $hosts |
172
|
3946
|
100
|
|
|
|
6635
|
next unless defined $value->{ $e }; |
173
|
3908
|
|
|
|
|
4735
|
$value->{ $e } =~ y/()[];?//d; |
174
|
3908
|
|
|
|
|
6105
|
push @$hosts, $value->{ $e }; |
175
|
|
|
|
|
|
|
} |
176
|
1973
|
|
|
|
|
7129
|
return $hosts; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub fillet { |
180
|
|
|
|
|
|
|
# Split given entire message body into error message lines and the original |
181
|
|
|
|
|
|
|
# message part only include email headers |
182
|
|
|
|
|
|
|
# @param [String] mbody Entire message body |
183
|
|
|
|
|
|
|
# @param [Regexp] regex Regular expression of the message/rfc822 or the |
184
|
|
|
|
|
|
|
# beginning of the original message part |
185
|
|
|
|
|
|
|
# @return [Array] [Error message lines, The original message] |
186
|
|
|
|
|
|
|
# @since v4.25.5 |
187
|
2864
|
|
|
2864
|
1
|
6417
|
my $class = shift; |
188
|
2864
|
|
50
|
|
|
7266
|
my $mbody = shift || return undef; |
189
|
2864
|
|
50
|
|
|
6301
|
my $regex = shift || return undef; |
190
|
|
|
|
|
|
|
|
191
|
2864
|
|
100
|
|
|
27168
|
my ($a, $b) = split($regex, $$mbody, 2); $b ||= ''; |
|
2864
|
|
|
|
|
7990
|
|
192
|
2864
|
100
|
|
|
|
5570
|
if( length $b ) { |
193
|
|
|
|
|
|
|
# Remove blank lines, the message body of the original message, and |
194
|
|
|
|
|
|
|
# append "\n" at the end of the original message headers |
195
|
|
|
|
|
|
|
# 1. Remove leading blank lines |
196
|
|
|
|
|
|
|
# 2. Remove text after the first blank line: \n\n |
197
|
|
|
|
|
|
|
# 3. Append "\n" at the end of test block when the last character is not "\n" |
198
|
2248
|
|
|
|
|
10063
|
$b =~ s/\A[\r\n\s]+//m; |
199
|
2248
|
100
|
|
|
|
9816
|
substr($b, index($b, "\n\n") + 1, length($b), '') if index($b, "\n\n") > 0; |
200
|
2248
|
100
|
|
|
|
7300
|
$b .= "\n" unless $b =~ /\n\z/; |
201
|
|
|
|
|
|
|
} |
202
|
2864
|
|
|
|
|
8960
|
return [$a, $b]; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
1; |
206
|
|
|
|
|
|
|
__END__ |