sisimai 4.25.16 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  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 +424 -393
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +15 -15
  10. data/README-JA.md +323 -149
  11. data/README.md +319 -148
  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 +23 -18
  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 -326
  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/google.rb +530 -0
  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 +12 -12
  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 +507 -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 +46 -31
  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 +41 -20
  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/lib/sisimai/rhost/googleapps.rb +0 -261
  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,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::GMX parses a bounce email which created by GMX.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::GMX parses a bounce email which created by GMX. Methods in the module are called
3
+ # from only Sisimai::Message.
4
4
  module GMX
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/GMX.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^---[ ]The[ ]header[ ]of[ ]the[ ]original[ ]message[ ]is[ ]following[.][ ]---|.freeze
9
+ Boundaries = ['--- The header of the original message is following. ---'].freeze
11
10
  StartingOf = { message: ['This message was created automatically by mail delivery software'] }.freeze
12
11
  MessagesOf = { 'expired' => ['delivery retry timeout exceeded'] }.freeze
13
12
 
@@ -16,23 +15,24 @@ module Sisimai::Lhost
16
15
  # @param [String] mbody Message body of a bounce email
17
16
  # @return [Hash] Bounce data list and message/rfc822 part
18
17
  # @return [Nil] it failed to parse or the arguments are missing
19
- def make(mhead, mbody)
18
+ def inquire(mhead, mbody)
20
19
  # Envelope-To: <kijitora@mail.example.com>
21
20
  # X-GMX-Antispam: 0 (Mail was not recognized as spam); Detail=V3;
22
21
  # X-GMX-Antivirus: 0 (no virus found)
23
22
  # X-UI-Out-Filterresults: unknown:0;
24
23
  return nil unless mhead['x-gmx-antispam']
25
24
 
25
+ require 'sisimai/smtp/command'
26
26
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
27
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
28
- bodyslices = emailsteak[0].split("\n")
27
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
28
+ bodyslices = emailparts[0].split("\n")
29
29
  readcursor = 0 # (Integer) Points the current cursor position
30
30
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
31
31
  v = nil
32
32
 
33
33
  while e = bodyslices.shift do
34
- # Read error messages and delivery status lines from the head of the email
35
- # to the previous line of the beginning of the original message.
34
+ # Read error messages and delivery status lines from the head of the email to the previous
35
+ # line of the beginning of the original message.
36
36
  if readcursor == 0
37
37
  # Beginning of the bounce message or delivery status part
38
38
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -53,7 +53,7 @@ module Sisimai::Lhost
53
53
  # 5.1.1 <shironeko@example.jp>... User Unknown
54
54
  v = dscontents[-1]
55
55
 
56
- if cv = e.match(/\A["]([^ ]+[@][^ ]+)["]:\z/) || e.match(/\A[<]([^ ]+[@][^ ]+)[>]\z/)
56
+ if e.include?('@') && ( e.start_with?('"') || e.start_with?('<') )
57
57
  # "shironeko@example.jp":
58
58
  # ---- OR ----
59
59
  # <kijitora@6jo.example.co.jp>
@@ -65,19 +65,20 @@ module Sisimai::Lhost
65
65
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
66
66
  v = dscontents[-1]
67
67
  end
68
- v['recipient'] = cv[1]
68
+ v['recipient'] = Sisimai::Address.s3s4(e)
69
69
  recipients += 1
70
70
 
71
- elsif cv = e.match(/\ASMTP error .+ ([A-Z]{4}) command:\z/)
71
+ elsif e.start_with?('SMTP error ')
72
72
  # SMTP error from remote server after RCPT command:
73
- v['command'] = cv[1]
73
+ v['command'] = Sisimai::SMTP::Command.find(e)
74
74
 
75
- elsif cv = e.match(/\Ahost:[ \t]*(.+)\z/)
75
+ elsif e.start_with?('host:')
76
76
  # host: mx.example.jp
77
- v['rhost'] = cv[1]
77
+ v['rhost'] = e[6, e.size]
78
78
  else
