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.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rake-test.yml +55 -0
  3. data/.travis.yml +3 -3
  4. data/ANALYTICAL-PRECISION +2 -2
  5. data/Benchmarks.mk +3 -3
  6. data/CONTRIBUTING +1 -1
  7. data/ChangeLog.md +451 -393
  8. data/Developers.mk +5 -6
  9. data/Gemfile +1 -1
  10. data/Makefile +15 -15
  11. data/README-JA.md +323 -149
  12. data/README.md +319 -149
  13. data/Rakefile +9 -3
  14. data/Repository.mk +2 -3
  15. data/lib/sisimai/address.rb +118 -74
  16. data/lib/sisimai/arf.rb +84 -82
  17. data/lib/sisimai/datetime.rb +5 -52
  18. data/lib/sisimai/{data → fact}/json.rb +7 -9
  19. data/lib/sisimai/fact/yaml.rb +31 -0
  20. data/lib/sisimai/fact.rb +506 -0
  21. data/lib/sisimai/lhost/activehunter.rb +12 -14
  22. data/lib/sisimai/lhost/amavis.rb +11 -14
  23. data/lib/sisimai/lhost/amazonses.rb +37 -42
  24. data/lib/sisimai/lhost/amazonworkmail.rb +15 -19
  25. data/lib/sisimai/lhost/aol.rb +12 -15
  26. data/lib/sisimai/lhost/apachejames.rb +19 -21
  27. data/lib/sisimai/lhost/barracuda.rb +10 -12
  28. data/lib/sisimai/lhost/bigfoot.rb +21 -22
  29. data/lib/sisimai/lhost/biglobe.rb +15 -16
  30. data/lib/sisimai/lhost/courier.rb +20 -20
  31. data/lib/sisimai/lhost/domino.rb +23 -20
  32. data/lib/sisimai/lhost/einsundeins.rb +23 -18
  33. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  34. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  35. data/lib/sisimai/lhost/exim.rb +179 -174
  36. data/lib/sisimai/lhost/ezweb.rb +31 -56
  37. data/lib/sisimai/lhost/facebook.rb +21 -34
  38. data/lib/sisimai/lhost/fml.rb +43 -48
  39. data/lib/sisimai/lhost/gmail.rb +29 -29
  40. data/lib/sisimai/lhost/gmx.rb +18 -17
  41. data/lib/sisimai/lhost/googlegroups.rb +11 -11
  42. data/lib/sisimai/lhost/gsuite.rb +21 -28
  43. data/lib/sisimai/lhost/imailserver.rb +25 -39
  44. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  45. data/lib/sisimai/lhost/kddi.rb +22 -28
  46. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  47. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  48. data/lib/sisimai/lhost/mailru.rb +37 -40
  49. data/lib/sisimai/lhost/mcafee.rb +21 -31
  50. data/lib/sisimai/lhost/messagelabs.rb +17 -21
  51. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  52. data/lib/sisimai/lhost/mfilter.rb +16 -17
  53. data/lib/sisimai/lhost/mxlogic.rb +24 -33
  54. data/lib/sisimai/lhost/notes.rb +17 -17
  55. data/lib/sisimai/lhost/office365.rb +64 -28
  56. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  57. data/lib/sisimai/lhost/outlook.rb +12 -16
  58. data/lib/sisimai/lhost/postfix.rb +179 -130
  59. data/lib/sisimai/lhost/powermta.rb +12 -14
  60. data/lib/sisimai/lhost/qmail.rb +44 -47
  61. data/lib/sisimai/lhost/receivingses.rb +15 -21
  62. data/lib/sisimai/lhost/sendgrid.rb +34 -34
  63. data/lib/sisimai/lhost/sendmail.rb +65 -53
  64. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  65. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  66. data/lib/sisimai/lhost/verizon.rb +35 -39
  67. data/lib/sisimai/lhost/x1.rb +18 -17
  68. data/lib/sisimai/lhost/x2.rb +17 -14
  69. data/lib/sisimai/lhost/x3.rb +19 -19
  70. data/lib/sisimai/lhost/x4.rb +72 -57
  71. data/lib/sisimai/lhost/x5.rb +17 -19
  72. data/lib/sisimai/lhost/x6.rb +41 -17
  73. data/lib/sisimai/lhost/yahoo.rb +17 -16
  74. data/lib/sisimai/lhost/yandex.rb +16 -21
  75. data/lib/sisimai/lhost/zoho.rb +16 -15
  76. data/lib/sisimai/lhost.rb +8 -10
  77. data/lib/sisimai/mail/maildir.rb +1 -3
  78. data/lib/sisimai/mail/mbox.rb +3 -4
  79. data/lib/sisimai/mail/memory.rb +0 -1
  80. data/lib/sisimai/mail/stdin.rb +1 -3
  81. data/lib/sisimai/mail.rb +3 -7
  82. data/lib/sisimai/mda.rb +28 -42
  83. data/lib/sisimai/message.rb +444 -326
  84. data/lib/sisimai/order.rb +5 -5
  85. data/lib/sisimai/reason/authfailure.rb +65 -0
  86. data/lib/sisimai/reason/badreputation.rb +53 -0
  87. data/lib/sisimai/reason/blocked.rb +96 -160
  88. data/lib/sisimai/reason/contenterror.rb +8 -9
  89. data/lib/sisimai/reason/delivered.rb +4 -6
  90. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  91. data/lib/sisimai/reason/expired.rb +7 -8
  92. data/lib/sisimai/reason/feedback.rb +2 -3
  93. data/lib/sisimai/reason/filtered.rb +17 -19
  94. data/lib/sisimai/reason/hasmoved.rb +9 -10
  95. data/lib/sisimai/reason/hostunknown.rb +15 -15
  96. data/lib/sisimai/reason/mailboxfull.rb +11 -12
  97. data/lib/sisimai/reason/mailererror.rb +18 -20
  98. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  99. data/lib/sisimai/reason/networkerror.rb +5 -8
  100. data/lib/sisimai/reason/norelaying.rb +8 -11
  101. data/lib/sisimai/reason/notaccept.rb +13 -14
  102. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  103. data/lib/sisimai/reason/onhold.rb +6 -9
  104. data/lib/sisimai/reason/policyviolation.rb +14 -12
  105. data/lib/sisimai/reason/rejected.rb +26 -24
  106. data/lib/sisimai/reason/requireptr.rb +69 -0
  107. data/lib/sisimai/reason/securityerror.rb +34 -36
  108. data/lib/sisimai/reason/spamdetected.rb +115 -147
  109. data/lib/sisimai/reason/speeding.rb +49 -0
  110. data/lib/sisimai/reason/suspend.rb +12 -11
  111. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  112. data/lib/sisimai/reason/systemerror.rb +7 -9
  113. data/lib/sisimai/reason/systemfull.rb +7 -8
  114. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  115. data/lib/sisimai/reason/undefined.rb +2 -3
  116. data/lib/sisimai/reason/userunknown.rb +129 -146
  117. data/lib/sisimai/reason/vacation.rb +3 -4
  118. data/lib/sisimai/reason/virusdetected.rb +10 -11
  119. data/lib/sisimai/reason.rb +59 -64
  120. data/lib/sisimai/rfc1894.rb +55 -28
  121. data/lib/sisimai/rfc2045.rb +373 -0
  122. data/lib/sisimai/rfc3464.rb +250 -308
  123. data/lib/sisimai/rfc3834.rb +42 -45
  124. data/lib/sisimai/rfc5322.rb +177 -146
  125. data/lib/sisimai/rfc5965.rb +31 -0
  126. data/lib/sisimai/rhost/cox.rb +5 -6
  127. data/lib/sisimai/rhost/franceptt.rb +6 -8
  128. data/lib/sisimai/rhost/godaddy.rb +12 -12
  129. data/lib/sisimai/rhost/google.rb +530 -0
  130. data/lib/sisimai/rhost/iua.rb +9 -10
  131. data/lib/sisimai/rhost/kddi.rb +6 -8
  132. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  133. data/lib/sisimai/rhost/mimecast.rb +51 -42
  134. data/lib/sisimai/rhost/nttdocomo.rb +12 -12
  135. data/lib/sisimai/rhost/spectrum.rb +10 -12
  136. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  137. data/lib/sisimai/rhost.rb +23 -31
  138. data/lib/sisimai/smtp/command.rb +59 -0
  139. data/lib/sisimai/smtp/error.rb +4 -7
  140. data/lib/sisimai/smtp/reply.rb +161 -74
  141. data/lib/sisimai/smtp/status.rb +507 -393
  142. data/lib/sisimai/smtp/transcript.rb +124 -0
  143. data/lib/sisimai/smtp.rb +0 -1
  144. data/lib/sisimai/string.rb +74 -5
  145. data/lib/sisimai/time.rb +1 -2
  146. data/lib/sisimai/version.rb +1 -1
  147. data/lib/sisimai.rb +46 -31
  148. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  149. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  150. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  154. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  155. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  156. data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
  157. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  162. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  163. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  164. data/sisimai-java.gemspec +1 -1
  165. data/sisimai.gemspec +1 -1
  166. metadata +48 -26
  167. data/.rspec +0 -2
  168. data/lib/sisimai/data/yaml.rb +0 -33
  169. data/lib/sisimai/data.rb +0 -411
  170. data/lib/sisimai/mime.rb +0 -456
  171. data/lib/sisimai/rhost/googleapps.rb +0 -261
  172. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  179. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  180. /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:
