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,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