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::Qmail parses a bounce email which created by qmail.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Qmail parses a bounce email which created by qmail. Methods in the module are called
3
+ # from only Sisimai::Message.
4
4
  module Qmail
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/qmail.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^--- Below this line is a copy of the message[.]|.freeze
9
+ Boundaries = ['--- Below this line is a copy of the message.'].freeze
11
10
  StartingOf = {
12
11
  # qmail-remote.c:248| if (code >= 500) {
13
12
  # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
@@ -16,45 +15,33 @@ module Sisimai::Lhost
16
15
  #
17
16
  # Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c
18
17
  # K = success, Z = temporary error, D = permanent error
19
- message: ['Hi. This is the qmail'],
20
18
  error: ['Remote host said:'],
19
+ message: ['Hi. This is the qmail'],
20
+ rhost: ['Giving up on ', 'Connected to ', 'remote host '],
21
21
  }.freeze
22
-
23
- ReSMTP = {
22
+ CommandSet = {
24
23
  # Error text regular expressions which defined in qmail-remote.c
25
24
  # qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
26
- 'conn' => %r/(?:Error:)?Connected to [^ ]+ but greeting failed[.]/,
25
+ 'conn' => [' but greeting failed.'],
27
26
  # qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
28
- 'ehlo' => %r/(?:Error:)?Connected to [^ ]+ but my name was rejected[.]/,
27
+ 'ehlo' => [' but my name was rejected.'],
29
28
  # qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected");
30
29
  # reason = rejected
31
- 'mail' => %r/(?:Error:)?Connected to [^ ]+ but sender was rejected[.]/,
30
+ 'mail' => [' but sender was rejected.'],
32
31
  # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
33
32
  # qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n");
34
33
  # reason = userunknown
35
- 'rcpt' => %r/(?:Error:)?[^ ]+ does not like recipient[.]/,
34
+ 'rcpt' => [' does not like recipient.'],
36
35
  # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command");
37
36
  # qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command");
38
37
  # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message");
39
38
  # qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message");
40
- 'data' => %r{(?:
41
- (?:Error:)?[^ ]+[ ]failed[ ]on[ ]DATA[ ]command[.]
42
- |(?:Error:)?[^ ]+[ ]failed[ ]after[ ]I[ ]sent[ ]the[ ]message[.]
43
- )
44
- }x,
39
+ 'data' => [' failed on DATA command', ' failed after I sent the message'],
45
40
  }.freeze
46
- # qmail-remote.c:261| if (!flagbother) quit("DGiving up on ","");
47
- ReHost = %r{(?:
48
- Giving[ ]up[ ]on[ ]([^ ]+[0-9a-zA-Z])[.]?\z
49
- |Connected[ ]to[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]
50
- |remote[ ]host[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]said:
51
- )
52
- }x
53
41
 
54
42
  # qmail-send.c:922| ... (&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
55
43
  HasExpired = 'this message has been in the queue too long.'
56
- ReCommands = %r/Sorry, no SMTP connection got far enough; most progress was ([A-Z]{4}) /
57
- ReIsOnHold = %r/\A[^ ]+ does not like recipient[.][ ]+.+this message has been in the queue too long[.]\z/
44
+ OnHoldPair = [' does not like recipient.', 'this message has been in the queue too long.'].freeze
58
45
  FailOnLDAP = {
59
46
  # qmail-ldap-1.03-20040101.patch:19817 - 19866
60
47
  'suspend' => ['Mailaddress is administrative?le?y disabled'], # 5.2.1
@@ -104,27 +91,31 @@ module Sisimai::Lhost
104
91
  # @param [String] mbody Message body of a bounce email
105
92
  # @return [Hash] Bounce data list and message/rfc822 part
106
93
  # @return [Nil] it failed to parse or the arguments are missing
107
- def make(mhead, mbody)
94
+ def inquire(mhead, mbody)
108
95
  # Pre process email headers and the body part of the message which generated
109
96
  # by qmail, see https://cr.yp.to/qmail.html
110
97
  # e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000
111
98
  # Subject: failure notice
112
- tryto = /\A[(]qmail[ ]+\d+[ ]+invoked[ ]+(?:for[ ]+bounce|from[ ]+network)[)]/
99
+ tryto = [['(qmail ', 'invoked for bounce)'], ['(qmail ', 'invoked from ', 'network)']]
113
100
  match = 0
114
101
  match += 1 if mhead['subject'] == 'failure notice'
115
- match += 1 if mhead['received'].any? { |a| a =~ tryto }
102
+ mhead['received'].each do |e|
103
+ # Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900
104
+ # Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900
105
+ match += 1 if tryto.any? { |a| Sisimai::String.aligned(e, a) }
106
+ end
116
107
  return nil unless match > 0
117
108
 
118
109
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
119
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
120
- bodyslices = emailsteak[0].split("\n")
110
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
111
+ bodyslices = emailparts[0].split("\n")
121
112
  readcursor = 0 # (Integer) Points the current cursor position
122
113
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
123
114
  v = nil
124
115
 
125
116
  while e = bodyslices.shift do
126
- # Read error messages and delivery status lines from the head of the email
127
- # to the previous line of the beginning of the original message.
117
+ # Read error messages and delivery status lines from the head of the email to the previous
118
+ # line of the beginning of the original message.
128
119
  if readcursor == 0
129
120
  # Beginning of the bounce message or delivery status part
130
121
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -139,14 +130,14 @@ module Sisimai::Lhost
139
130
  # Giving up on 192.0.2.153.
140
131
  v = dscontents[-1]
141
132
 
142
- if cv = e.match(/\A(?:To[ ]*:)?[<](.+[@].+)[>]:[ \t]*\z/)
133
+ if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':'])
143
134
  # <kijitora@example.jp>:
