sisimai 5.1.0-java → 5.2.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +1 -1
  3. data/ChangeLog.md +102 -0
  4. data/Makefile +4 -2
  5. data/README-JA.md +23 -16
  6. data/README.md +22 -15
  7. data/lib/sisimai/arf.rb +121 -210
  8. data/lib/sisimai/fact.rb +208 -158
  9. data/lib/sisimai/lda.rb +98 -0
  10. data/lib/sisimai/lhost/activehunter.rb +1 -1
  11. data/lib/sisimai/lhost/amazonses.rb +185 -301
  12. data/lib/sisimai/lhost/apachejames.rb +48 -51
  13. data/lib/sisimai/lhost/biglobe.rb +1 -2
  14. data/lib/sisimai/lhost/courier.rb +10 -8
  15. data/lib/sisimai/lhost/domino.rb +25 -25
  16. data/lib/sisimai/lhost/dragonfly.rb +3 -4
  17. data/lib/sisimai/lhost/einsundeins.rb +3 -4
  18. data/lib/sisimai/lhost/exchange2003.rb +6 -8
  19. data/lib/sisimai/lhost/exchange2007.rb +111 -101
  20. data/lib/sisimai/lhost/exim.rb +232 -242
  21. data/lib/sisimai/lhost/ezweb.rb +43 -51
  22. data/lib/sisimai/lhost/fml.rb +2 -3
  23. data/lib/sisimai/lhost/gmail.rb +32 -28
  24. data/lib/sisimai/lhost/gmx.rb +4 -16
  25. data/lib/sisimai/lhost/googlegroups.rb +9 -8
  26. data/lib/sisimai/lhost/googleworkspace.rb +94 -0
  27. data/lib/sisimai/lhost/imailserver.rb +7 -16
  28. data/lib/sisimai/lhost/interscanmss.rb +1 -1
  29. data/lib/sisimai/lhost/kddi.rb +3 -4
  30. data/lib/sisimai/lhost/mailfoundry.rb +2 -5
  31. data/lib/sisimai/lhost/mailmarshalsmtp.rb +1 -2
  32. data/lib/sisimai/lhost/messagingserver.rb +14 -13
  33. data/lib/sisimai/lhost/mfilter.rb +4 -3
  34. data/lib/sisimai/lhost/notes.rb +2 -4
  35. data/lib/sisimai/lhost/opensmtpd.rb +2 -2
  36. data/lib/sisimai/lhost/postfix.rb +25 -27
  37. data/lib/sisimai/lhost/qmail.rb +130 -106
  38. data/lib/sisimai/lhost/sendmail.rb +19 -18
  39. data/lib/sisimai/lhost/v5sendmail.rb +88 -60
  40. data/lib/sisimai/lhost/verizon.rb +2 -2
  41. data/lib/sisimai/lhost/x1.rb +1 -1
  42. data/lib/sisimai/lhost/x2.rb +1 -2
  43. data/lib/sisimai/lhost/x3.rb +2 -2
  44. data/lib/sisimai/lhost/x6.rb +1 -1
  45. data/lib/sisimai/lhost/zoho.rb +2 -2
  46. data/lib/sisimai/lhost.rb +18 -21
  47. data/lib/sisimai/message.rb +93 -146
  48. data/lib/sisimai/order.rb +21 -77
  49. data/lib/sisimai/reason/authfailure.rb +1 -4
  50. data/lib/sisimai/reason/badreputation.rb +2 -2
  51. data/lib/sisimai/reason/blocked.rb +7 -10
  52. data/lib/sisimai/reason/contenterror.rb +7 -1
  53. data/lib/sisimai/reason/exceedlimit.rb +1 -4
  54. data/lib/sisimai/reason/failedstarttls.rb +42 -0
  55. data/lib/sisimai/reason/filtered.rb +5 -4
  56. data/lib/sisimai/reason/hasmoved.rb +1 -2
  57. data/lib/sisimai/reason/hostunknown.rb +3 -3
  58. data/lib/sisimai/reason/mailboxfull.rb +2 -4
  59. data/lib/sisimai/reason/mailererror.rb +1 -2
  60. data/lib/sisimai/reason/mesgtoobig.rb +2 -4
  61. data/lib/sisimai/reason/norelaying.rb +2 -3
  62. data/lib/sisimai/reason/notaccept.rb +2 -3
  63. data/lib/sisimai/reason/notcompliantrfc.rb +10 -4
  64. data/lib/sisimai/reason/rejected.rb +1 -1
  65. data/lib/sisimai/reason/requireptr.rb +2 -2
  66. data/lib/sisimai/reason/securityerror.rb +1 -3
  67. data/lib/sisimai/reason/spamdetected.rb +6 -8
  68. data/lib/sisimai/reason/speeding.rb +1 -2
  69. data/lib/sisimai/reason/suppressed.rb +36 -0
  70. data/lib/sisimai/reason/suspend.rb +1 -3
  71. data/lib/sisimai/reason/systemerror.rb +5 -0
  72. data/lib/sisimai/reason/toomanyconn.rb +1 -2
  73. data/lib/sisimai/reason/userunknown.rb +1 -1
  74. data/lib/sisimai/reason/virusdetected.rb +5 -6
  75. data/lib/sisimai/reason.rb +77 -73
  76. data/lib/sisimai/rfc1123.rb +152 -0
  77. data/lib/sisimai/rfc1894.rb +102 -62
  78. data/lib/sisimai/rfc2045.rb +2 -1
  79. data/lib/sisimai/rfc3464/thirdparty.rb +102 -0
  80. data/lib/sisimai/rfc3464.rb +222 -343
  81. data/lib/sisimai/rfc3834.rb +1 -1
  82. data/lib/sisimai/rfc5322.rb +7 -17
  83. data/lib/sisimai/rfc791.rb +69 -0
  84. data/lib/sisimai/rhost/aol.rb +36 -0
  85. data/lib/sisimai/rhost/apple.rb +5 -2
  86. data/lib/sisimai/rhost/cox.rb +3 -2
  87. data/lib/sisimai/rhost/facebook.rb +100 -0
  88. data/lib/sisimai/rhost/franceptt.rb +3 -2
  89. data/lib/sisimai/rhost/godaddy.rb +3 -2
  90. data/lib/sisimai/rhost/google.rb +19 -17
  91. data/lib/sisimai/rhost/gsuite.rb +42 -0
  92. data/lib/sisimai/rhost/iua.rb +3 -3
  93. data/lib/sisimai/rhost/kddi.rb +3 -2
  94. data/lib/sisimai/rhost/messagelabs.rb +37 -0
  95. data/lib/sisimai/rhost/microsoft.rb +56 -49
  96. data/lib/sisimai/rhost/mimecast.rb +29 -27
  97. data/lib/sisimai/rhost/nttdocomo.rb +4 -3
  98. data/lib/sisimai/rhost/outlook.rb +36 -0
  99. data/lib/sisimai/rhost/spectrum.rb +3 -2
  100. data/lib/sisimai/rhost/tencent.rb +3 -2
  101. data/lib/sisimai/rhost/yahooinc.rb +4 -3
  102. data/lib/sisimai/rhost.rb +69 -39
  103. data/lib/sisimai/smtp/command.rb +31 -21
  104. data/lib/sisimai/smtp/failure.rb +103 -0
  105. data/lib/sisimai/smtp/reply.rb +29 -25
  106. data/lib/sisimai/smtp/status.rb +36 -19
  107. data/lib/sisimai/smtp/transcript.rb +15 -15
  108. data/lib/sisimai/string.rb +0 -46
  109. data/lib/sisimai/version.rb +1 -1
  110. data/set-of-emails/maildir/bsd/lhost-postfix-30.eml +81 -81
  111. data/set-of-emails/maildir/bsd/{lhost-aol-03.eml → rhost-aol-03.eml} +1264 -1264
  112. data/set-of-emails/maildir/bsd/{lhost-aol-04.eml → rhost-aol-04.eml} +1260 -1260
  113. data/set-of-emails/maildir/bsd/{lhost-aol-05.eml → rhost-aol-05.eml} +105 -105
  114. data/set-of-emails/maildir/bsd/{lhost-aol-06.eml → rhost-aol-06.eml} +105 -105
  115. data/set-of-emails/maildir/bsd/rhost-gsuite-01.eml +189 -0
  116. data/set-of-emails/maildir/bsd/rhost-gsuite-02.eml +180 -0
  117. data/set-of-emails/maildir/bsd/rhost-gsuite-03.eml +251 -0
  118. data/set-of-emails/maildir/bsd/rhost-gsuite-04.eml +211 -0
  119. data/set-of-emails/maildir/bsd/rhost-gsuite-05.eml +226 -0
  120. data/set-of-emails/maildir/bsd/rhost-gsuite-06.eml +257 -0
  121. data/set-of-emails/maildir/bsd/rhost-gsuite-07.eml +289 -0
  122. data/set-of-emails/maildir/bsd/rhost-gsuite-08.eml +231 -0
  123. data/set-of-emails/maildir/bsd/rhost-gsuite-09.eml +231 -0
  124. data/set-of-emails/maildir/bsd/rhost-gsuite-10.eml +254 -0
  125. data/set-of-emails/maildir/bsd/rhost-gsuite-11.eml +228 -0
  126. data/set-of-emails/maildir/bsd/rhost-gsuite-12.eml +271 -0
  127. data/set-of-emails/maildir/bsd/rhost-gsuite-13.eml +261 -0
  128. data/set-of-emails/maildir/bsd/rhost-gsuite-14.eml +273 -0
  129. data/set-of-emails/maildir/bsd/rhost-gsuite-15.eml +229 -0
  130. data/set-of-emails/maildir/bsd/{lhost-messagelabs-01.eml → rhost-messagelabs-01.eml} +93 -93
  131. data/set-of-emails/maildir/bsd/rhost-outlook-01.eml +72 -0
  132. data/set-of-emails/maildir/bsd/rhost-outlook-02.eml +72 -0
  133. data/set-of-emails/maildir/bsd/rhost-outlook-03.eml +72 -0
  134. data/set-of-emails/maildir/bsd/rhost-outlook-04.eml +79 -0
  135. data/set-of-emails/maildir/bsd/rhost-outlook-06.eml +75 -0
  136. data/set-of-emails/maildir/bsd/rhost-outlook-07.eml +70 -0
  137. data/set-of-emails/maildir/bsd/rhost-outlook-08.eml +70 -0
  138. data/set-of-emails/maildir/bsd/rhost-outlook-09.eml +56 -0
  139. data/set-of-emails/maildir/tmp/arf-22.eml +49 -0
  140. data/set-of-emails/maildir/tmp/arf-23.eml +49 -0
  141. data/set-of-emails/maildir/tmp/arf-24.eml +50 -0
  142. data/set-of-emails/maildir/tmp/lhost-exim-07.eml +28 -0
  143. metadata +73 -56
  144. data/lib/sisimai/lhost/amavis.rb +0 -163
  145. data/lib/sisimai/lhost/amazonworkmail.rb +0 -127
  146. data/lib/sisimai/lhost/aol.rb +0 -125
  147. data/lib/sisimai/lhost/barracuda.rb +0 -92
  148. data/lib/sisimai/lhost/bigfoot.rb +0 -125
  149. data/lib/sisimai/lhost/facebook.rb +0 -188
  150. data/lib/sisimai/lhost/gsuite.rb +0 -194
  151. data/lib/sisimai/lhost/mailru.rb +0 -214
  152. data/lib/sisimai/lhost/mcafee.rb +0 -109
  153. data/lib/sisimai/lhost/messagelabs.rb +0 -120
  154. data/lib/sisimai/lhost/mxlogic.rb +0 -198
  155. data/lib/sisimai/lhost/office365.rb +0 -252
  156. data/lib/sisimai/lhost/outlook.rb +0 -129
  157. data/lib/sisimai/lhost/powermta.rb +0 -118
  158. data/lib/sisimai/lhost/receivingses.rb +0 -126
  159. data/lib/sisimai/lhost/sendgrid.rb +0 -150
  160. data/lib/sisimai/lhost/surfcontrol.rb +0 -105
  161. data/lib/sisimai/lhost/x4.rb +0 -269
  162. data/lib/sisimai/lhost/x5.rb +0 -112
  163. data/lib/sisimai/lhost/yahoo.rb +0 -102
  164. data/lib/sisimai/lhost/yandex.rb +0 -118
  165. data/lib/sisimai/mda.rb +0 -121
  166. data/lib/sisimai/smtp/error.rb +0 -119
  167. /data/set-of-emails/maildir/bsd/{lhost-googlegroups-15.eml → lhost-googleworkspace-01.eml} +0 -0
  168. /data/set-of-emails/maildir/bsd/{lhost-x4-08.eml → lhost-x2-06.eml} +0 -0
  169. /data/set-of-emails/maildir/bsd/{lhost-gsuite-01.eml → rfc3464-51.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{lhost-gsuite-03.eml → rfc3464-52.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{lhost-gsuite-04.eml → rfc3464-53.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{lhost-gsuite-05.eml → rfc3464-54.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{lhost-gsuite-06.eml → rfc3464-55.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{lhost-gsuite-07.eml → rfc3464-56.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{lhost-gsuite-08.eml → rfc3464-57.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{lhost-gsuite-09.eml → rfc3464-58.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{lhost-gsuite-10.eml → rfc3464-59.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{lhost-gsuite-11.eml → rfc3464-60.eml} +0 -0
  179. /data/set-of-emails/maildir/bsd/{lhost-gsuite-12.eml → rfc3464-61.eml} +0 -0
  180. /data/set-of-emails/maildir/bsd/{lhost-gsuite-13.eml → rfc3464-62.eml} +0 -0
  181. /data/set-of-emails/maildir/bsd/{lhost-gsuite-14.eml → rfc3464-63.eml} +0 -0
  182. /data/set-of-emails/maildir/bsd/{lhost-gsuite-15.eml → rfc3464-64.eml} +0 -0
  183. /data/set-of-emails/maildir/bsd/{lhost-gsuite-02.eml → rfc3464-65.eml} +0 -0
  184. /data/set-of-emails/maildir/bsd/{lhost-aol-01.eml → rhost-aol-01.eml} +0 -0
  185. /data/set-of-emails/maildir/bsd/{lhost-aol-02.eml → rhost-aol-02.eml} +0 -0
  186. /data/set-of-emails/maildir/bsd/{lhost-facebook-03.eml → rhost-facebook-03.eml} +0 -0
  187. /data/set-of-emails/maildir/bsd/{lhost-facebook-04.eml → rhost-facebook-04.eml} +0 -0
  188. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-02.eml → rhost-messagelabs-02.eml} +0 -0
  189. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-03.eml → rhost-messagelabs-03.eml} +0 -0
  190. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-37.eml +0 -0
  191. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-38.eml +0 -0
  192. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-39.eml +0 -0
@@ -3,400 +3,279 @@ module Sisimai
3
3
  module RFC3464
4
4
  class << self
5
5
  require 'sisimai/lhost'
6
+ require 'sisimai/string'
6
7
  require 'sisimai/address'
8
+ require 'sisimai/rfc1123'
7
9
  require 'sisimai/rfc1894'
10
+ require 'sisimai/rfc2045'
11
+ require 'sisimai/rfc5322'
12
+ require 'sisimai/rfc3464/thirdparty'
8
13
 
9
- # http://tools.ietf.org/html/rfc3464
10
14
  Indicators = Sisimai::Lhost.INDICATORS
11
- StartingOf = {
12
- message: [
13
- 'content-type: message/delivery-status',
14
- 'content-type: message/disposition-notification',
15
- 'content-type: text/plain; charset=',
16
- 'the original message was received at ',
17
- 'this report relates to your message',
18
- 'your message could not be delivered',
19
- 'your message was not delivered to ',
20
- 'your message was not delivered to the following recipients',
21
- ],
22
- rfc822: [
23
- 'content-type: message/rfc822',
24
- 'content-type: text/rfc822-headers',
25
- 'return-path: <'
26
- ],
27
- }.freeze
28
- ReadUntil0 = [
29
- # Stop reading when the following string have appeared at the first of a line
30
- 'a copy of the original message below this line:',
31
- 'content-type: message/delivery-status',
32
- 'for further assistance, please contact ',
33
- 'here is a copy of the first part of the message',
34
- 'received:',
35
- 'received-from-mta:',
36
- 'reporting-mta:',
37
- 'reporting-ua:',
38
- 'return-path:',
39
- 'the non-delivered message is attached to this message',
40
- ].freeze
41
- ReadUntil1 = [
42
- # Stop reading when the following string have appeared in a line
43
- 'attachment is a copy of the message',
44
- 'below is a copy of the original message:',
45
- 'below this line is a copy of the message',
46
- 'message contains ',
47
- 'message text follows: ',
48
- 'original message follows',
49
- 'the attachment contains the original mail headers',
50
- 'the first ',
51
- 'unsent message below',
52
- 'your message reads (in part):',
53
- ].freeze
54
- ReadAfter0 = [
55
- # Do not read before the following strings
56
- ' the postfix ',
57
- 'a summary of the undelivered message you sent follows:',
58
- 'the following is the error message',
59
- 'the message that you sent was undeliverable to the following',
60
- 'your message was not delivered to ',
61
- ].freeze
62
- DoNotRead0 = [' -----', ' -----', '--', '|--', '*'].freeze
63
- DoNotRead1 = ['mail from:', 'message-id:', ' from: '].freeze
64
- ReadEmail0 = [' ', '"', '<',].freeze
65
- ReadEmail1 = [
66
- # There is an email address around the following strings
67
- 'address:',
68
- 'addressed to',
69
- 'could not be delivered to:',
70
- 'delivered to',
71
- 'delivery failed:',
72
- 'did not reach the following recipient:',
73
- 'error-for:',
74
- 'failed recipient:',
75
- 'failed to deliver to',
76
- 'intended recipient:',
77
- 'mailbox is full:',
78
- 'recipient:',
79
- 'rcpt to:',
80
- 'smtp server <',
81
- 'the following recipients returned permanent errors:',
82
- 'the following addresses had permanent errors',
83
- 'the following message to',
84
- 'to: ',
85
- 'unknown user:',
86
- 'unable to deliver mail to the following recipient',
87
- 'undeliverable to',
88
- 'undeliverable address:',
89
- 'you sent mail to',
90
- 'your message has encountered delivery problems to the following recipients:',
91
- 'was automatically rejected',
92
- 'was rejected due to',
15
+ Boundaries = [
16
+ # When the new value added, the part of the value should be listed in delimiters variable
17
+ # defined at Sisimai::RFC2045.makeFlat() method
18
+ "Content-Type: message/rfc822",
19
+ "Content-Type: text/rfc822-headers",
20
+ "Content-Type: message/partial",
21
+ "Content-Disposition: inline", # See lhost-amavis-*.eml, lhost-facebook-*.eml
93
22
  ].freeze
23
+ StartingOf = { message: ["Content-Type: message/delivery-status"] }.freeze
24
+ FieldTable = Sisimai::RFC1894.FIELDTABLE
94
25
 
95
- # Detect an error for RFC3464
26
+ # Decode a bounce mail which have fields defined in RFC3464
96
27
  # @param [Hash] mhead Message headers of a bounce email
97
28
  # @param [String] mbody Message body of a bounce email
98
29
  # @return [Hash] Bounce data list and message/rfc822 part
99
30
  # @return [Nil] it failed to decode or the arguments are missing
100
31
  def inquire(mhead, mbody)
101
- fieldtable = Sisimai::RFC1894.FIELDTABLE
102
- permessage = {} # (Hash) Store values of each Per-Message field
32
+ # There is no "Content-Type: message/rfc822" line in the message body
33
+ if Boundaries.any? { |a| mbody.include?(a) } == false
34
+ # Insert "Content-Type: message/rfc822" before "Return-Path:" of the original message
35
+ p0 = mbody.index("\n\nReturn-Path:")
36
+ mbody = sprintf("%s%s%s", mbody[0, p0], Boundaries[0], mbody[p0 + 1, mbody.size]) if p0
37
+ end
103
38
 
39
+ permessage = {}
104
40
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
105
- bodyslices = mbody.scrub('?').split("\n")
106
- readslices = ['']
107
- rfc822text = '' # (String) message/rfc822 part text
108
- maybealias = nil # (String) Original-Recipient Field
109
- lowercased = '' # (String) Lowercased each line of the loop
110
- blanklines = 0 # (Integer) The number of blank lines
111
- readcursor = 0 # (Integer) Points the current cursor position
112
- recipients = 0 # (Integer) The number of 'Final-Recipient' header
113
- itisbounce = false
114
- connheader = {
115
- 'date' => nil, # The value of Arrival-Date header
116
- 'rhost' => nil, # The value of Reporting-MTA header
117
- 'lhost' => nil, # The value of Received-From-MTA header
118
- }
41
+ alternates = Sisimai::Lhost.DELIVERYSTATUS
42
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
43
+ readslices = [""]
44
+ readcursor = 0 # (Integer) Points the current cursor position
45
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
46
+ beforemesg = "" # (String) String before StartingOf[:message]
47
+ goestonext = false # (Bool) Flag: do not append the line into beforemesg
48
+ isboundary = [Sisimai::RFC2045.boundary(mhead["content-type"], 0)]; isboundary[0] ||= ""
119
49
  v = nil
120
50
 
51
+ while emailparts[0].index('@').nil? do
52
+ # There is no email address in the first element of emailparts
53
+ # There is a bounce message inside of message/rfc822 part at lhost-x5-*
54
+ p0 = -1 # The index of the boundary string found first
55
+ p1 = 0 # Offset position of the message body after the boundary string
56
+ ct = "" # Boundary string found first such as "Content-Type: message/rfc822"
57
+
58
+ Boundaries.each do |e|
59
+ # Look for a boundary string from the message body
60
+ p0 = mbody.index(e + "\n"); next if p0.nil?
61
+ p1 = p0 + e.size + 2
62
+ ct = e; break
63
+ end
64
+ break if p0.nil?
65
+
66
+ cx = mbody[p1, mbody.size]
67
+ p2 = cx.index("\n\n")
68
+ cv = cx[p2 + 2, mbody.size]
69
+ emailparts = Sisimai::RFC5322.part(cv, [ct], 0)
70
+ break
71
+ end
72
+
73
+ if emailparts[0].index(StartingOf[:message][0]) == nil
74
+ # There is no "Content-Type: message/delivery-status" line in the message body
75
+ # Insert "Content-Type: message/delivery-status" before "Reporting-MTA:" field
76
+ cv = "\nReporting-MTA:"
77
+ e0 = emailparts[0]
78
+ p0 = e0.index(cv)
79
+ emailparts[0] = sprintf("%s\n\n%s%s", e0[0, p0], StartingOf[:message][0], e0[p0, e0.size]) if p0
80
+ end
81
+
82
+ %w[Final-Recipient Original-Recipient].each do |e|
83
+ # Fix the malformed field "Final-Recipient: <kijitora@example.jp>"
84
+ cv = "\n" + e + ": "
85
+ cx = cv + "<"
86
+ p0 = emailparts[0].index(cx); next if p0.nil?
87
+
88
+ emailparts[0] = emailparts[0].sub(": <", ": rfc822; ")
89
+ p1 = emailparts[0].index(">\n", p0 + 2); emailparts[0][p1, 1] = ""
90
+ end
91
+
92
+ bodyslices = emailparts[0].scrub('?').split("\n")
121
93
  while e = bodyslices.shift do
122
94
  # Read error messages and delivery status lines from the head of the email to the previous
123
95
  # line of the beginning of the original message.
124
96
  readslices << e # Save the current line for the next loop
125
- lowercased = e.downcase
126
97
 
127
98
  if readcursor == 0
128
99
  # Beginning of the bounce message or delivery status part
129
- if StartingOf[:message].any? { |a| lowercased.start_with?(a) }
130
- readcursor |= Indicators[:deliverystatus]
131
- next
132
- end
133
- end
100
+ readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
134
101
 
135
- if (readcursor & Indicators[:'message-rfc822']) == 0
136
- # Beginning of the original message part
137
- if StartingOf[:rfc822].any? { |a| lowercased == a }
138
- readcursor |= Indicators[:'message-rfc822']
139
- next
140
- end
141
- end
102
+ while true do
103
+ # Append each string before startingof["message"][0] except the following patterns
104
+ # for the later reference
105
+ break if e.empty? # Blank line
106
+ break if goestonext # Skip if the part is text/html, image/icon, in multipart/*
107
+
108
+ # This line is a boundary kept in "multiparts" as a string, when the end of the boundary
109
+ # appeared, the condition above also returns true.
110
+ if isboundary.any? { |a| e == a } then goestonext = false; break; end
111
+ if e.start_with?("Content-Type:")
112
+ # Content-Type: field in multipart/*
113
+ if e.include?("multipart/")
114
+ # Content-Type: multipart/alternative; boundary=aa00220022222222ffeebb
115
+ # Pick the boundary string and store it into "isboucdary"
116
+ isboundary << Sisimai::RFC2045.boundary(e, 0)
117
+ elsif e.include?("text/plain")
118
+ # Content-Type: "text/plain"
119
+ goestonext = false
120
+ else
121
+ # Other types: for example, text/html, image/jpg, and so on
122
+ goestonext = true
123
+ end
124
+ break
125
+ end
142
126
 
143
- if readcursor & Indicators[:'message-rfc822'] > 0
144
- # Inside of the original message part
145
- if e.empty?
146
- blanklines += 1
147
- break if blanklines > 1
148
- next
127
+ break if e.start_with?("Content-") # Content-Disposition, ...
128
+ break if e.start_with?("This is a MIME") # This is a MIME-formatted message.
129
+ break if e.start_with?("This is a multi") # This is a multipart message in MIME format
130
+ break if e.start_with?("This is an auto") # This is an automatically generated ...
131
+ break if e.start_with?("This multi-part") # This multi-part MIME message contains...
132
+ break if e.start_with?("###") # A frame like #####
133
+ break if e.start_with?("***") # A frame like *****
134
+ break if e.start_with?("--") # Boundary string
135
+ break if e.include?("--- The follow") # ----- The following addresses had delivery problems -----
136
+ break if e.include?("--- Transcript") # ----- Transcript of session follows -----
137
+ beforemesg << e + " "; break
149
138
  end
150
- rfc822text << e << "\n"
151
- else
152
- # Error message part
153
- next unless readcursor & Indicators[:deliverystatus] > 0
154
- next if e.empty?
139
+ next
140
+ end
141
+ next if (readcursor & Indicators[:deliverystatus]) == 0
142
+ next if e.empty?
155
143
 
144
+ f = Sisimai::RFC1894.match(e)
145
+ if f > 0
146
+ # "e" matched with any field defined in RFC3464
147
+ next unless o = Sisimai::RFC1894.field(e)
156
148
  v = dscontents[-1]
157
- if f = Sisimai::RFC1894.match(e)
158
- # "e" matched with any field defined in RFC3464
159
- next unless o = Sisimai::RFC1894.field(e)
160
149
 
161
- if o[-1] == 'addr'
150
+ if o[3] == "addr"
151
+ # Final-Recipient: rfc822; kijitora@example.jp
152
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
153
+ if o[0] == "final-recipient"
162
154
  # Final-Recipient: rfc822; kijitora@example.jp
163
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
164
- if o[0] == 'final-recipient' || o[0] == 'original-recipient'
165
- # Final-Recipient: rfc822; kijitora@example.jp
166
- if o[0] == 'original-recipient'
167
- # Original-Recipient: ...
168
- maybealias = o[2]
169
- else
170
- # Final-Recipient: ...
171
- x = v['recipient'] || ''
172
- y = Sisimai::Address.s3s4(o[2])
173
- y = maybealias unless Sisimai::Address.is_emailaddress(y)
174
-
175
- if !x.empty? && x != y
176
- # There are multiple recipient addresses in the message body.
177
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
178
- v = dscontents[-1]
179
- end
180
- v['recipient'] = y
181
- recipients += 1
182
- itisbounce ||= true
183
-
184
- v['alias'] ||= maybealias
185
- maybealias = nil
186
- end
187
- elsif o[0] == 'x-actual-recipient'
188
- # X-Actual-Recipient: RFC822; |IFS=' ' && exec procmail -f- || exit 75 ...
189
- # X-Actual-Recipient: rfc822; kijitora@neko.example.jp
190
- v['alias'] = o[2] unless o[2].include?(' ')
155
+ # Final-Recipient: x400; /PN=...
156
+ cv = Sisimai::Address.s3s4(o[2]); next unless Sisimai::Address.is_emailaddress(cv)
157
+ cw = dscontents.size; next if cw > 0 && cv == dscontents[cw - 1]["recipient"]
158
+
159
+ if v["recipient"] != ""
160
+ # There are multiple recipient addresses in the message body.
161
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
162
+ v = dscontents[-1]
191
163
  end
192
- elsif o[-1] == 'code'
193
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
194
- v['spec'] = o[1]
195
- v['diagnosis'] = o[2]
164
+ v["recipient"] = cv
165
+ recipients += 1
196
166
  else
197
- # Other DSN fields defined in RFC3464
198
- next unless fieldtable[o[0]]
199
- v[fieldtable[o[0]]] = o[2]
200
-
201
- next unless f
202
- permessage[fieldtable[o[0]]] = o[2]
167
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
168
+ v["alias"] = o[2]
203
169
  end
170
+ elsif o[3] == "code"
171
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
172
+ v["spec"] = o[1]
173
+ v["diagnosis"] = o[2]
204
174
  else
205
- # The line did not match with any fields defined in RFC3464
206
- if e.start_with?('Diagnostic-Code: ') && e.include?(';') == false
207
- # There is no value of "diagnostic-type" such as Diagnostic-Code: 554 ...
208
- v['diagnosis'] = e[e.index(' ') + 1, e.size]
209
-
210
- elsif e.start_with?('Status: ') && Sisimai::SMTP::Reply.find(e[8, 3])
211
- # Status: 553 Exceeded maximum inbound message size
212
- v['alterrors'] = e[8, e.size]
213
-
214
- elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.start_with?(' ')
215
- # Continued line of the value of Diagnostic-Code header
216
- v['diagnosis'] << ' ' << e
217
- readslices[-1] = 'Diagnostic-Code: ' << e
218
- else
219
- # Get error messages which is written in the message body directly
220
- next if e.start_with?(' ', ' ', 'X')
221
- cr = Sisimai::SMTP::Reply.find(e) || ''
222
- ca = Sisimai::Address.find(e) || []
223
- co = Sisimai::String.aligned(e, ['<', '@', '>'])
224
-
225
- if cr.size > 0 || (ca.size > 0 && co)
226
- v['alterrors'] ||= ' '
227
- v['alterrors'] << ' ' << e
228
- end
175
+ # Other DSN fields defined in RFC3464
176
+ if o[4].size > 0
177
+ # There are other error messages as a comment such as the following:
178
+ # Status: 5.0.0 (permanent failure)
179
+ # Status: 4.0.0 (cat.example.net: host name lookup failure)
180
+ v["diagnosis"] << " " + o[4] + " "
229
181
  end
230
- end
231
- end # End of if: rfc822
232
- end
233
-
234
- # -----------------------------------------------------------------------------------------
235
- while true
236
- # Fallback, decode the entire message body
237
- break if recipients > 0
182
+ next unless FieldTable[o[0]]
183
+ next if o[3] == "host" && Sisimai::RFC1123.is_internethost(o[2]) == false
184
+ v[FieldTable[o[0]]] = o[2]
238
185
 
239
- # Failed to get a recipient address at code above
240
- returnpath = (mhead['return-path'] || '').downcase
241
- headerfrom = (mhead['from'] || '').downcase
242
- errortitle = (mhead['subject'] || '').downcase
243
- patternsof = {
244
- 'from' => ['postmaster@', 'mailer-daemon@', 'root@'],
245
- 'return-path' => ['<>', 'mailer-daemon'],
246
- 'subject' => ['delivery fail', 'delivery report', 'failure notice', 'mail delivery',
247
- 'mail failed', 'mail error', 'non-delivery', 'returned mail',
248
- 'undeliverable mail', 'warning: '],
249
- }
250
-
251
- match = nil
252
- match ||= patternsof['from'].any? { |v| headerfrom.include?(v) }
253
- match ||= patternsof['subject'].any? { |v| errortitle.include?(v) }
254
- match ||= patternsof['return-path'].any? { |v| returnpath.include?(v) }
255
- break unless match
256
-
257
- b = dscontents[-1]
258
- hasmatched = 0 # There may be an email address around the line
259
- readslices = [] # Previous line of this loop
260
-
261
- ReadAfter0.each do |e|
262
- # Cut strings from the begining of "mbody" to the strings defined in ReadAfter0
263
- i = mbody.downcase.index(e)
264
- next unless i
265
- mbody = mbody[i, mbody.size - i]
266
- end
267
- lowercased = mbody.downcase
268
- bodyslices = mbody.split("\n")
269
-
270
- while e = bodyslices.shift do
271
- # Get the recipient's email address and error messages.
272
- next if e.empty?
273
- hasmatched = 0
274
- lowercased = e.downcase
275
- readslices << lowercased
276
-
277
- break if StartingOf[:rfc822].include?(lowercased)
278
- break if ReadUntil0.any? { |v| lowercased.start_with?(v) }
279
- break if ReadUntil1.any? { |v| lowercased.include?(v) }
280
- next if DoNotRead0.any? { |v| lowercased.start_with?(v) }
281
- next if DoNotRead1.any? { |v| lowercased.include?(v) }
282
-
283
- while true do
284
- # There is an email address with an error message at this line(1)
285
- break unless ReadEmail0.any? { |v| lowercased.start_with?(v) }
286
- break unless lowercased.include?('@')
287
-
288
- hasmatched = 1
289
- break
186
+ next unless f == 1
187
+ permessage[FieldTable[o[0]]] = o[2]
290
188
  end
291
-
292
- while true do
293
- # There is an email address with an error message at this line(2)
294
- break if hasmatched > 0
295
- break unless ReadEmail1.any? { |v| lowercased.include?(v) }
296
- break unless lowercased.include?('@')
297
-
298
- hasmatched = 2
299
- break
300
- end
301
-
302
- while true do
303
- # There is an email address without an error message at this line
304
- break if hasmatched > 0
305
- break if readslices.size < 2
306
- break unless ReadEmail1.any? { |v| readslices[-2].include?(v) }
307
- break unless lowercased.include?('@') # Must contain '@'
308
- break unless lowercased.include?('.') # Must contain '.'
309
- break if lowercased.include?('$')
310
-
311
- hasmatched = 3
312
- break
313
- end
314
-
315
- if hasmatched > 0 && lowercased.include?('@')
316
- # May be an email address
317
- w = e.split(' ')
318
- x = b['recipient'] || ''
319
- y = ''
320
-
321
- w.each do |ee|
322
- # Find an email address (including "@")
323
- next unless ee.include?('@')
324
- y = Sisimai::Address.s3s4(ee)
325
- next unless Sisimai::Address.is_emailaddress(y)
326
- break
189
+ else
190
+ # Check that the line is a continued line of the value of Diagnostic-Code: field or not
191
+ if e.start_with?("X-") && e.include?(": ")
192
+ # This line is a MTA-Specific fields begins with "X-"
193
+ next unless Sisimai::RFC3464::ThirdParty.is3rdparty(e)
194
+ cv = Sisimai::RFC3464::ThirdParty.xfield(e)
195
+
196
+ if cv.size > 0 && FieldTable[cv[0].downcase] == nil
197
+ # Check the first element is a field defined in RFC1894 or not
198
+ p1 = cv[4].index(":")
199
+ v["reason"] = cv[4][p1 + 1, cv[4].size] if cv[4].start_with?("reason:")
200
+ else
201
+ # Set the value picked from "X-*" field to $dscontents when the current value is empty
202
+ z = FieldTable[cv[0].downcase]; next unless z
203
+ v[z] ||= cv[2]
327
204
  end
328
-
329
- if !x.empty? && x != y
330
- # There are multiple recipient addresses in the message body.
331
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
332
- b = dscontents[-1]
205
+ else
206
+ # The line may be a continued line of the value of the Diagnostic-Code: field
207
+ if readslices[-2].start_with?("Diagnostic-Code:") == false
208
+ # In the case of multiple "message/delivery-status" line
209
+ next if e.start_with?("Content-") # Content-Disposition:, ...
210
+ next if e.start_with?("--") # Boundary string
211
+ beforemesg << e + " "; next
333
212
  end
334
- b['recipient'] = y
335
- recipients += 1
336
- itisbounce ||= true
337
213
 
338
- elsif e.include?('(expanded from') || e.include?('(generated from')
339
- # (expanded from: neko@example.jp)
340
- b['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size])
214
+ # Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
215
+ # has an SPF record with a hard fail
216
+ next unless e.start_with?(" ")
217
+ v["diagnosis"] << " " + Sisimai::String.sweep(e)
341
218
  end
342
- b['diagnosis'] ||= ''
343
- b['diagnosis'] << ' ' << e
344
219
  end
345
-
346
- break
347
220
  end
348
- return nil unless itisbounce
349
221
 
350
- p1 = rfc822text.index("\nTo: ") || -1
351
- p2 = rfc822text.index("\n", p1 + 6) || -1
352
- if recipients == 0 && p1 > 0
353
- # Try to get a recipient address from "To:" header of the original message
354
- if r = Sisimai::Address.find(rfc822text[p1 + 5, p2 - p1 - 5], true)
355
- # Found a recipient address
356
- dscontents << Sisimai::Lhost.DELIVERYSTATUS if dscontents.size == recipients
357
- b = dscontents[-1]
358
- b['recipient'] = r[0][:address]
359
- recipients += 1
360
- end
222
+ while recipients == 0 do
223
+ # There is no valid recipient address, Try to use the alias addaress as a final recipient
224
+ break if dscontents[0]["alias"].nil? || dscontents[0]["alias"].empty?
225
+ break if Sisimai::Address.is_emailaddress(dscontents[0]["alias"]) == false
226
+ dscontents[0]["recipient"] = dscontents[0]["alias"]
227
+ recipients += 1
228
+ end
229
+ return nil if recipients == 0
230
+
231
+ require "sisimai/smtp/reply"
232
+ require "sisimai/smtp/status"
233
+ require "sisimai/smtp/command"
234
+
235
+ if beforemesg != ""
236
+ # Pick some values of $dscontents from the string before StartingOf[:message]
237
+ beforemesg = Sisimai::String.sweep(beforemesg)
238
+ alternates["command"] = Sisimai::SMTP::Command.find(beforemesg)
239
+ alternates["replycode"] = Sisimai::SMTP::Reply.find(beforemesg, dscontents[0]["status"])
240
+ alternates["status"] = Sisimai::SMTP::Status.find(beforemesg, alternates["replycode"])
361
241
  end
362
- return nil unless recipients > 0
242
+ issuedcode = beforemesg.downcase
363
243
 
364
- require 'sisimai/smtp/command'
365
- require 'sisimai/mda'
366
- mdabounced = Sisimai::MDA.inquire(mhead, mbody)
367
244
  dscontents.each do |e|
368
- # Set default values if each value is empty.
369
- connheader.each_key { |a| e[a] ||= connheader[a] || '' }
370
-
371
- if e['alterrors']
372
- # Copy alternative error message
373
- unless e['alterrors'].empty?
374
- e['diagnosis'] ||= e['alterrors']
375
- if e['diagnosis'].start_with?('-') || e['diagnosis'].end_with?('__')
376
- # Override the value of diagnostic code message
377
- e['diagnosis'] = e['alterrors']
378
- end
379
- e.delete('alterrors')
245
+ # Set default values stored in "permessage" if each value in "dscontents" is empty.
246
+ permessage.each_key { |a| e[a] ||= permessage[a] || '' }
247
+ e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"])
248
+ lowercased = e["diagnosis"].downcase
249
+
250
+ if recipients == 1
251
+ # Do not mix the error message of each recipient with "beforemesg" when there is
252
+ # multiple recipient addresses in the bounce message
253
+ if issuedcode.include?(lowercased)
254
+ # beforemesg contains the entire strings of e["diagnosis"]
255
+ e["diagnosis"] = beforemesg
256
+ else
257
+ # The value of e["diagnosis"] is not contained in $beforemesg
258
+ # There may be an important error message in $beforemesg
259
+ e["diagnosis"] = Sisimai::String.sweep(sprintf("%s %s", beforemesg, e["diagnosis"]))
380
260
  end
381
261
  end
382
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
383
-
384
- if mdabounced
385
- # Make bounce data by the values returned from Sisimai::MDA.inquire()
386
- e['agent'] = mdabounced['mda'] || 'RFC3464'
387
- e['reason'] = mdabounced['reason'] || 'undefined'
388
- e['diagnosis'] = mdabounced['message'] unless mdabounced['message'].empty?
389
- e['command'] = nil
390
- end
391
-
392
- e['date'] ||= mhead['date']
393
- e['status'] ||= Sisimai::SMTP::Status.find(e['diagnosis']) || ''
394
- e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
262
+ e["command"] = Sisimai::SMTP::Command.find(e["diagnosis"])
263
+ e["command"] = alternates["command"] if e["command"].empty?
264
+ e["replycode"] = Sisimai::SMTP::Reply.find(e["diagnosis"], e["status"])
265
+ e["replycode"] = alternates["replycode"] if e["replycode"].empty?
266
+ e["status"] = Sisimai::SMTP::Status.find(e["diagnosis"], e["replycode"]) if e["status"].empty?
267
+ e["status"] = alternates["status"] if e["status"].empty?
395
268
  end
396
269
 
397
- return { 'ds' => dscontents, 'rfc822' => rfc822text }
270
+ if emailparts[1].nil? || emailparts[1].empty?
271
+ # Set the recipient address as To: header in the original message part
272
+ emailparts[1] = sprintf("To: <%s>\n", dscontents[0]["recipient"])
273
+ end
274
+ return { "ds" => dscontents, "rfc822" => emailparts[1] }
398
275
  end
399
- def description; 'Fallback Module for MTAs'; end
276
+
277
+ def description; 'RFC3464'; end
400
278
  end
401
279
  end
402
280
  end
281
+
@@ -85,7 +85,7 @@ module Sisimai
85
85
  break
86
86
  end
87
87
 
88
- if v['recipient']
88
+ if v["recipient"] != ""
89
89
  # Clean-up the recipient address
90
90
  v['recipient'] = Sisimai::Address.s3s4(v['recipient'])
91
91
  recipients += 1