sisimai 4.25.16-java → 5.0.2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/rake-test.yml +55 -0
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +451 -393
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +15 -15
- data/README-JA.md +323 -149
- data/README.md +319 -149
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +506 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -42
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -19
- data/lib/sisimai/lhost/aol.rb +12 -15
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -22
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -20
- data/lib/sisimai/lhost/einsundeins.rb +23 -18
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +179 -174
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -34
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +11 -11
- data/lib/sisimai/lhost/gsuite.rb +21 -28
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +37 -40
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -21
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +16 -17
- data/lib/sisimai/lhost/mxlogic.rb +24 -33
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +64 -28
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -16
- data/lib/sisimai/lhost/postfix.rb +179 -130
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -21
- data/lib/sisimai/lhost/sendgrid.rb +34 -34
- data/lib/sisimai/lhost/sendmail.rb +65 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -21
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +444 -326
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +65 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +96 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +7 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +11 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +34 -36
- data/lib/sisimai/reason/spamdetected.rb +115 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +12 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +177 -146
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/google.rb +530 -0
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +51 -42
- data/lib/sisimai/rhost/nttdocomo.rb +12 -12
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +507 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +46 -31
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +48 -26
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- data/lib/sisimai/rhost/googleapps.rb +0 -261
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
data/lib/sisimai/rfc3834.rb
CHANGED
@@ -1,67 +1,65 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::RFC3834 - RFC3834 auto reply message detector
|
3
3
|
module RFC3834
|
4
|
-
# Imported from p5-Sisimail/lib/Sisimai/RFC3834.pm
|
5
4
|
class << self
|
6
5
|
# http://tools.ietf.org/html/rfc3834
|
7
|
-
MarkingsOf = { :boundary =>
|
8
|
-
|
9
|
-
|
10
|
-
'
|
11
|
-
'
|
12
|
-
'
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|automatic[ ]reply:
|
17
|
-
|out[ ]of[ ](?:the[ ])*office:
|
18
|
-
)
|
19
|
-
/x,
|
6
|
+
MarkingsOf = { :boundary => '__SISIMAI_PSEUDO_BOUNDARY__' }
|
7
|
+
LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
|
8
|
+
DoNotParse = {
|
9
|
+
'from' => ['root@', 'postmaster@', 'mailer-daemon@'],
|
10
|
+
'to' => ['root@'],
|
11
|
+
'subject' => [
|
12
|
+
'security information for', # sudo(1)
|
13
|
+
'mail failure -', # Exim
|
14
|
+
],
|
20
15
|
}.freeze
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
'from' => %r/(?:root|postmaster|mailer-daemon)[@]/,
|
28
|
-
'to' => %r/root[@]/,
|
16
|
+
AutoReply0 = {
|
17
|
+
# http://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xhtml
|
18
|
+
'auto-submitted' => ['auto-generated', 'auto-replied', 'auto-notified'],
|
19
|
+
'precedence' => ['auto_reply'],
|
20
|
+
'subject' => ['auto:', 'auto response:', 'automatic reply:', 'out of office:', 'out of the office:'],
|
21
|
+
'x-apple-action' => ['vacation'],
|
29
22
|
}.freeze
|
30
23
|
SubjectSet = %r{\A(?>
|
31
|
-
(?:.+?)?
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
(?:.+?)?re:
|
25
|
+
|auto(?:[ ]response):
|
26
|
+
|automatic[ ]reply:
|
27
|
+
|out[ ]of[ ]office:
|
35
28
|
)
|
36
29
|
[ ]*(.+)\z
|
37
|
-
}
|
30
|
+
}x.freeze
|
38
31
|
|
39
32
|
# Detect auto reply message as RFC3834
|
40
33
|
# @param [Hash] mhead Message headers of a bounce email
|
41
34
|
# @param [String] mbody Message body of a bounce email
|
42
35
|
# @return [Hash] Bounce data list and message/rfc822 part
|
43
36
|
# @return [Nil] it failed to parse or the arguments are missing
|
44
|
-
def
|
37
|
+
def inquire(mhead, mbody)
|
45
38
|
leave = 0
|
46
39
|
match = 0
|
40
|
+
lower = {}
|
41
|
+
|
42
|
+
LowerLabel.each do |e|
|
43
|
+
# Set lower-cased value of each header related to auto-response
|
44
|
+
next unless mhead.has_key?(e)
|
45
|
+
lower[e] = mhead[e].downcase
|
46
|
+
end
|
47
47
|
|
48
48
|
# DETECT_EXCLUSION_MESSAGE
|
49
|
-
|
49
|
+
DoNotParse.each_key do |e|
|
50
50
|
# Exclude message from root@
|
51
|
-
next unless
|
52
|
-
next unless
|
53
|
-
next unless mhead[e].downcase =~ Excludings[e]
|
51
|
+
next unless lower[e]
|
52
|
+
next unless DoNotParse[e].any? { |a| lower[e].include?(a) }
|
54
53
|
leave = 1
|
55
54
|
break
|
56
55
|
end
|
57
56
|
return nil if leave > 0
|
58
57
|
|
59
|
-
#
|
60
|
-
|
58
|
+
# DETECT_AUTO_REPLY_MESSAGE0
|
59
|
+
AutoReply0.each_key do |e|
|
61
60
|
# RFC3834 Auto-Submitted and other headers
|
62
|
-
next unless
|
63
|
-
next unless
|
64
|
-
next unless mhead[e].downcase =~ AutoReply1[e]
|
61
|
+
next unless lower[e]
|
62
|
+
next unless AutoReply0[e].any? { |a| lower[e].include?(a) }
|
65
63
|
match += 1
|
66
64
|
break
|
67
65
|
end
|
@@ -95,16 +93,15 @@ module Sisimai
|
|
95
93
|
return nil unless recipients > 0
|
96
94
|
|
97
95
|
if mhead['content-type']
|
98
|
-
# Get the boundary string and set regular expression for matching with
|
99
|
-
|
100
|
-
|
101
|
-
MarkingsOf[:boundary] = %r/\A\Q#{b0}\E\z/ unless b0.empty?
|
96
|
+
# Get the boundary string and set regular expression for matching with the boundary string.
|
97
|
+
q = Sisimai::RFC2045.boundary(mhead['content-type'], 0) || ''
|
98
|
+
MarkingsOf[:boundary] = q unless q.empty?
|
102
99
|
end
|
103
100
|
|
104
101
|
# BODY_PARSER: Get vacation message
|
105
102
|
while e = bodyslices.shift do
|
106
103
|
# Read the first 5 lines except a blank line
|
107
|
-
countuntil += 1 if e
|
104
|
+
countuntil += 1 if e.include?(MarkingsOf[:boundary])
|
108
105
|
|
109
106
|
if e.empty?
|
110
107
|
# Check a blank line
|
@@ -113,8 +110,8 @@ module Sisimai
|
|
113
110
|
next
|
114
111
|
end
|
115
112
|
next unless e.include?(' ')
|
116
|
-
next if
|
117
|
-
next if
|
113
|
+
next if e.start_with?('Content-Type')
|
114
|
+
next if e.start_with?('Content-Transfer')
|
118
115
|
|
119
116
|
v['diagnosis'] ||= ''
|
120
117
|
v['diagnosis'] << e + ' '
|
@@ -127,7 +124,7 @@ module Sisimai
|
|
127
124
|
v['date'] = mhead['date']
|
128
125
|
v['status'] = ''
|
129
126
|
|
130
|
-
if cv =
|
127
|
+
if cv = lower['subject'].match(SubjectSet)
|
131
128
|
# Get the Subject header from the original message
|
132
129
|
rfc822part = 'Subject: ' << cv[1] + "\n"
|
133
130
|
end
|
data/lib/sisimai/rfc5322.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::RFC5322 provide methods for checking email address.
|
3
3
|
module RFC5322
|
4
|
-
# Imported from p5-Sisimail/lib/Sisimai/RFC5322.pm
|
5
4
|
class << self
|
5
|
+
require 'sisimai/string'
|
6
|
+
require 'sisimai/address'
|
6
7
|
HeaderTable = {
|
7
8
|
:messageid => %w[message-id],
|
8
9
|
:subject => %w[subject],
|
@@ -12,46 +13,26 @@ module Sisimai
|
|
12
13
|
:recipient => %w[to delivered-to forward-path envelope-to x-envelope-to resent-to apparently-to],
|
13
14
|
}.freeze
|
14
15
|
|
15
|
-
build_regular_expressions = lambda do
|
16
|
-
# See http://www.ietf.org/rfc/rfc5322.txt
|
17
|
-
# or http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html ...
|
18
|
-
# addr-spec = local-part "@" domain
|
19
|
-
# local-part = dot-atom / quoted-string / obs-local-part
|
20
|
-
# domain = dot-atom / domain-literal / obs-domain
|
21
|
-
# domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
|
22
|
-
# dcontent = dtext / quoted-pair
|
23
|
-
# dtext = NO-WS-CTL / ; Non white space controls
|
24
|
-
# %d33-90 / ; The rest of the US-ASCII
|
25
|
-
# %d94-126 ; characters not including "[",
|
26
|
-
# ; "]", or "\"
|
27
|
-
re = { rfc5322: nil, ignored: nil, domain: nil }
|
28
|
-
atom = %r([a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+)o
|
29
|
-
quoted_string = %r/"(?:\\[^\r\n]|[^\\"])*"/o
|
30
|
-
domain_literal = %r/\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\]/o
|
31
|
-
dot_atom = %r/#{atom}(?:[.]#{atom})*/o
|
32
|
-
local_part = %r/(?:#{dot_atom}|#{quoted_string})/o
|
33
|
-
domain = %r/(?:#{dot_atom}|#{domain_literal})/o
|
34
|
-
|
35
|
-
re[:rfc5322] = %r/\A#{local_part}[@]#{domain}\z/o
|
36
|
-
re[:ignored] = %r/\A#{local_part}[.]*[@]#{domain}\z/o
|
37
|
-
re[:domain] = %r/\A#{domain}\z/o
|
38
|
-
|
39
|
-
return re
|
40
|
-
end
|
41
|
-
|
42
16
|
build_flatten_rfc822header_list = lambda do
|
43
|
-
# Convert HEADER: structured hash table to flatten hash table for being
|
44
|
-
# called from Sisimai::Lhost::*
|
17
|
+
# Convert HEADER: structured hash table to flatten hash table for being called from Sisimai::Lhost::*
|
45
18
|
fv = {}
|
46
19
|
HeaderTable.each_value do |e|
|
47
20
|
e.each { |ee| fv[ee] = true }
|
48
21
|
end
|
49
22
|
return fv
|
50
23
|
end
|
51
|
-
|
52
|
-
Re = build_regular_expressions.call
|
53
24
|
HeaderIndex = build_flatten_rfc822header_list.call
|
54
25
|
|
26
|
+
def FIELDINDEX
|
27
|
+
return %w[
|
28
|
+
Resent-Date From Sender Reply-To To Message-ID Subject Return-Path Received Date X-Mailer
|
29
|
+
Content-Type Content-Transfer-Encoding Content-Description Content-Disposition
|
30
|
+
]
|
31
|
+
# The following fields are not referred in Sisimai
|
32
|
+
# Resent-From Resent-Sender Resent-Cc Cc Bcc Resent-Bcc In-Reply-To References
|
33
|
+
# Comments Keywords
|
34
|
+
end
|
35
|
+
|
55
36
|
# Grouped RFC822 headers
|
56
37
|
# @param [Symbol] group RFC822 Header group name
|
57
38
|
# @return [Array,Hash] RFC822 Header list
|
@@ -67,140 +48,190 @@ module Sisimai
|
|
67
48
|
return { 'to' => true, 'from' => true, 'subject' => true, 'message-id' => true }
|
68
49
|
end
|
69
50
|
|
70
|
-
# Check that the argument is an email address or not
|
71
|
-
# @param [String] email Email address string
|
72
|
-
# @return [True,False] true: is an email address
|
73
|
-
# false: is not an email address
|
74
|
-
def is_emailaddress(email)
|
75
|
-
return false unless email.is_a?(::String)
|
76
|
-
return false if email =~ %r/(?:[\x00-\x1f]|\x1f)/
|
77
|
-
return false if email.size > 254
|
78
|
-
return true if email =~ Re[:ignored]
|
79
|
-
return false
|
80
|
-
end
|
81
|
-
|
82
|
-
# Check that the argument is mailer-daemon or not
|
83
|
-
# @param [String] email Email address
|
84
|
-
# @return [True,False] true: mailer-daemon
|
85
|
-
# false: Not mailer-daemon
|
86
|
-
def is_mailerdaemon(email)
|
87
|
-
return false unless email.is_a?(::String)
|
88
|
-
regex = %r/(?:
|
89
|
-
(?:mailer-daemon|postmaster)[@]
|
90
|
-
|[<(](?:mailer-daemon|postmaster)[)>]
|
91
|
-
|\A(?:mailer-daemon|postmaster)\z
|
92
|
-
|[ ]?mailer-daemon[ ]
|
93
|
-
)
|
94
|
-
/x.freeze
|
95
|
-
return true if email.downcase =~ regex
|
96
|
-
return false
|
97
|
-
end
|
98
|
-
|
99
51
|
# Convert Received headers to a structured data
|
100
|
-
# @param [String]
|
52
|
+
# @param [String] argv1 Received header
|
101
53
|
# @return [Array] Received header as a structured data
|
102
|
-
def received(
|
103
|
-
return [] unless
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
54
|
+
def received(argv1)
|
55
|
+
return [] unless argv1.is_a?(::String)
|
56
|
+
return [] if argv1.include?(' invoked by uid')
|
57
|
+
return [] if argv1.include?(' invoked from network')
|
58
|
+
|
59
|
+
# - https://datatracker.ietf.org/doc/html/rfc5322
|
60
|
+
# received = "Received:" *received-token ";" date-time CRLF
|
61
|
+
# received-token = word / angle-addr / addr-spec / domain
|
62
|
+
#
|
63
|
+
# - Appendix A.4. Message with Trace Fields
|
64
|
+
# Received:
|
65
|
+
# from x.y.test
|
66
|
+
# by example.net
|
67
|
+
# via TCP
|
68
|
+
# with ESMTP
|
69
|
+
# id ABC12345
|
70
|
+
# for <mary@example.net>; 21 Nov 1997 10:05:43 -0600
|
71
|
+
recvd = argv1.split(' ')
|
72
|
+
label = %w[from by via with id for]
|
73
|
+
token = {}
|
74
|
+
other = []
|
75
|
+
alter = []
|
76
|
+
right = false
|
77
|
+
range = recvd.size
|
78
|
+
index = -1
|
79
|
+
|
80
|
+
recvd.each do |e|
|
81
|
+
# Look up each label defined in "label" from Received header
|
82
|
+
index += 1
|
83
|
+
break unless index < range; f = e.downcase
|
84
|
+
next unless label.any? { |a| f == a }
|
85
|
+
token[f] = recvd[index + 1] || next
|
86
|
+
token[f] = token[f].downcase.delete('();')
|
87
|
+
|
88
|
+
next unless f == 'from'
|
89
|
+
break unless index + 2 < range
|
90
|
+
next unless recvd[index + 2].start_with?('(')
|
91
|
+
|
92
|
+
# Get and keep a hostname in the comment as follows:
|
93
|
+
# from mx1.example.com (c213502.kyoto.example.ne.jp [192.0.2.135]) by mx.example.jp (V8/cf)
|
94
|
+
# [
|
95
|
+
# "from", # index + 0
|
96
|
+
# "mx1.example.com", # index + 1
|
97
|
+
# "(c213502.kyoto.example.ne.jp", # index + 2
|
98
|
+
# "[192.0.2.135])", # index + 3
|
99
|
+
# "by",
|
100
|
+
# "mx.example.jp",
|
101
|
+
# "(V8/cf)",
|
102
|
+
# ...
|
103
|
+
# ]
|
104
|
+
# The 2nd element after the current element is NOT a continuation of the current element
|
105
|
+
# such as "(c213502.kyoto.example.ne.jp)"
|
106
|
+
other << recvd[index + 2].delete('();')
|
107
|
+
|
108
|
+
# The 2nd element after the current element is a continuation of the current element.
|
109
|
+
# such as "(c213502.kyoto.example.ne.jp", "[192.0.2.135])"
|
110
|
+
break unless index + 3 < range
|
111
|
+
other << recvd[index + 3].delete('();')
|
123
112
|
end
|
124
113
|
|
125
|
-
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
# Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
|
138
|
-
if e =~ /\A[\[(]\d+[.]\d+[.]\d+[.]\d+[)\]]\z/
|
139
|
-
# [192.0.2.1] or (192.0.2.1)
|
140
|
-
e = e.delete('[]()')
|
141
|
-
addrlist << e
|
142
|
-
else
|
143
|
-
# hostname
|
144
|
-
e = e.delete('()')
|
145
|
-
namelist << e
|
146
|
-
end
|
147
|
-
end
|
114
|
+
other.each do |e|
|
115
|
+
# Check alternatives in "other", and then delete uninformative values.
|
116
|
+
next if e.nil?
|
117
|
+
next if e.size < 4
|
118
|
+
next if e == 'unknown'
|
119
|
+
next if e == 'localhost'
|
120
|
+
next if e == '[127.0.0.1]'
|
121
|
+
next if e == '[IPv6:::1]'
|
122
|
+
next unless e.include?('.')
|
123
|
+
next if e.include?('=')
|
124
|
+
alter << e
|
125
|
+
end
|
148
126
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
127
|
+
%w[from by].each do |e|
|
128
|
+
# Remove square brackets from the IP address such as "[192.0.2.25]"
|
129
|
+
next if token[e].nil?
|
130
|
+
next if token[e].empty?
|
131
|
+
next unless token[e].start_with?('[')
|
132
|
+
token[e] = Sisimai::String.ipv4(token[e]).shift || ''
|
133
|
+
end
|
134
|
+
token['from'] ||= ''
|
135
|
+
|
136
|
+
while true do
|
137
|
+
# Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
|
138
|
+
break if token['from'] == 'localhost'
|
139
|
+
break if token['from'] == 'localhost.localdomain'
|
140
|
+
break unless token['from'].include?('.') # A hostname without a domain name
|
141
|
+
break unless Sisimai::String.ipv4(token['from']).empty?
|
142
|
+
|
143
|
+
# No need to rewrite token['from']
|
144
|
+
right = true
|
145
|
+
break
|
146
|
+
end
|
155
147
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
148
|
+
while true do
|
149
|
+
# Try to rewrite uninformative hostnames and IP addresses in token['from']
|
150
|
+
break if right # There is no need to rewrite
|
151
|
+
break if alter.empty? # There is no alternative to rewriting
|
152
|
+
break if alter[0].include?(token['from'])
|
153
|
+
|
154
|
+
if token['from'].start_with?('localhost')
|
155
|
+
# localhost or localhost.localdomain
|
156
|
+
token['from'] = alter[0]
|
157
|
+
elsif token['from'].index('.')
|
158
|
+
# A hostname without a domain name such as "mail", "mx", or "mbox"
|
159
|
+
token['from'] = alter[0] if alter[0].include?('.')
|
160
|
+
else
|
161
|
+
# An IPv4 address
|
162
|
+
token['from'] = alter[0]
|
165
163
|
end
|
166
|
-
|
167
|
-
value['from'] = hostname || hostaddr || addrlist[-1]
|
164
|
+
break
|
168
165
|
end
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
166
|
+
token.delete('from') if token['from'].nil?
|
167
|
+
token.delete('by') if token['by'].nil?
|
168
|
+
token['for'] = Sisimai::Address.s3s4(token['for']) if token.has_key?('for')
|
169
|
+
|
170
|
+
token.keys.each do |e|
|
171
|
+
# Delete an invalid value
|
172
|
+
token[e] = '' if token[e].include?(' ')
|
173
|
+
token[e].delete!('[]') # Remove "[]" from the IP address
|
175
174
|
end
|
176
|
-
|
175
|
+
|
176
|
+
return [
|
177
|
+
token['from'] || '',
|
178
|
+
token['by'] || '',
|
179
|
+
token['via'] || '',
|
180
|
+
token['with'] || '',
|
181
|
+
token['id'] || '',
|
182
|
+
token['for'] || '',
|
183
|
+
]
|
177
184
|
end
|
178
185
|
|
179
|
-
# Split given entire message body into error message lines and the original
|
180
|
-
#
|
181
|
-
# @param [String]
|
182
|
-
# @param [
|
183
|
-
#
|
186
|
+
# Split given entire message body into error message lines and the original message part only
|
187
|
+
# include email headers
|
188
|
+
# @param [String] email Entire message body
|
189
|
+
# @param [Array] cutby List of strings which is a boundary of the original message part
|
190
|
+
# @param [Bool] keeps Flag for keeping strings after "\n\n"
|
184
191
|
# @return [Array] [Error message lines, The original message]
|
185
|
-
# @since
|
186
|
-
def
|
187
|
-
return nil if
|
188
|
-
return nil
|
192
|
+
# @since v5.0.0
|
193
|
+
def part(email = '', cutby = [], keeps = false)
|
194
|
+
return nil if email.empty?
|
195
|
+
return nil if cutby.empty?
|
196
|
+
|
197
|
+
boundaryor = '' # A boundary string divides the error message part and the original message part
|
198
|
+
positionor = nil # A Position of the boundary string
|
199
|
+
formerpart = '' # The error message part
|
200
|
+
latterpart = '' # The original message part
|
201
|
+
|
202
|
+
cutby.each do |e|
|
203
|
+
# Find a boundary string(2nd argument) from the 1st argument
|
204
|
+
positionor = email.index(e); next unless positionor
|
205
|
+
boundaryor = e
|
206
|
+
break
|
207
|
+
end
|
189
208
|
|
190
|
-
|
191
|
-
|
209
|
+
if positionor
|
210
|
+
# There is the boundary string in the message body
|
211
|
+
formerpart = email[0, positionor]
|
212
|
+
latterpart = email[positionor + boundaryor.size + 1, email.size - positionor]
|
213
|
+
else
|
214
|
+
# Substitute the entire message to the former part when the boundary string is not included
|
215
|
+
# the "email"
|
216
|
+
formerpart = email
|
217
|
+
latterpart = ''
|
218
|
+
end
|
192
219
|
|
193
|
-
|
220
|
+
if latterpart.size > 0
|
194
221
|
# Remove blank lines, the message body of the original message, and append "\n" at the end
|
195
222
|
# of the original message headers
|
196
223
|
# 1. Remove leading blank lines
|
197
224
|
# 2. Remove text after the first blank line: \n\n
|
198
225
|
# 3. Append "\n" at the end of test block when the last character is not "\n"
|
199
|
-
|
200
|
-
|
201
|
-
|
226
|
+
latterpart.sub!(/\A[\r\n\s]+/, '')
|
227
|
+
if keeps == false
|
228
|
+
# Remove text after the first blank line: \n\n when "keeps" is false
|
229
|
+
latterpart = latterpart[0, latterpart.index("\n\n")] if latterpart.include?("\n\n")
|
230
|
+
end
|
231
|
+
latterpart << "\n" unless latterpart.end_with?("\n")
|
202
232
|
end
|
203
|
-
|
233
|
+
|
234
|
+
return [formerpart, latterpart]
|
204
235
|
end
|
205
236
|
|
206
237
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sisimai
|
2
|
+
# Sisimai::RFC5965 - A class for An Extensible Format for Email Feedback Reports
|
3
|
+
module RFC5965
|
4
|
+
class << self
|
5
|
+
# https://datatracker.ietf.org/doc/html/rfc5965
|
6
|
+
def FIELDINDEX
|
7
|
+
return [
|
8
|
+
# Required Fields
|
9
|
+
# The following report header fields MUST appear exactly once:
|
10
|
+
'Feedback-Type', 'User-Agent', 'Version',
|
11
|
+
|
12
|
+
# Optional Fields Appearing Once
|
13
|
+
# The following header fields are optional and MUST NOT appear more than once:
|
14
|
+
# - "Reporting-MTA" is defined in Sisimai::RFC1894->FIELDINDEX()
|
15
|
+
'Original-Envelope-Id', 'Original-Mail-From', 'Arrival-Date', 'Source-IP', 'Incidents',
|
16
|
+
|
17
|
+
# Optional Fields Appearing Multiple Times
|
18
|
+
# The following set of header fields are optional and may appear any number of times as
|
19
|
+
# appropriate:
|
20
|
+
'Authentication-Results', 'Original-Rcpt-To', 'Reported-Domain', 'Reported-URI',
|
21
|
+
|
22
|
+
# The historic field "Received-Date" SHOULD also be accepted and interpreted identically
|
23
|
+
# to "Arrival-Date". However, if both are present, the report is malformed and SHOULD be
|
24
|
+
# treated as described in Section 4.
|
25
|
+
'Received-Date',
|
26
|
+
]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/lib/sisimai/rhost/cox.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
module Sisimai
|
2
2
|
module Rhost
|
3
|
-
# Sisimai::Rhost detects the bounce reason from the content of Sisimai::
|
4
|
-
#
|
5
|
-
#
|
3
|
+
# Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument
|
4
|
+
# of get() method when the value of "destination" of the object is "charter.net". This class is
|
5
|
+
# called only Sisimai::Fact class.
|
6
6
|
module Cox
|
7
7
|
class << self
|
8
|
-
# Imported from p5-Sisimail/lib/Sisimai/Rhost/Cox.pm
|
9
8
|
ErrorCodes = {
|
10
9
|
# https://www.cox.com/residential/support/email-error-codes.html
|
11
10
|
'CXBL' => 'blocked', # The sending IP address has been blocked by Cox due to exhibiting spam-like behavior.
|
@@ -79,11 +78,11 @@ module Sisimai
|
|
79
78
|
}.freeze
|
80
79
|
|
81
80
|
# Detect bounce reason from https://cox.com/
|
82
|
-
# @param [Sisimai::
|
81
|
+
# @param [Sisimai::Fact] argvs Parsed email object
|
83
82
|
# @return [String, Nil] The bounce reason at Cox
|
84
83
|
# @since v4.25.8
|
85
84
|
def get(argvs)
|
86
|
-
statusmesg = argvs
|
85
|
+
statusmesg = argvs['diagnosticcode']
|
87
86
|
codenumber = 0
|
88
87
|
|
89
88
|
if cv = statusmesg.match(/AUP#([0-9A-Z]+)/)
|
@@ -1,12 +1,10 @@
|
|
1
1
|
module Sisimai
|
2
2
|
module Rhost
|
3
|
-
# Sisimai::Rhost detects the bounce reason from the content of Sisimai::
|
4
|
-
#
|
5
|
-
# is
|
6
|
-
# This class is called only Sisimai::Data class.
|
3
|
+
# Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument
|
4
|
+
# of get() method when the value of "rhost" of the object is "*.laposte.net" or "*.orange.fr".
|
5
|
+
# This class is called only Sisimai::Fact class.
|
7
6
|
module FrancePTT
|
8
7
|
class << self
|
9
|
-
# Imported from p5-Sisimail/lib/Sisimai/Rhost/FrancePTT.pm
|
10
8
|
ErrorCodes = {
|
11
9
|
# 550 5.7.1 Service unavailable; client [X.X.X.X] blocked using Spamhaus
|
12
10
|
# Les emails envoyes vers la messagerie Laposte.net ont ete bloques par nos services.
|
@@ -133,12 +131,12 @@ module Sisimai
|
|
133
131
|
}.freeze
|
134
132
|
|
135
133
|
# Detect bounce reason from Oranage or La Poste
|
136
|
-
# @param [Sisimai::
|
134
|
+
# @param [Sisimai::Fact] argvs Parsed email object
|
137
135
|
# @return [String] The bounce reason for Orange or La Poste
|
138
136
|
def get(argvs)
|
139
|
-
return argvs
|
137
|
+
return argvs['reason'] unless argvs['reason'].empty?
|
140
138
|
|
141
|
-
statusmesg = argvs
|
139
|
+
statusmesg = argvs['diagnosticcode']
|
142
140
|
reasontext = ''
|
143
141
|
|
144
142
|
if cv = statusmesg.match(/\b(LPN|LPNAAA|OFR|OUK)(_[0-9]{3}|[0-9]{3}[-_][0-9]{3})\b/i)
|