79
79
  # Get error message
80
- if e =~ /\b[45][.]\d[.]\d\b/ || e =~ /[<][^ ]+[@][^ ]+[>]/ || e =~ /\b[45]\d{2}\b/
80
+ if Sisimai::SMTP::Status.find(e) || Sisimai::String.aligned(e, ['<', '@', '>'])
81
+ # 5.1.1 <shironeko@example.jp>... User Unknown
81
82
  v['diagnosis'] ||= e
82
83
  else
83
84
  next if e.empty?
@@ -105,7 +106,7 @@ module Sisimai::Lhost
105
106
  end
106
107
  end
107
108
 
108
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
109
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
109
110
  end
110
111
  def description; return 'GMX: https://www.gmx.net'; end
111
112
  end
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::GoogleGroups parses a bounce email which created by Google
3
- # Groups. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::GoogleGroups parses a bounce email which created by Google Groups. Methods in the
3
+ # module are called from only Sisimai::Message.
4
4
  module GoogleGroups
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/GoogleGroups.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r/^-----[ ]Original[ ]message[ ]-----$/.freeze
9
+ Boundaries = ['----- Original message -----'].freeze
11
10
 
12
11
  # Parse bounce messages from Google Groups
13
12
  # @param [Hash] mhead Message headers of a bounce email
@@ -15,7 +14,7 @@ module Sisimai::Lhost
15
14
  # @return [Hash] Bounce data list and message/rfc822 part
16
15
  # @return [Nil] it failed to parse or the arguments are missing
17
16
  # @since v4.25.6
18
- def make(mhead, mbody)
17
+ def inquire(mhead, mbody)
19
18
  return nil unless mhead['from'].end_with?('<mailer-daemon@googlemail.com>')
20
19
  return nil unless mhead['subject'].start_with?('Delivery Status Notification')
21
20
  return nil unless mhead['x-failed-recipients']
@@ -39,7 +38,7 @@ module Sisimai::Lhost
39
38
  #
40
39
  # Google Groups
41
40
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
42
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
41
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
43
42
  recordwide = { 'rhost' => '', 'reason' => '', 'diagnosis' => '' }
44
43
  recipients = 0
45
44
  v = dscontents[-1]
@@ -48,14 +47,14 @@ module Sisimai::Lhost
48
47
  # * The owner of the group may have removed this group.
49
48
  # * You may need to join the group before receiving permission to post.
50
49
  # * This group may not be open to posting.
51
- entiremesg = emailsteak[0].split(/\n\n/, 5).slice(0, 4).join(' ').tr("\n", ' ');
50
+ entiremesg = emailparts[0].split(/\n\n/, 5).slice(0, 4).join(' ').tr("\n", ' ');
52
51
  recordwide['diagnosis'] = Sisimai::String.sweep(entiremesg)
53
- recordwide['reason'] = emailsteak[0].scan(/^[ ]?[*][ ]?/).size == 4 ? 'rejected' : 'onhold'
52
+ recordwide['reason'] = emailparts[0].scan(/^[ ]?[*][ ]?/).size == 4 ? 'rejected' : 'onhold'
54
53
  recordwide['rhost'] = Sisimai::RFC5322.received(mhead['received'][0]).shift
55
54
 
56
55
  mhead['x-failed-recipients'].split(',').each do |e|
57
56
  # X-Failed-Recipients: neko@example.jp, nyaan@example.org, ...
58
- next unless Sisimai::RFC5322.is_emailaddress(e)
57
+ next unless Sisimai::Address.is_emailaddress(e)
59
58
 
60
59
  if v['recipient']
61
60
  # There are multiple recipient addresses in the message body.
@@ -67,7 +66,7 @@ module Sisimai::Lhost
67
66
  recordwide.each_key { |r| v[r] = recordwide[r] }
68
67
  end
69
68
  return nil unless recipients > 0
70
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
69
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
71
70
  end
72
71
  def description; return 'Google Groups: https://groups.google.com'; end
73
72
  end
