sisimai 4.25.16 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) 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 +412 -393
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +15 -15
  10. data/README-JA.md +140 -78
  11. data/README.md +290 -143
  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/{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 +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 +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 +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/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  177. /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::OpenSMTPD parses a bounce email which created by
3
- # OpenSMTPD. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::OpenSMTPD parses a bounce email which created by OpenSMTPD. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module OpenSMTPD
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/OpenSMTPD.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^[ ]+Below is a copy of the original message:|.freeze
9
+ Boundaries = [' Below is a copy of the original message:'].freeze
11
10
  StartingOf = {
12
11
  # http://www.openbsd.org/cgi-bin/man.cgi?query=smtpd&sektion=8
13
12
  # opensmtpd-5.4.2p1/smtpd/
@@ -70,21 +69,21 @@ module Sisimai::Lhost
70
69
  # @param [String] mbody Message body of a bounce email
71
70
  # @return [Hash] Bounce data list and message/rfc822 part
72
71
  # @return [Nil] it failed to parse or the arguments are missing
73
- def make(mhead, mbody)
72
+ def inquire(mhead, mbody)
74
73
  return nil unless mhead['subject'].start_with?('Delivery status notification')
75
74
  return nil unless mhead['from'].start_with?('Mailer Daemon <')
76
75
  return nil unless mhead['received'].any? { |a| a.include?(' (OpenSMTPD) with ') }
77
76
 
78
77
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
79
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
80
- bodyslices = emailsteak[0].split("\n")
78
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
79
+ bodyslices = emailparts[0].split("\n")
81
80
  readcursor = 0 # (Integer) Points the current cursor position
82
81
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
83
82
  v = nil
84
83
 
85
84
  while e = bodyslices.shift do
86
- # Read error messages and delivery status lines from the head of the email
87
- # to the previous line of the beginning of the original message.
85
+ # Read error messages and delivery status lines from the head of the email to the previous
86
+ # line of the beginning of the original message.
88
87
  if readcursor == 0
89
88
  # Beginning of the bounce message or delivery status part
90
89
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -105,15 +104,15 @@ module Sisimai::Lhost
105
104
  # Below is a copy of the original message:
106
105
  v = dscontents[-1]
107
106
 
108
- if cv = e.match(/\A([^ ]+?[@][^ ]+?):?[ ](.+)\z/)
107
+ if e.include?('@') && Sisimai::String.aligned(e, ['@', ' '])
109
108
  # kijitora@example.jp: 550 5.2.2 <kijitora@example>... Mailbox Full
110
109
  if v['recipient']
111
110
  # There are multiple recipient addresses in the message body.
112
111
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
113
112
  v = dscontents[-1]
114
113
  end
115
- v['recipient'] = cv[1]
116
- v['diagnosis'] = cv[2]
114
+ v['recipient'] = e[0, e.index(':')]
115
+ v['diagnosis'] = e[e.index(':') + 1, e.size]
117
116
  recipients += 1
118
117
  end
119
118
  end
@@ -128,7 +127,7 @@ module Sisimai::Lhost
128
127
  break
129
128
  end
130
129
  end
131
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
130
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
132
131
  end
133
132
  def description; return 'OpenSMTPD'; end
134
133
  end
@@ -1,14 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Outlook parses a bounce email which created by
3
- # Microsoft Outlook.com.
4
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Outlook parses a bounce email which created by Microsoft Outlook.com. Methods in
3
+ # the module are called from only Sisimai::Message.
5
4
  module Outlook
6
5
  class << self
7
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/US/Outlook.pm
8
6
  require 'sisimai/lhost'
9
7
 
10
8
  Indicators = Sisimai::Lhost.INDICATORS
11
- ReBackbone = %r|^Content-Type: message/rfc822|.freeze
9
+ Boundaries = ['Content-Type: message/rfc822'].freeze
12
10
  StartingOf = { message: ['This is an automatically generated Delivery Status Notification'] }.freeze
13
11
  MessagesOf = {
14
12
  'hostunknown' => ['The mail could not be delivered to the recipient because the domain is not reachable'],
@@ -20,7 +18,7 @@ module Sisimai::Lhost
20
18
  # @param [String] mbody Message body of a bounce email
21
19
  # @return [Hash] Bounce data list and message/rfc822 part
22
20
  # @return [Nil] it failed to parse or the arguments are missing
23
- def make(mhead, mbody)
21
+ def inquire(mhead, mbody)
24
22
  # X-Message-Delivery: Vj0xLjE7RD0wO0dEPTA7U0NMPTk7bD0xO3VzPTE=
25
23
  # X-Message-Info: AuEzbeVr9u5fkDpn2vR5iCu5wb6HBeY4iruBjnutBzpStnUabbM...
26
24
  match = 0
@@ -30,21 +28,20 @@ module Sisimai::Lhost
30
28
  match += 1 if mhead['received'].any? { |a| a.include?('.hotmail.com') }
31
29
  return nil if match < 2
32
30
 
33
- require 'sisimai/rfc1894'
34
31
  fieldtable = Sisimai::RFC1894.FIELDTABLE
35
32
  permessage = {} # (Hash) Store values of each Per-Message field
36
33
 
37
34
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
38
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
39
- bodyslices = emailsteak[0].split("\n")
35
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
36
+ bodyslices = emailparts[0].split("\n")
40
37
  readslices = ['']
41
38
  readcursor = 0 # (Integer) Points the current cursor position
42
39
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
43
40
  v = nil
44
41
 
45
42
  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.
43
+ # Read error messages and delivery status lines from the head of the email to the previous
44
+ # line of the beginning of the original message.
48
45
  readslices << e # Save the current line for the next loop
49
46
 
50
47
  if readcursor == 0
@@ -85,14 +82,14 @@ module Sisimai::Lhost
85
82
  next unless fieldtable[o[0]]
86
83
  v[fieldtable[o[0]]] = o[2]
87
84
 
88
- next unless f == 1
85
+ next unless f
89
86
  permessage[fieldtable[o[0]]] = o[2]
90
87
  end
91
88
  else
92
89
  # Continued line of the value of Diagnostic-Code field
93
90
  next unless readslices[-2].start_with?('Diagnostic-Code:')
94
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
95
- v['diagnosis'] << ' ' << cv[1]
91
+ next unless e.start_with?(' ')
92
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
96
93
  readslices[-1] = 'Diagnostic-Code: ' << e
97
94
  end
98
95
  end
@@ -124,7 +121,7 @@ module Sisimai::Lhost
124
121
  end
125
122
  end
126
123
 
127
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
124
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
128
125
  end
129
126
  def description; return 'Microsoft Outlook.com: https://www.outlook.com/'; end
130
127
  end
@@ -1,34 +1,23 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Postfix parses a bounce email which created by
3
- # Postfix. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Postfix parses a bounce email which created by Postfix. Methods in the module are
3
+ # called from only Sisimai::Message.
4
4
  module Postfix
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/Postfix.pm
7
6
  require 'sisimai/lhost'
7
+ require 'sisimai/smtp/command'
8
8
 
9
9
  # Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html
10
10
  Indicators = Sisimai::Lhost.INDICATORS
11
- ReBackbone = %r<^Content-Type:[ ](?:message/rfc822|text/rfc822-headers)>.freeze
12
- MarkingsOf = {
13
- message: %r{\A(?>
14
- [ ]+The[ ](?:
15
- Postfix[ ](?:
16
- program\z # The Postfix program
17
- |on[ ].+[ ]program\z # The Postfix on <os name> program
18
- )
19
- |\w+[ ]Postfix[ ]program\z # The <name> Postfix program
20
- |mail[ \t]system\z # The mail system
21
- |\w+[ \t]program\z # The <custmized-name> program
22
- )
23
- |This[ ]is[ ]the[ ](?:
24
- Postfix[ ]program # This is the Postfix program
25
- |\w+[ ]Postfix[ ]program # This is the <name> Postfix program
26
- |\w+[ ]program # This is the <customized-name> Postfix program
27
- |mail[ ]system[ ]at[ ]host # This is the mail system at host <hostname>.
28
- )
29
- )
30
- }x,
31
- # :from => %r/ [(]Mail Delivery System[)]\z/,
11
+ Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
12
+ StartingOf = {
13
+ # Postfix manual - bounce(5) - http://www.postfix.org/bounce.5.html
14
+ message: [
15
+ ['The ', 'Postfix '], # The Postfix program, The Postfix on <os> program
16
+ ['The ', 'mail system'], # The mail system
17
+ ['The ', 'program'], # The <name> pogram
18
+ ['This is the', 'Postfix'], # This is the Postfix program
19
+ ['This is the', 'mail system'], # This is the mail system at host <hostname>
20
+ ],
32
21
  }.freeze
33
22
 
34
23
  # Parse bounce messages from Postfix
@@ -36,109 +25,160 @@ module Sisimai::Lhost
36
25
  # @param [String] mbody Message body of a bounce email
37
26
  # @return [Hash] Bounce data list and message/rfc822 part
38
27
  # @return [Nil] it failed to parse or the arguments are missing
39
- def make(mhead, mbody)
40
- return nil unless mhead['subject'] == 'Undelivered Mail Returned to Sender'
28
+ def inquire(mhead, mbody)
29
+ match = nil
30
+ sessx = nil
31
+
32
+ if mhead['subject'].include?('SMTP server: errors from ')
33
+ # src/smtpd/smtpd_chat.c:|337: post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
34
+ # src/smtpd/smtpd_chat.c:|338: var_mail_name, state->namaddr);
35
+ match = true
36
+ sessx = true
37
+ else
38
+ # Subject: Undelivered Mail Returned to Sender
39
+ match = true if mhead['subject'] == 'Undelivered Mail Returned to Sender'
40
+ end
41
+ return nil unless match
41
42
  return nil if mhead['x-aol-ip']
42
43
 
43
- require 'sisimai/rfc1894'
44
- require 'sisimai/address'
45
- fieldtable = Sisimai::RFC1894.FIELDTABLE
46
44
  permessage = {} # (Hash) Store values of each Per-Message field
47
-
48
45
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
49
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
50
- bodyslices = emailsteak[0].split("\n")
46
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
47
+ bodyslices = emailparts[0].split("\n")
51
48
  readslices = ['']
52
- readcursor = 0 # (Integer) Points the current cursor position
53
49
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
54
50
  nomessages = false # (Boolean) Delivery report unavailable
55
51
  commandset = [] # (Array) ``in reply to * command'' list
56
52
  anotherset = {} # Another error information
57
53
  v = nil
58
54
 
59
- while e = bodyslices.shift do
60
- # Read error messages and delivery status lines from the head of the email
61
- # to the previous line of the beginning of the original message.
62
- readslices << e # Save the current line for the next loop
55
+ if sessx
56
+ # The message body starts with 'Transcript of session follows.'
57
+ require 'sisimai/smtp/transcript'
58
+ transcript = Sisimai::SMTP::Transcript.rise(emailparts[0], 'In:', 'Out:')
59
+
60
+ return nil unless transcript
61
+ return nil if transcript.size == 0
62
+
63
+ transcript.each do |e|
64
+ # Pick email addresses, error messages, and the last SMTP command.
65
+ v ||= dscontents[-1]
66
+ p = e['response']
67
+
68
+ if e['command'] == 'HELO' || e['command'] == 'EHLO'
69
+ # Use the argument of EHLO/HELO command as a value of "lhost"
70
+ v['lhost'] = e['argument']
71
+
72
+ elsif e['command'] == 'MAIL'
73
+ # Set the argument of "MAIL" command to pseudo To: header of the original message
74
+ emailparts[1] += sprintf("To: %s\n", e['argument']) if emailparts[1].size == 0
63
75
 
64
- if readcursor == 0
65
- # Beginning of the bounce message or message/delivery-status part
66
- readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message]
67
- next
76
+ elsif e['command'] == 'RCPT'
77
+ # RCPT TO: <...>
78
+ if v['recipient']
79
+ # There are multiple recipient addresses in the transcript of session
80
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
81
+ v = dscontents[-1]
82
+ end
83
+ v['recipient'] = e['argument']
84
+ recipients += 1
85
+ end
86
+
87
+ next if p['reply'].to_i < 400
88
+ commandset << e['command']
89
+ v['diagnosis'] ||= p['text'].join(' ')
90
+ v['replycode'] ||= p['reply']
91
+ v['status'] ||= p['status']
68
92
  end
69
- next if (readcursor & Indicators[:deliverystatus]) == 0
70
- next if e.empty?
71
-
72
- if f = Sisimai::RFC1894.match(e)
73
- # "e" matched with any field defined in RFC3464
74
- next unless o = Sisimai::RFC1894.field(e)
75
- v = dscontents[-1]
76
-
77
- if o[-1] == 'addr'
78
- # Final-Recipient: rfc822; kijitora@example.jp
79
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
80
- if o[0] == 'final-recipient'
93
+ else
94
+ fieldtable = Sisimai::RFC1894.FIELDTABLE
95
+ readcursor = 0 # (Integer) Points the current cursor position
96
+
97
+ while e = bodyslices.shift do
98
+ # Read error messages and delivery status lines from the head of the email to the previous
99
+ # line of the beginning of the original message.
100
+ readslices << e # Save the current line for the next loop
101
+
102
+ if readcursor == 0
103
+ # Beginning of the bounce message or message/delivery-status part
104
+ readcursor |= Indicators[:deliverystatus] if StartingOf[:message].any? { |a| Sisimai::String.aligned(e, a) }
105
+ next
106
+ end
107
+ next if (readcursor & Indicators[:deliverystatus]) == 0
108
+ next if e.empty?
109
+
110
+ if f = Sisimai::RFC1894.match(e)
111
+ # "e" matched with any field defined in RFC3464
112
+ next unless o = Sisimai::RFC1894.field(e)
113
+ v = dscontents[-1]
114
+
115
+ if o[-1] == 'addr'
81
116
  # Final-Recipient: rfc822; kijitora@example.jp
82
- if v['recipient']
83
- # There are multiple recipient addresses in the message body.
84
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
85
- v = dscontents[-1]
117
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
118
+ if o[0] == 'final-recipient'
119
+ # Final-Recipient: rfc822; kijitora@example.jp
120
+ if v['recipient']
121
+ # There are multiple recipient addresses in the message body.
122
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
123
+ v = dscontents[-1]
124
+ end
125
+ v['recipient'] = o[2]
126
+ recipients += 1
127
+ else
128
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
129
+ v['alias'] = o[2]
86
130
  end
87
- v['recipient'] = o[2]
88
- recipients += 1
131
+ elsif o[-1] == 'code'
132
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
133
+ v['spec'] = o[1]
134
+ v['spec'] = 'SMTP' if v['spec'] == 'X-POSTFIX'
135
+ v['diagnosis'] = o[2]
89
136
  else
90
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
91
- v['alias'] = o[2]
137
+ # Other DSN fields defined in RFC3464
138
+ next unless fieldtable[o[0]]
139
+ v[fieldtable[o[0]]] = o[2]
140
+
141
+ next unless f
142
+ permessage[fieldtable[o[0]]] = o[2]
92
143
  end
93
- elsif o[-1] == 'code'
94
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
95
- v['spec'] = o[1]
96
- v['spec'] = 'SMTP' if v['spec'] == 'X-POSTFIX'
97
- v['diagnosis'] = o[2]
98
144
  else
99
- # Other DSN fields defined in RFC3464
100
- next unless fieldtable[o[0]]
101
- v[fieldtable[o[0]]] = o[2]
145
+ # If you do so, please include this problem report. You can
146
+ # delete your own text from the attached returned message.
147
+ #
148
+ # The mail system
149
+ #
150
+ # <userunknown@example.co.jp>: host mx.example.co.jp[192.0.2.153] said: 550
151
+ # 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO command)
152
+ if readslices[-2].start_with?('Diagnostic-Code:') && e.include?(' ')
153
+ # Continued line of the value of Diagnostic-Code header
154
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
155
+ readslices[-1] = 'Diagnostic-Code: ' << e
102
156
 
103
- next unless f == 1
104
- permessage[fieldtable[o[0]]] = o[2]
105
- end
106
- else
107
- # If you do so, please include this problem report. You can
108
- # delete your own text from the attached returned message.
109
- #
110
- # The mail system
111
- #
112
- # <userunknown@example.co.jp>: host mx.example.co.jp[192.0.2.153] said: 550
113
- # 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO command)
114
- if readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ \t]+(.+)\z/)
115
- # Continued line of the value of Diagnostic-Code header
116
- v['diagnosis'] << ' ' << cv[1]
117
- readslices[-1] = 'Diagnostic-Code: ' << e
118
-
119
- elsif cv = e.match(/\A(X-Postfix-Sender):[ ]*rfc822;[ ]*(.+)\z/)
120
- # X-Postfix-Sender: rfc822; shironeko@example.org
121
- emailsteak[1] << cv[1] << ': ' << cv[2] << "\n"
157
+ elsif Sisimai::String.aligned(e, ['X-Postfix-Sender:', 'rfc822;', '@'])
158
+ # X-Postfix-Sender: rfc822; shironeko@example.org
159
+ emailparts[1] << 'X-Postfix-Sender: ' << Sisimai::Address.s3s4(e[e.index(';') + 1, e.size]) << "\n"
122
160
 
123
- else
124
- if cv = e.match(/[ \t][(]in reply to (?:end of )?([A-Z]{4}).*/) ||
125
- cv = e.match(/([A-Z]{4})[ \t]*.*command[)]\z/)
126
- # 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO
127
- commandset << cv[1]
128
- anotherset['diagnosis'] ||= ''
129
- anotherset['diagnosis'] << ' ' << e
130
161
  else
131
162
  # Alternative error message and recipient
132
- if cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>] [(]expanded from [<](.+)[>][)]:[ \t]*(.+)\z/)
163
+ if e.include?(' (in reply to ') || e.include?('command)')
164
+ # 5.1.1 <userunknown@example.co.jp>... User Unknown (in reply to RCPT TO
165
+ q = Sisimai::SMTP::Command.find(e); commandset << q if q
166
+ anotherset['diagnosis'] ||= ''
167
+ anotherset['diagnosis'] << ' ' << e
168
+
169
+ elsif Sisimai::String.aligned(e, ['<', '@', '>', '(expanded from<', '):'])
133
170
  # <r@example.ne.jp> (expanded from <kijitora@example.org>): user ...