144
135
  if v['recipient']
145
136
  # There are multiple recipient addresses in the message body.
146
137
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
147
138
  v = dscontents[-1]
148
139
  end
149
- v['recipient'] = cv[1]
140
+ v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size])
150
141
  recipients += 1
151
142
 
152
143
  elsif dscontents.size == recipients
@@ -157,8 +148,15 @@ module Sisimai::Lhost
157
148
  v['alterrors'] = e if e.start_with?(StartingOf[:error][0])
158
149
 
159
150
  next if v['rhost']
160
- next unless cv = e.match(ReHost)
161
- v['rhost'] = cv[1]
151
+ StartingOf[:rhost].each do |r|
152
+ # Find a remote host name
153
+ p1 = e.index(r); next unless p1
154
+ cm = r.size
155
+ p2 = e.index(' ', p1 + cm + 1) || p2 = e.rindex('.')
156
+
157
+ v['rhost'] = e[p1 + cm, p2 - p1 - cm]
158
+ break
159
+ end
162
160
  end
163
161
  end
164
162
  return nil unless recipients > 0
@@ -169,17 +167,16 @@ module Sisimai::Lhost
169
167
 
170
168
  unless e['command']
171
169
  # Get the SMTP command name for the session
172
- ReSMTP.each_key do |r|
170
+ CommandSet.each_key do |r|
173
171
  # Verify each regular expression of SMTP commands
174
- next unless e['diagnosis'] =~ ReSMTP[r]
172
+ next unless CommandSet[r].any? { |a| e['diagnosis'].include?(a) }
175
173
  e['command'] = r.upcase
176
174
  break
177
175
  end
178
176
 
179
- unless e['command']
180
- # Verify each regular expression of patches
181
- if cv = e['diagnosis'].match(ReCommands) then e['command'] = cv[1].upcase end
182
- e['command'] ||= ''
177
+ if e['diagnosis'].include?('Sorry, no SMTP connection got far enough; most progress was ')
178
+ # Get the last SMTP command:from the error message
179
+ e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis']) || ''
183
180
  end
184
181
  end
185
182
 
@@ -193,9 +190,8 @@ module Sisimai::Lhost
193
190
  e['reason'] = 'blocked'
194
191
  else
195
192
  # Try to match with each error message in the table
196
- if e['diagnosis'] =~ ReIsOnHold
197
- # To decide the reason require pattern match with
198
- # Sisimai::Reason::* modules
193
+ if Sisimai::String.aligned(e['diagnosis'], OnHoldPair)
194
+ # To decide the reason require pattern match with Sisimai::Reason::* modules
199
195
  e['reason'] = 'onhold'
200
196
  else
201
197
  MessagesOf.each_key do |r|
@@ -227,10 +223,11 @@ module Sisimai::Lhost
227
223
  end
228
224
  end
229
225
 
230
- e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || ''
226
+ e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
227
+ e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || ''
231
228
  end
232
229
 
233
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
230
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
234
231
  end
235
232
  def description; return 'qmail'; end
