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/Repository.mk
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# | _ < __/ |_) | (_) \__ \ | || (_) | | | |_| |_| | | | | | <
|
6
6
|
# |_| \_\___| .__/ \___/|___/_|\__\___/|_| \__, (_)_| |_| |_|_|\_\
|
7
7
|
# |_| |___/
|
8
|
-
#
|
8
|
+
# -------------------------------------------------------------------------------------------------
|
9
9
|
SHELL := /bin/sh
|
10
10
|
GIT ?= git
|
11
11
|
CP := cp
|
@@ -16,8 +16,7 @@ EMAILS = set-of-emails
|
|
16
16
|
|
17
17
|
|
18
18
|
.DEFAULT_GOAL = git-status
|
19
|
-
|
20
|
-
# -----------------------------------------------------------------------------
|
19
|
+
# -------------------------------------------------------------------------------------------------
|
21
20
|
.PHONY: clean
|
22
21
|
|
23
22
|
git-status:
|
data/lib/sisimai/address.rb
CHANGED
@@ -1,9 +1,34 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::Address provide methods for dealing email address.
|
3
3
|
class Address
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
build_regular_expressions = lambda do
|
5
|
+
# See http://www.ietf.org/rfc/rfc5322.txt
|
6
|
+
# or http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html ...
|
7
|
+
# addr-spec = local-part "@" domain
|
8
|
+
# local-part = dot-atom / quoted-string / obs-local-part
|
9
|
+
# domain = dot-atom / domain-literal / obs-domain
|
10
|
+
# domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
|
11
|
+
# dcontent = dtext / quoted-pair
|
12
|
+
# dtext = NO-WS-CTL / ; Non white space controls
|
13
|
+
# %d33-90 / ; The rest of the US-ASCII
|
14
|
+
# %d94-126 ; characters not including "[", "]", or "\"
|
15
|
+
re = { rfc5322: nil, ignored: nil, domain: nil }
|
16
|
+
atom = %r([a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+)o
|
17
|
+
quoted_string = %r/"(?:\\[^\r\n]|[^\\"])*"/o
|
18
|
+
domain_literal = %r/\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\]/o
|
19
|
+
dot_atom = %r/#{atom}(?:[.]#{atom})*/o
|
20
|
+
local_part = %r/(?:#{dot_atom}|#{quoted_string})/o
|
21
|
+
domain = %r/(?:#{dot_atom}|#{domain_literal})/o
|
22
|
+
|
23
|
+
re[:rfc5322] = %r/\A#{local_part}[@]#{domain}\z/o
|
24
|
+
re[:ignored] = %r/\A#{local_part}[.]*[@]#{domain}\z/o
|
25
|
+
re[:domain] = %r/\A#{domain}\z/o
|
26
|
+
|
27
|
+
return re
|
28
|
+
end
|
29
|
+
|
30
|
+
Re = build_regular_expressions.call
|
31
|
+
Indicators = {
|
7
32
|
:'email-address' => (1 << 0), # <neko@example.org>
|
8
33
|
:'quoted-string' => (1 << 1), # "Neko, Nyaan"
|
9
34
|
:'comment-block' => (1 << 2), # (neko)
|
@@ -11,36 +36,44 @@ module Sisimai
|
|
11
36
|
Delimiters = { '<' => 1, '>' => 1, '(' => 1, ')' => 1, '"' => 1, ',' => 1 }.freeze
|
12
37
|
|
13
38
|
# Return pseudo recipient or sender address
|
14
|
-
# @param [Symbol]
|
15
|
-
# @return [String,
|
16
|
-
#
|
17
|
-
def self.undisclosed(
|
18
|
-
return
|
19
|
-
return nil unless %w[r s].index(argv1)
|
20
|
-
|
21
|
-
local = argv1 == 'r' ? 'recipient' : 'sender'
|
22
|
-
return sprintf('undisclosed-%s-in-headers@libsisimai.org.invalid', local)
|
39
|
+
# @param [Symbol] argv0 Address type: true = recipient, false = sender
|
40
|
+
# @return [String, nil] Pseudo recipient address or sender address or nil when the argv1 is
|
41
|
+
# neither :r nor :s
|
42
|
+
def self.undisclosed(argv0 = false)
|
43
|
+
return sprintf('undisclosed-%s-in-headers@libsisimai.org.invalid', argv0 ? 'recipient' : 'sender')
|
23
44
|
end
|
24
45
|
|
25
|
-
#
|
26
|
-
# @param [
|
27
|
-
# @return [
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
return
|
33
|
-
return
|
34
|
-
return
|
35
|
-
|
36
|
-
thing = Sisimai::Address.new(argvs[:address])
|
37
|
-
return nil unless thing
|
38
|
-
return nil if thing.void
|
39
|
-
|
40
|
-
thing.name = argvs[:name] || ''
|
41
|
-
thing.comment = argvs[:comment] || ''
|
46
|
+
# Check that the argument is an email address or not
|
47
|
+
# @param [String] email Email address string
|
48
|
+
# @return [True,False] true: is an email address
|
49
|
+
# false: is not an email address
|
50
|
+
def self.is_emailaddress(email)
|
51
|
+
return false unless email.is_a?(::String)
|
52
|
+
return false if email =~ %r/(?:[\x00-\x1f]|\x1f)/
|
53
|
+
return false if email.size > 254
|
54
|
+
return true if email =~ Re[:ignored]
|
55
|
+
return false
|
56
|
+
end
|
42
57
|
|
43
|
-
|
58
|
+
# Check that the argument is mailer-daemon or not
|
59
|
+
# @param [String] argv0 Email address
|
60
|
+
# @return [True,False] true: mailer-daemon
|
61
|
+
# false: Not mailer-daemon
|
62
|
+
def self.is_mailerdaemon(argv0 = nil)
|
63
|
+
return false unless argv0
|
64
|
+
return false unless argv0.size > 0
|
65
|
+
return false unless argv0.is_a?(::String)
|
66
|
+
|
67
|
+
email = argv0.downcase
|
68
|
+
postmaster = [
|
69
|
+
'mailer-daemon@', '<mailer-daemon>', '(mailer-daemon)', ' mailer-daemon ',
|
70
|
+
'postmaster@', '<postmaster>', '(postmaster)'
|
71
|
+
].freeze
|
72
|
+
|
73
|
+
return true if postmaster.any? { |a| email.include?(a) }
|
74
|
+
return true if email == 'mailer-daemon'
|
75
|
+
return true if email == 'postmaster'
|
76
|
+
return false
|
44
77
|
end
|
45
78
|
|
46
79
|
def self.find(argv1 = nil, addrs = false)
|
@@ -48,8 +81,7 @@ module Sisimai
|
|
48
81
|
# @param [String] argv1 String including email address
|
49
82
|
# @param [Boolean] addrs true: Returns list including all the elements
|
50
83
|
# false: Returns list including email addresses only
|
51
|
-
# @return [Array, Nil] Email address list or nil when there is no
|
52
|
-
# email address in the argument
|
84
|
+
# @return [Array, Nil] Email address list or nil when there is no email address in the argument
|
53
85
|
# @example Parse email address
|
54
86
|
# find('Neko <neko(nyaan)@example.org>')
|
55
87
|
# #=> [{ address: 'neko@example.org', name: 'Neko', comment: '(nyaan)'}]
|
@@ -98,7 +130,7 @@ module Sisimai
|
|
98
130
|
if e == '<'
|
99
131
|
# <: The beginning of an email address or not
|
100
132
|
if v[:address].size > 0
|
101
|
-
p.empty?
|
133
|
+
p.empty? ? (v[:name] << e) : (v[p] << e)
|
102
134
|
else
|
103
135
|
# <neko@nyaan.example.org>
|
104
136
|
readcursor |= Indicators[:'email-address']
|
@@ -213,18 +245,19 @@ module Sisimai
|
|
213
245
|
# String like an email address will be set to the value of "address"
|
214
246
|
v[:address] = cv[1] + '@' + cv[2]
|
215
247
|
|
216
|
-
elsif Sisimai::
|
248
|
+
elsif Sisimai::Address.is_mailerdaemon(v[:name])
|
217
249
|
# Allow if the argument is MAILER-DAEMON
|
218
250
|
v[:address] = v[:name]
|
219
251
|
end
|
220
252
|
|
221
253
|
unless v[:address].empty?
|
222
254
|
# Remove the comment from the address
|
223
|
-
if
|
224
|
-
# (nyaan)nekochan@example.org, nekochan(nyaan)cat@example.org or
|
225
|
-
|
226
|
-
v[:address]
|
227
|
-
v[:
|
255
|
+
if Sisimai::String.aligned(v[:address], ['(', ')'])
|
256
|
+
# (nyaan)nekochan@example.org, nekochan(nyaan)cat@example.org or nekochan(nyaan)@example.org
|
257
|
+
p1 = v[:address].index('(')
|
258
|
+
p2 = v[:address].index(')')
|
259
|
+
v[:address] = v[:address][0, p1] + v[:address][p2 + 1, v[:address].size]
|
260
|
+
v[:comment] = v[:address][p1, p2 - p1 - 1]
|
228
261
|
end
|
229
262
|
readbuffer << v
|
230
263
|
end
|
@@ -233,15 +266,15 @@ module Sisimai
|
|
233
266
|
while e = readbuffer.shift do
|
234
267
|
# The element must not include any character except from 0x20 to 0x7e.
|
235
268
|
next if e[:address] =~ /[^\x20-\x7e]/
|
236
|
-
|
269
|
+
if e[:address].include?('@') == false
|
237
270
|
# Allow if the argument is MAILER-DAEMON
|
238
|
-
next unless Sisimai::
|
271
|
+
next unless Sisimai::Address.is_mailerdaemon(e[:address])
|
239
272
|
end
|
240
273
|
|
241
|
-
# Remove angle brackets, other brackets, and quotations: []<>{}'`
|
242
|
-
#
|
243
|
-
e[:address] = e[:address].
|
244
|
-
e[:address]
|
274
|
+
# Remove angle brackets, other brackets, and quotations: []<>{}'` except a domain part is
|
275
|
+
# an IP address like neko@[192.0.2.222]
|
276
|
+
e[:address] = e[:address].gsub(/\A[\[<{('`]/, '').gsub(/[.,'`>});]\z/, '')
|
277
|
+
e[:address] = e[:address].gsub(/[^A-Za-z]\z/, '') unless e[:address].include?('@[')
|
245
278
|
e[:address] = e[:address].sub(/\A["]/, '').chomp('"') unless e[:address] =~ /\A["].+["][@]/
|
246
279
|
|
247
280
|
if addrs
|
@@ -271,7 +304,7 @@ module Sisimai
|
|
271
304
|
return nil unless input
|
272
305
|
return input unless input.is_a? Object::String
|
273
306
|
|
274
|
-
addrs = Sisimai::Address.find(input,
|
307
|
+
addrs = Sisimai::Address.find(input, true) || []
|
275
308
|
return input if addrs.empty?
|
276
309
|
return addrs[0][:address]
|
277
310
|
end
|
@@ -285,7 +318,7 @@ module Sisimai
|
|
285
318
|
return nil unless email.is_a? Object::String
|
286
319
|
return nil unless cv = email.split('@', 2).first.match(/\A[-\w]+?[+](\w[-.\w]+\w)[=](\w[-.\w]+\w)\z/)
|
287
320
|
verp0 = cv[1] + '@' + cv[2]
|
288
|
-
return verp0 if Sisimai::
|
321
|
+
return verp0 if Sisimai::Address.is_emailaddress(verp0)
|
289
322
|
end
|
290
323
|
|
291
324
|
# Expand alias: remove from '+' to '@'
|
@@ -294,7 +327,7 @@ module Sisimai
|
|
294
327
|
# @example Expand alias
|
295
328
|
# expand_alias('neko+straycat@example.org') #=> 'neko@example.org'
|
296
329
|
def self.expand_alias(email)
|
297
|
-
return nil unless Sisimai::
|
330
|
+
return nil unless Sisimai::Address.is_emailaddress(email)
|
298
331
|
|
299
332
|
local = email.split('@')
|
300
333
|
return nil unless cv = local[0].match(/\A([-\w]+?)[+].+\z/)
|
@@ -312,62 +345,72 @@ module Sisimai
|
|
312
345
|
attr_accessor :name, :comment
|
313
346
|
|
314
347
|
# Constructor of Sisimai::Address
|
315
|
-
# @param
|
316
|
-
# @return
|
317
|
-
#
|
318
|
-
def initialize(
|
319
|
-
return nil unless
|
320
|
-
|
321
|
-
|
322
|
-
return nil unless addrs
|
323
|
-
return nil if addrs.empty?
|
348
|
+
# @param [Hash] argvs Email address, name, and other elements
|
349
|
+
# @return [Sisimai::Address] Object or nil when the email address was not valid.
|
350
|
+
# @example new({address: 'neko@example.org', name: 'Neko', comment: '(nyaan)')} # => Sisimai::Address object
|
351
|
+
def initialize(argvs)
|
352
|
+
return nil unless argvs.is_a? Hash
|
353
|
+
return nil unless argvs[:address]
|
354
|
+
return nil if argvs[:address].empty?
|
324
355
|
|
325
|
-
thing = addrs.shift
|
326
356
|
heads = ['<']
|
327
357
|
tails = ['>', ',', '.', ';']
|
328
|
-
|
329
|
-
if cv =
|
330
|
-
|
358
|
+
point = argvs[:address].rindex('@')
|
359
|
+
if cv = argvs[:address].match(/\A([^\s]+)[@]([^@]+)\z/) ||
|
360
|
+
argvs[:address].match(/\A(["].+?["])[@]([^@]+)\z/)
|
331
361
|
# Get the local part and the domain part from the email address
|
332
|
-
lpart =
|
333
|
-
dpart =
|
334
|
-
email = Sisimai::Address.expand_verp(
|
362
|
+
lpart = argvs[:address][0, point]
|
363
|
+
dpart = argvs[:address][point + 1, argvs[:address].size]
|
364
|
+
email = Sisimai::Address.expand_verp(argvs[:address])
|
335
365
|
aname = nil
|
336
366
|
|
337
367
|
unless email
|
338
368
|
# Is not VERP address, try to expand the address as an alias
|
339
|
-
email = Sisimai::Address.expand_alias(
|
369
|
+
email = Sisimai::Address.expand_alias(argvs[:address]) || ''
|
340
370
|
aname = true unless email.empty?
|
341
371
|
end
|
342
372
|
|
343
|
-
if email
|
373
|
+
if email.include?('@')
|
344
374
|
# The address is a VERP or an alias
|
345
375
|
if aname
|
346
376
|
# The address is an alias: neko+nyaan@example.jp
|
347
|
-
@alias =
|
377
|
+
@alias = argvs[:address]
|
348
378
|
else
|
349
379
|
# The address is a VERP: b+neko=example.jp@example.org
|
350
|
-
@verp =
|
380
|
+
@verp = argvs[:address]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
heads.each do |e|
|
385
|
+
while lpart[0, 1] == e
|
386
|
+
lpart[0, 1] = ''
|
351
387
|
end
|
352
388
|
end
|
389
|
+
|
390
|
+
tails.each do |e|
|
391
|
+
while dpart[-1, 1] == e
|
392
|
+
dpart[-1, 1] = ''
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
353
396
|
@user = lpart
|
354
397
|
@host = dpart
|
355
398
|
@address = lpart + '@' + dpart
|
356
399
|
else
|
357
400
|
# The argument does not include "@"
|
358
|
-
return nil unless Sisimai::
|
359
|
-
return nil if
|
401
|
+
return nil unless Sisimai::Address.is_mailerdaemon(argvs[:address])
|
402
|
+
return nil if argvs[:address].include?(' ')
|
360
403
|
|
361
404
|
# The argument does not include " "
|
362
|
-
@user =
|
405
|
+
@user = argvs[:address]
|
363
406
|
@host ||= ''
|
364
|
-
@address =
|
407
|
+
@address = argvs[:address]
|
365
408
|
end
|
366
409
|
|
367
410
|
@alias ||= ''
|
368
411
|
@verp ||= ''
|
369
|
-
@name =
|
370
|
-
@comment =
|
412
|
+
@name = argvs[:name] || ''
|
413
|
+
@comment = argvs[:comment] || ''
|
371
414
|
end
|
372
415
|
|
373
416
|
# Check whether the object has valid content or not
|
@@ -391,3 +434,4 @@ module Sisimai
|
|
391
434
|
|
392
435
|
end
|
393
436
|
end
|
437
|
+
|
data/lib/sisimai/arf.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module Sisimai
|
2
2
|
# Sisimai::ARF is a parser for email returned as a FeedBack Loop report message.
|
3
3
|
module ARF
|
4
|
-
# Imported from p5-Sisimail/lib/Sisimai/ARF.pm
|
5
4
|
class << self
|
6
5
|
require 'sisimai/lhost'
|
7
6
|
require 'sisimai/rfc5322'
|
@@ -15,23 +14,18 @@ module Sisimai
|
|
15
14
|
# Abusix ARF uses this is an autogenerated email abuse complaint regarding your network.
|
16
15
|
Indicators = Sisimai::Lhost.INDICATORS
|
17
16
|
StartingOf = {
|
18
|
-
rfc822:
|
19
|
-
report:
|
17
|
+
rfc822: ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'],
|
18
|
+
report: ['Content-Type: message/feedback-report'],
|
19
|
+
message: [
|
20
|
+
['this is a', 'abuse report'],
|
21
|
+
['this is a', 'authentication', 'failure report'],
|
22
|
+
['this is a', ' report for'],
|
23
|
+
['this is an authentication', 'failure report'],
|
24
|
+
['this is an autogenerated email abuse complaint'],
|
25
|
+
['this is an email abuse report'],
|
26
|
+
],
|
20
27
|
}.freeze
|
21
|
-
|
22
|
-
message: %r{\A(?>
|
23
|
-
[Tt]his[ ]is[ ]a[ ][^ ]+[ ](?:email[ ])?[Aa]buse[ ][Rr]eport
|
24
|
-
|[Tt]his[ ]is[ ]an[ ]email[ ]abuse[ ]report
|
25
|
-
|[Tt]his[ ]is[ ](?:
|
26
|
-
a[ ][^ ]+[ ]authentication[ -]failure[ ]report
|
27
|
-
|an[ ]authentication[ -]failure[ ]report
|
28
|
-
|an[ ]autogenerated[ ]email[ ]abuse[ ]complaint
|
29
|
-
|an?[ ][^ ]+[ ]report[ ]for
|
30
|
-
)
|
31
|
-
)
|
32
|
-
}x,
|
33
|
-
}.freeze
|
34
|
-
ReportFrom = /(?:staff[@]hotmail[.]com|complaints[@]email-abuse[.]amazonses[.]com)\z/.freeze
|
28
|
+
ReportFrom = ['staff@hotmail.com', 'complaints@email-abuse.amazonses.com'];
|
35
29
|
LongFields = Sisimai::RFC5322.LONGFIELDS
|
36
30
|
RFC822Head = Sisimai::RFC5322.HEADERFIELDS
|
37
31
|
|
@@ -45,19 +39,19 @@ module Sisimai
|
|
45
39
|
return false unless heads
|
46
40
|
match = false
|
47
41
|
|
48
|
-
if heads['content-type']
|
42
|
+
if Sisimai::String.aligned(heads['content-type'], ['report-type=', 'feedback-report'])
|
49
43
|
# Content-Type: multipart/report; report-type=feedback-report; ...
|
50
44
|
match = true
|
51
45
|
|
52
|
-
elsif heads['content-type'].
|
46
|
+
elsif heads['content-type'].include?('multipart/mixed')
|
53
47
|
# Microsoft (Hotmail, MSN, Live, Outlook) uses its own report format.
|
54
48
|
# Amazon SES Complaints bounces
|
55
|
-
|
56
|
-
if
|
49
|
+
cv = Sisimai::Address.s3s4(heads['from'])
|
50
|
+
if heads['subject'].include?('complaint about message from ')
|
57
51
|
# From: staff@hotmail.com
|
58
52
|
# From: complaints@email-abuse.amazonses.com
|
59
53
|
# Subject: complaint about message from 192.0.2.1
|
60
|
-
match = true
|
54
|
+
match = true if ReportFrom.any? { |a| cv.include?(a) }
|
61
55
|
end
|
62
56
|
end
|
63
57
|
return match
|
@@ -68,7 +62,7 @@ module Sisimai
|
|
68
62
|
# @param [String] mbody Message body of a bounce email
|
69
63
|
# @return [Hash] Bounce data list and message/rfc822 part
|
70
64
|
# @return [Nil] it failed to parse or the arguments are missing
|
71
|
-
def
|
65
|
+
def inquire(mhead, mbody)
|
72
66
|
return nil unless self.is_arf(mhead)
|
73
67
|
|
74
68
|
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
@@ -117,8 +111,9 @@ module Sisimai
|
|
117
111
|
# message-id of 0000-000000000000000000000000000000000@mx
|
118
112
|
# received from IP address 192.0.2.1 on
|
119
113
|
# Thu, 29 Apr 2010 00:00:00 +0900 (JST)
|
120
|
-
|
121
|
-
|
114
|
+
p = e.downcase
|
115
|
+
if commondata['diagnosis'].empty?
|
116
|
+
commondata['diagnosis'] = e if StartingOf[:message].any? { |a| Sisimai::String.aligned(p, a) }
|
122
117
|
end
|
123
118
|
|
124
119
|
if readcursor == 0
|
@@ -136,23 +131,22 @@ module Sisimai
|
|
136
131
|
|
137
132
|
if readcursor & Indicators[:'message-rfc822'] > 0
|
138
133
|
# message/rfc822 OR text/rfc822-headers part
|
139
|
-
if
|
134
|
+
if e.start_with?('X-HmXmrOriginalRecipient:')
|
140
135
|
# Microsoft ARF: original recipient.
|
141
|
-
dscontents[-1]['recipient'] = Sisimai::Address.s3s4(
|
136
|
+
dscontents[-1]['recipient'] = Sisimai::Address.s3s4(e[e.index(':') + 1, e.size])
|
142
137
|
recipients += 1
|
143
138
|
|
144
|
-
# The "X-HmXmrOriginalRecipient" header appears only once so
|
145
|
-
#
|
146
|
-
# Microsoft's implementation.
|
139
|
+
# The "X-HmXmrOriginalRecipient" header appears only once so we take this opportunity
|
140
|
+
# to hard-code ARF headers missing in Microsoft's implementation.
|
147
141
|
arfheaders['feedbacktype'] = 'abuse'
|
148
142
|
arfheaders['agent'] = 'Microsoft Junk Mail Reporting Program'
|
149
143
|
|
150
|
-
elsif
|
144
|
+
elsif e.start_with?('From: ')
|
151
145
|
# Microsoft ARF: original sender.
|
152
|
-
commondata['from'] = Sisimai::Address.s3s4(
|
146
|
+
commondata['from'] = Sisimai::Address.s3s4(e[6, e.size]) if commondata['from'].empty?
|
153
147
|
previousfn = 'from'
|
154
148
|
|
155
|
-
elsif e.start_with?(' '
|
149
|
+
elsif e.start_with?(' ')
|
156
150
|
# Continued line from the previous line
|
157
151
|
if previousfn == 'from'
|
158
152
|
# Multiple lines at "From:" field
|
@@ -166,7 +160,7 @@ module Sisimai
|
|
166
160
|
|
167
161
|
else
|
168
162
|
# Get required headers only
|
169
|
-
(lhs, rhs) = e.split(/:[ ]
|
163
|
+
(lhs, rhs) = e.split(/:[ ]/, 2)
|
170
164
|
next unless lhs
|
171
165
|
lhs.downcase!
|
172
166
|
|
@@ -191,10 +185,8 @@ module Sisimai
|
|
191
185
|
# Source-IP: 192.0.2.1
|
192
186
|
v = dscontents[-1]
|
193
187
|
|
194
|
-
if
|
195
|
-
|
196
|
-
# Original-Rcpt-To header field is optional and may appear any
|
197
|
-
# number of times as appropriate:
|
188
|
+
if e.start_with?('Original-Rcpt-To: ', 'Redacted-Address: ')
|
189
|
+
# Original-Rcpt-To header field is optional and may appear any number of times as appropriate:
|
198
190
|
# Original-Rcpt-To: <user@example.com>
|
199
191
|
# Redacted-Address: localpart@
|
200
192
|
if v['recipient']
|
@@ -202,48 +194,45 @@ module Sisimai
|
|
202
194
|
dscontents << Sisimai::Lhost.DELIVERYSTATUS
|
203
195
|
v = dscontents[-1]
|
204
196
|
end
|
205
|
-
v['recipient'] = Sisimai::Address.s3s4(
|
197
|
+
v['recipient'] = Sisimai::Address.s3s4(e[e.index(' ') + 1, e.size])
|
206
198
|
recipients += 1
|
207
199
|
|
208
|
-
elsif
|
200
|
+
elsif e.start_with?('Feedback-Type: ')
|
209
201
|
# The header field MUST appear exactly once.
|
210
202
|
# Feedback-Type: abuse
|
211
|
-
arfheaders['feedbacktype'] =
|
203
|
+
arfheaders['feedbacktype'] = e[e.index(' ') + 1, e.size]
|
212
204
|
|
213
|
-
elsif
|
214
|
-
# "Authentication-Results" indicates the result of one or more
|
215
|
-
#
|
216
|
-
#
|
217
|
-
|
218
|
-
# spf=fail smtp.mail=somespammer@example.com
|
219
|
-
arfheaders['authres'] = cv[1]
|
205
|
+
elsif e.start_with?('Authentication-Results: ')
|
206
|
+
# "Authentication-Results" indicates the result of one or more authentication checks
|
207
|
+
# run by the report generator.
|
208
|
+
# Authentication-Results: mail.example.jp; spf=fail smtp.mail=spammer@example.com
|
209
|
+
arfheaders['authres'] = e[e.index(' ') + 1, e.size]
|
220
210
|
|
221
|
-
elsif
|
211
|
+
elsif e.start_with?('User-Agent: ')
|
222
212
|
# The header field MUST appear exactly once.
|
223
213
|
# User-Agent: SomeGenerator/1.0
|
224
|
-
arfheaders['agent'] =
|
214
|
+
arfheaders['agent'] = e[e.index(' ') + 1, e.size]
|
225
215
|
|
226
|
-
elsif
|
227
|
-
# Arrival-Date header is optional and MUST NOT appear more than
|
228
|
-
# once.
|
216
|
+
elsif e.start_with?('Received-Date: ', 'Arrival-Date: ')
|
217
|
+
# Arrival-Date header is optional and MUST NOT appear more than once.
|
229
218
|
# Received-Date: Thu, 29 Apr 2010 00:00:00 JST
|
230
219
|
# Arrival-Date: Thu, 29 Apr 2010 00:00:00 +0000
|
231
|
-
arfheaders['date'] =
|
220
|
+
arfheaders['date'] = e[e.index(' ') + 1, e.size]
|
232
221
|
|
233
|
-
elsif
|
222
|
+
elsif e.start_with?('Reporting-MTA: dns; ')
|
234
223
|
# The header is optional and MUST NOT appear more than once.
|
235
224
|
# Reporting-MTA: dns; mx.example.jp
|
236
|
-
commondata['rhost'] =
|
225
|
+
commondata['rhost'] = e[e.index(';') + 2, e.size]
|
237
226
|
|
238
|
-
elsif
|
227
|
+
elsif e.start_with?('Source-IP: ')
|
239
228
|
# The header is optional and MUST NOT appear more than once.
|
240
229
|
# Source-IP: 192.0.2.45
|
241
|
-
arfheaders['rhost'] =
|
230
|
+
arfheaders['rhost'] = e[e.index(' ') + 1, e.size]
|
242
231
|
|
243
|
-
elsif
|
232
|
+
elsif e.start_with?('Original-Mail-From: ')
|
244
233
|
# the header is optional and MUST NOT appear more than once.
|
245
234
|
# Original-Mail-From: <somespammer@example.net>
|
246
|
-
commondata['from'] = Sisimai::Address.s3s4(
|
235
|
+
commondata['from'] = Sisimai::Address.s3s4(e[e.index(' ') + 1, e.size]) if commondata['from'].empty?
|
247
236
|
|
248
237
|
end
|
249
238
|
end
|
@@ -254,41 +243,51 @@ module Sisimai
|
|
254
243
|
commondata['diagnosis'] << ' ' << arfheaders['authres']
|
255
244
|
end
|
256
245
|
|
257
|
-
|
246
|
+
if recipients == 0
|
258
247
|
# The original recipient address was not found
|
259
|
-
if
|
248
|
+
if Sisimai::String.aligned(rfc822part, ["\nTo: ", '@'])
|
260
249
|
# pick the address from To: header in message/rfc822 part.
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
250
|
+
p1 = rfc822part.index("\nTo: ") + 5
|
251
|
+
p2 = rfc822part.index("\n", p1 + 1)
|
252
|
+
cm = p2 > 0 ? p2 - p1 : 255
|
253
|
+
dscontents[-1]['recipient'] = Sisimai::Address.s3s4(rfc822part[p1, cm])
|
254
|
+
recipients = 1
|
255
|
+
end
|
256
|
+
|
257
|
+
while true
|
258
|
+
# Insert pseudo recipient address when there is no valid recipient address in the message
|
259
|
+
# for example,
|
260
|
+
# Date: Thu, 29 Apr 2015 23:34:45 +0000
|
261
|
+
# To: "undisclosed"
|
262
|
+
# Subject: Nyaan
|
263
|
+
# Message-ID: <ffffffffffffffffffffffff00000000@example.net>
|
264
|
+
dscontents[-1]['recipient'] ||= ''
|
265
|
+
break if dscontents[-1]['recipient'].include?('@')
|
266
|
+
dscontents[-1]['recipient'] = Sisimai::Address.undisclosed(true)
|
267
|
+
recipients = 1
|
268
|
+
break
|
266
269
|
end
|
267
|
-
recipients = 1
|
268
270
|
end
|
269
271
|
|
270
|
-
unless rfc822part
|
271
|
-
# There is no "From:" header in the original message
|
272
|
-
#
|
272
|
+
unless Sisimai::String.aligned(rfc822part, ['From: ', '@'])
|
273
|
+
# There is no "From:" header in the original message. Append the value of "Original-Mail-From"
|
274
|
+
# value as a sender address.
|
273
275
|
rfc822part << 'From: ' << commondata['from'] + "\n" unless commondata['from'].empty?
|
274
276
|
end
|
275
277
|
|
276
|
-
if
|
278
|
+
if mhead['subject'].include?('complaint about message from ')
|
277
279
|
# Microsoft ARF: remote host address.
|
278
|
-
arfheaders['rhost'] =
|
280
|
+
arfheaders['rhost'] = mhead['subject'][mhead['subject'].rindex(' ') + 1, mhead['subject'].size]
|
279
281
|
commondata['diagnosis'] =
|
280
282
|
'This is a Microsoft email abuse report for an email message received from IP' << arfheaders['rhost'] + ' on ' << mhead['date']
|
281
283
|
end
|
282
284
|
|
283
285
|
dscontents.each do |e|
|
284
|
-
|
285
|
-
|
286
|
-
e['recipient'] = Sisimai::Address.s3s4(rcptintext)
|
287
|
-
end
|
286
|
+
# AOL = http://forums.cpanel.net/f43/aol-brutal-work-71473.html
|
287
|
+
e['recipient'] = Sisimai::Address.s3s4(rcptintext) if e['recipient'][-1, 1] == '@'
|
288
288
|
arfheaders.each_key { |a| e[a] ||= arfheaders[a] || '' }
|
289
289
|
e.delete('authres')
|
290
290
|
|
291
|
-
e['softbounce'] = -1
|
292
291
|
e['diagnosis'] = commondata['diagnosis'] unless e['diagnosis']
|
293
292
|
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
294
293
|
e['date'] = mhead['date'] if e['date'].empty?
|
@@ -302,10 +301,12 @@ module Sisimai
|
|
302
301
|
# The value of "Reporting-MTA" header
|
303
302
|
e['rhost'] = commondata['rhost']
|
304
303
|
|
305
|
-
|
306
|
-
#
|
307
|
-
# from IP address 24.64.1.1
|
308
|
-
|
304
|
+
else
|
305
|
+
# Try to get an IP address from the error message
|
306
|
+
# This is an email abuse report for an email message received from IP address 24.64.1.1
|
307
|
+
# on Thu, 29 Apr 2010 00:00:00 +0000
|
308
|
+
ip = Sisimai::String.ipv4(e['diagnosis']) || []
|
309
|
+
e['rhost'] = ip[0] if ip.size > 0
|
309
310
|
end
|
310
311
|
end
|
311
312
|
return { 'ds' => dscontents, 'rfc822' => rfc822part }
|
@@ -313,3 +314,4 @@ module Sisimai
|
|
313
314
|
end
|
314
315
|
end
|
315
316
|
end
|
317
|
+
|