134
- anotherset['recipient'] = cv[1]
135
- anotherset['alias'] = cv[2]
136
- anotherset['diagnosis'] = cv[3]
171
+ p1 = e.index('> ')
172
+ p2 = e.index('(expanded from ', p1)
173
+ p3 = e.index('>): ', p2 + 14)
174
+ anotherset['recipient'] = Sisimai::Address.s3s4(e[0, p1])
175
+ anotherset['alias'] = Sisimai::Address.s3s4(e[p2 + 15, p3 - p2 - 15])
176
+ anotherset['diagnosis'] = e[p3 + 3, e.size]
137
177
 
138
- elsif cv = e.match(/\A[<]([^ ]+[@][^ ]+)[>]:(.*)\z/)
178
+ elsif e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:'])
139
179
  # <kijitora@exmaple.jp>: ...
140
- anotherset['recipient'] = cv[1]
141
- anotherset['diagnosis'] = cv[2]
180
+ anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')])
181
+ anotherset['diagnosis'] = e[e.index('>:') + 2, e.size]
142
182
 
143
183
  elsif e.include?('--- Delivery report unavailable ---')
144
184
  # postfix-3.1.4/src/bounce/bounce_notify_util.c
@@ -152,16 +192,17 @@ module Sisimai::Lhost
152
192
  # bounce_notify_util.c:602|} else {