236
233
  end
@@ -1,15 +1,13 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::ReceivingSES parses a bounce email which created
3
- # by Amazon Simple Email Service. Methods in the module are called from
4
- # only Sisimai::Message.
2
+ # Sisimai::Lhost::ReceivingSES parses a bounce email which created by Amazon Simple Email Service.
3
+ # Methods in the module are called from only Sisimai::Message.
5
4
  module ReceivingSES
6
5
  class << self
7
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/ReceivingSES.pm
8
6
  require 'sisimai/lhost'
9
7
 
10
8
  # https://aws.amazon.com/ses/
11
9
  Indicators = Sisimai::Lhost.INDICATORS
12
- ReBackbone = %r|^content-type:[ ]text/rfc822-headers|.freeze
10
+ Boundaries = ['Content-Type: text/rfc822-headers'].freeze
13
11
  StartingOf = { message: ['This message could not be delivered.'] }.freeze
14
12
  MessagesOf = {
15
13
  # The followings are error messages in Rule sets/*/Actions/Template
@@ -24,26 +22,25 @@ module Sisimai::Lhost
24
22
  # @param [String] mbody Message body of a bounce email
25
23
  # @return [Hash] Bounce data list and message/rfc822 part
26
24
  # @return [Nil] it failed to parse or the arguments are missing
27
- def make(mhead, mbody)
25
+ def inquire(mhead, mbody)
28
26
  # X-SES-Outgoing: 2015.10.01-54.240.27.7
29
27
  # Feedback-ID: 1.us-west-2.HX6/J9OVlHTadQhEu1+wdF9DBj6n6Pa9sW5Y/0pSOi8=:AmazonSES
30
28
  return nil unless mhead['x-ses-outgoing']
31
29
 
32
- require 'sisimai/rfc1894'
33
30
  fieldtable = Sisimai::RFC1894.FIELDTABLE
34
31
  permessage = {} # (Hash) Store values of each Per-Message field
35
32
 
36
33
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
37
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
38
- bodyslices = emailsteak[0].split("\n")
34
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
35
+ bodyslices = emailparts[0].split("\n")
39
36
  readslices = ['']
40
37
  readcursor = 0 # (Integer) Points the current cursor position
41
38
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
42
39
  v = nil
43
40
 
44
41
  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.
42
+ # Read error messages and delivery status lines from the head of the email to the previous
43
+ # line of the beginning of the original message.
47
44
  readslices << e # Save the current line for the next loop
48
45
 
49
46
  if readcursor == 0
@@ -84,14 +81,14 @@ module Sisimai::Lhost
84
81
  next unless fieldtable[o[0]]
85
82
  v[fieldtable[o[0]]] = o[2]
86
83
 
87
- next unless f == 1
84
+ next unless f
88
85
  permessage[fieldtable[o[0]]] = o[2]
89
86
  end
90
87
  else
91
88
  # Continued line of the value of Diagnostic-Code field
92
89
  next unless readslices[-2].start_with?('Diagnostic-Code:')
93
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
94
- v['diagnosis'] << ' ' << cv[1]
90
+ next unless e.start_with?(' ')
91
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
95
92
  readslices[-1] = 'Diagnostic-Code: ' << e
96
93
  end
97
94
  end
@@ -106,11 +103,9 @@ module Sisimai::Lhost
106
103
  if e['status'].to_s.start_with?('5.0.0', '5.1.0', '4.0.0', '4.1.0')
107
104
  # Get other D.S.N. value from the error message
108
105
  errormessage = e['diagnosis']
109
-
110
- if cv = e['diagnosis'].match(/["'](\d[.]\d[.]\d.+)['"]/)
111
- # 5.1.0 - Unknown address error 550-'5.7.1 ...
112
- errormessage = cv[1]
113
- end
106
+ p1 = e['diagnosis'].index("-'"); p1 = e['diagnosis'].index('-"') unless p1
107
+ p2 = e['diagnosis'].rindex("' "); p2 = e['diagnosis'].rindex('" ') unless p2
108
+ errormessage = e['diagnosis'][p1 + 2, p2 - p1 - 2] if p1 && p2
114
109
  e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status']
115
110
  end
116
111
 
@@ -123,7 +118,7 @@ module Sisimai::Lhost
123
118
  e['reason'] ||= Sisimai::SMTP::Status.name(e['status']) || ''
124
119
  end
125
120
 
126
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
121
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
127
122
  end
128
123
  def description; return 'Amazon SES(Receiving): https://aws.amazon.com/ses/'; end
129
124
  end
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::SendGrid parses a bounce email which created by
3
- # SendGrid. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::SendGrid parses a bounce email which created by SendGrid. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module SendGrid
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/SendGrid.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^Content-Type:[ ]message/rfc822|.freeze
9
+ Boundaries = ['Content-Type: message/rfc822'].freeze
11
10
  StartingOf = { message: ['This is an automatically generated message from SendGrid.'] }.freeze
12
11
 
13
12
  # Parse bounce messages from SendGrid
@@ -15,29 +14,29 @@ module Sisimai::Lhost
15
14
  # @param [String] mbody Message body of a bounce email
16
15
  # @return [Hash] Bounce data list and message/rfc822 part
17
16
  # @return [Nil] it failed to parse or the arguments are missing
18
- def make(mhead, mbody)
17
+ def inquire(mhead, mbody)
19
18
  # Return-Path: <apps@sendgrid.net>
20
19
  # X-Mailer: MIME-tools 5.502 (Entity 5.502)
21
20
  return nil unless mhead['return-path']
22
21
  return nil unless mhead['return-path'] == '<apps@sendgrid.net>'
23
22
  return nil unless mhead['subject'] == 'Undelivered Mail Returned to Sender'
24
23
 
25
- require 'sisimai/rfc1894'
24
+ require 'sisimai/smtp/command'
26
25
  fieldtable = Sisimai::RFC1894.FIELDTABLE
27
26
  permessage = {} # (Hash) Store values of each Per-Message field
28
27
 
29
28
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
30
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
31
- bodyslices = emailsteak[0].split("\n")
29
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
30
+ bodyslices = emailparts[0].split("\n")
32
31
  readslices = ['']
33
32
  readcursor = 0 # (Integer) Points the current cursor position
34
33
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
35
- commandtxt = '' # (String) SMTP Command name begin with the string '>>>'
34
+ thecommand = '' # (String) SMTP Command name begin with the string '>>>'
36
35
  v = nil
37
36
 
38
37
  while e = bodyslices.shift do
39
- # Read error messages and delivery status lines from the head of the email
40
- # to the previous line of the beginning of the original message.
38
+ # Read error messages and delivery status lines from the head of the email to the previous
39
+ # line of the beginning of the original message.
41
40
  readslices << e # Save the current line for the next loop
42
41
 
43
42
  if readcursor == 0
@@ -57,8 +56,7 @@ module Sisimai::Lhost
57
56
  # Fallback code for empty value or invalid formatted value
58
57
  # - Status: (empty)
59
58
  # - Diagnostic-Code: 550 5.1.1 ... (No "diagnostic-type" sub field)
60
- next unless cv = e.match(/\ADiagnostic-Code:[ ]*(.+)/)
61
- v['diagnosis'] = cv[1]
59
+ v['diagnosis'] = e[e.index(':') + 2, e.size] if e.start_with?('Diagnostic-Code: ')
62
60
  next
63
61
  end
64
62
 
@@ -84,29 +82,37 @@ module Sisimai::Lhost
84
82
  v['diagnosis'] = o[2]
85
83
  elsif o[-1] == 'date'
86
84
  # Arrival-Date: 2012-12-31 23-59-59
87
- next unless cv = e.match(/\AArrival-Date: (\d{4})[-](\d{2})[-](\d{2}) (\d{2})[-](\d{2})[-](\d{2})\z/)
88
- o[1] << 'Thu, ' << cv[3] + ' '
89
- o[1] << Sisimai::DateTime.monthname(false)[cv[2].to_i - 1]
90
- o[1] << ' ' << cv[1] + ' ' << [cv[4], cv[5], cv[6]].join(':')
85
+ next unless e.start_with?('Arrival-Date: ')
86
+ cf = e[e.index(': ') + 2, e.size].split(' '); next unless cf.size == 2
87
+ cw = cf[0].split('-'); next unless cw.size == 3
88
+ ce = cf[1].split('-'); next unless ce.size == 3
89
+
90
+ o[1] << 'Thu, ' << cw[2] + ' '
91
+ o[1] << Sisimai::DateTime.monthname(false)[cw[1].to_i - 1]
92
+ o[1] << ' ' << cw[0] + ' ' << ce.join(':')
91
93
  o[1] << ' ' << Sisimai::DateTime.abbr2tz('CDT')
92
94
  else
93
95
  # Other DSN fields defined in RFC3464
94
96
  next unless fieldtable[o[0]]
95
97
  v[fieldtable[o[0]]] = o[2]
96
98
 
97
- next unless f == 1
99
+ next unless f
98
100
  permessage[fieldtable[o[0]]] = o[2]
99
101
  end
100
102
  else
101
103
  # The line does not begin with a DSN field defined in RFC3464
102
- if cv = e.match(/.+ in (?:End of )?([A-Z]{4}).*\z/)
104
+ if cv = Sisimai::SMTP::Command.find(e)
103
105
  # in RCPT TO, in MAIL FROM, end of DATA
104
- commandtxt = cv[1]
106
+ thecommand = cv
107
+ elsif e.start_with?('Diagnostic-Code: ')
108
+ # Diagnostic-Code: 550 5.1.1 <kijitora@example.jp>... User Unknown
109
+ v['diagnosis'] = e[e.index(':') + 2, e.size]
105
110
  else
106
111
  # Continued line of the value of Diagnostic-Code field
107
112
  next unless readslices[-2].start_with?('Diagnostic-Code:')
108
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
109
- v['diagnosis'] << ' ' << cv[1]
113
+ next unless e.start_with?(' ')
114
+ v['diagnosis'] ||= ''
115
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
110
116
  readslices[-1] = 'Diagnostic-Code: ' << e
111
117
  end
112
118
  end
@@ -116,14 +122,11 @@ module Sisimai::Lhost
116
122
  dscontents.each do |e|
117
123
  # Get the value of SMTP status code as a pseudo D.S.N.
118
124
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
119
- if cv = e['diagnosis'].match(/\b([45])\d\d[ \t]*/)
120
- # 4xx or 5xx
121
- e['status'] = cv[1] + '.0.0'
122
- end
125
+ e['replycode'] = Sisimai::SMTP::Reply.find(e['diagnosis']) || ''
126
+ e['status'] = e['replycode'][0, 1] + '.0.0' if e['replycode'].size == 3
123
127
 
124
128
  if e['status'] == '5.0.0' || e['status'] == '4.0.0'
125
- # Get the value of D.S.N. from the error message or the value of
126
- # Diagnostic-Code header.
129
+ # Get the value of D.S.N. from the error message or the value of Diagnostic-Code header.
127
130
  e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || e['status']
128
131
  end
129
132
 
@@ -131,17 +134,16 @@ module Sisimai::Lhost
131
134
  # Action: expired
132
135
  e['reason'] = 'expired'
133
136
  if !e['status'] || e['status'].end_with?('.0.0')
134
- # Set pseudo Status code value if the value of Status is not
135
- # defined or 4.0.0 or 5.0.0.
137
+ # Set pseudo Status code value if the value of Status is not defined or 4.0.0 or 5.0.0.
136
138
  e['status'] = Sisimai::SMTP::Status.code('expired') || e['status']
137
139
  end
138
140
  end
139
141
 
140
142
  e['lhost'] ||= permessage['rhost']
141
- e['command'] = commandtxt
143
+ e['command'] = thecommand
142
144
  end
143
145
 
144
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
146
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
145
147
  end
146
148
  def description; return 'SendGrid: https://sendgrid.com/'; end
147
149
  end
@@ -1,13 +1,15 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Sendmail parses a bounce email which created by
3
- # v8 Sendmail. Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Sendmail parses a bounce email which created by v8 Sendmail. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module Sendmail
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/Sendmail.pm
7
6
  require 'sisimai/lhost'
7
+ require 'sisimai/smtp/reply'
8
+ require 'sisimai/smtp/status'
9
+ require 'sisimai/smtp/command'
8
10
 
9
11
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r<^Content-Type:[ ](?:message/rfc822|text/rfc822-headers)>.freeze
12
+ Boundaries = ['Content-Type: message/rfc822', 'Content-Type: text/rfc822-headers'].freeze
11
13
  StartingOf = {
12
14
  # Error text regular expressions which defined in sendmail/savemail.c
13
15
  # savemail.c:1040|if (printheader && !putline(" ----- Transcript of session follows -----\n",
@@ -26,29 +28,30 @@ module Sisimai::Lhost
26
28
  # @param [String] mbody Message body of a bounce email
27
29
  # @return [Hash] Bounce data list and message/rfc822 part
28
30
  # @return [Nil] it failed to parse or the arguments are missing
29
- def make(mhead, mbody)
30
- return nil unless mhead['subject'] =~ /(?:see transcript for details\z|\AWarning: )/
31
+ def inquire(mhead, mbody)
31
32
  return nil if mhead['x-aol-ip']
33
+ match = nil
34
+ match ||= true if mhead['subject'].end_with?('see transcript for details')
35
+ match ||= true if mhead['subject'].start_with?('Warning: ')
36
+ return nil unless match
32
37
 
33
- require 'sisimai/rfc1894'
34
38
  fieldtable = Sisimai::RFC1894.FIELDTABLE
35
39
  permessage = {} # (Hash) Store values of each Per-Message field
36
-
37
40
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
38
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
39
- bodyslices = emailsteak[0].split("\n")
41
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
42
+ bodyslices = emailparts[0].split("\n")
40
43
  readslices = ['']
41
44
  readcursor = 0 # (Integer) Points the current cursor position
42
45
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
43
- commandtxt = '' # (String) SMTP Command name begin with the string '>>>'
46
+ thecommand = '' # (String) SMTP Command name begin with the string '>>>'
44
47
  esmtpreply = [] # (Array) Reply from remote server on SMTP session
45
48
  sessionerr = false # (Boolean) Flag, "true" if it is SMTP session error
46
49
  anotherset = {} # Another error information
47
50
  v = nil
48
51
 
49
52
  while e = bodyslices.shift do
50
- # Read error messages and delivery status lines from the head of the email
51
- # to the previous line of the beginning of the original message.
53
+ # Read error messages and delivery status lines from the head of the email to the previous
54
+ # line of the beginning of the original message.
52
55
  readslices << e # Save the current line for the next loop
53
56
 
54
57
  if readcursor == 0
@@ -89,7 +92,7 @@ module Sisimai::Lhost
89
92
  next unless fieldtable[o[0]]
90
93
  v[fieldtable[o[0]]] = o[2]
91
94
 
92
- next unless f == 1
95
+ next unless f
93
96
  permessage[fieldtable[o[0]]] = o[2]
94
97
  end
95
98
  else
@@ -105,13 +108,14 @@ module Sisimai::Lhost
105
108
  # Received-From-MTA: DNS; x1x2x3x4.dhcp.example.ne.jp
106
109
  # Arrival-Date: Wed, 29 Apr 2009 16:03:18 +0900
107
110
  unless e.start_with?(' ')
108
- if cv = e.match(/\A[>]{3}[ ]+([A-Z]{4})[ ]?/)
111
+ if e.start_with?('>>> ')
109
112
  # >>> DATA
110
- commandtxt = cv[1]
113
+ thecommand = Sisimai::SMTP::Command.find(e)
111
114
 
112
- elsif cv = e.match(/\A[<]{3}[ ]+(.+)\z/)
115
+ elsif e.start_with?('<<< ')
113
116
  # <<< Response
114
- esmtpreply << cv[1] unless esmtpreply.index(cv[1])
117
+ cv = e[4, e.size - 4]
118
+ esmtpreply << cv unless esmtpreply.index(cv)
115
119
  else
116
120
  # Detect SMTP session error or connection error
117
121
  next if sessionerr
@@ -122,21 +126,23 @@ module Sisimai::Lhost
122
126
  next
123
127
  end
124
128
 
125
- if cv = e.match(/\A[<](.+)[>][.]+ (.+)\z/)
129
+ if e.start_with?('<') && Sisimai::String.aligned(e, ['@', '>.', ' '])
126
130
  # <kijitora@example.co.jp>... Deferred: Name server: example.co.jp.: host name lookup failure
127
- anotherset['recipient'] = cv[1]
128
- anotherset['diagnosis'] = cv[2]
131
+ anotherset['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>')])
132
+ anotherset['diagnosis'] = e[e.index(' ') + 1, e.size]
129
133
  else