@@ -1,9 +1,34 @@
1
1
  module Sisimai
2
2
  # Sisimai::Address provide methods for dealing email address.
3
3
  class Address
4
- # Imported from p5-Sisimail/lib/Sisimai/Address.pm
5
- require 'sisimai/rfc5322'
6
- Indicators = {
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] argv1 Address type: :r or :s
15
- # @return [String, Nil] Pseudo recipient address or sender address or
16
- # nil when the argv1 is neither :r nor :s
17
- def self.undisclosed(argv1)
18
- return nil unless argv1
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
- # New constructor of Sisimai::Address
26
- # @param [Hash] argvs Email address, name, and other elements
27
- # @return [Sisimai::Address] Object or nil when the email address was
28
- # not valid.
29
- # @example make({address: 'neko@example.org', name: 'Neko', comment: '(nyaan)')}
30
- # # => Sisimai::Address object
31
- def self.make(argvs)
32
- return nil unless argvs.is_a? Hash
33
- return nil unless argvs[:address]
34
- return nil if argvs[:address].empty?
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
- return thing
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? ? (v[:name] << e) : (v[p] << e)
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::RFC5322.is_mailerdaemon(v[:name])
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 cv = v[:address].match(/(.*)([(].+[)])(.*)/)
224
- # (nyaan)nekochan@example.org, nekochan(nyaan)cat@example.org or
225
- # nekochan(nyaan)@example.org
226
- v[:address] = cv[1] << cv[3]
227
- v[:comment] = cv[2]
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
- unless e[:address] =~ /\A.+[@].+\z/
269
+ if e[:address].include?('@') == false
237
270
  # Allow if the argument is MAILER-DAEMON