153
193
  nomessages = true
154
194
  else
155
- # Get error message continued from the previous line
195
+ # Get an error message continued from the previous line
156
196
  next unless anotherset['diagnosis']
157
- if e =~ /\A[ \t]{4}(.+)\z/
197
+ if e.start_with?(' ')
158
198
  # host mx.example.jp said:...
159
- anotherset['diagnosis'] << ' ' << e
199
+ anotherset['diagnosis'] << ' ' << e[4, e.size]
160
200
  end
161
201
  end
162
202
  end
163
203
  end
164
- end
204
+ end # end of while()
205
+
165
206
  end
166
207
 
167
208
  unless recipients > 0
@@ -171,12 +212,13 @@ module Sisimai::Lhost
171
212
  dscontents[-1]['recipient'] = anotherset['recipient']
172
213
  recipients += 1
173
214
  else
174
- # Get a recipient address from message/rfc822 part if the delivery
175
- # report was unavailable: '--- Delivery report unavailable ---'
176
- if nomessages && cv = emailsteak[1].match(/^To:[ ]*(.+)$/)
177
- # Try to get a recipient address from To: field in the original
178
- # message at message/rfc822 part
179
- dscontents[-1]['recipient'] = Sisimai::Address.s3s4(cv[1])
215
+ # Get a recipient address from message/rfc822 part if the delivery report was unavailable:
216
+ # '--- Delivery report unavailable ---'
217
+ p1 = emailparts[1].index("\nTo: ") || -1
218
+ p2 = emailparts[1].index("\n", p1 + 6) || -1
219
+ if nomessages && p1 > 0
220
+ # Try to get a recipient address from To: field in the original message at message/rfc822 part
221
+ dscontents[-1]['recipient'] = Sisimai::Address.s3s4(emailparts[1][p1 + 5, p2 - p1 - 5])
180
222
  recipients += 1