130
134
  # ----- Transcript of session follows -----
131
135
  # Message could not be delivered for too long
132
136
  # Message will be deleted from queue
133
- next if e =~ /\A[ \t]*[-]+/
134
- if cv = e.match(/\A[45]\d\d[ \t]([45][.]\d[.]\d)[ \t].+/)
137
+ cr = Sisimai::SMTP::Reply.find(e) || ''
138
+ cs = Sisimai::SMTP::Status.find(e) || ''
139
+
140
+ if cr.size + cs.size > 7
135
141
  # 550 5.1.2 <kijitora@example.org>... Message
136
142
  #
137
143
  # DBI connect('dbname=...')
138
144
  # 554 5.3.0 unknown mailer error 255
139
- anotherset['status'] = cv[1]
145
+ anotherset['status'] = cs
140
146
  anotherset['diagnosis'] ||= ''
141
147
  anotherset['diagnosis'] << ' ' << e
142
148
 
@@ -151,8 +157,8 @@ module Sisimai::Lhost
151
157
  else
152
158
  # Continued line of the value of Diagnostic-Code field
153
159
  next unless readslices[-2].start_with?('Diagnostic-Code:')
154
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
155
- v['diagnosis'] << ' ' << cv[1]
160
+ next unless e.start_with?(' ')
161
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
156
162
  readslices[-1] = 'Diagnostic-Code: ' << e