@@ -1,17 +1,15 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::GSuite parses a bounce email which created by G Suite.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::GSuite parses a bounce email which created by G Suite. Methods in the module are
3
+ # called from only Sisimai::Message.
4
4
  module GSuite
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/GSuite.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r<^Content-Type:[ ](?:message/rfc822|text/rfc822-headers)>.freeze
9
+ Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
11
10
  MarkingsOf = {
12
- message: %r/\A[*][*][ ].+[ ][*][*]\z/,
13
- error: %r/\AThe[ ]response([ ]from[ ]the[ ]remote[ ]server)?[ ]was:\z/,
14
- html: %r{\AContent-Type:[ ]*text/html;[ ]*charset=['"]?(?:UTF|utf)[-]8['"]?\z},
11
+ message: ['** '],
12
+ error: ['The response was:', 'The response from the remote server was:'],
15
13
  }.freeze
16
14
  MessagesOf = {
17
15
  'userunknown' => ["because the address couldn't be found. Check for typos or unnecessary spaces and try again."],
@@ -24,18 +22,17 @@ module Sisimai::Lhost
24
22
  # @param [String] mbody Message body of a bounce email
25
23
  # @return [Hash] Bounce data list and message/rfc822 part
26
24
  # @return [Nil] it failed to parse or the arguments are missing
27
- def make(mhead, mbody)
25
+ def inquire(mhead, mbody)
28
26
  return nil unless mhead['from'].end_with?('<mailer-daemon@googlemail.com>')
29
27
  return nil unless mhead['subject'].start_with?('Delivery Status Notification')
30
28
  return nil unless mhead['x-gm-message-state']
31
29
 
32
- require 'sisimai/rfc1894'
33
30
  fieldtable = Sisimai::RFC1894.FIELDTABLE
34
31
  permessage = {} # (Hash) Store values of each Per-Message field
35
32
 
36
33
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
37
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
38
- bodyslices = emailsteak[0].split("\n")
34
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
35
+ bodyslices = emailparts[0].split("\n")
39
36
  readcursor = 0 # (Integer) Points the current cursor position
40
37
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
41
38
  endoferror = false # (Integer) Flag for a blank line after error messages
@@ -44,11 +41,11 @@ module Sisimai::Lhost
44
41
  v = nil
45
42
 
46
43
  while e = bodyslices.shift do
47
- # Read error messages and delivery status lines from the head of the email
48
- # to the previous line of the beginning of the original message.
44
+ # Read error messages and delivery status lines from the head of the email to the previous
45
+ # line of the beginning of the original message.
49
46
  if readcursor == 0
50
47
  # Beginning of the bounce message or message/delivery-status part
51
- readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message]
48
+ readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.start_with?(a) }
52
49
  end
53
50
  next if (readcursor & Indicators[:deliverystatus]) == 0
54
51
 
@@ -87,12 +84,12 @@ module Sisimai::Lhost
87
84
  v['lhost'] = '' if v['lhost'].include?('@')
88
85
  end
89
86
 
90
- next unless f == 1
87
+ next unless f
91
88
  permessage[fieldtable[o[0]]] = o[2]
92
89
  end
93
90
  else
94
- # The line does not begin with a DSN field defined in RFC3464
95
- # Append error messages continued from the previous line
91
+ # The line does not begin with a DSN field defined in RFC3464 Append error messages continued
92
+ # from the previous line
96
93
  if endoferror == false && v && ! v['diagnosis'].to_s.empty?
97
94
  endoferror ||= true if e.empty?
98
95
 
@@ -100,10 +97,10 @@ module Sisimai::Lhost
100
97
  next unless e.start_with?(' ')
101
98
  v['diagnosis'] << e
102
99
 
103
- elsif e =~ MarkingsOf[:error]
100
+ elsif MarkingsOf[:error].any? { |a| e.start_with?(a) }
104
101
  # Detect SMTP session error or connection error
105
102
  # The response from the remote server was:
106
- anotherset['diagnosis'] << e
103
+ anotherset['diagnosis'] << ' ' << e
107
104
  else
108
105
  # ** Address not found **
109
106
  #
@@ -112,12 +109,9 @@ module Sisimai::Lhost
112
109
  #
113
110
  # The response from the remote server was:
114
111
  # 550 #5.1.0 Address rejected.
115
- next if e =~ MarkingsOf[:html]
116
-
112
+ next if e.start_with?('Content-Type:')
117
113
  if anotherset['diagnosis']
118
- # Continued error messages from the previous line like
119
- # "550 #5.1.0 Address rejected."
120
- next if e =~ /\AContent-Type:/
114
+ # Continued error messages from the previous line like "550 #5.1.0 Address rejected."
121
115
  next if emptylines > 5
122
116
  if e.empty?
123
117
  # Count and next()
@@ -131,7 +125,7 @@ module Sisimai::Lhost
131
125
  # Your message wasn't delivered to * because the address couldn't be found.
132
126
  # Check for typos or unnecessary spaces and try again.
133
127
  next if e.empty?
134
- next unless e =~ MarkingsOf[:message]
128
+ next unless MarkingsOf[:message].any? { |a| e.start_with?(a) }
135
129
  anotherset['diagnosis'] = e
136
130
  end
137
131
  end
@@ -148,7 +142,7 @@ module Sisimai::Lhost
148
142
  # Copy alternative error message
149
143
  e['diagnosis'] = anotherset['diagnosis'] unless e['diagnosis']
150
144
 
151
- if e['diagnosis'] =~ /\A\d+\z/
145
+ if e['diagnosis'].include?(' ') == false && e['diagnosis'].to_i > 0
152
146
  e['diagnosis'] = anotherset['diagnosis']
153
147
  else
154
148
  # More detailed error message is in "anotherset"
@@ -192,7 +186,7 @@ module Sisimai::Lhost
192
186
  end
193
187
  end
194
188
 
195
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
189
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
196
190
  end
197
191
  def description; return 'G Suite: https://gsuite.google.com'; end
198
192
  end
@@ -1,29 +1,19 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::IMailServer parses a bounce email which created by
3
- # Ipswitch IMail Server.
2
+ # Sisimai::Lhost::IMailServer parses a bounce email which created by Ipswitch IMail Server.
4
3
  # Methods in the module are called from only Sisimai::Message.
5
4
  module IMailServer
6
5
  class << self
7
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/IMailServer.pm
8
6
  require 'sisimai/lhost'
9
7
 
10
- ReBackbone = %r|^Original[ ]message[ ]follows[.]|.freeze
8
+ Boundaries = ['Original message follows.'].freeze
11
9
  StartingOf = { error: ['Body of message generated response:'] }.freeze
12
-
13
- ReSMTP = {
14
- 'conn' => %r/(?:SMTP connection failed,|Unexpected connection response from server:)/,
15
- 'ehlo' => %r|Unexpected response to EHLO/HELO:|,
16
- 'mail' => %r|Server response to MAIL FROM:|,
17
- 'rcpt' => %r|Additional RCPT TO generated following response:|,
18
- 'data' => %r|DATA command generated response:|,
19
- }.freeze
20
10
  ReFailures = {
21
- 'hostunknown' => %r/Unknown host/,
22
- 'userunknown' => %r/\A(?:Unknown user|Invalid final delivery userid)/,
23
- 'mailboxfull' => %r/\AUser mailbox exceeds allowed size/,
24
- 'virusdetected' => %r/\ARequested action not taken: virus detected/,
25
- 'undefined' => %r/\Aundeliverable to /,
26
- 'expired' => %r/\ADelivery failed \d+ attempts/,
11
+ 'hostunknown' => ['Unknown host'],
12
+ 'userunknown' => ['Unknown user', 'Invalid final delivery userid'],
13
+ 'mailboxfull' => ['User mailbox exceeds allowed size'],
14
+ 'virusdetected' => ['Requested action not taken: virus detected'],
15
+ 'undefined' => ['undeliverable to'],
16
+ 'expired' => ['Delivery failed '],
27
17
  }.freeze
28
18
 
29
19
  # Parse bounce messages from IMailServer
@@ -31,47 +21,48 @@ module Sisimai::Lhost
31
21
  # @param [String] mbody Message body of a bounce email
32
22
  # @return [Hash] Bounce data list and message/rfc822 part
33
23
  # @return [Nil] it failed to parse or the arguments are missing
34
- def make(mhead, mbody)
24
+ def inquire(mhead, mbody)
35
25
  # X-Mailer: <SMTP32 v8.22>
36
26
  match = 0
37
- match += 1 if mhead['subject'] =~ /\AUndeliverable Mail[ ]*\z/
27
+ match += 1 if mhead['subject'].start_with?('Undeliverable Mail ')
38
28
  match += 1 if mhead['x-mailer'].to_s.start_with?('<SMTP32 v')
39
29
  return nil unless match > 0
40
30
 
41
31
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
42
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
43
- bodyslices = emailsteak[0].split("\n")
32
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
33
+ bodyslices = emailparts[0].split("\n")
44
34
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
45
35
  v = nil
46
36
 
47
37
  while e = bodyslices.shift do
48
- # Read error messages and delivery status lines from the head of the email
49
- # to the previous line of the beginning of the original message.
38
+ # Read error messages and delivery status lines from the head of the email to the previous
39
+ # line of the beginning of the original message.
50
40
 
51
41
  # Unknown user: kijitora@example.com
52
42
  #
53
43
  # Original message follows.
54
44
  v = dscontents[-1]
55
45
 
56
- if cv = e.match(/\A([^ ]+)[ ](.+)[:][ \t]*([^ ]+[@][^ ]+)/)
46
+ p0 = e.index(': ') || -1
47
+ if p0 > 8 && Sisimai::String.aligned(e, [': ', '@'])
57
48
  # Unknown user: kijitora@example.com
58
49
  if v['recipient']
59
50
  # There are multiple recipient addresses in the message body.
60
51
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
61
52
  v = dscontents[-1]
62
53
  end
63
- v['diagnosis'] = cv[1] + ' ' + cv[2]
64
- v['recipient'] = cv[3]
54
+ v['diagnosis'] = e
55
+ v['recipient'] = Sisimai::Address.s3s4(e[p0 + 2, e.size])
65
56
  recipients += 1
66
57
 
67
- elsif cv = e.match(/\Aundeliverable[ ]+to[ ]+(.+)\z/)
58
+ elsif e.start_with?('undeliverable ')
68
59
  # undeliverable to kijitora@example.com
69
60
  if v['recipient']
70
61
  # There are multiple recipient addresses in the message body.
71
62
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
72
63
  v = dscontents[-1]
73
64
  end
74
- v['recipient'] = Sisimai::Address.s3s4(cv[1])
65
+ v['recipient'] = Sisimai::Address.s3s4(e)
75
66
  recipients += 1
76
67
  else
77
68
  # Other error message text
@@ -81,6 +72,7 @@ module Sisimai::Lhost
81
72
  end
82
73
  return nil unless recipients > 0
83
74
 
75
+ require 'sisimai/smtp/command'
84
76
  dscontents.each do |e|
85
77
  unless e['alterrors'].to_s.empty?
86
78
  # Copy alternative error message
@@ -92,24 +84,18 @@ module Sisimai::Lhost
92
84
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
93
85
  e.delete('alterrors')
94
86
  end
95
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
96
-
97
- ReSMTP.each_key do |r|
98
- # Detect SMTP command from the message
99
- next unless e['diagnosis'] =~ ReSMTP[r]
100
- e['command'] = r.upcase
101
- break
102
- end
87
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
88
+ e['command'] = Sisimai::SMTP::Command.find(e['diagnosis'])
103
89
 
104
90
  ReFailures.each_key do |r|
105
91
  # Verify each regular expression of session errors
106
- next unless e['diagnosis'] =~ ReFailures[r]
92
+ next unless ReFailures[r].any? { |a| e['diagnosis'].include?(a) }
107
93
  e['reason'] = r
108
94
  break
109
95
  end
110
96
  end
111
97
 
112
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
98
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
113
99
  end
114
100
  def description; return 'IPSWITCH IMail Server'; end
115
101
  end
@@ -1,19 +1,17 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::InterScanMSS parses a bounce email which created by
3
- # Trend Micro InterScan Messaging Security Suite. Methods in the module are
4
- # called from only Sisimai::Message.
2
+ # Sisimai::Lhost::InterScanMSS parses a bounce email which created by Trend Micro InterScan Messaging
3
+ # Security Suite. Methods in the module are called from only Sisimai::Message.
5
4
  module InterScanMSS
6
5
  class << self
7
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/InterScanMSS.pm
8
6
  require 'sisimai/lhost'
9
- ReBackbone = %r|^Content-type:[ ]message/rfc822|.freeze
7
+ Boundaries = ['Content-Type: message/rfc822'].freeze
10
8
 
11
9
  # Parse bounce messages from InterScanMSS
12
10
  # @param [Hash] mhead Message headers of a bounce email
13
11
  # @param [String] mbody Message body of a bounce email
14
12
  # @return [Hash] Bounce data list and message/rfc822 part
15
13
  # @return [Nil] it failed to parse or the arguments are missing
16
- def make(mhead, mbody)
14
+ def inquire(mhead, mbody)
17
15
  # :received => %r/[ ][(]InterScanMSS[)][ ]with[ ]/,
18
16
  match = 0
19
17
  tryto = [
@@ -26,60 +24,59 @@ module Sisimai::Lhost
26
24
  match += 1 if tryto.any? { |a| mhead['subject'] == a }
27
25
  return nil unless match > 0
28
26
 
27
+ require 'sisimai/smtp/command'
29
28
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
30
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
31
- bodyslices = emailsteak[0].split("\n")
29
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
30
+ bodyslices = emailparts[0].split("\n")
32
31
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
33
32
  v = nil
34
33
 
35
34
  while e = bodyslices.shift do
36
- # Read error messages and delivery status lines from the head of the email
37
- # to the previous line of the beginning of the original message.
35
+ # Read error messages and delivery status lines from the head of the email to the previous
36
+ # line of the beginning of the original message.
38
37
  next if e.empty?
39
38
 
40
- v = dscontents[-1]
41
- if cv = e.match(/\A.+[<>]{3}[ \t]+.+[<]([^ ]+[@][^ ]+)[>]\z/) ||
42
- e.match(/\A.+[<>]{3}[ \t]+.+[<]([^ ]+[@][^ ]+)[>]/) ||
43
- e.match(/\A(?:Reason:[ ]+)?Unable[ ]to[ ]deliver[ ]message[ ]to[ ][<](.+)[>]/)
39
+ v = dscontents[-1]
40
+ p1 = e.index(' <<< ') || -1 # Sent <<< ...
41
+ p2 = e.index(' >>> ') || -1 # Received >>> ...
42
+ if e.include?('@') && e.include?(' <') && ( p1 > 1 || p2 > 1 || e.include?('Unable to deliver ') )
44
43
  # Sent <<< RCPT TO:<kijitora@example.co.jp>
45
44
  # Received >>> 550 5.1.1 <kijitora@example.co.jp>... user unknown
46
45
  # Unable to deliver message to <kijitora@neko.example.jp>
47
- if v['recipient'] && cv[1] != v['recipient']
46
+ cr = e[e.rindex('<') + 1, e.rindex('>') - e.rindex('<') - 1]
47
+
48
+ if v['recipient'] && cr != v['recipient']
48
49
  # There are multiple recipient addresses in the message body.
49
50
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
50
51
  v = dscontents[-1]
51
52
  end
52
- v['recipient'] = cv[1]
53
- v['diagnosis'] = e if e =~ /Unable[ ]to[ ]deliver[ ]/
53
+ v['recipient'] = cr
54
+ v['diagnosis'] = e if e.include?('Unable to deliver ')
54
55
  recipients = dscontents.size
55
56
  end
56
57
 
57
- if cv = e.match(/\ASent[ ]+[<]{3}[ ]+([A-Z]{4})[ ]/)
58
+ if e.start_with?('Sent <<< ')
58
59
  # Sent <<< RCPT TO:<kijitora@example.co.jp>
59
- v['command'] = cv[1]
60
+ v['command'] = Sisimai::SMTP::Command.find(e)
60
61
 
61
- elsif cv = e.match(/\AReceived[ ]+[>]{3}[ ]+(\d{3}[ ]+.+)\z/)
62
+ elsif e.start_with?('Received >>> ')
62
63
  # Received >>> 550 5.1.1 <kijitora@example.co.jp>... user unknown
63
- v['diagnosis'] = cv[1]
64
+ v['diagnosis'] = e[e.index(' >>> ') + 4, e.size]
64
65
  else
65
66
  # Error message in non-English
66
- if cv = e.match(/[ ][>]{3}[ ]([A-Z]{4})/)
67
- # >>> RCPT TO ...
68
- v['command'] = cv[1]
69
-
70
- elsif cv = e.match(/[ ][<]{3}[ ](.+)/)
71
- # <<< 550 5.1.1 User unknown
72
- v['diagnosis'] = cv[1]
73
- end
67
+ v['command'] = Sisimai::SMTP::Command.find(e) if e.include?(' >>> ')
68
+ p3 = e.index(' <<< ')
69
+ next unless p3
70
+ v['diagnosis'] = e[p3 + 4, e.size]
74
71
  end
75
72
  end
76
73
  return nil unless recipients > 0
77
74
 
78
75
  dscontents.each do |e|
79
76
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
80
- e['reason'] = 'userunknown' if e['diagnosis'] =~ /Unable[ ]to[ ]deliver/
77
+ e['reason'] = 'userunknown' if e['diagnosis'].include?('Unable to deliver')
81
78
  end
82
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
79
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
83
80
  end
84
81
  def description; return 'Trend Micro InterScan Messaging Security Suite'; end
85
82
  end
@@ -1,20 +1,13 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::KDDI parses a bounce email which created by au by KDDI.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::KDDI parses a bounce email which created by au by KDDI. Methods in the module are
3
+ # called from only Sisimai::Message.
4
4
  module KDDI
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/KDDI.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^Content-Type:[ ]message/rfc822|.freeze
11
- MarkingsOf = {
12
- message: %r/\AYour[ ]mail[ ](?:
13
- sent[ ]on:?[ ][A-Z][a-z]{2}[,]
14
- |attempted[ ]to[ ]be[ ]delivered[ ]on:?[ ][A-Z][a-z]{2}[,]
15
- )
16
- /x,
17
- }.freeze
9
+ Boundaries = ['Content-Type: message/rfc822'].freeze
10
+ MarkingsOf = { message: ['Your mail sent on:', 'Your mail attempted to be delivered on:'] }.freeze
18
11
  MessagesOf = {
19
12
  'mailboxfull' => ['As their mailbox is full'],
20
13
  'norelaying' => ['Due to the following SMTP relay error'],
@@ -26,35 +19,35 @@ module Sisimai::Lhost
26
19
  # @param [String] mbody Message body of a bounce email
27
20
  # @return [Hash] Bounce data list and message/rfc822 part
28
21
  # @return [Nil] it failed to parse or the arguments are missing
29
- def make(mhead, mbody)
22
+ def inquire(mhead, mbody)
30
23
  # :'message-id' => %r/[@].+[.]ezweb[.]ne[.]jp[>]\z/,
31
24
  match = 0
32
- match += 1 if mhead['from'] =~ /no-reply[@].+[.]dion[.]ne[.]jp/
25
+ match += 1 if Sisimai::String.aligned(mhead['from'], ['no-reply@.', '.dion.ne.jp'])
33
26
  match += 1 if mhead['reply-to'].to_s == 'no-reply@app.auone-net.jp'
34
27
  match += 1 if mhead['received'].any? { |a| a.include?('ezweb.ne.jp (') }
35
28
  match += 1 if mhead['received'].any? { |a| a.include?('.au.com (') }
36
29
  return nil unless match > 0
37
30
 
38
31
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
39
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
40
- bodyslices = emailsteak[0].split("\n")
32
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
33
+ bodyslices = emailparts[0].split("\n")
41
34
  readcursor = 0 # (Integer) Points the current cursor position
42
35
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
43
36
  v = nil
44
37
 
45
38
  while e = bodyslices.shift do
46
- # Read error messages and delivery status lines from the head of the email
47
- # to the previous line of the beginning of the original message.
39
+ # Read error messages and delivery status lines from the head of the email to the previous
40
+ # line of the beginning of the original message.
48
41
  if readcursor == 0
49
42
  # Beginning of the bounce message or delivery status part
50
- readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message]
43
+ readcursor |= Indicators[:deliverystatus] if MarkingsOf[:message].any? { |a| e.start_with?(a) }
51
44
  next
52
45
  end
53
46
  next if (readcursor & Indicators[:deliverystatus]) == 0
54
47
  next if e.empty?
55
48
 
56
49
  v = dscontents[-1]
57
- if cv = e.match(/\A[ \t]+Could not be delivered to: [<]([^ ]+[@][^ ]+)[>]/)
50
+ if e.include?(' Could not be delivered to: <')
58
51
  # Your mail sent on: Thu, 29 Apr 2010 11:04:47 +0900
59
52
  # Could not be delivered to: <******@**.***.**>
60
53
  # As their mailbox is full.
@@ -63,24 +56,26 @@ module Sisimai::Lhost
63
56
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
64
57
  v = dscontents[-1]
65
58
  end
66
- r = Sisimai::Address.s3s4(cv[1])
67
- next unless Sisimai::RFC5322.is_emailaddress(r)
59
+ r = Sisimai::Address.s3s4(e[e.index('<') + 1, e.size])
60
+ next unless Sisimai::Address.is_emailaddress(r)
68
61
  v['recipient'] = r
69
62
  recipients += 1
70
63
 
71
- elsif cv = e.match(/Your mail sent on: (.+)\z/)
64
+ elsif e.include?('Your mail sent on: ')
72
65
  # Your mail sent on: Thu, 29 Apr 2010 11:04:47 +0900
73
- v['date'] = cv[1]
66
+ v['date'] = e[19, e.size]
74
67
  else
75
68
  # As their mailbox is full.
76
69
  v['diagnosis'] ||= ''
77
- v['diagnosis'] << e + ' ' if e.start_with?(' ', "\t")
70
+ v['diagnosis'] << e + ' ' if e.start_with?(' ')
78
71
  end
79
72
  end
80
73
  return nil unless recipients > 0
81
74
 
75
+ require 'sisimai/smtp/command'
82
76
  dscontents.each do |e|
83
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
77
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
78
+ e['command'] = Sisimai::SMTP::Command.find(e['diagnosis'])
84
79
 
85
80
  if mhead['x-spasign'].to_s == 'NG'
86
81
  # Content-Type: text/plain; ..., X-SPASIGN: NG (spamghetti, au by KDDI)
@@ -88,8 +83,7 @@ module Sisimai::Lhost
88
83
  e['reason'] = 'filtered'
89
84
  else
90
85
  if e['command'] == 'RCPT'
91
- # set "userunknown" when the remote server rejected after RCPT
92
- # command.
86
+ # set "userunknown" when the remote server rejected after RCPT command.
93
87
  e['reason'] = 'userunknown'
94
88
  else
95
89
  # SMTP command is not RCPT
@@ -104,7 +98,7 @@ module Sisimai::Lhost
104
98
 
105
99
  end
106
100
 
107
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
101
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
108
102
  end
109
103
  def description; return 'au by KDDI: https://www.au.kddi.com'; end
110
104
  end