181
223
  end
182
224
  end
@@ -190,50 +232,58 @@ module Sisimai::Lhost
190
232
 
191
233
  if anotherset['diagnosis']
192
234
  # Copy alternative error message
193
- e['diagnosis'] = anotherset['diagnosis'] unless e['diagnosis']
235
+ anotherset['diagnosis'] = Sisimai::String.sweep(anotherset['diagnosis'])
236
+ e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].nil? || e['diagnosis'].empty?
194
237
 
195
238
  if e['diagnosis'] =~ /\A\d+\z/
239
+ # Override the value of diagnostic code message
196
240
  e['diagnosis'] = anotherset['diagnosis']
197
241
  else
198
242
  # More detailed error message is in "anotherset"
199
- as = nil # status
200
- ar = nil # replycode
243
+ as = '' # status
244
+ ar = '' # replycode
201
245
 
202
246
  e['status'] ||= ''
203
247
  e['replycode'] ||= ''
204
248
 
205
- if e['status'] == '' || e['status'].start_with?('4.0.0', '5.0.0')
249
+ if e['status'].empty? || e['status'].start_with?('4.0.0', '5.0.0')
206
250
  # Check the value of D.S.N. in anotherset
207
- as = Sisimai::SMTP::Status.find(anotherset['diagnosis'])
208
- if as && as[-3, 3] != '0.0'
251
+ as = Sisimai::SMTP::Status.find(anotherset['diagnosis']) || ''
252
+ if as.size > 0 && as[-4, 4] != '.0.0'
209
253
  # The D.S.N. is neither an empty nor *.0.0
