sisimai 4.25.16-java → 5.0.2-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|