sisimai 4.25.17-java → 5.0.0-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.
Files changed (178) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +3 -3
  3. data/ANALYTICAL-PRECISION +2 -2
  4. data/Benchmarks.mk +3 -3
  5. data/CONTRIBUTING +1 -1
  6. data/ChangeLog.md +406 -407
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +12 -12
  10. data/README-JA.md +142 -94
  11. data/README.md +282 -150
  12. data/Rakefile +9 -3
  13. data/Repository.mk +2 -3
  14. data/lib/sisimai/address.rb +118 -74
  15. data/lib/sisimai/arf.rb +84 -82
  16. data/lib/sisimai/datetime.rb +5 -52
  17. data/lib/sisimai/{data → fact}/json.rb +7 -9
  18. data/lib/sisimai/fact/yaml.rb +31 -0
  19. data/lib/sisimai/fact.rb +468 -0
  20. data/lib/sisimai/lhost/activehunter.rb +12 -14
  21. data/lib/sisimai/lhost/amavis.rb +11 -14
  22. data/lib/sisimai/lhost/amazonses.rb +37 -41
  23. data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
  24. data/lib/sisimai/lhost/aol.rb +12 -14
  25. data/lib/sisimai/lhost/apachejames.rb +19 -21
  26. data/lib/sisimai/lhost/barracuda.rb +10 -12
  27. data/lib/sisimai/lhost/bigfoot.rb +21 -21
  28. data/lib/sisimai/lhost/biglobe.rb +15 -16
  29. data/lib/sisimai/lhost/courier.rb +20 -20
  30. data/lib/sisimai/lhost/domino.rb +23 -19
  31. data/lib/sisimai/lhost/einsundeins.rb +20 -16
  32. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  33. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  34. data/lib/sisimai/lhost/exim.rb +175 -161
  35. data/lib/sisimai/lhost/ezweb.rb +31 -56
  36. data/lib/sisimai/lhost/facebook.rb +21 -33
  37. data/lib/sisimai/lhost/fml.rb +43 -48
  38. data/lib/sisimai/lhost/gmail.rb +29 -29
  39. data/lib/sisimai/lhost/gmx.rb +18 -17
  40. data/lib/sisimai/lhost/googlegroups.rb +9 -10
  41. data/lib/sisimai/lhost/gsuite.rb +21 -27
  42. data/lib/sisimai/lhost/imailserver.rb +25 -39
  43. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  44. data/lib/sisimai/lhost/kddi.rb +22 -28
  45. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  46. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  47. data/lib/sisimai/lhost/mailru.rb +33 -27
  48. data/lib/sisimai/lhost/mcafee.rb +21 -31
  49. data/lib/sisimai/lhost/messagelabs.rb +17 -20
  50. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  51. data/lib/sisimai/lhost/mfilter.rb +15 -16
  52. data/lib/sisimai/lhost/mxlogic.rb +24 -23
  53. data/lib/sisimai/lhost/notes.rb +17 -17
  54. data/lib/sisimai/lhost/office365.rb +63 -27
  55. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  56. data/lib/sisimai/lhost/outlook.rb +12 -15
  57. data/lib/sisimai/lhost/postfix.rb +179 -129
  58. data/lib/sisimai/lhost/powermta.rb +12 -14
  59. data/lib/sisimai/lhost/qmail.rb +44 -47
  60. data/lib/sisimai/lhost/receivingses.rb +15 -20
  61. data/lib/sisimai/lhost/sendgrid.rb +34 -32
  62. data/lib/sisimai/lhost/sendmail.rb +66 -53
  63. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  64. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  65. data/lib/sisimai/lhost/verizon.rb +35 -39
  66. data/lib/sisimai/lhost/x1.rb +18 -17
  67. data/lib/sisimai/lhost/x2.rb +17 -14
  68. data/lib/sisimai/lhost/x3.rb +19 -19
  69. data/lib/sisimai/lhost/x4.rb +72 -57
  70. data/lib/sisimai/lhost/x5.rb +17 -19
  71. data/lib/sisimai/lhost/x6.rb +41 -17
  72. data/lib/sisimai/lhost/yahoo.rb +17 -16
  73. data/lib/sisimai/lhost/yandex.rb +16 -20
  74. data/lib/sisimai/lhost/zoho.rb +16 -15
  75. data/lib/sisimai/lhost.rb +8 -10
  76. data/lib/sisimai/mail/maildir.rb +1 -3
  77. data/lib/sisimai/mail/mbox.rb +3 -4
  78. data/lib/sisimai/mail/memory.rb +0 -1
  79. data/lib/sisimai/mail/stdin.rb +1 -3
  80. data/lib/sisimai/mail.rb +3 -7
  81. data/lib/sisimai/mda.rb +28 -42
  82. data/lib/sisimai/message.rb +435 -325
  83. data/lib/sisimai/order.rb +5 -5
  84. data/lib/sisimai/reason/authfailure.rb +64 -0
  85. data/lib/sisimai/reason/badreputation.rb +53 -0
  86. data/lib/sisimai/reason/blocked.rb +94 -160
  87. data/lib/sisimai/reason/contenterror.rb +8 -9
  88. data/lib/sisimai/reason/delivered.rb +4 -6
  89. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  90. data/lib/sisimai/reason/expired.rb +6 -8
  91. data/lib/sisimai/reason/feedback.rb +2 -3
  92. data/lib/sisimai/reason/filtered.rb +17 -19
  93. data/lib/sisimai/reason/hasmoved.rb +9 -10
  94. data/lib/sisimai/reason/hostunknown.rb +15 -15
  95. data/lib/sisimai/reason/mailboxfull.rb +10 -12
  96. data/lib/sisimai/reason/mailererror.rb +18 -20
  97. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  98. data/lib/sisimai/reason/networkerror.rb +5 -8
  99. data/lib/sisimai/reason/norelaying.rb +8 -11
  100. data/lib/sisimai/reason/notaccept.rb +13 -14
  101. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  102. data/lib/sisimai/reason/onhold.rb +6 -9
  103. data/lib/sisimai/reason/policyviolation.rb +14 -12
  104. data/lib/sisimai/reason/rejected.rb +26 -24
  105. data/lib/sisimai/reason/requireptr.rb +69 -0
  106. data/lib/sisimai/reason/securityerror.rb +33 -36
  107. data/lib/sisimai/reason/spamdetected.rb +114 -147
  108. data/lib/sisimai/reason/speeding.rb +49 -0
  109. data/lib/sisimai/reason/suspend.rb +11 -11
  110. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  111. data/lib/sisimai/reason/systemerror.rb +7 -9
  112. data/lib/sisimai/reason/systemfull.rb +7 -8
  113. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  114. data/lib/sisimai/reason/undefined.rb +2 -3
  115. data/lib/sisimai/reason/userunknown.rb +129 -146
  116. data/lib/sisimai/reason/vacation.rb +3 -4
  117. data/lib/sisimai/reason/virusdetected.rb +10 -11
  118. data/lib/sisimai/reason.rb +59 -64
  119. data/lib/sisimai/rfc1894.rb +55 -28
  120. data/lib/sisimai/rfc2045.rb +373 -0
  121. data/lib/sisimai/rfc3464.rb +250 -308
  122. data/lib/sisimai/rfc3834.rb +42 -45
  123. data/lib/sisimai/rfc5322.rb +75 -100
  124. data/lib/sisimai/rfc5965.rb +31 -0
  125. data/lib/sisimai/rhost/cox.rb +5 -6
  126. data/lib/sisimai/rhost/franceptt.rb +6 -8
  127. data/lib/sisimai/rhost/godaddy.rb +12 -12
  128. data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
  129. data/lib/sisimai/rhost/iua.rb +9 -10
  130. data/lib/sisimai/rhost/kddi.rb +6 -8
  131. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  132. data/lib/sisimai/rhost/mimecast.rb +42 -40
  133. data/lib/sisimai/rhost/nttdocomo.rb +13 -18
  134. data/lib/sisimai/rhost/spectrum.rb +10 -12
  135. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  136. data/lib/sisimai/rhost.rb +23 -31
  137. data/lib/sisimai/smtp/command.rb +59 -0
  138. data/lib/sisimai/smtp/error.rb +4 -7
  139. data/lib/sisimai/smtp/reply.rb +161 -74
  140. data/lib/sisimai/smtp/status.rb +504 -393
  141. data/lib/sisimai/smtp/transcript.rb +124 -0
  142. data/lib/sisimai/smtp.rb +0 -1
  143. data/lib/sisimai/string.rb +74 -5
  144. data/lib/sisimai/time.rb +1 -2
  145. data/lib/sisimai/version.rb +1 -1
  146. data/lib/sisimai.rb +35 -21
  147. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  148. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  149. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  150. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  154. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  155. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  156. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  157. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  162. data/sisimai-java.gemspec +1 -1
  163. data/sisimai.gemspec +1 -1
  164. metadata +42 -23
  165. data/.rspec +0 -2
  166. data/lib/sisimai/data/yaml.rb +0 -33
  167. data/lib/sisimai/data.rb +0 -411
  168. data/lib/sisimai/mime.rb +0 -456
  169. data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
  170. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -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
+