210
254
  e['status'] = as
211
255
  end
212
256
  end
213
257
 
214
- if e['replycode'] == '' || e['replycode'].start_with?('400', '500')
215
- # Check the value of SMTP reply code in anotherset
216
- ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis'])
217
- if ar && ar[-2, 2].to_i != 0
258
+ if e['replycode'].empty? || e['replycode'].end_with?('00')
259
+ # Check the value of SMTP reply code in $anotherset
260
+ ar = Sisimai::SMTP::Reply.find(anotherset['diagnosis']) || ''
261
+ if ar.size > 0 && ar.end_with?('00') == false
218
262
  # The SMTP reply code is neither an empty nor *00
219
263
  e['replycode'] = ar
220
264
  end
221
265
  end
222
266
 
223
- if (as || ar) && (anotherset['diagnosis'].size > e['diagnosis'].size)
224
- # Update the error message in e['diagnosis']
267
+ while true
268
+ # Replace e['diagnosis'] with the value of anotherset['diagnosis'] when all the
269
+ # following conditions have not matched.
270
+ break if (as + ar).size == 0
271
+ break if anotherset['diagnosis'].size < e['diagnosis'].size
272
+ break if anotherset['diagnosis'].include?(e['diagnosis']) == false
273
+
225
274
  e['diagnosis'] = anotherset['diagnosis']