157
163
  end
158
164
  end
@@ -161,47 +167,54 @@ module Sisimai::Lhost
161
167
 
162
168
  dscontents.each do |e|
163
169
  # Set default values if each value is empty.
164
- e['lhost'] ||= permessage['rhost']
170
+ e['diagnosis'] ||= ''
171
+ e['lhost'] ||= permessage['rhost']
165
172
  permessage.each_key { |a| e[a] ||= permessage[a] || '' }
166
173
 
167
- e['command'] ||= commandtxt
168
- if e['command'].empty?
169
- e['command'] = 'EHLO' unless esmtpreply.empty?
170
- end
171
-
172
174
  if anotherset['diagnosis']
173
175
  # Copy alternative error message
174
- e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'] =~ /\A[ \t]+\z/
175
- e['diagnosis'] = anotherset['diagnosis'] unless e['diagnosis']
176
- e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'] =~ /\A\d+\z/
176
+ e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].start_with?(' ')
177
+ e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].=~ /\A\d+\z/
178
+ e['diagnosis'] = anotherset['diagnosis'] if e['diagnosis'].empty?
177
179
  end
178
- unless esmtpreply.empty?
179
- # Replace the error message in "diagnosis" with the ESMTP Reply
180
- r = esmtpreply.join(' ')
181
- e['diagnosis'] = r if r.size > e['diagnosis'].to_s.size
180
+
181
+ while true
182
+ # Replace or append the error message in "diagnosis" with the ESMTP Reply Code when the
183
+ # following conditions have matched
184
+ break if esmtpreply.empty?
185
+ break if recipients != 1
186
+
187
+ e['diagnosis'] = sprintf("%s %s", esmtpreply.join(' '), e['diagnosis'])
188
+ break
182
189
  end