238
- next unless Sisimai::RFC5322.is_mailerdaemon(e[:address])
271
+ next unless Sisimai::Address.is_mailerdaemon(e[:address])
239
272
  end
240
273
 
241
- # Remove angle brackets, other brackets, and quotations: []<>{}'`
242
- # except a domain part is an IP address like neko@[192.0.2.222]
243
- e[:address] = e[:address].sub(/\A[\[<{('`]/, '').sub(/[.'`>});]\z/, '')
244
- e[:address].chomp!(']') unless e[:address] =~ /[@]\[[0-9A-Za-z:\.]+\]\z/
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, 1) || []
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::RFC5322.is_emailaddress(verp0)
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::RFC5322.is_emailaddress(email)
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 <str> [String] argv1 Email address
316
- # @return [Sisimai::Address, Nil] Object or nil when the email
317
- # address was not valid
318
- def initialize(argv1)
319
- return nil unless argv1
320
-
321
- addrs = Sisimai::Address.find(argv1)
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 = thing[:address].match(/\A([^\s]+)[@]([^@]+)\z/) ||
330
- thing[:address].match(/\A(["].+?["])[@]([^@]+)\z/)
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 = cv[1]; heads.each { |e| lpart.gsub!(/\A#{e}/, '') if lpart.start_with?(e) }
333
- dpart = cv[2]; tails.each { |e| dpart.gsub!(/#{e}\z/, '') if dpart.end_with?(e) }
334
- email = Sisimai::Address.expand_verp(thing[:address])
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(thing[:address]) || ''
369
+ email = Sisimai::Address.expand_alias(argvs[:address]) || ''
340
370
  aname = true unless email.empty?
341
371
  end
342
372
 
343
- if email =~ /\A.+[@].+?\z/
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 = thing[:address]
377
+ @alias = argvs[:address]
348
378
  else
349
379
  # The address is a VERP: b+neko=example.jp@example.org
350
- @verp = thing[:address]
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::RFC5322.is_mailerdaemon(thing[:address])
359
- return nil if thing[:address].include?(' ')
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 = thing[:address]
405
+ @user = argvs[:address]
363
406
  @host ||= ''
364
- @address = thing[:address]
407
+ @address = argvs[:address]
365
408
  end
366
409
 
367
410
  @alias ||= ''
368
411
  @verp ||= ''
369
- @name = thing[:name] || ''
370
- @comment = thing[: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: ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'],
19
- report: ['Content-Type: message/feedback-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
- MarkingsOf = {
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'] =~ /report-type=["]?feedback-report["]?/
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'].start_with?('multipart/mixed')
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
- p = Sisimai::Address.s3s4(heads['from'])
56
- if p =~ ReportFrom && heads['subject'].include?('complaint about message from ')
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 make(mhead, mbody)
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
- if e =~ MarkingsOf[:message]
121
- commondata['diagnosis'] = e if commondata['diagnosis'].empty?
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 cv = e.match(/X-HmXmrOriginalRecipient:[ ]*(.+)\z/)
134
+ if e.start_with?('X-HmXmrOriginalRecipient:')
140
135
  # Microsoft ARF: original recipient.
141
- dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1])
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
- # we take this opportunity to hard-code ARF headers missing in
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 cv = e.match(/\AFrom:[ ]*(.+)\z/)
144
+ elsif e.start_with?('From: ')
151
145
  # Microsoft ARF: original sender.
152
- commondata['from'] = Sisimai::Address.s3s4(cv[1]) if commondata['from'].empty?
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?(' ', "\t")
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(/:[ ]*/, 2)
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 cv = e.match(/\AOriginal-Rcpt-To:[ ]+[<]?(.+)[>]?\z/) ||
195
- e.match(/\ARedacted-Address:[ ]([^ ].+[@])\z/)
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(cv[1])
197
+ v['recipient'] = Sisimai::Address.s3s4(e[e.index(' ') + 1, e.size])
206
198
  recipients += 1
207
199
 
208
- elsif cv = e.match(/\AFeedback-Type:[ ]*([^ ]+)\z/)
200
+ elsif e.start_with?('Feedback-Type: ')
209
201
  # The header field MUST appear exactly once.
210
202
  # Feedback-Type: abuse
211
- arfheaders['feedbacktype'] = cv[1]
203
+ arfheaders['feedbacktype'] = e[e.index(' ') + 1, e.size]
212
204
 
213
- elsif cv = e.match(/\AAuthentication-Results:[ ]*(.+)\z/)
214
- # "Authentication-Results" indicates the result of one or more
215
- # authentication checks run by the report generator.
216
- #
217
- # Authentication-Results: mail.example.com;
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 cv = e.match(/\AUser-Agent:[ ]*(.+)\z/)
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'] = cv[1]
214
+ arfheaders['agent'] = e[e.index(' ') + 1, e.size]
225
215
 
226
- elsif cv = e.match(/\A(?:Received|Arrival)-Date:[ ]*(.+)\z/)
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'] = cv[1]
220
+ arfheaders['date'] = e[e.index(' ') + 1, e.size]
232
221
 
233
- elsif cv = e.match(/\AReporting-MTA:[ ]*dns;[ ]*(.+)\z/)
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'] = cv[1]
225
+ commondata['rhost'] = e[e.index(';') + 2, e.size]
237
226
 
238
- elsif cv = e.match(/\ASource-I[Pp]:[ ]*(.+)\z/)
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'] = cv[1]
230
+ arfheaders['rhost'] = e[e.index(' ') + 1, e.size]
242
231
 
243
- elsif cv = e.match(/\AOriginal-Mail-From:[ ]*(.+)\z/)
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(cv[1]) if commondata['from'].empty?
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
- unless recipients > 0
246
+ if recipients == 0
258
247
  # The original recipient address was not found
259
- if cv = rfc822part.match(/^To: (.+[@].+)$/)
248
+ if Sisimai::String.aligned(rfc822part, ["\nTo: ", '@'])
260
249
  # pick the address from To: header in message/rfc822 part.
261
- dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1])
262
- else
263
- # Insert pseudo recipient address when there is no valid recipient
264
- # address in the message.
265
- dscontents[-1]['recipient'] = Sisimai::Address.undisclosed('r')
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 =~ /\bFrom: [^ ]+[@][^ ]+\b/
271
- # There is no "From:" header in the original message
272
- # Append the value of "Original-Mail-From" value as a sender address.
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 cv = mhead['subject'].match(/complaint about message from (\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3})/)
278
+ if mhead['subject'].include?('complaint about message from ')
277
279
  # Microsoft ARF: remote host address.
278
- arfheaders['rhost'] = cv[1]
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
- if e['recipient'] =~ /\A[^ ]+[@]\z/
285
- # AOL = http://forums.cpanel.net/f43/aol-brutal-work-71473.html
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
- elsif cv = e['diagnosis'].match(/\breceived from IP address ([^ ]+)/)
306
- # This is an email abuse report for an email message received
307
- # from IP address 24.64.1.1 on Thu, 29 Apr 2010 00:00:00 +0000
308
- e['rhost'] = cv[1]
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
+