275
+ break
226
276
  end
227
277
  end
228
278
  end
229
279
 
230
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
231
- e['command'] = commandset.shift || nil
232
- e['command'] ||= 'HELO' if e['diagnosis'] =~ /refused to talk to me:/
233
- e['spec'] ||= 'SMTP' if e['diagnosis'] =~ /host .+ said:/
280
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
281
+ e['command'] = commandset.shift || Sisimai::SMTP::Command.find(e['diagnosis'])
282
+ e['command'] ||= 'HELO' if e['diagnosis'].include?('refused to talk to me:')
283
+ e['spec'] ||= 'SMTP' if Sisimai::String.aligned(e['diagnosis'], ['host ', ' said:'])
234
284
  end
235
285
 
236
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
286
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
237
287
  end
238
288
  def description; return 'Postfix'; end
239
289
  end
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::PowerMTA parses a bounce email which created by
3
- # amavsid-new. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::PowerMTA parses a bounce email which created by PowerMTA. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module PowerMTA
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/PowerMTA.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^Content-Type:[ ]text/rfc822-headers|.freeze
9
+ Boundaries = ['Content-Type: text/rfc822-headers'].freeze
11
10
  StartingOf = { message: ['Hello, this is the mail server on '] }.freeze
12
11
  Categories = {
13
12
  'bad-domain' => 'hostunknown',
@@ -27,23 +26,22 @@ module Sisimai::Lhost
27
26
  # @return [Hash] Bounce data list and message/rfc822 part
28
27
  # @return [Nil] it failed to parse or the arguments are missing
29
28
  # @since v4.25.6
30
- def make(mhead, mbody)
29
+ def inquire(mhead, mbody)
31
30
  return nil unless mhead['subject'].to_s.start_with?('Delivery report')
32
31
 
33
- require 'sisimai/rfc1894'
34
32
  fieldtable = Sisimai::RFC1894.FIELDTABLE
35
33
  permessage = {} # (Hash) Store values of each Per-Message field
36
34
 
37
35
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
38
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
39
- bodyslices = emailsteak[0].split("\n")
36
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
37
+ bodyslices = emailparts[0].split("\n")
40
38
  readcursor = 0 # (Integer) Points the current cursor position
41
39
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
42
40
  v = nil
43
41
 
44
42
  while e = bodyslices.shift do
45
- # Read error messages and delivery status lines from the head of the email
46
- # to the previous line of the beginning of the original message.
43
+ # Read error messages and delivery status lines from the head of the email to the previous
44
+ # line of the beginning of the original message.
47
45
  if readcursor == 0
48
46
  # Beginning of the bounce message or message/delivery-status part
49
47
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -82,7 +80,7 @@ module Sisimai::Lhost
82
80
  next unless fieldtable[o[0]]
83
81
  v[fieldtable[o[0]]] = o[2]
84
82
 
85
- next unless f == 1
83
+ next unless f
86
84
  permessage[fieldtable[o[0]]] = o[2]
87
85
  end
88
86
  else
@@ -96,9 +94,9 @@ module Sisimai::Lhost
96
94
  #
97
95
  # <kijitora@example.jp> delivery failed; will not continue trying
98
96
  #
99
- if cv = e.match(/\AX-PowerMTA-BounceCategory:[ ]*(.+)\z/)
97
+ if e.start_with?('X-PowerMTA-BounceCategory: ')
100
98
  # X-PowerMTA-BounceCategory: bad-mailbox
101
- v['category'] = cv[1]
99
+ v['category'] = e[e.index(':') + 2, e.size]
102
100
  end
103
101
  end
104
102
  end
@@ -111,7 +109,7 @@ module Sisimai::Lhost
111
109
  e['reason'] = Categories[e['category']] || ''
112
110
  end
113
111
 
114
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
112
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
115
113
  end
116
114
  def description; return 'PowerMTA: https://www.sparkpost.com/powermta/'; end
117
115
  end