183
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
184
190
 
185
- if anotherset['status']
186
- # Check alternative status code
187
- if e['status'].empty? || e['status'] !~ /\A[45][.]\d[.]\d{1,3}\z/
188
- # Override alternative status code
189
- e['status'] = anotherset['status']
190
- end
191
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
192
+ e['command'] ||= thecommand || Sisimai::SMTP::Command.find(e['diagnosis']) || ''
193
+ if e['command'].empty?
194
+ e['command'] = 'EHLO' unless esmtpreply.empty?
191
195
  end
192
196
 
193
- unless e['recipient'] =~ /\A[^ ]+[@][^ ]+\z/
194
- # @example.jp, no local part
195
- if cv = e['diagnosis'].match(/[<]([^ ]+[@][^ ]+)[>]/)
196
- # Get email address from the value of Diagnostic-Code header
197
- e['recipient'] = cv[1]
198
- end
197
+ while true
198
+ # Check alternative status code and override it
199
+ break unless anotherset.has_key?('status')
200
+ break unless anotherset['status'].size > 0
201
+ break if Sisimai::SMTP::Status.test(e['status'])
202
+
203
+ e['status'] = anotherset['status']
204
+ break
199
205
  end
206
+
207
+ # @example.jp, no local part
208
+ # # Get email address from the value of Diagnostic-Code field
209
+ next unless e['recipient'].start_with?('@')
210
+ cv = Sisimai::Address.find(e['diagnosis'], true) || []
211
+ e['recipient'] = cv[0][:address] if cv.size > 0
200
212
  end
201
213
 
202
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
214
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
203
215
  end
204
216
  def description; return 'V8Sendmail: /usr/sbin/sendmail'; end
205
217
  end
206
218
  end
207
219
  end
220
+